Authorize.Net recurring billing 的金额更新与续费控制
结论
- 已创建的 Authorize.Net ARB subscription 支持更新
amount。新金额只适用于尚未进入处理流程的后续扣款,不会补收当前周期的差价,也不会按剩余天数自动计算 prorate。 - ARB 会自动续费,但不提供可以随时切换的
autoRenew布尔字段。关闭自动续费通常需要通过ARBCancelSubscriptionRequest取消订阅。 - ARB subscription 取消后无法直接恢复。用户重新开启自动续费时,通常需要创建新的 subscription。
- 关闭自动续费后,可以通过 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 的页面地址发给用户。可以按以下流程实现:
- 向用户发送业务系统自己的链接,例如
https://example.com/renew/abc123。 - 用户打开链接后,由后端验证订单、用户身份、金额和到期状态。
- 后端实时调用
getHostedPaymentPageRequest。 - 页面使用新 token 跳转到 Accept Hosted。
- 收到付款结果并完成核实后,更新服务到期时间。
- 如果用户重新选择自动续费,再创建新的 ARB subscription。
业务付款链接应使用不可预测、会过期且只能使用一次的标识。收费金额必须由后端根据订单重新计算,不能把浏览器传入的金额作为最终收费依据。
实现时需要注意
- 更新
amount只影响后续扣款,不处理 prorate、补款或退款。 - ARB subscription 取消后,不能把原 subscription 当作暂停记录重新启用。
- 修改金额或取消订阅后,应保存网关响应和
subscriptionId,并同步更新本地状态。 - API 凭据只能保存在服务端配置或密钥管理系统中。
- 套餐升级、一次性补差价和周期续费应分别使用不同的业务订单号,以免重复收费。
- 不能只根据前端跳转结果确认付款,应通过服务端查询或网关通知核实交易状态。
- Sandbox 与 Production 的 API 地址、Accept Hosted 地址和凭据不能混用。
备注:内容仅供参考。
