对于 PC 网站支付的交易,在用户支付完成之后,支付宝会根据 API 中商家传入的 notify_url,通过 POST 请求的形式将支付结果作为参数通知到商家系统。 :::info 由于网络异常、系统波动等原因,可能会存在用户支付成功、但是商户侧未能成功接收到支付结果异步通知的情况。这将会导致商户获取不到支付结果、用户订单显示为未支付,用户体验差且易造成客诉。
更严重的,易大批量出现用户对一笔订单进行重复支付的现象,带来糟糕的用户体验与强烈客诉。
为杜绝以上问题,需要商户同时接入支付结果异步通知与统一收单交易查询接口。当商户侧未收到支付结果异步通知时,必须调用 alipay.trade.query(统一收单交易查询接口)查询订单的支付结果。
当商户针对同一笔业务订单更换商户订单号(out_trade_no)来请求支付宝前,强烈建议商户先查询前一个商户订单号的支付结果: - 若为支付成功(trade_status = TRADE_SUCCESS),则不要再请求支付宝重复进行支付。 - 若为买家仍未支付(trade_status = WAIT_BUYER_PAY),则调用 alipay.trade.close(统一收单交易关闭接口)关闭此订单后再请求支付宝进行支付。 ::: # 异步通知参数 ## 公共参数 | **参数** | **类型** | **是否必填** | **最大长度** | **描述** | **示例值** | | --- | --- | --- | --- | --- | --- | | notify_time | Date | 是 | - | 通知的发送时间。格式为 yyyy-MM-dd HH:mm:ss | 2018-10-21 15:45:22  | | notify_type | String | 是 | 64 | 通知类型 | trade_status_sync | | notify_id | String | 是 | 128 | 通知校验 ID | ac05099524730693a8b330c45cf72da943 | | charset | String | 是 | 10 | 编码格式。如 utf-8、gbk、gb312等。 | utf-8 | | version | String | 是 | 3 | 调用的接口版本。固定为1.0 | 1.0 | | sign_type | String | 是 | 10 | 签名类型。签名算法类型,目前支持RSA2和RSA,推荐使用 RSA2 | RSA2 | | sign | String | 是 | 344 | 签名。详情可查看 [异步返回结果的验签](https://ideservice.alipay.com/cms/site/0izbju) | 601510b7970e52cc63db0f44997cf70e | | auth_app_id | String | 是 | 32 | 授权方的APPID。由于本接口暂不开放第三方应用授权,因此 auth_app_id=app_id | 2018072300007418 | ## 业务参数 | **参数** | **类型** | **是否必填** | **最大长度** | **描述** | **示例值** | | --- | --- | --- | --- | --- | --- | | trade_no | String | 是 | 64 | 支付宝交易号,支付宝交易凭证号。 | 2013112011001004330000121536 | | app_id | String | 是 | 32 | 支付宝应用的APPID。支付宝分配给开发者的应用 ID | 2019082200007148 | | out_trade_no | String | 是 | 64 | 商家订单号。原支付请求的商家订单号 | 6823789339978248 | | out_biz_no | String | 否 | 64 | 商家业务号。商家业务ID,通常是退款通知中返回的退款申请流水号 | HZRF001 | | buyer_id(buyer_open_id) | String | 否 | 128 | 买家支付宝用户号。买家支付宝账号对应的支付宝唯一用户号。新商户建议使用open_id替代该字段。对于新商户,user_id字段未来计划逐步回收,存量商户可继续使用。如使用open_id,请确认 应用-开发配置-openid配置管理 已启用。无该配置项,可查看[openid配置申请](https://ideservice.alipay.com/cms/site/0ai9ok?pathHash=a43b913d)。 | - | | seller_id | String | 否 | 30 | 卖家支付宝账号 ID。以 2088 开头的纯 16 位数字 | 20881***2239364 | | trade_status | String | 否 | 32 | 交易状态。交易目前所处状态,详情可查看下表 **交易状态说明** | TRADE_CLOSED | | total_amount | Number | 否 | 11 | 订单金额。本次交易支付订单金额,单位为人民币(元),精确到小数点后 2 位 | 20.00 | | receipt_amount | Number | 否 | 11 | 实收金额。商家在交易中实际收到的款项,单位为人民币(元),精确到小数点后 2 位 | 15.00 | | invoice_amount | Number | 否 | 11 | 开票金额。用户在交易中支付的可开发票的金额,单位为人民币(元),精确到小数点后 2 位 | 13.88 | | buyer_pay_amount | Number | 否 | 11 | 用户在交易中支付的金额,单位为人民币(元),精确到小数点后 2 位 | 12.00 | | point_amount | Number | 否 | 11 | 使用集分宝支付金额,单位为人民币(元),精确到小数点后 2 位 | 12.00 | | refund_fee | Number | 否 | 11 | 总退款金额。退款通知中,返回总退款金额,单位为人民币(元),精确到小数点后 2 位 | 2.58 | | subject | String | 否 | 256 | 订单标题/商品标题/交易标题/订单关键字等,是请求时对应参数,会在通知中原样传回 | XXXX交易 | | body | String | 否 | 400 | 商品描述。该订单的备注、描述、明细等。对应请求时的 body 参数,会在通知中原样传回 | XXX交易内容 | | gmt_create | Date | 否 | - | 交易创建时间。格式为 yyyy-MM-dd HH:mm:ss | 2018-08-25 15:34:42 | | gmt_payment | Date | 否 | - | 交易付款时间。格式为 yyyy-MM-dd HH:mm:ss | 2018-08-25 15:34:42 | | gmt_refund | Date | 否 | - | 交易退款时间。格式为 yyyy-MM-dd HH:mm:ss.S | 2018-08-26 10:34:44.340 | | gmt_close | Date | 否 | - | 交易结束时间。格式为 yyyy-MM-dd HH:mm:ss | 2018-08-26 16:32:30 | | fund_bill_list | String | 否 | 512 | 支付金额信息。支付成功的各个渠道金额信息。详情可查看下文 **资金明细信息说明** | [{"amount":"15.00","fundChannel":"ALIPAYACCOUNT"}] | | vocher_detail_list | String | 否 | 512 | 优惠券信息。本交易支付时所使用的所有优惠券信息。详情可查看下表 **优惠券信息说明** | [{"amount":"0.20",
"merchantContribute":"0.00",
"name":"一键创建券模板名称",
"otherContribute":"0.20",
"type":"ALIAPY_BIZ_VOUCHER","memo":"学生卡8折优惠"}] | | passback_params | String | 否 | 512 | 回传参数,公共回传参数,如果请求时传递了该参数,则返回的异步通知会原样传回。本参数必须进行 UrlEncode 之后才可传入。 | merchantBizType%3d3C%26merchantBizNo%3d201601001111 | # 交易状态说明 | **枚举名称** | **枚举说明** | | --- | --- | | WAIT_BUYER_PAY | 交易创建,等待买家付款。 | | TRADE_CLOSED | 未付款交易超时关闭,或支付完成后全额退款。 | | TRADE_SUCCESS | 交易支付成功。 | | TRADE_FINISHED | 交易结束,不可退款。 | # 通知触发条件 | **触发条件名** | **触发条件描述** | **触发条件默认值** | | --- | --- | --- | | TRADE_FINISHED | 交易完成 | false(不触发通知) | | TRADE_SUCCESS | 支付成功 | true(触发通知) | | WAIT_BUYER_PAY | 交易创建 | false(不触发通知) | | TRADE_CLOSED | 交易关闭 | false(不触发通知) | # 资金明细信息说明 | **参数** | **类型** | **是否必填** | **最大长度** | **描述** | **示例值** | | --- | --- | --- | --- | --- | --- | | fundChannel | String | 否 | - | 支付渠道。详情可查看 [支付渠道说明](https://ideservice.alipay.com/cms/site/009zkj)。 | ALIPAYACCOUNT | | amount | String | 否 | 11 | 支付金额。使用指定支付渠道支付的金额,单位为元。 | 15.00 | # 优惠券信息说明 | **参数** | **类型** | **是否必填** | **最大长度** | **描述** | **示例值** | | --- | --- | --- | --- | --- | --- | | voucherId | String | 是 | 32 | 券 ID | 2015102600073002039000002D5O | | templateId | String | 否 | 64 | 券模板 ID | 20171030000730015359000EMZP0 | | name | String | 是 | 64 | 券名称 | 5 元代金券 | | type | String | 是 | 32 | 优惠类型。当前支持以下几种主要类型:
- ALIPAY_BIZ_VOUCHER:商家全场券。
- ALIPAY_COMMON_ITEM_VOUCHER:商家单品券。
- ALIPAY_CASH_VOUCHER:平台优惠券,支付宝或第三方出资。
- ALICREDIT_INTFREE_VOUCHER:花呗分期券,该券仅做订单外的花呗分期费用减免,并不抵扣订单内支付金额。
**注意**:不排除未来新增其它类型的可能,商家接入时请注意兼容性,避免硬编码。 | ALIPAY_BIZ_VOUCHER | | amount | Number | 是 | 11 | 优惠金额。优惠金额中,由商家出资的金额。 | 10.00 | | merchantContribute | Number | 否 | 11 | 商家出资金额。优惠金额中,由商家出资的金额。 | 9.00 | | otherContribute | Number | 否 | 11 | 其他出资方出资金额。可能是支付宝,可能是品牌商,或者其他方,也可能是他们的共同出资。 | 1.00 | | otherContributeDetail | ContributeDetail[] | 否 | - | 优惠券的其他出资方明细 | - | | L contributeType | String | 否 | 32 | 出资方类型,如品牌商出资、支付宝平台出资等。 | PLATFORM | | L contributeAmount | Number | 否 | 8 | 出资方金额 | 0.18 | | memo | String | 否 | 256 | 优惠券备注信息。 | 学生专用优惠 | # 异步通知特性 1. 在进行异步通知交互时,如果支付宝收到的应答不是 `success` ,支付宝会认为通知失败,会通过一定的策略定期重新发起通知。**通知的间隔频率为:4m、10m、10m、1h、2h、6h、15h**。 2. 商家设置的异步地址(notify_url)需保证无任何字符,如空格、HTML 标签,且不能重定向。(如果重定向,支付宝会收不到 success 字符,会被支付宝服务器判定为该页面程序运行出现异常,而重发处理结果通知) 3. 支付宝是用 POST 方式发送通知信息,商户获取参数的方式如下:`request.Form("out_trade_no")`、`$_POST['out_trade_no']`。 4. 支付宝针对同一条异步通知重试时,异步通知参数中的 notify_id 是不变的。 # 异步返回结果的验签 某商家设置的通知地址为 https:// 商家网站通知地址,对应接收到通知的示例如下: ```java https: //商家网站通知地址?voucher_detail_list=[{"amount":"0.20","merchantContribute":"0.00","name":"5折券","otherContribute":"0.20","type":"ALIPAY_DISCOUNT_VOUCHER","voucherId":"2016101200073002586200003BQ4"}]&fund_bill_list=[{"amount":"0.80","fundChannel":"ALIPAYACCOUNT"},{"amount":"0.20","fundChannel":"MDISCOUNT"}]&subject=PC网站支付交易&trade_no=2016101221001004580200203978&gmt_create=2016-10-12 21:36:12¬ify_type=trade_status_sync&total_amount=1.00&out_trade_no=mobile_rdm862016-10-12213600&invoice_amount=0.80&seller_id=2088201909970555¬ify_time=2016-10-12 21:41:23&trade_status=TRADE_SUCCESS&gmt_payment=2016-10-12 21:37:19&receipt_amount=0.80&passback_params=passback_params123&buyer_id=2088102114562585&buyer_open_id=074a1CcTG1LelxKe4xQC0zgNdId0nxi95b5lsNpazWYoCo5&app_id=2016092101248425¬ify_id=7676a2e1e4e737cff30015c4b7b55e3kh6& sign_type=RSA2&buyer_pay_amount=0.80&sign=***&point_amount=0.00 // 用户Id类字段根据选用的ID标准(UID或openid)选择示例 ``` **第一步**:在通知返回参数列表中,除去 sign、sign_type 两个参数外,凡是通知返回回来的参数皆是待验签的参数。
**第二步**:将剩下参数进行 url_decode,然后进行字典排序,组成字符串,得到待签名字符串: ```java app_id=2016092101248425&buyer_id=2088102114562585&buyer_open_id=074a1CcTG1LelxKe4xQC0zgNdId0nxi95b5lsNpazWYoCo5&buyer_pay_amount=0.80&fund_bill_list=[{"amount":"0.80","fundChannel":"ALIPAYACCOUNT"},{"amount":"0.20","fundChannel":"MDISCOUNT"}]&gmt_create=2016-10-12 21:36:12&gmt_payment=2016-10-12 21:37:19&invoice_amount=0.80¬ify_id=7676a2e1e4e737cff30015c4b7b55e3kh6¬ify_time=2016-10-12 21:41:23¬ify_type=trade_status_sync&out_trade_no=mobile_rdm862016-10-12213600&passback_params=passback_params123&point_amount=0.00&receipt_amount=0.80&seller_id=2088201909970555&subject=PC网站支付交易&total_amount=1.00&trade_no=2016101221001004580200203978&trade_status=TRADE_SUCCESS&voucher_detail_list=[{"amount":"0.20","merchantContribute":"0.00","name":"5折券","otherContribute":"0.20","type":"ALIPAY_DISCOUNT_VOUCHER","voucherId":"2016101200073002586200003BQ4"}] // 用户Id类字段根据选用的ID标准(UID或openid)选择示例 ``` **第三步**:将签名参数(sign)使用 base64 解码为字节码串。
**第四步**:使用 RSA 的验签方法,通过签名字符串、签名参数(经过 base64 解码)及支付宝公钥验证签名。
**第五步**:需要严格按照如下描述校验通知数据的正确性: 1. 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。 2. 判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。 3. 校验通知中的 seller_id(或者 seller_email)是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商家可能有多个 seller_id/seller_email)。 4. 验证 app_id 是否为该商家本身。 上述 1、2、3、4 有任何一个验证不通过,**则表明本次通知是异常通知**,务必忽略。在上述验证通过后商家必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
**注意**: - **状态 TRADE_SUCCESS** 的通知触发条件是商家开通的产品支持退款功能的前提下,买家付款成功。 - **交易状态 TRADE_FINISHED** 的通知触发条件是商家开通的产品不支持退款功能的前提下,买家付款成功;或者,商家开通的产品支持退款功能的前提下,交易已经成功并且已经超过可退款期限。 ## 异步通知验签 ```java Map paramsMap = ... //将异步通知中收到的所有参数都存放到map中 boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, ALIPAY_PUBLIC_KEY, CHARSET, SIGN_TYPE) //调用SDK验证签名 if(signVerified){ // TODO 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure }else{ // TODO 验签失败则记录异常日志,并在response中返回failure. } ```