PAYATHON 2026

网页应用商家扣款如何设置首次与后续扣款金额不同?

支付老李

结论

可以实现,不需要 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。

另一种处理方式

如果当前签约产品不支持“签约并支付”,可以拆成两个步骤:

  1. 用户扫码完成纯签约。
  2. 服务端确认签约成功后,立即使用协议号发起第一笔扣款。

这样仍能实现首次扣 99.00 元、以后每期扣 29.00 元,只是签约和首笔交易分别使用两个支付宝请求。

如果首笔费用不属于周期扣款协议,也可以先通过普通支付接口收取首笔费用,再单独签订周期扣款协议。不过,页面必须明确展示首笔费用、后续费用、扣款周期和取消方式,避免用户误以为两笔费用适用同一套规则。

注意事项

  • 金额必须由服务端计算,不能直接信任网页提交的金额。
  • 每次扣款都要使用新的 out_trade_no。同一笔业务订单重试时,则必须沿用原订单号,避免重复扣款。
  • 支付结果要以支付宝异步通知和主动查询结果为准,不能只看同步返回值。
  • 发起扣款前,需要检查协议状态、扣款周期、单次限额,以及本期费用是否已经扣过。
  • 如果首次金额高于后续金额,协议的单次限额必须覆盖首次金额,否则首笔扣款可能因超出授权范围而失败。
  • 首次优惠、后续恢复原价等规则,要在签约页面向用户清楚说明,并与实际扣款逻辑一致。
  • 商家扣款产品可能对扣款日期、频率、金额和适用行业有所限制。最终可用的接口、产品码及参数,要以商户账号实际开通的能力和支付宝当前接入文档为准。

备注:内容仅供参考。