PAYATHON 2026

Authorize.Net recurring billing 的金额更新与续费控制

技术老齐

结论

  1. 已创建的 Authorize.Net ARB subscription 支持更新 amount。新金额只适用于尚未进入处理流程的后续扣款,不会补收当前周期的差价,也不会按剩余天数自动计算 prorate。
  2. ARB 会自动续费,但不提供可以随时切换的 autoRenew 布尔字段。关闭自动续费通常需要通过 ARBCancelSubscriptionRequest 取消订阅。
  3. ARB subscription 取消后无法直接恢复。用户重新开启自动续费时,通常需要创建新的 subscription。
  4. 关闭自动续费后,可以通过 Accept Hosted 收取下一期费用。Accept Hosted 返回的是短期有效的 hosted payment token,无法作为固定付款链接长期保存和重复发送。实际使用时,应先向用户发送业务系统自己的付款 URL,再由后端按需生成 token。

更新后续订阅金额

在这个示例中,可以把 subscription 的 amount 从 50.00 改为 60.00。更新成功后,尚未处理的 billing cycle 会按 60.00 扣款。

请求结构如下:

{
  "ARBUpdateSubscriptionRequest": {
    "merchantAuthentication": {
      "name": "YOUR_API_LOGIN_ID",
      "transactionKey": "YOUR_TRANSACTION_KEY"
    },
    "refId": "order-12345",
    "subscriptionId": "12345678",
    "subscription": {
      "amount": "60.00"
    }
  }
}

Sandbox 请求地址:

https://apitest.authorize.net/xml/v1/request.api

Production 请求地址:

https://api.authorize.net/xml/v1/request.api

如果当前 .NET SDK 的更新模型只提供银行卡字段,应先核对 SDK 版本和所用的请求类型。也可以绕过 SDK 封装,直接向 Authorize.Net API 发送 JSON 请求。

在 ASP.NET Web API 中可以这样提交:

using System.Net.Http.Json;

public sealed class AuthorizeNetArbService
{
    private readonly HttpClient _httpClient;
    private readonly string _apiLoginId;
    private readonly string _transactionKey;

    public AuthorizeNetArbService(
        HttpClient httpClient,
        IConfiguration configuration)
    {
        _httpClient = httpClient;
        _apiLoginId = configuration["AuthorizeNet:ApiLoginId"]!;
        _transactionKey = configuration["AuthorizeNet:TransactionKey"]!;
    }

    public async Task<string> UpdateAmountAsync(
        string subscriptionId,
        decimal amount,
        CancellationToken cancellationToken = default)
    {
        var request = new
        {
            ARBUpdateSubscriptionRequest = new
            {
                merchantAuthentication = new
                {
                    name = _apiLoginId,
                    transactionKey = _transactionKey
                },
                refId = $"subscription-{subscriptionId}",
                subscriptionId,
                subscription = new
                {
                    amount = amount.ToString(
                        "0.00",
                        System.Globalization.CultureInfo.InvariantCulture)
                }
            }
        };

        using var response = await _httpClient.PostAsJsonAsync(
            "https://apitest.authorize.net/xml/v1/request.api",
            request,
            cancellationToken);

        var responseBody = await response.Content.ReadAsStringAsync(
            cancellationToken);

        response.EnsureSuccessStatusCode();
        return responseBody;
    }
}

HTTP 200 不能单独作为更新成功的依据。还要解析响应中的 messages.resultCode 和错误消息,因为网关可能通过正常的 HTTP 响应返回业务错误。

如何处理本期增加的 license

如果在 1 月 15 日把金额改为 60.00,Authorize.Net 不会自动收取当前周期新增 license 对应的 10.00,也不会回算 1 月 15 日至下次账单日之间的费用。

通常有两种处理方式:

  • 新 license 立即生效:创建一次性 authCaptureTransaction,收取全部差价或按天计算的差价,同时把 subscription 更新为 60.00。
  • 新 license 从下个周期生效:只更新 subscription 金额,从下个周期开始按 60.00 扣款。

ARB 只管理后续的计划扣款。修改金额后,Authorize.Net 不会自动补扣差价;套餐升级、按比例计费和补差价都需要业务系统自行计算。

如果在临近扣款日时更新金额,当前批次可能已经进入网关处理流程。应预留处理时间,并通过交易通知或查询结果核对实际扣款金额。

自动续费如何开启和关闭

ARB subscription 创建后,会按 paymentSchedule 自动执行,直至达到 totalOccurrences、subscription 被取消,或者因支付或账户问题无法继续处理。

要开启自动续费,需要创建并保留一个有效的 ARB subscription。关闭自动续费时,则调用取消接口:

{
  "ARBCancelSubscriptionRequest": {
    "merchantAuthentication": {
      "name": "YOUR_API_LOGIN_ID",
      "transactionKey": "YOUR_TRANSACTION_KEY"
    },
    "refId": "cancel-12345",
    "subscriptionId": "12345678"
  }
}

应用层可以为用户提供续费开关,但不能只修改本地数据库:

  • 用户关闭续费时,调用 ARBCancelSubscriptionRequest。请求成功后,记录取消时间和当前服务到期时间。
  • 已支付周期内的服务仍可继续使用。
  • 用户再次开启续费时,创建新的 subscription,并保存新的 subscriptionId。
  • 本地记录 autoRenew = false 不代表取消已经完成,还必须确认 Authorize.Net 返回成功。

取消 subscription 不会自动退款,也不会撤销已经进入处理流程的交易。如果用户在临近扣款日时关闭续费,应处理好取消请求与下一次扣款同时发生的情况。

关闭自动续费后的付款链接

ARB 不提供取消订阅后自动生成下一期付款链接的标准流程。一次性付款可以通过 Accept Hosted 完成。

后端先申请 hosted payment token:

{
  "getHostedPaymentPageRequest": {
    "merchantAuthentication": {
      "name": "YOUR_API_LOGIN_ID",
      "transactionKey": "YOUR_TRANSACTION_KEY"
    },
    "refId": "renewal-12345",
    "transactionRequest": {
      "transactionType": "authCaptureTransaction",
      "amount": "60.00"
    },
    "hostedPaymentSettings": {
      "setting": [
        {
          "settingName": "hostedPaymentReturnOptions",
          "settingValue": "{\"showReceipt\":true,\"url\":\"https://example.com/payment/complete\",\"urlText\":\"Continue\",\"cancelUrl\":\"https://example.com/payment/cancel\",\"cancelUrlText\":\"Cancel\"}"
        },
        {
          "settingName": "hostedPaymentButtonOptions",
          "settingValue": "{\"text\":\"Pay now\"}"
        }
      ]
    }
  }
}

取得 token 后,通过表单打开 Authorize.Net 托管的付款页面:

<form method="post"
      action="https://test.authorize.net/payment/payment">
    <input type="hidden" name="token" value="HOSTED_PAYMENT_TOKEN">
    <button type="submit">Pay now</button>
</form>

生产环境使用:

https://accept.authorize.net/payment/payment

hosted payment token 有有效期,因此不适合提前数天把含 token 的页面地址发给用户。可以按以下流程实现:

  1. 向用户发送业务系统自己的链接,例如 https://example.com/renew/abc123。
  2. 用户打开链接后,由后端验证订单、用户身份、金额和到期状态。
  3. 后端实时调用 getHostedPaymentPageRequest。
  4. 页面使用新 token 跳转到 Accept Hosted。
  5. 收到付款结果并完成核实后,更新服务到期时间。
  6. 如果用户重新选择自动续费,再创建新的 ARB subscription。

业务付款链接应使用不可预测、会过期且只能使用一次的标识。收费金额必须由后端根据订单重新计算,不能把浏览器传入的金额作为最终收费依据。

实现时需要注意

  • 更新 amount 只影响后续扣款,不处理 prorate、补款或退款。
  • ARB subscription 取消后,不能把原 subscription 当作暂停记录重新启用。
  • 修改金额或取消订阅后,应保存网关响应和 subscriptionId,并同步更新本地状态。
  • API 凭据只能保存在服务端配置或密钥管理系统中。
  • 套餐升级、一次性补差价和周期续费应分别使用不同的业务订单号,以免重复收费。
  • 不能只根据前端跳转结果确认付款,应通过服务端查询或网关通知核实交易状态。
  • Sandbox 与 Production 的 API 地址、Accept Hosted 地址和凭据不能混用。

备注:内容仅供参考。