收款 Webhook
当收款状态发生变更时,A55 向您的 webhook_url 发送 POST 请求,携带 JSON 载荷。
载荷字段
| 字段 | 类型 | 说明 |
|---|---|---|
charge_uuid | string (UUID) | 收款标识符 |
status | string | 当前状态:confirmed、error、refunded、chargeback_requested、chargeback_refunded |
transaction_reference | string | 您的外部引用(如创建时提供) |
subscription_uuid | string | 订阅标识符(如适用) |
amount | number | 收款金额 |
currency | string | ISO 货币代码 |
created_at | string | 创建时间戳(ISO 8601) |
updated_at | string | 最后更新时间戳(ISO 8601) |
各状态载荷示例
- 已确认
- 错误 / 已拒绝
- 已退款
- 拒付
{
"charge_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "confirmed",
"transaction_reference": "ord_001",
"amount": 100.50,
"currency": "BRL",
"created_at": "2026-03-20T10:00:00Z",
"updated_at": "2026-03-20T10:00:15Z"
}
{
"charge_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "error",
"transaction_reference": "ord_001",
"amount": 100.50,
"currency": "BRL",
"created_at": "2026-03-20T10:00:00Z",
"updated_at": "2026-03-20T10:00:20Z"
}
{
"charge_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "refunded",
"transaction_reference": "ord_001",
"amount": 100.50,
"currency": "BRL",
"created_at": "2026-03-20T10:00:00Z",
"updated_at": "2026-03-21T14:30:00Z"
}
{
"charge_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "chargeback_requested",
"transaction_reference": "ord_001",
"amount": 100.50,
"currency": "BRL",
"created_at": "2026-03-20T10:00:00Z",
"updated_at": "2026-03-25T09:00:00Z"
}
处理代码
- Python (Flask)
- JavaScript (Express)
import json, os
from flask import Flask, request, Response
app = Flask(__name__)
@app.route("/webhooks/a55", methods=["POST"])
def handle_charge_webhook():
"""处理收款状态变更 Webhook。"""
raw = request.get_data()
sig = request.headers.get("X-Webhook-Signature", "")
ts = request.headers.get("X-Webhook-Timestamp", "")
if not verify_signature(raw, sig, ts, os.environ["WEBHOOK_SECRET"]):
return Response(status=401)
event = json.loads(raw)
charge_uuid = event["charge_uuid"]
status = event["status"]
if already_processed(charge_uuid, status):
return Response(status=200)
if status == "confirmed":
fulfill_order(charge_uuid)
elif status == "error":
notify_payment_failed(charge_uuid)
elif status == "refunded":
process_refund(charge_uuid)
elif status == "chargeback_requested":
flag_chargeback(charge_uuid)
mark_processed(charge_uuid, status)
return Response(status=200)
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhooks/a55', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-webhook-signature'] || '';
const ts = req.headers['x-webhook-timestamp'] || '';
if (!verifySignature(req.body, sig, ts, process.env.WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body);
const { charge_uuid, status } = event;
switch (status) {
case 'confirmed':
fulfillOrder(charge_uuid);
break;
case 'error':
notifyPaymentFailed(charge_uuid);
break;
case 'refunded':
processRefund(charge_uuid);
break;
case 'chargeback_requested':
flagChargeback(charge_uuid);
break;
}
res.sendStatus(200);
});
最佳实践
| 实践 | 原因 |
|---|---|
| 首先验证 HMAC 签名 | 防止伪造事件 |
| 立即返回 200 | 避免超时和重试 |
| 在后台队列中处理 | 耗时操作不应阻塞 HTTP 处理器 |
按 charge_uuid + status 去重 | 至少一次投递意味着可能收到重复事件 |
| 记录原始载荷 | 调试审计追踪 |
| 切勿仅依赖重定向 URL 中的状态 | 始终通过 Webhook 确认 |