Multi-project payment orchestration

一个稳定接口,接入多个项目与支付网关

Payment Hub 负责订单、供应商和可靠回调。你的项目仍负责用户、商品权益与退款后的业务处理。

  • Base URL https://payments.null2null.com
  • API 版本 v1
  • 认证方式 Bearer pay_live_...

明确的职责边界

Payment Hub 不创建用户,不保存会员权益,也不把 API Key 发送给浏览器或 App。订单成功后,由项目后端独立授予权益。

查看安全规则

调用架构

客户端只请求项目后端。所有带密钥的调用都发生在服务器之间,支付结果通过签名事件返回。

  1. 01
    项目后端创建本地订单

    先生成唯一的 merchantOrderId,并记录期望的商品与用户。

  2. 02
    项目后端请求 Payment Hub

    传入服务端配置好的 productId 和销售渠道,获得托管收银台地址。

  3. 03
    客户端打开收银台

    客户端只打开返回的 checkoutUrl,不会接触项目 API Key。

  4. 04
    项目后端确认最终状态

    验签回调并主动查询订单。只有 succeeded 可以授予权益。

开通一个新项目

以下资料交给 Payment Hub 运维方。管理接口不会从公网开放。

项目身份

项目 slug、显示名称、生产环境与测试环境标识。

业务回调

HTTPS 回调地址,以及保存于双方服务端的 HMAC 密钥。

商品目录

商品 ID、固定价格、结算币种和允许的分发渠道。

供应商账户

项目独立商户、商店或钱包。不同项目不得共用收款账户。

开通完成后只返回一次项目 API Key。请直接写入项目后端的密钥管理系统,不要保存到代码仓库、前端配置或日志。供应商账户和商品创建后默认停用,只有凭据与收银台安全参数通过服务端校验并由运维显式启用后才可创建订单。

创建第一笔订单

所有创建请求都必须包含长度 16 至 80 的 Idempotency-Key。相同密钥和相同请求会返回同一订单。

POST /v1/orderscurl
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"
  }'

成功响应

200 response bodyJSON
{
  "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
Webhook verificationNode.js
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 分支,不能只匹配英文消息。

Error responseJSON
{
  "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。它们必须使用各自商店的应用内结算。

不可省略的安全规则

  • 密钥只在后端。浏览器、移动端安装包、桌面端安装包和公开仓库都不能包含项目 API Key。
  • 价格只在服务端。调用方只传 productId,不能传金额、币种或钱包列表。
  • 回调只是事件。验签完成后,仍通过订单查询接口确认状态和订单归属。
  • 成功必须幂等。同一事件、订单和状态版本重复到达时,不得重复发放权益。
  • 供应商通知不是凭证。Payment Hub 收到通知后会使用供应商认证 API 再查询,不直接信任回调内容。
  • 项目资金相互隔离。每个项目使用独立商户或钱包,Payment Hub 不归集、不托管也不分发资金。

生产验收

完成以下测试后才能启用真实支付。测试记录应包含供应商订单号、Payment Hub 订单号、本地订单号和最终权益结果。

  1. 使用测试商品完成创建、重复幂等请求和冲突请求。
  2. 验证正常支付、少付、过期、失败和退款状态。
  3. 验证伪造签名、过期时间戳和重复事件都被拒绝或幂等忽略。
  4. 断开回调接收端,确认出站事件重试后能恢复交付。
  5. 模拟供应商短暂不可用,确认项目不会错误授予或撤销权益。
  6. 完成低金额真实支付和退款,并核对资金实际进入项目独立账户。

把接入任务交给 AI

将以下三个地址提供给编码 AI,并要求它严格遵守接口和安全边界。

建议提示词

请阅读 https://payments.null2null.com/docs/integration.md 和
https://payments.null2null.com/openapi.json,为当前项目实现 Payment Hub 服务端接入。
API Key 只能存放在后端。先创建本地订单,再调用 Payment Hub。
严格验证签名回调,并且只有 succeeded 状态可以幂等授予权益。