Appearance
异步通知接口
接口说明
支付完成后,平台会向商户提供的 notify_url 发送异步通知。商户需正确处理通知并返回成功响应。
通知特性
- 通知方式: HTTP POST
- Content-Type:
application/json - 编码: UTF-8
- 签名: RSA-SHA256
- 重试机制: 最多重试 10 次
通知参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| pay_mch_id | String | 商户号 |
| trade_no | String | 平台交易号 |
| out_trade_no | String | 商户订单号 |
| trade_no | String | 支付交易号 |
| transaction_id | String | 支付宝交易号(仅支付宝) |
| total_amount | Integer | 订单总金额,单位:分 |
| pay_amount | Integer | 实际支付金额,单位:分 |
| status | String | 订单状态:SUCCESS/CLOSED/REFUND |
| channel | String | 支付渠道:wechat/alipay |
| pay_time | String | 支付完成时间,格式:yyyy-MM-dd HH:mm:ss |
| notify_time | String | 通知时间,格式:yyyy-MM-dd HH:mm:ss |
| subject | String | 商品名称 |
| buyer_id | String | 买家ID |
| attach | String | 附加数据 |
| sign_type | String | 签名类型:RSA |
| sign | String | 签名值 |
通知示例
微信支付通知
json
{
"pay_mch_id": "1234567890",
"trade_no": "MF20240101120000001",
"out_trade_no": "ORDER20240101120000",
"trade_no": "WX20240101120000001",
"total_amount": 100,
"pay_amount": 100,
"status": "SUCCESS",
"channel": "wechat",
"pay_time": "2024-01-01 12:00:00",
"notify_time": "2024-01-01 12:00:01",
"subject": "测试商品",
"buyer_id": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
"attach": "自定义数据",
"sign_type": "RSA",
"sign": "签名值"
}支付宝通知
json
{
"pay_mch_id": "1234567890",
"trade_no": "MF20240101120000001",
"out_trade_no": "ORDER20240101120000",
"trade_no": "ALI20240101120000001",
"transaction_id": "2024010122001234567890",
"total_amount": 100,
"pay_amount": 100,
"status": "SUCCESS",
"channel": "alipay",
"pay_time": "2024-01-01 12:00:00",
"notify_time": "2024-01-01 12:00:01",
"subject": "测试商品",
"buyer_id": "20881234567890",
"attach": "",
"sign_type": "RSA",
"sign": "签名值"
}处理流程
1. 验证签名
使用平台公钥验证通知签名,确保通知来自平台。
2. 验证数据
- 检查
out_trade_no是否存在 - 验证
total_amount与订单金额是否一致 - 确认订单状态为
SUCCESS
3. 处理业务
- 更新订单状态
- 发货或提供服务
- 发送通知给用户
4. 返回响应
必须返回纯文本 success,否则平台会认为通知失败并重试。
success失败时返回:
failure代码示例
Node.js (Express)
javascript
const express = require('express');
const router = express.Router();
router.post('/api/notify', async (req, res) => {
try {
const notifyData = req.body;
// 1. 验证签名
const isValid = verifySign(notifyData, platformPublicKey);
if (!isValid) {
console.error('签名验证失败');
res.send('failure');
return;
}
// 2. 验证订单金额
const order = await getOrder(notifyData.out_trade_no);
if (order.total_amount !== notifyData.total_amount) {
console.error('订单金额不匹配');
res.send('failure');
return;
}
// 3. 处理业务
if (notifyData.status === 'SUCCESS') {
await updateOrderStatus(notifyData.out_trade_no, 'PAID');
// 发货、发送通知等
await sendNotification(notifyData.out_trade_no);
}
// 4. 返回成功
res.send('success');
} catch (error) {
console.error('处理通知失败:', error);
res.send('failure');
}
});
module.exports = router;Java (Spring Boot)
java
@RestController
@RequestMapping("/api")
public class NotifyController {
@PostMapping("/notify")
public String handleNotify(@RequestBody NotifyData notifyData) {
try {
// 1. 验证签名
if (!SignUtil.verify(notifyData, platformPublicKey)) {
log.error("签名验证失败");
return "failure";
}
// 2. 验证订单
Order order = orderService.getByOutTradeNo(notifyData.getOutTradeNo());
if (!order.getTotalAmount().equals(notifyData.getTotalAmount())) {
log.error("订单金额不匹配");
return "failure";
}
// 3. 处理业务
if ("SUCCESS".equals(notifyData.getStatus())) {
orderService.updateStatus(notifyData.getOutTradeNo(), "PAID");
// 发货等
}
// 4. 返回成功
return "success";
} catch (Exception e) {
log.error("处理通知失败", e);
return "failure";
}
}
}PHP
php
<?php
$postData = file_get_contents('php://input');
$notifyData = json_decode($postData, true);
try {
// 1. 验证签名
if (!SignUtil::verify($notifyData, $platformPublicKey)) {
echo 'failure';
exit;
}
// 2. 验证订单
$order = getOrder($notifyData['out_trade_no']);
if ($order['total_amount'] != $notifyData['total_amount']) {
echo 'failure';
exit;
}
// 3. 处理业务
if ($notifyData['status'] === 'SUCCESS') {
updateOrderStatus($notifyData['out_trade_no'], 'PAID');
// 发货等
}
// 4. 返回成功
echo 'success';
} catch (Exception $e) {
echo 'failure';
}重试机制
通知频率
平台会在以下时间点发送通知:
0s, 15s, 15s, 30s, 3min, 10min, 20min, 30min, 30min总共最多重试 10 次。
停止重试条件
- 商户返回
success - 达到最大重试次数
幂等性处理
由于可能收到多次通知,商户系统必须保证幂等性:
javascript
// 检查订单是否已处理
const order = await getOrder(outTradeNo);
if (order.status === 'PAID') {
// 已处理,直接返回成功
res.send('success');
return;
}注意事项
- 必须验证签名: 确保通知来自平台,防止伪造
- 验证订单金额: 防止金额被篡改
- 保证幂等性: 同一通知多次处理结果一致
- 快速响应: 尽快返回响应,避免超时
- 记录日志: 记录所有通知,便于问题排查
- HTTPS 地址: notify_url 必须是公网可访问的 HTTPS 地址
常见问题
Q: 收不到通知怎么办?
- 检查
notify_url是否为公网 HTTPS 地址 - 检查服务器防火墙是否拦截
- 查看平台通知发送记录
- 主动调用交易查询接口确认订单状态
Q: 通知处理失败怎么办?
- 检查签名验证逻辑
- 验证订单金额是否匹配
- 查看服务器错误日志
- 平台会按重试机制继续发送
Q: 如何处理重复通知?
实现幂等性检查:
javascript
// 先查询订单状态
const order = await getOrder(outTradeNo);
// 如果已处理,直接返回成功
if (order.status === 'PAID') {
res.send('success');
return;
}
// 否则处理业务逻辑安全建议
- 使用 HTTPS 接收通知
- 严格验证签名
- 记录所有请求日志
- 限制访问频率,防止恶意请求
- 定期检查通知处理是否正常
