订单异步通知

更新时间:2026-07-01  ·  通知方式:POST

接口说明

异步通知回调接口是数汇科技支付系统在订单支付成功后,主动向商户服务器发送的支付结果通知。商户通过该接口可以实时获取订单支付状态,确保业务处理的及时性和准确性。

通知方式
POST
通知地址
商户下单时提供的 notifyUrl 地址
数据格式
application/x-www-form-urlencodedapplication/json
⚠️
重要说明:异步通知是确保支付结果准确性的重要机制,商户必须正确处理该回调接口。

异步通知流程图

异步通知流程图

通知参数

数汇科技系统会以 POST 方式向商户在下单时提供的 notifyUrl 地址发送支付结果通知。

参数名类型必填说明
userIdString Y 由我司分配的唯一用户编号(代理通用)
orderNoString Y 商户订单编号
transactionNoString Y 系统订单编号(平台订单号)
amountString Y 订单金额(元)
signString Y 签名(签名方式同下单时一致)

通知示例

Form Data
{
  "userId": "your_open_userid",
  "orderNo": "ORDER20250115001",
  "transactionNo": "YSF202501151030001234567890",
  
  "amount": "100.00",
  "sign": "calculated_sign_value"
}

返回要求

🚫
重要要求:收到通知后需要返回大写 SUCCESS

商户服务器收到异步通知后,必须返回字符串 SUCCESS(大写),表示已成功接收通知。

返回示例是否正确说明
SUCCESS ✓ 正确 正确返回,大写
success ✗ 错误 错误:小写
OK ✗ 错误 错误:错误字符串
{"code": "success"} ✗ 错误 错误:JSON 格式

重试机制

ℹ️
重试规则:如未返回 SUCCESS,系统将按以下频率进行回调,最多回调 15 次。
回调次数重试间隔说明
首次回调失败后5 秒快速重试
第二次30 秒短期重试
第三次60 秒中期重试
第四次10 分钟长期重试开始
第五次及后续1 小时定期重试直到 15 次

签名验证

收到回调参数后,需要验证签名以确保请求来源的合法性。签名方式与下单时保持一致(RSA2 / MD5 / SM2)。

⚠️
本页为接口介绍,仅作示例参考。各签名方式的完整实现请查看「通用规则 - 签名规则」页面。

验签流程

步骤操作说明
1获取回调数据从请求 Body 中获取原始数据(支持 JSON 或 URL 编码格式)
2解析参数根据数据格式解析为关联数组 / 对象,包含 userId、orderNo、sign 等字段
3提取并移除 sign保存 sign 参数值,然后从参数中移除 sign 字段
4构建验签字符串将剩余参数按字母排序后转为 JSON 字符串(不转义中文、不转义斜杠)
5验证签名使用对应签名算法(RSA2 / MD5 / SM2)验证签名
6处理业务逻辑验签成功后更新订单状态,返回 SUCCESS

代码示例

PHP
<?php

/** 异步回调处理示例 */
public function notify()
{
    // 获取回调参数
    $param = [
        'userId' => $_POST['userId'],
        'orderNo' => $_POST['orderNo'],
        'transactionNo' => $_POST['transactionNo'],
        
        'amount' => $_POST['amount'],
    ];
    
    // 对参数进行排序
    ksort($param);
    
    // 验证签名(根据签名方式选择 RSA2 或 MD5)
    $sign = $_POST['sign'];
    $verifyResult = $this->verifySign($param, $sign);
    
    if ($verifyResult && !empty($sign)) {
        // 签名验证成功
        $outTradeNo = $_POST['orderNo'];
        
        // 幂等判断:检查订单是否已处理
        $order = $this->orderModel->where('orderNo', $outTradeNo)->find();
        if ($order && $order['status'] == 1) {
            // 订单已处理,直接返回成功
            return 'SUCCESS';
        }
        
        // 更新订单状态
        $this->orderModel->where('orderNo', $outTradeNo)->update([
            'status' => 1,
            'transactionNo' => $_POST['transactionNo'],
            'pay_time' => date('Y-m-d H:i:s')
        ]);
        
        return 'SUCCESS';  // 必须大写
    } else {
        return 'sign fail';
    }
}

/** MD5 验签方法 */
private function md5Verify($data, $sign, $md5Key)
{
    ksort($data);
    $str = '';
    foreach ($data as $k => $v) {
        $str .= "&{$k}={$v}";
    }
    $str = substr($str, 1);
    return md5($str . $md5Key) === $sign;
}

/** RSA2 验签方法 */
private function rsaVerify($data, $sign, $publicKey)
{
    ksort($data);
    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $res = "-----BEGIN PUBLIC KEY-----\n" .
        wordwrap($publicKey, 64, "\n", true) .
        "\n-----END PUBLIC KEY-----";
    $pubkeyid = openssl_pkey_get_public($res);
    return openssl_verify($json, base64_decode($sign), $pubkeyid, OPENSSL_ALGO_SHA256) === 1;
}
Java
import java.util.*;
import javax.servlet.http.HttpServletRequest;

/** 异步通知处理示例 */
@PostMapping("/notify")
public String handleNotify(HttpServletRequest request) {
    try {
        // 获取回调参数
        Map<String, String> params = new TreeMap<>();
        params.put("userId", request.getParameter("userId"));
        params.put("orderNo", request.getParameter("orderNo"));
        params.put("transactionNo", request.getParameter("transactionNo"));
        
        params.put("amount", request.getParameter("amount"));
        
        String sign = request.getParameter("sign");
        
        // 验证签名
        boolean verifyResult = verifySign(params, sign);
        
        if (verifyResult) {
            String outTradeNo = params.get("orderNo");
            
            // 幂等判断
            Order order = orderService.findByOutTradeNo(outTradeNo);
            if (order != null && order.getStatus() == 1) {
                return "SUCCESS";
            }
            
            // 更新订单状态
            orderService.updatePayStatus(outTradeNo, 
                params.get("transactionNo"), 
                params.get("amount"));
            
            return "SUCCESS";
        } else {
            return "sign fail";
        }
    } catch (Exception e) {
        log.error("处理异步通知异常", e);
        return "error";
    }
}

/** MD5验签 */
private boolean verifySign(Map<String, String> params, String sign) {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry<String, String> entry : params.entrySet()) {
        if (sb.length() > 0) sb.append("&");
        sb.append(entry.getKey()).append("=").append(entry.getValue());
    }
    sb.append(md5Key);
    String calculatedSign = DigestUtils.md5Hex(sb.toString());
    return calculatedSign.equals(sign);
}
Python (Flask)
from flask import Flask, request
import hashlib

app = Flask(__name__)

@app.route('/notify', methods=['POST'])
def handle_notify():
    """异步通知处理"""
    try:
        # 获取回调参数
        params = {
            'userId': request.form.get('userId'),
            'orderNo': request.form.get('orderNo'),
            'transactionNo': request.form.get('transactionNo'),
            
            'amount': request.form.get('amount'),
        }
        
        sign = request.form.get('sign')
        
        # 验证签名
        if verify_sign(params, sign):
            orderNo = params['orderNo']
            
            # 幂等判断
            order = Order.query.filter_by(orderNo=orderNo).first()
            if order and order.status == 1:
                return 'SUCCESS'
            
            # 更新订单状态
            order.status = 1
            order.transactionNo = params['transactionNo']
            order.pay_time = datetime.now()
            db.session.commit()
            
            return 'SUCCESS'
        else:
            return 'sign fail'
    except Exception as e:
        print(f"处理异步通知异常: {e}")
        return 'error'

def verify_sign(params, sign):
    """MD5验签"""
    md5_key = 'your_md5_key'
    sorted_params = sorted(params.items())
    sign_str = '&'.join([f'{k}={v}' for k, v in sorted_params])
    sign_str += md5_key
    calculated_sign = hashlib.md5(sign_str.encode()).hexdigest()
    return calculated_sign == sign

if __name__ == '__main__':
    app.run(debug=True)

注意事项

🚫
重要提醒:
  • 商户必须在收到通知后验证签名,确保通知来源的合法性
  • 商户处理完业务后必须返回大写 SUCCESS,否则系统会持续重试
  • 通知可能会因网络等原因延迟或重复,商户需做好幂等处理
  • 不能依赖通知的顺序,应以支付平台返回的支付状态为准
⚠️
幂等性处理:
  • 同一订单可能收到多次通知,商户需根据订单号做幂等判断
  • 建议在处理前先检查订单状态,避免重复处理
  • 使用数据库事务或分布式锁确保数据一致性
最佳实践:
  • 先验签再处理业务,验签失败直接返回错误
  • 验签成功后立即返回 SUCCESS,业务处理异步进行
  • 记录完整的通知日志,便于问题排查
  • 建议配合订单查询接口,主动查询支付结果作为补充