网页应用商家扣款如何设置首次与后续扣款金额不同?
结论
可以实现,不需要 App。二维码只负责引导用户在支付宝完成签约。首次扣款和后续扣款的金额,由服务端在各自的交易请求中指定:
- 签约时建立商家扣款协议。
- 首次支付请求传入首次金额,例如
99.00元。 - 后续每次扣款时传入续费金额,例如
29.00元。
不要把 period_rule.single_amount 理解为后续每期的固定扣款金额。它通常表示协议允许的单次扣款上限或签约授权信息。每笔交易实际扣多少钱,仍取决于该次请求中的 total_amount。
推荐实现流程
1. 服务端创建签约请求
网页向服务端请求签约地址,再由服务端调用支付宝的周期扣款签约能力,例如:
alipay.user.agreement.page.sign
常见签约参数如下:
{
"external_agreement_no": "AGR_202609090001",
"personal_product_code": "CYCLE_PAY_AUTH_P",
"sign_scene": "INDUSTRY",
"period_rule": {
"period_type": "MONTH",
"period": 1,
"execute_time": 10,
"single_amount": 99.00
},
"sign_notify_url": "https://example.com/alipay/agreement/notify",
"return_url": "https://example.com/alipay/agreement/result"
}
需要注意:
external_agreement_no由商户生成,而且必须唯一。period_rule要与页面向用户展示并由用户同意的扣款规则一致。single_amount要覆盖协议期内可能出现的最高单次扣款金额。personal_product_code、sign_scene和周期规则字段,应以商户实际开通的支付宝产品及当前接口文档为准。
服务端取得签约页面地址后,可以将地址生成二维码并展示在网页上。用户用手机支付宝扫码即可完成签约,整个流程不依赖原生 App。
2. 等待签约成功通知
不能只根据浏览器的跳转结果认定签约成功。服务端需要验签并处理支付宝的异步通知,同时保存支付宝返回的协议号,例如:
{
"external_agreement_no": "AGR_202609090001",
"agreement_no": "2026xxxxxxxxxxxx",
"status": "NORMAL"
}
其中,agreement_no 是后续通过协议扣款的重要凭据。
如果异步通知暂时没有到达,可以调用协议查询接口确认最终状态。只有确认协议处于有效状态后,才能发起协议扣款。
3. 发起首次扣款
签约成功后,服务端立即创建第一笔扣款交易,并把 total_amount 设置为首次金额:
{
"out_trade_no": "FIRST_202609090001",
"total_amount": "99.00",
"subject": "会员首次开通费用",
"agreement_params": {
"agreement_no": "2026xxxxxxxxxxxx"
}
}
下面是示意 Java 代码。具体请求类和字段写法取决于当前使用的支付宝 SDK 版本:
AlipayTradePayRequest request = new AlipayTradePayRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", "FIRST_202609090001");
bizContent.put("total_amount", "99.00");
bizContent.put("subject", "会员首次开通费用");
JSONObject agreementParams = new JSONObject();
agreementParams.put("agreement_no", "2026xxxxxxxxxxxx");
bizContent.put("agreement_params", agreementParams);
request.setBizContent(bizContent.toJSONString());
AlipayTradePayResponse response = alipayClient.execute(request);
这里的 99.00 就是第一笔交易的实际支付金额。
部分支付宝产品支持“签约并支付”,可以让用户在一次操作中完成签约和首笔支付。如果商户开通的接口支持这种模式,需要在签约并支付请求的交易金额字段中填写首次金额。普通签约接口不足以判断具体接口和参数,仍要以商户后台对应产品的接入文档为准。
4. 后续按续费金额扣款
进入下一个扣款周期后,服务端创建一笔新交易,并将金额改为后续价格:
{
"out_trade_no": "RENEW_202610100001",
"total_amount": "29.00",
"subject": "会员月度续费",
"agreement_params": {
"agreement_no": "2026xxxxxxxxxxxx"
}
}
例如:
AlipayTradePayRequest request = new AlipayTradePayRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", "RENEW_202610100001");
bizContent.put("total_amount", "29.00");
bizContent.put("subject", "会员月度续费");
JSONObject agreementParams = new JSONObject();
agreementParams.put("agreement_no", "2026xxxxxxxxxxxx");
bizContent.put("agreement_params", agreementParams);
request.setBizContent(bizContent.toJSONString());
AlipayTradePayResponse response = alipayClient.execute(request);
金额之间的关系如下:
首次扣款:total_amount = 99.00
后续扣款:total_amount = 29.00
协议限额:single_amount >= 协议内最高单次扣款金额
即使签约规则中填写了金额,支付宝也不会自动为商户创建每期交易。商户仍要按照约定周期,由服务端逐笔发起扣款,并为每笔交易生成唯一的 out_trade_no。
另一种处理方式
如果当前签约产品不支持“签约并支付”,可以拆成两个步骤:
- 用户扫码完成纯签约。
- 服务端确认签约成功后,立即使用协议号发起第一笔扣款。
这样仍能实现首次扣 99.00 元、以后每期扣 29.00 元,只是签约和首笔交易分别使用两个支付宝请求。
如果首笔费用不属于周期扣款协议,也可以先通过普通支付接口收取首笔费用,再单独签订周期扣款协议。不过,页面必须明确展示首笔费用、后续费用、扣款周期和取消方式,避免用户误以为两笔费用适用同一套规则。
注意事项
- 金额必须由服务端计算,不能直接信任网页提交的金额。
- 每次扣款都要使用新的
out_trade_no。同一笔业务订单重试时,则必须沿用原订单号,避免重复扣款。 - 支付结果要以支付宝异步通知和主动查询结果为准,不能只看同步返回值。
- 发起扣款前,需要检查协议状态、扣款周期、单次限额,以及本期费用是否已经扣过。
- 如果首次金额高于后续金额,协议的单次限额必须覆盖首次金额,否则首笔扣款可能因超出授权范围而失败。
- 首次优惠、后续恢复原价等规则,要在签约页面向用户清楚说明,并与实际扣款逻辑一致。
- 商家扣款产品可能对扣款日期、频率、金额和适用行业有所限制。最终可用的接口、产品码及参数,要以商户账号实际开通的能力和支付宝当前接入文档为准。
备注:内容仅供参考。
