PAYATHON 2026

H5 如何接入支付宝支付并签约实现自动续费

支付小周

结论

按照题述接口文档限定的终端范围,普通 H5/PC 网站无法通过 alipay.trade.wap.pay,在同一个支付宝收银台内完成“首笔付款 + 自动续费签约”。H5 支持网页支付和独立签约,但这是两次独立的业务操作,不能靠拼接参数合并成一次。

如果必须在一次流程中完成付款和签约,通常有两种方案:

  1. 将实际交易页面迁移到支付宝小程序或原生 App,申请并接入支付宝的“支付并签约”能力。H5 只负责引导用户进入受支持的终端。
  2. 联系支付宝商务或技术支持,确认当前商户能否申请专门的行业产品或定向能力。有些页面看起来由 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 引导进入支付宝小程序

需要“一次完成付款和签约”时,这种方案更合适。

基本流程如下:

  1. 用户在 H5 选择自动续费套餐。
  2. 服务端创建业务订单,生成唯一的 out_trade_no 和 external_agreement_no。
  3. H5 通过支付宝官方支持的方式打开指定的支付宝小程序页面。
  4. 小程序向商户服务端申请支付并签约参数。
  5. 服务端调用已获准的支付并签约接口,并对请求参数签名。
  6. 小程序调用支付宝提供的对应客户端能力,进入支付并签约收银台。
  7. 服务端分别处理支付通知和签约通知。
  8. 支付成功且协议签约成功后,业务系统才将订阅标记为生效。

在这个流程中,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,就需要让用户完成两次操作。常见的流程顺序有两种。

先付款,后签约

  1. H5 调用 alipay.trade.wap.pay 完成首笔付款。
  2. 服务端收到支付成功异步通知。
  3. 支付结果页提示用户开通自动续费。
  4. 用户确认后进入 alipay.user.agreement.page.sign 独立签约页面。
  5. 服务端收到签约通知后启用自动续费。

这种顺序可以避免用户尚未付款就留下代扣协议,但部分用户可能在付款后不再继续签约。

先签约,后完成首笔交易

  1. 用户先通过 alipay.user.agreement.page.sign 签约。
  2. 服务端确认协议已经生效。
  3. 再完成首笔支付,或按照已开通产品的规定执行首次扣款。
  4. 首笔交易成功后启用订阅。

采用这种顺序时,必须单独处理“签约成功但首次交易失败”的情况。交易失败后应暂停服务,并根据产品规则决定是否解约,不能默认订阅已经生效。

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 商户可以通过公开的网页支付接口复现。

备注:内容仅供参考。