介绍
通过买家已经在用的钱包 App 收款:Venmo、Cash App、Zelle、PayPal、Chime、Revolut、Wise、Monzo、N26。付款经零知识证明验证后,资金点对点直达你指定的链上钱包。买家也可以直接用链上钱包付数字货币,或用 Apple Pay。
besofinance.peer 在 Peer 支付网络之上提供统一 API:你只对接我们一套接口和一套密钥,每个商户在支付网络上有自己独立的收款账户(子账户),资金不经过我们,直接结算到你的钱包。
买家付款方式
| 方式 | 说明 |
|---|---|
| 法币钱包 | 9 个钱包 App,报价支持 33 种法币(见“支持的法币钱包”),实际可用以当时的流动性为准。 |
| 数字货币 | 买家选择一条链(12 条 Relay 链 + Zcash),用该网络上的钱包付款,转账桥接到你设置的收款币种与网络。 |
| Apple Pay | 经 Coinbase Apple Pay 付款,按其结算所走的 Relay 通道记账。 |
一笔支付如何完成
- 你的后端创建订单
调用POST /v1/orders(或 SDK 的createCheckout),传入金额,得到结账页地址checkoutUrl。 - 买家选择钱包
买家在结账页看到金额,选择自己的付款 App 或数字货币,生成一次支付尝试(payment)。 - 买家完成付款
按页面指引在自己的 App 里转账,无需注册账户。 - 零知识证明验证
系统用零知识证明确认付款真实发生,不泄露买家账户信息。 - 结算与通知
USDC 结算到你的钱包,我们向你的服务器发送签名的ORDER_FULFILLEDWebhook。
两种验证方式
| 方式 | 说明 |
|---|---|
| 买家验证(默认) | 买家在结账页内(手机 App Clip 或桌面浏览器扩展)确认自己的付款,凭证不经过我们。Zelle 与 N26 需要桌面浏览器扩展。 |
| 卖家自动放行(SAR) | 买家无需安装或操作任何东西,系统按收款方自动核对到账。适用于 Venmo、Cash App、Wise 与 PayPal 中收款方支持的情况,是否开通取决于你的账户配置,由 Besofinance 为你开通。若买家已付款但未验证,可用 fulfill-sar 凭交易号放行(PayPal 除外)。 |
接入方式
| 方式 | 适合场景 |
|---|---|
| 托管结账页(API / SDK) | 自有购物车、自定义下单流程。SDK:Node.js、Python、PHP、Go |
| 电商插件 | WooCommerce、Shopify、Magento 2、OpenCart、PrestaShop、WHMCS(见“电商插件与多语言 SDK”) |
| 支付链接 | 发票、私信收款:商家后台“扫码收款”页生成链接 |
| 扫码支付 | 线下柜台收款:同一链接生成二维码 |
上线前准备
- 在 商家后台 提交开户申请,审核通过并开通后登录。
- 开通时会生成首期订阅账单(1,000 美元 / 月),在后台“账单”页付款;保持订阅有效。
- 在后台“开发者”页生成沙盒 API 密钥(公钥 + 私钥),注册沙盒 Webhook。
- 用沙盒跑通完整流程(可用 沙盒测试单 一键结算),再生成正式密钥上线。
- 可选:在“开发者”页为正式密钥设置 IP 白名单。
EXPIRED,约 6 小时后未完成会变为 FAILED。尝试过期后买家可以在同一订单上重新发起付款,你也可以用 补救接口 重新打开或延长。快速开始
1. 拿到密钥
在商家后台“开发者 → API 密钥”创建一把沙盒密钥,得到公钥 beso_pk_test_… 和私钥 beso_sk_test_…。私钥只显示一次,保存到服务器环境变量:
BESO_API_KEY=beso_pk_test_xxxxxxxx
BESO_API_SECRET=beso_sk_test_xxxxxxxx
BESO_WEBHOOK_SECRET=whsec_xxxxxxxx # 注册 Webhook 时返回
2. 安装 SDK
npm install @besofinance/peer-sdk
需要 Node.js 18 及以上。SDK 会自动为每个请求签名(见 鉴权与请求签名)。Python / PHP / Go 见 多语言 SDK。
3. 创建结账
import { BesoClient } from '@besofinance/peer-sdk';
const beso = new BesoClient({
apiBaseUrl: 'https://api.peer.besofinance.xyz/v1',
apiKey: process.env.BESO_API_KEY,
apiSecret: process.env.BESO_API_SECRET,
});
const order = await beso.createCheckout({
requestedAmount: '50.00',
requestedCurrency: 'USD',
reference: 'ORDER-1001', // 你的订单号,同时是幂等键
settleChain: 8453, // 可选,数字链 ID;不传用开户时登记的收款钱包
settleToken: 'USDC',
successUrl: 'https://yoursite.com/orders/1001',
notes: { customerId: 'C-88' },
});
4. 跳转到结账页
// 后端把 order.checkoutUrl 返回给前端
window.location.href = order.checkoutUrl;
successUrl),不会自动跳转;cancelUrl 目前不会被访问。请以 Webhook 为准判断是否已付款。5. 处理 Webhook
在你的服务器上提供一个接口,先验证签名,再监听 ORDER_FULFILLED 事件,把订单标记为已支付。详见 验签。
6. 端到端测试
// 沙盒:新建一张 10 美元的测试单并直接结算,触发真实签名的 ORDER_FULFILLED
await beso.sandboxTestOrder({ amount: '10.00' });
// 或者结算你刚用 createCheckout 建的那张单
await beso.sandboxTestOrder({ orderId: order.id });
BesoApiError(旧名 PeerApiError 仍可用),含 statusCode、errorCode、fieldErrors。开户、订阅与环境
开户流程
- 提交申请
在 login.html 的“申请开户”页填写公司、联系邮箱、网站、行业、预计月交易额、收款钱包和后台登录密码;或调用POST /v1/onboarding/applications(无需密钥)。 - 审核
Besofinance 审核(KYB)。状态:pending_review→approved/rejected。审核期间可以登录后台查看进度,但不能生成密钥、不能调用 API(403 MERCHANT_NOT_ACTIVE)。 - 开设收款账户
审核通过后,Besofinance 在支付网络上为你开设独立的子账户(沙盒 + 正式各一个)。每个子账户有自己的 API 密钥和结算通知签名密钥,与其他商户完全隔离;收款设置(含手续费由谁承担,默认商户承担)沿用平台账户。 - 支付网络店铺审核
正式收款前,支付网络还会审核你的店铺资料(网站、经营内容等)。审核期间沙盒可正常联调,正式环境状态为pending_peer_review,不能生成正式密钥,正式环境建单返回403 PEER_REVIEW_PENDING。审核通过(approved)后自动解除。当前状态见GET /v1/merchants/me的liveReviewStatus。 - 开通与首期账单
开通后状态变为active,系统生成首期订阅账单(结账链接),付款后即可使用。 - 生成密钥
在后台“开发者”页按环境生成 API 密钥(每个环境最多 5 把有效密钥;正式密钥需店铺审核通过)。
开户申请接口
POST https://api.peer.besofinance.xyz/v1/onboarding/applications
Content-Type: application/json
{
"companyName": "Acme Ltd",
"contactEmail": "ops@acme.com",
"password": "至少 10 位,用于登录商家后台",
"website": "https://acme.com",
"industry": "电商",
"country": "SG",
"expectedMonthlyVolume": "50000",
"note": "可选备注",
"payoutAddress": "0x…",
"payoutChainId": 8453,
"payoutToken": "USDC"
}
返回 201 与 { merchantId, onboardingStatus: "pending_review" }。同一邮箱重复申请返回 409 APPLICATION_EXISTS;每个 IP 每分钟最多 10 次。
订阅计费
| 项目 | 说明 |
|---|---|
| 费用 | 每个商户 1,000 美元 / 月(以商务协议为准),按自然月周期,开通日起算。 |
| 付款方式 | 后台“账单”页的“续费”按钮生成结账链接,用任一支持的钱包或数字货币支付;也可调用 POST /v1/merchants/me/subscription/invoices。到期前 5 天会自动生成下期账单。 |
| 宽限期 | 到期后有 3 天宽限期(状态 past_due),期间接口照常可用。 |
| 自动停用 | 宽限期结束仍未付款,状态变为 suspended:除账户/账单类接口外,所有 API 请求返回 402 SUBSCRIPTION_EXPIRED,后台无法新建订单。 |
| 自动恢复 | 账单付款结算后立即恢复为 active,无需人工处理。 |
| 交易手续费 | 订阅费之外的交易手续费在开户时书面约定,默认由商户承担,从结算金额中扣除(见支付对象的 totalUsdcFeeAmount)。 |
停用期间仍可用的接口:GET /merchants/me、订阅与账单、/integration/status、Webhook 配置与投递日志、退款申请。被 Besofinance 人工冻结(风控)时所有接口返回 403 MERCHANT_SUSPENDED。
沙盒与正式环境
| 沙盒 | 正式 | |
|---|---|---|
| 密钥前缀 | beso_pk_test_ / beso_sk_test_ | beso_pk_live_ / beso_sk_live_ |
| 资金 | 不动真钱,支付由模拟结算 | 真实付款与结算 |
| 数据 | 订单、支付、Webhook、投递日志、IP 白名单按环境完全隔离;同一个 reference 在两个环境里是两张单 | |
| 只在该环境可用 | POST /sandbox/test-order | 支付补救、退款 |
环境由密钥决定,无需额外参数。响应里的订单、Webhook 事件都带 environment 字段(SANDBOX / LIVE)。商家后台用右上角的环境开关切换。
SDK 参考
请在后端创建订单,API 私钥只能保存在服务器端。Node.js 包名 @besofinance/peer-sdk(0.2+ 自动签名)。所有函数都有两种写法:函数式 createCheckout(params, opts),或面向对象 new BesoClient(opts).createCheckout(params)。
客户端参数
| 参数 | 说明 |
|---|---|
apiKey | 公钥 beso_pk_test_… / beso_pk_live_…(旧版单一密钥 beso_sk_… 也可,此时不传 apiSecret) |
apiSecret | 私钥,用于请求签名 |
apiBaseUrl | 默认 https://api.peer.besofinance.xyz/v1 |
timeoutMs / signal / fetcher | 可选:超时(默认 15 秒)、取消信号、自定义 fetch |
函数
| 函数 | 对应接口 |
|---|---|
createCheckout(params, opts, idempotencyKey?) | POST /orders,返回订单(含 checkoutUrl) |
getCheckoutUrl(order) | 从订单对象取结账页地址(重放请求时可能为 null,见“创建订单”) |
createCheckoutAndRedirect(params, opts) | 创建订单;在浏览器环境中立即跳转(仅限你自己的后端代理场景,勿把私钥放进浏览器) |
getMerchantOrder / getOrder | GET /merchants/me/orders/{id}(对账用) / GET /orders/{id}(免密钥) |
listOrders / listPayments / listOrderPayments | 订单与支付列表 |
getMerchant / getUsage / getTierUsage / getIntegrationStatus | 商家资料、用量、套餐额度、接入状态 |
checkQuoteAvailability | POST /merchants/me/quotes/availability |
createWebhook / listWebhooks / updateWebhook / rotateWebhookSecret / deleteWebhook / testWebhook | Webhook 管理 |
listDeliveries / getDelivery / resendDelivery | 投递日志与手动重发 |
createRefundRequest / listRefundRequests / getRefundRequest / cancelRefundRequest | 退款申请 |
paymentAction / getAction | 支付补救(仅正式环境) |
getSubscription / createSubscriptionInvoice | 订阅与账单 |
sandboxTestOrder | 沙盒测试单 |
verifyWebhook(rawBody, headers, secret) | Webhook 验签 |
错误处理
import { BesoApiError } from '@besofinance/peer-sdk';
try {
await beso.createCheckout(params);
} catch (error) {
if (error instanceof BesoApiError) {
console.error(error.statusCode, error.errorCode, error.fieldErrors);
if (error.errorCode === 'SUBSCRIPTION_EXPIRED') { /* 提醒续费 */ }
}
}
BesoApiError 包含:statusCode、errorCode、message、fieldErrors、formErrors、responseObject。PeerApiError 是它的别名,旧代码无需修改;另提供与 Peer SDK 同名的别名 PayApiError,从 @zkp2p/pay-sdk 迁移时只需改 import。
电商插件与多语言 SDK
插件和 SDK 的源码都在交付包的 plugins/ 与 sdk/ 目录中,全部对接本页的统一 API(公钥 + 私钥签名),收到 ORDER_FULFILLED 后把订单置为已支付。
| 平台 | 目录 | 形式 |
|---|---|---|
| WooCommerce | plugins/woocommerce | WordPress 插件(支付网关 + Webhook 回调) |
| Shopify | plugins/shopify | 独立小服务(Node.js):订单创建时生成支付链接,收到 Webhook 后通过 Admin API 标记已付款 |
| Magento 2 | plugins/magento2 | Magento 模块 Besofinance_Peer |
| OpenCart | plugins/opencart | OpenCart 4 扩展 |
| PrestaShop | plugins/prestashop | PrestaShop 8 支付模块 |
| WHMCS | plugins/whmcs | 第三方支付网关模块 + 回调 |
| Node.js / TypeScript | sdk/ | @besofinance/peer-sdk |
| Python | sdk/python | 单文件,无第三方依赖(Python 3.8+) |
| PHP | sdk/php | 单文件,依赖 ext-curl(PHP 7.4+) |
| Go | sdk/go | 标准库实现(Go 1.20+) |
插件统一的配置项:API 基础地址、公钥、私钥、Webhook 签名密钥;可选:结算链 ID、结算币种、收款地址(不填用开户时登记的钱包)。
AI 辅助接入
如果你习惯用 AI 编程助手,可以把下面这段提示词交给它,让它按我们的文档,在你的项目里完成 SDK 安装、创建结账、跳转和 Webhook 验签。
使用步骤
- 准备密钥
在商家后台“开发者 → API 密钥”生成沙盒密钥(公钥 + 私钥)。 - 放进环境变量
写到项目的.env:BESO_API_KEY、BESO_API_SECRET、BESO_WEBHOOK_SECRET。 - 交给 AI
把下面的提示词发给你的 AI 编程助手,并让它在你的项目目录里工作。 - 本地验证
用沙盒跑通:创建结账 → 调用沙盒测试单结算 → 收到ORDER_FULFILLEDWebhook。 - 切换正式环境
确认无误后,换成正式密钥上线。
提示词模板
请帮我在当前项目里接入 besofinance.peer 的支付 API。要求:
1. 安装 npm 包 @besofinance/peer-sdk(Node.js 18+),用 new BesoClient({ apiBaseUrl,
apiKey, apiSecret }) 创建客户端;SDK 会自动给每个请求加 X-Timestamp / X-Nonce /
X-Signature 签名。
2. API 基础地址 https://api.peer.besofinance.xyz/v1 ;公钥从环境变量 BESO_API_KEY、
私钥从 BESO_API_SECRET 读取,不要写进代码,不要让我把密钥贴给你。
3. 在后端新增创建订单接口:调用 createCheckout,传 requestedAmount + requestedCurrency
(或 requestedUsdcAmount)、reference(我们自己的订单号,用作幂等键)、successUrl、notes。
把返回的 checkoutUrl 给前端跳转。successUrl 只是“返回商户”链接,不会自动跳转。
4. 新增 Webhook 接收接口:读取原始请求体,用 X-Webhook-Timestamp 与 X-Webhook-Signature
做 HMAC-SHA256 验签(签名内容为 timestamp + "." + 原始请求体,十六进制;secret 来自
BESO_WEBHOOK_SECRET),时间戳误差超过 5 分钟就拒绝;用 X-Webhook-Id 去重;先返回 2xx
再异步处理;只在收到 ORDER_FULFILLED 时才把订单标记为已支付并发货(用 data.order.reference
或 data.order.id 对应到我们的订单)。
5. 接口调用失败时捕获 BesoApiError,记录 statusCode 与 errorCode;402 SUBSCRIPTION_EXPIRED
表示订阅到期。
6. 全程使用沙盒密钥测试:调用 POST /v1/sandbox/test-order 结算测试单,并给我一份本地测试步骤。
只做以上 SDK 与 API 密钥的接入,不要改动与支付无关的代码。它能做什么,不能做什么
| 可以 | 不可以 |
|---|---|
| 帮你安装 SDK、写创建结账与跳转代码 | 在 AI 对话里直接替买家付款 |
| 帮你写 Webhook 验签与去重逻辑 | 绕过后台,自行生成或保管你的 API 密钥 |
| 帮你写本地测试步骤 | 替你决定是否上线或放行订单 |
API 参考 · 鉴权与请求签名
API 密钥
- 每把密钥由公钥(
beso_pk_test_…/beso_pk_live_…)和私钥(beso_sk_test_…/beso_sk_live_…)组成,在商家后台“开发者 → API 密钥”创建。私钥只在创建或轮换时显示一次。 - 公钥放在请求头
X-API-Key;私钥不随请求发送,只用来计算签名。 - 密钥决定环境:
test= 沙盒,live= 正式。每个环境最多 5 把有效密钥。 - 仅限服务器端使用,不要放进浏览器或手机 App。
请求签名
每个带密钥的请求都必须带以下三个请求头:
| 请求头 | 说明 |
|---|---|
X-Timestamp | 当前 Unix 时间(秒),与服务器时间相差不能超过 300 秒 |
X-Nonce | 每个请求唯一的随机串,16–64 位 [A-Za-z0-9_-];同一把密钥的 nonce 在 10 分钟内不能重复(防重放) |
X-Signature | 十六进制 HMAC-SHA256,算法如下 |
待签名串 = X-Timestamp + "\n"
+ X-Nonce + "\n"
+ 大写 HTTP 方法(GET / POST / PATCH / DELETE)+ "\n"
+ 请求路径和查询串(以 /v1 开头,如 /v1/merchants/me/orders?page=2)+ "\n"
+ hex(SHA256(原始请求体)) # 没有请求体时是空串的 SHA256
X-Signature = hex(HMAC-SHA256(私钥, 待签名串))
curl 示例
BODY='{"requestedAmount":"50.00","requestedCurrency":"USD","reference":"ORDER-1001"}'
TS=$(date +%s); NONCE=$(openssl rand -hex 16)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.* //')
SIG=$(printf '%s\n%s\n%s\n%s\n%s' "$TS" "$NONCE" POST /v1/orders "$BODY_HASH" \
| openssl dgst -sha256 -hmac "$BESO_API_SECRET" -hex | sed 's/^.* //')
curl -X POST https://api.peer.besofinance.xyz/v1/orders \
-H "Content-Type: application/json" \
-H "X-API-Key: $BESO_API_KEY" -H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" -H "X-Signature: $SIG" \
-d "$BODY"
Python 示例
import hashlib, hmac, json, os, secrets, time, urllib.request
def beso(method, path, body=None):
raw = json.dumps(body, separators=(",", ":")) if body is not None else ""
ts, nonce = str(int(time.time())), secrets.token_hex(16)
msg = "\n".join([ts, nonce, method, path, hashlib.sha256(raw.encode()).hexdigest()])
sig = hmac.new(os.environ["BESO_API_SECRET"].encode(), msg.encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request("https://api.peer.besofinance.xyz" + path, data=raw.encode() or None, method=method,
headers={"X-API-Key": os.environ["BESO_API_KEY"], "X-Timestamp": ts, "X-Nonce": nonce,
"X-Signature": sig, "Content-Type": "application/json"})
return json.load(urllib.request.urlopen(req))
beso("POST", "/v1/orders", {"requestedAmount": "50.00", "requestedCurrency": "USD", "reference": "ORDER-1001"})
密钥轮换与吊销
- 轮换:后台“开发者”页点“轮换”,立即生成新密钥;旧密钥在宽限期内继续有效(默认 24 小时,可选 0–168 小时),到期后返回
401 API_KEY_EXPIRED。 - 吊销:立即失效,之后的请求返回
401 API_KEY_REVOKED。怀疑泄露时请直接吊销。
IP 白名单(可选)
在后台“开发者 → IP 白名单”为每个环境设置允许调用的公网 IPv4 / IPv6 地址或 CIDR 段(IPv4 前缀 /8–/32,IPv6 /32–/128,最多 50 条;单个地址按 /32 或 /128 保存)。空列表表示不限制。白名单只约束 API 密钥调用,不影响商家后台登录。不在名单内的请求返回:
{
"success": false,
"message": "IP address 203.0.114.7 is not in this merchant's API key IP allowlist",
"responseObject": { "ip": "203.0.114.7", "reason": "IP_NOT_ALLOWED" },
"statusCode": 403,
"errorCode": "IP_NOT_ALLOWED"
}
无法确定来源 IP 时 reason 为 CLIENT_IP_UNRESOLVED(状态码与 errorCode 相同)。
旧版单一密钥
早期发放的 beso_sk_… 单一密钥(只放 X-API-Key,不签名)在过渡期内继续可用,作用于部署时的默认环境。被平台关闭后返回 401 LEGACY_KEY_DISABLED,请在后台改用公钥 + 私钥。
免密钥接口
GET /v1/orders/{orderId} 无需密钥(订单 ID 本身即凭证),供结账页读取订单;POST /v1/onboarding/applications 用于提交开户申请。
API 参考 · 基础约定
基础地址(沙盒与正式相同,环境由密钥决定):
https://api.peer.besofinance.xyz/v1
统一响应格式
{
"success": true,
"message": "Merchant orders retrieved",
"responseObject": { ... },
"statusCode": 200
}
| 字段 | 说明 |
|---|---|
success | 2xx 响应为 true |
message | 便于阅读的说明,不要用来做程序判断 |
responseObject | 数据主体;失败时为 null 或错误详情 |
statusCode | 与 HTTP 状态码一致 |
errorCode | 仅失败时出现,机器可读的错误码(见“错误码”) |
参数校验失败返回 400、errorCode: "VALIDATION_ERROR",具体字段错误在 responseObject.fieldErrors(对象:字段名 → 错误数组),整体错误在 responseObject.formErrors。
数据格式
- 时间:ISO 8601 UTC,如
2026-10-06T10:15:42.318Z。 - 金额:十进制字符串,不使用浮点数,如
"25"、"23.14"。末尾的 0 不保证保留,请解析后再比较。 - 链 ID:响应里是十进制字符串(如
"8453");请求里数字或字符串都可以。 - ID:订单
ord_…、Webhookwh_…、投递dlv_…、退款申请rfr_…、事件evt_…;支付 ID 沿用支付网络的 ID。
API 参考 · 接口列表
路径相对于 https://api.peer.besofinance.xyz。“密钥”= 签名的 API 密钥;“停用时”= 订阅停用(402)期间是否仍可调用。
| 方法 | 路径 | 鉴权 | 停用时 | 说明 |
|---|---|---|---|---|
| POST | /v1/orders | 密钥 | 402 | 创建结账订单 |
| GET | /v1/orders/{orderId} | 无 | 可用 | 按 ID 读取订单(结账页用) |
| GET | /v1/merchants/me/orders | 密钥 | 402 | 订单列表(分页;status、displayStatus、chargebackStatus、orderId、reference、search) |
| GET | /v1/merchants/me/orders/{orderId} | 密钥 | 402 | 订单详情(会先向支付网络刷新) |
| GET | /v1/merchants/me/orders/{orderId}/payments | 密钥 | 402 | 某订单下的全部支付尝试 |
| GET | /v1/merchants/me/payments | 密钥 | 402 | 支付列表(分页;status、chargebackStatus、orderId) |
| POST | /v1/merchants/me/orders/{orderId}/payments/{paymentId}/recreate | 正式密钥 | 402 | 补救:重新打开已过期/取消/失败的支付 |
| POST | /v1/merchants/me/orders/{orderId}/payments/{paymentId}/extend | 正式密钥 | 402 | 补救:延长 24 小时 |
| POST | /v1/merchants/me/orders/{orderId}/payments/{paymentId}/fulfill-sar | 正式密钥 | 402 | 补救:凭交易号放行 |
| GET | /v1/merchants/me/orders/{orderId}/actions/{actionId} | 密钥 | 402 | 查询补救动作结果 |
| GET | /v1/merchants/me | 密钥 | 可用 | 商家资料、订阅状态、可用环境 |
| GET | /v1/merchants/me/usage | 密钥 | 402 | 近 30 天用量统计 |
| GET | /v1/merchants/me/tier-usage | 密钥 | - | 上游套餐月度额度与已用量(透传 Peer) |
| POST | /v1/merchants/me/quotes/availability | 密钥 | 402 | 下单前检查某金额是否有流动性 |
| GET | /v1/integration/status | 密钥 | 可用 | 接入自检 |
| POST / GET | /v1/webhooks | 密钥 | 可用 | 注册 / 列出 Webhook |
| PATCH / DELETE | /v1/webhooks/{webhookId} | 密钥 | 可用 | 更新 / 删除 |
| POST | /v1/webhooks/{webhookId}/rotate-secret | 密钥 | 可用 | 轮换签名密钥 |
| POST | /v1/webhooks/{webhookId}/test | 密钥 | 可用 | 发送测试事件 |
| GET | /v1/webhooks/{webhookId}/deliveries | 密钥 | 可用 | 该端点最近 50 次投递 |
| GET | /v1/webhook-deliveries | 密钥 | 可用 | 全部投递日志(分页) |
| GET | /v1/webhook-deliveries/{deliveryId} | 密钥 | 可用 | 投递详情(每次尝试 + 原始负载) |
| POST | /v1/webhook-deliveries/{deliveryId}/resend | 密钥 | 可用 | 手动重发 |
| POST / GET | /v1/refund-requests | 密钥 | 可用 | 提交 / 列出退款申请 |
| GET | /v1/refund-requests/{id} | 密钥 | 可用 | 退款申请详情 |
| POST | /v1/refund-requests/{id}/cancel | 密钥 | 可用 | 撤回尚未审核的申请 |
| GET | /v1/merchants/me/subscription | 密钥 | 可用 | 订阅状态与账单 |
| POST | /v1/merchants/me/subscription/invoices | 密钥 | 可用 | 生成续费账单(返回结账链接) |
| POST | /v1/sandbox/test-order | 沙盒密钥 | 402 | 测试单:新建并直接结算 |
| POST | /v1/onboarding/applications | 无 | — | 提交开户申请 |
商家后台使用同一套接口(路径前缀为 /portal,登录会话鉴权),另有 API 密钥管理与 IP 白名单设置,只能在后台操作。
API 参考 · 创建订单
POST https://api.peer.besofinance.xyz/v1/orders
请求字段
金额二选一:requestedAmount + requestedCurrency(按法币报价),或 requestedUsdcAmount(按 USDC 报价)。
| 字段 | 类型 | 说明 |
|---|---|---|
requestedAmount | string | 法币金额,最多 2 位小数,如 "100.00" |
requestedCurrency | string | ISO 4217 代码,默认 USD。建单时按实时汇率换算成 USDC;没有汇率时返回 EXCHANGE_RATE_LOOKUP_FAILED |
requestedUsdcAmount | string | USDC 金额,最多 6 位小数。与上两个字段互斥 |
reference | string | 你的订单号,1–64 位 [A-Za-z0-9_.:-]。同时是幂等键(同一环境内唯一)。不传时依次取 notes.orderId、Idempotency-Key 请求头,都没有则自动生成 |
settleAddress | string | 收款钱包地址;不传用开户时登记的钱包 |
settleChain | number | string | 收款网络的数字链 ID,如 8453(Base)、42161(Arbitrum)、728126428(Tron)。为兼容旧接入也接受链名(base、tron 等) |
settleToken | string | 收款币种,如 USDC、USDT;默认开户时登记的币种 |
enabledRails | string[] | 本单允许的付款通道,非空数组:法币钱包标识、relay_<chainId>、near_intents_133701、apple_pay(见“支持的钱包 / 数字货币”)。旧写法 crypto_<chainId> 自动换成 relay_<chainId>。不传用账户默认配置 |
feePayer | string | 手续费承担方:MERCHANT(商家承担,从到账金额中扣)、PAYEE(买家承担,买家支付总额包含手续费,你收到请求的净额)、SPLIT(按比例分摊)。不传用账户默认 |
buyerFeeShareBps | integer | 买家分摊比例,单位基点,0–10000,必须是 1000 的倍数;feePayer 为 SPLIT 时必填 |
dynamicOrdersEnabled | boolean | 可选,覆盖账户的动态订单设置(没有报价能落在费用上限内时是否允许调整) |
successUrl | string | 完成页“返回商户”链接的地址(买家点击才会跳转,不会自动跳转;嵌入模式不显示)。链接会附带 order_id、payment_id、tx_hash、status=success 等查询参数,不要据此发货 |
cancelUrl | string | 保留字段,目前结账页不会访问 |
notes | object | 自定义元数据(客户 ID 等),原样出现在订单与 Webhook 的 order.notes 中 |
金额限制
- 每单折合 USDC 须在支付网络的最小额(默认 10 USDC,沙盒同样适用)与 10,000 USDC 之间,否则返回
400 AMOUNT_BELOW_MIN/AMOUNT_ABOVE_MAX;每笔钱包付款含手续费也不能超过 10,000 USDC:买家承担手续费(PAYEE)的大额订单,超出部分的钱包会显示为不可用,买家发起付款时返回INTENT_ABOVE_MAX(建单时不会返回)。超过上限的购买请拆成多张订单。 - 暂不支持开放金额订单(买家自填金额,
openAmount),传入会返回400。
幂等与重放
同一环境内用同一个 reference 再次创建,返回 200 和原订单,并带 idempotentReplay: true,不会重复建单;但如果金额、币种或金额模式与原订单不同,返回 409 IDEMPOTENCY_KEY_CONFLICT。首次请求返回 201。如果首次请求在支付网络已建单但链接未能保存(极少见),返回 409 CHECKOUT_URL_UNRECOVERABLE,请换一个 reference 重建。
响应(订单对象)
{
"id": "ord_2b9c…",
"environment": "SANDBOX",
"status": "CREATED",
"amountMode": "FIXED",
"requestedAmount": "50.00", "requestedCurrency": "USD",
"requestedUsdcAmount": "50", "remainingUsdcAmount": "50",
"netSettledUsdcAmount": null, "fulfillTransaction": null,
"destinationAddress": "0x…", "destinationChainId": "8453", "destinationToken": "USDC",
"checkoutUrl": "https://pay.peer.xyz/checkout?…",
"chargebackStatus": "NONE", "refundStatus": "NONE", "settledPaymentCount": 0,
"notes": { "customerId": "C-88" },
"reference": "ORDER-1001",
"lastEvent": null,
"createdAt": "2026-10-06T10:15:42.000Z", "updatedAt": "2026-10-06T10:15:42.000Z"
}
| 字段 | 说明 |
|---|---|
status | 见“订单与支付状态”。PENDING_UPSTREAM 表示支付网络尚未确认建单(网络异常时短暂出现) |
requestedUsdcAmount / remainingUsdcAmount | 应收 USDC / 还差多少 USDC |
netSettledUsdcAmount | 扣除手续费后实际到账的 USDC |
fulfillTransaction | 结算交易哈希 |
chargebackStatus | NONE / PARTIALLY_CHARGEBACKED / CHARGEBACKED,不会改变 status |
refundStatus | NONE / PENDING / COMPLETED,不会改变 status |
lastEvent | 最近一次收到的事件类型 |
chargebacks | 拒付明细数组(无则为空数组) |
refundAmountUsdc / refundTransactionHash / refundDepositId | 退款金额 / 退款交易哈希 / 退款存款 ID(无退款时为 null) |
feePayer / buyerFeeShareBps / enabledRails | 本单生效的手续费承担方式、买家分摊比例、允许的通道 |
successUrl / cancelUrl | 建单时传入的值(见上文,两者都不会自动跳转) |
metadata / requestedFiat | 上游写入的法币报价快照(requestAmountInputMode、requestedFiatAmount、requestedFiatCurrency、requestedUsdcAmountAtCreation)/ 未被改价的法币单的原始报价。订单被动态改价(ORDER_RESIZED)后请以 requestedUsdcAmount 为准 |
bridgeInfo | 跨链结算信息(仅非 Base 结算时) |
inPersonCheckout / cancelledAt / completedAt | 是否线下收款 / 取消时间 / 完成时间 |
上述上游字段取自最近一次同步的支付网络快照;刚建单、尚未同步时可能为 null。上游的子账户 ID、分成配置与分成明细等内部字段不会返回。
API 参考 · 订单、支付与商家
支付对象
由 /merchants/me/orders/{orderId}/payments、/merchants/me/payments 和 Webhook 的 data.payment 返回:
| 字段 | 说明 |
|---|---|
id / orderId | 支付 ID(支付网络的 ID,补救接口用它)/ 所属订单(我们的 ord_…) |
status | CREATED / SETTLED / EXPIRED / FAILED / CANCELLED |
rail | 付款通道,如 venmo、relay_8453 |
paymentAmount / currency / currencyPerUsdRate | 买家支付的法币金额、币种、报价汇率 |
netSettledUsdcAmount / totalUsdcFeeAmount | 到账 USDC / 手续费 USDC |
penalties | 罚扣明细(如有) |
chargebackStatus | 该笔支付的拒付状态 |
railIdentifier | 链上 / 通道侧的参考号;用 id 做外部关联,不要用它 |
fulfillTransaction | 结算交易哈希 |
quoteExpiresAt | 本次尝试的截止时间 |
errorMessage / errorCode | 失败原因(如有)/ 最近一次链上结算失败时为 FULFILL_INTENT_FAILED |
chargeback / quote | 拒付详情 / 报价快照(上游字段,原样透传) |
createdAt / completedAt | 创建 / 完成时间(completedAt 只在结算或买家取消时写入,过期与失败保持 null) |
订单列表 GET /v1/merchants/me/orders
按创建时间倒序;page(默认 1)、limit(默认 20,最大 100,超过返回 400 而不是截断)。返回 { orders, total, page, limit }(旧字段 rows 与 orders 相同,保留兼容)。
| 参数 | 说明 |
|---|---|
status | CREATED / PARTIALLY_FULFILLED / FULFILLED / CANCELLED;传了 displayStatus 时忽略 |
displayStatus | 只看未完成订单的支付活动:ACTIVE(有进行中的付款尝试)、EXPIRED(有过期尝试且无进行中的)、CREATED(两者都没有) |
chargebackStatus | NONE / PARTIALLY_CHARGEBACKED / CHARGEBACKED |
orderId | 订单号子串匹配,完整的 ord_… 精确命中一单;传了 search 时忽略 |
reference | 你的订单号,精确匹配 |
search | 至少 3 个字符(更短的会被忽略):在订单号、reference、notes、支付通道和支付 ID 中做不区分大小写的子串匹配;纯数字(可带 $)按金额精确匹配,$20 与 20.00 都命中 20 |
列表中的每个订单比单笔查询多 5 个由支付记录推导的字段:hasActivePayment、hasExpiredPayment、paymentSelectedRail(进行中尝试的通道,否则最近一次过期尝试的通道)、settledPaymentCount、settledPaymentRails(按支付创建时间去重)。
商家资料 GET /v1/merchants/me
| 字段 | 说明 |
|---|---|
id / name / environment | 商户 ID、名称、当前密钥的环境 |
evmWalletAddress / destinationChainId / destinationToken | 默认收款钱包、网络、币种 |
enabledRails | 可用的法币通道 |
subscription | 订阅对象(见“订阅与账单”) |
onboardingStatus / accountStatus | 开户状态 / ok、suspended_unpaid、suspended_by_admin |
liveReviewStatus | 支付网络对店铺的审核状态:not_submitted、pending_peer_review、approved、rejected。只有 approved 才能在正式环境收款 |
environments | { sandbox: bool, live: bool },哪些环境已开通 |
apiKeyIpAllowlist | 当前环境的 IP 白名单,空数组表示不限制 |
用量 GET /v1/merchants/me/usage
返回近 30 天(当前环境)的 orders、fulfilledOrders、settledUsdc、successRate(%)和订阅对象。订阅本身不限笔数和金额,cap 恒为 null。
套餐额度 GET /v1/merchants/me/tier-usage
原样透传上游 Peer 的同名接口:{ tier, limits, usage, windowStart, windowEnd }。limits(volumeUsdc、orderCount)为 null 表示没有上限(本平台目前的上游账户与沙盒均为 null);usage 统计本自然月(UTC)创建、且至少有一笔法币支付结算的已完成 / 部分完成订单,按订单的请求 USDC 金额累计。上游不会在接近上限时发通知,建议自行定时查询;达到上限后创建订单会返回 403 MERCHANT_MONTHLY_VOLUME_LIMIT_EXCEEDED 或 MERCHANT_MONTHLY_ORDER_LIMIT_EXCEEDED。
额度检查 POST /v1/merchants/me/quotes/availability
| 字段 | 说明 |
|---|---|
amount | 必填,十进制字符串 |
quoteMode | exact-token(默认,amount 为 USDC)或 exact-fiat(amount 为 fiatCurrency 计价的法币)。请与建单时的金额模式保持一致 |
fiatCurrency | 法币代码;省略时使用收款账户的默认币种,再回落到 USD |
enabledRails | 只检查这些通道 |
nearbyQuotesCount | 1–10,默认 3;无法成交时每个方向(更低 / 更高)最多返回多少个附近可成交金额 |
destinationChainId / destinationToken / destinationAddress | 可选,默认使用开户时登记的收款配置;与建单时的按单覆盖保持一致 |
收款目的地自动使用你的默认钱包。返回支付网络的可用性结果(是否可成交,以及附近金额建议)。
接入自检 GET /v1/integration/status
返回 environment、authMode(signed / legacy)、subscription(状态)、webhooks(启用的端点数)、lastWebhookDelivery、lastOrderAt、ready(订阅有效且至少有一个 Webhook)。另有与 Peer 同名的接入清单字段:verified(你的接口已成功接收过一次签名的沙盒投递;判断是否接好请看它)、liveWebhookReady、complete(已有正式订单)、steps(每步含 id、actor、done、evidence;actor 为 MERCHANT 的步骤需在商家后台完成,BESOFINANCE 的由我们开通)和 nextAction(下一个未完成步骤)。
API 参考 · 限流与分页
限流
- 每把 API 密钥每分钟 600 次请求(平台可调整)。
- 测试类接口:Webhook 测试每 10 分钟 5 次(与 Peer 相同),沙盒测试单每分钟 5 次;开户申请每个 IP 每分钟 10 次。
- 补救接口另受支付网络限制:每个商户每分钟 30 次操作、120 次读取。
每个响应带 x-rate-limit-remaining。超限返回 429 RATE_LIMITED,响应头含 x-retry-after(多少秒后可重试,同时提供标准 Retry-After)与 x-rate-limiter-resets-at(ISO 时间)。
分页
| 参数 | 默认 | 上限 | 说明 |
|---|---|---|---|
page | 1 | — | 页码,从 1 开始 |
limit | 20 | 100 | 每页条数,超出 1–100 返回 400 |
列表响应为 { <列表名>, total, page, limit },按创建时间倒序:订单列表是 orders,支付列表是 payments,投递日志是 deliveries,退款申请是 refundRequests。订单列表另带一个与 orders 相同的 rows 字段,仅为兼容旧接入,新代码请读 orders。
API 参考 · 支付补救
买家付款遇到问题时(尝试过期但其实已付款、需要更多时间、已付款但没完成验证),可以用补救接口处理。仅正式环境,沙盒返回 403 SANDBOX_NOT_SUPPORTED;只适用于法币通道。paymentId 来自 GET /v1/merchants/me/orders/{orderId}/payments。
| 动作 | 路径 | 适用条件 | 结果 |
|---|---|---|---|
| 重新打开 | POST …/payments/{paymentId}/recreate | 支付为 EXPIRED / CANCELLED / FAILED,订单为 CREATED / PARTIALLY_FULFILLED | 支付回到 CREATED,对同一收款方重新计时;之后结算照常触发 PAYMENT_SETTLED |
| 延长 | POST …/payments/{paymentId}/extend | 支付为 CREATED,且距截止不足 6 小时 | 截止时间加 24 小时(自创建起最多 5 天);result.confirmedExpiryTime 为新截止时间 |
| 凭交易号放行 | POST …/payments/{paymentId}/fulfill-sar | 支付为 CREATED;Venmo / Cash App / Wise 且收款方支持自动放行;请求体需 txId(1–256 位,买家收据上的交易号) | 支付直接结算,触发 PAYMENT_SETTLED / ORDER_FULFILLED |
请求体都可带 idempotencyKey(8–200 位),同一个键重试返回同一个动作。成功返回 202 与动作对象:
{
"actionId": "…", "actionName": "RECREATE", "paymentId": "…", "orderId": "ord_…",
"status": "QUEUED", // PENDING_APPROVAL / QUEUED / RUNNING / SUCCEEDED / BLOCKED / FAILED
"error": null, "result": null, "payment": { … }, "createdAt": "…", "startedAt": null, "finishedAt": null
}
用 GET /v1/merchants/me/orders/{orderId}/actions/{actionId} 轮询结果。不满足条件返回 422,responseObject.reason 给出原因(如 PAYMENT_STATUS_NOT_ELIGIBLE、RAIL_NOT_SUPPORTED、EXTEND_TOO_EARLY、SAR_NOT_AVAILABLE);同类动作进行中返回 409 ACTION_IN_PROGRESS(带 actionId);功能关闭时 503 FEATURE_DISABLED。错误码同时出现在 errorCode 与 responseObject.code。
API 参考 · 退款申请
退款由 Besofinance 审核后代为执行,全额退回买家原付款账户。你通过 API 或后台“退款”页提交申请,状态变化会以 REFUND_REQUEST_UPDATED 事件通知你;支付网络完成退款时另有 REFUND_PENDING / REFUND_COMPLETED 事件。
可退款条件
- 正式环境订单,状态为
FULFILLED,且恰好有一笔已结算支付; - 付款通道为 Venmo、Cash App、PayPal、Zelle、Revolut、Wise 或 Monzo(Chime、N26、数字货币、Apple Pay 不支持);
- 结算到 Base 网络的 USDC;没有拒付记录,也没有进行中或已完成的退款;
- 买家付款币种需在支持范围内(包括 USD、EUR、GBP);只支持全额退款:买家拿回原付款平台上、原币种的全部付款金额。
- 退款以 USDC 报价发出,为了尽快成交,所需 USDC 通常比按市场汇率折算多约 1%;原交易的手续费不退还。退款资金的结算方式以商务协议为准。
提交申请
POST /v1/refund-requests
{ "orderId": "ord_…", "refundTo": "@buyer-venmo", "reason": "客户取消" }
| 字段 | 说明 |
|---|---|
orderId | 必填,我们的订单 ID |
refundTo | 必填,买家在原付款平台上的账户(如 Venmo 用户名、Revolut 标签),2–120 字 |
reason | 可选,最多 500 字 |
返回 201 与退款申请对象:id、orderId、environment、status、type(恒为 FULL)、reason、refundTo、rail、amount、currency、adminNote、peerRefundStatus、refundTransactionHash、createdAt、updatedAt。
错误:传 amount 返回 400 PARTIAL_REFUND_NOT_SUPPORTED;已有进行中的申请返回 409 REFUND_ALREADY_REQUESTED;不满足条件返回 422 REFUND_NOT_ELIGIBLE,responseObject.reason 为 SANDBOX_NOT_SUPPORTED、ORDER_NOT_FULFILLED、ALREADY_REFUNDED、CHARGEBACK_RECORDED、MULTIPLE_OR_NO_SETTLED_PAYMENTS、RAIL_NOT_REFUNDABLE 或 PAYOUT_NOT_BASE_USDC。
状态
| 状态 | 含义 |
|---|---|
requested | 已提交,等待审核;此时可调用 POST /v1/refund-requests/{id}/cancel 撤回 |
approved | 审核通过,等待执行 |
processing | 已发起退款(peerRefundStatus: PENDING) |
completed | 退款完成(peerRefundStatus: COMPLETED,带交易哈希) |
rejected / cancelled | 被拒绝(见 adminNote)/ 已撤回 |
API 参考 · 订阅与账单
GET /v1/merchants/me/subscription
{
"subscription": {
"plan": "merchant", "monthlyFee": "1000", "currency": "USD",
"activatedAt": "2026-10-01", "paidThrough": "2026-10-31", "currentPeriodEnd": "2026-10-31",
"nextChargeDate": "2026-11-01", "cutoffDate": "2026-11-03", "graceDays": 3,
"status": "active" // active / past_due / suspended / cancelled / not_started
},
"invoices": [ { "id": "inv_…", "periodStart": …, "periodEnd": …, "amount": "1000.00", "currency": "USD",
"months": 1, "method": "peer", "status": "open", "checkoutUrl": "https://…", "paidAt": null } ]
}
POST /v1/merchants/me/subscription/invoices
请求体 { "months": 1 }(1–12)。已有未付账单时直接返回它;否则生成新账单并返回带 checkoutUrl 的账单对象。账单在结账页付款结算后自动入账,订阅立即顺延,并发送 SUBSCRIPTION_PAID / SUBSCRIPTION_ACTIVE 事件。
API 参考 · 沙盒测试单
POST /v1/sandbox/test-order
{ "amount": "10.00", "currency": "USD", "rail": "venmo" }
| 字段 | 说明 |
|---|---|
orderId | 可选,结算一张你已经建好的沙盒订单;不传则新建一张 |
amount / currency | 新建时的金额,默认 10.00 USD |
rail | 可选,模拟的付款通道 |
settle | 默认 true:模拟买家付款并结算,触发真实签名的 PAYMENT_SETTLED / ORDER_FULFILLED,转发到你的沙盒 Webhook;传 false 只建单 |
只能用沙盒密钥(正式密钥返回 403 SANDBOX_ONLY),每分钟最多 5 次。不动真钱。
Webhooks · 创建与管理
Webhook 以带签名的 POST 请求,把订单、支付、退款和订阅的状态变化推送到你的服务器,每次都附带订单与支付的完整快照。Webhook 按环境注册:用沙盒密钥注册的端点只收沙盒事件,用正式密钥注册的只收正式事件,各有自己的签名密钥。
基本流程
- 创建 Webhook 并妥善保存签名密钥(只显示一次)。
- 订阅需要的事件。
- 每次收到请求都验证签名与时间戳。
- 用
X-Webhook-Id去重,避免重复处理。 - 用
data.order.id/data.order.reference和data.payment.id与你自己的订单对账。
创建
POST https://api.peer.besofinance.xyz/v1/webhooks
{
"url": "https://yoursite.com/webhooks/beso",
"events": ["ORDER_FULFILLED", "PAYMENT_SETTLED", "REFUND_REQUEST_UPDATED"],
"customHeaders": [{ "key": "X-Auth", "value": "Bearer 共享口令" }]
}
| 字段 | 必填 | 说明 |
|---|---|---|
url | 是 | 公网 HTTPS 地址。不接受 HTTP、内网 / 回环 / 保留地址、本机域名(localhost、*.localhost、*.local、*.internal)和带账号密码的地址。每次投递前都会重新解析域名,解析到非公网地址时该次投递直接失败、不再重试 |
events | 是 | 一个或多个事件名(见“事件与负载”) |
customHeaders | 否 | 最多 10 组 { key, value },每次投递都会带上(例如共享的 Bearer 口令)。规则见下 |
返回 201:{ id, url, events, environment, active, customHeaders, secret },其中 secret(whsec_…)仅显示一次。
x-webhook-id、x-webhook-timestamp、x-webhook-signature、host、content-type、content-length、connection、expect、forwarded、proxy-authenticate、proxy-authorization、te、trailer、transfer-encoding、upgrade,以及所有 proxy-、x-forwarded- 开头的名称。头的值加密保存,列表接口只回显打码后的值。你的接口需要做到
- 接收带 JSON 的 POST 请求。
- 先验证签名,再处理数据。
- 30 秒内返回 2xx,耗时的处理放到异步。重定向(3xx)视为失败。
重试策略
首次投递失败后按下表重试,最多共 7 次尝试(约 35 小时):
| 失败次数 | 下次重试间隔 |
|---|---|
| 1 | 1 分钟 |
| 2 | 5 分钟 |
| 3 | 30 分钟 |
| 4 | 2 小时 |
| 5 | 8 小时 |
| 6 | 24 小时 |
7 次都失败后标记为 FAILED,不再自动重试,但你可以随时手动重发。投递状态:PENDING(待投递或待重试)、DELIVERED(收到 2xx)、FAILED(次数用尽或端点已删除 / 停用)。重试队列持久化保存,服务重启不会丢失。
管理接口
# 列表(当前环境)
GET /v1/webhooks
# 更新:url / events / active / customHeaders(customHeaders 整体替换,传 [] 清空)
PATCH /v1/webhooks/{webhookId} { "active": false }
# 轮换签名密钥:返回新 secret(只显示一次),旧 secret 立即失效
POST /v1/webhooks/{webhookId}/rotate-secret
# 删除
DELETE /v1/webhooks/{webhookId}
# 发送测试事件:只发给这个端点,类型为 ORDER_CREATED,data.test = true,快照均为 null;只尝试一次、不重试(每 10 分钟最多 5 次)
# 返回 { success, deliveryId, responseCode, error }
POST /v1/webhooks/{webhookId}/test
# 该端点最近 50 次投递
GET /v1/webhooks/{webhookId}/deliveries
Webhooks · 事件与负载
负载结构
{
"id": "evt_…", // 同 X-Webhook-Id,用来去重
"type": "ORDER_FULFILLED",
"environment": "LIVE", // SANDBOX / LIVE;订阅类事件为 null
"timestamp": "2026-10-06T10:20:03.112Z",
"data": {
"order": { … }, // 订单对象(同“创建订单”的响应)
"payment": { … } | null, // 支付对象
"refund": { … } | null, // 退款事件
"paymentBridge": { … } | null // 桥接事件
// 仅特定事件:trigger(拒付)、resize(ORDER_RESIZED)、amountChange(ORDER_AMOUNT_SET)
}
}
订单与支付事件
| 事件 | 触发时机 | data.payment |
|---|---|---|
ORDER_CREATED | 订单创建成功(测试投递也用这个类型,带 data.test: true) | null |
ORDER_FULFILLED | 订单已全额结算,可以发货 | 完成订单的那笔支付 |
ORDER_CANCELLED | 没有任何支付尝试的 CREATED 订单被取消 | null |
ORDER_RESIZED | 动态订单被买家调整为附近可成交的金额(data.resize 含新旧金额) | null |
ORDER_AMOUNT_SET | 开放金额订单设定了金额(本 API 暂不创建此类订单) | null |
PAYMENT_CREATED | 买家发起一次支付尝试 | 有 |
PAYMENT_SETTLED | 一笔支付已结算(部分付款也会触发;与 ORDER_FULFILLED 到达顺序不固定) | 有 |
PAYMENT_FAILED | 尝试无法发起,或数字货币转账失败 / 停滞(停滞的尝试若迟到到账仍可能结算) | 有 |
PAYMENT_EXPIRED | 尝试超过付款时限(非终态,迟到的付款仍可能结算) | 有 |
PAYMENT_CANCELLED | 尝试被取消(例如买家换了钱包) | 有 |
PAYMENT_BRIDGE_PENDING / _SUBMITTED / _COMPLETED / _FAILED | 结算后跨链转到你的收款网络:排队 / 已提交 / 已确认 / 失败(data.paymentBridge) | 有 |
REFUND_PENDING / REFUND_COMPLETED | 退款已发起 / 已完成(data.refund) | null |
PAYMENT_CHARGEBACKED | 一笔已结算支付被拒付(data.trigger) | 有 |
ORDER_PARTIALLY_CHARGEBACKED / ORDER_CHARGEBACKED | 订单部分 / 全部已结算支付被拒付 | 有 |
Besofinance 平台事件
| 事件 | 触发时机 | data |
|---|---|---|
REFUND_REQUEST_UPDATED | 退款申请状态变化(审核、执行、完成、拒绝) | { refundRequest } |
SUBSCRIPTION_INVOICE | 生成了新的订阅账单 | { invoiceId, amount, currency, period, dueBy, cutoff, checkoutUrl, subscription } |
SUBSCRIPTION_PAID | 订阅账单已付款 | { invoiceId, periodEnd, subscription } |
SUBSCRIPTION_PAST_DUE / _SUSPENDED / _ACTIVE / _CANCELLED | 订阅状态变化 | { subscription } |
订阅类事件与环境无关(environment 为 null),会投递到两个环境中订阅了该事件的端点。
Webhooks · 验签
请求头
| 请求头 | 说明 |
|---|---|
X-Webhook-Id | 事件唯一 ID(重试和手动重发时不变) |
X-Webhook-Timestamp | 本次发送的 Unix 时间戳(秒),每次尝试都会更新 |
X-Webhook-Signature | HMAC-SHA256 签名,小写十六进制,无前缀 |
算法
signature = hex(HMAC-SHA256(secret, timestamp + "." + rawBody))
时间戳与当前时间相差超过 5 分钟(300 秒)的请求应当拒绝。
Node.js 示例
const crypto = require('crypto');
function verify(rawBody, headers, secret) {
const ts = headers['x-webhook-timestamp'];
const sig = headers['x-webhook-signature'] || '';
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(ts + '.' + rawBody)
.digest('hex');
const a = Buffer.from(sig, 'hex'), b = Buffer.from(expected, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b); // 长度不同时 timingSafeEqual 会抛异常,先比长度
}
使用 SDK 时直接调用 verifyWebhook(rawBody, req.headers, process.env.BESO_WEBHOOK_SECRET)。Express 中请用 express.raw({ type: 'application/json' }) 拿到原始请求体。
Python 示例
import hmac, hashlib, time
def verify(raw_body: bytes, headers, secret: str) -> bool:
ts, sig = headers.get("X-Webhook-Timestamp", ""), headers.get("X-Webhook-Signature", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
return False
expected = hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(sig, expected)
处理清单
- 每次都验证签名与时间戳,超过 5 分钟的一律拒绝。
- 使用防时序攻击的比较函数,不要直接用
==。 - 先验证原始请求体,再解析 JSON。
- 先返回 2xx,再异步处理。
- 用
X-Webhook-Id作为幂等键去重(重试与手动重发的 ID 相同)。 - 只在收到
ORDER_FULFILLED(或data.order.status为FULFILLED)后发货。 - 先核对
environment:正式店铺只处理LIVE事件。 - 记录事件 ID、类型和订单 ID。
- 签名密钥放在环境变量中,不要写进日志或分享给他人。
Webhooks · 投递日志与重发
每次投递都会记录:状态、尝试次数、每次尝试的 HTTP 状态码 / 错误 / 耗时、下次重试时间和原始负载。商家后台“开发者 → 投递日志”可查看并一键重发。
# 列表(分页;可按 status=PENDING|DELIVERED|FAILED、type=事件名 过滤)
GET /v1/webhook-deliveries?status=FAILED&limit=50
# 详情:含 attemptLog(每次尝试)与 payload(原始负载)
GET /v1/webhook-deliveries/{deliveryId}
# 手动重发:任何状态都可以(包括 DELIVERED),事件 ID 不变,签名用新的时间戳
POST /v1/webhook-deliveries/{deliveryId}/resend
投递对象字段:id、webhookId、eventId、type、environment、status、attempts、lastStatusCode、lastError、nextAttemptAt、deliveredAt、createdAt、updatedAt。手动重发不占用自动重试次数。
GET /v1/merchants/me/orders?status=FULFILLED 回读订单状态补齐数据。参考 · 订单与支付状态
订单代表需要收取的总金额;支付代表买家在某个钱包上的一次付款尝试。一张订单可以有多次支付尝试。
订单状态
| 状态 | 含义 |
|---|---|
PENDING_UPSTREAM | Besofinance 已受理,支付网络尚未确认建单(网络异常时短暂出现;用同一 reference 重试即可) |
CREATED | 尚无已结算的支付,底下的支付可能进行中、过期、失败或取消。订单不会过期 |
PARTIALLY_FULFILLED | 已有支付结算,但剩余金额仍高于完成阈值 |
FULFILLED | 剩余金额归零或进入阈值内,订单完成(终态) |
CANCELLED | 没有任何支付尝试的订单被取消(终态) |
拒付(chargebackStatus)和退款(refundStatus)单独记录,不会改变订单的 status:被拒付或已退款的订单仍是 FULFILLED。
支付状态
| 状态 | 含义 |
|---|---|
CREATED | 尝试进行中,买家在时限内付款或等待验证。验证被拒也会停留在 CREATED |
SETTLED | 已结算到收款钱包(终态) |
EXPIRED | 付款时限已过(非终态:迟到的付款仍可能结算,也可用补救接口重新打开) |
FAILED | 结算未能发起,或数字货币转账失败 / 停滞 |
CANCELLED | 买家放弃了这次尝试,例如更换了钱包 |
付款时限
| 通道 | 时限 | 超时后 |
|---|---|---|
| 法币钱包 | 约 1 小时 | EXPIRED;迟到的付款仍会结算并触发 PAYMENT_SETTLED;可 extend 延长或 recreate 重新打开 |
| Relay 数字货币 | 约 6 小时 10 分钟 | 不会进入 EXPIRED;约 6 小时 20 分钟后仍未完成则 FAILED,迟到的充值仍可能结算 |
| Zcash(NEAR Intents) | 20 分钟内完成充值 | 过期 |
状态流转
- 买家选择钱包 → 支付
CREATED CREATED→SETTLED(结算确认)/EXPIRED/FAILED/CANCELLEDEXPIRED / FAILED / CANCELLED可通过补救接口recreate重新打开为CREATEDEXPIRED / CANCELLED遇到迟到的结算可转为SETTLED- 支付
SETTLED后订单变为PARTIALLY_FULFILLED或FULFILLED
参考 · 支持的法币钱包
| 钱包 | 通道标识 | 市场 / 币种 | 可退款 | 可能被拒付 |
|---|---|---|---|---|
| Venmo | venmo | 美国 · USD | 是 | 是 |
| Cash App | cashapp | 美国 · USD | 是 | 否 |
| Zelle | zelle | 美国 · USD(Bank of America、Chase、Citi) | 是 | 否 |
| Chime | chime | 美国 · USD | 否 | 否 |
| PayPal | paypal | 多币种 | 是 | 是 |
| Revolut | revolut | 多币种 | 是 | 否 |
| Wise | wise | 多币种(覆盖最广) | 是 | 否 |
| Monzo | monzo | 英国 · GBP | 是 | 否 |
| N26 | n26 | 欧元区 · EUR | 否 | 否 |
报价支持以下 33 种法币(某张订单实际能用哪些,取决于启用的钱包和当时的流动性;没有流动性的钱包在结账页显示为不可用):
AED ARS AUD CAD CHF CNY CZK DKK EUR GBP HKD
HUF IDR ILS INR JPY KES MXN MYR NOK NZD PHP
PLN RON SAR SEK SGD THB TRY UGX USD VND ZAR
每笔支付记录报价所用的汇率 currencyPerUsdRate。
PAYMENT_CHARGEBACKED 等)通知你,订单的 chargebackStatus 随之变化。请为每张订单保留订单详情、沟通记录与履约证明至少 24 个月。其他钱包不会被拒付,买家要退款只能走退款流程。使用 Zelle 时,买家会选择自己的银行(例如 zelle-chase、zelle-bofa、zelle-citi),对应支付对象里的 paymentMethodId 字段;其他钱包该字段为 null。Zelle 与 N26 的买家验证需要桌面浏览器扩展。
参考 · 支持的数字货币
结账页除法币钱包外,还提供数字货币付款通道。默认收款目的地为 Base 网络上的 USDC;收款目的地由订单的 destinationChainId 与 destinationToken(即请求里的 settleChain / settleToken)单独设置。
买家如何用数字货币付款
- 买家选择一条链。
- 买家从该网络上的钱包完成转账。
- 转账被桥接到你的收款目的地,过程中会收到
PAYMENT_BRIDGE_*事件。
数字货币通道标识
在建单请求的 enabledRails 中使用,格式为 relay_<chainId>,每条受支持的买家付款链对应一个;另有 Zcash 与 Apple Pay 两个标识。旧写法 crypto_<chainId> 仍被接受并自动转换。
| 网络 | 通道标识 |
|---|---|
| Ethereum | relay_1 |
| Optimism | relay_10 |
| BNB Smart Chain | relay_56 |
| Polygon | relay_137 |
| World Chain | relay_480 |
| Hyperliquid | relay_999 |
| Arc | relay_5042 |
| Base | relay_8453 |
| Arbitrum | relay_42161 |
| Bitcoin | relay_8253038 |
| Tron | relay_728126428 |
| Solana | relay_792703809 |
| Zcash(经 NEAR Intents) | near_intents_133701 |
| Apple Pay(Coinbase) | apple_pay(结算后按所走的 Relay 通道记账) |
买家可用的数字货币(以结账页实际显示为准)
| 资产 | 说明 |
|---|---|
| BTC | Bitcoin |
| ETH | 5 条链 |
| USDT | 8 条链 |
| USDC | 9 条链 |
| PYUSD | 2 条链 |
| SOL | 2 条链 |
| BNB | BNB Smart Chain |
| HYPE | Hyperliquid |
| WBTC | 5 条链 |
| USDH | Hyperliquid |
| ZEC | Zcash |
网络:Bitcoin、Ethereum、Solana、Base、Optimism、BNB Smart Chain、Polygon、World Chain、Hyperliquid、Arc、Arbitrum、Zcash、Tron。
订单里与数字货币有关的字段
| 字段 | 说明 |
|---|---|
destinationAddress | 收款钱包地址 |
destinationToken | 收款币种 |
destinationChainId | 收款网络(链 ID,字符串) |
netSettledUsdcAmount | 最终结算到账的 USDC 数量(Relay 支付在 PAYMENT_SETTLED 中可能是估算值,几分钟后回读为准) |
currencyPerUsdRate | 支付对象上的汇率 |
fulfillTransaction | 结算交易哈希:钱包付款为 Base 上的哈希,Relay 付款为收款网络上的哈希 |
默认收款钱包是开户时登记的 EVM 地址(evmWalletAddress);结算到 Solana、Tron 等非 EVM 网络时,请在建单时传入对应网络的 settleAddress。
手续费承担方
通过 feePayer 设置:MERCHANT(商家承担)、PAYEE(买家承担)、SPLIT(分摊);SPLIT 时用 buyerFeeShareBps(0–10000,1000 的倍数)设置买家分摊的比例。
参考 · 错误码
请按 errorCode 做程序判断,message 只用于日志。支付网络返回的业务错误码会原样透传。
| 状态码 | errorCode | 场景 |
|---|---|---|
400 | VALIDATION_ERROR | 路径、查询或请求体校验失败,详见 fieldErrors / formErrors |
400 | AMOUNT_BELOW_MIN / AMOUNT_ABOVE_MAX / AMOUNT_NON_POSITIVE | 订单金额低于最小额(默认 10 USDC)/ 高于 10,000 USDC / 换算后不大于 0 |
400 | INTENT_ABOVE_MAX | 买家发起付款时,含手续费超过每笔 10,000 USDC |
400 | DESTINATION_INVALID / PAYOUT_CONFIG_INVALID | 收款地址与网络不匹配 / 不支持的收款网络或币种 |
400 | NO_ELIGIBLE_PAYMENT_RAILS | 本单允许的通道都不可用 |
400 | AMOUNT_BELOW_MERCHANT_MIN / AMOUNT_ABOVE_MERCHANT_MAX | 超出为你的收款账户单独设置的单笔金额范围(沙盒不检查),需调整请联系 Besofinance |
400 | MERCHANT_CONFIG_MISSING | 收款账户尚未完成结账配置(由 Besofinance 处理) |
400 | DYNAMIC_ORDERS_DISABLED | 账户未开启动态订单却传了 dynamicOrdersEnabled: true |
400 | PARTIAL_REFUND_NOT_SUPPORTED | 退款申请里传了金额(只支持全额) |
401 | UNAUTHORIZED | 缺少或无效的 API 密钥 / 后台会话 |
401 | SIGNATURE_REQUIRED / INVALID_SIGNATURE | 缺少签名请求头 / 签名不匹配 |
401 | TIMESTAMP_OUT_OF_RANGE / INVALID_NONCE / NONCE_REUSED | 时间戳偏差超过 300 秒 / nonce 格式不对 / nonce 重复(疑似重放) |
401 | API_KEY_REVOKED / API_KEY_EXPIRED | 密钥已吊销 / 轮换宽限期已过 |
401 | LEGACY_KEY_DISABLED | 旧版单一密钥已停用 |
402 | SUBSCRIPTION_EXPIRED | 订阅到期且宽限期已过,付清账单后自动恢复 |
403 | MERCHANT_NOT_ACTIVE | 账户还在审核中或未开通 |
403 | MERCHANT_SUSPENDED | 账户被 Besofinance 冻结 |
403 | PEER_REVIEW_PENDING | 支付网络尚未通过店铺审核:不能生成正式密钥,正式环境建单(POST /v1/orders)与额度检查被拒绝;读接口、Webhook 配置与沙盒不受影响 |
403 | MERCHANT_TIER_FORBIDDEN | 上游正式收款账户尚未选定套餐(由 Besofinance 处理,沙盒不会出现) |
403 | MERCHANT_MONTHLY_ORDER_LIMIT_EXCEEDED / MERCHANT_MONTHLY_VOLUME_LIMIT_EXCEEDED | 上游账户的本月法币订单笔数 / 金额已达上限(见“套餐额度”)。目前本平台的上游账户与沙盒均不设上限 |
403 | IP_NOT_ALLOWED | 来源 IP 不在白名单(reason:IP_NOT_ALLOWED / CLIENT_IP_UNRESOLVED) |
403 | SANDBOX_ONLY / SANDBOX_NOT_SUPPORTED | 该接口只能用沙盒密钥 / 只能在正式环境使用 |
404 | NOT_FOUND | 资源不存在,或不属于当前商户 / 环境 |
409 | IDEMPOTENCY_KEY_CONFLICT | 同一 reference 已用于金额或币种不同的订单 |
409 | MERCHANT_OWNERSHIP_CHANGED | 上游账户在建单过程中变更了所有者,未保存任何数据;用同一 reference 重试即可 |
409 | CHECKOUT_URL_UNRECOVERABLE | 订单已在支付网络创建但链接丢失,请换 reference 重建 |
409 | ENVIRONMENT_NOT_AVAILABLE | 该环境的收款账户尚未开通 |
409 | REFUND_ALREADY_REQUESTED / INVALID_TRANSITION | 已有进行中的退款申请 / 当前状态不能执行该操作 |
409 | APPLICATION_EXISTS / KEY_LIMIT_REACHED | 该邮箱已有申请或账户 / 该环境有效密钥已达 5 把 |
409 | ACTION_IN_PROGRESS | 同类补救动作进行中(responseObject.actionId) |
422 | REFUND_NOT_ELIGIBLE | 订单不满足退款条件(responseObject.reason) |
422 | PAYMENT_NOT_ELIGIBLE / IDEMPOTENCY_KEY_MISMATCH | 支付不满足补救条件(reason)/ 补救的幂等键已用于别的请求 |
429 | RATE_LIMITED | 请求过于频繁,按 x-retry-after 等待 |
500 | INTERNAL_ERROR | 服务内部错误,可重试 |
502 | UPSTREAM_ERROR / EXCHANGE_RATE_LOOKUP_FAILED | 支付网络暂时不可用 / 汇率查询失败,可用同一 reference 重试 |
502 | UPSTREAM_AUTH_FAILED | 你的收款账户凭证异常,Besofinance 已收到告警 |
503 | FEATURE_DISABLED / TREASURY_NOT_CONFIGURED | 补救功能暂时关闭 / 平台暂不能生成在线账单 |
参考 · 与 Peer 原生 API 的差异
如果你之前直接对接过 Peer Pay(api.pay.peer.xyz),迁移到本 API 时注意以下差异:
| 项目 | Peer 原生 | besofinance.peer |
|---|---|---|
| 基础地址 | https://api.pay.peer.xyz/api/v1 | https://api.peer.besofinance.xyz/v1,路径去掉 /api 前缀,其余一致 |
| 鉴权 | 单一 X-API-Key,无前缀 | 公钥 + 私钥 HMAC 签名,前缀区分环境;支持轮换宽限期 |
| 金额字段 | requestedFiatAmount / requestedFiatCurrency | requestedAmount / requestedCurrency(原名也接受;requestedUsdcAmount 相同) |
| 收款字段 | destinationAddress / destinationChainId / destinationToken | settleAddress / settleChain / settleToken(原名也接受);不传用开户时登记的钱包 |
| 幂等 | 请求体 idempotencyKey,重放返回 checkoutUrl: null | reference(或 Idempotency-Key 头 / idempotencyKey),重放返回原订单和原链接 |
| 订单 ID | Peer 订单 ID | ord_…;单张订单可用 API 密钥读取(/merchants/me/orders/{id}) |
| 开放金额订单 | 支持 openAmount | 暂不支持 |
| Webhook 负载 | { id, type, timestamp, data },订单为 CheckoutOrder | 同结构,另加 environment;data.order 为本 API 的订单对象(含 reference) |
| Webhook 创建响应 | { webhook, secret } | { id, url, events, environment, active, customHeaders, secret } |
| 投递记录 | 每端点最近 50 条,{ deliveries, hasMore };无手动重发 | 每端点最近 50 条(数组)+ 全量分页日志、每次尝试详情、手动重发;另可轮换签名密钥 |
| 退款 | 仅 Owner 在后台发起,无 API | 退款申请 API + 审核流程,状态通过事件通知 |
| 限流 | 每 IP 每分钟 1200 次 | 每把密钥每分钟 600 次 |
| 用量 | /merchants/me/tier-usage(套餐笔数 / 金额上限) | /merchants/me/tier-usage 原样透传;另有 /merchants/me/usage(近 30 天统计) |
| 计费 | 上游套餐 + 交易费 | 1,000 美元 / 月订阅 + 交易费;欠费返回 402 |
| 订单取消 | 仅后台 | 暂不提供(请联系 Besofinance) |
联系我们
接入、费率、退款或合作问题,请发邮件至 xiongfei@besofinance.xyz。