You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GP定期支付PHP代码报Error 31:签名双重Base64编码问题排查

解决GP定期支付Error 31:签名重复Base64编码问题

对接GP定期支付接口时返回Error 31,技术支持确认该错误是由于签名被执行两次Base64编码导致的,核心问题出在以下代码行:

$params['regularSubscriptionPaymentRequest']['signature'] = new SoapVar($params['regularSubscriptionPaymentRequest']['signature'], XSD_BASE64BINARY);

问题分析

  1. signMessage函数中,已经对openssl生成的二进制签名执行了base64_encode,返回Base64格式的字符串:
    return base64_encode($signature);
    
  2. 当使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 16:50:55