主题
回调通知机制
当订单状态变更为 confirmed 时,系统会异步发送回调通知到商户配置的回调 URL。回调通知采用队列机制,确保可靠投递。
调试回调
可用 回调验签工具 验证收到的回调签名、生成测试回调、向你的回调地址发送测试。
回调触发时机
- 买单: 交易员确认收款后(状态:
pending→confirmed) - 卖单: 商户确认付款后(状态:
matching→confirmed)
回调数据格式
请求格式
Content-Type: application/x-www-form-urlencoded,参数以表单形式提交。接收端使用标准表单解析即可(PHP 用 $_POST,Java 用 @RequestParam,Python 用 request.form)。
系统会向商户配置的回调 URL 发送 POST 请求,包含以下参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
merchant_id | int | 商户 ID |
order_id | int | 订单 ID |
order_no | string | 订单编号 |
order_type | int | 订单类型(1=买入,2=卖出) |
status | string | 订单状态(confirmed) |
cny_amount | decimal | CNY 金额 |
merchant_amount | decimal | 商户 USDT 数量 |
merchant_actual_amount | decimal | 商户实际到账 USDT 数量 |
service_amount | decimal | 服务费金额 |
createtime | int | 订单创建时间戳 |
updatetime | int | 订单更新时间戳 |
timestamp | int | 回调时间戳 |
nonce | string | 随机字符串 |
signature | string | 回调签名,用于验证回调真实性 |
回调签名验证
商户端需要验证回调签名的真实性,验证步骤如下:
- 移除回调数据中的
signature参数 - 按照参数名字典序排序所有参数
- 拼接参数为
key=value&格式(跳过空值) - 在末尾追加
&api_secret=YOUR_API_SECRET - 对完整字符串进行 MD5 加密并转为大写
- 与回调中的
signature参数对比
算法一致
回调签名验证与创建订单的签名算法保持一致,都使用 &api_secret=API_SECRET 格式,详见 签名算法。
回调响应要求
商户的回调接口需要返回特定的响应内容:
- HTTP 状态码: 200
- 响应内容: 包含
success或SUCCESS字符串
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');
});