false, 'message' => 'playX angpow_import not configured', ]; } $baseUrl = rtrim(strval($conf['base_url'] ?? ''), '/'); $path = strval($conf['path'] ?? ''); $merchantCode = strval($conf['merchant_code'] ?? ''); $authKey = strval($conf['auth_key'] ?? ''); if ($baseUrl === '' || $path === '' || $merchantCode === '' || $authKey === '') { return [ 'ok' => false, 'message' => 'playX Angpow Import API not configured', ]; } $url = $baseUrl . $path; $asset = MallUserAsset::where('playx_user_id', $order->user_id)->find(); if (!$asset || !is_string($asset->playx_user_id ?? null) || strval($asset->playx_user_id) === '') { return [ 'ok' => false, 'message' => 'User asset not found', ]; } $memberLogin = trim(strval($asset->username ?? '')); if ($memberLogin === '') { return [ 'ok' => false, 'message' => 'User username empty', ]; } $item = MallItem::where('id', $order->mall_item_id)->find(); if (!$item) { return [ 'ok' => false, 'message' => 'Item not found', ]; } $reportDate = strval(time()); $signatureInput = 'merchant_code=' . $merchantCode . '&report_date=' . $reportDate; $signature = self::buildSignature($signatureInput, $authKey); if ($signature === null) { return [ 'ok' => false, 'message' => 'Build signature failed', ]; } $start = gmdate('Y-m-d\TH:i:s\Z', strtotime(strval($order->start_time))); $end = gmdate('Y-m-d\TH:i:s\Z', strtotime(strval($order->end_time))); $multiplier = intval($order->multiplier ?? 0); if ($multiplier <= 0) { $multiplier = 1; } $payload = [ 'merchant_code' => $merchantCode, 'report_date' => $reportDate, 'angpow' => [ [ 'member_login' => $memberLogin, 'start_time' => $start, 'end_time' => $end, 'amount' => $order->amount, 'reward_name' => strval($item->title ?? ''), 'description' => strval($item->description ?? ''), 'member_inbox_message' => 'Congratulations! You received an angpow.', 'category' => strval($item->category ?? ''), 'category_title' => strval($item->category_title ?? ''), 'one_time_turnover' => 'yes', 'multiplier' => $multiplier, ], ], 'currency_visual' => [ [ 'currency' => strval($conf['currency'] ?? 'MYR'), 'visual_name' => strval($conf['visual_name'] ?? 'Angpow'), ], ], ]; $client = new Client([ 'timeout' => 20, 'http_errors' => false, ]); try { $res = $client->post($url, [ 'headers' => [ 'Content-Type' => 'application/json', 'X-Request-Signature' => $signature, ], 'json' => $payload, ]); $httpStatus = 0; if (is_object($res) && method_exists($res, 'getStatusCode')) { $sc = $res->getStatusCode(); if (is_int($sc)) { $httpStatus = $sc; } } $rawBody = strval($res->getBody()); $data = json_decode($rawBody, true); if (!is_array($data)) { $jsonDetail = 'root not JSON object'; if (json_last_error() !== JSON_ERROR_NONE) { $jem = json_last_error_msg(); $jsonDetail = is_string($jem) && $jem !== '' ? $jem : 'JSON parse error'; } Log::error( '[MallBonusGrantPush] response not a JSON object | order_id=' . $order->id . ' | http=' . $httpStatus . ' | ' . $jsonDetail . ' | body=' . self::truncateForLog($rawBody) ); return [ 'ok' => false, 'message' => 'Invalid response', ]; } if (($data['code'] ?? null) === '0' || ($data['code'] ?? null) === 0) { return [ 'ok' => true, 'message' => '', ]; } $remoteMsg = $data['message'] ?? $data['msg'] ?? ''; if (!is_string($remoteMsg)) { $remoteMsg = ''; } if ($remoteMsg === '') { $remoteMsg = 'playX angpow import not accepted'; } $flags = JSON_UNESCAPED_UNICODE; if (defined('JSON_INVALID_UTF8_SUBSTITUTE')) { $flags = $flags | JSON_INVALID_UTF8_SUBSTITUTE; } $encoded = json_encode($data, $flags); if (!is_string($encoded)) { $encoded = $rawBody; } Log::error( '[MallBonusGrantPush] angpow import rejected | order_id=' . $order->id . ' | http=' . $httpStatus . ' | parsed=' . self::truncateForLog($encoded) ); return [ 'ok' => false, 'message' => $remoteMsg, ]; } catch (\Throwable $e) { Log::error('[MallBonusGrantPush] request exception | order_id=' . $order->id . ' | ' . $e->getMessage()); return [ 'ok' => false, 'message' => $e->getMessage(), ]; } } private static function truncateForLog(string $text, int $maxLen = 8000): string { $s = trim($text); if ($s === '') { return ''; } $oneLine = preg_replace('/\s+/', ' ', $s); $snippet = is_string($oneLine) && $oneLine !== '' ? $oneLine : $s; if (function_exists('mb_strlen') && function_exists('mb_substr')) { if (mb_strlen($snippet, 'UTF-8') > $maxLen) { return mb_substr($snippet, 0, $maxLen, 'UTF-8') . '…'; } return $snippet; } if (strlen($snippet) > $maxLen) { return substr($snippet, 0, $maxLen) . '…'; } return $snippet; } private static function buildSignature(string $input, string $authKey): ?string { $keyBytes = null; $maybeBase64 = base64_decode($authKey, true); if ($maybeBase64 !== false && $maybeBase64 !== '') { $keyBytes = $maybeBase64; } if ($keyBytes === null) { $isHex = ctype_xdigit($authKey) && (strlen($authKey) % 2 === 0); if ($isHex) { $hex = hex2bin($authKey); if ($hex !== false && $hex !== '') { $keyBytes = $hex; } } } if ($keyBytes === null) { $keyBytes = $authKey; } $raw = hash_hmac('sha1', $input, $keyBytes, true); if (!is_string($raw) || $raw === '') { return null; } return base64_encode($raw); } }