商家扣款是否支持支付宝网站扫码支付并同步签约
结论
可以实现“电脑网站扫码支付,并在支付成功后完成商家扣款签约”。前提是商户已经开通支付宝的商家扣款和“支付后签约”能力,而且当前使用的支付产品与接口允许传入签约参数。
普通的 alipay.trade.page.pay 扫码支付默认不包含这项功能。如果只创建电脑网站支付订单,用户扫码后通常只会完成付款,不会自动生成代扣协议。要把支付和签约放在同一流程中,需要在支付请求里传入 agreement_sign_params 等协议签约信息。
实现思路
可以按以下流程处理:
- 用户选择连续会员。
- 商户生成唯一的支付订单号和外部协议号。
- 调用支持支付后签约的支付接口,并传入
agreement_sign_params。 - 用户在电脑网页打开支付宝收银台,再使用手机支付宝扫码。
- 用户确认支付和签约授权。
- 商户分别处理支付结果与签约结果。
- 保存支付宝协议号,后续根据该协议发起扣款。
“支付成功”和“签约成功”是两个独立的业务状态。即使用户在同一次操作中完成支付和授权,服务端也不能仅凭支付结果判定签约成功。
请求示例
下面的示例用于说明参数如何组织。实际可用字段应以商户后台已开通产品所对应的接口文档和 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、用户及会员订单绑定。后续扣款应使用支付宝返回的有效协议标识,不能只根据前端跳转参数执行代扣。
不支持组合模式时的替代方案
如果商户权限或电脑网站支付接口不支持同时支付和签约,可以拆成两个步骤处理:
- 调用签约页面接口,例如商户当前产品文档指定的签约接口,让用户先完成商家扣款授权。
- 服务端收到签约成功通知,或通过主动查询确认协议有效。
- 创建首期支付订单,或者按照已开通产品的规则发起首次扣款。
- 后续续费通过有效协议执行商家扣款。
这种方式会增加一次跳转,但业务状态更容易控制,也可以避免首期已支付、协议却未签成功时误开通连续续费。
注意事项
agreement_sign_params能否用于alipay.trade.page.pay,取决于商户开通的产品、签约方案和接口权限,不能只根据 SDK 中是否存在同名字段来判断。personal_product_code、sign_scene等参数必须使用支付宝为该商户及业务场景配置的实际值,不能直接照搬示例。external_agreement_no应全局唯一,并与用户、会员套餐和商户订单建立稳定映射。- 前端同步跳转只能用于展示结果,不能作为支付成功或签约成功的判断依据。
- 必须验证支付宝异步通知的签名,同时核对订单号、金额、商户身份和应用身份。
- 支付通知和签约通知可能先后到达,也可能重复发送,因此处理逻辑需要支持乱序和幂等。
- 如果支付成功但签约失败,应将其按普通单次购买处理,或者根据业务规则退款,不能继续自动扣款。
- 页面需要清楚展示扣款金额、周期、续费规则和取消方式等信息,并取得用户明确授权。
- 上线前应通过支付宝提供的测试环境或联调流程,验证支付成功、签约失败、用户取消、重复通知和协议解约等情况。
备注:内容仅供参考。
