项目身份
项目 slug、显示名称、生产环境与测试环境标识。
Multi-project payment orchestration
Payment Hub 负责订单、供应商和可靠回调。你的项目仍负责用户、商品权益与退款后的业务处理。
https://payments.null2null.com
v1
Bearer pay_live_...
Payment Hub 不创建用户,不保存会员权益,也不把 API Key 发送给浏览器或 App。订单成功后,由项目后端独立授予权益。
客户端只请求项目后端。所有带密钥的调用都发生在服务器之间,支付结果通过签名事件返回。
先生成唯一的 merchantOrderId,并记录期望的商品与用户。
传入服务端配置好的 productId 和销售渠道,获得托管收银台地址。
客户端只打开返回的 checkoutUrl,不会接触项目 API Key。
验签回调并主动查询订单。只有 succeeded 可以授予权益。
以下资料交给 Payment Hub 运维方。管理接口不会从公网开放。
项目 slug、显示名称、生产环境与测试环境标识。
HTTPS 回调地址,以及保存于双方服务端的 HMAC 密钥。
商品 ID、固定价格、结算币种和允许的分发渠道。
项目独立商户、商店或钱包。不同项目不得共用收款账户。
开通完成后只返回一次项目 API Key。请直接写入项目后端的密钥管理系统,不要保存到代码仓库、前端配置或日志。供应商账户和商品创建后默认停用,只有凭据与收银台安全参数通过服务端校验并由运维显式启用后才可创建订单。
所有创建请求都必须包含长度 16 至 80 的 Idempotency-Key。相同密钥和相同请求会返回同一订单。
curl --request POST \
--url https://payments.null2null.com/v1/orders \
--header "Authorization: Bearer $PAYMENT_HUB_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: 0123456789abcdef0123456789abcdef" \
--data '{
"productId": "premium_monthly",
"merchantOrderId": "project-order-0001",
"customerRef": "opaque-customer-0001",
"distributionChannel": "web"
}'
{
"order": {
"id": "58f93f31-0c02-4a13-9f9f-74d71fc014f1",
"merchantOrderId": "project-order-0001",
"customerRef": "opaque-customer-0001",
"productId": "premium_monthly",
"provider": "bitcart",
"providerPaymentId": "provider-invoice-id",
"state": "waiting",
"stateVersion": 1,
"priceAmount": 9.99,
"priceCurrency": "USD",
"paymentMethodIdsDigest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"payAmount": 9.98,
"payCurrency": "USDT_TRON",
"checkoutUrl": "https://checkout.example/invoice/...",
"paymentExpiresAt": "2026-08-25T12:20:00Z",
"createdAt": "2026-08-25T12:00:00Z",
"updatedAt": "2026-08-25T12:00:02Z"
}
}
示例中的金额、币种、时间和地址仅用于说明字段,实际值由已配置的商品与支付供应商返回。
项目后端必须按终态处理权益,不能把付款页面跳转当作支付成功。
| 状态 | 含义 | 项目动作 |
|---|---|---|
creating | 正在创建供应商订单 | 短暂等待后查询 |
waiting | 等待用户付款 | 不授予权益 |
partially_paid | 金额不足 | 不授予权益 |
processing | 已检测付款,仍在确认 | 不授予权益 |
succeeded | 供应商权威确认成功 | 幂等授予权益 |
failed | 支付失败 | 关闭本地订单 |
expired | 订单过期 | 允许创建新订单 |
refunded | 已退款 | 按业务规则撤销权益 |
Payment Hub 会发送 payment.succeeded、payment.refunded、payment.failed 和 payment.expired。成功响应必须是 HTTP 2xx。
X-Payment-Event-Id事件 UUID,用于防止重复处理。X-Payment-TimestampUnix 秒时间戳,用于限制重放窗口。X-Payment-Signaturev1= 前缀加十六进制 HMAC-SHA256。timestamp + "." + eventId + "." + rawBody
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyPaymentWebhook(rawBody, headers, secret) {
const eventId = headers["x-payment-event-id"];
const timestamp = headers["x-payment-timestamp"];
const received = headers["x-payment-signature"];
const signed = `${timestamp}.${eventId}.${rawBody}`;
const expected = `v1=${createHmac("sha256", secret)
.update(signed, "utf8")
.digest("hex")}`;
const a = Buffer.from(received ?? "", "utf8");
const b = Buffer.from(expected, "utf8");
return a.length === b.length && timingSafeEqual(a, b);
}
必须对原始请求体计算签名。不要先解析 JSON 再重新序列化。验签后还要校验时间戳、事件 ID、项目 ID、订单 ID、商品与金额,并按事件 ID 幂等处理。
回调负责及时通知,查询接口负责恢复一致性。网络失败、服务重启或回调延迟时,应按本地订单号主动查询。
GET /v1/orders/{orderId}?refresh=true
使用 Payment Hub 订单 UUID 查询,默认会在必要时向供应商刷新。
GET /v1/orders/by-merchant-order/{merchantOrderId}?refresh=true
使用项目自己的订单号恢复状态,适合任务重试和人工对账。
查询失败时保留最后一次已认证状态并重试。不要因为暂时无法访问供应商而回退已成功订单。
所有错误都返回统一 JSON。业务代码应根据 code 分支,不能只匹配英文消息。
{
"timestamp": "2026-08-25T12:00:00Z",
"status": 401,
"code": "authentication_required",
"message": "Bearer authentication is required",
"path": "/v1/orders"
}
400请求、幂等键或字段格式无效。修正后再提交。
401 / 403密钥无效、权限不足或渠道不允许。不要自动降级绕过。
404商品或订单不属于当前项目,检查环境与标识。
409本地订单号或幂等键冲突,禁止生成重复权益。
429达到项目限流,使用带抖动的指数退避。
5xx保留本地待处理状态并重试,不要推断支付结果。
商品允许的渠道由 Payment Hub 服务端配置。调用方不能临时修改价格、币种、支付方式或允许渠道。
web、standard_android、windows、macos、linux 等批准的直销渠道。
App Store 版 iOS 与 Google Play 版 Android。它们必须使用各自商店的应用内结算。
productId,不能传金额、币种或钱包列表。完成以下测试后才能启用真实支付。测试记录应包含供应商订单号、Payment Hub 订单号、本地订单号和最终权益结果。
将以下三个地址提供给编码 AI,并要求它严格遵守接口和安全边界。
建议提示词
请阅读 https://payments.null2null.com/docs/integration.md 和
https://payments.null2null.com/openapi.json,为当前项目实现 Payment Hub 服务端接入。
API Key 只能存放在后端。先创建本地订单,再调用 Payment Hub。
严格验证签名回调,并且只有 succeeded 状态可以幂等授予权益。