Skip to content

回调通知机制

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

回调 URL 配置

回调 URL 从商户表 sys_huanyu_merchantcallback_url 字段读取。商户需要在后台正确配置回调地址以接收支付结果通知。

回调触发时机

  • 买单: 支付渠道确认收款后(状态:pendingconfirmed
  • 卖单: 支付渠道确认付款后(状态:pendingconfirmed

回调数据格式

系统向商户回调 URL 发送 POST 请求(application/x-www-form-urlencoded 格式),包含以下参数:

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

回调数据示例

merchant_id=42&order_id=123&order_no=ORD202601202128386362&order_type=1&status=confirmed&cny_amount=73000&merchant_amount=73000&merchant_actual_amount=70808&service_amount=2192&createtime=1768915718&updatetime=1768915900&timestamp=1768915900&nonce=abc123def456&signature=GENERATED_SIGNATURE_HERE

回调签名验证

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

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

WARNING

收到回调后必须进行签名验证,防止伪造通知。验证通过后,建议通过 查询订单详情 二次确认订单状态,避免被篡改的回调误导。

回调响应要求

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

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

或者

json
{"status": "success"}

回调重试机制

当回调失败时(非 200、响应不含 success、或请求超时),系统按以下间隔重试:

重试次数间隔
第 1 次60 秒后
第 2 次120 秒后
第 3 次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) {
        // 更新数据库中的订单状态
        // TODO: 实现具体的业务逻辑
    }
}

// 处理回调
$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 { order_no, status } = callbackData;

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

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

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

        const sortedKeys = Object.keys(data).sort();

        let signString = '';
        sortedKeys.forEach(key => {
            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 crypto.timingSafeEqual(
            Buffer.from(expectedSignature),
            Buffer.from(signature)
        );
    }

    updateOrderStatus(orderNo, status) {
        // 更新数据库中的订单状态
        // TODO: 实现具体的业务逻辑
        console.log(`更新订单 ${orderNo} 状态为 ${status}`);
    }
}

const handler = new CallbackHandler();

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

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