diff --git a/addons/shopro/library/ccblife/CcbPaymentService.php b/addons/shopro/library/ccblife/CcbPaymentService.php index bf7df9c..9dacdc1 100644 --- a/addons/shopro/library/ccblife/CcbPaymentService.php +++ b/addons/shopro/library/ccblife/CcbPaymentService.php @@ -140,7 +140,8 @@ class CcbPaymentService $macParams['TOKEN'] = $this->config['token']; } if (!empty($this->config['pay_success_url'])) { - $macParams['PAYSUCCESSURL'] = urlencode($this->config['pay_success_url']); + // ⚠️ 不要在这里urlencode,会在构建最终支付串时统一编码 + $macParams['PAYSUCCESSURL'] = $this->config['pay_success_url']; } // 支付位图和账户位图 @@ -203,7 +204,8 @@ class CcbPaymentService // 扩展域 if (!empty($this->config['extend_params'])) { - $macParams['EXTENDPARAMS'] = urlencode($this->config['extend_params']); + // ⚠️ 不要在这里urlencode,会在构建最终支付串时统一编码 + $macParams['EXTENDPARAMS'] = $this->config['extend_params']; } // 特殊字段(中石化专用) @@ -211,11 +213,17 @@ class CcbPaymentService $macParams['IDENTITYCODE'] = $this->config['identity_code']; } if (!empty($this->config['notify_url'])) { - $macParams['NOTIFY_URL'] = urlencode($this->config['notify_url']); + // ⚠️ 不要在这里urlencode,会在构建最终支付串时统一编码 + $macParams['NOTIFY_URL'] = $this->config['notify_url']; } - // 2. 构建签名字符串(按照定义顺序拼接,不排序!) - $signString = http_build_query($macParams); + // 2. 手动构建签名字符串(不能用http_build_query,避免URL编码破坏escape格式) + // ⚠️ 关键:按照建行文档4.2示例,签名字符串不进行URL编码 + $signParts = []; + foreach ($macParams as $key => $value) { + $signParts[] = $key . '=' . $value; + } + $signString = implode('&', $signParts); // 3. 添加PLATFORMPUB参与MD5签名(但不作为HTTP参数传递) $platformPubKey = $this->config['public_key']; // 服务方公钥 @@ -224,6 +232,9 @@ class CcbPaymentService // 4. 生成MAC签名(32位小写MD5) $mac = strtolower(md5($macSignString)); + Log::info('[建行支付] MAC签名字符串(前500字符): ' . mb_substr($macSignString, 0, 500)); + Log::info('[建行支付] 生成MAC: ' . $mac); + // 5. 构建不参与MAC的参数 $nonMacParams = []; @@ -243,17 +254,33 @@ class CcbPaymentService // 6. 生成ENCPUB(商户公钥密文,不参与MAC校验) $encpub = $this->encryptPublicKeyLast30(); - // 7. 组装最终支付串 + // 7. 组装最终支付串(传给建行收银台的URL参数) + // ⚠️ 注意:最终支付串需要对特殊字符进行URL编码 // 格式:参与MAC的参数 + 不参与MAC的参数 + MAC + PLATFORMID + ENCPUB - $finalPaymentString = $signString; - // 添加不参与MAC的参数 - if (!empty($nonMacParams)) { - $finalPaymentString .= '&' . http_build_query($nonMacParams); + // 7.1 构建参与MAC的参数部分(需要URL编码) + $finalParts = []; + foreach ($macParams as $key => $value) { + // 对值进行URL编码(建行收银台需要) + $finalParts[] = $key . '=' . urlencode($value); } - // 添加MAC、PLATFORMID、ENCPUB - $finalPaymentString .= '&MAC=' . $mac . '&PLATFORMID=' . $this->config['service_id'] . '&ENCPUB=' . urlencode($encpub); + // 7.2 添加不参与MAC的参数 + if (!empty($nonMacParams)) { + foreach ($nonMacParams as $key => $value) { + $finalParts[] = $key . '=' . urlencode($value); + } + } + + // 7.3 添加MAC、PLATFORMID、ENCPUB(ENCPUB已经是base64,需要URL编码) + $finalParts[] = 'MAC=' . $mac; + $finalParts[] = 'PLATFORMID=' . $this->config['service_id']; + $finalParts[] = 'ENCPUB=' . urlencode($encpub); + + // 7.4 拼接最终支付串 + $finalPaymentString = implode('&', $finalParts); + + Log::info('[建行支付] 最终支付串(前500字符): ' . mb_substr($finalPaymentString, 0, 500)); // 保存支付流水号到订单 Order::where('id', $orderId)->update([