代码 签约成功,扣款被拒:支付宝周期扣款(免密代扣)的传参约束与 ACQ.* 归因

2026-09-12 09:01:09

签约成功,扣款被拒:支付宝周期扣款(免密代扣)的传参约束与 ACQ.* 归因

用户在支付宝 H5 页面上走完签约流程,agreement_no 也拿到了,第一次用 alipay.trade.pay 发起扣款却返回 40004 / ACQ.AGREEMENT_ERROR。这条链路上接口本身不长,绝大多数失败都集中在两处:签约时 period_rule_params 定下的周期约束,以及扣款时 auth_code 到底传了什么。

适用范围:支付宝钱包 H5 场景下的周期/商家扣款,签约产品码 CYCLE_PAY_AUTH_P(支付并签约场景用 CYCLE_PAY_AUTH)。如果不涉及周期计划、只是单次免密支付,本文的周期约束部分不适用。

一、签约侧:alipay.user.agreement.page.sign

接口名 alipay.user.agreement.page.sign,支付宝个人协议页面签约接口,用在支付宝钱包 H5 页面场景。

必传参数:

  • personal_product_code(必选,64 位)
  • access_params(必选)
  • period_rule_params(特殊可选:签约周期扣款产品如 CYCLE_PAY_AUTH_P 时必传,签约其他产品无需传)

一对多模式下 external_agreement_no 与 sign_scene 的配对

external_agreement_no(外部商户签约号)与 sign_scene(协议签约场景)有配对规则:设置了 external_agreement_nosign_scene 必传;只设置 sign_sceneexternal_agreement_no 选传。sign_scene 设错会直接报「开通失败 / 签约失败」。

period_rule_params 的周期管控规则

  • period_type=DAY 时,period 最少为 7 天,execute_time 可设置任意一天。
  • period_type=MONTH 时,period 表示几月为一周期,execute_time 需在 28 日之前,29–31 不可设置(月份可能不存在)。
  • 周期扣款产品必须传入 period_rule_params,其中 period_typeperiodexecute_timesingle_amount 都要填。
  • execute_time:首次执行时间,周期扣款产品必填,即商户发起首次扣款的时间,精确到日,格式 yyyy-MM-dd。结合其他必填的扣款周期参数,会确定商户以后的扣款计划;发起扣款的时间需符合这个计划。
  • 官方对 period_rule_params 的描述是:周期扣款产品会按这里传入的参数提示用户,并对发起扣款的时间、金额、次数做相应限制。

支付后签约:参数放 agreement_sign_params

支付后签约场景,签约业务参数放在 agreement_sign_params 里。常见的「支付成功但签约未成功」不是支付回调没收到,而是签约业务参数配错。逐项检查:product_code 是否改成 CYCLE_PAY_AUTHagreement_sign_params 里是否配置了签约业务参数;personal_product_codeaccess_paramsperiod_rule_params 是否齐备;period_rule_params 规则是否正确;一对多模式下 external_agreement_nosign_scene 是否同时存在;用户在支付签约页面是否开通了周期/商家扣款协议按钮。

稳妥的顺序是先调 alipay.user.agreement.page.sign 能签约成功,再把签约业务参数搬进 agreement_sign_params

签约失败同步跳转回带的报错原文形如:

https://同步地址?msg=Business Failed&charset=utf-8&code=40004&method=alipay.user.agreement.page.sign.return&sub_msg=系统异常,签约失败,商户请求数据错误&sub_code=INVALID_PARAMETER&sign=***&external_agreement_no=202006199000000000&version=1.0&auth_app_id=2019*********42&personal_product_code=CYCLE_PAY_AUTH_P&app_id=2019*********42&sign_type=RSA2&sign_scene=INDUSTRY|CARRENTAL&timestamp=2020-06-19 15:24:10

无线端唤起签约

alipay.user.agreement.page.sign 请求生成的数据重新转义拼接,在商家 App 端唤起支付宝客户端:

alipays://platformapi/startapp?appId=60000157&appClearTop=false&startMultApp=YES&sign_params=转义后的数据

拼接出的 alipay 协议短链数据固定不变,不要改动其中的 appId 等值。

排查按官方三步走:

  1. 先用 pageExecute() 以 get 请求生成 https://openapi.alipay.com/gateway.do 开头的请求参数链接,浏览器或客户端直接访问。测试时先只传必传参数,避免选填参数干扰。
  2. 接口生成的参数能正常访问,说明参数正确,问题在拼接或唤起方式;把拼接好的数据放到钉钉聊天页直接点击,看能否唤起支付宝客户端。
  3. 前两步都正常,再查唤起方式:Android 判断 package name com.eg.android.AlipayGphone 是否存在;iOS 判断 alipays:// 能否打开,iOS 9 以上要把 alipays 配置进 LSApplicationQueriesSchemes,不要配到 URL Schemes。

签约结果通知

notify_type=dut_user_sign,携带 statusNORMAL)、agreement_noexternal_agreement_nosign_timevalid_timeinvalid_timesingle_quotanext_deduct_timesign_scenepersonal_product_codealipay_user_id 等字段。通知示例(脱敏):

https://www.merchant.com/receive_notify.htm?notify_id=...&notify_time=2017-02-16 21:46:15&sign_type=RSA2&sign=...&app_id=2017060101317939&external_agreement_no=test&merchant_app_id=2014072300007148&invalid_time=2017-05-20 11:49:19&sign_time=2017-05-20 11:49:19&alipay_user_id=...&status=NORMAL&personal_product_code=GENERAL_WITHHOLDING_P&valid_time=2017-05-20 11:49:19&agreement_no=20170502000610755993&sign_scene=INDUSTRY|CARRENTAL&external_logon_id=...&notify_type=dut_user_sign&single_quota=100&next_deduct_time=2024-01-01

二、扣款侧:alipay.trade.pay

接口 alipay.trade.pay(统一收单交易支付接口,部分文档写作 alipay.trade.order.pay)。用户在商户侧已授权下单并享受服务后,商户用授权单号通过本接口对已授权金额发起扣款。

传参要点:

  • product_code,值必须是周期/商家扣款的固定值 CYCLE_PAY_AUTH(对比当面付 product_code=FACE_TO_FACE_PAYMENT)。
  • scene=deduct_pay
  • auth_code = 签约成功返回的 agreement_no
  • 核实 auth_code 的值是否存在、是否填对。

ACQ.AGREEMENT_ERROR 的报错原文:

{"alipay_trade_pay_response":{"code":"40004","msg":"Business Failed","sub_code":"ACQ.AGREEMENT_ERROR","sub_msg":"协议信息异常","buyer_pay_amount":"0.00","invoice_amount":"0.00","point_amount":"0.00","receipt_amount":"0.00"},"sign":"****"}

原因有两类:接口并发;传入的 auth_code 不属于该 product_code 下的协议号。并发导致的报错不要高频重试,过几秒再试;协议传参错误就按上面几条核对 product_code / scene / auth_code

三、ACQ.* 错误码逐条归因

  • ACQ.ACCESS_FORBIDDEN 无权限使用接口:未签约对应产品合约。核对 product_code 是否正确,确认商户是否签约了对应产品合约。
  • ACQ.AGREEMENT_ERROR 协议信息异常:检查传入的协议信息是否正确。
  • ACQ.AGREEMENT_NOT_EXIST / USER_AGREEMENT_NOT_EXIST 协议信息不存在 / 用户协议不存在:代扣传入的协议号对应的用户协议不存在或已解约。已解约的需引导用户重新签约,用新生成的协议号发起扣款。
  • AGREEMENT_INVALID 用户协议失效。
  • ACQ.AMOUNT_OR_CURRENCY_ERROR 订单金额或币种信息错误:检查金额信息,或当前币种是否未签约。
  • ACQ.AUTH_AMOUNT_NOT_ENOUGH 授权金额不足:订单金额大于授权剩余金额。
  • ACQ.AUTH_NO_ERROR 预授权号错误或状态不对:确认 auth_no 正确、参与方一致、状态为已授权。
  • ACQ.CYCLE_PAY_DATE_NOT_MATCH 扣款日期不在签约时的允许范围之内:签约时约定了扣款周期,发起日期不符合约定周期则不允许扣款,重新检查扣款日期。
  • ACQ.CYCLE_PAY_SINGLE_FEE_EXCEED 周期扣款的单笔金额超过签约时限制。
  • ACQ.CYCLE_PAY_TOTAL_FEE_EXCEED 周期扣款的累计金额超过签约时限制。
  • ACQ.CYCLE_PAY_TOTAL_TIMES_EXCEED 周期扣款的总次数超过签约时限制。
  • ACQ.MERCHANT_AGREEMENT_INVALID 商户协议已失效:商户与支付宝的合同已失效。
  • ACQ.OVER_DEDUCT_PERIOD 超过扣款周期:已超过扣款周期时效无法发起新的扣款,请使用已经发起的扣款单号重试。
  • ACQ.ZM_AUTH_AMOUNT_EXCEED 先用后付场景下超过约定的免密支付金额:需商户调用支付宝 SDK 唤起收银台,用户确认后付款。

四、设计上的取舍

  1. agreement_no 是扣款链路的主键和幂等键。签约通知里拿到的要落库,扣款时传的是它,不是用户的 uid,也不是 external_agreement_no(商户侧单号,用于一对多把同一用户的多份协议区分开)。
  2. 扣款计划在签约那一刻就被 period_rule_params 锁死了。period_type / period / execute_time 决定以后能扣的日期,single_amount 与签约限额决定单笔上限。上线后想改扣款日或提额,改代码没用,得让用户重新签约。
  3. 解约/失效是正常业务分支,不是异常。出现 ACQ.AGREEMENT_NOT_EXIST 时不要再重试扣款,落库标记协议不可用,走站内信/短信引导重签。
  4. ACQ.OVER_DEDUCT_PERIOD 是特殊分支:超过扣款周期时效不能用新单号,要用已经发起的那个扣款单号重试,实现「新单号重试」反而会一直报错。
  5. 签约和扣款都是异步的。签约结果以 dut_user_sign 通知里的 status 为准,扣款结果以异步通知为准,不要拿接口返回的 200 当最终状态。
  6. 需要按自己申请的产品合约核对的项:SCENE 值枚举、各签约产品码的准入资质、扣款失败重试次数与频控策略。这几项在官方文档里分散在不同页面。

推荐文章

程序员茄子在线接单