PAYATHON 2026

实现 2Checkout payment API 后支付调用失败

支付小周

结论

这两类错误的原因不同:

  • curl call failed 通常说明请求还没有被 2Checkout 正常处理,应先检查网络、DNS、TLS、代理、防火墙和 cURL 配置。
  • Payment Authorization Failed 通常说明请求已经到达支付系统,但授权遭到拒绝。需要检查请求参数、测试与生产环境、商户配置、3D Secure、风控规则及发卡行返回结果。

不要把两者统一显示为”支付失败”。传输层错误和 API 响应应分开记录,但日志中不能保存完整卡号、CVV、密钥或访问令牌。

排查 curl call failed

先输出 cURL 错误编号、错误信息、HTTP 状态码和响应内容。只记录 curl_exec() 失败,通常无法定位具体原因。

<?php

$ch = curl_init($apiUrl);

curl_setopt_array($ch, [
    CURLOPT_POST            => true,
    CURLOPT_POSTFIELDS      => json_encode($payload),
    CURLOPT_RETURNTRANSFER  => true,
    CURLOPT_CONNECTTIMEOUT  => 10,
    CURLOPT_TIMEOUT         => 30,
    CURLOPT_HTTPHEADER      => [
        'Content-Type: application/json',
        'Accept: application/json',
        'Authorization: ' . $authorizationHeader,
    ],
]);

$response = curl_exec($ch);
$curlErrno = curl_errno($ch);
$curlError = curl_error($ch);
$httpCode  = curl_getinfo($ch, CURLINFO_HTTP_CODE);

curl_close($ch);

if ($response === false) {
    error_log(sprintf(
        '2Checkout transport error: errno=%d, error=%s',
        $curlErrno,
        $curlError
    ));

    throw new RuntimeException('Payment service is temporarily unavailable.');
}

error_log(sprintf(
    '2Checkout response: http_code=%d, body=%s',
    $httpCode,
    redactPaymentData($response)
));

实际使用时需要实现 redactPaymentData(),对卡号、CVV、令牌、邮箱和地址等敏感信息进行脱敏。

常见检查项如下:

  1. 确认服务器能够访问 API 域名

    在应用所在的服务器上检查 DNS、HTTPS 连通性和证书验证。开发电脑能访问,并不代表生产服务器也能访问。

  2. 检查 TLS 和 CA 证书

    PHP、libcurl、OpenSSL 或系统 CA 证书过旧时,可能导致 TLS 握手失败。不要长期使用以下配置绕过证书验证:

    CURLOPT_SSL_VERIFYPEER => false

    应更新 CA 证书和运行环境,并保持 CURLOPT_SSL_VERIFYPEER 开启。

  3. 检查超时设置

    偶发失败可能与网络波动或超时时间过短有关。连接超时和请求总超时应分别设置。对于创建支付或扣款的请求,不要无条件直接重试,否则可能产生重复交易。

  4. 检查代理、防火墙和出口限制

    确认服务器是否需要配置代理,并检查防火墙、安全组或主机服务商是否限制外部 HTTPS 请求。

  5. 确认 URL 和请求格式

    核对 API 地址、HTTP 方法、Content-Type、认证头及 JSON 编码结果,同时处理 json_encode() 失败的情况:

    $json = json_encode($payload, JSON_THROW_ON_ERROR);

排查支付授权失败

Payment Authorization Failed 是业务层拒绝,原因可能是参数错误、环境混用、商户配置、风控或发卡行拒绝。仅凭这一条通用提示,无法判断具体原因。

1. 检查测试与生产环境

以下内容必须属于同一环境:

  • API endpoint
  • 商户账号或 seller/merchant 标识
  • API 用户名和密码
  • 私钥、密钥或访问令牌
  • 测试卡与真实银行卡
  • Webhook 和返回地址配置

使用测试凭证请求生产接口,或用测试卡调用生产环境,通常都会失败。不同 2Checkout API 产品和版本使用的字段名称可能不同,应以对应版本的接口文档为准。

2. 记录完整的结构化错误

不要只读取顶层错误消息,还需要记录:

  • HTTP 状态码
  • API 错误代码
  • 错误字段或参数名
  • 交易号、订单号或请求 ID
  • 3D Secure 状态
  • 网关或发卡行返回的拒绝类别

下面是一个通用的响应处理示例。具体字段需要根据实际 API 响应调整:

<?php

$data = json_decode($response, true, 512, JSON_THROW_ON_ERROR);

if ($httpCode < 200 || $httpCode >= 300) {
    error_log(json_encode([
        'http_code'  => $httpCode,
        'error_code' => $data['error_code'] ?? null,
        'message'    => $data['message'] ?? null,
        'request_id' => $data['request_id'] ?? null,
    ], JSON_UNESCAPED_UNICODE));

    throw new RuntimeException(
        'Payment request was rejected by the payment service.'
    );
}

示例中的 error_code 和 request_id 不一定存在。使用前应先核对当前接口的响应结构。

3. 验证支付参数

重点检查以下内容:

  • 金额是否使用接口要求的格式和最小货币单位
  • 商户账号是否支持当前币种
  • 账单姓名、地址、国家和邮政编码是否完整
  • 国家代码和币种代码是否符合接口格式
  • 卡片令牌是否有效、是否过期或已经使用
  • 商户订单号是否重复
  • 签名、哈希或认证字符串是否按要求生成
  • 客户端生成的 token 是否提交到了正确的环境

如果使用托管组件或 tokenization,应尽量让敏感卡片信息直接提交给支付服务,服务器只接收 token。不要自行记录或持久化 CVV。

4. 检查 3D Secure 和前端流程

部分交易需要完成 3D Secure 验证。如果后端直接发起扣款,却没有正确处理跳转、challenge、回调或验证结果,最终也可能显示授权失败。

检查浏览器控制台和网络请求,确认:

  • 支付组件已经成功加载
  • token 已成功创建
  • 3D Secure 页面没有被 CSP、iframe 或弹窗策略拦截
  • 验证完成后,最终状态已经提交到后端
  • 返回地址和 webhook 使用的是可公开访问的 HTTPS 地址

5. 区分参数拒绝与银行拒绝

即使请求参数正确,交易仍可能因为以下原因失败:

  • 余额不足
  • 卡片过期或被冻结
  • 没有开通网上支付、跨境支付或特定币种支付
  • 发卡行风控
  • CVV 或账单地址校验失败
  • 支付平台或商户账号的风控规则
  • 同一银行卡在短时间内重复尝试

这些问题通常无法通过修改 PHP 代码解决。前端可以向用户显示适度的通用提示,并允许用户更换支付方式。后台则应保留脱敏后的错误代码和交易标识,方便后续查询。

建议的错误处理方式

传输失败、服务端错误和明确的授权拒绝需要分别处理:

<?php

if ($response === false) {
    // 网络或 TLS 错误。交易状态可能未知,不要立即重复扣款。
    markOrderAsPaymentUnknown($orderId);
} elseif ($httpCode >= 500) {
    // 支付服务暂时异常。查询原交易状态后再决定是否重试。
    markOrderAsPaymentPending($orderId);
} elseif ($httpCode >= 400) {
    // 参数、认证或支付授权被拒绝。
    markOrderAsPaymentFailed($orderId);
} else {
    // 仍应解析响应中的最终交易状态,不能仅凭 HTTP 200 判定支付成功。
    handlePaymentResponse($response);
}

对于可能创建交易的请求,建议使用 API 提供的幂等机制。如果当前接口不支持幂等键,可以使用唯一订单号,并在重试前查询原交易状态。

仍然无法定位时

可以准备以下信息并提交给 2Checkout 支持团队,但不要发送完整卡号、CVV、密码或密钥:

  • 错误发生时间及服务器时区
  • 使用的是测试环境还是生产环境
  • HTTP 状态码和 API 错误代码
  • 脱敏后的请求与响应
  • 商户订单号
  • 交易号或请求 ID
  • cURL 错误编号
  • 是否完成 3D Secure 验证

排查时,先确认请求是没有送达,还是已经送达但授权被拒。前者主要检查网络环境和 cURL 配置,后者则检查支付参数、商户配置、3D Secure、风控及发卡行返回结果。

备注:内容仅供参考。