GP定期支付PHP代码报Error 31:签名双重Base64编码问题排查
解决GP定期支付Error 31:签名重复Base64编码问题
对接GP定期支付接口时返回Error 31,技术支持确认该错误是由于签名被执行两次Base64编码导致的,核心问题出在以下代码行:
$params['regularSubscriptionPaymentRequest']['signature'] = new SoapVar($params['regularSubscriptionPaymentRequest']['signature'], XSD_BASE64BINARY);
问题分析
signMessage函数中,已经对openssl生成的二进制签名执行了base64_encode,返回Base64格式的字符串:return base64_encode($signature);- 当使用
XSD_BASE64BINARY类型创建SoapVar时,PHP的SoapClient会自动对传入的字符串再次进行Base64编码,最终导致签名被两次编码,不符合支付网关的校验要求。
修改方案
只需要让签名仅被编码一次即可,两种等价修改方式任选其一:
方式一:修改signMessage函数返回原始二进制签名
保留SoapVar的XSD_BASE64BINARY类型,让SoapClient自动完成Base64编码:
function signMessage($data, $privateKeyPath, $password) { $privateKey = openssl_pkey_get_private("file://{$privateKeyPath}", $password); if (!$privateKey) { throw new Exception('error private key'); } if (!openssl_sign($data, $signature, $privateKey)) { throw new Exception('error signature'); } // 返回原始二进制签名,不再手动Base64编码 return $signature; }
方式二:移除SoapVar包装,直接传入已Base64编码的签名
如果不想修改signMessage函数,可去掉XSD_BASE64BINARY的SoapVar,直接使用已编码的签名字符串:
// 注释或删除这一行 // $params['regularSubscriptionPaymentRequest']['signature'] = new SoapVar($params['regularSubscriptionPaymentRequest']['signature'], XSD_BASE64BINARY);
修改后的完整代码
<?php $wsdl = __DIR__ . '/cws_v1.wsdl'; $privateKey = __DIR__ . '/private-key.pem'; $publicCert = __DIR__ . '/pub-key.pem'; $privateKeyPassword = '**************'; $soapLocation = 'https://test.3dsecure.gpwebpay.com/pay-ws/v1/PaymentService'; function signMessage($data, $privateKeyPath, $password) { $privateKey = openssl_pkey_get_private("file://{$privateKeyPath}", $password); if (!$privateKey) { throw new Exception('error private key'); } if (!openssl_sign($data, $signature, $privateKey)) { throw new Exception('error signature'); } // 返回原始二进制签名,由SoapClient自动处理Base64编码 return $signature; } function processRegularSubscriptionPayment($merchantNumber, $privateKeyPath, $password, $wsdl, $soapLocation) { try { $streamContext = stream_context_create(array( 'http' => array( 'header' => 'Content-Type: application/soap+xml; charset=utf-8', ) )); $client = new SoapClient($wsdl, array( 'trace' => 1, 'exceptions' => true, 'location' => $soapLocation, 'soap_version' => SOAP_1_1, 'encoding' => 'UTF-8', 'cache_wsdl' => WSDL_CACHE_NONE, 'stream_context' => $streamContext, )); $messageId = date('YmdHis') . rand(1000, 9999); $params = [ 'regularSubscriptionPaymentRequest' => [ 'messageId' => $messageId, 'provider' => 'XXXX', 'merchantNumber' => $merchantNumber, 'paymentNumber' => '123456789', 'masterPaymentNumber' => '123456788', 'orderNumber' => '123456789', 'captureFlag' => 1, 'cardHolderData' => [ 'cardholderDetails' => [ 'name' => 'Name Surname', 'email' => 'email@test.com' ], ], ] ]; $name = htmlspecialchars($params['regularSubscriptionPaymentRequest']['cardHolderData']['cardholderDetails']['name'], ENT_QUOTES, 'UTF-8'); $email = htmlspecialchars($params['regularSubscriptionPaymentRequest']['cardHolderData']['cardholderDetails']['email'], ENT_QUOTES, 'UTF-8'); $params['regularSubscriptionPaymentRequest']['cardHolderData']['cardholderDetails']['name'] = $name; $params['regularSubscriptionPaymentRequest']['cardHolderData']['cardholderDetails']['email'] = $email; $dataToSign = $params['regularSubscriptionPaymentRequest']['messageId'] . "|" . $params['regularSubscriptionPaymentRequest']['provider'] . "|" . $params['regularSubscriptionPaymentRequest']['merchantNumber'] . "|" . $params['regularSubscriptionPaymentRequest']['paymentNumber'] . "|" . $params['regularSubscriptionPaymentRequest']['masterPaymentNumber'] . "|" . $params['regularSubscriptionPaymentRequest']['orderNumber'] . "|" . $params['regularSubscriptionPaymentRequest']['captureFlag'] . "|" . $params['regularSubscriptionPaymentRequest']['cardHolderData']['cardholderDetails']['name'] . "|" . $params['regularSubscriptionPaymentRequest']['cardHolderData']['cardholderDetails']['email']; $params['regularSubscriptionPaymentRequest']['signature'] = signMessage($dataToSign, $privateKeyPath, $password); $params['regularSubscriptionPaymentRequest']['signature'] = new SoapVar($params['regularSubscriptionPaymentRequest']['signature'], XSD_BASE64BINARY); $response = $client->__soapCall('processRegularSubscriptionPayment', [$params]); if (isset($response->resultCode) && $response->resultCode == '0') { return $response->resultMessage; } else { return $response->resultCode . ' - ' . $response->resultMessage; } } catch (SoapFault $e) { print_r($e); return "SOAP error: " . $e->getMessage(); } catch (Exception $e) { return "error: " . $e->getMessage(); } } $merchantNumber = '147258369'; echo processRegularSubscriptionPayment($merchantNumber, $privateKey, $privateKeyPassword, $wsdl, $soapLocation);
内容的提问来源于stack exchange,提问作者Radek Lyčka
相关产品推荐
相关产品推荐

