# 简介 异步通知是指一笔订单支付完成后,支付宝会将该笔订单的变更信息,沿着商家调用支付请求时所传入的异步通知地址 notify_url,通过 POST 请求的形式将支付结果作为参数通知到商家系统。
异步回调地址状态码 (HTTP 状态码) 为 200 时表示异步通知成功,返回码为 404 或 500 时则表示服务器内部错误,需要商家自行排查。 :::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。 | 2020-12-27 06:20:30 | | notify_type | String | 是 | 64 | 通知类型。
枚举值:trade_status_sync。 | trade_status_sync | | notify_id | String | 是 | 128 | 通知校验 ID。 | ac05099524730693a8b330c5ecf72da9786 | | sign_type | String | 是 | 10 | 签名类型。商家生成签名字符串所使用的签名算法类型,目前支持 RSA2 和 RSA,推荐使用 RSA2(如果开发者手动验签,不使用 SDK 验签,可以不传此参数)。 | RSA2 | | sign | String | 是 | 344 | 签名。可查看异步返回结果的验签(如果开发者手动验签,不使用 SDK 验签,可以不传此参数)。 | 601510b7970e52cc63db0f44997cf70e | | trade_no | String | 是 | 64 | 支付宝交易号。支付宝交易凭证号。 | 20213112011001004330000121536 | | app_id | String | 是 | 32 | 开发者的 app_id。支付宝分配给开发者的应用 APPID。 | 2014072300007148 | | auth_app_id | String | 是 | 32 | 开发者的 app_id,在服务商调用的场景下为授权方的 app_id。 | 2014072300007148 | | 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)。 | - | | buyer_logon_id | String | 否 | 100 | 买家支付宝账号。 | 180****0062 | | seller_id | String | 否 | 30 | 卖家支付宝用户号。 | 2088101106XXXXXX | | seller_email | String | 否 | 100 | 卖家支付宝账号。 | zhuzxxxxxx@alitest.com | | trade_status | String | 是 | 32 | 交易状态。咨询目前所处的状态。 | TRADE_CLOSED | | total_amount | Number | 是 | 11 | 订单金额。本次交易支付的订单金额,单位为人民币(元)。支持小数点后两位。 | 20 | | receipt_amount | Number | 是 | 11 | 实收金额。商家在交易中实际收到的款项,单位为人民币(元)。支持小数点后两位。 | 15 | | invoice_amount | Number | 否 | 11 | 开票金额。用户在交易中支付的可开发票的金额。支持小数点后两位。 | 10.00 | | buyer_pay_amount | Number | 否 | 11 | 付款金额。用户在交易中支付的金额。支持小数点后两位。 | 13.88 | | point_amount | Number | 否 | 11 | 集分宝金额。使用集分宝支付的金额。支持小数点后两位。 | 12.00 | | refund_fee | Number | 否 | 11 | 总退款金额。退款通知中,返回总退款金额,单位为元,支持小数点后两位。 | 2.58 | | send_back_fee | Number | 否 | 11 | 实际退款金额。商家实际退款给用户的金额,单位为元,支持小数点后两位。 | 2.08 | | subject | String | 否 | 256 | 订单标题。商品的标题/交易标题/订单标题/订单关键字等,是请求时对应的参数,原样通知回来。 | XXX交易 | | body | String | 否 | 400 | 商品描述。该订单的备注、描述、明细等。对应请求时的 body 参数,原样通知回来。 | XXX交易内容 | | gmt_create | Date | 否 | - | 交易创建时间。该笔交易创建的时间。格式 为 yyyy-MM-dd HH:mm:ss。 | 2015-04-27 15:45:57 | | gmt_payment | Date | 否 | - | 交易 付款时间。该笔交易的买家付款时间。格式为 yyyy-MM-dd HH:mm:ss。 | 2015-04-27 15:45:57 | | gmt_refund | Date | 否 | - | 交易退款时间。该笔交易的退款时间。格式 为 yyyy-MM-dd HH:mm:ss.SS。 | 2015-04-28 15:45:57.320 | | gmt_close | Date | 否 | - | 交易结束时间。该笔交易结束时间。格式为 yyyy-MM-dd HH:mm:ss。 | 2015-04-29 15:45:57 | | fund_bill_list | String | 否 | 512 | 支付金额信息。支付成功的各个渠道金额信息,详请可查看下表**资金明细信息说明**。 | [{"amount":"15.00",
"fundChannel":"ALIPAYACCOUNT"
}] | | voucher_detail_list | String | 否 | - | 优惠券信息。本交易支付时所使用的所有优惠券信息,详请可查看下表**优惠券信息说明**。 | [{"amount":"0.20",
"merchantContribute":"0.00",
"name":"一键创建券模板的券名称",
"otherContribute":"0.20",
"type":"ALIPAY_BIZ_VOUCHER",
"memo":"学生8折优惠"
}] | | biz_settle_mode | String | 可选 | 64 | 账期结算标识,指已完成支付的订单会进行账期管控,不会实时结算。该参数目前会在使用小程序交易组件场景下返回。 | PERIOD | ## 交易状态说明 | **枚举名称** | **枚举说明** | | --- | --- | | 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` ,支付宝会认为通知失败,会通过一定的策略定期重新发起通知。重试逻辑为:当未收到`success` 时**立即尝试重发 3 次通知**,若 3 次仍不成功,**则后续通知的间隔频率为: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://api.xx.com/receive_notify.htm`,对应接收到通知的示例如下:
**注意**:以下示例报文仅供参考,实际返回的详细报文请以实际返回为准。 ``` https://api.xx.com/receive_notify.htm?gmt_payment=2015-06-11 22:33:59¬ify_id=42af7baacd1d3746cf7b56752b91edcj34&seller_email=testyufabu07@alipay.com¬ify_type=trade_status_sync&sign=kPbQIjX+xQc8F0/A6/AocELIjhhZnGbcBN6G4MM/HmfWL4ZiHM6fWl5NQhzXJusaklZ1LFuMo+lHQUELAYeugH8LYFvxnNajOvZhuxNFbN2LhF0l/KL8ANtj8oyPM4NN7Qft2kWJTDJUpQOzCzNnV9hDxh5AaT9FPqRS6ZKxnzM=&trade_no=2015061121001004400068549373&out_trade_no=21repl2ac2eOutTradeNo322&gmt_create=2015-06-11 22:33:46&seller_id=2088211521646673¬ify_time=2015-06-11 22:34:03&subject=FACE_TO_FACE_PAYMENT_PRECREATE中文&trade_status=TRADE_SUCCESS&sign_type=RSA2 ``` **第一步:**在通知返回参数列表中,除去 `sign`、`sign_type` 两个参数外,通知返回的参数均为待验签的参数。
**第二步:**将剩下参数进行 **URLDecode**,然后进行字典排序,组成字符串,得到待签名字符串: ``` gmt_create=2015-06-11 22:33:46&gmt_payment=2015-06-11 22:33:59¬ify_id=42af7baacd1d3746cf7b56752b91edcj34¬ify_time=2015-06-11 22:34:03¬ify_type=trade_status_sync&out_trade_no=21repl2ac2eOutTradeNo322&seller_email=testyufabu07@alipay.com&seller_id=2088211521646673&subject=FACE_TO_FACE_PAYMENT_PRECREATE中文&trade_no=2015061121001004400068549373&trade_status=TRADE_SUCCESS ``` **第三步:**将签名参数(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 是否为该商家本身。 5. 上述 1、2、3、4 有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。在上述验证通过后商家必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。 SDK 接收以及验签示例代码(此处以 Java 语言为例,按照服务端 SDK 中提供的工具类,注意区分公钥和公钥证书验签代码): ```java //获取支付宝POST过来反馈信息,将异步通知中收到的待验证所有参数都存放到map中 Map< String , String > params = new HashMap < String , String > (); Map requestParams = request.getParameterMap(); for(Iterator iter = requestParams.keySet().iterator();iter.hasNext();){ String name = (String)iter.next(); String[] values = (String [])requestParams.get(name); String valueStr = ""; for(int i = 0;i < values.length;i ++ ){ valueStr = (i==values.length-1)?valueStr + values [i]:valueStr + values[i] + ","; } //乱码解决,这段代码在出现乱码时使用。 //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8"); params.put (name,valueStr); } //调用SDK验证签名 //公钥验签示例代码 boolean signVerified = AlipaySignature.rsaCheckV1(params, ALIPAY_PUBLIC_KEY, CHARSET,sign_type) ; //公钥证书验签示例代码 // boolean flag = AlipaySignature.rsaCertCheckV1(params,alipayPublicCertPath,"UTF-8","RSA2"); if (signVerified){ // TODO 验签成功后 //按照支付结果异步通知中的描述,对支付结果中的业务内容进行1\2\3\4二次校验,校验成功后在response中返回success } else { // TODO 验签失败则记录异常日志,并在response中返回fail. } ``` **注意**: - 状态 `TRADE_SUCCESS` 的通知触发条件是商家开通的产品支持退款功能的前提下,买家付款成功。 - 交易状态 `TRADE_FINISHED` 的通知触发条件是商家开通的产品不支持退款功能的前提下,买家付款成功或商家开通的产品支持退款功能的前提下,交易已经成功并且已经超过可退款期限。 - 响应值 | **响应值** | **描述** | **异步是否重试发送** | | --- | --- | --- | | fail | 消息获取失败 | 重试 | | success | 消息获取成功 | 不重试 |