PAYATHON 2026

商家扣款是否支持支付宝网站扫码支付并同步签约

支付老李

结论

可以实现“电脑网站扫码支付,并在支付成功后完成商家扣款签约”。前提是商户已经开通支付宝的商家扣款和“支付后签约”能力,而且当前使用的支付产品与接口允许传入签约参数。

普通的 alipay.trade.page.pay 扫码支付默认不包含这项功能。如果只创建电脑网站支付订单,用户扫码后通常只会完成付款,不会自动生成代扣协议。要把支付和签约放在同一流程中,需要在支付请求里传入 agreement_sign_params 等协议签约信息。

实现思路

可以按以下流程处理:

  1. 用户选择连续会员。
  2. 商户生成唯一的支付订单号和外部协议号。
  3. 调用支持支付后签约的支付接口,并传入 agreement_sign_params。
  4. 用户在电脑网页打开支付宝收银台,再使用手机支付宝扫码。
  5. 用户确认支付和签约授权。
  6. 商户分别处理支付结果与签约结果。
  7. 保存支付宝协议号,后续根据该协议发起扣款。

“支付成功”和“签约成功”是两个独立的业务状态。即使用户在同一次操作中完成支付和授权,服务端也不能仅凭支付结果判定签约成功。

请求示例

下面的示例用于说明参数如何组织。实际可用字段应以商户后台已开通产品所对应的接口文档和 SDK 模型为准。

AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();

request.setReturnUrl("https://example.com/alipay/return");
request.setNotifyUrl("https://example.com/alipay/payment-notify");

JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", "ORDER_202609090001");
bizContent.put("total_amount", "29.90");
bizContent.put("subject", "连续会员首期费用");
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");

JSONObject agreementSignParams = new JSONObject();
agreementSignParams.put("personal_product_code", "商户实际开通的签约产品码");
agreementSignParams.put("sign_scene", "商户实际申请的签约场景");
agreementSignParams.put("external_agreement_no", "AGREEMENT_202609090001");

bizContent.put("agreement_sign_params", agreementSignParams);

request.setBizContent(bizContent.toJSONString());

AlipayTradePagePayResponse response = alipayClient.pageExecute(request);

if (response.isSuccess()) {
    String paymentPage = response.getBody();
    // 将支付宝返回的表单内容输出到浏览器,由浏览器跳转至支付宝收银台。
} else {
    // 记录 code、subCode、subMsg,避免只记录笼统的失败信息。
}

如果当前 SDK 的 alipay.trade.page.pay 请求模型或产品文档中没有 agreement_sign_params,不要自行拼接参数并假设支付宝会处理。这通常说明当前接口、产品配置或商户权限不支持这种组合模式。此时应改用支付宝为商家扣款提供的签约页面接口,先完成签约再支付,或者联系支付宝确认能否开通支付后签约能力。

回调处理

支付通知和签约通知需要分别验签、分别落库。建议至少保存以下状态:

payment_status:
  INIT
  SUCCESS
  CLOSED

agreement_status:
  INIT
  SIGNED
  UNSIGNED
  TERMINATED

支付异步通知可以这样处理:

@PostMapping("/alipay/payment-notify")
public String paymentNotify(HttpServletRequest request) {
    Map<String, String> params = extractAlipayParams(request);

    boolean valid = AlipaySignature.rsaCheckV1(
        params,
        alipayPublicKey,
        "UTF-8",
        "RSA2"
    );

    if (!valid) {
        return "failure";
    }

    String outTradeNo = params.get("out_trade_no");
    String tradeStatus = params.get("trade_status");
    String totalAmount = params.get("total_amount");
    String appId = params.get("app_id");
    String sellerId = params.get("seller_id");

    // 校验 out_trade_no、金额、app_id、seller_id,并保证重复通知可幂等处理。
    if ("TRADE_SUCCESS".equals(tradeStatus)
            || "TRADE_FINISHED".equals(tradeStatus)) {
        markPaymentSuccess(outTradeNo);
    }

    return "success";
}

签约结果应以支付宝提供的签约通知或协议查询结果为准。取得支付宝协议号后,需要将它与 external_agreement_no、用户及会员订单绑定。后续扣款应使用支付宝返回的有效协议标识,不能只根据前端跳转参数执行代扣。

不支持组合模式时的替代方案

如果商户权限或电脑网站支付接口不支持同时支付和签约,可以拆成两个步骤处理:

  1. 调用签约页面接口,例如商户当前产品文档指定的签约接口,让用户先完成商家扣款授权。
  2. 服务端收到签约成功通知,或通过主动查询确认协议有效。
  3. 创建首期支付订单,或者按照已开通产品的规则发起首次扣款。
  4. 后续续费通过有效协议执行商家扣款。

这种方式会增加一次跳转,但业务状态更容易控制,也可以避免首期已支付、协议却未签成功时误开通连续续费。

注意事项

  • agreement_sign_params 能否用于 alipay.trade.page.pay,取决于商户开通的产品、签约方案和接口权限,不能只根据 SDK 中是否存在同名字段来判断。
  • personal_product_code、sign_scene 等参数必须使用支付宝为该商户及业务场景配置的实际值,不能直接照搬示例。
  • external_agreement_no 应全局唯一,并与用户、会员套餐和商户订单建立稳定映射。
  • 前端同步跳转只能用于展示结果,不能作为支付成功或签约成功的判断依据。
  • 必须验证支付宝异步通知的签名,同时核对订单号、金额、商户身份和应用身份。
  • 支付通知和签约通知可能先后到达,也可能重复发送,因此处理逻辑需要支持乱序和幂等。
  • 如果支付成功但签约失败,应将其按普通单次购买处理,或者根据业务规则退款,不能继续自动扣款。
  • 页面需要清楚展示扣款金额、周期、续费规则和取消方式等信息,并取得用户明确授权。
  • 上线前应通过支付宝提供的测试环境或联调流程,验证支付成功、签约失败、用户取消、重复通知和协议解约等情况。

备注:内容仅供参考。