Skip to content

异步通知接口

接口说明

支付完成后,平台会向商户提供的 notify_url 发送异步通知。商户需正确处理通知并返回成功响应。

通知特性

  • 通知方式: HTTP POST
  • Content-Type: application/json
  • 编码: UTF-8
  • 签名: RSA-SHA256
  • 重试机制: 最多重试 10 次

通知参数

参数名类型说明
pay_mch_idString商户号
trade_noString平台交易号
out_trade_noString商户订单号
trade_noString支付交易号
transaction_idString支付宝交易号(仅支付宝)
total_amountInteger订单总金额,单位:分
pay_amountInteger实际支付金额,单位:分
statusString订单状态:SUCCESS/CLOSED/REFUND
channelString支付渠道:wechat/alipay
pay_timeString支付完成时间,格式:yyyy-MM-dd HH:mm:ss
notify_timeString通知时间,格式:yyyy-MM-dd HH:mm:ss
subjectString商品名称
buyer_idString买家ID
attachString附加数据
sign_typeString签名类型:RSA
signString签名值

通知示例

微信支付通知

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;
}

注意事项

  1. 必须验证签名: 确保通知来自平台,防止伪造
  2. 验证订单金额: 防止金额被篡改
  3. 保证幂等性: 同一通知多次处理结果一致
  4. 快速响应: 尽快返回响应,避免超时
  5. 记录日志: 记录所有通知,便于问题排查
  6. HTTPS 地址: notify_url 必须是公网可访问的 HTTPS 地址

常见问题

Q: 收不到通知怎么办?

  1. 检查 notify_url 是否为公网 HTTPS 地址
  2. 检查服务器防火墙是否拦截
  3. 查看平台通知发送记录
  4. 主动调用交易查询接口确认订单状态

Q: 通知处理失败怎么办?

  1. 检查签名验证逻辑
  2. 验证订单金额是否匹配
  3. 查看服务器错误日志
  4. 平台会按重试机制继续发送

Q: 如何处理重复通知?

实现幂等性检查:

javascript
// 先查询订单状态
const order = await getOrder(outTradeNo);

// 如果已处理,直接返回成功
if (order.status === 'PAID') {
  res.send('success');
  return;
}

// 否则处理业务逻辑

安全建议

  1. 使用 HTTPS 接收通知
  2. 严格验证签名
  3. 记录所有请求日志
  4. 限制访问频率,防止恶意请求
  5. 定期检查通知处理是否正常

Copyright © 2024 米付科技