# 简介
异步通知是指一笔订单支付完成后,支付宝会将该笔订单的变更信息,沿着商家调用支付请求时所传入的异步通知地址 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 | 消息获取成功 | 不重试 |