Skip to content

回调通知机制

当订单状态变更为 confirmed 时,系统会异步发送回调通知到商户配置的回调 URL。回调通知采用队列机制,确保可靠投递。

调试回调

可用 回调验签工具 验证收到的回调签名、生成测试回调、向你的回调地址发送测试。

回调触发时机

  • 买单: 交易员确认收款后(状态:pendingconfirmed
  • 卖单: 商户确认付款后(状态:matchingconfirmed

回调数据格式

请求格式

Content-Type: application/x-www-form-urlencoded,参数以表单形式提交。接收端使用标准表单解析即可(PHP 用 $_POST,Java 用 @RequestParam,Python 用 request.form)。

系统会向商户配置的回调 URL 发送 POST 请求,包含以下参数:

参数名类型说明
merchant_idint商户 ID
order_idint订单 ID
order_nostring订单编号
order_typeint订单类型(1=买入,2=卖出)
statusstring订单状态(confirmed
cny_amountdecimalCNY 金额
merchant_amountdecimal商户 USDT 数量
merchant_actual_amountdecimal商户实际到账 USDT 数量
service_amountdecimal服务费金额
createtimeint订单创建时间戳
updatetimeint订单更新时间戳
timestampint回调时间戳
noncestring随机字符串
signaturestring回调签名,用于验证回调真实性

回调签名验证

商户端需要验证回调签名的真实性,验证步骤如下:

  1. 移除回调数据中的 signature 参数
  2. 按照参数名字典序排序所有参数
  3. 拼接参数为 key=value& 格式(跳过空值)
  4. 在末尾追加 &api_secret=YOUR_API_SECRET
  5. 对完整字符串进行 MD5 加密并转为大写
  6. 与回调中的 signature 参数对比

算法一致

回调签名验证与创建订单的签名算法保持一致,都使用 &api_secret=API_SECRET 格式,详见 签名算法

回调响应要求

商户的回调接口需要返回特定的响应内容:

  • HTTP 状态码: 200
  • 响应内容: 包含 successSUCCESS 字符串
text
success

或者

json
{"status": "success"}

回调重试机制

  • 首次失败: 60 秒后重试
  • 第二次失败: 120 秒后重试
  • 第三次失败: 180 秒后重试
  • 最多重试: 默认 5 次(可配置)
  • 最终失败: 记录日志,不再重试

回调处理示例代码

php
<?php
// 回调处理示例
class CallbackHandler {
    private $apiSecret = 'your_api_secret';

    public function handleCallback() {
        // 获取POST数据
        $callbackData = $_POST;

        // 验证签名
        if (!$this->verifySignature($callbackData)) {
            http_response_code(400);
            echo 'error: Invalid signature';
            return;
        }

        // 处理业务逻辑
        $orderNo = $callbackData['order_no'];
        $status = $callbackData['status'];

        // 更新本地订单状态
        $this->updateOrderStatus($orderNo, $status);

        // 返回成功响应
        echo 'success';
    }

    private function verifySignature($data) {
        $signature = $data['signature'] ?? '';
        unset($data['signature']);

        ksort($data);

        $signString = '';
        foreach ($data as $key => $value) {
            if ($value !== '' && $value !== null) {
                $signString .= $key . '=' . $value . '&';
            }
        }
        $signString .= 'api_secret=' . $this->apiSecret;

        $expectedSignature = strtoupper(md5($signString));

        return hash_equals($expectedSignature, $signature);
    }

    private function updateOrderStatus($orderNo, $status) {
        // 更新数据库中的订单状态
        // 实现具体的业务逻辑
    }
}

// 处理回调
$handler = new CallbackHandler();
$handler->handleCallback();
javascript
const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.urlencoded({ extended: true }));

class CallbackHandler {
    constructor() {
        this.apiSecret = 'your_api_secret';
    }

    handleCallback(req, res) {
        const callbackData = req.body;

        // 验证签名
        if (!this.verifySignature(callbackData)) {
            return res.status(400).send('error: Invalid signature');
        }

        // 处理业务逻辑
        const orderNo = callbackData.order_no;
        const status = callbackData.status;

        // 更新本地订单状态
        this.updateOrderStatus(orderNo, status);

        // 返回成功响应
        res.send('success');
    }

    verifySignature(data) {
        const signature = data.signature;
        delete data.signature;

        const sortedKeys = Object.keys(data).sort();
        let signString = '';
        for (const key of sortedKeys) {
            if (data[key] !== '' && data[key] !== null) {
                signString += key + '=' + data[key] + '&';
            }
        }
        signString += 'api_secret=' + this.apiSecret;

        const expectedSignature = crypto
            .createHash('md5')
            .update(signString)
            .digest('hex')
            .toUpperCase();

        return expectedSignature === signature;
    }

    updateOrderStatus(orderNo, status) {
        // 更新数据库中的订单状态
        console.log(`更新订单 ${orderNo} 状态为 ${status}`);
    }
}

const handler = new CallbackHandler();

// 回调接口
app.post('/callback', (req, res) => {
    handler.handleCallback(req, res);
});

app.listen(3000, () => {
    console.log('回调服务器运行在端口 3000');
});