PAYATHON 2026

PHP Comtrust SOAP API 返回 Response Code 5000

支付老李

结论

.pfx 证书不是 SOAP envelope 中的字段。它会在建立 HTTPS 连接时作为客户端证书参与 TLS 双向认证。PHP SoapClient 通常无法直接使用 .pfx/.p12 文件,需要先将其转换为包含客户端证书和私钥的 PEM 文件,再通过 local_cert 和 passphrase 传入。

ResponseCode 5000 也未必是证书格式导致的。证书没有绑定到当前 demo 商户、商户凭据错误、请求来源 IP 未获授权,或者正式环境与 demo 环境的证书混用,都可能返回这个错误。

将 PFX 转换为 PEM

可以先生成一个同时包含客户端证书和私钥的 PEM 文件:

openssl pkcs12 -in merchant.pfx -clcerts -out merchant-client.pem

执行命令时通常需要输入两个密码:

  1. .pfx 文件的导入密码;
  2. 为导出的 PEM 私钥设置的新密码。

之后配置 SoapClient 时,passphrase 应填写第 2 步设置的 PEM 私钥密码。它不一定与原来的 PFX 密码相同。

如果旧证书使用了 OpenSSL 3 默认不再启用的加密算法,可能需要加上 -legacy:

openssl pkcs12 -legacy -in merchant.pfx -clcerts -out merchant-client.pem

也可以分别导出证书和私钥:

openssl pkcs12 -in merchant.pfx -clcerts -nokeys -out merchant-cert.pem
openssl pkcs12 -in merchant.pfx -nocerts -out merchant-key.pem

然后将两者合并:

cat merchant-cert.pem merchant-key.pem > merchant-client.pem

merchant-client.pem 包含私钥,属于敏感文件。不要将它提交到 Git,也不要放在可被 Web 直接访问的目录中。

在 SoapClient 中使用证书

<?php

$wsdl = 'https://demo-ipg.comtrust.ae:2443/MerchantAPI.svc?singleWsdl';

$options = [
    'local_cert'        => __DIR__ . '/certificates/merchant-client.pem',
    'passphrase'        => 'PEM_PRIVATE_KEY_PASSWORD',
    'exceptions'        => true,
    'trace'             => true,
    'cache_wsdl'        => WSDL_CACHE_NONE,
    'connection_timeout' => 30,
];

try {
    $client = new SoapClient($wsdl, $options);

    $request = [
        'request' => [
            'Customer'        => 'Demo Merchant',
            'Channel'         => 'Web',
            'Address'         => 'demo-ipg.comtrust.ae',
            'Language'        => 'en',
            'Password'        => 'Comtrust',
            'version'         => '2.0',
            'Amount'          => '99.98',
            'Currency'        => 'AED',
            'OrderID'         => '123',
            'OrderInfo'       => 'Test Info',
            'OrderName'       => 'Test Name',
            'ReturnPath'      => 'https://example.test/payment/return',
            'TransactionHint' => 'CPT:Y',
        ],
    ];

    $result = $client->Register($request);
    print_r($result);
} catch (SoapFault $e) {
    echo 'SOAP Fault: ' . $e->getMessage() . PHP_EOL;

    if (isset($client)) {
        echo "Last request headers:\n";
        echo $client->__getLastRequestHeaders() . PHP_EOL;

        echo "Last request:\n";
        echo $client->__getLastRequest() . PHP_EOL;

        echo "Last response headers:\n";
        echo $client->__getLastResponseHeaders() . PHP_EOL;

        echo "Last response:\n";
        echo $client->__getLastResponse() . PHP_EOL;
    }
}

证书是在 TLS 层发送的,所以 __getLastRequest() 返回的 SOAP XML 中不会出现证书。这是正常现象。

如果合并后的 PEM 仍不能工作

可以明确指定 SSL stream context:

<?php

$sslContext = stream_context_create([
    'ssl' => [
        'local_cert'       => __DIR__ . '/certificates/merchant-cert.pem',
        'local_pk'         => __DIR__ . '/certificates/merchant-key.pem',
        'passphrase'       => 'PRIVATE_KEY_PASSWORD',
        'verify_peer'      => true,
        'verify_peer_name' => true,
    ],
]);

$client = new SoapClient(
    'https://demo-ipg.comtrust.ae:2443/MerchantAPI.svc?singleWsdl',
    [
        'stream_context' => $sslContext,
        'exceptions'     => true,
        'trace'          => true,
        'cache_wsdl'     => WSDL_CACHE_NONE,
    ]
);

不同 PHP 和 OpenSSL 版本对分离证书与私钥的兼容性可能不同。实际使用中,包含证书和私钥的单一 PEM 文件通常更稳妥。

不要通过关闭服务器证书验证来绕过问题:

'verify_peer' => false,
'verify_peer_name' => false,

这两项设置只会关闭对服务器身份的验证,无法解决客户端证书未被认可的问题,还会降低连接安全性。

检查证书是否正确

查看 PFX 中的证书信息:

openssl pkcs12 -in merchant.pfx -clcerts -nokeys -info

查看导出的客户端证书:

openssl x509 -in merchant-cert.pem -noout -subject -issuer -serial -dates

检查私钥能否正常读取:

openssl pkey -in merchant-key.pem -check

检查私钥与证书是否匹配:

openssl x509 -in merchant-cert.pem -pubkey -noout | openssl sha256
openssl pkey -in merchant-key.pem -pubout | openssl sha256

两条命令输出的摘要应当一致。

还可以直接测试使用客户端证书建立 TLS 连接:

openssl s_client \
  -connect demo-ipg.comtrust.ae:2443 \
  -servername demo-ipg.comtrust.ae \
  -cert merchant-cert.pem \
  -key merchant-key.pem

这个测试只能检查 TLS 连接和证书交换情况,不能证明 Comtrust 已经将该证书正确绑定到商户账号。

继续排查 Response Code 5000

如果已经收到结构化的 RegisterResult,说明请求已到达服务端应用。接下来需要逐项检查:

  • Customer 和 Password 是否为 Comtrust 分配的真实 demo 凭据,而不是文档中的示例值。
  • 客户端证书是否属于 demo 环境,以及它是否已绑定到同一个商户账号。
  • 证书是否过期、被吊销,或者缺少完整证书链。
  • 当前服务器的公网 IP 是否需要加入 Comtrust 白名单。
  • Address 表示的究竟是商户地址、来源地址还是授权主机名,而不是 API 域名。该字段应以当前版本的 Comtrust 文档为准。
  • API 版本、端点和账号是否属于同一环境。
  • ReturnPath 是否必须是支付平台能够访问的 HTTPS 地址。127.0.0.1 只指向访问者自身,远程支付平台无法回调这个地址。
  • PHP 进程是否有权限读取 PEM 文件及其上级目录。
  • PHP 是否缓存了 WSDL。调试时使用 WSDL_CACHE_NONE,以免继续读取旧配置。

如果这些设置都正确,但服务仍然返回 5000,应将响应中的 UniqueID 提交给 Comtrust 支持团队,由他们查询服务端日志。例如:

1942d0d0-37f5-4504-951e-3343f9b86ea7

只有服务端日志才能确认具体原因,例如客户端证书未收到、证书未绑定商户、商户密码错误、IP 未授权,或者其他权限策略。仅凭统一的 5000 响应,无法可靠地区分这些情况。

备注:内容仅供参考。