PAYATHON 2026

使用 cURL 调用 Payment Gateway 时如何配置 SSL?

支付阿杰

结论

你的域名证书与这次 cURL 请求没有直接关系。

域名证书供客户端访问你的服务器时使用。PHP cURL 连接 Payment Gateway,则是服务器主动发起的出站 HTTPS 请求,连接加密和身份验证依赖支付网关的服务器证书。

一般应使用以下配置:

  • 开启 CURLOPT_SSL_VERIFYPEER。
  • 将 CURLOPT_SSL_VERIFYHOST 设为 2。
  • 优先使用系统信任的 CA。系统 CA 无法使用时,再配置 CURLOPT_CAINFO。
  • 只有支付网关明确要求双向 TLS(mTLS)时,才配置 CURLOPT_SSLCERT、私钥和证书密码。

不要靠关闭证书验证来解决连接失败。信用卡支付涉及敏感数据,关闭验证会让连接容易受到中间人攻击。

推荐配置

<?php

$ch = curl_init('https://secure.paymentgateway.com/blah.php');

$postData = [
    'variable1' => 'value1',
    'variable2' => 'value2',
];

curl_setopt_array($ch, [
    CURLOPT_POST            => true,
    CURLOPT_POSTFIELDS      => http_build_query($postData),
    CURLOPT_RETURNTRANSFER  => true,
    CURLOPT_CONNECTTIMEOUT  => 10,
    CURLOPT_TIMEOUT         => 30,

    // 验证支付网关证书是否由可信 CA 签发
    CURLOPT_SSL_VERIFYPEER  => true,

    // 验证证书中的主机名是否与请求域名一致
    CURLOPT_SSL_VERIFYHOST  => 2,

    CURLOPT_HTTPHEADER => [
        'Content-Type: application/x-www-form-urlencoded',
    ],
]);

$response = curl_exec($ch);

if ($response === false) {
    $errorNumber = curl_errno($ch);
    $errorMessage = curl_error($ch);

    curl_close($ch);

    throw new RuntimeException(
        "Payment Gateway request failed ({$errorNumber}): {$errorMessage}"
    );
}

$httpStatus = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($httpStatus < 200 || $httpStatus >= 300) {
    throw new RuntimeException(
        "Payment Gateway returned HTTP status {$httpStatus}"
    );
}

// 按支付网关规定的格式解析 $response

需要注意 CURLOPT_RETURNTRANSFER。如果没有开启,curl_exec() 会直接输出响应,而不会把响应内容赋给 $response。

CURLOPT_POST 应设为布尔值 true,不要使用变量数量 $count。虽然设置 CURLOPT_POSTFIELDS 通常也会让 cURL 发起 POST 请求,但显式设置更容易理解。

什么时候配置 CURLOPT_CAINFO

正常情况下,PHP/libcurl 会使用操作系统的可信 CA 存储,不需要手动指定。

如果遇到下面这类错误:

SSL certificate problem: unable to get local issuer certificate

先检查服务器上的 CA 证书包是否完整。如果系统 CA 无法使用,可以指定可信的 CA bundle:

curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . '/cert/cacert.pem');

这个文件必须是用于验证支付网关证书的 CA 证书包,不能使用你自己域名的服务器证书。也可以在 php.ini 中统一配置:

curl.cainfo = "/absolute/path/to/cacert.pem"
openssl.cafile = "/absolute/path/to/cacert.pem"

修改 php.ini 后,通常需要重启对应的 PHP-FPM、Apache 或其他 PHP 运行环境。

支付网关要求客户端证书时

只有支付网关文档明确要求 client certificate、mutual TLS 或 mTLS 时,才需要配置客户端证书。

如果证书和私钥分开存放:

curl_setopt_array($ch, [
    CURLOPT_SSLCERT         => __DIR__ . '/cert/client-cert.pem',
    CURLOPT_SSLKEY          => __DIR__ . '/cert/client-key.pem',
    CURLOPT_SSLCERTPASSWD   => 'certificate-password',
    CURLOPT_SSL_VERIFYPEER  => true,
    CURLOPT_SSL_VERIFYHOST  => 2,
]);

如果支付网关提供的是 PKCS#12 文件:

curl_setopt_array($ch, [
    CURLOPT_SSLCERT         => __DIR__ . '/cert/client-cert.p12',
    CURLOPT_SSLCERTTYPE     => 'P12',
    CURLOPT_SSLCERTPASSWD   => 'certificate-password',
    CURLOPT_SSL_VERIFYPEER  => true,
    CURLOPT_SSL_VERIFYHOST  => 2,
]);

证书格式、私钥配置方式和密码要求,应以支付网关的接入文档为准。不要直接把网站的域名证书当作客户端证书,除非支付网关明确认可并要求这样配置。

原代码中的主要问题

下面的设置关闭了 TLS 身份验证:

curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);

连接仍然会加密,但 cURL 无法确认对端是否确实是支付网关。应改为:

curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);

即使配置了 CURLOPT_CAINFO 和 CURLOPT_SSLCERT,也不能代替这两项验证。它们的作用分别是:

  • CURLOPT_CAINFO:提供可信 CA,用于验证远端服务器的证书。
  • CURLOPT_SSLCERT:向远端服务器出示客户端证书,仅用于 mTLS。
  • CURLOPT_SSL_VERIFYHOST:检查证书中的域名是否与请求域名一致。
  • CURLOPT_SSL_VERIFYPEER:检查证书链是否可信。

注意事项

生产环境中不要长期启用 CURLOPT_VERBOSE。调试输出可能包含连接信息、请求头或其他敏感内容。排查问题时,应把日志写入受保护的临时文件,并对令牌、卡号和身份验证信息做脱敏处理。

TLS 版本通常由当前的 libcurl 和 OpenSSL 自动协商。只有支付网关明确要求固定最低 TLS 版本时,才需要手动设置,例如:

curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);

是否需要这个选项,取决于支付网关的要求,以及服务器安装的 PHP、libcurl 和 TLS 库版本。不了解运行环境时,不要强制使用过时的 SSL/TLS 版本。

备注:内容仅供参考。