Automated Recurring Billing 拒付后能否补扣款?
明确结论
在 ARB(Automated Recurring Billing)中,更新 subscription 的信用卡信息不会自动补扣之前失败的付款,也不会立即触发扣款。
针对题述场景:
- 14 日失败的 payment 不会因为 16 日更新信用卡而由 ARB 自动重试。
ARBUpdateSubscriptionRequest只更新 subscription 信息,不执行交易,也不会自动把 payment cycle 改成每月 16 日。- 原 subscription 通常仍会按照既定计划,在下个月 14 日发起下一次扣款。
- 如果要收回本月欠款,应通过普通单笔交易接口单独扣款,例如使用
createTransactionRequest的authCaptureTransaction。 - subscription 显示为
active,只说明订阅计划仍然有效,不代表最近一期付款成功。
因此,系统需要分别处理”订阅状态”和”每一期交易结果”。
为什么扣款失败后仍然是 active
subscription status 描述的是整个 recurring subscription 的生命周期,例如:
activesuspendedcanceledexpiredterminated
单笔交易被 declined,并不代表整个 subscription 一定失效。只要订阅没有被取消、终止、到期或转为其他非活动状态,仍可能返回 active。
因此,不能只根据以下条件:
subscription.status == "active"
判断用户已经支付当前账期。
会员本期是否已经付费,应以对应交易的实际结果为准,例如:
- Transaction Reporting API 返回的交易状态;
- 网关发送的 webhook;
- ARB transaction notification;
- 交易的
responseCode、transactionStatus和transId。
建议在业务数据库中分别保存:
subscription_status
current_billing_period
current_period_payment_status
gateway_transaction_id
decline_reason
grace_period_end
subscription_status 用于记录订阅生命周期,current_period_payment_status 用于记录当前账期是否结清。
更新信用卡后的实际流程
假设原付款日为每月 14 日:
4 月 14 日:ARB 发起周期扣款,交易被拒绝
4 月 14 日:系统记录本期 payment 为 failed
4 月 14~16 日:用户进入 grace period
4 月 16 日:用户提交新信用卡
4 月 16 日:系统调用 ARBUpdateSubscriptionRequest
4 月 16 日:仅更新订阅支付信息,不自动扣款
4 月 16 日:系统另外发起单笔 authCaptureTransaction,收取 4 月欠款
5 月 14 日:ARB 使用新卡处理下一期正常扣款
如果系统没有发起单笔交易,4 月 14 日失败的金额通常会继续保持未支付状态。ARB 不会仅因信用卡信息更新而自动追回这笔款项。
账户的最终表现还可能受到商户账户配置、网关的 ARB 重试策略以及 subscription 实际状态影响。上线前,应在 Authorize.Net sandbox 中测试 active、suspended 和 declined transaction 等场景。
推荐的解决步骤
1. 记录失败账期
收到 declined 结果后,不要立即把 subscription 视为失效,而应将对应账期标记为失败:
subscription_status = active
current_period_payment_status = failed
grace_period_end = 2026-04-16T23:59:59Z
同时保存:
subscriptionId- 原扣款日期
- 应付金额
transId,如果网关返回responseCoderesponseReasonCode- 错误信息
程序判断不能只依赖错误文本 "this transaction has been declined",应优先保存和检查结构化响应码。
2. 安全更新信用卡信息
用户更新信用卡后,调用 ARBUpdateSubscriptionRequest 更新 payment profile。
示意请求如下:
<?xml version="1.0" encoding="utf-8"?>
<ARBUpdateSubscriptionRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
<merchantAuthentication>
<name>API_LOGIN_ID</name>
<transactionKey>TRANSACTION_KEY</transactionKey>
</merchantAuthentication>
<subscriptionId>12345678</subscriptionId>
<subscription>
<payment>
<creditCard>
<cardNumber>4111111111111111</cardNumber>
<expirationDate>2028-12</expirationDate>
<cardCode>123</cardCode>
</creditCard>
</payment>
</subscription>
</ARBUpdateSubscriptionRequest>
以上仅为结构示例。生产环境不应记录完整卡号、cardCode 或 transactionKey,支付数据也应按照 PCI DSS 要求处理。更稳妥的方式是使用 Authorize.Net 提供的托管支付或 token/profile 机制,避免信用卡信息直接经过业务服务器。
3. 单独补收失败款项
确认 subscription 更新成功后,通过普通交易 API 发起一笔 authCaptureTransaction:
<?xml version="1.0" encoding="utf-8"?>
<createTransactionRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
<merchantAuthentication>
<name>API_LOGIN_ID</name>
<transactionKey>TRANSACTION_KEY</transactionKey>
</merchantAuthentication>
<refId>subscription-12345678-period-2026-04</refId>
<transactionRequest>
<transactionType>authCaptureTransaction</transactionType>
<amount>29.99</amount>
<payment>
<creditCard>
<cardNumber>4111111111111111</cardNumber>
<expirationDate>2028-12</expirationDate>
<cardCode>123</cardCode>
</creditCard>
</payment>
<order>
<invoiceNumber>ARB-12345678-2026-04</invoiceNumber>
<description>Recurring payment recovery</description>
</order>
</transactionRequest>
</createTransactionRequest>
如果系统使用 Customer Profiles,应提交对应的 customer/payment profile 标识,不要再次传输完整信用卡信息。
补扣成功后:
current_period_payment_status = paid
payment_source = manual_recovery
gateway_transaction_id = returned_trans_id
如果补扣再次失败,应保留 grace period,或按照业务规则暂停服务,不要无限自动重试。
4. 保留原来的周期计划
补扣交易和 ARB subscription 是两条不同的支付路径:
单笔补扣:解决已经逾期的账款
ARB subscription:处理未来每月 14 日的正常账款
单独补扣不会改变 subscription 的下一次计划日期,更新信用卡信息也不等于修改 ARB schedule。
如果确实要把今后的扣款日改为每月 16 日,应先确认当前 Authorize.Net API 是否允许修改该 subscription 的对应 schedule 字段。部分 schedule 信息在 subscription 创建后无法直接修改,这种情况下通常需要取消原 subscription,再创建一个以 16 日为 startDate 的新 subscription。
重建 subscription 前,应妥善处理首期金额和开始日期,否则可能重复扣款或少收一期。
防止重复补扣
“更新信用卡”和”补扣欠款”是两个独立请求。网络超时或用户重复提交都可能引发重复交易,因此补扣接口应设计为幂等操作。
例如使用唯一业务键:
recovery:{subscriptionId}:{billingPeriod}
示例:
recovery:12345678:2026-04
发起补扣前检查:
payment = findPayment(subscriptionId, billingPeriod)
if payment.status == "paid":
return payment
if payment.recoveryRequestAlreadyProcessing:
return existingResult
markRecoveryAsProcessing(payment)
result = createAuthCaptureTransaction(payment.amount)
if result.approved:
markPaymentAsPaid(payment, result.transId)
else:
markRecoveryAsFailed(payment, result.responseCode)
API 请求超时后,不要立即重新扣款。超时可能只说明业务系统没有收到响应,原交易仍可能已经被网关处理。此时应先通过 Transaction Reporting API,按照 transId、refId 或业务订单号核对交易结果。
关于自动重试
不要默认认为 ARB 会在信用卡更新后重试历史 declined transaction。业务流程应遵循以下原则:
- 每个 scheduled payment 都是一笔独立交易;
- 已失败的历史交易不会因更新 subscription 而自动变成成功;
- 更新信用卡主要影响未来的 scheduled payments;
- 历史欠款应由商户系统明确发起单笔补扣;
- 是否存在额外的自动重试行为,应以当前商户账户配置和 Authorize.Net 官方文档为准。
即使账户支持某种重试机制,也应避免”平台自动重试”与”业务系统手工补扣”同时运行,否则同一期账款可能被收取两次。
其他注意事项
- 不要因为 subscription 是
active就立即恢复会员权益,还应检查当前账期的 payment 状态。 declined不一定是信用卡过期造成的,也可能与余额不足、发卡行拒绝、风控限制或支付信息不匹配有关。- 不要向最终用户完整展示网关返回的具体拒绝原因,应使用便于用户理解的通用提示。
- 更新信用卡成功不等于补扣成功,两个操作的响应必须分别检查。
- 补扣金额应绑定原账期和唯一订单号,以便后续退款、对账和处理争议。
- 建议以 webhook 或交易通知驱动主流程,再用 Transaction Reporting API 核对结果并执行补偿查询。
- webhook 处理必须支持重复通知和乱序到达。
- 如需自动重试,应设置有限的重试次数、合理的间隔和明确的停止条件,并检查发卡网络及当地法规对重复扣款的要求。
备注:内容仅供参考。
