PAYATHON 2026

如何通过 Authorize.net API 收取逾期费用

支付小周

结论

可以,但 Authorize.net 没有专门用于处理“逾期费用”或“未结余额”的 API。

一般按以下方式实现:

  1. 学校的业务系统负责计算逾期费用和未结余额。
  2. 获取 Customer Information Manager(CIM)中保存的 customerProfileId 和 paymentProfileId。
  3. 调用 createTransactionRequest,通过 authCaptureTransaction 创建一笔独立交易。
  4. 在本地账务系统中记录这笔交易对应的学生、账单、逾期费用,以及 Authorize.net 返回的 transId。

无论客户是否加入 recurring billing subscription,只要有可用的 Customer Profile 和 Payment Profile,并且学校取得了相应的扣款授权,就可以尝试发起单次扣款。

为什么不能直接通过订阅 API 收取

Recurring Billing Subscription 用于按既定计划自动扣款,不负责管理学校的应收账款,也不会自动判断某笔费用是否逾期。

不同类型的费用应分别处理:

  • 正常的周期性学费由 recurring billing subscription 收取。
  • 一次性逾期费用通过 createTransactionRequest 单独收取。
  • 历史未结余额在计算出实际金额后,通过 createTransactionRequest 单独收取。
  • 如果需要永久调整以后每期的金额,可以在业务规则允许的情况下更新订阅,不要把一次性逾期费用混入后续周期。

Create a Subscription from Customer Profile 适合创建未来的周期性收费计划,不适合立即收取临时产生的逾期费用。

推荐实现流程

1. 在学校系统中计算余额

Authorize.net 不会替学校维护“应付金额”“逾期天数”或“滞纳金规则”。这些数据应由学校自己的账务系统管理,例如:

amount_to_charge = overdue_principal + late_fee

计算时应保留费用明细,不能只保存合计金额:

{
  "invoiceId": "INV-2026-00831",
  "overduePrincipal": "120.00",
  "lateFee": "10.00",
  "amountToCharge": "130.00"
}

处理财务金额时,应使用十进制定点数或最小货币单位,避免直接使用二进制浮点数计算。

2. 确定 Customer Profile 和 Payment Profile

需要取得以下两个 ID:

  • customerProfileId
  • paymentProfileId

可以通过 getCustomerProfileRequest 或 getCustomerPaymentProfileRequest 检查支付资料是否仍然存在。返回的银行卡信息通常经过掩码处理。发起扣款时不需要,也不应重新取得完整卡号。

如果一个客户保存了多个 Payment Profile,学校必须在自己的系统中明确哪种支付方式已获授权,可用于收取逾期费用,不能随意选择。

3. 创建独立交易

下面是使用 Customer Profile 发起 authCaptureTransaction 的 JSON 示例:

{
  "createTransactionRequest": {
    "merchantAuthentication": {
      "name": "API_LOGIN_ID",
      "transactionKey": "TRANSACTION_KEY"
    },
    "refId": "latefee-INV-2026-00831",
    "transactionRequest": {
      "transactionType": "authCaptureTransaction",
      "amount": "130.00",
      "profile": {
        "customerProfileId": "123456789",
        "paymentProfile": {
          "paymentProfileId": "987654321"
        }
      },
      "order": {
        "invoiceNumber": "INV-2026-00831",
        "description": "Overdue balance and late fee"
      },
      "transactionSettings": {
        "setting": {
          "settingName": "duplicateWindow",
          "settingValue": "120"
        }
      }
    }
  }
}

各字段的用途如下:

  • amount 是本次实际收取的总额。
  • refId 用于关联学校内部的收费请求,但不能代替本地幂等控制。
  • invoiceNumber 建议填写学校账单号。
  • description 可以说明交易用途,但不要包含儿童姓名、健康信息等不必要的敏感数据。
  • duplicateWindow 只能协助发现短时间内的疑似重复交易,不能作为完整的防重复扣款机制。

如果业务需要先授权,再确认扣款,可以使用 authOnlyTransaction,之后对已授权的交易执行 capture。对于一般的即时逾期费用,使用 authCaptureTransaction 通常更直接。

响应处理

不能只检查 HTTP 状态码,还需要检查 API 消息和交易级响应,例如:

function handleAuthorizeNetResponse(response) {
  const apiResult = response?.messages?.resultCode;
  const transaction = response?.transactionResponse;

  if (
    apiResult === "Ok" &&
    transaction?.responseCode === "1" &&
    transaction?.transId
  ) {
    return {
      success: true,
      transactionId: transaction.transId,
      authorizationCode: transaction.authCode
    };
  }

  return {
    success: false,
    responseCode: transaction?.responseCode,
    errors: transaction?.errors || response?.messages?.message || []
  };
}

交易成功后,至少应保存以下信息:

  • 内部账单号
  • customerProfileId
  • paymentProfileId
  • 收取金额和费用明细
  • transId
  • 授权码
  • 交易时间
  • 当前结算状态

只有确认交易成功后,才能把对应账单标记为已支付。如果交易被拒绝,应保留未结余额,并按照学校的重试和通知规则处理。

已加入订阅的客户

客户已经加入 recurring billing subscription,不代表所有额外费用都会自动计入订阅。

对于一次性逾期费用,可以继续使用同一个 Customer Profile 下经过授权的 Payment Profile,另行创建一笔交易。不要为了收取一次逾期费用而新建订阅,也不要先修改订阅金额再立即改回,否则会增加账务对账和失败恢复的复杂度。

还需要防止以下情况:

  • 订阅当天已经完成扣款,学校系统又针对同一余额发起扣款。
  • 订阅扣款失败后,自动重试程序和人工补扣同时发起交易。
  • 修改订阅金额后,又单独收取了相同的逾期费用。

可以在本地为每次收费生成唯一的业务键,例如:

customerId + invoiceId + feeType + billingPeriod

发起 API 请求前,可以对该业务键设置唯一约束或事务锁,防止并发请求造成重复收费。

没有订阅的客户

没有 recurring billing subscription 不影响单次扣款。只要客户已有有效的 Customer Profile 和 Payment Profile,就可以通过同样的方式创建交易。

如果客户还没有 Payment Profile,需要先通过符合 PCI 要求的方式采集支付信息并创建 Payment Profile。除非现有系统和合规范围明确允许,否则不要让银行卡数据经过学校自己的服务器。通常应优先使用 Authorize.net 提供的托管式支付信息采集方案。

部分付款和剩余余额

如果只收取部分余额,本次扣款金额应由学校系统确定。交易成功后,再更新剩余余额:

remaining_balance = previous_balance - captured_amount

每笔付款都应保存为独立的账务记录。Authorize.net 返回的是支付交易结果,不是学校账单的最终余额,因此不能只依靠 Authorize.net 的交易列表重建完整的应收账款。

退款也应关联原交易,并通过相应的退款交易流程处理,不能只在本地调低余额。

注意事项

扣款授权

技术上可以使用已保存的 Payment Profile,不代表学校一定有权收取逾期费用。学校应确认:

  • 家长已事先同意保存支付方式。
  • 授权条款明确涵盖额外费用或逾期费用。
  • 逾期费用的金额或计算方式已经充分披露。
  • 通知、宽限期和扣款时间符合合同及适用法律。
  • 已确认银行卡网络对 stored credential 或 merchant-initiated transaction 的要求。

具体的法律和卡组织要求会因地区、商户类别、收单机构及账户配置而异,应由学校的法律顾问、支付服务商或收单机构确认。

交易失败

支付资料存在并不代表扣款一定会成功。常见的失败原因包括卡片过期、资金不足、发卡行拒绝,以及 Payment Profile 已失效。

不要无限自动重试。学校应设置明确的重试次数、重试间隔和家长通知流程,并记录每次尝试。

对账和通知

可以使用 Webhook 或定期查询交易,处理后续状态变化,并把结算、退款、撤销和拒付同步到学校账务系统。API 返回“已授权或已提交”,不一定表示资金已经完成最终结算。

测试

应先在 Authorize.net Sandbox 中验证以下场景:

  • 正常收取逾期费用
  • Payment Profile 无效
  • 发卡行拒绝
  • 重复提交
  • 部分付款
  • 订阅扣款和额外扣款发生在同一天
  • 退款和撤销
  • API 请求超时,但交易实际已经创建

最后一种场景尤其需要注意。请求超时后不要直接重发,应先根据本地业务键、refId 和 Authorize.net 交易记录确认第一次请求是否成功,以免重复扣款。

备注:内容仅供参考。