H5 如何接入支付宝支付并签约实现自动续费
结论
按照题述接口文档限定的终端范围,普通 H5/PC 网站无法通过 alipay.trade.wap.pay,在同一个支付宝收银台内完成“首笔付款 + 自动续费签约”。H5 支持网页支付和独立签约,但这是两次独立的业务操作,不能靠拼接参数合并成一次。
如果必须在一次流程中完成付款和签约,通常有两种方案:
- 将实际交易页面迁移到支付宝小程序或原生 App,申请并接入支付宝的“支付并签约”能力。H5 只负责引导用户进入受支持的终端。
- 联系支付宝商务或技术支持,确认当前商户能否申请专门的行业产品或定向能力。有些页面看起来由 H5 发起,实际进入的可能是支付宝小程序、原生收银台或特定行业产品,不能仅凭页面样式判断它使用了 H5 接口。
不要把 H5 请求伪装成 App 或小程序请求。即使客户端可以拉起支付宝,只要服务端接口、应用类型、产品权限或签约场景不匹配,仍然无法使用“支付并签约”能力。
为什么独立签约不能同时完成付款
alipay.user.agreement.page.sign 用于建立代扣协议。签约成功后,支付宝会返回协议号或签约状态,但该操作本身不是消费交易,因此不会完成首笔付款。
alipay.trade.wap.pay 用于完成 H5 网页支付。如果当前产品文档没有说明它支持 agreement_sign_params,就不能在 biz_content 中额外加入签约参数,把它变成支付并签约接口。
两个流程使用的业务对象也不同:
- 支付通过
out_trade_no标识商户订单。 - 签约通过
external_agreement_no标识商户侧协议。 - 支付结果以支付异步通知为准。
- 签约结果以签约异步通知或协议查询结果为准。
独立接入时,系统必须分别记录订单状态和协议状态,不能把用户返回页面作为最终成功的依据。
推荐接入方案
方案一:H5 引导进入支付宝小程序
需要“一次完成付款和签约”时,这种方案更合适。
基本流程如下:
- 用户在 H5 选择自动续费套餐。
- 服务端创建业务订单,生成唯一的
out_trade_no和external_agreement_no。 - H5 通过支付宝官方支持的方式打开指定的支付宝小程序页面。
- 小程序向商户服务端申请支付并签约参数。
- 服务端调用已获准的支付并签约接口,并对请求参数签名。
- 小程序调用支付宝提供的对应客户端能力,进入支付并签约收银台。
- 服务端分别处理支付通知和签约通知。
- 支付成功且协议签约成功后,业务系统才将订阅标记为生效。
在这个流程中,H5 只是入口,真正发起支付并签约的是支付宝小程序。该能力能否使用,仍取决于应用类型、商户签约产品、行业准入和接口权限。
前端不应自行拼装支付参数,完整参数应由服务端生成。例如:
async function createPayment() {
const response = await fetch("/api/subscriptions/pay-and-sign", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
planId: "plan_monthly"
})
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.message || "创建支付签约请求失败");
}
// result.orderString 或其他字段应直接交给支付宝对应客户端 API。
// 具体调用方式以获准产品的小程序接入文档为准。
return result;
}
这里无法给出适用于所有商户的客户端 API 名称和参数结构。不同产品能力的调用方式可能不同,应以商户后台实际开通后显示的接口文档为准。
方案二:H5 分两步完成
如果暂时无法建设支付宝小程序或 App,就需要让用户完成两次操作。常见的流程顺序有两种。
先付款,后签约
- H5 调用
alipay.trade.wap.pay完成首笔付款。 - 服务端收到支付成功异步通知。
- 支付结果页提示用户开通自动续费。
- 用户确认后进入
alipay.user.agreement.page.sign独立签约页面。 - 服务端收到签约通知后启用自动续费。
这种顺序可以避免用户尚未付款就留下代扣协议,但部分用户可能在付款后不再继续签约。
先签约,后完成首笔交易
- 用户先通过
alipay.user.agreement.page.sign签约。 - 服务端确认协议已经生效。
- 再完成首笔支付,或按照已开通产品的规定执行首次扣款。
- 首笔交易成功后启用订阅。
采用这种顺序时,必须单独处理“签约成功但首次交易失败”的情况。交易失败后应暂停服务,并根据产品规则决定是否解约,不能默认订阅已经生效。
H5 网页支付示例
以下示例只生成 H5 支付页面,不包含自动续费签约:
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setNotifyUrl("https://example.com/api/alipay/payment/notify");
request.setReturnUrl("https://example.com/payment/result");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609090001",
"total_amount": "19.90",
"subject": "月度会员首月费用",
"product_code": "QUICK_WAP_WAY",
"quit_url": "https://example.com/order/ORDER_202609090001"
}
""");
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
response.getCode() + ": " + response.getSubMsg()
);
}
String paymentForm = response.getBody();
paymentForm 通常是支付宝返回的跳转表单,应直接返回给浏览器,不要修改其中的签名字段。
独立签约示例
独立签约可以使用 alipay.user.agreement.page.sign。产品码、签约场景、周期规则和访问渠道必须填写商户实际获准产品要求的值,不能直接照搬其他商户的参数。
AlipayUserAgreementPageSignRequest request =
new AlipayUserAgreementPageSignRequest();
request.setNotifyUrl("https://example.com/api/alipay/agreement/notify");
request.setReturnUrl("https://example.com/subscription/sign-result");
request.setBizContent("""
{
"external_agreement_no": "AGREEMENT_202609090001",
"personal_product_code": "<以已开通产品为准>",
"sign_scene": "<以产品文档为准>",
"access_params": {
"channel": "<以当前接入渠道为准>"
},
"period_rule_params": {
"period_type": "<以产品规则为准>",
"period": 1,
"execute_time": "<按产品要求填写>",
"single_amount": "19.90"
}
}
""");
AlipayUserAgreementPageSignResponse response =
alipayClient.pageExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
response.getCode() + ": " + response.getSubMsg()
);
}
String signForm = response.getBody();
示例中的占位值不能原样提交。是否需要 period_rule_params、允许使用哪些周期类型、金额字段有哪些限制,都应以该商户已开通产品的控制台和接口文档为准。
服务端状态设计
支付和签约应分别建模,避免把两种结果合并成一个状态:
payment_status:
CREATED
PAID
CLOSED
REFUNDED
agreement_status:
NOT_SIGNED
SIGNING
SIGNED
TERMINATED
subscription_status:
PENDING
ACTIVE
SUSPENDED
CANCELED
只有满足业务定义的条件,才能激活订阅:
if (paymentStatus == PaymentStatus.PAID
&& agreementStatus == AgreementStatus.SIGNED) {
subscription.activate();
}
异步通知处理必须具备幂等性:
@Transactional
public void handleAgreementNotify(AgreementNotify notify) {
verifyAlipaySignature(notify);
Agreement agreement =
agreementRepository.findByExternalAgreementNo(
notify.getExternalAgreementNo()
);
if (agreement.isSigned()) {
return;
}
agreement.markSigned(notify.getAgreementNo());
agreementRepository.save(agreement);
}
接入时需要确认的事项
- 商户账号是否已经开通周期扣款或对应的自动续费产品。
- 应用类型是否属于支付宝认可的 App、小程序或网页应用类型。
- “支付并签约”是否出现在该应用的已授权接口列表中。
- 所属行业、签约场景和扣款周期是否符合产品准入条件。
- 首次付款、后续扣款、解约和退款分别需要使用哪些接口。
- 异步通知地址能否从公网访问,是否已经完成支付宝签名验证。
- 是否向用户明确展示扣款金额、扣款周期、首次扣款时间、续费规则和解约入口。
- 是否保存用户确认记录、协议号、协议版本和签约时间。
- 后续扣款失败时,是否有重试、暂停服务和通知用户的机制。
如果支付宝开放平台后台只向当前应用展示“独立签约”,通常说明该应用或商户还不具备 H5 支付并签约权限。此时应选择“H5 分两步”,或者先跳转到支付宝小程序,再完成支付并签约,不要继续调整 alipay.trade.wap.pay 的参数。社区截图只能证明存在相似的交互,不能证明普通 H5 商户可以通过公开的网页支付接口复现。
备注:内容仅供参考。
