PHP:Moneris Vault 免费试用后无法设置 Recurring Bills
结论
data_key 没有问题。它是 Moneris Vault 中永久支付资料对应的令牌,可以用于 res_purchase_cc。
在 Live 环境中,如果 getRecurSuccess() 返回空值,通常表示响应里没有 recurring 结果字段。常见原因是生产商户账户尚未启用 Recurring Billing,或当前 store 没有相应权限。Sandbox 通常预先开放了测试功能,所以同一份代码在 Sandbox 成功,并不代表生产账户也已开通该功能。
当前代码还有一个配置问题:它设置的是每天扣款一次,而不是每月扣款一次。
'recur_unit' => 'day',
'period' => '1',
首先检查生产账户权限
联系 Moneris 支持或账户经理,确认以下事项:
- Live
store_id是否已启用 Recurring Billing。 - Recurring Billing 是否允许与 Vault 的
data_key配合使用。 - 生产账户是否接受金额为
0.00的res_purchase_cc。 - 当前交易是否需要 Credential on File(COF)相关字段。
- 使用的
crypt_type是否符合该商户账户和交易场景。
Sandbox 和生产环境的这些配置通常互相独立。如果生产端没有启用 recurring,主交易仍可能返回响应,但系统不会创建 recurring 计划,此时 getRecurSuccess() 可能为空。
不要只检查 getRecurSuccess()
读取 recurring 结果之前,先确认主交易是否获批,并记录完整的响应字段:
$mpgResponse = $mpgHttpPost->getMpgResponse();
$responseCode = $mpgResponse->getResponseCode();
$message = $mpgResponse->getMessage();
$complete = $mpgResponse->getComplete();
$receiptId = $mpgResponse->getReceiptId();
$recurSuccess = $mpgResponse->getRecurSuccess();
error_log('Moneris response: ' . print_r([
'response_code' => $responseCode,
'message' => $message,
'complete' => $complete,
'receipt_id' => $receiptId,
'recur_success' => $recurSuccess,
], true));
如果 PHP SDK 可以访问原始响应或 response tags,也应一并记录。不要把卡号、CVV、API token 或其他敏感支付数据写入日志。
getRecurSuccess() 为空与返回 "false" 是两种不同情况:
"true":Recurring 计划已创建。"false":响应中明确包含失败结果。- 空值:响应里没有该字段。常见原因包括主交易失败、Recurring 未启用,或请求没有被当作 recurring 交易处理。
修正每月计费配置
如果业务需求是免费试用一个月,之后每月收取 99.00,应将周期设为 month:
$startDate = (new DateTimeImmutable('today'))
->modify('+1 month')
->format('Y/m/d');
$recurArray = [
'recur_unit' => 'month',
'start_now' => 'false',
'start_date' => $startDate,
'num_recurs' => '98',
'period' => '1',
'recur_amount' => '99.00',
];
$mpgRecur = new mpgRecur($recurArray);
$mpgTxn->setRecur($mpgRecur);
当前的 day + period 1 会请求每天扣款一次,共执行 98 次。如果生产端接受了这项配置,实际扣款安排会严重偏离按月订阅的业务目标。
建议的完整处理方式
$clientName = $_POST['cust_id'] ?? '';
$billingKey = $_POST['data_key'] ?? '';
if ($clientName === '' || $billingKey === '') {
throw new RuntimeException('Missing cust_id or data_key');
}
$txnArray = [
'type' => 'res_purchase_cc',
'data_key' => $billingKey,
'order_id' => $orderID,
'cust_id' => $clientName,
'amount' => '0.00',
'crypt_type' => '7',
];
$mpgTxn = new mpgTransaction($txnArray);
$startDate = (new DateTimeImmutable('today'))
->modify('+1 month')
->format('Y/m/d');
$recurArray = [
'recur_unit' => 'month',
'start_now' => 'false',
'start_date' => $startDate,
'num_recurs' => '98',
'period' => '1',
'recur_amount' => '99.00',
];
$mpgTxn->setRecur(new mpgRecur($recurArray));
$mpgRequest = new mpgRequest($mpgTxn);
$mpgHttpPost = new mpgHttpsPost(
$store_id,
$api_token,
$mpgRequest
);
$mpgResponse = $mpgHttpPost->getMpgResponse();
$responseCode = $mpgResponse->getResponseCode();
$message = $mpgResponse->getMessage();
$recurSuccess = $mpgResponse->getRecurSuccess();
if ($recurSuccess !== 'true') {
error_log('Recurring setup failed or was not processed: ' . print_r([
'response_code' => $responseCode,
'message' => $message,
'recur_success' => $recurSuccess,
'order_id' => $orderID,
], true));
throw new RuntimeException(
'The payment profile was saved, but recurring billing was not created.'
);
}
即使 Vault 中已经出现永久 profile,也不能直接把订阅标记为有效。Vault 保存成功与 recurring 计划创建成功是两个独立的结果。
关于 data_key 与 Credential on File
data_key 是 Vault 令牌。Credential on File 则是一组规则或交易标识,用来描述支付凭据的保存方式、客户授权以及后续交易的发起方。两者不能互相替代。
因此:
- 使用
data_key这个方向是正确的。 - 是否必须同时提交 COF 字段,取决于 Moneris 当前接口、账户配置、SDK 版本和交易类型。
- 缺少 COF 信息可能导致生产交易被拒绝或不符合要求,但不能仅凭
getRecurSuccess()为空就认定 COF 是根因。 - 应请 Moneris 根据生产
store_id、订单号、交易时间和响应码检查服务端日志,同时确认 COF 与crypt_type的具体配置要求。
还需要注意
+30 day 不等于下个月的同一天。例如跨越二月时,两种计算方式可能得到不同的日期。按月订阅应优先使用 recur_unit => month,并明确处理月末日期规则。
如果生产账户不接受 0.00 的 res_purchase_cc,不要擅自改成小额扣款后立即退款。应向 Moneris 确认其支持的无扣款验证方式,或正确的 Vault 和订阅建立流程,因为这涉及持卡人授权、账单展示和支付合规要求。
建议按以下顺序排查:先确认 Live Recurring Billing 已开通,再检查主交易响应,然后确认零金额交易、COF 字段和 crypt_type 在生产环境中的要求,最后把周期从 day 修正为 month。
备注:内容仅供参考。
