主题
回调通知
支付终态平台主动 POST 你的 notify_url(创建时快照):
X-App-Id / X-Timestamp / X-Nonce / X-Signature // 与请求签名同一套,用 app_secret 验签
{"event_type":"payment.succeeded","payment_no":"PAY...","out_trade_no":"...","amount":1000,"paid_at":"..."}
// closed 事件为 closed_at + closed_reason(expired|manual)应答约定
- HTTP 2xx = 成功;否则重试梯度
1m→5m→30m→2h→6h共 5 次后置 dead(平台人工兜底)。 - 单次超时 5s——请快速应答,业务处理异步化。
- at-least-once:可能重复投递,请按
payment_no + event_type幂等处理。 - 极端时序:可能先收
closed再收succeeded(渠道扣款晚于关单的补单),以查单为准。
回调验签(必须)
收到回调后,用你的 app_secret 对收到的请求按签名串规则重新计算 X-Signature,constant-time 比较;验证通过才处理业务。
Go
go
func verifyNotify(secret string, r *http.Request, body []byte) (bool, error) {
raw := r.Method + "\n" + r.URL.Path + "\n" + r.URL.RawQuery + "\n" +
r.Header.Get("X-Timestamp") + "\n" + r.Header.Get("X-Nonce") + "\n" + string(body)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(raw))
want := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(want), []byte(r.Header.Get("X-Signature"))), nil
}PHP
php
function verifyNotify(string $secret, array $headers, string $body): bool {
$raw = implode("\n", [$headers['REQUEST_METHOD'] ?? 'POST', parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?? '',
$_SERVER['QUERY_STRING'] ?? '', $headers['X-Timestamp'] ?? '', $headers['X-Nonce'] ?? ''])
. "\n" . $body;
$sig = hash_hmac('sha256', $raw, $secret);
return hash_equals($sig, strtolower($headers['X-Signature'] ?? ''));
}若
X-Timestamp与本地时差 > 5 分钟直接丢弃(防重放),X-Nonce建议记录 8h 去重(稳健对接方在验签后统一处理)。
外发事件 event_type
| 事件 | 触发 | 说明 |
|---|---|---|
payment.succeeded | 支付成功 | 含 paid_at |
payment.closed | 订单关闭 | 含 closed_at + closed_reason(expired/manual) |
payment.refunded | 退款成功 | 含 refunded_at + refund_amount |
订单状态字典见查单 · 状态字典。