" * * 可选环境变量(未设置则从项目根 .env 读取): * PLAYX_TOKEN_VERIFY_URL 默认 https://callback-mallsys.superior3.net/callback/api/mallsys/plx/auth/verify-token * PLAYX_ANGPOW_MERCHANT_CODE 回调 Body merchant_code(必填) * PLAYX_ANGPOW_IMPORT_AUTH_KEY 与 angpow-imports 相同的 HMAC 密钥 */ $root = dirname(__DIR__); function loadDotEnv(string $path): void { if (!is_file($path)) { return; } foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) { $line = trim($line); if ($line === '' || str_starts_with($line, '#')) { continue; } if (!str_contains($line, '=')) { continue; } $pos = strpos($line, '='); $name = trim(substr($line, 0, $pos)); $value = trim(substr($line, $pos + 1)); if ($name !== '') { $_ENV[$name] = $value; putenv($name . '=' . $value); } } } function resolveKeyBytes(string $authKey): string { $maybeBase64 = base64_decode($authKey, true); if (is_string($maybeBase64) && $maybeBase64 !== '') { return $maybeBase64; } $isHex = ctype_xdigit($authKey) && (strlen($authKey) % 2 === 0); if ($isHex) { $hex = hex2bin($authKey); if (is_string($hex) && $hex !== '') { return $hex; } } return $authKey; } function buildSignature(string $input, string $authKey): string { $raw = hash_hmac('sha1', $input, resolveKeyBytes($authKey), true); return base64_encode($raw); } loadDotEnv($root . DIRECTORY_SEPARATOR . '.env'); $token = $argv[1] ?? ''; if ($token === '') { fwrite(STDERR, "用法: php scripts/playx-verify-token-callback-test.php \"\"\n"); exit(1); } $url = strval($_ENV['PLAYX_TOKEN_VERIFY_URL'] ?? getenv('PLAYX_TOKEN_VERIFY_URL') ?: ''); if ($url === '') { $url = 'https://callback-mallsys.superior3.net/callback/api/mallsys/plx/auth/verify-token'; } $merchantCode = strval($_ENV['PLAYX_ANGPOW_MERCHANT_CODE'] ?? getenv('PLAYX_ANGPOW_MERCHANT_CODE') ?: ''); $authKey = strval($_ENV['PLAYX_ANGPOW_IMPORT_AUTH_KEY'] ?? getenv('PLAYX_ANGPOW_IMPORT_AUTH_KEY') ?: ''); if ($merchantCode === '') { fwrite(STDERR, "缺少 PLAYX_ANGPOW_MERCHANT_CODE(请在 .env 配置或导出环境变量)\n"); exit(1); } if ($authKey === '') { fwrite(STDERR, "缺少 PLAYX_ANGPOW_IMPORT_AUTH_KEY(请在 .env 配置或导出环境变量)\n"); exit(1); } $requestId = 'mall_cli_' . uniqid(); $canonical = 'merchant_code=' . $merchantCode . '&request_id=' . $requestId . '&token=' . $token; $signature = buildSignature($canonical, $authKey); $payload = json_encode([ 'merchant_code' => $merchantCode, 'request_id' => $requestId, 'token' => $token, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($payload === false) { fwrite(STDERR, "JSON 编码失败\n"); exit(1); } echo "--- 等价 curl(Linux / macOS / Git Bash)---\n"; echo "curl -sS -X POST '" . $url . "' \\\n"; echo " -H 'Content-Type: application/json' \\\n"; echo " -H 'X-Request-Signature: " . $signature . "' \\\n"; echo " -d '" . $payload . "'\n\n"; echo "--- 本机直接请求(PHP stream)---\n"; echo "request_id={$requestId}\n"; echo "canonical={$canonical}\n\n"; $body = $payload; $ctx = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => "Content-Type: application/json\r\nX-Request-Signature: {$signature}\r\n", 'content' => $body, 'timeout' => 15, // 4xx/5xx 仍返回响应体,便于查看对端错误说明(否则 file_get_contents 直接 false) 'ignore_errors' => true, ], 'ssl' => [ 'verify_peer' => true, 'verify_peer_name' => true, ], ]); $result = @file_get_contents($url, false, $ctx); if ($result === false) { $err = error_get_last(); fwrite(STDERR, "请求失败: " . ($err['message'] ?? 'unknown') . "\n"); exit(1); } $statusLine = isset($http_response_header[0]) ? $http_response_header[0] : ''; echo "--- HTTP 响应行 ---\n"; echo $statusLine . "\n"; if (isset($http_response_header[1])) { echo "--- 响应头(节选)---\n"; $max = min(12, count($http_response_header)); for ($i = 1; $i < $max; $i++) { echo $http_response_header[$i] . "\n"; } } echo "--- 响应体 ---\n"; echo $result . "\n"; if (!preg_match('/\b200\b/', $statusLine)) { fwrite(STDERR, "\n提示:非 2xx 时请根据响应体与对端核对签名字符串、Body 字段是否与回调网关约定一致。\n"); exit(2); }