PAYATHON 2026

Zen-Cart Checkout 页面出现 null 和信用卡被拒错误

支付小周

结论

fecaction=null 通常不是 SecurePay 返回的信用卡拒付代码。它更可能表示快速结账扩展(常见缩写为 FEC)在生成跳转 URL 时,没有取得有效的结账动作值。这说明结账流程或前端脚本的状态异常,但不一定是信用卡被拒的直接原因。

“Your card has been declined” 是 Zen Cart 支付模块的通用失败提示。即使卡号已通过 SecurePay 验证,交易仍可能因为接口配置、请求字段、金额、账单地址、测试与生产环境不一致而失败。模块没有正确解析网关响应时,也可能显示这条提示。

这两个问题需要分开排查:

  1. SecurePay 实际返回了什么响应;
  2. 哪段代码把 fecaction 设置成了 null。

为什么通过验证的信用卡仍会被拒

“信用卡已验证”通常只表示卡号格式、账户或某次验证请求有效,不代表当前付款交易已经获得授权。常见原因有:

  • SecurePay 商户账号、API 凭据或签名配置错误;
  • 测试凭据被用于生产环境,或生产凭据被用于测试端点;
  • 支付模块使用了错误的交易类型,例如模块发送 capture,但商户账户只允许 authorize;
  • 订单金额、币种或小数格式不符合接口要求;
  • 账单地址、邮编、CVV 或持卡人姓名校验失败;
  • SecurePay 拒绝了交易,但模块把具体错误统一转换成了 “card declined”;
  • 网关已经批准交易,但旧版或定制模块无法识别新的响应格式;
  • PHP、Zen Cart、SecurePay 模块与 FEC 扩展的版本不兼容;
  • 回调、重定向或 AJAX 请求受到防火墙、缓存、Cookie 设置或 HTTPS 配置的干扰;
  • 模块出现 PHP 警告或异常,导致响应变量没有正确赋值。

不同供应商都可能使用 “SecurePay” 这个名称。具体响应字段和错误代码,应以当前支付服务商及所安装模块的接口文档为准。

fecaction=null 的可能来源

fecaction 看起来是 FEC 扩展内部使用的路由或动作参数。正常情况下,它应包含扩展能够识别的动作名称。URL 中出现字面值 null,通常说明 JavaScript 或 PHP 在拼接 URL 时使用了空变量。

例如,前端代码可能包含以下逻辑:

const fecAction = getCheckoutAction();

window.location.href =
  'index.php?main_page=checkout&fecaction=' + fecAction;

如果 getCheckoutAction() 返回 null,最终 URL 就会变成:

index.php?main_page=checkout&fecaction=null

另一种可能是 PHP 收到了不受支持的值,又把它传回了 URL:

$fecAction = $_GET['fecaction'] ?? null;

PHP 的 null 通常会转换为空字符串。因此,URL 中直接出现 null,更常见的原因是 JavaScript 拼接,或者代码主动使用了字符串 "null"。

排查步骤

1. 先查看 SecurePay 交易记录

在 SecurePay 商户后台找到时间和金额对应的交易,确认:

  • SecurePay 是否真正收到了授权请求;
  • 交易状态是批准、拒绝,还是根本没有创建;
  • 网关返回了什么响应代码和说明;
  • 请求使用的是测试环境还是生产环境;
  • 金额、币种和交易类型是否正确。

如果商户后台找不到这笔交易,问题很可能发生在 Zen Cart 向 SecurePay 发送请求之前。此时应优先检查 PHP 和 JavaScript 错误、接口地址以及模块配置。

如果后台有明确的拒绝代码,应以该代码为准,不要只看 Zen Cart 页面上的通用提示。

如果后台显示交易成功,但 Zen Cart 显示失败,应重点检查模块的响应解析、订单创建和返回跳转逻辑。同时确认是否已经扣款,以免重复提交测试订单。

2. 检查 Zen Cart 日志

查看 Zen Cart 的 logs 目录,重点检查支付失败时生成的 PHP 错误日志。常见线索包括:

Undefined array key
Undefined variable
Trying to access array offset on value of type null
Call to undefined method
Deprecated
Fatal error

重点关注以下位置:

  • SecurePay 支付模块文件;
  • FEC 扩展文件;
  • Checkout 页面模板;
  • Observer、Notifier 或自定义覆盖文件;
  • AJAX 结账处理程序。

不要把关闭错误显示当成解决办法。错误不再显示,并不代表请求或响应已经恢复正常。

3. 检查浏览器请求和 JavaScript 错误

打开浏览器开发者工具,重新提交一笔安全的测试订单,然后检查:

  • Console 中是否出现 JavaScript 异常;
  • Network 中哪个请求跳转到了 fecaction=null;
  • 请求采用普通表单提交还是 AJAX;
  • 跳转前的响应是否包含 PHP 警告、空 JSON 或非预期的 HTML;
  • 表单中是否存在预期的 action、token 和支付方式字段;
  • 是否发生了重复提交。

如果结账扩展期望服务器返回 JSON,实际收到的却是 PHP 警告或 HTML 错误页,前端代码就可能无法取得动作字段,随后把 null 拼入 URL。

4. 临时区分 FEC 与支付模块问题

可以在测试环境中暂时停用 FEC,改用 Zen Cart 原生结账流程完成一次测试。

可按以下结果判断:

  • 原生结账成功:优先检查 FEC 与 SecurePay 模块的兼容性;
  • 原生结账仍然失败:优先检查 SecurePay 模块和商户配置;
  • SecurePay 后台显示成功,但两种结账流程都失败:检查响应解析和订单创建逻辑。

不要直接在生产站点停用扩展,除非已经确认不会影响正在结账的用户。

5. 核对环境与配置

逐项确认:

  • 商户 ID、API key、密码或签名密钥前后没有空格;
  • 测试模式与接口地址匹配;
  • 商户账户允许当前交易类型;
  • 当前币种受商户账户和模块支持;
  • 服务器时间和时区正确;
  • HTTPS 证书及证书链有效;
  • PHP 已启用模块所需的扩展,例如 curl;
  • 服务器能够连接 SecurePay 接口;
  • Zen Cart、FEC 和 SecurePay 模块的版本组合兼容;
  • 最近是否升级过 PHP、Zen Cart、模板或结账扩展;
  • 是否存在同名支付模块文件、旧覆盖文件或未完成升级的文件。

增加安全的调试日志

如果模块没有调试模式,可以在测试环境中记录非敏感响应。具体变量名要根据实际使用的 SecurePay 模块调整:

$debugResponse = [
    'http_status'   => $httpStatus ?? null,
    'response_code' => $response['response_code'] ?? null,
    'message'       => $response['message'] ?? null,
    'transaction_id'=> $response['transaction_id'] ?? null,
];

error_log(
    'SecurePay response: ' .
    json_encode($debugResponse, JSON_UNESCAPED_SLASHES)
);

如需记录结账动作,应先过滤参数:

$fecAction = $_GET['fecaction'] ?? '';

error_log(
    'Checkout fecaction: ' .
    json_encode($fecAction, JSON_UNESCAPED_SLASHES)
);

不要记录以下信息:

  • 完整卡号;
  • CVV/CVC;
  • API 密钥或密码;
  • 完整请求头中的认证信息;
  • 未脱敏的个人资料。

生产环境调试结束后,应关闭额外日志,并妥善删除含有交易信息的临时日志。

对 fecaction 做防御性处理

如果已经确认问题来自自定义代码,可以只接受明确允许的动作值:

$allowedActions = [
    'process',
    'confirm',
    'update',
];

$fecAction = $_GET['fecaction'] ?? '';

if (!in_array($fecAction, $allowedActions, true)) {
    $fecAction = '';
}

前端在拼接 URL 前也要检查该值:

const fecAction = getCheckoutAction();

if (typeof fecAction !== 'string' || fecAction === '') {
  console.error('Invalid checkout action:', fecAction);
  return;
}

const url = new URL('index.php', window.location.origin);
url.searchParams.set('main_page', 'checkout');
url.searchParams.set('fecaction', fecAction);

window.location.assign(url.toString());

这些处理能阻止无效参数继续传播,但不能替代根因修复。如果动作值本应由服务器响应提供,仍要查清服务器为什么没有返回该值。

最可能的修复方向

根据目前的现象,建议按以下顺序处理:

  1. 从 SecurePay 后台取得这笔交易的实际响应代码;
  2. 查看同一时间生成的 Zen Cart PHP 日志;
  3. 在浏览器的 Network 和 Console 中找到首次产生 null 的请求;
  4. 在测试环境停用 FEC,验证原生结账是否正常;
  5. 核对 SecurePay 模块与当前 Zen Cart、PHP、FEC 版本的兼容性;
  6. 修正接口环境、凭据或响应解析问题;
  7. 更新或修补 FEC 中没有处理空动作值的代码。

如果 SecurePay 后台明确记录了拒绝,应根据网关响应代码处理支付问题。如果后台没有收到请求,或者显示交易已批准,那么 “card declined” 很可能只是支付模块的兜底提示。真正的故障更可能出在结账扩展、接口调用或响应解析环节。

备注:内容仅供参考。