﻿## 使用说明

异步通知是指一笔订单支付完成后，支付宝会将该笔订单的变更信息，沿着商家调用支付请求时传入的异步通知地址 `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 | 商家业务号，主要是退款通知中返回退款申请的流水号。 | - | HZRF001 |
| `buyer_id` / `buyer_open_id` | String | 可选 | 128 | 买家支付宝用户号或买家支付宝 open_id。 | 新商户建议使用 `buyer_open_id` 替代 `buyer_id`。如使用 open_id，请确认“应用-开发配置-openid 配置管理”已启用。 | - |
| `buyer_logon_id` | String | 可选 | 100 | 买家支付宝账号。 | - | 180****0062 |
| `seller_id` | String | 可选 | 30 | 卖家支付宝用户号。 | - | 2088101106499364 |
| `seller_email` | String | 可选 | 100 | 卖家支付宝账号。 | - | zhuzhanghu@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交易内容 |
| `passback_params` | String | 可选 | 512 | 公共回传参数。 | 如果请求时传递该参数，则异步通知会原样返回。本参数必须先进行 UrlEncode 后再发送给支付宝。 | - |
| `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折优惠"}]` |

## 交易状态说明

| 枚举名称 | 枚举说明 |
| --- | --- |
| `WAIT_BUYER_PAY` | 交易创建，等待买家付款。 |
| `TRADE_CLOSED` | 未付款交易超时关闭，或支付完成后全额退款。 |
| `TRADE_SUCCESS` | 交易支付成功。 |
| `TRADE_FINISHED` | 交易结束，不可退款。 |

## 通知触发条件

| 触发条件名 | 触发条件描述 | 触发条件默认值 |
| --- | --- | --- |
| `TRADE_FINISHED` | 交易完成 | `true`（触发通知） |
| `TRADE_SUCCESS` | 支付成功 | `true`（触发通知） |
| `TRADE_CLOSED` | 交易关闭 | `true`（触发通知） |
| `WAIT_BUYER_PAY` | 交易创建 | `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[] | 可选 | - | 优惠券的其他出资方明细。 | 子字段见下方 `otherContributeDetail[]`。 | - |
| `otherContributeDetail[].contributeType` | String | 可选 | 32 | 出资方类型。 | 如品牌商出资、支付宝平台出资等。 | PLATFORM |
| `otherContributeDetail[].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://api.xx.com/receive_notify.htm`，对应接收到通知的示例如下。

注意：以下示例报文仅供参考，实际返回的详细报文请以实际返回为准。

```text
https://api.xx.com/receive_notify.htm?gmt_payment=2015-06-11 22:33:59&notify_id=42af7baacd1d3746cf7b56752b91edcj34&seller_email=testyufabu07@alipay.com&notify_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&notify_time=2015-06-11 22:34:03&subject=FACE_TO_FACE_PAYMENT_PRECREATE中文&trade_status=TRADE_SUCCESS&sign_type=RSA2
```

验签步骤如下：

1. 在通知返回参数列表中，除去 `sign`、`sign_type` 两个参数外，通知返回的参数均为待验签参数。
2. 将剩余参数进行 `URLDecode`，然后按字典排序组成字符串，得到待签名字符串：

```text
gmt_create=2015-06-11 22:33:46&gmt_payment=2015-06-11 22:33:59&notify_id=42af7baacd1d3746cf7b56752b91edcj34&notify_time=2015-06-11 22:34:03&notify_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
```

3. 将签名参数 `sign` 使用 base64 解码为字节码串。
4. 使用 RSA 验签方法，通过签名字符串、签名参数（经过 base64 解码）及支付宝公钥验证签名。
5. 在步骤四验证签名正确后，必须再严格校验通知数据的正确性：

| 校验项 | 校验说明 |
| --- | --- |
| `out_trade_no` | 校验通知数据中的 `out_trade_no` 是否为商家系统中创建的订单号。 |
| `total_amount` | 判断 `total_amount` 是否确实为该订单的实际金额，即商家订单创建时的金额。 |
| `seller_id` / `seller_email` | 校验通知中的 `seller_id` 或 `seller_email` 是否为 `out_trade_no` 这笔单据的对应操作方。 |
| `app_id` | 验证 `app_id` 是否为该商家本身。 |

上述任一校验项不通过，则表明本次通知是异常通知，务必忽略。

上述校验通过后，商家必须根据支付宝不同类型的业务通知进行业务处理，并过滤重复通知结果数据。在支付宝的业务通知中，只有交易通知状态为 `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` | 消息获取成功 | 不重试 |

## 异步通知触发时机和通知参数说明

注意：如果签约不支持退款，或者定制了异步通知的触发条件和通知字段，则本节说明不适用。

在交易支付成功、交易关单、交易退款、交易分账等场景下都可能触发异步通知。各场景的关键通知参数区分如下：

| 场景 | 通知参数特征 |
| --- | --- |
| 用户支付成功通知 | `trade_status=TRADE_SUCCESS`，且不包含 `out_biz_no` 字段。 |
| 退款成功通知 | 通知参数包含 `gmt_refund`、`refund_fee`、`out_biz_no` 字段，其中 `out_biz_no` 为退款请求号。 |
| 交易未支付超时关单通知 | 通知参数包含 `gmt_close`、`refund_fee` 字段，其中 `refund_fee=0`、`trade_status=TRADE_CLOSED`。如果用户在收银台没有点击过提交支付，则不会真正创建交易，该场景下不会有未支付超时关单通知。 |
| 交易分账成功通知 | 通知参数包含 `out_biz_no` 字段，其中 `out_biz_no` 为分账请求号。 |
