限流与幂等
限流保护服务稳定,幂等保证重试安全。资金类请求请始终携带 Idempotency-Key。
请求限流
| 项目 | 值 |
|---|---|
| 限额 | 每个 API Key 每分钟 120 次请求(可由部署方用 MERCHANT_RATE_LIMIT 调整) |
| 超出时 | HTTP 429,code 为 RATE_LIMITED |
| 计数范围 | 按 Key 统计,与该 Key 下的接口、参数无关 |
建议为不同环境(测试 / 生产)创建不同的 Key,避免互相占用额度。
Idempotency-Key
资金类 POST 接口支持 Idempotency-Key 请求头。服务端会保存首次成功响应 24 小时:
| 场景 | 行为 |
|---|---|
| 同键、同参数、首次成功 | 正常执行并记录响应 |
| 同键、同参数、24 小时内重试 | 直接重放原响应,并返回响应头 Idempotent-Replayed: true |
| 同键、不同参数 | HTTP 422,IDEMPOTENCY_KEY_REUSED |
| 同键、首次请求仍在处理中 | HTTP 409,IDEMPOTENCY_IN_PROGRESS |
| 首次请求失败(非 2xx) | 不保存,可用同一个键安全重试 |
键的格式:8–255 位字母、数字或 - _ : .,推荐 UUID v4。校验时不会把 PIN、密码等敏感字段计入参数摘要。
reference 去重
POST /api/v1/payments 的 reference 是业务订单号:同一商户内,相同 reference 且金额一致时返回同一笔支付;金额不同返回 409 REFERENCE_CONFLICT。适合作为支付创建的业务幂等键。
重试示例
超时或 5xx 时,用同一个 Idempotency-Key 重试;4xx 不要重试。
import os
import time
import uuid
import requests
key = str(uuid.uuid4())
payload = {"amount": "12.34", "reference": "order-2026-0001"}
headers = {
"Authorization": f"Bearer {os.environ['SPACEPAY_KEY']}",
"Idempotency-Key": key,
}
for attempt in range(5):
try:
resp = requests.post(
"https://your-domain/api/v1/payments",
headers=headers, json=payload, timeout=15,
)
except requests.Timeout:
time.sleep(2 ** attempt)
continue
if resp.status_code < 500 and resp.status_code != 429:
resp.raise_for_status()
break
time.sleep(2 ** attempt)
else:
raise RuntimeError("重试次数用尽")
import { randomUUID } from 'node:crypto';
const idempotencyKey = randomUUID();
const payload = { amount: '12.34', reference: 'order-2026-0001' };
let data;
for (let attempt = 0; attempt < 5; attempt++) {
try {
const resp = await fetch('https://your-domain/api/v1/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SPACEPAY_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(15000),
});
if (resp.status < 500 && resp.status !== 429) {
data = await resp.json();
if (!resp.ok) throw new Error(data.error || data.code);
break;
}
} catch (e) {
if (attempt === 4) throw e;
}
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
}
有效期一览
| 对象 | 有效期 |
|---|---|
支付单(checkout_url) | 30 分钟,过期后不能再支付 |
| Idempotency-Key 记录 | 24 小时 |
| Webhook 重试 | 最多 6 次,跨度约 8 小时 |
| 商户后台 API 请求日志 | 最近 5 万条 |
i幂等键记录保存在数据库中,多实例共享;限流计数器按部署实例维护,多实例部署时建议在网关层统一限流。