| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- <?php
- namespace App\Services\Apcopay;
- use App\Services\PayConfig;
- use GuzzleHttp\Client;
- use GuzzleHttp\Exception\GuzzleException;
- use Illuminate\Support\Facades\Log;
- class ApcopayClient
- {
- protected $client;
- protected $config;
- public function __construct()
- {
- $payConfigService = new PayConfig();
- $this->config =$payConfigService->getConfig('Apcopay');
- // 确保base_uri不重复包含/merchanttools
- $baseUrl = rtrim($this->config['api_url'], '/');
- // 如果baseUrl已经包含merchanttools,则移除它
- if (strpos($baseUrl, '/merchanttools') !== false) {
- $baseUrl = str_replace('/merchanttools', '', $baseUrl);
- }
- $this->client = new Client([
- 'base_uri' => $baseUrl,
- 'timeout' => 30,
- 'verify' => false // 开发环境可能需要关闭SSL验证
- ]);
- // 记录初始化信息
- Log::info('Apcopay Client initialized', [
- 'base_uri' => $baseUrl,
- 'config' => $this->config
- ]);
- }
- /**
- * 生成Basic认证头
- */
- protected function getAuthorizationHeader(): string
- {
- $credentials = base64_encode($this->config['client_id'] . ':' . $this->config['secret_key']);
- return 'Basic ' . $credentials;
- }
- /**
- * 生成签名
- */
- protected function generateSignature(array $data): string
- {
- // 记录签名前的数据
- Log::info('Apcopay Signature Data Before', [
- 'data' => $data
- ]);
- // 使用品牌密钥 - 直接使用,不做解码
- $brandKey = $this->config['brand_key'];
- // 1. 扁平化并按字母顺序排序字段
- $fields = $this->flattenAndSortFields($data);
- // 记录扁平化后的字段
- Log::info('Apcopay Flattened Fields', [
- 'fields' => $fields
- ]);
- // 2. 拼接字段
- $fieldsString = $this->concatenateFields($fields);
- // 记录拼接后的字符串
- Log::info('Apcopay Concatenated Fields', [
- 'fieldsString' => $fieldsString
- ]);
- // 3. 转为小写
- $lowercaseString = strtolower($fieldsString);
- Log::info('Apcopay Lowercase String', [
- 'lowercaseString' => $lowercaseString
- ]);
- // 4. 使用HMAC SHA256计算签名
- Log::info('Apcopay Brand Key Length', [
- 'length' => strlen($brandKey)
- ]);
- // 生成签名
- $signature = base64_encode(hash_hmac('sha256', $lowercaseString, $brandKey, true));
- // 记录生成的签名
- Log::info('Apcopay Generated Signature', [
- 'signature' => $signature
- ]);
- return $signature;
- }
- /**
- * 扁平化并排序字段 - 完全按照官方文档实现
- */
- protected function flattenAndSortFields(array $data, string $prefix = ''): array
- {
- $fields = [];
- foreach ($data as $key => $value) {
- $fieldKey = $prefix ? "{$prefix}.{$key}" : $key;
- if (is_null($value)) {
- $fields[$fieldKey] = '';
- } elseif (is_array($value)) {
- // 检查是否为索引数组
- if (array_keys($value) === range(0, count($value) - 1)) {
- // 索引数组处理方式
- foreach ($value as $index => $item) {
- $arrayKey = $fieldKey . "[" . $index . "]";
- if (is_array($item)) {
- $fields = array_merge($fields, $this->flattenAndSortFields($item, $arrayKey));
- } else {
- $fields[$arrayKey] = $this->formatValue($item);
- }
- }
- } else {
- // 关联数组处理方式
- $fields = array_merge($fields, $this->flattenAndSortFields($value, $fieldKey));
- }
- } else {
- $fields[$fieldKey] = $this->formatValue($value);
- }
- }
- ksort($fields, SORT_STRING | SORT_FLAG_CASE);
- return $fields;
- }
- /**
- * 处理布尔值转字符串 - 确保与官方文档一致
- */
- protected function formatValue($value)
- {
- if (is_bool($value)) {
- return $value ? 'true' : 'false';
- } elseif (is_null($value)) {
- return '';
- }
- return (string)$value;
- }
- /**
- * 拼接字段
- */
- protected function concatenateFields(array $fields): string
- {
- $pairs = [];
- foreach ($fields as $key => $value) {
- $pairs[] = $key . '=' . $value;
- }
- return implode('&', $pairs);
- }
- /**
- * 发送请求
- */
- protected function request(string $method, string $uri, array $data = [], array $headers = []): array
- {
- try {
- // 记录请求信息
- Log::info('Apcopay API Request', [
- 'method' => $method,
- 'uri' => $uri,
- 'data' => $data
- ]);
- // 生成签名
- $signature = $this->generateSignature($data);
- // 1. 准备请求头
- $headers = array_merge([
- 'Authorization' => $this->getAuthorizationHeader(),
- 'User-Agent' => 'PHP/ApcopayAPI',
- 'Content-Type' => 'application/json',
- 'Accept' => 'application/json',
- 'Signature' => $signature // 添加签名到请求头
- ], $headers);
- // 2. 不再将签名添加到请求体中,按照文档仅添加到请求头
- // 记录完整请求头和请求体
- Log::info('Apcopay API Request Details', [
- 'headers' => array_merge(
- $headers,
- ['Authorization' => 'Basic **REDACTED**'] // 不记录完整的Authorization头
- ),
- 'data' => $data
- ]);
- // 3. 发送请求
- $options = [
- 'headers' => $headers,
- 'http_errors' => false // 不自动抛出HTTP错误,我们自己处理
- ];
- if (!empty($data)) {
- $options['json'] = $data;
- }
- $response = $this->client->request($method, $uri, $options);
- // 3. 解析响应
- $statusCode = $response->getStatusCode();
- $body = $response->getBody()->getContents();
- Log::info('Apcopay API Raw Response', [
- 'uri' => $uri,
- 'status_code' => $statusCode,
- 'body' => $body
- ]);
- $result = json_decode($body, true);
- if (json_last_error() !== JSON_ERROR_NONE) {
- Log::error('Apcopay API Invalid JSON', [
- 'uri' => $uri,
- 'body' => $body,
- 'json_error' => json_last_error_msg()
- ]);
- throw new \RuntimeException('Invalid JSON response: ' . $body);
- }
- // 4. 检查HTTP状态码
- if ($statusCode >= 400) {
- Log::error('Apcopay API HTTP Error', [
- 'uri' => $uri,
- 'status_code' => $statusCode,
- 'result' => $result
- ]);
- // 返回错误信息而不是抛出异常,让调用者决定如何处理
- return [
- 'isSuccess' => false,
- 'errorMessage' => $result['errorMessage'] ?? "HTTP Error: {$statusCode}",
- 'statusCode' => $statusCode,
- 'rawResponse' => $result
- ];
- }
- // 记录响应信息
- Log::info('Apcopay API Response', [
- 'uri' => $uri,
- 'result' => $result
- ]);
- return $result;
- } catch (GuzzleException $e) {
- Log::error('Apcopay API Error', [
- 'method' => $method,
- 'uri' => $uri,
- 'data' => $data,
- 'error' => $e->getMessage(),
- 'trace' => $e->getTraceAsString()
- ]);
- return [
- 'isSuccess' => false,
- 'errorMessage' => 'Apcopay API request failed: ' . $e->getMessage(),
- 'exception' => get_class($e)
- ];
- } catch (\Exception $e) {
- Log::error('Apcopay General Error', [
- 'method' => $method,
- 'uri' => $uri,
- 'data' => $data,
- 'error' => $e->getMessage(),
- 'trace' => $e->getTraceAsString()
- ]);
- return [
- 'isSuccess' => false,
- 'errorMessage' => 'Error processing Apcopay request: ' . $e->getMessage(),
- 'exception' => get_class($e)
- ];
- }
- }
- /**
- * GET请求
- */
- protected function get(string $uri, array $query = [], array $headers = []): array
- {
- return $this->request('GET', $uri . '?' . http_build_query($query), [], $headers);
- }
- /**
- * POST请求
- */
- protected function post(string $uri, array $data = [], array $headers = []): array
- {
- return $this->request('POST', $uri, $data, $headers);
- }
- /**
- * PUT请求
- */
- protected function put(string $uri, array $data = [], array $headers = []): array
- {
- return $this->request('PUT', $uri, $data, $headers);
- }
- /**
- * DELETE请求
- */
- protected function delete(string $uri, array $headers = []): array
- {
- return $this->request('DELETE', $uri, [], $headers);
- }
- }
|