|
|
|
|
@ -114,7 +114,61 @@ class HFPayHttpClient implements IHttpClient |
|
|
|
|
*/ |
|
|
|
|
private ISigner $signClient; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 准备 Multipart 数据 |
|
|
|
|
* 核心原则:文件流只进 multipart,不进 signData;文本字段两者都进 |
|
|
|
|
*/ |
|
|
|
|
private function _prepareMultipartData(array $body): array |
|
|
|
|
{ |
|
|
|
|
$multipart = []; |
|
|
|
|
$signData = []; |
|
|
|
|
|
|
|
|
|
foreach ($body as $key => $value) { |
|
|
|
|
// 1. 资源类型(文件流):仅放入 multipart,绝不参与签名 |
|
|
|
|
if (is_resource($value)) { |
|
|
|
|
$multipart[] = [ |
|
|
|
|
'name' => $key, |
|
|
|
|
'contents' => $value, |
|
|
|
|
]; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 2. 已是 Guzzle Multipart 格式的数组 |
|
|
|
|
if (is_array($value) && isset($value['contents'])) { |
|
|
|
|
$multipart[] = $value; |
|
|
|
|
// 仅当 contents 非资源时才参与签名 |
|
|
|
|
if (!is_resource($value['contents'])) { |
|
|
|
|
$signData[$key] = (string)$value['contents']; |
|
|
|
|
} |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 3. 普通数组:转 JSON 字符串 |
|
|
|
|
if (is_array($value)) { |
|
|
|
|
$jsonStr = json_encode($value, JSON_UNESCAPED_UNICODE); |
|
|
|
|
$multipart[] = ['name' => $key, 'contents' => $jsonStr]; |
|
|
|
|
$signData[$key] = $jsonStr; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 4. NULL 值:转为空字符串(汇付通常要求空值也参与签名) |
|
|
|
|
if ($value === null) { |
|
|
|
|
$multipart[] = ['name' => $key, 'contents' => '']; |
|
|
|
|
$signData[$key] = ''; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 5. 标量类型:强制转为字符串 |
|
|
|
|
$strVal = (string)$value; |
|
|
|
|
$multipart[] = ['name' => $key, 'contents' => $strVal]; |
|
|
|
|
$signData[$key] = $strVal; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return [ |
|
|
|
|
'multipart' => $multipart, |
|
|
|
|
'sign_data' => $signData, |
|
|
|
|
]; |
|
|
|
|
} |
|
|
|
|
/** |
|
|
|
|
* @param string $method |
|
|
|
|
* @param string $uri |
|
|
|
|
@ -134,10 +188,11 @@ class HFPayHttpClient implements IHttpClient |
|
|
|
|
if (!empty($query)) { |
|
|
|
|
$options['query'] = array_filter($query, fn($v) => $v !== null); |
|
|
|
|
} |
|
|
|
|
//添加固定的参数 |
|
|
|
|
//版本号 |
|
|
|
|
|
|
|
|
|
// ✅ 关键修正:固定参数必须在签名之前注入到 $body 中 |
|
|
|
|
// 这样无论是 form/json 还是 multipart,都能保证这些字段参与签名 |
|
|
|
|
if (empty($body['version'])) { |
|
|
|
|
$body['version'] = $this->config['version'] ?? ''; |
|
|
|
|
$body['version'] = $this->config['version'] ?? self::API_VERSION; |
|
|
|
|
} |
|
|
|
|
if (empty($body['mer_cust_id'])) { |
|
|
|
|
$body['mer_cust_id'] = $this->config['mid'] ?? ''; |
|
|
|
|
@ -146,16 +201,25 @@ class HFPayHttpClient implements IHttpClient |
|
|
|
|
$body['bg_ret_url'] = $this->config['callback']; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//将内容生成签名 |
|
|
|
|
if ($format === 'multipart') { |
|
|
|
|
// 1. 分离文件流和文本参数 |
|
|
|
|
$processed = $this->_prepareMultipartData($body); |
|
|
|
|
|
|
|
|
|
// 2. 使用纯文本参数生成签名(文件流已被排除) |
|
|
|
|
$sign = $this->signClient->sign($processed['sign_data']); |
|
|
|
|
|
|
|
|
|
// 3. 将签名结果作为普通文本字段追加到 multipart |
|
|
|
|
$processed['multipart'][] = [ |
|
|
|
|
'name' => 'check_value', |
|
|
|
|
'contents' => $sign, |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
$options['multipart'] = $processed['multipart']; |
|
|
|
|
} else { |
|
|
|
|
// 非 multipart 逻辑保持不变 |
|
|
|
|
$sign = $this->signClient->sign($body); |
|
|
|
|
$body['check_value'] = $sign; |
|
|
|
|
|
|
|
|
|
// echo '请求参数 #####'; |
|
|
|
|
// print_r($body); |
|
|
|
|
// echo '请求参数 end'; |
|
|
|
|
if(empty($format)) { |
|
|
|
|
$format = 'form'; |
|
|
|
|
} |
|
|
|
|
switch ($format) { |
|
|
|
|
case 'json': |
|
|
|
|
$options['json'] = array_filter($body, fn($v) => $v !== null); |
|
|
|
|
@ -164,31 +228,27 @@ class HFPayHttpClient implements IHttpClient |
|
|
|
|
$options['form_params'] = array_filter($body, fn($v) => $v !== null); |
|
|
|
|
break; |
|
|
|
|
case 'body': |
|
|
|
|
$options['body'] = array_filter($body, fn($v) => $v !== null); |
|
|
|
|
break; |
|
|
|
|
// 新增的分支 |
|
|
|
|
case 'multipart': |
|
|
|
|
$options['multipart'] = array_filter($body, fn($v) => $v !== null); |
|
|
|
|
$options['body'] = json_encode(array_filter($body, fn($v) => $v !== null), JSON_UNESCAPED_UNICODE); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
try { |
|
|
|
|
$resp = $this->client->request($method, $uri, $options); |
|
|
|
|
} catch (GuzzleException $e) { |
|
|
|
|
throw new ApiException($e->getMessage()); |
|
|
|
|
} |
|
|
|
|
$response = Response::make($resp); |
|
|
|
|
|
|
|
|
|
$response = Response::make($resp); |
|
|
|
|
if (!$response->isSuccess()) { |
|
|
|
|
throw new ApiException('汇付反馈状态不正确', $resp->getStatusCode()); |
|
|
|
|
} |
|
|
|
|
//验证数据 |
|
|
|
|
$body = $response->getBody(); |
|
|
|
|
if(!$this->signClient->verify($body)) { |
|
|
|
|
|
|
|
|
|
$respBody = $response->getBody(); |
|
|
|
|
if (!$this->signClient->verify($respBody)) { |
|
|
|
|
throw new ApiException('签名验证未通过'); |
|
|
|
|
} |
|
|
|
|
//解码 |
|
|
|
|
$decodeBody = $this->signClient->decode($body['check_value']); |
|
|
|
|
|
|
|
|
|
$decodeBody = $this->signClient->decode($respBody['check_value']); |
|
|
|
|
ErrCodeHelper::parseError($decodeBody['resp_code'], $decodeBody['resp_desc']); |
|
|
|
|
return Response::tempInit(200, $decodeBody); |
|
|
|
|
} |
|
|
|
|
|