开发者文档
API 文档
集成账单、结账与付款通知。
搜索结果
没有结果。试试端点、字段或指南名称。
快速入门
创建第一张账单。
- 准备商店
启用支付方式,配置服务商,并备份项目钱包。
- 创建 API 凭据
在控制台“设置 → API 访问”中选择读写权限,并分配项目。
- 发送请求
使用 API 主机和 复制项目和商店 ID。十进制金额请以字符串发送。
- 打开结账
重定向到
links.checkout,该值来自响应。履行订单前先验证结算。
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: order-1042-attempt-1' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new invoice.
const body = `{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new invoice.
$body = <<<'JSON'
{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: order-1042-attempt-1", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new invoice.
headers = {
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))示例使用占位符,不会从此页面发送请求。 查看所有账单字段和响应格式 →
项目与商店 ID
在哪里找到 YOUR_PROJECT_ID 和 YOUR_STORE_ID。
使用控制台中的 UUID,不要使用项目或商店名称,也不要使用其可读标识符。
| 占位符 | 获取位置 | 用途 |
|---|---|---|
| YOUR_PROJECT_ID | 项目 → 设置 → API ID → 项目 API ID → 复制。商店的“基本设置”标签也会显示。 | 项目级和商店级请求。 |
| YOUR_STORE_ID | 项目 → 商店 → 选择商店 → 基本设置 → API ID → 商店 API ID → 复制。 | 创建账单和商店支付方式请求。 |
- 创建账单需要两个 ID,即使使用默认商店也一样。商店必须属于该项目,API 凭据也必须有权访问该项目。
- 账单创建、列表、详情和结账均返回 invoice_id:与 IPN/Webhook 发送的 UUID 相同。账单路径中请使用它,而非内部 id 或 order_id。自商户版 4.0.0 起,旧 public_id 响应字段已移除;升级前请更新集成。
- REST API 不提供项目/商店列表路由。请从控制台复制 ID,或在商户版 5.0.0+ 使用限定范围的 list_projects 和 list_stores MCP 工具。
- “商店 → 基本设置 → 商店域名”用于选择已启用的商户、支付和 API 主机名。返回的结账链接和新回调链接优先使用该商店,其次默认商店,最后系统默认值。已停用或未启用的名称不会被选中。请用首选 API 主机名配置 SDK;更改首选项不会重定向其他已启用别名。
身份验证与范围
将凭据保存在服务器上,只授予所需权限。
| 默认主机 | 用途 |
|---|---|
| merchant.example.com | 商户控制台与设置 |
| pay.example.com | 客户结账 |
| api.example.com | 商户 API 请求 |
替换 example.com 为你的域名。现有安装保留已配置名称;可在“设置 → 系统”管理别名。
Authorization: Bearer YOUR_MERCHANT_API_TOKEN| 设置 | 使用流程 |
|---|---|
| 访问级别 | 只读凭据可以列出和获取数据。读写凭据还可以创建账单并更新文档中列出的资产策略。 |
| 项目 | 分配凭据可以访问的项目。商店和账单 ID 必须属于已分配项目。 |
| IP 限制 | 可在“设置 → API 访问”中选择允许准确的公网 IPv4 或 IPv6 出口地址。 |
| 凭据存储 | 将令牌保存在后端配置中。绝不要在浏览器或结账链接中包含 Bearer 凭据。 |
公共结账路由使用账单的公共 ID,仅公开可安全用于结账的数据。控制台会话和管理控制与商户 API 凭据相互独立。
资产与钱包
为每个商店单独选择支付方式。
- 读取 项目支付资产 及其就绪状态。
- 启用原生链,并配置其钱包和服务商。
- 浏览 代币候选项 和 验证合约或 mint ,然后再启用代币。
- 选择商店中按顺序排列的 支付方式。新账单使用其中已就绪的选项。
代币与其原生链共用钱包。 钱包余额 返回精确的最小单位金额及参考法币价值。使用返回的就绪字段判断哪些方式可以收款。
已验证的 ERC-20 代币使用支持的 EVM 网络;已验证的 SPL 代币使用 Solana。30 个集成网络均提供原生支付方式。Monero 使用绑定项目的外部只读钱包连接。
收款 API 与原生币/代币覆盖范围
| 支付通道 | 支持情况 | 证据 | 要求 |
|---|---|---|---|
| 原生币支付通道 | 支持 | 交易扫描 | BTC、SOL、ETH(Ethereum/Base/Arbitrum/OP)、BNB、HYPE、AVAX 和 POL;Bitcoin 输出、规范 EVM 交易/收据及解析后的 Solana 转账提供账单证据。 |
| ERC-20 代币支付通道 | 支持 | 交易扫描 | Ethereum、Base、BNB Chain、HyperEVM、Avalanche、Polygon、Arbitrum 和 Optimism 需要链上验证;索引的 Transfer 日志用于归属付款。 |
| SPL 代币支付通道 | 支持 | 交易扫描 | Solana 候选代币需要主网和 mint 验证;解析交易中的精确代币余额变化用于归属付款。 |
| 其他 UTXO 原生币通道 | 支持 | 交易扫描 | BCH/LTC/DOGE 使用 Esplora;BCH/DOGE 也接受 Bitcore,LTC/DOGE/DASH 接受 BlockCypher,Dash 接受 Insight,透明 ZEC 接受 zcash-explorer。它们也都接受完整保留的 Core 兼容 node-rpc 区块。原始模式需要 1–48 次确认,不使用内存池检测。不支持隐私 Zcash。 |
| 索引账户型原生币通道 | 支持 | 交易扫描 | TRON 使用 tron-indexer 或已固化的 node-rpc;XRP 使用 xrpl-jsonrpc;Stellar 使用 stellar-horizon 或保留的 Stellar node-rpc 账本;Cosmos Hub 使用 cometbft-jsonrpc;Algorand 使用 algorand-indexer 或 algod node-rpc;Hedera 需要 hedera-mirror,而非 EVM 中继。 |
| 原生账本支付通道 | 支持 | 交易扫描 | Aptos 使用 aptos-rest;Sui 使用 sui-graphql;NEAR 使用 near-jsonrpc;Kaspa 使用 kaspa-rest。Polkadot Asset Hub 接受 substrate-rest 或识别元数据的最终确认 node-rpc;Tezos 接受 tezos-tzkt 或完整 Octez node-rpc 操作。仅原生币收款;旧账单需要归档保留。 |
| Cardano 与 TON 原生币通道 | 支持 | 交易扫描 | Cardano 需要 cardano-koios,TON 需要 toncenter-v3。XRP tag、Stellar memo ID 和 TON 账单备注以 destination_tag 返回,必须原样发送。 |
| 结算完整性 | 支持 | 独立验证 | 默认情况下,最终结算要求两个独立服务商对准确的交易/事件、金额、规范区块或 slot 及最终性达成一致。原始扫描和共享 EVM 窗口也会验证覆盖完整性。管理员可明确选择某链只使用一个可信服务商;这会取消独立交叉核验,但不会取消身份、完整性或最终性检查。 |
| Monero 原生币通道 | 支持 | 绑定项目的只读钱包 RPC | 通过 HTTPS 方法白名单网关连接一个专用的外部只读 wallet-RPC,创建 account-0 子地址。配置的主网守护进程阈值(默认 2 个独立来源,可选 1 个)提供结算证据。原生 --restricted-rpc 与 create_address 不兼容;运营商须明确确认钱包备份及不存在花费密钥,且不会向 Wholly Crypto 发送任何密钥材料。 |
交易所余额及按资产选择钱包或交易所归集,可在控制台使用,不通过公共 v1 API 提供。 查看交易所设置.
账单生命周期
付款证据、结算与订单履行。
| 状态 | 含义 |
|---|---|
| new | 等待付款 |
| processing | 已发现付款;等待满足金额要求或最终确认 |
| settled | 按账单结算策略或手动接受 |
| expired | 截止时间已过;延迟付款监控可能继续 |
| invalid | 无法自动接受付款 |
| cancelled | 已取消;只有明确的对账操作才能重新开启 |
amount_status 记录 none, partial, paid 或 overpaid. timing_status 区分准时和延迟付款。商店规则决定所需确认数及接受的少付容差。
使用账单的 invoice_id 配合 账单详情路由。仅有结账重定向并不能证明结算完成。请通过以下功能审核异常: 对账.
安全重试
创建账单需要 Idempotency-Key。超时后,使用同一凭据、密钥和完全相同的请求体重试。只有新账单才使用新密钥。
EVM 付款扫描
共享原生区块和 ERC-20 发现会将近期账单与旧账单补扫分组处理。每张账单保留持久化历史游标。代币查询每次最多请求 100 个区块,并根据更严格的服务商限制缩小范围。默认由两个独立服务商验证每个窗口。“设置 → 区块链连接 → 详情”可将某链切换为一个可信来源,取消独立交叉核验;规范交易、金额和确认检查仍保留。连接详情会区分扫描延迟、历史限制、配额冷却与基础节点健康。公共 RPC 容量不作保证。
IPN 与 Webhook
接收并验证付款事件。
IPN 在账单有效的 ipn_url 接收每个生成的账单事件。Webhook 仅接收为各个已启用商店端点选择的事件。两者都 POST 相同 JSON 快照,但彼此独立,因此同时启用可能使应用收到两次通知。
设置 ipn_url 在创建账单时指定,或继承商店默认值。IPN 使用 商店 → IPN 密钥;每个 商店 → Webhook 端点都有自己的密钥。两者都不是 API 密钥。
应何时履行订单?
按事件处理时,使用 event_type = invoice.settled 和 status = settled 一起触发订单检查。验证当前账单,每个订单只履行一次。
status 是事件创建时的账单状态,event_type 表示发生了什么。payment.received 可带有 processing 或 settled;它不表示第二笔付款,也不是独立的履单信号。
会发送哪些事件和状态?
| 设置/历史中的事件 | 请求体状态 | 含义 |
|---|---|---|
| invoice.created | new | 账单已创建并等待付款。受控重新开启使账单回到 new 时也使用此事件。 |
| payment.received | Resulting invoice status | 已记录付款或收款金额增加。通常为 processing 或 settled;仅此事件不能证明结算完成。 |
| invoice.processing | processing | 已检测到付款,但接受金额或所需最终性尚未满足。包括部分付款。 |
| invoice.settled | settled | 满足结算策略,或经手动接受。履行前请检查 resolution 和订单。 |
| invoice.expired | expired | 付款截止时间已过。监控继续时,延迟付款仍可改变状态。 |
| invoice.invalid | invalid | 无法自动接受、付款证据丢失,或商户拒绝。请审核账单。 |
| invoice.cancelled | cancelled | 账单已取消。不要履行订单;取消不会退还链上付款。 |
为何 Ethereum 与 Solana 的事件流程可能不同
确认稍后到达(Ethereum 示例)
| 序列 | event_type | status |
|---|---|---|
| 1 | invoice.created | new |
| 2 | payment.received | processing |
| 2 | invoice.processing | processing |
| 3 | invoice.settled | settled |
检测时已最终确认(Solana 示例)
| 序列 | event_type | status |
|---|---|---|
| 1 | invoice.created | new |
| 2 | payment.received | settled |
| 2 | invoice.settled | settled |
这里展示事件创建顺序,不保证发送顺序。其他链也可能因检测时机和结算策略出现任一种流程。不要要求 settled 前必须有 processing 事件。
仅履行一次:接收端示例与重复保护
| 方式 | 处理方法 |
|---|---|
| 基于事件的接收端 | 按带签名的 event_id 保留不同事件,再选择 status = settled 的 invoice.settled。不要因为同一 sequence 的 payment.received 先到达,就丢弃该事件。 |
| SDK 订单状态收件箱 | 提供的 PHP、Python 和 Node 接收端示例按项目 + invoice_id + sequence 合并。无论 event_type 是什么,都处理已保存状态,获取当前账单,并在已结算时只履行一次。合并后不要再添加只接受 invoice.settled 的筛选。 |
重试保留 event_id 和原始请求体。不同事件可共享 sequence,但具有不同 event_id。基于事件处理时按带签名的 event_id 去除重复发送;另按配置的安装/项目 + invoice_id 及订单独立防止重复履行。后续重新结算不得重复给订单记账。
HTTP receiver:
Verify raw-body signature, timestamp and configured project/store scope.
Save to a durable inbox; deduplicate the signed event_id.
Return HTTP 2xx only after persistence succeeds.
Event-based background worker:
Other events go to status/reconciliation handling, not fulfilment.
Continue here only for event_type = invoice.settled and status = settled.
Fetch the current invoice from your configured API origin.
Check settled status, project/store, order, amount, currency and review policy.
In one database transaction:
Lock the order and check the scoped invoice has not been fulfilled.
Credit/complete once and save the fulfilment record.
Queue any external fulfilment with the same business idempotency key.
SDK order-state worker:
Use the same current-invoice checks and fulfil-once transaction.
Do not filter event_type after collapsing events by invoice revision.这是伪代码,不是可直接使用的接收端。
所有账单状态与付款异常
| 字段 | 值 | 含义 |
|---|---|---|
| status | new, processing, settled, expired, invalid, cancelled | 事件创建时的账单状态,未必是发送时的当前状态。 |
| amount_status | none, partial, paid, overpaid | 收到的金额,包含接受的容差。paid 不代表最终确认。 |
| timing_status | on_time, late | 付款是否赶上账单截止时间。 |
| resolution | automatic, manually_settled, manually_invalidated | 结果是由正常规则还是手动接受/拒绝决定。 |
| requires_review | false, true | 异常提示,不是另一种账单状态,也不代表自动允许履单或退款。 |
| 情况 | 处理 |
|---|---|
| 少付 / 容差 | 自动规则下,partial 不会结算。paid 可以包含接受的少付金额,但仍需最终确认。请使用账单状态,不要只比较金额。 |
| 多付 | overpaid 可与 settled 和 requires_review = true 同时存在。请应用多付处理策略;绝不要为订单重复记账,也不要自动退款到未经验证的地址。 |
| 延迟付款 | 持续监控期间 expired 仍可能改变。timing_status = late 表示需要审核;不要自动重新开启或发货已取消的订单。 |
| 手动接受 | invoice.settled 可能带有 resolution = manually_settled,而没有符合条件的链上资金。请决定你的集成是否接受这种覆盖;付款汇总字段可能为 null。 |
| 链重组 / 失效 | 较新的修订可能使先前的付款证据失效。重新获取当前状态,并通过对账处理冲正。不要仅因订单曾经结算就忽略它。 |
| 零确认 / 零金额 | 零确认结算可在检测时发生,存在链重组风险。明确允许的零金额账单会在没有付款时结算。两者都不要求先出现 payment.received 事件。 |
履单使用 status = settled,不要使用 amount_status = paid 或结账重定向。要求确认数为零时,结算可在检测时发生,存在链重组风险。
少付为 amount_status = partial;多付为 overpaid。paid 表示已收到可接受的最低金额,包含账单的少付容差。这些是金额状态,不是账单状态。late 是 timing_status,不是独立事件。
典型流程为 new → processing → settled,但可能跳过中间状态。明确允许的零金额账单无须付款即结算,且保持 amount_status = none。手动接受标记为 manually_settled。
回调是不可变快照,不是实时状态响应。它们可能延迟、乱序或重复到达。付款事件和状态事件可共享账单 sequence 及相同的账单状态字段,但签名的 event_id 和 event_type 不同。确认数变化不保证每个区块都有回调。
你会收到什么
{
"invoice_id": "11111111-2222-4333-8444-555555555555",
"status": "settled",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 3,
"amount": "49.9",
"currency": "EUR",
"order_id": "order-1042",
"payload_version": 2,
"event_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"event_type": "invoice.settled",
"occurred_at": "2026-09-14T12:05:00Z",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"description": "Annual plan",
"email": "ada@example.test",
"customer": {
"firstname": "Ada",
"lastname": "Lovelace",
"countryiso2": "GB"
},
"metadata": {
"firstname": "Ada",
"lastname": "Lovelace",
"countryiso2": "GB",
"cart_id": "cart-681"
},
"created_at": "2026-09-14T12:00:00Z",
"updated_at": "2026-09-14T12:05:00Z",
"expires_at": "2026-09-14T12:15:00Z",
"monitoring_expires_at": "2026-09-21T12:15:00Z",
"settled_at": "2026-09-14T12:05:00Z",
"paid_chain": "ethereum",
"paid_asset": "USDC",
"paid_asset_amount": "58.17342",
"paid_asset_amount_received": "58.17342",
"paid_payment_method_id": "33333333-3333-4333-8333-333333333333",
"settlement_exchange_rate": {
"rate": "1.17",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"observed_at": "2026-09-14T12:05:00Z",
"as_of": "2026-09-14T12:04:30Z",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T12:04:30Z",
"asset_fetched_at": "2026-09-14T12:04:30Z",
"stale": false,
"is_fixed": false,
"reference_currency": "USD",
"uses_reference_proxy": false
},
"cancelled_at": null,
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"reason_code": "payment_confirmed",
"requires_review": false,
"links": {
"checkout": "https://pay.example.com/invoice/11111111-2222-4333-8444-555555555555",
"invoice": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555",
"payments": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555/payments"
},
"payment_info": {
"active_payment_method_id": "33333333-3333-4333-8333-333333333333",
"method_count": 1,
"methods_truncated": false,
"methods": [
{
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"caip_network_id": "eip155:1",
"asset_id": "44444444-4444-4444-8444-444444444444",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"asset_name": "USD Coin",
"symbol": "USDC",
"asset_kind": "token",
"asset_decimals": 6,
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"token_standard": "erc20",
"destination_address": "0x1111111111111111111111111111111111111111",
"destination_tag": null,
"status": "paid",
"amounts": {
"expected_amount": "58.17342",
"expected_amount_atomic": "58173420",
"received_amount": "58.17342",
"received_amount_atomic": "58173420",
"confirmed_amount": "58.17342",
"confirmed_amount_atomic": "58173420",
"unconfirmed_amount": "0",
"unconfirmed_amount_atomic": "0",
"minimum_payment_amount": "57.591686",
"minimum_payment_amount_atomic": "57591686",
"remaining_amount": "0",
"remaining_amount_atomic": "0",
"remaining_to_full_amount": "0",
"remaining_to_full_amount_atomic": "0",
"overpaid_amount": "0",
"overpaid_amount_atomic": "0"
},
"acceptance": {
"finality_mode": "confirmations",
"required_confirmations": 2,
"observed_confirmations": 2,
"underpayment_tolerance_percent": "1"
},
"quote": {
"effective_rate": "1.1658",
"reference_rate": "1.16",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"exchange_rate_spread_percent": "0.5",
"quote_expires_at": "2026-09-14T12:15:00Z",
"provenance_available": true,
"rounding": "up",
"unrounded_payment_amount": "58.17342",
"rounding_adjustment": "0",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T11:59:30Z",
"asset_fetched_at": "2026-09-14T11:59:30Z"
},
"market_rate_at_event": {
"rate": "1.17",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"observed_at": "2026-09-14T12:05:00Z",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T12:04:30Z",
"asset_fetched_at": "2026-09-14T12:04:30Z",
"as_of": "2026-09-14T12:04:30Z",
"stale": false,
"is_fixed": false,
"reference_currency": "USD",
"uses_reference_proxy": false
},
"payment_count": 1,
"payments_truncated": false,
"payments": [
{
"payment_id": "55555555-5555-4555-8555-555555555555",
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"transaction_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"payment_hash": null,
"event_index": 0,
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_id": "44444444-4444-4444-8444-444444444444",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"symbol": "USDC",
"asset_decimals": 6,
"amount": "58.17342",
"amount_atomic": "58173420",
"status": "final",
"counts_towards_received": true,
"confirmations": 2,
"block_height": 26000000,
"observed_at": "2026-09-14T12:04:30Z",
"chain_time": "2026-09-14T12:04:20Z",
"finalized_at": "2026-09-14T12:05:00Z",
"explorer_name": "Etherscan",
"explorer_url": "https://etherscan.io/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
],
"links": {
"payments": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555/payments?payment_method_id=33333333-3333-4333-8333-333333333333"
}
}
]
}
}amount 是原始账单总额。 payment_info 描述观察到的加密货币转账、仍缺少的金额和锁定汇率。第 2 版还会签署事件名称、事件 ID 和项目/商店范围。
所有回调字段与额外账单数据
| 字段 | 类型 | 含义 |
|---|---|---|
| invoice_id | UUID | 公共账单 UUID,用于经过身份验证的账单详情路由 |
| status | string | 账单状态快照:new、processing、settled、expired、invalid、cancelled |
| amount_status | string | none、partial、paid 或 overpaid;paid 包含接受的少付容差,不代表最终确认 |
| timing_status | string | on_time 或 late |
| resolution | string | automatic、manually_settled 或 manually_invalidated |
| sequence | integer | 递增的账单修订号;不同事件可共享同一修订。比较时不得丢失整数精度 |
| amount | decimal string | 原始账单总额,不是收到的加密货币金额;保留十进制精度 |
| currency | string | amount 的币种,例如用 USDC 支付的 EUR 账单仍为 EUR |
| order_id | string | null | 商户订单参考 |
| payload_version | integer | 新生成的 4.1.0+ 事件为 2;保留的旧事件中不存在 |
| event_id | UUID | 带签名的事件标识,在重试和手动重发时保持不变 |
| event_type | string | 七种订阅事件之一 |
| occurred_at | timestamp | 此不可变事件的创建时间,而非发送时间 |
| project_id | UUID | 商户项目范围;需与配置的接收端匹配 |
| store_id | UUID | 商户商店范围;需与配置的接收端匹配 |
| description | string | null | 原始账单说明 |
| string | null | 事件创建时的可选客户邮箱 | |
| customer | object | 识别的可选客户元数据字段;不猜测或补充个人数据 |
| metadata | object | 事件创建时原样保留的商户元数据 |
| created_at | timestamp | 账单创建时间 |
| updated_at | timestamp | 账单状态更新时间 |
| expires_at | timestamp | 账单付款截止时间 |
| monitoring_expires_at | timestamp | 延迟付款监控截止时间 |
| settled_at | timestamp | null | 结算时间 |
| paid_chain | string | null | 4.1.2+:已证实结算方式的链 slug,例如 ethereum;没有保存的合格结算则为 null |
| paid_asset | string | null | 4.1.2+:原生币或代币代码,例如 BTC、ETH 或 USDC;仅为显示标签,不是唯一资产标识 |
| paid_asset_amount | decimal string | null | 5.0.1+:以 paid_asset 为单位的完整锁定请求金额,尚未扣除容差;结算时保存 |
| paid_asset_amount_received | decimal string | null | 5.0.1+:结算时最终采用方式收到的有效总金额,包含接受的少付/多付;为冻结值,不是实时余额 |
| paid_payment_method_id | UUID | null | 4.1.2+:结算意图 ID;匹配 payment_info.methods[].payment_method_id 及其准确网络/合约 |
| settlement_exchange_rate | object | null | 4.1.2+:结算时保存的加价前市场快照,包含明确的单位、币种、来源时间戳和质量标记;发送时绝不重新定价 |
| cancelled_at | timestamp | null | 取消时间 |
| exchange_rate_spread_percent | decimal string | 锁定的加价比例,不是当前商店默认值 |
| underpayment_tolerance_percent | decimal string | 锁定的账单容差;每种方式也会报告其实际容差 |
| reason_code | string | null | 机器可读的状态转换原因 |
| requires_review | boolean | 付款异常提示,不是自动履单或退款的授权 |
| links | object | 事件创建时的结账、需认证的账单和付款 URL。先使用“商店 → 基本设置”的域名首选项,再用默认商店,最后全局主域名;仅使用已启用且角色匹配的域名。重试保留原始签名链接;没有活动主机记录时为 null。 |
| payment_info | object | 实际观察到的方式、精确金额、锁定报价、参考市场快照和有数量上限的付款观察记录;见下方字段组 |
结算汇总:settlement_exchange_rate
| 字段 | 类型 | 含义 |
|---|---|---|
| rate / units / currency / symbol | strings | 加价前每一单位账单币种对应的资产单位数。十进制字符串,不是付款金额或已执行交易。 |
| observed_at / as_of | timestamps | 结算快照时间 / 较早的来源时间戳。不要将缓存数据视为实时行情。 |
| pricing_provider / asset_provider / pricing_fetched_at / asset_fetched_at | strings / timestamps | 结算时保存的法币与资产定价来源及获取时间。 |
| stale / is_fixed / uses_reference_proxy / reference_currency | booleans / string | 与 market_rate_at_event 相同的质量标记。固定项目价格会标注;参考币种为 USD。 |
| Missing snapshot or price | null | 不会猜测历史汇率。结算前所有汇总字段为 null;仅缺少价格时,经过证实的 paid_* 标识符仍可用。 |
支付方式:payment_info
| 字段 | 类型 | 含义 |
|---|---|---|
| active_payment_method_id | UUID | null | 最终采用或选中的已观察方式。检测前或失效后为 null,不会猜测默认方式。 |
| method_count / methods_truncated | integer / boolean | 观察到的方式总数,以及嵌入的方式列表是否不完整。 |
| methods[] | object[] | 最多八种已观察方式,活动方式优先。不提供跨资产合计。 |
| payment_method_id / payment_rail | UUID / string | 账单意图标识及 onchain 或 lightning 传输方式。 |
| chain_slug / network / caip_network_id | string | 网络标识。代币标识必须与网络一起使用。 |
| asset_id / asset_key / caip_asset_id | UUID / string / nullable string | 经验证的注册表标识;仅符号并不唯一。 |
| asset_name / symbol / asset_kind | string | 资产显示名称、代码,以及原生币或代币类型。 |
| contract_address / token_standard | string | null | 代币合约或 mint 及标准;原生资产为 null。 |
| asset_decimals | integer | 最小单位精度;Lightning BTC 为 11。 |
| destination_address / destination_tag | string | null | 公共收款地址及必需的 memo/tag。Lightning 地址为 null;绝不是私钥。 |
| status | string | 方式状态:pending、partial、paid、overpaid、expired 或 invalid。paid 本身并不表示账单已结算。 |
| payment_count / payments_truncated / payments[] | integer / boolean / object[] | 观察记录总数及最近最多五条记录。每条记录的说明见下方。 |
| links.payments | HTTPS URL | null | 在已配置 API 源站上此方式的需认证分页历史。 |
精确金额:methods[].amounts
| 字段 | 类型 | 含义 |
|---|---|---|
| expected_amount | decimal string | 完整锁定报价,已包含加价和向上取整。 |
| received_amount / confirmed_amount | decimal strings | 有效的已检测资金 / 满足此方式确认或最终性策略的资金。 |
| unconfirmed_amount | decimal string | max(received - confirmed, 0)。不是需要额外发送的金额。 |
| minimum_payment_amount | decimal string | 扣除容差后的接受阈值,可能低于完整报价。 |
| remaining_amount | decimal string | max(minimum accepted - received, 0)。达到接受阈值还需的金额,不是确认进度。 |
| remaining_to_full_amount | decimal string | max(full quote - received, 0),忽略容差。 |
| overpaid_amount | decimal string | max(received - full quote, 0)。不代表授权自动退款。 |
| Every amount's *_atomic companion | integer string | 精确的最小单位表示。请使用十进制或整数库;金额绝不要使用浮点数或 JavaScript Number。 |
确认策略:methods[].acceptance
| 字段 | 类型 | 含义 |
|---|---|---|
| finality_mode / required_confirmations | string / integer | 锁定的确认数或最终确认策略。零确认是商户策略明确允许的,不代表通用网络最终性。 |
| observed_confirmations | integer | null | 有效观察记录中的最低值,不只是最新转账。Lightning 或无有效记录时为 null。 |
| underpayment_tolerance_percent | decimal string | 方式的实际容差。即使账单链上容差不为零,Lightning 仍使用零容差。 |
汇率:methods[].quote 与 market_rate_at_event
| 字段 | 类型 | 含义 |
|---|---|---|
| quote.effective_rate / units / currency / symbol | strings | 包含加价的锁定 asset_per_invoice_currency 汇率;币种和符号明确说明方向。 |
| quote.exchange_rate_spread_percent / quote_expires_at | decimal string / timestamp | 锁定的加价和报价截止时间。绝不会替换为当前商店设置。 |
| quote.reference_rate / unrounded_payment_amount / rounding_adjustment | decimal string | null | 加价前参考值、取整前付款金额,以及以资产单位计的向上调整额。 |
| quote.pricing_provider / asset_provider / pricing_fetched_at / asset_fetched_at | string or timestamp | null | 原始币种与资产价格来源/时间。不含 API 密钥或服务商凭据。 |
| quote.provenance_available / rounding | boolean / string | 未保存来源快照的旧账单为 false;采用向上取整。 |
| market_rate_at_event | object | null | 此事件创建时的参考缓存市场快照。缺失数据保持 null;绝不会更改账单金额,也不会为等待网络获取而延迟。 |
| market_rate_at_event.rate / units / currency / symbol | strings | 加价前市场汇率,方向与 quote 相同并明确标示。 |
| market_rate_at_event.observed_at / as_of / pricing_fetched_at / asset_fetched_at | timestamps | 事件快照时间 / 两个来源中较早的时间 / 每个来源时间。 |
| market_rate_at_event.pricing_provider / asset_provider | strings | 缓存的币种和资产来源,包括配置的自定义代币价格。 |
| market_rate_at_event.stale / is_fixed / uses_reference_proxy / reference_currency | booleans / string | 缓存是否过时、代币价格是否固定、USD 参考是否使用稳定币代理。参考币种为 USD。过时数据仅供参考,绝不是新报价。 |
转账记录:methods[].payments[] 与 GET …/payments
| 字段 | 类型 | 含义 |
|---|---|---|
| payment_id / payment_method_id | UUID | 观察记录标识 / 上级意图标识。用 payment_id 对历史记录去重。 |
| transaction_id / payment_hash / event_index | string | null / integer | 链上哈希及转账/日志/输出索引,或 Lightning 哈希。Lightning 没有交易或浏览器链接。 |
| payment_rail / chain_slug / network / asset_id / asset_key / caip_asset_id / symbol / asset_decimals | strings / UUID / integer | 与所属方式相同的资产和网络标识符。 |
| amount / amount_atomic | decimal / integer strings | 本次转账的精确值,绝不是法币换算值。 |
| status / counts_towards_received | string / boolean | detected、confirming 和 final 计入;reorged、replaced 和 invalid 不计入。保留失效历史用于对账。 |
| confirmations / block_height | integer | null | 观察记录的区块数据;Lightning 的 confirmations 为 null。 |
| observed_at / chain_time / finalized_at | timestamp | null | 本地首次发现时间、可用时的可信链上时间,以及达到策略最终性时的时间。 |
| explorer_name / explorer_url | string | null | 经过验证的公共区块浏览器链接,仅适用于支持的网络。 |
商户版 5.13.3 会将经过验证的内部 Gas 补充转账从客户付款总额、payment_info、账单付款 API、退款限额及 payment.received 事件中排除。链上和资金管理记录仍可用于钱包记账。普通转账和真正的多付仍会计入。现有签名回调请求体绝不会重写。如果历史结算依赖内部补充而非客户资金,对账会发出 reason_code 为 internal_gas_funding_excluded 的 invoice.invalid;请审核,不要再次履单。
商户版 4.1.0 新增 payload_version 2,不移动或更改原有九个字段。此前排队的事件保留原始请求体,可能没有 payload_version。event_id、event_type 和项目/商店 ID 现在位于签名请求体内;传输事件/发送请求头仍未签名。
payment_info 描述已观察到的付款,不是结账提供的所有选项。检测前 active_payment_method_id 为 null,methods 为空。即使活动方式变为 null,重组/失效记录仍可能保留在 methods 中。绝不要相加不同资产或网络的金额。
所有金额、最小单位整数、汇率和百分比都是字符串。received_amount 包含等待确认的有效资金;confirmed_amount 满足该方式的最终性策略。remaining_amount 是 max(minimum_payment_amount 减 received_amount, 0);remaining_to_full_amount 是 max(expected_amount 减 received_amount, 0)。例如:应付 100 USDC,已收 99,容差 1%,则 remaining_amount 为 0、remaining_to_full_amount 为 1。仍然需要最终确认。
quote 是锁定的账单计算结果:每一单位账单币种对应的资产单位数。先加价,再向上取整。使用 expected_amount_atomic 精确比较付款;仅凭显示汇率可能无法重现向上取整。未保存来源出处的旧账单会将来源/参考/取整字段显示为 null,provenance_available 为 false,绝不会把今日数据当作历史报价。
market_rate_at_event 是加价前的参考缓存数据,在事件创建时冻结。它包含来源时间、过时和参考代理标记;没有可用缓存币对时为 null。通知不会被实时汇率请求阻塞,此市场观察值也绝不会改变应付金额。固定自定义代币标记为 is_fixed;DEX 代币使用其项目专属来源,不使用同名代币。
顶层 paid_chain、paid_asset、paid_payment_method_id 和 settlement_exchange_rate(4.1.2+)标识结算后经过证实的最终采用方式,不是所选结账选项,也不是不同方式的合计。结算前、失效后、旧版未保存快照的结算,或无符合策略最终性资金的手动接受,汇总字段均为 null。符号只是显示标签:请按方式 ID 确认准确的网络/资产/合约身份。
商户版 5.0.1 新增以 paid_asset 为单位的精确十进制字符串 paid_asset_amount 和 paid_asset_amount_received;payload_version 仍为 2。paid_asset_amount 是包含加价和向上取整的完整锁定报价,绝不是容差阈值或剩余余额。paid_asset_amount_received 是结算时最终方式的有效收款总额,包含等待确认的资金及接受的少付或多付。例如报价 100 USDC,实收 99 并按容差接受,则为 100 和 99,而非 99 和 99。两者随结算快照冻结;各事件的收款使用 payment_info.methods[].amounts,当前记录使用付款 API。无合格快照及 5.0.1 之前的快照均为 null;旧排队事件的请求体不变。记账时绝不要将精确十进制字符串转为浮点数。
settlement_exchange_rate 是结算时保存的加价前缓存市场观察值,不是锁定账单报价或已执行的交易所交易。结构与 market_rate_at_event 相同;EUR/USDC 的 asset_per_invoice_currency 为 1.17,表示 1 EUR = 1.17 USDC。来源时间戳及过时/固定/代理标记说明质量。缺少币对会使汇率为 null,但已证实的方式仍有 paid_* 字段。它绝不会改变应付金额,也不等待实时服务商调用。相同方式的后续付款、重试和重发均无法替换已保存快照,包括已保存的 null 汇率。真正的重新结算或结算方式变更会生成新快照;observed_at 标识此次保存,而 settled_at 可能保留首次结算时间。旧事件请求体保持不变。
最多包含八种已观察方式和每种方式最新五条付款观察记录,并提供总数和截断标记。载荷预算可能进一步缩小数组。一条付款观察记录是一次转账/日志/UTXO 输出,不一定对应唯一交易哈希。使用 GET /v1/projects/YOUR_PROJECT_ID/invoices/{invoice_id}/payments,搭配 payment_method_id、limit 和 offset 获取完整当前历史。账单详情保留每种报价方式及其 quote_details。API 链接需要你配置的主机和凭据,绝不要将 Bearer 令牌转发到任意回调提供的 URL。
Lightning 使用 payment_hash 而非 transaction_id;收款地址、浏览器和观察到的确认数为 null。精确 BTC 金额使用 11 位小数(毫聪),实际容差为零。不包含 BOLT11 支付原像、钱包密钥、签名密钥或服务商凭据。客户/元数据字段只能出现在商户响应及签名回调中,绝不能用于公共结账;不要把凭据放入元数据。
安全接收
- 解析前先用匹配密钥验证完全原始的请求体。“商店 → IPN”提供 IPN 密钥,也用于自定义 ipn_url 发送。每个“商店 → Webhook”端点都有独立密钥。它们都不是 API 令牌;轮换其中一个不会轮换其他密钥。
- 检查带签名的时间戳(SDK 默认允许前后五分钟),存在签名项目/商店 ID 时,将其与接收端配置匹配。返回 HTTP 2xx 前先可靠入队。逐事件处理时,v2 event_id 已签名;仅请求头 ID 无法防重放,因为请求头未签名。订单状态收件箱应对 invoice_id 和 sequence 去重,并比较原始账单状态字段,而非整个 v2 请求体;不同事件类型/ID 可能共享同一修订。
- 在后台任务中从已配置的 API 源站获取当前账单,不要使用任意回调链接。匹配已保存订单、项目/商店、金额和币种,要求当前为 settled 状态,并应用你的手动接受和异常策略。锁定订单并在数据库事务中仅履行一次,此保护独立于事件去重。
- 绝不要用较旧的 sequence 覆盖较新的。不同事件可共享修订;不要将修订级去重与仅允许 invoice.settled 的筛选结合。重新开启/对账可能改变状态;更新顺序由 sequence 决定,而非固定状态排序。记录冲正以供审核,不要再次履单。
接收端示例: PHP · Python · Node.js / TypeScript.
签名验证与发送规则
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWhollySignature(rawBody, header, signingSecret, toleranceSeconds = 300) {
const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
if (!match) return false;
const timestamp = Number(match[1]);
if (!Number.isSafeInteger(timestamp)) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > toleranceSeconds) return false;
// rawBody must be the exact request Buffer, before JSON parsing.
const expected = createHmac("sha256", signingSecret)
.update(String(timestamp))
.update(".")
.update(rawBody)
.digest();
const presented = Buffer.from(match[2], "hex");
return timingSafeEqual(expected, presented);
}| 发送规则 | 详情 |
|---|---|
| 请求头 | Wholly-Signature、Wholly-Event-Id 和 Wholly-Delivery-Id;Content-Type 为 application/json。 |
| 签名 | 对 <unix timestamp>.<exact raw body> 计算 HMAC-SHA256;请求头格式为 t=<timestamp>,v1=<64 lowercase hex>。 |
| 成功 | 任何 HTTP 2xx 响应。不会跟随重定向;非 2xx 响应均视为失败。 |
| 超时 | 连接超时 5 秒,请求总超时 10 秒。 |
| 重试计划 | 可重试失败最多尝试 8 次:立即一次,之后每次在上一次完成后等待 10 秒、1 分钟、5 分钟、15 分钟、1 小时、6 小时及 24 小时。IPN 自动重试;Webhook 自动重试可按端点禁用。 |
| 目标安全 | 仅公共 HTTPS。发送时重新验证并固定 DNS;拒绝本地、私有或保留目标。 |
| 事件保留 | 通知事件载荷和发送记录计划保留 90 天;保留详情按有上限的批次清除。 |
| 去重 | 按配置的项目范围持久化保存带签名的 invoice_id 和 sequence。Wholly-Event-Id 标识事件;Wholly-Delivery-Id 标识发送记录(重试复用,手动重发创建新记录)。这两个 ID 请求头均未签名。 |
| 事件命名 | 第 2 版在请求体中签署 event_id 和 event_type。旧版排队事件两者都没有。不同事件类型可共享账单 sequence;请按修订号核对账单状态,或按签名 event_id 对单个事件去重。 |
| 密钥轮换 | 轮换没有重叠期或版本请求头,会立即改变排队、重试和手动发送的签名。 |
| 暂停的发送 | 处理额度不足会暂停 IPN/Webhook,包括重试。入账付款继续;充值后,排队通知会在载荷保留期内恢复发送。 |
AI 助手 · MCP
将助手连接到商户安装。
商户版 5.0.0 在配置的 API 域名上包含可自行启用的 MCP 服务器。它在你的安装中运行,不经过共享的 Wholly Crypto 中继。
- 打开“设置 → API 访问”。创建专用凭据,只分配助手需要的项目,并从只读权限开始。运营商托管账户需要运营商先启用安装的 MCP 服务;你只管理自己的凭据和授权。
- 在“AI 连接 · MCP”中启用 MCP,选择凭据并保存其 MCP 访问权限。现有凭据须明确启用后才有 MCP 访问权。
- 将 MCP 服务器 URL 复制到客户端的远程 HTTP 服务器设置。使用 OAuth 时,登录商户控制台,检查客户端名称和返回地址,选择凭据并批准。现有 Basic Auth 和 TOTP 保护仍然适用。
- 创建账单还需要读写凭据、MCP 策略中的“读取 + 创建账单”、mcp:invoice:create OAuth 范围和明确批准。批准后再添加到凭据中的项目,不会自动授予现有 OAuth 连接。
{
"mcpServers": {
"whollycrypto": {
"url": "https://api.example.com/mcp"
}
}
}| 工具 | 访问权限 | 用途 |
|---|---|---|
| list_projects | 读取 | 分配给连接的已启用项目;limit/offset 分页。 |
| list_stores | 读取 | project_id 内的商店、ID 和启用状态;limit/offset 分页。 |
| list_payment_methods | 读取 | project_id + store_id 配置的链、代币和 Lightning 支付方式。 |
| get_wallet_balances | 读取 | 收款地址与缓存余额,包含时效和可用性字段;绝不包含钱包机密。 |
| list_invoices | 读取 | 项目账单,可按商店、状态或搜索筛选;limit/offset 分页。 |
| get_invoice | 读取 | 通过 project_id + invoice_id 获取完整账单详情和结账链接。 |
| get_delivery_history | 读取 | 商店 IPN/Webhook 状态、尝试次数和 HTTP 结果。可选 invoice_id/kind 筛选;不含机密或回调请求体。 |
| convert_amount | 读取 | 使用 from、to 和十进制字符串 amount 的缓存参考换算;不是账单报价。 |
| create_invoice | 明确写入权限 | project_id、store_id、idempotency_key 和 invoice(现有账单创建请求体)。invoice.payment_methods 筛选已启用商店方式;5.4.0+ 忽略未启用/未接受的选项,无匹配时使用商店默认值。仅指定链会选中所有已启用且接受的资产。5.3.0 起支持限定链的 asset_tickers。返回正常账单响应。 |
协议、OAuth 与安全
通过 HTTPS 使用 Streamable HTTP。协商一个公布的协议版本,并在后续 POST 中包含 MCP-Protocol-Version。发送 Content-Type: application/json 和 Accept: application/json, text/event-stream。响应为有限 JSON;重新连接不需要 MCP 会话 ID。
OAuth 使用短期访问令牌(15 分钟)、一次性 S256 PKCE 代码(5 分钟)和轮换刷新令牌(连接有效期 30 天)。复用已使用的刷新令牌会撤销连接。过期、凭据轮换、策略变更或规范 API 域名变更后需重新连接。
仅在启用 MCP 时公开 OAuth 发现。resource 参数必须等于发现返回的规范 URL,包括 /mcp。支持动态注册;不支持远程客户端 ID 元数据文档或客户端密钥。
支持自定义 Authorization 请求头的客户端,也可将启用 MCP 的商户 API 令牌用作 Bearer。它保留独立 REST 权限;仅需 MCP 的连接优先使用 OAuth。绝不要将凭据粘贴到聊天、URL、工具参数或版本控制中。
MCP 共用凭据每分钟 REST 配额和准确来源 IP 限制,另适用 API 主机 IP 限制。OAuth 不会绕过白名单。远程 AI 客户端请允许其文档公布的出口 IP,或有意识地关闭此限制。MCP/OAuth 路由不应启用网页安全验证或缓存。
HTTP 错误:401 需要身份验证,403 拒绝源站/IP/权限,404 表示 MCP 已关闭或主机错误,405 表示应使用 POST,413 表示超过 32 KiB 请求体限制,429 包含 Retry-After。JSON-RPC 错误使用 error.code;工具级失败即使 HTTP 200 也使用 result.isError=true。成功结果包含 content 和 structuredContent。
列表默认 25 行,最多 100 行;offset 上限为 1000000。工具响应上限为 2 MiB。过期授权、授权请求和速率桶自动清理;设置中最多显示 100 个活动 OAuth 连接。
无法通过 MCP 操作已禁用的项目或商店。连接可以列出商店启用状态,但读取支付方式、发送历史或创建账单需要商店已启用。普通控制台项目用户不能管理 MCP。
新账单使用新的 idempotency_key;超时后使用同一凭据、密钥和完全一致的 invoice 对象重试。十进制金额、加价、容差、确认数和结账外观遵循 REST 账单契约。MCP 绝不绕过商户付款或额度策略。
初始工具无法显示私钥/助记词、发送或归集资金、退款、重发回调、更改支付方式、编辑账户/域名或管理计费。将账单说明、客户字段和元数据视为不可信数据,而非代理指令。已连接 AI 服务商会收到你授权其读取的数据。
| 方法 | 路径 | 契约 |
|---|---|---|
| POST | /mcp | 需认证的 JSON-RPC:initialize、ping、tools/list、tools/call。通知请求返回 202;拒绝批量请求。 |
| GET / DELETE | /mcp | 认证后返回 405:有限 JSON 响应,无独立 SSE 流,也无服务器端 MCP 会话。 |
| GET | /.well-known/oauth-protected-resource/mcp | 规范资源 URL 与授权服务器发现;也可通过 /.well-known/oauth-protected-resource 获取。 |
| GET | /.well-known/oauth-authorization-server | OAuth 端点、authorization_code/refresh_token、S256 PKCE 和支持的范围。 |
| POST | /mcp/oauth/register | 公共客户端注册:client_name 和准确的 redirect_uris。仅 HTTPS 或环回 HTTP。不使用客户端密钥或远程元数据获取。 |
| GET | /mcp/oauth/authorize | client_id、redirect_uri、response_type=code、resource、code_challenge、code_challenge_method=S256、可选 scope/state;重定向到控制台批准。 |
| POST | /mcp/oauth/token | 表单编码的 authorization_code + code + code_verifier + redirect_uri,或 refresh_token + refresh_token。始终包含 client_id 和 resource。 |
| POST | /mcp/oauth/revoke | 表单编码的 client_id 和 token。撤销匹配的访问/刷新令牌连接。 |
直接工具请求示例
先通过 MCP 客户端初始化并协商协议。这里展示的是后续请求。
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/mcp" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'MCP-Protocol-Version: 2025-11-25' \
--header 'Accept: application/json, text/event-stream' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}`;
const response = await fetch("https://api.example.com/mcp", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"MCP-Protocol-Version": "2025-11-25",
"Accept": "application/json, text/event-stream",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}
JSON;
$ch = curl_init("https://api.example.com/mcp");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "MCP-Protocol-Version: 2025-11-25", "Accept: application/json, text/event-stream", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"MCP-Protocol-Version": "2025-11-25",
"Accept": "application/json, text/event-stream",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}""".encode("utf-8")
request = Request("https://api.example.com/mcp",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))运营商 API
使用独立且限定范围的服务器端密钥创建托管商户。
托管多个业务并通过 api.example.com/v1/operator 自动设置。7.4.0 起仅在运营商模式可用。普通商户 API 保持不变。
- 打开“运营商 → 设置 → 运营商 API”并启用(默认关闭)。创建独立凭据,仅授予所需权限和托管商户范围。
- 将 wc_operator_ 密钥保留在服务器上。使用 API 主机名,不要使用运营商面板主机名或商户密钥。
- 每次运营商 POST 前,先持久化保存 Idempotency-Key 和完全一致的请求体。结果不确定时读取账户核对;绝不要仅为重试而更换密钥。
- 创建商户时使用 onboarding: direct 和密码,或 onboarding: invitation 且不带密码。随后创建项目/商店,并签发限定项目的商户密钥用于结账集成。
| 范围 | 访问权限 |
|---|---|
| merchants.read / merchants.write | 列出/读取及创建/更新托管商户。 |
| users.read / users.write / users.security | 读取/创建/更新用户;另行更改密码或撤销会话。绝不会创建运营商管理员。 |
| invitations.read / invitations.write | 列出/读取、创建、替换和撤销一次性邀请/重置链接。新用户还需要 users.write;重置还需要 users.security。 |
| credits.read / credits.write / fees.write | 读取余额/账本;赠送或更正本地额度;设置未来费率。非零初始额度需要 credits.write。 |
| topups.read / topups.write | 读取或创建托管商户的额度充值结账请求。任何 API 操作都不能将其标记为已付款。 |
| projects.read / projects.write / reports.read | 配置商户项目、商店、外观和支付设置;读取账单、钱包余额和财务报表。 |
| merchant_credentials.read / merchant_credentials.write | 管理普通的限定范围商户密钥。权限强大:这些密钥签发后独立生效。 |
| events.read / webhooks.write / audit.read / health.read | 读取生命周期历史;配置签名生命周期回调;读取审计/功能/节点健康。 |
入驻、额度、权限与安全重试
| 主题 | 规则 |
|---|---|
| 凭据 | 可选有效期及准确 IPv4/IPv6 白名单;默认每分钟 60 请求,可配置 1–600。每次请求都会检查签发管理员和当前范围。HTTP 429 包含 Retry-After。 |
| 隔离 | 密钥只能访问分配的托管商户。创建商户和查看安装级报表需要所有商户权限。运营商自营业务不包含在内。 |
| 首次登录 | 直接创建的账户需确认主机管理员可访问其钱包密钥。require_password_change 会要求首次登录改密。接受邀请需要明确确认托管责任,再正常登录。Basic Auth 和现有 TOTP 仍有效。 |
| 邀请 | 新用户链接有效期 48 小时,密码重置链接 1 小时。令牌仅限一次使用。重新签发会撤销旧链接。SMTP 接受不保证收件箱送达;请检查 email_delivery。 |
| 安全重试 | 每个运营商 POST 都需要 16–128 字符的键(字母、数字、-、_ 或 .)。同一键与完全相同的 URL/请求体返回已保存结果。字节不同则返回 409。重放不返回机密/链接;需要时请通过新的明确操作轮换或重新签发。 |
| 不确定结果 | operator_request_in_progress 表示操作正在运行,或在记录回执前被中断。检查资源和审计,不要盲目提交新键。完成回执在 30 天后压缩;旧键仍不能再次执行。 |
| 额度与费用 | 使用十进制字符串,最多六位小数。starting_credit 是一次性本地赠送。调整需要带正负号的金额、备注和 request_id,以及 HTTP 重试键。fee_bps=100 表示 1%;更改只影响未来账单。赠送不会给安装自身的预付余额充值。 |
| 暂停 | enabled=false 禁用托管账户并撤销控制台会话。payments_paused=true 停止创建新账单。现有付款监控继续。项目/商店创建和自动化仍遵守安装的额度策略。 |
| 未提供的功能 | 不提供钱包机密、签名、发送、退款、永久删除、TOTP 重置、域名更改或服务器级配置。普通账单请求仍使用商户密钥和商户 API。 |
运营商生命周期 Webhook
| 事件 | 数据 |
|---|---|
| merchant.created / merchant.updated | merchant_id、enabled、payments_paused、fee_bps。 |
| user.created / user.updated | merchant_id、user_id、enabled。更新事件涵盖邮箱、启用状态和管理员角色变更。 |
| invitation.accepted / password_reset.completed | merchant_id、user_id、invitation_id。 |
| topup.settled / credit.balance_changed | merchant_id、ledger_id、kind、amount 和 balance。对账时读取商户额度币种或账本详情。 |
运营商生命周期事件与账单 IPN/商店 Webhook 分开。订阅属于创建它的运营商凭据,每个键最多 10 个端点。仅将未来匹配事件入队;保留历史请用 GET /events 获取。
请求体包含 event_id、event_type、merchant_id、occurred_at 和 data。用端点一次性显示的 signing_secret 对原始请求体验证 Wholly-Signature:HMAC-SHA256(secret, timestamp + '.' + raw_body),请求头为 t=...,v1=....。限制时间戳仅允许较短偏差。
使用 SDK 通用签名验证器,不要使用账单通知解析器。然后验证 merchant_id 和 event_type,在事务中持久化并对 event_id 去重,只有可靠接收后才返回 2xx。Wholly-Event-Id 必须与签名请求体一致。不要将未签名请求头作为业务数据。
发送采用至少一次语义,可能乱序,最多尝试 8 次。读取当前资源进行核对;occurred_at 不是单调递增序列。发送前重新检查范围及启用/过期设置。禁用订阅会暂停已排队任务,但禁用期间不排入新事件。
事件和发送历史保留 30 天。安装自动化策略可暂停发送。GET /webhooks/{id}/deliveries 显示结果和不可变载荷;公共 API 不会强制发送过期记录。
{
"event_id": "55555555-5555-4555-8555-555555555555",
"event_type": "merchant.created",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"occurred_at": "2026-10-01T12:00:00Z",
"data": {
"merchant_id": "11111111-1111-4111-8111-111111111111",
"enabled": true,
"payments_paused": false,
"fee_bps": 300
}
}错误与限制
以可预测的方式处理验证、配额和重试。
检查 HTTP 状态和 Content-Type 后再解析响应。遇到 429,至少等待 Retry-After 指定时长后再重试。
| 限制 | 详情 |
|---|---|
| 请求速率 | 每个凭据的配额:默认每个 UTC 分钟 120 次请求,可在“设置 → API”中配置为 1–6000。所有经认证 v1 读写,包括幂等重试和认证后的授权/验证失败,跨域名、项目和进程共用此额度。无效凭据、控制台路由和公共结账不消耗配额。 |
| 速率限制请求头 | 经认证的 v1 响应包含 X-RateLimit-Limit、X-RateLimit-Remaining 和 X-RateLimit-Reset(下一 UTC 分钟边界的 Unix 秒)。超额请求返回 JSON 429 rate_limit_exceeded 和整数秒 Retry-After。至少等待这么久,并加入随机重试延迟。固定窗口允许分钟交界处突发请求,不保证每秒速率。 |
| 商户请求体 | 应用路由器最多允许 32 KiB。边缘层可能在生成 JSON 错误封装前就拒绝超大请求。 |
| 账单列表 | limit 默认 50,可为 1–100;offset 可为 0–1,000,000。搜索最多 100 字符。结果按最新优先排序,并包含 total/has_more 元数据。 |
| 商店方式 | 每个商店最多选择 64 项资产,足以涵盖全部 30 条原生链及有上限的已验证代币目录。创建账单仍受项目策略、扫描器能力和已就绪且已备份的链钱包约束。 |
| 代币发现 | 候选 limit 默认 50,可为 1–100。发现结果只有链上验证成功后才成为支付资产。 |
| 已注册项目代币 | 每个项目最多 20 项持久代币资产。已注册资产可复用,不再占用名额。 |
| 幂等性 | 创建账单必需。1–128 个不含空格的可见 ASCII 字符;键在每个商店内唯一,重放须使用原凭据和完全相同的原始请求体。 |
| 元数据 | 仅 JSON 对象,编码后最多 4,096 字节,嵌套最多五层。 |
| 回调 | 公共 HTTPS URL,最多 2,048 字节。通知请求体上限 256 KiB;保留的账单事件载荷上限 64 KiB,付款历史有数量限制。 |
| 结账素材 | 二维码 SVG 响应为 private 和 no-store,因为少付会改变精确剩余金额。带版本的 PNG 标志公开缓存一年且不可变。 |
| API 边缘层 | 受管理的 API 上游请求读取超时为 30 秒。调用端应设置小于任务时间预算的明确超时。 |
| 非 JSON 失败 | 错误的 UUID/查询提取、错误方法和 32 KiB 长度限制可能返回框架文本或空响应。未知 /v1 路径目前返回 404 控制台 HTML;解析前验证状态和 Content-Type。 |
错误参考
| HTTP | 错误代码 | 含义 |
|---|---|---|
| 400 | invalid_reconciliation_action | 异常状态、原因、搜索或历史页筛选无效。 |
| 500 | reconciliation_unavailable | 无法加载异常队列或证据。请退避后重试读取。 |
| 402 | billing_required | 每张新账单都需要经验证的已配对额度账户和有效授权。预付额度不足不会阻止创建或入账付款,而会暂停 IPN、Webhook 和归集,费用仍继续累计。账户暂停、计费验证过期/无效、额度服务不可达或账单法币基准未获授权时,仍会阻止创建。费用按原始账单法币金额计算,不按实收加密货币、加价、多付或网络费计算。该金额和独立换算在创建结账前注册。服务故障期间仍监控现有账单并允许获取账单。充值后,排队通知在正常载荷保留期内恢复,已启用归集规则也恢复。检查“设置 → 费用”,并用同一 Idempotency-Key 重试失败的创建。 |
| 400 | invalid_json | JSON 格式错误、未知字段,或请求体与文档不符。 |
| 400 | idempotency_key_required | 创建账单时缺少 Idempotency-Key。 |
| 400 | invalid_idempotency_key | 键为空、超过 128 字节、非 ASCII、含空白或控制字节。 |
| 400 | invalid_payment_request | 验证字段或所选活动方式失败。读取 error.message 和 error.details.payment_methods(PaymentMethodIssue[])了解准确阻塞原因。SDK 2.4.0+ 提供安全且可操作的异常摘要和问题辅助方法;旧 PHP SDK 提供 getApiMessage()。 |
| 400 | invalid_invoice_status | 列表状态不在文档列出的六种账单状态内。 |
| 400 | invalid_callback_url | 有效 IPN 目标未通过 HTTPS、公共地址、DNS 或 SSRF 验证。 |
| 400 | invalid_wallet_request | 钱包/地址准备输入无效。 |
| 400 | invalid_token_asset | 代币链、候选查询、CoinGecko 身份、目录元数据或合约/mint 输入无效。 |
| 401 | authentication_required | Bearer 令牌缺失、格式错误、被禁用、已轮换或未知。 |
| 403 | source_ip_denied | 凭据 IP 限制未包含请求的准确公网来源地址。 |
| 403 | source_ip_not_allowed | 主机名的来源 IP 限制排除此客户端。管理员可在“设置 → 系统”管理在线主机白名单;它与凭据 IP 限制叠加适用。 |
| 503 | source_access_unavailable | 主机名访问验证暂时不可用。稍后重试;验证失败时保持限制。 |
| 403 / 409 / 500 | merchant_api_access_denied | 授权失败:权限/项目范围可能返回 403,禁用项目/商店可能返回 409,授权后端失败可能返回 500。运营商收款钱包仅供运营商面板使用,商户 API 凭据或 MCP 均不可访问,即使有旧的明确项目授权。 |
| 403 | project_access_denied | 创建时的事务内复查发现凭据已无权访问项目。 |
| 404 | invoice_not_found | 授权项目中不存在该公共 ID 的账单,或结账无法公开它。 |
| 404 | payment_resource_not_found | 准备账单所需的项目、商店、资产或钱包已不存在。 |
| 404 | token_candidate_not_found | 项目不可用,或当前匹配的发现目录中已无该代币。 |
| 409 | idempotency_conflict | 商店范围的键已存在,但凭据或原始请求字节不一致。 |
| 409 | store_unavailable | 项目/商店已禁用或不可用。 |
| 409 | no_ready_payment_methods | 没有就绪的商店方式。读取 error.message 和 error.details.payment_methods 中的 chain_slug、asset_ticker 和 reason_code。钱包备份/启用、已安装适配器和定价须有效。自 6.0.6 起,扫描器冷却、失败或过时的健康检查、缺少服务商法定数量都不阻止创建。 |
| 409 | payment_method_unavailable | 选定方式在创建时的原子复查中变为不可用。 |
| 409 | store_payment_method_not_selected | 请求为商店当前未选择的资产覆盖确认策略。 |
| 409 | wallet_unavailable | 支付钱包在创建时的原子复查中变为不可用。 |
| 409 | ipn_secret_required | 存在有效 IPN URL,但商店没有 IPN 签名密钥。 |
| 409 | payment_resource_not_ready | 所需支付资产或钱包已禁用、未备份、等待共享账户激活证明、已耗尽或因其他原因未就绪。 |
| 409 | account_activation_unverified | 无法通过配置数量的健康主网端点(默认 2,可选 1)证明 XRP Ledger 或 Stellar 账户已激活;请为准确账户注资后重试验证。 |
| 400 | invalid_monero_wallet_rpc | HTTPS 端点、准确主网主地址、标签或完整 Digest/Basic/header 认证输入无效。 |
| 404 | monero_wallet_rpc_not_found | 不存在项目范围的 Monero wallet-RPC 绑定。 |
| 409 | monero_wallet_rpc_not_ready | Monero 资产、双守护进程法定数量、不可变绑定或明确的备份/只读确认尚未就绪。 |
| 409 | monero_wallet_rpc_unavailable | 创建账单需要启用、经过验证和确认的项目 Monero wallet-RPC 绑定,并具备有效服务器端凭据。 |
| 503 | lightning_unavailable | 商店唯一就绪方式为 Lightning,但无法验证钱包或报价。请用同一幂等键重试。若还有其他就绪链上方式,则省略不可用的 Lightning 方式。 |
| 422 | monero_wallet_rpc_verification_failed | 准确钱包、HTTPS 固定、同步、主网守护进程法定数量或网关方法拒绝证明失败。 |
| 503 | monero_wallet_rpc_failed | 外部只读 wallet-RPC 无法安全创建并重新读取账单子地址;不会编造备用地址。 |
| 409 | token_chain_not_ready | 原生链资产已禁用,验证期间发现映射改变,或项目已达到当前 20 个注册代币资产上限。 |
| 503 | dex_price_unavailable | DEX 服务商不可用、繁忙、限流、响应过时或数据格式错误。一分钟后重试;固定定价仍可用。 |
| 422 | invalid_dex_price | 价格模式组合无效,或所选交易池无法为准确合约提供符合条件的价格。请选择其他池或固定美元定价。 |
| 422 | token_verification_failed | 所有符合条件的节点都未通过链身份、合约代码、小数位、余额查询或 mint 验证。 |
| 422 | invalid_store_confirmation_policy | 商店覆盖设置不适用于此最终性模式、超出返回的链特定范围,或请求了不支持的零确认接受。 |
| 409 | invoice_not_payable | 结账账单已进入终态或付款截止时间已过。 |
| 409 | invoice_payment_method_locked | 有效付款已选定另一项资产;请继续使用 active_payment_method_id。 |
| 409 | payment_method_not_payable | 所选方式已完成,或不再接受另一笔付款。 |
| 422 | payment_qr_unavailable | 结账支付请求过大,无法编码为 SVG 二维码。 |
| 503 | payment_rates_unavailable | 所有就绪支付方式都没有新鲜可信报价。 |
| 500 | authentication_unavailable | Bearer 认证无法安全读取或验证保存的凭据。 |
| 429 | rate_limit_exceeded | 此凭据已用尽当前 UTC 分钟的配额。至少等待 Retry-After 秒;创建账单请用同一幂等键重试。 |
| 500 | database_error / internal_error | 临时服务器端故障;使用同一幂等键安全重试。 |
API 概览
选择端点查看字段、示例和响应。
账单
POST创建账单/v1/projects/{project_id}/stores/{store_id}/invoicesGET列出账单/v1/projects/{project_id}/invoicesGET获取账单/v1/projects/{project_id}/invoices/{invoice_id}GET列出账单付款/v1/projects/{project_id}/invoices/{invoice_id}/payments支付方式
GET列出项目支付资产/v1/projects/{project_id}/payment-assetsPUT更新项目资产策略/v1/projects/{project_id}/payment-assets/{asset_id}GET浏览支付代币候选项/v1/projects/{project_id}/payment-token-candidatesPOST验证并注册代币/v1/projects/{project_id}/payment-token-assetsGET查找自定义代币 DEX 池/v1/projects/{project_id}/payment-token-dex-poolsPOST添加自定义代币或重新定价/v1/projects/{project_id}/payment-token-assets/customGET列出商店支付方式/v1/projects/{project_id}/stores/{store_id}/payment-assetsPUT替换商店支付方式/v1/projects/{project_id}/stores/{store_id}/payment-assetsPUT设置商店确认策略/v1/projects/{project_id}/stores/{store_id}/payment-assets/{asset_id}/confirmation-policy钱包
GET列出项目钱包与余额/v1/projects/{project_id}/wallets对账
GET列出付款异常/v1/projects/{project_id}/reconciliationGET读取对账证据/v1/projects/{project_id}/reconciliation/{invoice_id}运营商 API
GET功能/v1/operator/capabilitiesGET健康状态/v1/operator/healthGET列出商户/v1/operator/merchantsPOST创建商户/v1/operator/merchantsGET获取商户/v1/operator/merchants/{merchant_id}POST更新商户/v1/operator/merchants/{merchant_id}GET列出用户/v1/operator/merchants/{merchant_id}/usersPOST创建用户/v1/operator/merchants/{merchant_id}/usersGET获取用户/v1/operator/merchants/{merchant_id}/users/{user_id}POST更新用户/v1/operator/merchants/{merchant_id}/users/{user_id}POST设置用户密码/v1/operator/merchants/{merchant_id}/users/{user_id}/passwordPOST撤销用户会话/v1/operator/merchants/{merchant_id}/users/{user_id}/revoke-sessionsGET列出邀请/v1/operator/merchants/{merchant_id}/invitationsPOST创建邀请/v1/operator/merchants/{merchant_id}/invitationsGET获取邀请/v1/operator/invitations/{invitation_id}POST重新发送邀请/v1/operator/invitations/{invitation_id}/resendPOST撤销邀请/v1/operator/invitations/{invitation_id}/revokeGET获取额度/v1/operator/merchants/{merchant_id}/creditsGET列出额度账本/v1/operator/merchants/{merchant_id}/credits/ledgerPOST调整额度/v1/operator/merchants/{merchant_id}/credits/adjustmentsGET列出充值/v1/operator/merchants/{merchant_id}/topupsPOST创建充值/v1/operator/merchants/{merchant_id}/topupsGET获取充值/v1/operator/merchants/{merchant_id}/topups/{topup_id}GET报表/v1/operator/reportsGET列出审计记录/v1/operator/auditGET列出事件/v1/operator/eventsGET列出 Webhook/v1/operator/webhooksPOST创建 Webhook/v1/operator/webhooksPOST更新 Webhook/v1/operator/webhooks/{webhook_id}POST轮换 Webhook 密钥/v1/operator/webhooks/{webhook_id}/rotateGET列出 Webhook 发送记录/v1/operator/webhooks/{webhook_id}/deliveriesGET列出项目/v1/operator/merchants/{merchant_id}/projectsPOST创建项目/v1/operator/merchants/{merchant_id}/projectsGET获取项目/v1/operator/merchants/{merchant_id}/projects/{project_id}POST更新项目/v1/operator/merchants/{merchant_id}/projects/{project_id}GET列出商店/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesPOST创建商店/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesGET获取商店/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}POST更新商店/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}GET获取商店外观/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearancePOST更新商店外观/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearanceGET列出商店支付资产/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsPOST更新商店支付资产/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsGET列出商店 Webhook/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksPOST创建商店 Webhook/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksPOST更新商店 Webhook/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooks/{webhook_id}GET列出账单/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoicesGET获取账单/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoices/{invoice_id}GET列出钱包/v1/operator/merchants/{merchant_id}/projects/{project_id}/walletsGET列出钱包地址/v1/operator/merchants/{merchant_id}/projects/{project_id}/wallets/{wallet_id}/addressesGET列出商户凭据/v1/operator/merchants/{merchant_id}/api-credentialsPOST创建商户凭据/v1/operator/merchants/{merchant_id}/api-credentialsPOST更新商户凭据/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}POST轮换商户凭据/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/rotatePOST撤销商户凭据/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/revokePOST检查邀请令牌/v1/onboarding/invitations/checkPOST接受邀请或密码重置/v1/onboarding/invitations/accept结账
GET结账页面外壳/GET托管结账页面/invoice/{invoice_id}GET可安全用于结账的账单数据/checkout-api/invoices/{invoice_id}GET商店结账预览/invoice/preview/{project_id}GET结账预览数据/checkout-api/previews/{project_id}GET商店结账图片/checkout-api/invoices/{invoice_id}/appearance-images/{kind}/{revision}/image.pngGET商店预览图片/checkout-api/previews/{project_id}/stores/{store_id}/appearance-images/{kind}/{revision}/image.pngGET带版本的预览标志/checkout-api/previews/{project_id}/logo/{revision}/image.pngGET支付二维码图片/checkout-api/invoices/{invoice_id}/payment-methods/{intent_id}/qr.svgGET带版本的结账标志/checkout-api/invoices/{invoice_id}/logo/{revision}/image.png服务
GETAPI 服务发现/GET服务健康状态/healthzGET功能/v1/operator/capabilities只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 health.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/capabilities" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/capabilities", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/capabilities");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/capabilities",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"api_version": "v1",
"operator_version": "7.4.0",
"scopes": [
"health.read"
],
"all_merchants": false,
"merchant_ids": [
"11111111-1111-4111-8111-111111111111"
],
"onboarding": [
"direct",
"invitation"
],
"write_methods": [
"POST"
],
"idempotency_required": true
}GET健康状态/v1/operator/health只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 health.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/health" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/health", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/health");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/health",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"version": "7.4.0",
"nodes": []
}GET列出商户/v1/operator/merchants只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 merchants.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POST创建商户/v1/operator/merchants读取 + 写入
原子地创建托管商户和首个管理员,可直接设置密码或通过邀请。
- 需要 merchants.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 需要全局商户权限。明确覆盖费率还需 fees.write;非零 starting_credit 需 credits.write。邀请入驻还需 invitations.write。不自动登录,不绕过 Basic Auth,重试不追补额度。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| name, email | string · required | 商户名称及全局唯一的首个管理员邮箱。 |
| onboarding | direct | invitation · required | direct 需要 password,不发送邀请邮件。invitation 不填写 password。 |
| password | string · direct only | 12–128 字符(最多 512 UTF-8 字节);绝不返回或发送邮件。临时密码请使用 require_password_change。 |
| require_password_change | boolean · default false | 首次登录需要新密码。每个直接创建账户都必须确认托管钱包的保管责任。 |
| currency | fiat code · optional | 预付账户币种;默认为地区币种,之后不能更改。 |
| fee_bps | integer · optional | 0–10000;100 表示 1%。省略时使用运营商默认值。需要 fees.write。 |
| starting_credit | decimal string · default 0 | 精确的一次性本地赠送。非零需要 credits.write。不会补充运营商安装余额。 |
| external_id | string · optional | 唯一集成参考,1–120 字符。 |
| default_timezone | IANA timezone · optional | 默认使用安装的地区时区。 |
| send_invitation_email | boolean · default false | 仅邀请使用。需要配置 SMTP;响应区分中继接受与账户创建。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 或 200 application/json
{
"merchant_id": "11111111-1111-4111-8111-111111111111",
"user_id": "22222222-2222-4222-8222-222222222222",
"merchant": {
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "0"
},
"onboarding": "direct",
"access_link": null,
"email_delivery": {
"status": "not_requested"
},
"custody_acceptance_required": true
}GET获取商户/v1/operator/merchants/{merchant_id}只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 merchants.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false,
"external_id": "customer-1042"
}POST更新商户/v1/operator/merchants/{merchant_id}读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 merchants.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| name, enabled, payments_paused, fee_bps, external_id | optional fields | 禁用会撤销会话。payments_paused 阻止新账单,不停止现有付款扫描。修改费率需要 fees.write,仅影响未来账单;账户币种不能改变。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"payments_paused": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"payments_paused": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"payments_paused": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"payments_paused": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false
}GET列出用户/v1/operator/merchants/{merchant_id}/users只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 users.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POST创建用户/v1/operator/merchants/{merchant_id}/users读取 + 写入
添加商户管理员或仅限所选项目的用户。
- 需要 users.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| email, display_name | strings · required | 邮箱在整个安装中唯一。 |
| onboarding, password, require_password_change, send_invitation_email | same as merchant creation | 创建邀请还需要 invitations.write。 |
| access_level | admin | projects · default admin | admin 仅为此商户的管理员,绝不是安装/运营商管理员。 |
| project_ids | UUID[] | 仅限商户拥有的项目。项目受限访问必须选择项目;绝不跨租户。 |
| default_timezone | IANA timezone · optional | 省略时使用地区默认值。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 或 200 application/json
{
"user": {
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
},
"user_id": "22222222-2222-4222-8222-222222222222",
"access_link": null,
"email_delivery": {
"status": "not_requested"
},
"merchant_id": "11111111-1111-4111-8111-111111111111"
}GET获取用户/v1/operator/merchants/{merchant_id}/users/{user_id}只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 users.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| user_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
}POST更新用户/v1/operator/merchants/{merchant_id}/users/{user_id}读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 users.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| user_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| email, display_name, enabled, access_level, project_ids, default_timezone | optional fields | 更新提供的字段;保留最后管理员保护。密码使用独立的 users.security 操作。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"display_name": "Store manager"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"display_name": "Store manager"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"display_name": "Store manager"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"display_name": "Store manager"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
}POST设置用户密码/v1/operator/merchants/{merchant_id}/users/{user_id}/password读取 + 写入
设置托管账户密码并撤销会话。保留现有 TOTP。
- 需要 users.security;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| user_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| password | string · required | 修改密码并撤销会话,保留 TOTP。需要 users.security。 |
| require_password_change | boolean · default true | 用户须在下次成功登录时设置自己的密码。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"user_id": "11111111-1111-4111-8111-111111111111",
"sessions_revoked": true,
"totp_preserved": true
}POST撤销用户会话/v1/operator/merchants/{merchant_id}/users/{user_id}/revoke-sessions读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 users.security;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| user_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"user_id": "11111111-1111-4111-8111-111111111111",
"sessions_revoked": true,
"totp_preserved": true
}GET列出邀请/v1/operator/merchants/{merchant_id}/invitations只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 invitations.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POST创建邀请/v1/operator/merchants/{merchant_id}/invitations读取 + 写入
创建或替换一次性邀请或密码重置链接。
- 需要 invitations.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| user_id, send_email | UUID, boolean | 为现有账户签发/替换一次性链接。已激活用户收到一小时有效的重置链接,且需要 users.security。 |
| new user fields | alternative to user_id | 使用 email、display_name、access_level 和 project_ids 创建受邀用户;需要 users.write。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 或 200 application/json
{
"user_id": "22222222-2222-4222-8222-222222222222",
"email": "admin@example.test",
"kind": "invitation",
"expires_at": "2026-10-03T12:00:00Z",
"url": "https://merchant.example.com/account-access.html#token=YOUR_PRIVATE_INVITATION_TOKEN"
}GET获取邀请/v1/operator/invitations/{invitation_id}只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 invitations.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| invitation_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"user_id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"kind": "invitation",
"status": "pending",
"created_at": "2026-10-01T12:00:00Z",
"expires_at": "2026-10-03T12:00:00Z"
}POST重新发送邀请/v1/operator/invitations/{invitation_id}/resend读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 invitations.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| invitation_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| send_email | boolean · default false | 替换旧令牌,绝不增加额度。新生成链接只返回一次。已激活账户需要 users.security。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"send_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"send_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"send_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"send_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"user_id": "22222222-2222-4222-8222-222222222222",
"email": "admin@example.test",
"kind": "invitation",
"expires_at": "2026-10-03T12:00:00Z",
"url": "https://merchant.example.com/account-access.html#token=YOUR_PRIVATE_INVITATION_TOKEN"
}POST撤销邀请/v1/operator/invitations/{invitation_id}/revoke读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 invitations.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| invitation_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"revoked": true,
"invitation_id": "44444444-4444-4444-8444-444444444444"
}GET获取额度/v1/operator/merchants/{merchant_id}/credits只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 credits.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false,
"external_id": "customer-1042"
}GET列出额度账本/v1/operator/merchants/{merchant_id}/credits/ledger只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 credits.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| page, q | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POST调整额度/v1/operator/merchants/{merchant_id}/credits/adjustments读取 + 写入
向此商户预付账本追加附有原因的赠送或更正。
- 需要 credits.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| amount | signed decimal string · required | 正数赠送或负数更正,以商户额度币种表示,最多六位小数。不是链上转账。 |
| note | string · required | 原因保留在只追加的账本中。 |
| request_id | UUID · required | 除 HTTP Idempotency-Key 外,还需与金额和原因一同持久化保存。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"balance": "25"
}GET列出充值/v1/operator/merchants/{merchant_id}/topups只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 topups.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POST创建充值/v1/operator/merchants/{merchant_id}/topups读取 + 写入
创建预付额度结账;绝不手动标记为已付款。
- 需要 topups.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| amount | decimal string · required | 至少一个商户额度币种单位。需要已就绪的运营商收款商店。 |
| request_id | UUID · required | 重试期间保持不变。若账单已创建则返回现有账单。仅在观察到结算后增加额度。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 或 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"invoice_id": "33333333-3333-4333-8333-333333333333",
"checkout_url": "https://pay.example.com/invoice/33333333-3333-4333-8333-333333333333"
}GET获取充值/v1/operator/merchants/{merchant_id}/topups/{topup_id}只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 topups.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| topup_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"invoice_id": "33333333-3333-4333-8333-333333333333",
"amount": "25",
"currency": "EUR",
"status": "pending",
"invoice_status": "new",
"checkout_url": "https://pay.example.com/invoice/33333333-3333-4333-8333-333333333333"
}GET报表/v1/operator/reports只读
读取运营商财务概览。需要所有托管商户的访问权。
- 需要 reports.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| period, start, end, currency, timezone, merchant_id | query · optional | 财务筛选。period 默认 last30;自定义时用 custom,并提供 YYYY-MM-DD 格式 start/end。仅所有商户凭据可用。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/reports" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/reports", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/reports");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/reports",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"summary": {
"fees": "10",
"costs": "3",
"margin": "7",
"credits": "25",
"pending": 0,
"missing_rates": 0
},
"merchants": [],
"filters": {
"period": "last30",
"currency": "EUR",
"timezone": "UTC"
},
"basis": "first_settlement_latest_net_fees"
}GET列出审计记录/v1/operator/audit只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 audit.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id, event_type / search | query · optional | 筛选允许的商户、准确事件类型(events)或操作文本(audit)。事件保留 30 天。 |
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/audit" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/audit", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/audit");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/audit",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GET列出事件/v1/operator/events只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 events.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id, event_type / search | query · optional | 筛选允许的商户、准确事件类型(events)或操作文本(audit)。事件保留 30 天。 |
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/events" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/events", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/events");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/events",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GET列出 Webhook/v1/operator/webhooks只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 events.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/webhooks", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/webhooks",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POST创建 Webhook/v1/operator/webhooks读取 + 写入
订阅未来运营商生命周期事件,不是商店付款 Webhook。
- 需要 webhooks.write + events.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| url | public HTTPS URL · required | 不允许凭据、私有 IP 或重定向。发送时再次检查 DNS/IP。 |
| events | string[] · required | 选择运营商指南中的生命周期事件,不是账单回调。 |
| merchant_ids | UUID[] · optional | 空值表示此凭据允许的全部商户。会重新检查实时范围限制。 |
| enabled | boolean · default true | 暂停端点保留排队发送;重新启用后恢复有效的保留任务。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 或 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"signing_secret": "wco_whsec_EXAMPLE_ONLY_SAVE_ONCE"
}POST更新 Webhook/v1/operator/webhooks/{webhook_id}读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 webhooks.write + events.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| webhook_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| url | public HTTPS URL · required | 不允许凭据、私有 IP 或重定向。发送时再次检查 DNS/IP。 |
| events | string[] · required | 选择运营商指南中的生命周期事件,不是账单回调。 |
| merchant_ids | UUID[] · optional | 空值表示此凭据允许的全部商户。会重新检查实时范围限制。 |
| enabled | boolean · default true | 暂停端点保留排队发送;重新启用后恢复有效的保留任务。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"signing_secret": null
}POST轮换 Webhook 密钥/v1/operator/webhooks/{webhook_id}/rotate读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 webhooks.write + events.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| webhook_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"signing_secret": "wco_whsec_EXAMPLE_ONLY_SAVE_ONCE"
}GET列出 Webhook 发送记录/v1/operator/webhooks/{webhook_id}/deliveries只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 events.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| webhook_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| page | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GET列出项目/v1/operator/merchants/{merchant_id}/projects只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POST创建项目/v1/operator/merchants/{merchant_id}/projects读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| name, slug | strings · required | 名称和唯一稳定项目标识符。通过现有项目初始化创建本地钱包,绝不转移资金。 |
| enabled, reporting_timezone, checkout_title, checkout_description, checkout_theme, checkout_accent_color | optional | enabled 默认为 true;建议先以暂停状态创建,再配置商店。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 或 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}GET获取项目/v1/operator/merchants/{merchant_id}/projects/{project_id}只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}POST更新项目/v1/operator/merchants/{merchant_id}/projects/{project_id}读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| name, enabled, reporting_currency, reporting_timezone, checkout_title, checkout_description, checkout_theme, checkout_accent_color | optional | 部分更新。标识符和商户归属不能更改。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}GET列出商店/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POST创建商店/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| name, slug | strings · required | 商店名称和稳定标识符。 |
| default_currency, invoice_expiry_minutes, exchange_rate_spread_percent, underpayment_tolerance_percent | optional | 百分比使用十进制字符串。新商店继承项目默认商店外观。 |
| enabled, is_default, allow_zero_amount_invoices, allow_underpayments, allow_overpayments, allowed_chain_slugs | optional | 通过 payment-assets 配置接受的资产;零金额账单默认关闭。 |
| ipn_enabled, default_ipn_url, default_redirect_url, default_cancel_url, redirect_automatically | optional | IPN 和返回 URL 遵循现有 URL 验证。不允许任意 HTML/JavaScript。 |
| checkout_language, embed_enabled, allowed_embed_origins, domains | optional | 使用支持的语言和已启用角色域名;明确配置嵌入源站。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 或 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}GET获取商店/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| store_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}POST更新商店/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| store_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| store fields | optional | 除 slug 外,可变设置与创建商店相同。只更改提供的字段。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}GET获取商店外观/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearance只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| store_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"revision": 1,
"settings": {
"inherit_default_store": true
},
"effective": {
"title": "Pay securely"
}
}POST更新商店外观/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearance读取 + 写入
保存经过验证且有修订保护的商店设计。
- 需要 projects.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| store_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| revision | integer · required | 先通过 GET 读取当前修订。旧修订会失败,不覆盖其他编辑者的更改。 |
| settings | appearance object · required | 经验证的结账外观,包括 inherit_default_store、品牌、介绍/结尾、字号和可见性。不允许任意 HTML/JavaScript。图片字节上传仅限控制台。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
},
"revision": 2
}GET列出商店支付资产/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assets只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| store_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": []
}POST更新商店支付资产/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assets读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| store_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| assets | array · required | 完全替换链上方式:asset_id UUID 和 display_order。[] 清空接受的链上资产。仅限经验证的项目资产;不配置 Lightning。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": []
}GET列出商店 Webhook/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooks只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| store_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POST创建商店 Webhook/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooks读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| store_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| name, url, event_types | strings / array · required | 公共 HTTPS 接收端,以及 IPN 与 Webhook 文档中的账单事件名称。 |
| enabled, automatic_redelivery | booleans · default true | 创建时仅返回一次签名密钥。这是商店付款回调,不是运营商生命周期事件。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 或 200 application/json
{
"signing_secret": "EXAMPLE_ONLY_SAVE_ONCE",
"endpoint": {
"id": "44444444-4444-4444-8444-444444444444",
"name": "Orders",
"enabled": true
},
"secret_visible_once": true
}POST更新商店 Webhook/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooks/{webhook_id}读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 projects.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| store_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| webhook_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| name, url, event_types | strings / array · required | 公共 HTTPS 接收端,以及 IPN 与 Webhook 文档中的账单事件名称。 |
| enabled, automatic_redelivery | booleans · default true | 创建时仅返回一次签名密钥。这是商店付款回调,不是运营商生命周期事件。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"name": "Orders",
"enabled": true
}GET列出账单/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoices只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 reports.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| limit, offset, search, status, store_id | query · optional | 账单分页和筛选,与项目账单列表相同。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"pagination": {
"limit": 25,
"offset": 0,
"total": 0,
"has_more": false
}
}GET获取账单/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoices/{invoice_id}只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 reports.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
- invoice_id 是创建时和回调中返回的公共账单 ID,不是内部 id。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| invoice_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"invoice_id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"amount": "25",
"currency": "EUR",
"status": "new",
"metadata": {},
"payment_intents": []
}GET列出钱包/v1/operator/merchants/{merchant_id}/projects/{project_id}/wallets只读
读取缓存的公共钱包余额,绝不读取私钥或助记词。
- 需要 reports.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 余额是带时效字段的缓存观察值,不保证可花费余额。此 API 不提供发送和密钥导出。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GET列出钱包地址/v1/operator/merchants/{merchant_id}/projects/{project_id}/wallets/{wallet_id}/addresses只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 reports.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| project_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| wallet_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| limit, before, search, has_balance, hide_small_balances | query · optional | limit 为 1–50,默认 25。下一页将 next_cursor 作为 before 传入;第 1 页省略 before。has_balance=false 和 hide_small_balances=false 会包含空余额和小额余额。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"wallet": {
"id": "22222222-2222-4222-8222-222222222222",
"chain_slug": "ethereum"
},
"items": [],
"total": 0,
"total_pages": 1,
"next_cursor": null,
"reporting_currency": "EUR",
"has_balance": true,
"hide_small_balances": true,
"small_balance_threshold": {
"amount": "0.20",
"currency": "EUR"
}
}GET列出商户凭据/v1/operator/merchants/{merchant_id}/api-credentials只读
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 merchant_credentials.read;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| page, search | query · optional | 页码从 1 开始,每页 25 项。商户、用户、项目、商店、钱包、凭据和 Webhook 支持搜索;原生事件列表使用其专用筛选。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POST创建商户凭据/v1/operator/merchants/{merchant_id}/api-credentials读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 merchant_credentials.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| name | string · required | 新普通商户密钥的标签,不是运营商密钥。 |
| access_level | read_only | read_write · default read_only | 读写权限启用现有商户 API 契约。 |
| project_ids | UUID[] | 仅限选定商户拥有的项目;空列表遵循现有所有商户项目策略。 |
| enabled, ip_restriction_enabled, allowed_ips, requests_per_minute | optional | 现有商户密钥控制。机密仅返回一次;需要 merchant_credentials.write。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 或 200 application/json
{
"credential": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
},
"token": "wc_live_EXAMPLE_ONLY_SAVE_ONCE"
}POST更新商户凭据/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 merchant_credentials.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| credential_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| name, access_level, enabled, ip_restriction_enabled, allowed_ips | required fields | 发送包含更改的完整当前凭据配置。project_ids 默认 [];requests_per_minute 默认商户 API 配额。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
}POST轮换商户凭据/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/rotate读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 merchant_credentials.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| credential_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"credential": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
},
"token": "wc_live_EXAMPLE_ONLY_SAVE_ONCE"
}POST撤销商户凭据/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/revoke读取 + 写入
使用独立运营商凭据管理或查看指定托管商户资源。
- 需要 merchant_credentials.write;仅限允许的托管商户。运营商密钥不能访问所有者的自营业务空间。
- 发送前持久化保存唯一 Idempotency-Key 和完全一致的请求体。重试绝不重复已提交的操作。重放会省略机密字段;机密响应若丢失,请查看已创建资源并明确轮换/重新签发。409 operator_request_in_progress 可能表示请求中断且结果未知:检查资源/审计,不要盲目换新键重试。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | 必需 | 16–128 个字母、数字、-、_ 或 .;为此操作持久化保存 |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| merchant_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
| credential_id | path UUID | 规范小写资源 UUID;必须属于凭据的商户范围。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"revoked": true
}POST检查邀请令牌/v1/onboarding/invitations/check公开
仅令牌入驻。不接受运营商密钥,也不自动登录。控制台登录仍需要网站 Basic Auth 和现有 TOTP。
- 48 小时邀请,一小时密码重置链接。令牌经过哈希且仅限一次使用。重新签发撤销旧链接。接受时保留 TOTP,并撤销旧会话。
- 不自动重试。接受超时时,检查链接状态并尝试登录,不要假定失败。按观察到的来源 IP 限流。接收人必须自行确认托管责任。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| token | string · required | 邀请 URL 片段中的机密。绝不记录到日志。 |
请求
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/onboarding/invitations/check" \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}'// Node.js 18+ · run on your server, never in browser code.
const body = `{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}`;
const response = await fetch("https://api.example.com/v1/onboarding/invitations/check", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$body = <<<'JSON'
{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}
JSON;
$ch = curl_init("https://api.example.com/v1/onboarding/invitations/check");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
body = """{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/onboarding/invitations/check",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"kind": "invitation",
"email": "admin@example.test",
"merchant_name": "Example shop"
}POST接受邀请或密码重置/v1/onboarding/invitations/accept公开
仅令牌入驻。不接受运营商密钥,也不自动登录。控制台登录仍需要网站 Basic Auth 和现有 TOTP。
- 48 小时邀请,一小时密码重置链接。令牌经过哈希且仅限一次使用。重新签发撤销旧链接。接受时保留 TOTP,并撤销旧会话。
- 不自动重试。接受超时时,检查链接状态并尝试登录,不要假定失败。按观察到的来源 IP 限流。接收人必须自行确认托管责任。
- 响应示例展示部分字段。额外响应字段应视为兼容的新增内容。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| token | string · required | 邀请 URL 片段中的机密。绝不记录到日志。 |
| password | string · required | 新密码,12–128 字符(最多 512 UTF-8 字节)。 |
| custody_acknowledged | boolean | 接受新的托管钱包邀请时必须为 true。 |
请求
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/onboarding/invitations/accept" \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}'// Node.js 18+ · run on your server, never in browser code.
const body = `{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}`;
const response = await fetch("https://api.example.com/v1/onboarding/invitations/accept", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$body = <<<'JSON'
{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/onboarding/invitations/accept");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
body = """{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/onboarding/invitations/accept",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"password_set": true
}GET列出付款异常/v1/projects/{project_id}/reconciliation只读
统一的分页审核队列,涵盖少付、多付、延迟、重组或不明确付款、发送失败及已禁用/过期方式。运营人员已确认的案例在出现新证据时会重新打开。
- 只读、限定项目范围,并计入凭据配额。财务决定和退款仍只可在控制台操作。
- 每行包含 id(内部 UUID)、invoice_id(公共 UUID,与回调相同)、商店信息、原始法币金额/币种、invoice_status、案例状态、原因、修订号和 updated_at。商户详情端点使用 invoice_id。
- 自动检测遵循原始账单监控窗口;重新扫描会延长观察一小时,但不启用结账。已结算/已取消方式在该窗口内仍继续监控。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给此凭据的项目。 |
| status | query string | open(默认)、resolved 或 all。 |
| reason | query string | underpaid、overpaid、late、reorged、ambiguous、delivery_failed、disabled_method 或 expired_method。 |
| search | query string | 最多 100 字符:账单 ID、订单、客户或商店。 |
| store_id | query UUID | 可选商店筛选。 |
| page | query integer | 1–40001。每页固定 25 个案例。 |
异常队列响应
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| data | ExceptionRow[] | 始终 | 最近更新的案例优先。商户详情 URL 使用 invoice_id,而非内部 id。 |
| pagination | object | 始终 | page(1–40001)、per_page(25)、匹配行总数 total、has_more。 |
| counts | object | 始终 | 整个项目的 open 和 resolved 总数,不受当前筛选影响。 |
ExceptionRow
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id / invoice_id | UUID | 始终 | 内部记录 ID / 面向客户的账单 UUID。invoice_id 与回调载荷匹配。 |
| store_id / store_name | UUID / string | 始终 | 所属商店。 |
| order_id / email | string | null | 始终 | 私密的商户订单参考和客户邮箱。 |
| amount / currency | decimal string / string | 始终 | 原始法币账单金额和币种。 |
| invoice_status | invoice status | 始终 | 当前付款生命周期状态。 |
| status / reasons | open|resolved / string[] | 始终 | 案例状态及 reason 筛选列出的异常类型。 |
| revision / updated_at | integer / timestamp | 始终 | 当前审核修订号和更新时间。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{"data":[],"pagination":{"page":1,"per_page":25,"total":0,"has_more":false},"counts":{"open":0,"resolved":0}}GET读取对账证据/v1/projects/{project_id}/reconciliation/{invoice_id}只读
返回账单、案例、精确方式合计和可退款金额、观察交易、发送历史、商户决定及关联退款转账。绝不暴露签名密钥或回调机密。
- 账单未产生异常时 case 为 null。返回最近 100 条观察记录和 50 次发送;决定历史分页提供。
- refundable_atomic 至少需要一次网络确认,排除现有退款预留,不保证钱包资金可花费。实时报价还会验证钱包就绪状态、来源余额和费用。
- 退款已广播表示已提交到链端点,不代表独立确认客户收到。费用另计,发起退款不会自动返还法币处理费。
- 控制台项目菜单 → 需要关注,提供取消、接受、拒绝、重新开启、审核、备注、重新扫描、发送重试及支持链上的退款。决定需要受 CSRF 保护的会话、唯一 request_id、当前案例修订、必填备注和明确确认;Bearer 令牌不能调用这些修改。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 已分配项目。 |
| invoice_id | path UUID | 公共账单 UUID,不是内部 id。 |
| page | query integer | 决定历史页码,从 1 开始,每页 25 项。 |
对账响应
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| invoice | InvoiceDetail | 始终 | 完整商户账单:汇总字段、私密元数据和 payment_intents。不包裹在 data 中。 |
| case | object | null | 始终 | 当前案例,含状态、原因、修订和时间戳;无异常时为 null。不包含内部证据。 |
| methods | object[] | 始终 | id、wallet_id、asset_id、symbol、chain、decimals、expected_atomic、received_atomic、confirmed_atomic、refundable_atomic、address、tag、monitor_error、last_checked_at、monitoring_expires_at 和 spending_supported。最小单位金额为字符串。 |
| history | object[] | 始终 | 本页最新 25 项决定:id、action、note、actor、result、created_at。 |
| history_pagination | object | 始终 | page、per_page(25)、total。仅决定历史按 page 分页。 |
| refunds | object[] | 始终 | 最新 100 笔退款:id、payment_intent_id、amount_atomic、destination、status、request、treasury_intent_id、transfer_status、created_at 和 transactions(id/status)。提交退款仅限控制台。 |
| observations | object[] | 始终 | 最新 100 项:payment_intent_id、transaction_id、event_index、amount、status、confirmations、observed_at、symbol、chain 和 disabled_at_detection。支持时还包含 explorer_name/explorer_url。 |
| deliveries | object[] | 始终 | 最新 50 项:id、kind、status、attempts、response_status、error、next_attempt_at、event_type 和 created_at。不含回调机密。 |
账单汇总
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 内部账单 UUID。不要用于商户详情或结账路径。 |
| invoice_id | UUID | 始终 | 用于商户详情和结账路径的公共账单 UUID。 |
| project_id | UUID | 始终 | 所属项目。 |
| store_id | UUID | 始终 | 所属商店。 |
| source | manual | api | 始终 | 账单的创建方式。 |
| order_id | string | null | 始终 | 商户订单参考。 |
| string | null | 始终 | 仅供商户查看的客户邮箱,公共结账绝不返回。 | |
| customer_name | string | null | 始终 | 从私密 firstname、lastname 和 company 元数据派生的显示名称。 |
| customer_address | string | null | 始终 | 从私密 company、street、street2、zip、city、country、countryiso2 和 vatid 元数据派生的单行商户地址。 |
| description | string | null | 始终 | 面向客户的说明。 |
| amount | decimal string | 始终 | 规范账单金额。 |
| currency | string | 始终 | 标准化的账单币种/资产代码。 |
| exchange_rate_spread_percent | decimal string | 始终 | 锁定报价加价:创建时覆盖值,省略时为商店默认值。向上取整前应用;此账单上绝不改变。 |
| underpayment_tolerance_percent | decimal string | 始终 | 创建账单时保存快照的不可变接受少付百分比。 |
| status | invoice status | 始终 | new、processing、settled、expired、invalid 或 cancelled。 |
| amount_status | amount status | 始终 | none、partial、paid 或 overpaid。明确允许的零金额账单以 none 结算,不包含支付方式。 |
| timing_status | timing status | 始终 | on_time 或 late。 |
| resolution | resolution | 始终 | automatic、manually_settled 或 manually_invalidated。 |
| sequence | integer | 始终 | 单调递增的账单状态序列,从 1 开始。 |
| winning_payment_intent_id | UUID | null | 始终 | 使账单完成结算的支付方式(已选定时)。 |
| expires_at | RFC 3339 timestamp | 始终 | 报价/付款截止时间。 |
| monitoring_expires_at | RFC 3339 timestamp | 始终 | 所有支付方式配置的最晚延迟监控截止时间。 |
| settled_at | timestamp | null | 始终 | 已结算时的结算时间。 |
| cancelled_at | timestamp | null | 始终 | 已取消时的取消时间。 |
| archived_at | timestamp | null | 始终 | 已归档时的归档时间。 |
| created_at | RFC 3339 timestamp | 始终 | 创建时间。 |
| updated_at | RFC 3339 timestamp | 始终 | 最近状态更新时间。 |
账单详情附加字段
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| ipn_url | string | null | 始终 | 每张账单的有效 IPN 目标。仅商户响应提供;公共结账省略。 |
| redirect_url | string | null | 始终 | 结算后使用的有效成功 URL。 |
| cancel_url | string | null | 始终 | 结账未成功付款结束时使用的有效返回 URL。 |
| redirect_automatically | boolean | 始终 | 成功后结账是否自动重定向。 |
| checkout_language | string | 始终 | 有效结账语言标签。 |
| metadata | object | 始终 | 商户元数据。公共结账绝不返回。 |
| payment_intents | PaymentIntent[] | 始终 | 已报价支付方式和监控状态。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{"invoice":{"invoice_id":"YOUR_PUBLIC_INVOICE_ID","status":"processing"},"case":{"status":"open","reasons":["underpaid"],"revision":1},"methods":[],"history":[],"history_pagination":{"page":1,"per_page":25,"total":0},"refunds":[],"observations":[],"deliveries":[]}GETAPI 服务发现/公开
受管理 API 主机边缘响应,用于确认 v1 公共 API 角色。此响应由受管理代理产生,不是商户 Axum 路由器。
- 不需要 Bearer 令牌。
- 只有受管理的 API 主机名保证此准确根路径响应。
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/"// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://api.example.com/", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://api.example.com/");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://api.example.com/",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"service": "Wholly Crypto API",
"status": "ready",
"version": "v1"
}GET服务健康状态/healthz公开
检查应用可达性和两秒数据库 ping。用于监控,不能替代账单状态。
- 不需要 Bearer 令牌。
- version 值是正在运行的软件包版本,不是 API 路径版本。
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/healthz"// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://api.example.com/healthz", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://api.example.com/healthz");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://api.example.com/healthz",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 健康;503 数据库不可用
{
"status": "ok",
"database": "ok",
"version": "0.1.0"
}GET列出项目支付资产/v1/projects/{project_id}/payment-assets只读
列出原生资产和已验证代币,包含项目策略、链钱包就绪状态及已安装扫描/余额能力。scanner_ready 是适配器构建门槛,不是实时端点法定数量结果。自 6.0.6 起,扫描器停机时创建仍保留已配置方式。收款验证仍需要配置的健康且角色完全匹配的服务商数量(默认 2,可选 1)。
- 代币可能在全局列出,但 scanner_ready 或 payment_supported 为 false 时仍不可选择。
- 运营商能力矩阵也要求扫描器的准确端点角色;提供不兼容 API 的健康端点不会计入。
- 代币共用原生链的项目钱包,不会创建另一组助记词。
- 嵌入的钱包摘要仅表示就绪状态,余额保持空;获取详细余额请使用 GET /v1/projects/{project_id}/wallets。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给凭据的已启用项目。 |
PaymentAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 项目和商店策略路由使用的持久支付资产标识符。 |
| asset_key | string | 始终 | 规范 CAIP 风格原生币或合约资产标识。 |
| chain_slug / network | string | 始终 | Wholly Crypto 链标识符及配置的网络。 |
| caip_network_id / caip_asset_id | string / string|null | 始终 | 规范网络和资产标识。 |
| asset_kind | native | token | 始终 | 结算使用链币种还是经验证的合约/mint。 |
| payment_rail | string | 始终 | 运行时通道:utxo、evm-native、solana-native、account-native、privacy-native 或 token-transfer。 |
| symbol / name / decimals | string / string / integer | 始终 | 显示标识及精确最小单位精度。 |
| contract_address | string | null | 始终 | 代币的规范 ERC-20 合约或 SPL mint;原生资产为 null。 |
| coingecko_id | string | null | 始终 | 发现/定价标识。自定义合约为 null;绝不要通过代码推断市场价。仅 CoinGecko 元数据不会使代币可选。 |
| custom_token | boolean | 始终 | 经过链上验证的自定义合约,采用项目范围固定美元价格或选定 DEX 池定价。 |
| icon_path | path | null | 始终 | 可用时提供本地缓存的代币图标。 |
| token_standard | erc20 | spl-token | null | 始终 | 经过验证的运行时代币标准;原生资产为 null。 |
| metadata_verified_at | timestamp | null | 始终 | 已注册代币的链上元数据验证时间。 |
| payment_supported / scanner_ready / balance_ready | boolean | 始终 | 构建时注册表门槛。scanner_ready 表示已安装付款扫描运行组件;付款确认需要配置数量的健康且角色准确的服务商(默认 2,可选 1);自 6.0.6 起,临时扫描器不可用不阻止创建账单。balance_ready 仅在余额适配器已实现时为 true。 |
| default_finality_mode | confirmations | finalized | 始终 | 新项目策略继承的默认最终性模型。 |
| default_required_confirmations / default_monitoring_minutes | integer | 始终 | 默认确认与监控策略。 |
ProjectPaymentAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| asset | PaymentAsset | 始终 | 持久原生币或已验证代币资产。 |
| policy | ProjectAssetPolicy | null | 始终 | 项目启用/最终性策略,未配置时为 null。包含 custom_price_mode(fixed/dex)、custom_price_usd(固定十进制字符串或 null)、custom_dex_pair(所选池或 null),以及 custom_dex(dex_id、quote_symbol、当前 price_usd 或 null、liquidity_usd、fetched_at、last_error)。项目内商店共用自定义定价。 |
| wallet | WalletSummary | null | 始终 | 该链的非托管项目钱包。代币共用原生链钱包。 |
| wallet_readiness | readiness enum | 始终 | unsupported、project_disabled、project_asset_disabled、store_disabled、store_asset_disabled、wallet_missing、wallet_pending、wallet_disabled、wallet_error、backup_required、account_activation_required、external_wallet_rpc_required 或 ready。 |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | 共享的项目收款设置评估。包含钱包和独立扫描服务商检查,与余额时效和发送 Gas 分开。无项目策略时为 null。创建账单时检查币种和汇率。 |
WalletSummary
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | 始终 | 钱包、所属项目和链原生资产标识符。 |
| chain_slug / network | string | 始终 | 钱包区块链与网络。 |
| asset_symbol / asset_name | string | 始终 | 链原生币显示标识。 |
| status | pending | active | disabled | error | 始终 | 钱包运行状态。 |
| label | string | 始终 | 运营者标签。 |
| public_key / primary_address | string | null | 始终 | 公共钱包标识;不暴露助记词或私钥。 |
| derivation_scheme / address_format | string | null | 始终 | 地址策略与格式。 |
| backup_confirmed_at | timestamp | null | 始终 | 运营者确认恢复备份后为非 null。 |
| activation_required / activation_verified_at | boolean / timestamp|null | 始终 | XRP 和 Stellar 共享账户在运营者向显示地址注资,且配置的扫描服务商验证该准确账户前不可用。持久证明不会过期;实时扫描器健康单独用于付款验证,不用于创建账单。 |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | 钱包列表包含:项目收款设置和链扫描器前提。与余额、代币 Gas 和发送就绪状态分开。其他钱包响应可能为 null。 |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | 始终 | 经脱敏的 Monero 外部只读 wallet-RPC 绑定状态。包含端点、认证模式、account-0 主地址、技术证明标记/高度和运营者确认时间戳;绝不序列化凭据、钱包密钥或钱包文件。 |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | 始终 | 控制台端机密披露审计元数据。 |
| next_receive_index | integer | 始终 | 下一个预留子地址索引。 |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | 始终 | 钱包扫描状态。 |
| balances | WalletAssetBalance[] | 始终 | 全部 30 条原生链通道及已验证 ERC-20、SPL 资产的缓存余额。Monero 需要配置外部只读 wallet-RPC。 |
| total_value_usd | decimal string | null | 始终 | 有当前美元价格的余额参考合计。 |
| balance_status | pending | refreshing | fresh | stale | error | unknown | 始终 | 汇总缓存时效;unknown 是防御性回退,这些状态都不能证明账单已结算。 |
| balance_checked_at | timestamp | null | 始终 | 汇总中相关成功余额检查的最早时间。 |
| recent_payments | WalletRecentPayment[] | 始终 | 归属于此准确钱包的最多三条最新有效 detected、confirming 或 final 记录。 |
| created_at / updated_at | RFC 3339 timestamp | 始终 | 钱包创建与最近更新时间。 |
ReceiveReadiness
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| ready | boolean | 始终 | 收款设置检查通过。不表示支出就绪、Gas、余额刷新或保证的未来报价。 |
| invoice_creatable | boolean | 6.0.6+ | 配置允许在扫描器临时警告下创建账单方式。币种定价在创建时检查。这不是付款验证:ready 为 false 时 invoice_creatable 仍可为 true。缺失钱包、禁用策略和不支持的适配器仍会安全拒绝。 |
| checked_at | timestamp | 始终 | 评估时间。列表查询不发起网络请求或分配地址。 |
| issues | PaymentMethodIssue[] | 始终 | 就绪时为空;否则为收款警告或配置阻塞原因。检查 invoice_creatable 以区分临时扫描警告与账单设置失败。 |
PaymentMethodIssue
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | 已知时 | 标识受影响的链和资产。Lightning 可省略 asset_id。 |
| reason_code | string | 始终 | scanner_provider_quorum、scanner_not_checked、scanner_unavailable、wallet_missing、wallet_disabled、wallet_backup_required、wallet_key_unavailable、wallet_activation_required、monero_binding_unavailable、rate_unavailable、custom_rate_unavailable、lightning_unavailable、project_disabled、store_disabled、chain_disabled、asset_disabled 或 asset_not_accepted。 |
| message / action | string | 可用时 | 面向商户的说明和操作标识符:chain_connections、wallets、rates、payment_methods、project_settings 或 store_settings。不含凭据或私有服务商 URL。 |
| required_endpoint_role | string | null | 链上 | 首选扫描器 API 角色(旧字段)。完整兼容列表请使用 accepted_endpoint_roles。基础节点健康不能证明支持付款历史。 |
| accepted_endpoint_roles | string[] | null | 链上 | 兼容 API 方言,不证明端点历史或容量。原始 node-rpc 支持 BTC/BCH/LTC/DOGE/DASH 和透明 ZEC(完整解码区块,1–48 次确认)、已固化原生 TRX、经 algod 的原生 ALGO、经 Octez 的 XTZ、经 SCALE 元数据的已最终确认 Asset Hub DOT,以及通过 Stellar RPC 且带账单 memo ID 的原生 XLM。裁剪或不完整历史不合格。这些原始适配器不增加代币通道。索引 API 仍可选择,见下方通道表。混合原始/索引来源独立验证有界窗口;默认仍需两个独立服务商,不能是同一运营者的别名。基础节点高度、ORDnet 链信息及非 EVM 通道的 EVM 中继都不是收款证明。Monero 仍需绑定项目的只读 wallet-RPC。 |
| healthy_endpoints | integer | 链上 | 匹配的健康端点数量,不是独立服务商数量。 |
| usable_independent_providers / required_independent_providers | integer | 链上 | 可用验证名额,最多两个。required_independent_providers 是链设置:默认 2,管理员明确选择后可为 1。双服务商模式需要不同服务商键且不同主机。禁用、过时(超过十分钟)或冷却中的来源不占名额。Lightning 使用自己的连接规则。 |
| last_checked_at | timestamp | null | 链上 | 最新的匹配端点健康检查,与评估时间不同。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [
{
"asset": {
"id": "10000000-0000-4000-8000-000000000003",
"asset_key": "eip155:1/slip44:60",
"chain_slug": "ethereum",
"network": "mainnet",
"caip_network_id": "eip155:1",
"caip_asset_id": "eip155:1/slip44:60",
"asset_kind": "native",
"payment_rail": "evm-native",
"symbol": "ETH",
"name": "Ethereum",
"decimals": 18,
"contract_address": null,
"coingecko_id": "ethereum",
"icon_path": "/assets/coingecko/ethereum.png",
"token_standard": null,
"metadata_verified_at": null,
"payment_supported": true,
"scanner_ready": true,
"balance_ready": true,
"default_finality_mode": "confirmations",
"default_required_confirmations": 12,
"default_monitoring_minutes": 60
},
"policy": {
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 12,
"monitoring_minutes": 60,
"late_monitoring_days": 30
},
"wallet": null,
"wallet_readiness": "wallet_missing",
"receive_readiness": { "ready": false, "invoice_creatable": false, "checked_at": "2026-09-16T09:00:00Z", "issues": [{ "chain_slug": "ethereum", "asset_id": "10000000-0000-4000-8000-000000000003", "asset_ticker": "ETH", "reason_code": "wallet_missing", "message": "ethereum / ETH: Create a project wallet for this chain.", "action": "wallets" }] }
}
]
}PUT更新项目资产策略/v1/projects/{project_id}/payment-assets/{asset_id}读取 + 写入
创建或替换单个持久资产的项目策略,返回刷新的项目资产列表。禁用原生链会使其原生资产和代币对新账单不可用,但保留代币策略、钱包和商店选择,以便之后恢复。
- 请求体完全替换策略,拒绝未知字段。
- 项目启用本身不会为任何商店选择该资产。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | 必需 | application/json |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给凭据的已启用项目。 |
| asset_id | path UUID | 由项目资产列表或代币注册返回的资产 id。 |
项目资产策略更新
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| enabled | boolean | 必需 | 为项目启用或禁用资产。启用任何代币前必须先启用原生链。 |
| finality_mode | confirmations | finalized | 必需 | 资产通道支持的最终性策略。finalized 要求 required_confirmations=1。 |
| required_confirmations | integer | 必需 | Bitcoin 和 EVM 通道接受零;其他确认型通道至少为一,仅最终确认通道必须恰好为一。EVM 限定 0–48,确保每笔转账仍在交易重放窗口内。 |
| monitoring_minutes | integer | 必需 | 账单活动期间的轮询窗口,1–10,080 分钟。 |
| late_monitoring_days | integer | 必需 | 账单过期后的监控期,0–3,650 天。 |
PaymentAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 项目和商店策略路由使用的持久支付资产标识符。 |
| asset_key | string | 始终 | 规范 CAIP 风格原生币或合约资产标识。 |
| chain_slug / network | string | 始终 | Wholly Crypto 链标识符及配置的网络。 |
| caip_network_id / caip_asset_id | string / string|null | 始终 | 规范网络和资产标识。 |
| asset_kind | native | token | 始终 | 结算使用链币种还是经验证的合约/mint。 |
| payment_rail | string | 始终 | 运行时通道:utxo、evm-native、solana-native、account-native、privacy-native 或 token-transfer。 |
| symbol / name / decimals | string / string / integer | 始终 | 显示标识及精确最小单位精度。 |
| contract_address | string | null | 始终 | 代币的规范 ERC-20 合约或 SPL mint;原生资产为 null。 |
| coingecko_id | string | null | 始终 | 发现/定价标识。自定义合约为 null;绝不要通过代码推断市场价。仅 CoinGecko 元数据不会使代币可选。 |
| custom_token | boolean | 始终 | 经过链上验证的自定义合约,采用项目范围固定美元价格或选定 DEX 池定价。 |
| icon_path | path | null | 始终 | 可用时提供本地缓存的代币图标。 |
| token_standard | erc20 | spl-token | null | 始终 | 经过验证的运行时代币标准;原生资产为 null。 |
| metadata_verified_at | timestamp | null | 始终 | 已注册代币的链上元数据验证时间。 |
| payment_supported / scanner_ready / balance_ready | boolean | 始终 | 构建时注册表门槛。scanner_ready 表示已安装付款扫描运行组件;付款确认需要配置数量的健康且角色准确的服务商(默认 2,可选 1);自 6.0.6 起,临时扫描器不可用不阻止创建账单。balance_ready 仅在余额适配器已实现时为 true。 |
| default_finality_mode | confirmations | finalized | 始终 | 新项目策略继承的默认最终性模型。 |
| default_required_confirmations / default_monitoring_minutes | integer | 始终 | 默认确认与监控策略。 |
ProjectPaymentAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| asset | PaymentAsset | 始终 | 持久原生币或已验证代币资产。 |
| policy | ProjectAssetPolicy | null | 始终 | 项目启用/最终性策略,未配置时为 null。包含 custom_price_mode(fixed/dex)、custom_price_usd(固定十进制字符串或 null)、custom_dex_pair(所选池或 null),以及 custom_dex(dex_id、quote_symbol、当前 price_usd 或 null、liquidity_usd、fetched_at、last_error)。项目内商店共用自定义定价。 |
| wallet | WalletSummary | null | 始终 | 该链的非托管项目钱包。代币共用原生链钱包。 |
| wallet_readiness | readiness enum | 始终 | unsupported、project_disabled、project_asset_disabled、store_disabled、store_asset_disabled、wallet_missing、wallet_pending、wallet_disabled、wallet_error、backup_required、account_activation_required、external_wallet_rpc_required 或 ready。 |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | 共享的项目收款设置评估。包含钱包和独立扫描服务商检查,与余额时效和发送 Gas 分开。无项目策略时为 null。创建账单时检查币种和汇率。 |
ReceiveReadiness
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| ready | boolean | 始终 | 收款设置检查通过。不表示支出就绪、Gas、余额刷新或保证的未来报价。 |
| invoice_creatable | boolean | 6.0.6+ | 配置允许在扫描器临时警告下创建账单方式。币种定价在创建时检查。这不是付款验证:ready 为 false 时 invoice_creatable 仍可为 true。缺失钱包、禁用策略和不支持的适配器仍会安全拒绝。 |
| checked_at | timestamp | 始终 | 评估时间。列表查询不发起网络请求或分配地址。 |
| issues | PaymentMethodIssue[] | 始终 | 就绪时为空;否则为收款警告或配置阻塞原因。检查 invoice_creatable 以区分临时扫描警告与账单设置失败。 |
PaymentMethodIssue
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | 已知时 | 标识受影响的链和资产。Lightning 可省略 asset_id。 |
| reason_code | string | 始终 | scanner_provider_quorum、scanner_not_checked、scanner_unavailable、wallet_missing、wallet_disabled、wallet_backup_required、wallet_key_unavailable、wallet_activation_required、monero_binding_unavailable、rate_unavailable、custom_rate_unavailable、lightning_unavailable、project_disabled、store_disabled、chain_disabled、asset_disabled 或 asset_not_accepted。 |
| message / action | string | 可用时 | 面向商户的说明和操作标识符:chain_connections、wallets、rates、payment_methods、project_settings 或 store_settings。不含凭据或私有服务商 URL。 |
| required_endpoint_role | string | null | 链上 | 首选扫描器 API 角色(旧字段)。完整兼容列表请使用 accepted_endpoint_roles。基础节点健康不能证明支持付款历史。 |
| accepted_endpoint_roles | string[] | null | 链上 | 兼容 API 方言,不证明端点历史或容量。原始 node-rpc 支持 BTC/BCH/LTC/DOGE/DASH 和透明 ZEC(完整解码区块,1–48 次确认)、已固化原生 TRX、经 algod 的原生 ALGO、经 Octez 的 XTZ、经 SCALE 元数据的已最终确认 Asset Hub DOT,以及通过 Stellar RPC 且带账单 memo ID 的原生 XLM。裁剪或不完整历史不合格。这些原始适配器不增加代币通道。索引 API 仍可选择,见下方通道表。混合原始/索引来源独立验证有界窗口;默认仍需两个独立服务商,不能是同一运营者的别名。基础节点高度、ORDnet 链信息及非 EVM 通道的 EVM 中继都不是收款证明。Monero 仍需绑定项目的只读 wallet-RPC。 |
| healthy_endpoints | integer | 链上 | 匹配的健康端点数量,不是独立服务商数量。 |
| usable_independent_providers / required_independent_providers | integer | 链上 | 可用验证名额,最多两个。required_independent_providers 是链设置:默认 2,管理员明确选择后可为 1。双服务商模式需要不同服务商键且不同主机。禁用、过时(超过十分钟)或冷却中的来源不占名额。Lightning 使用自己的连接规则。 |
| last_checked_at | timestamp | null | 链上 | 最新的匹配端点健康检查,与评估时间不同。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [
{ "asset": { "id": "ASSET_UUID", "symbol": "USDC", "asset_kind": "token", "scanner_ready": true }, "policy": { "enabled": true, "finality_mode": "confirmations", "required_confirmations": 2, "monitoring_minutes": 60, "late_monitoring_days": 30 }, "wallet_readiness": "ready" }
]
}GET浏览支付代币候选项/v1/projects/{project_id}/payment-token-candidates只读
仅在已实现代币账单扫描器和余额适配器的链上搜索本地缓存 CoinGecko 合约映射。结果是发现候选,不是可信支付资产。
- 支持的代币适配器:Ethereum、Base、BNB Chain、HyperEVM、Avalanche、Polygon、Arbitrum、Optimism 上的 ERC-20,以及 Solana 上的 SPL。
- 不支持的目录链会被拒绝,不会显示为可选。
- CoinGecko 排名、图标和价格仅为参考发现数据。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给凭据的已启用项目。 |
| chain_slug | query string | 必需的受支持 EVM 链 slug 或 solana。 |
| q | query string | 可选名称、符号、CoinGecko id、合约或 mint 子串,最多 80 字符。 |
| limit | query integer | 可选,1–100;默认 50。 |
TokenCandidate
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| coingecko_id | string | 始终 | 注册请求使用的 CoinGecko 发现标识。 |
| chain_slug | string | 始终 | 匹配的 Wholly Crypto 链。 |
| symbol / name | string | 始终 | 目录显示标识。 |
| contract_address | string | 始终 | 匹配的合约或 mint;注册前进行链上验证。 |
| market_cap_rank | integer | null | 始终 | 发现排名,不是可信度或支付就绪信号。 |
| icon_path | path | 始终 | 本地缓存 CoinGecko 图标路径。 |
| current_price_usd | decimal string | null | 始终 | 参考缓存美元价格。 |
| token_standard | erc20 | spl-token | 始终 | 所选链适配器支持的代币标准。 |
| scanner_ready | boolean | 始终 | 仅此构建已实现代币通道上的候选为 true。 |
| registered_asset_id | UUID | null | 始终 | 已注册时对应的现有持久资产。 |
| project_enabled | boolean | 始终 | 已注册资产是否为此项目启用。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [
{
"coingecko_id": "usd-coin",
"chain_slug": "ethereum",
"symbol": "USDC",
"name": "USDC",
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"market_cap_rank": 7,
"icon_path": "/assets/coingecko/usd-coin.png",
"current_price_usd": "1.0001",
"token_standard": "erc20",
"scanner_ready": true,
"registered_asset_id": null,
"project_enabled": false
}
]
}POST验证并注册代币/v1/projects/{project_id}/payment-token-assets读取 + 写入
只有配置节点验证链身份、合约/mint 身份、小数位和可用余额查询后,才将当前候选注册到持久支付注册表。绝不单凭 CoinGecko 元数据注册;每项目最多 20 项注册代币资产。
- 注册代币前先启用其链的原生项目资产。
- 每项目最多注册 20 项代币资产;超限新候选返回 token_chain_not_ready(409)。复用已注册资产不占新名额。
- 节点验证可能比目录读取更久;请设置明确客户端超时。
- 注册后,为需要提供该资产的每个商店选择它。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | 必需 | application/json |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给凭据的已启用项目。 |
代币注册请求体
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| chain_slug | string | 必需 | ethereum、base、bnb-chain、hyperliquid、avalanche、polygon、arbitrum、optimism 或 solana。 |
| coingecko_id | string | 必需 | 代币搜索返回的准确候选标识。保留开头的下划线或连字符,如 _ 或 -6。不要从代币名称或代码推导此 ID。 |
| enabled | boolean | 可选 | 验证后的项目策略状态;默认为 true。 |
RegisteredTokenAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| asset_id | UUID | 始终 | 持久支付资产标识符。 |
| chain_slug / coingecko_id | string | 始终 | 已验证链及保留的发现/定价标识。 |
| contract_address | string | 始终 | 规范的已验证合约或 mint。 |
| token_standard | erc20 | spl-token | 始终 | 已验证运行时代币标准。 |
| symbol / name / decimals | string / string / integer | 始终 | 注册后的显示标识和准确精度。 |
| enabled | boolean | 始终 | 初始项目策略状态。 |
| metadata_verified_at | RFC 3339 timestamp | 始终 | 链上验证时间。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 application/json
{
"data": {
"asset_id": "44444444-4444-4444-8444-444444444444",
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"token_standard": "erc20",
"symbol": "USDC",
"name": "USDC",
"decimals": 6,
"enabled": true,
"metadata_verified_at": "2026-08-31T18:00:00Z"
}
}GET查找自定义代币 DEX 池/v1/projects/{project_id}/payment-token-dex-pools只读
通过 DEX Screener 按准确链和基础代币合约查找最多 12 个合格池,按流动性排序。此操作不会注册或启用代币。
- 空 data 数组表示没有合格池。只返回请求的准确合约为基础代币的池,绝不推断报价侧美元价格。
- DEX 上架不代表安全审计。最低流动性和近期活跃度可减少不可用报价,但不能防止市场操纵。
- 现有链扫描器支持代币时,可使用 Uniswap、PancakeSwap 等已索引 DEX。API 访问仍限定项目范围并受速率限制。服务商调用也会串行执行并节流。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 已分配项目。 |
| chain_slug | query string | 受支持的 EVM 代币链或 solana。 |
| contract_address | query string | 准确的 ERC-20 合约或经典 SPL mint。 |
CustomDexPool
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| pair_address / dex_id / quote_symbol | string | 始终 | 准确池标识、交易所 ID(如 uniswap/pancakeswap),以及仅供显示的配对代码。 |
| price_usd / liquidity_usd | decimal string | 始终 | 请求的基础代币美元价格及池总流动性。需要至少 $10,000 流动性,且过去一小时内有交易。 |
| fetched_at | RFC 3339 timestamp | 始终 | 服务器获取服务商观察数据的时间,不是链上交易时间戳。 |
| url | HTTPS URL | 始终 | 经过验证、指向此池的 DEX Screener 链接。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{"data":[{"pair_address":"0x2222222222222222222222222222222222222222","dex_id":"uniswap","quote_symbol":"WETH","price_usd":"0.25","liquidity_usd":"250000.00","fetched_at":"2026-09-09T12:00:00Z","url":"https://dexscreener.com/ethereum/0x2222222222222222222222222222222222222222"}]}POST添加自定义代币或重新定价/v1/projects/{project_id}/payment-token-assets/custom读取 + 写入
使用配置的链节点验证自定义合约并注册,无需 CoinGecko 上架。固定美元价格或所选自动 DEX 池属于此项目,不属于代码或其他项目。重复同一身份会更新项目价格,不改变现有启用/禁用策略。
- 注册后,在商店 payment-assets 端点选择 asset_id;仅注册绝不会启用商店支付方式。
- 自定义和目录代币共用每项目 20 个代币上限。不同链上的相同合约属于不同支付资产。
- 已存在的目录合约返回 409:应使用目录注册,以保留自动市场汇率。自定义代码绝不会借用同名代币价格。
- 固定价格是运营者估算。自动 DEX 价格是通过 DEX Screener 获取的所选池现货观察值,不是抗操纵预言机。商店加价与向上取整仍适用,并使用新鲜法币汇率。已发出报价不变。
- DEX 模式先发现池,再发送 price_mode: dex 和 dex_pair_address,并省略 price_usd。共享后台任务每分钟刷新所选池。检查失败或价格超过五分钟会使代币退出新报价,不会静默回退到固定价格或同名代码。
- 只接受标准 ERC-20 和经典 SPL 代币。拒绝 Token-2022/扩展和仅原生币的链。技术验证不是发行方/合约安全审计;转账扣费、弹性供应或黑名单代币可能不兼容。
- 客户端超时至少设为 60 秒。验证有时限,可尝试备用节点。无效输入返回 400;链/合约检查失败返回 422;身份冲突或超限返回 409。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | 必需 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给此可写凭据的项目。 |
自定义代币注册
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| chain_slug | string | 必需 | ethereum、base、bnb-chain、hyperliquid、avalanche、polygon、arbitrum、optimism 或 solana。此合约的链固定不变。 |
| contract_address | string | 必需 | ERC-20 合约(0x 加 40 个十六进制字符)或经典 SPL mint。节点验证网络身份和准确小数位;拒绝调用方提供的小数位和 RPC URL。 |
| name / symbol | string / string | 必需 | 显示名称(1–80 字符)和代码(1–16 个字母/数字/点/下划线/连字符,首字符须为字母或数字)。此端点不能重命名已有身份。 |
| price_mode | fixed | dex | 可选 | 为向后兼容,默认 fixed。DEX 使用按准确链和合约发现的特定池。 |
| price_usd | decimal string | fixed 模式 | 一个代币的固定美元价值,必须为正,最多 30 位小数,最大 1000000000000000000000000。不接受指数或浮点数。dex 模式省略。 |
| dex_pair_address | string | dex 模式 | 来自 payment-token-dex-pools 的池地址。dex 模式必需,fixed 模式省略。每次保存时服务器重新检查池身份、价格、流动性和活跃度。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{"data":{"asset_id":"44444444-4444-4444-8444-444444444444"}}GET列出商店支付方式/v1/projects/{project_id}/stores/{store_id}/payment-assets只读
在 data 列出链上资产,并在 lightning 单独提供 Lightning 就绪状态。链上方式需要就绪的链钱包。Lightning 使用商店所选的已验证外部收款连接,与链上 Bitcoin 钱包独立。
- selected 为链上配置;wallet_readiness 是实时资格门槛。
- lightning 响应成员包含 payment_rail: lightning、symbol: BTC、asset_decimals: 11、enabled 和 ready,绝不包含节点凭据。在商店控制台配置此方式;更新 assets 数组不改变 Lightning。
- confirmation_policy 仅适用于链上方式。Lightning 无区块确认即结算,要求完整 BOLT11 金额,不适用部分付款容差。
- 同一链上的原生币和代币使用该链钱包的同一账单目的地址。
- 嵌入钱包摘要仅表示就绪状态,余额为空;当前数值请用专用项目钱包路由获取。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给凭据的项目;可能已暂停。 |
| store_id | path UUID | 属于 project_id 的商店;可能已暂停。 |
PaymentAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 项目和商店策略路由使用的持久支付资产标识符。 |
| asset_key | string | 始终 | 规范 CAIP 风格原生币或合约资产标识。 |
| chain_slug / network | string | 始终 | Wholly Crypto 链标识符及配置的网络。 |
| caip_network_id / caip_asset_id | string / string|null | 始终 | 规范网络和资产标识。 |
| asset_kind | native | token | 始终 | 结算使用链币种还是经验证的合约/mint。 |
| payment_rail | string | 始终 | 运行时通道:utxo、evm-native、solana-native、account-native、privacy-native 或 token-transfer。 |
| symbol / name / decimals | string / string / integer | 始终 | 显示标识及精确最小单位精度。 |
| contract_address | string | null | 始终 | 代币的规范 ERC-20 合约或 SPL mint;原生资产为 null。 |
| coingecko_id | string | null | 始终 | 发现/定价标识。自定义合约为 null;绝不要通过代码推断市场价。仅 CoinGecko 元数据不会使代币可选。 |
| custom_token | boolean | 始终 | 经过链上验证的自定义合约,采用项目范围固定美元价格或选定 DEX 池定价。 |
| icon_path | path | null | 始终 | 可用时提供本地缓存的代币图标。 |
| token_standard | erc20 | spl-token | null | 始终 | 经过验证的运行时代币标准;原生资产为 null。 |
| metadata_verified_at | timestamp | null | 始终 | 已注册代币的链上元数据验证时间。 |
| payment_supported / scanner_ready / balance_ready | boolean | 始终 | 构建时注册表门槛。scanner_ready 表示已安装付款扫描运行组件;付款确认需要配置数量的健康且角色准确的服务商(默认 2,可选 1);自 6.0.6 起,临时扫描器不可用不阻止创建账单。balance_ready 仅在余额适配器已实现时为 true。 |
| default_finality_mode | confirmations | finalized | 始终 | 新项目策略继承的默认最终性模型。 |
| default_required_confirmations / default_monitoring_minutes | integer | 始终 | 默认确认与监控策略。 |
StorePaymentAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| asset | PaymentAsset | 始终 | 项目可见的原生币或已验证代币资产。 |
| project_policy | ProjectAssetPolicy | null | 始终 | 上级项目策略。 |
| selected | boolean | 始终 | 该方式是否属于商店保存的期望配置。项目策略、钱包、已安装适配器和定价有效时提供。临时扫描器故障不会将其从新账单移除。 |
| display_order | integer | null | 始终 | 选中时在商店结账中的顺序。 |
| confirmation_policy | StoreConfirmationPolicy | null | 始终 | 项目已配置资产的有效商店策略。无项目策略时为 null。 |
| wallet | WalletSummary | null | 始终 | 原生币和代币共用的链钱包。 |
| wallet_readiness | readiness enum | 始终 | 仅钱包/策略状态;扫描器前提请用 receive_readiness。 |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | 共享收款设置加上商店接受状态。使用缓存观察值,不是预留或保证。创建时重新检查要求和账单实际汇率。 |
StoreConfirmationPolicy
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| finality_mode | confirmations | finalized | 始终 | 结算使用可配置区块数还是网络最终性。 |
| project_required_confirmations | integer | 始终 | 未设置商店覆盖时,未来账单使用的当前项目默认值。 |
| override_required_confirmations | integer | null | 始终 | 商店指定确认数,或 null 以继承项目默认值。 |
| effective_required_confirmations | integer | 始终 | 此商店和资产的新账单将保存的确认数快照。 |
| editable | boolean | 始终 | 无法覆盖最终性策略的 finalized 网络为 false。 |
| minimum_required_confirmations | integer | 始终 | 按链设定的下限,包含边界;仅支持检测时接受的通道显示 0。 |
| maximum_required_confirmations | integer | 始终 | 按链设定的上限,包含边界。 |
WalletSummary
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | 始终 | 钱包、所属项目和链原生资产标识符。 |
| chain_slug / network | string | 始终 | 钱包区块链与网络。 |
| asset_symbol / asset_name | string | 始终 | 链原生币显示标识。 |
| status | pending | active | disabled | error | 始终 | 钱包运行状态。 |
| label | string | 始终 | 运营者标签。 |
| public_key / primary_address | string | null | 始终 | 公共钱包标识;不暴露助记词或私钥。 |
| derivation_scheme / address_format | string | null | 始终 | 地址策略与格式。 |
| backup_confirmed_at | timestamp | null | 始终 | 运营者确认恢复备份后为非 null。 |
| activation_required / activation_verified_at | boolean / timestamp|null | 始终 | XRP 和 Stellar 共享账户在运营者向显示地址注资,且配置的扫描服务商验证该准确账户前不可用。持久证明不会过期;实时扫描器健康单独用于付款验证,不用于创建账单。 |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | 钱包列表包含:项目收款设置和链扫描器前提。与余额、代币 Gas 和发送就绪状态分开。其他钱包响应可能为 null。 |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | 始终 | 经脱敏的 Monero 外部只读 wallet-RPC 绑定状态。包含端点、认证模式、account-0 主地址、技术证明标记/高度和运营者确认时间戳;绝不序列化凭据、钱包密钥或钱包文件。 |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | 始终 | 控制台端机密披露审计元数据。 |
| next_receive_index | integer | 始终 | 下一个预留子地址索引。 |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | 始终 | 钱包扫描状态。 |
| balances | WalletAssetBalance[] | 始终 | 全部 30 条原生链通道及已验证 ERC-20、SPL 资产的缓存余额。Monero 需要配置外部只读 wallet-RPC。 |
| total_value_usd | decimal string | null | 始终 | 有当前美元价格的余额参考合计。 |
| balance_status | pending | refreshing | fresh | stale | error | unknown | 始终 | 汇总缓存时效;unknown 是防御性回退,这些状态都不能证明账单已结算。 |
| balance_checked_at | timestamp | null | 始终 | 汇总中相关成功余额检查的最早时间。 |
| recent_payments | WalletRecentPayment[] | 始终 | 归属于此准确钱包的最多三条最新有效 detected、confirming 或 final 记录。 |
| created_at / updated_at | RFC 3339 timestamp | 始终 | 钱包创建与最近更新时间。 |
ReceiveReadiness
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| ready | boolean | 始终 | 收款设置检查通过。不表示支出就绪、Gas、余额刷新或保证的未来报价。 |
| invoice_creatable | boolean | 6.0.6+ | 配置允许在扫描器临时警告下创建账单方式。币种定价在创建时检查。这不是付款验证:ready 为 false 时 invoice_creatable 仍可为 true。缺失钱包、禁用策略和不支持的适配器仍会安全拒绝。 |
| checked_at | timestamp | 始终 | 评估时间。列表查询不发起网络请求或分配地址。 |
| issues | PaymentMethodIssue[] | 始终 | 就绪时为空;否则为收款警告或配置阻塞原因。检查 invoice_creatable 以区分临时扫描警告与账单设置失败。 |
PaymentMethodIssue
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | 已知时 | 标识受影响的链和资产。Lightning 可省略 asset_id。 |
| reason_code | string | 始终 | scanner_provider_quorum、scanner_not_checked、scanner_unavailable、wallet_missing、wallet_disabled、wallet_backup_required、wallet_key_unavailable、wallet_activation_required、monero_binding_unavailable、rate_unavailable、custom_rate_unavailable、lightning_unavailable、project_disabled、store_disabled、chain_disabled、asset_disabled 或 asset_not_accepted。 |
| message / action | string | 可用时 | 面向商户的说明和操作标识符:chain_connections、wallets、rates、payment_methods、project_settings 或 store_settings。不含凭据或私有服务商 URL。 |
| required_endpoint_role | string | null | 链上 | 首选扫描器 API 角色(旧字段)。完整兼容列表请使用 accepted_endpoint_roles。基础节点健康不能证明支持付款历史。 |
| accepted_endpoint_roles | string[] | null | 链上 | 兼容 API 方言,不证明端点历史或容量。原始 node-rpc 支持 BTC/BCH/LTC/DOGE/DASH 和透明 ZEC(完整解码区块,1–48 次确认)、已固化原生 TRX、经 algod 的原生 ALGO、经 Octez 的 XTZ、经 SCALE 元数据的已最终确认 Asset Hub DOT,以及通过 Stellar RPC 且带账单 memo ID 的原生 XLM。裁剪或不完整历史不合格。这些原始适配器不增加代币通道。索引 API 仍可选择,见下方通道表。混合原始/索引来源独立验证有界窗口;默认仍需两个独立服务商,不能是同一运营者的别名。基础节点高度、ORDnet 链信息及非 EVM 通道的 EVM 中继都不是收款证明。Monero 仍需绑定项目的只读 wallet-RPC。 |
| healthy_endpoints | integer | 链上 | 匹配的健康端点数量,不是独立服务商数量。 |
| usable_independent_providers / required_independent_providers | integer | 链上 | 可用验证名额,最多两个。required_independent_providers 是链设置:默认 2,管理员明确选择后可为 1。双服务商模式需要不同服务商键且不同主机。禁用、过时(超过十分钟)或冷却中的来源不占名额。Lightning 使用自己的连接规则。 |
| last_checked_at | timestamp | null | 链上 | 最新的匹配端点健康检查,与评估时间不同。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [
{ "asset": { "id": "ASSET_UUID", "chain_slug": "ethereum", "symbol": "USDC", "asset_kind": "token", "token_standard": "erc20", "scanner_ready": true }, "project_policy": { "enabled": true, "required_confirmations": 12 }, "selected": true, "display_order": 0, "confirmation_policy": { "finality_mode": "confirmations", "project_required_confirmations": 12, "override_required_confirmations": 3, "effective_required_confirmations": 3, "editable": true, "minimum_required_confirmations": 0, "maximum_required_confirmations": 48 }, "wallet": { "id": "WALLET_UUID", "status": "active" }, "wallet_readiness": "ready" }
],
"lightning": { "payment_rail": "lightning", "symbol": "BTC", "asset_decimals": 11, "enabled": true, "ready": true }
}PUT替换商店支付方式/v1/projects/{project_id}/stores/{store_id}/payment-assets读取 + 写入
原子地替换商店完整有序资产子集并返回刷新列表。省略的资产会取消选择。
- 数组最多接受 64 个唯一资产和显示顺序。
- 选择项是已保存的期望配置,可在钱包备份前或链暂停时预先设置。创建账单仍仅提供项目策略、原生父级策略、钱包和运行时检查均就绪的方式。
- 发送空 assets 数组可配置为不接受任何支付方式。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | 必需 | application/json |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给凭据的项目;可能已暂停。 |
| store_id | path UUID | 属于 project_id 的商店;可能已暂停。 |
商店支付资产选择请求体
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| assets | StoreAssetSelection[] | 必需 | 完整替换列表,最多 64 项。每项含唯一 asset_id 和 0–10,000 范围的唯一 display_order。 |
PaymentAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 项目和商店策略路由使用的持久支付资产标识符。 |
| asset_key | string | 始终 | 规范 CAIP 风格原生币或合约资产标识。 |
| chain_slug / network | string | 始终 | Wholly Crypto 链标识符及配置的网络。 |
| caip_network_id / caip_asset_id | string / string|null | 始终 | 规范网络和资产标识。 |
| asset_kind | native | token | 始终 | 结算使用链币种还是经验证的合约/mint。 |
| payment_rail | string | 始终 | 运行时通道:utxo、evm-native、solana-native、account-native、privacy-native 或 token-transfer。 |
| symbol / name / decimals | string / string / integer | 始终 | 显示标识及精确最小单位精度。 |
| contract_address | string | null | 始终 | 代币的规范 ERC-20 合约或 SPL mint;原生资产为 null。 |
| coingecko_id | string | null | 始终 | 发现/定价标识。自定义合约为 null;绝不要通过代码推断市场价。仅 CoinGecko 元数据不会使代币可选。 |
| custom_token | boolean | 始终 | 经过链上验证的自定义合约,采用项目范围固定美元价格或选定 DEX 池定价。 |
| icon_path | path | null | 始终 | 可用时提供本地缓存的代币图标。 |
| token_standard | erc20 | spl-token | null | 始终 | 经过验证的运行时代币标准;原生资产为 null。 |
| metadata_verified_at | timestamp | null | 始终 | 已注册代币的链上元数据验证时间。 |
| payment_supported / scanner_ready / balance_ready | boolean | 始终 | 构建时注册表门槛。scanner_ready 表示已安装付款扫描运行组件;付款确认需要配置数量的健康且角色准确的服务商(默认 2,可选 1);自 6.0.6 起,临时扫描器不可用不阻止创建账单。balance_ready 仅在余额适配器已实现时为 true。 |
| default_finality_mode | confirmations | finalized | 始终 | 新项目策略继承的默认最终性模型。 |
| default_required_confirmations / default_monitoring_minutes | integer | 始终 | 默认确认与监控策略。 |
StorePaymentAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| asset | PaymentAsset | 始终 | 项目可见的原生币或已验证代币资产。 |
| project_policy | ProjectAssetPolicy | null | 始终 | 上级项目策略。 |
| selected | boolean | 始终 | 该方式是否属于商店保存的期望配置。项目策略、钱包、已安装适配器和定价有效时提供。临时扫描器故障不会将其从新账单移除。 |
| display_order | integer | null | 始终 | 选中时在商店结账中的顺序。 |
| confirmation_policy | StoreConfirmationPolicy | null | 始终 | 项目已配置资产的有效商店策略。无项目策略时为 null。 |
| wallet | WalletSummary | null | 始终 | 原生币和代币共用的链钱包。 |
| wallet_readiness | readiness enum | 始终 | 仅钱包/策略状态;扫描器前提请用 receive_readiness。 |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | 共享收款设置加上商店接受状态。使用缓存观察值,不是预留或保证。创建时重新检查要求和账单实际汇率。 |
StoreConfirmationPolicy
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| finality_mode | confirmations | finalized | 始终 | 结算使用可配置区块数还是网络最终性。 |
| project_required_confirmations | integer | 始终 | 未设置商店覆盖时,未来账单使用的当前项目默认值。 |
| override_required_confirmations | integer | null | 始终 | 商店指定确认数,或 null 以继承项目默认值。 |
| effective_required_confirmations | integer | 始终 | 此商店和资产的新账单将保存的确认数快照。 |
| editable | boolean | 始终 | 无法覆盖最终性策略的 finalized 网络为 false。 |
| minimum_required_confirmations | integer | 始终 | 按链设定的下限,包含边界;仅支持检测时接受的通道显示 0。 |
| maximum_required_confirmations | integer | 始终 | 按链设定的上限,包含边界。 |
ReceiveReadiness
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| ready | boolean | 始终 | 收款设置检查通过。不表示支出就绪、Gas、余额刷新或保证的未来报价。 |
| invoice_creatable | boolean | 6.0.6+ | 配置允许在扫描器临时警告下创建账单方式。币种定价在创建时检查。这不是付款验证:ready 为 false 时 invoice_creatable 仍可为 true。缺失钱包、禁用策略和不支持的适配器仍会安全拒绝。 |
| checked_at | timestamp | 始终 | 评估时间。列表查询不发起网络请求或分配地址。 |
| issues | PaymentMethodIssue[] | 始终 | 就绪时为空;否则为收款警告或配置阻塞原因。检查 invoice_creatable 以区分临时扫描警告与账单设置失败。 |
PaymentMethodIssue
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | 已知时 | 标识受影响的链和资产。Lightning 可省略 asset_id。 |
| reason_code | string | 始终 | scanner_provider_quorum、scanner_not_checked、scanner_unavailable、wallet_missing、wallet_disabled、wallet_backup_required、wallet_key_unavailable、wallet_activation_required、monero_binding_unavailable、rate_unavailable、custom_rate_unavailable、lightning_unavailable、project_disabled、store_disabled、chain_disabled、asset_disabled 或 asset_not_accepted。 |
| message / action | string | 可用时 | 面向商户的说明和操作标识符:chain_connections、wallets、rates、payment_methods、project_settings 或 store_settings。不含凭据或私有服务商 URL。 |
| required_endpoint_role | string | null | 链上 | 首选扫描器 API 角色(旧字段)。完整兼容列表请使用 accepted_endpoint_roles。基础节点健康不能证明支持付款历史。 |
| accepted_endpoint_roles | string[] | null | 链上 | 兼容 API 方言,不证明端点历史或容量。原始 node-rpc 支持 BTC/BCH/LTC/DOGE/DASH 和透明 ZEC(完整解码区块,1–48 次确认)、已固化原生 TRX、经 algod 的原生 ALGO、经 Octez 的 XTZ、经 SCALE 元数据的已最终确认 Asset Hub DOT,以及通过 Stellar RPC 且带账单 memo ID 的原生 XLM。裁剪或不完整历史不合格。这些原始适配器不增加代币通道。索引 API 仍可选择,见下方通道表。混合原始/索引来源独立验证有界窗口;默认仍需两个独立服务商,不能是同一运营者的别名。基础节点高度、ORDnet 链信息及非 EVM 通道的 EVM 中继都不是收款证明。Monero 仍需绑定项目的只读 wallet-RPC。 |
| healthy_endpoints | integer | 链上 | 匹配的健康端点数量,不是独立服务商数量。 |
| usable_independent_providers / required_independent_providers | integer | 链上 | 可用验证名额,最多两个。required_independent_providers 是链设置:默认 2,管理员明确选择后可为 1。双服务商模式需要不同服务商键且不同主机。禁用、过时(超过十分钟)或冷却中的来源不占名额。Lightning 使用自己的连接规则。 |
| last_checked_at | timestamp | null | 链上 | 最新的匹配端点健康检查,与评估时间不同。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [
{ "asset": { "id": "44444444-4444-4444-8444-444444444444", "symbol": "USDC" }, "selected": true, "display_order": 0, "confirmation_policy": { "finality_mode": "confirmations", "project_required_confirmations": 12, "override_required_confirmations": null, "effective_required_confirmations": 12, "editable": true, "minimum_required_confirmations": 0, "maximum_required_confirmations": 48 }, "wallet_readiness": "ready" }
]
}PUT设置商店确认策略/v1/projects/{project_id}/stores/{store_id}/payment-assets/{asset_id}/confirmation-policy读取 + 写入
设置或清除单个商店确认数覆盖,并返回刷新后的支付方式列表。该资产必须已被商店选择。项目、商店、链或钱包暂停期间仍可配置。
- 使用 {"strategy":"inherit"} 移除商店覆盖,让未来账单遵循当前项目默认值。
- 最终确认网络返回 editable false,并使用网络最终性;不接受自定义区块数覆盖。
- 值 0 表示检测时即接受,没有网络确认,也无链重组保护。仅 minimum_required_confirmations 为 0 时可用。
- 策略更改仅影响新账单。已有账单保留创建时的项目/商店确认策略快照。
- 每次更新一项资产;同一商店资产的并发编辑须串行执行,并以刷新后的响应作为当前状态。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | 必需 | application/json |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给凭据的项目;可能已暂停。 |
| store_id | path UUID | 属于 project_id 的商店;可能已暂停。 |
| asset_id | path UUID | 当前已选择、待更新的商店支付资产。 |
商店确认策略请求体
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| strategy | inherit | custom | 必需 | 带标签的策略。inherit 移除商店覆盖;custom 需要 required_confirmations。 |
| required_confirmations | integer | 仅 custom | 在此资产返回的最小/最大值范围内的整数。未知或额外字段会被拒绝。 |
PaymentAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 项目和商店策略路由使用的持久支付资产标识符。 |
| asset_key | string | 始终 | 规范 CAIP 风格原生币或合约资产标识。 |
| chain_slug / network | string | 始终 | Wholly Crypto 链标识符及配置的网络。 |
| caip_network_id / caip_asset_id | string / string|null | 始终 | 规范网络和资产标识。 |
| asset_kind | native | token | 始终 | 结算使用链币种还是经验证的合约/mint。 |
| payment_rail | string | 始终 | 运行时通道:utxo、evm-native、solana-native、account-native、privacy-native 或 token-transfer。 |
| symbol / name / decimals | string / string / integer | 始终 | 显示标识及精确最小单位精度。 |
| contract_address | string | null | 始终 | 代币的规范 ERC-20 合约或 SPL mint;原生资产为 null。 |
| coingecko_id | string | null | 始终 | 发现/定价标识。自定义合约为 null;绝不要通过代码推断市场价。仅 CoinGecko 元数据不会使代币可选。 |
| custom_token | boolean | 始终 | 经过链上验证的自定义合约,采用项目范围固定美元价格或选定 DEX 池定价。 |
| icon_path | path | null | 始终 | 可用时提供本地缓存的代币图标。 |
| token_standard | erc20 | spl-token | null | 始终 | 经过验证的运行时代币标准;原生资产为 null。 |
| metadata_verified_at | timestamp | null | 始终 | 已注册代币的链上元数据验证时间。 |
| payment_supported / scanner_ready / balance_ready | boolean | 始终 | 构建时注册表门槛。scanner_ready 表示已安装付款扫描运行组件;付款确认需要配置数量的健康且角色准确的服务商(默认 2,可选 1);自 6.0.6 起,临时扫描器不可用不阻止创建账单。balance_ready 仅在余额适配器已实现时为 true。 |
| default_finality_mode | confirmations | finalized | 始终 | 新项目策略继承的默认最终性模型。 |
| default_required_confirmations / default_monitoring_minutes | integer | 始终 | 默认确认与监控策略。 |
StorePaymentAsset
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| asset | PaymentAsset | 始终 | 项目可见的原生币或已验证代币资产。 |
| project_policy | ProjectAssetPolicy | null | 始终 | 上级项目策略。 |
| selected | boolean | 始终 | 该方式是否属于商店保存的期望配置。项目策略、钱包、已安装适配器和定价有效时提供。临时扫描器故障不会将其从新账单移除。 |
| display_order | integer | null | 始终 | 选中时在商店结账中的顺序。 |
| confirmation_policy | StoreConfirmationPolicy | null | 始终 | 项目已配置资产的有效商店策略。无项目策略时为 null。 |
| wallet | WalletSummary | null | 始终 | 原生币和代币共用的链钱包。 |
| wallet_readiness | readiness enum | 始终 | 仅钱包/策略状态;扫描器前提请用 receive_readiness。 |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | 共享收款设置加上商店接受状态。使用缓存观察值,不是预留或保证。创建时重新检查要求和账单实际汇率。 |
StoreConfirmationPolicy
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| finality_mode | confirmations | finalized | 始终 | 结算使用可配置区块数还是网络最终性。 |
| project_required_confirmations | integer | 始终 | 未设置商店覆盖时,未来账单使用的当前项目默认值。 |
| override_required_confirmations | integer | null | 始终 | 商店指定确认数,或 null 以继承项目默认值。 |
| effective_required_confirmations | integer | 始终 | 此商店和资产的新账单将保存的确认数快照。 |
| editable | boolean | 始终 | 无法覆盖最终性策略的 finalized 网络为 false。 |
| minimum_required_confirmations | integer | 始终 | 按链设定的下限,包含边界;仅支持检测时接受的通道显示 0。 |
| maximum_required_confirmations | integer | 始终 | 按链设定的上限,包含边界。 |
ReceiveReadiness
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| ready | boolean | 始终 | 收款设置检查通过。不表示支出就绪、Gas、余额刷新或保证的未来报价。 |
| invoice_creatable | boolean | 6.0.6+ | 配置允许在扫描器临时警告下创建账单方式。币种定价在创建时检查。这不是付款验证:ready 为 false 时 invoice_creatable 仍可为 true。缺失钱包、禁用策略和不支持的适配器仍会安全拒绝。 |
| checked_at | timestamp | 始终 | 评估时间。列表查询不发起网络请求或分配地址。 |
| issues | PaymentMethodIssue[] | 始终 | 就绪时为空;否则为收款警告或配置阻塞原因。检查 invoice_creatable 以区分临时扫描警告与账单设置失败。 |
PaymentMethodIssue
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | 已知时 | 标识受影响的链和资产。Lightning 可省略 asset_id。 |
| reason_code | string | 始终 | scanner_provider_quorum、scanner_not_checked、scanner_unavailable、wallet_missing、wallet_disabled、wallet_backup_required、wallet_key_unavailable、wallet_activation_required、monero_binding_unavailable、rate_unavailable、custom_rate_unavailable、lightning_unavailable、project_disabled、store_disabled、chain_disabled、asset_disabled 或 asset_not_accepted。 |
| message / action | string | 可用时 | 面向商户的说明和操作标识符:chain_connections、wallets、rates、payment_methods、project_settings 或 store_settings。不含凭据或私有服务商 URL。 |
| required_endpoint_role | string | null | 链上 | 首选扫描器 API 角色(旧字段)。完整兼容列表请使用 accepted_endpoint_roles。基础节点健康不能证明支持付款历史。 |
| accepted_endpoint_roles | string[] | null | 链上 | 兼容 API 方言,不证明端点历史或容量。原始 node-rpc 支持 BTC/BCH/LTC/DOGE/DASH 和透明 ZEC(完整解码区块,1–48 次确认)、已固化原生 TRX、经 algod 的原生 ALGO、经 Octez 的 XTZ、经 SCALE 元数据的已最终确认 Asset Hub DOT,以及通过 Stellar RPC 且带账单 memo ID 的原生 XLM。裁剪或不完整历史不合格。这些原始适配器不增加代币通道。索引 API 仍可选择,见下方通道表。混合原始/索引来源独立验证有界窗口;默认仍需两个独立服务商,不能是同一运营者的别名。基础节点高度、ORDnet 链信息及非 EVM 通道的 EVM 中继都不是收款证明。Monero 仍需绑定项目的只读 wallet-RPC。 |
| healthy_endpoints | integer | 链上 | 匹配的健康端点数量,不是独立服务商数量。 |
| usable_independent_providers / required_independent_providers | integer | 链上 | 可用验证名额,最多两个。required_independent_providers 是链设置:默认 2,管理员明确选择后可为 1。双服务商模式需要不同服务商键且不同主机。禁用、过时(超过十分钟)或冷却中的来源不占名额。Lightning 使用自己的连接规则。 |
| last_checked_at | timestamp | null | 链上 | 最新的匹配端点健康检查,与评估时间不同。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"strategy": "custom",
"required_confirmations": 0
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"strategy": "custom",
"required_confirmations": 0
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"strategy": "custom",
"required_confirmations": 0
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"strategy": "custom",
"required_confirmations": 0
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [
{
"asset": { "id": "YOUR_ASSET_ID", "chain_slug": "bitcoin", "symbol": "BTC" },
"selected": true,
"display_order": 0,
"confirmation_policy": {
"finality_mode": "confirmations",
"project_required_confirmations": 2,
"override_required_confirmations": 0,
"effective_required_confirmations": 0,
"editable": true,
"minimum_required_confirmations": 0,
"maximum_required_confirmations": 10000
},
"wallet_readiness": "ready"
}
]
}GET列出项目钱包与余额/v1/projects/{project_id}/wallets只读
返回公共钱包元数据,以及钱包准确链和网络上所有已注册且支持余额查询的资产。涵盖全部 30 条原生链通道,也跟踪已验证 ERC-20 和 SPL 资产。资产会立即出现,即使尚未首次扫描或不接受其付款。project_enabled 表示付款接受状态;tracking_active 独立表示只读刷新资格。Monero 需要绑定项目的外部只读 wallet-RPC。控制台“扫描余额”优先执行有界读取并显示逐资产进度/错误;只有完整周期才更新新鲜合计。账单结算仍由交易级监控和确认策略驱动,不由缓存余额决定。
- 此 Bearer 路由绝不返回助记词、私钥、加密机密或支出方法。
- 新注册同链资产在首次完整扫描前返回 null 余额和 pending 状态;绝不会编造零余额。
- 禁用项目、钱包收款、原生通道或单项资产不会停止只读余额跟踪:具有主地址的活动和禁用钱包继续刷新每项已注册、受支持的同链资产。pending 和 error 钱包不扫描。
- project_enabled 仅表示项目资产接受策略,在 tracking_active 仍为 true 时也可为 false。
- balance 和 balance_atomic 是精确字符串;price_usd、value_usd 和 total_value_usd 仅供参考,可为 null。余额新鲜不保证市场价格新鲜。
- 估值优先使用两小时内的 CoinGecko 价格。原生币及已验证的规范 USDC/USDT 可回退到已启用 Kraken/Binance 的五分钟内美元报价,主服务商优先。不假定美元锚定,不按代码为自定义代币定价;项目固定/DEX 价格独立。账单报价不变。
- Pending 表示没有完整快照。Refreshing 保留上次完成的金额和 checked_at,不表示链上转账待处理。Stale/error 金额也可能保留旧值。绝不要把不可用缓存视为零或缺少付款。常规 EVM/Solana 刷新在审查之间最多复用近期检查过的空地址 30 分钟,同时重新检查有资金、新增和变化地址。明确执行控制台“扫描余额”会请求完整扫描。
- recent_payments 每个钱包最多三条观察记录,并排除失效历史。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给凭据的已启用项目。 |
WalletSummary
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | 始终 | 钱包、所属项目和链原生资产标识符。 |
| chain_slug / network | string | 始终 | 钱包区块链与网络。 |
| asset_symbol / asset_name | string | 始终 | 链原生币显示标识。 |
| status | pending | active | disabled | error | 始终 | 钱包运行状态。 |
| label | string | 始终 | 运营者标签。 |
| public_key / primary_address | string | null | 始终 | 公共钱包标识;不暴露助记词或私钥。 |
| derivation_scheme / address_format | string | null | 始终 | 地址策略与格式。 |
| backup_confirmed_at | timestamp | null | 始终 | 运营者确认恢复备份后为非 null。 |
| activation_required / activation_verified_at | boolean / timestamp|null | 始终 | XRP 和 Stellar 共享账户在运营者向显示地址注资,且配置的扫描服务商验证该准确账户前不可用。持久证明不会过期;实时扫描器健康单独用于付款验证,不用于创建账单。 |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | 钱包列表包含:项目收款设置和链扫描器前提。与余额、代币 Gas 和发送就绪状态分开。其他钱包响应可能为 null。 |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | 始终 | 经脱敏的 Monero 外部只读 wallet-RPC 绑定状态。包含端点、认证模式、account-0 主地址、技术证明标记/高度和运营者确认时间戳;绝不序列化凭据、钱包密钥或钱包文件。 |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | 始终 | 控制台端机密披露审计元数据。 |
| next_receive_index | integer | 始终 | 下一个预留子地址索引。 |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | 始终 | 钱包扫描状态。 |
| balances | WalletAssetBalance[] | 始终 | 全部 30 条原生链通道及已验证 ERC-20、SPL 资产的缓存余额。Monero 需要配置外部只读 wallet-RPC。 |
| total_value_usd | decimal string | null | 始终 | 有当前美元价格的余额参考合计。 |
| balance_status | pending | refreshing | fresh | stale | error | unknown | 始终 | 汇总缓存时效;unknown 是防御性回退,这些状态都不能证明账单已结算。 |
| balance_checked_at | timestamp | null | 始终 | 汇总中相关成功余额检查的最早时间。 |
| recent_payments | WalletRecentPayment[] | 始终 | 归属于此准确钱包的最多三条最新有效 detected、confirming 或 final 记录。 |
| created_at / updated_at | RFC 3339 timestamp | 始终 | 钱包创建与最近更新时间。 |
WalletAssetBalance
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| wallet_id / asset_id | UUID | 始终 | 钱包和持久资产标识。 |
| project_enabled | boolean | 始终 | 项目资产策略当前是否启用此资产。 |
| active_store_count | integer | 始终 | 当前选择此资产的已启用商店数量。这是接受状态的映射,只读余额跟踪保持独立。 |
| active_store_ids | UUID[] | 始终 | 此项目当前接受该资产的已启用商店。无需另一次 API 请求,即可在本地准确筛选商店。 |
| tracking_active | boolean | 始终 | 此可读钱包和已注册同链资产是否可在后台刷新余额。项目和支付方式接受开关不暂停只读跟踪。 |
| asset_kind | native | token | 始终 | 原生币种或已验证合约/mint 资产。 |
| contract_address | string | null | 始终 | 代币合约或 mint;原生币种为 null。 |
| symbol / name / decimals | string / string / integer | 始终 | 显示标识及最小单位精度。 |
| coingecko_id | string | null | 始终 | 映射后的定价标识。 |
| balance / balance_atomic | decimal string|null / integer string|null | 始终 | 钱包主地址和已签发账单地址的精确显示余额及最小单位余额。无完整值时为 null。 |
| price_usd | decimal string | null | 始终 | 估值使用的参考缓存美元单价。 |
| value_usd | decimal string | null | 始终 | 存在当前汇率时的参考法币估值。 |
| status | pending | refreshing | fresh | stale | error | 始终 | 缓存扫描状态。refreshing 可保留已完成余额:用 checked_at 判断时效。Pending 表示没有完整快照。这些状态都不能证明转账待处理或账单已结算。 |
| checked_at | timestamp | null | 始终 | 已完成余额扫描所代表的时间。 |
| last_error | string | null | 始终 | 安全的运营者诊断。 |
WalletRecentPayment
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| invoice_public_id | UUID | 始终 | 与观察记录关联的面向客户的账单标识。 |
| chain_slug / symbol | string | 始终 | 链及原生币或已验证代币的显示符号。 |
| transaction_id / event_index | string / integer | 始终 | 规范交易与转账事件标识。 |
| amount | decimal string | 始终 | 未经浮点转换的精确观察资产金额。 |
| status | detected | confirming | final | 始终 | 当前有效观察状态。排除 reorged、replaced 和 invalid 记录。 |
| confirmations | integer | 始终 | 最新观察到的确认数。 |
| observed_at | RFC 3339 timestamp | 始终 | Wholly Crypto 首次观察到付款的时间。 |
ReceiveReadiness
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| ready | boolean | 始终 | 收款设置检查通过。不表示支出就绪、Gas、余额刷新或保证的未来报价。 |
| invoice_creatable | boolean | 6.0.6+ | 配置允许在扫描器临时警告下创建账单方式。币种定价在创建时检查。这不是付款验证:ready 为 false 时 invoice_creatable 仍可为 true。缺失钱包、禁用策略和不支持的适配器仍会安全拒绝。 |
| checked_at | timestamp | 始终 | 评估时间。列表查询不发起网络请求或分配地址。 |
| issues | PaymentMethodIssue[] | 始终 | 就绪时为空;否则为收款警告或配置阻塞原因。检查 invoice_creatable 以区分临时扫描警告与账单设置失败。 |
PaymentMethodIssue
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | 已知时 | 标识受影响的链和资产。Lightning 可省略 asset_id。 |
| reason_code | string | 始终 | scanner_provider_quorum、scanner_not_checked、scanner_unavailable、wallet_missing、wallet_disabled、wallet_backup_required、wallet_key_unavailable、wallet_activation_required、monero_binding_unavailable、rate_unavailable、custom_rate_unavailable、lightning_unavailable、project_disabled、store_disabled、chain_disabled、asset_disabled 或 asset_not_accepted。 |
| message / action | string | 可用时 | 面向商户的说明和操作标识符:chain_connections、wallets、rates、payment_methods、project_settings 或 store_settings。不含凭据或私有服务商 URL。 |
| required_endpoint_role | string | null | 链上 | 首选扫描器 API 角色(旧字段)。完整兼容列表请使用 accepted_endpoint_roles。基础节点健康不能证明支持付款历史。 |
| accepted_endpoint_roles | string[] | null | 链上 | 兼容 API 方言,不证明端点历史或容量。原始 node-rpc 支持 BTC/BCH/LTC/DOGE/DASH 和透明 ZEC(完整解码区块,1–48 次确认)、已固化原生 TRX、经 algod 的原生 ALGO、经 Octez 的 XTZ、经 SCALE 元数据的已最终确认 Asset Hub DOT,以及通过 Stellar RPC 且带账单 memo ID 的原生 XLM。裁剪或不完整历史不合格。这些原始适配器不增加代币通道。索引 API 仍可选择,见下方通道表。混合原始/索引来源独立验证有界窗口;默认仍需两个独立服务商,不能是同一运营者的别名。基础节点高度、ORDnet 链信息及非 EVM 通道的 EVM 中继都不是收款证明。Monero 仍需绑定项目的只读 wallet-RPC。 |
| healthy_endpoints | integer | 链上 | 匹配的健康端点数量,不是独立服务商数量。 |
| usable_independent_providers / required_independent_providers | integer | 链上 | 可用验证名额,最多两个。required_independent_providers 是链设置:默认 2,管理员明确选择后可为 1。双服务商模式需要不同服务商键且不同主机。禁用、过时(超过十分钟)或冷却中的来源不占名额。Lightning 使用自己的连接规则。 |
| last_checked_at | timestamp | null | 链上 | 最新的匹配端点健康检查,与评估时间不同。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [
{
"id": "55555555-5555-4555-8555-555555555555",
"project_id": "11111111-1111-4111-8111-111111111111",
"native_asset_id": "10000000-0000-4000-8000-000000000003",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_symbol": "ETH",
"asset_name": "Ethereum",
"status": "active",
"label": "Primary Ethereum wallet",
"public_key": "0x…",
"primary_address": "0x…",
"derivation_scheme": "bip44",
"address_format": "eip55",
"backup_confirmed_at": "2026-08-31T17:00:00Z",
"last_secret_revealed_at": null,
"secret_reveal_count": 0,
"next_receive_index": 43,
"last_scanned_height": 23123456,
"last_scanned_at": "2026-08-31T18:05:00Z",
"last_error": null,
"balances": [
{ "wallet_id": "55555555-5555-4555-8555-555555555555", "asset_id": "10000000-0000-4000-8000-000000000003", "project_enabled": true, "tracking_active": true, "asset_kind": "native", "contract_address": null, "symbol": "ETH", "name": "Ethereum", "decimals": 18, "coingecko_id": "ethereum", "balance": "0.125", "balance_atomic": "125000000000000000", "price_usd": "4500", "value_usd": "562.50", "status": "fresh", "checked_at": "2026-08-31T18:05:00Z", "last_error": null },
{ "wallet_id": "55555555-5555-4555-8555-555555555555", "asset_id": "10000000-0000-4000-8000-000000000099", "project_enabled": false, "tracking_active": true, "asset_kind": "token", "contract_address": "0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48", "symbol": "USDC", "name": "USDC", "decimals": 6, "coingecko_id": "usd-coin", "balance": null, "balance_atomic": null, "price_usd": "1", "value_usd": null, "status": "pending", "checked_at": null, "last_error": null }
],
"total_value_usd": "562.50",
"balance_status": "fresh",
"balance_checked_at": "2026-08-31T18:05:00Z",
"recent_payments": [
{ "invoice_public_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50", "chain_slug": "ethereum", "symbol": "USDC", "transaction_id": "0x…", "event_index": 0, "amount": "25", "status": "final", "confirmations": 12, "observed_at": "2026-08-31T18:04:00Z" }
],
"created_at": "2026-08-31T16:00:00Z",
"updated_at": "2026-08-31T18:05:00Z"
}
]
}POST创建账单/v1/projects/{project_id}/stores/{store_id}/invoices读取 + 写入
原子地创建账单及钱包目的地址、新鲜精确报价、审计历史和通知发件箱记录。使用同一凭据、Idempotency-Key 和完全相同原始请求字节重放会返回原账单。
- payment_methods 仅为此账单筛选商店已启用的方式。省略/null 保留全部商店方式;[] 无效。在“项目 → 商店 → 支付方式”查找 chain_slug 提示及显示的资产代码。API payment-assets 列表提供 chain_slug、asset.symbol 和 asset.id。接受的以太坊代币可用 {chain_slug: ethereum, asset_tickers: [USDC, USDT]};BTC 和 PEPE 在所选链上同理。代码不区分大小写,限定链范围,并只在商店内解析。两个已接受合约代码相同时,即使其中一个未就绪,也返回 400 而不任选其一;这种情况请用 asset_ids。原生资产、目录代币和自定义代币规则相同。每个链/通道只能出现一次;最终最多 64 种方式。商户版 5.4.0+:忽略未知、禁用、错误链或未接受选项。整个选择无活动且接受的匹配时使用商店默认值,否则只用匹配项。仅链条目包括所有活动且接受的链上资产。活动选定方式需要有效钱包、已安装扫描适配器及可信汇率。自 6.0.6 起,扫描器不可用、冷却、待处理/过时健康检查不会阻止创建账单或移除配置的链上方式。检测自动重试;结算仍需服务商法定数量和确认。监控 receive_readiness 并保持服务商可用:扫描器恢复前账单可能一直未验证。Monero 子地址分配和 Lightning BOLT11 生成仍需外部钱包/节点服务。失败返回 error.message 及 error.details.payment_methods,其中含 chain_slug、asset_ticker、reason_code;扫描诊断还含 required_endpoint_role、healthy_endpoints 和 required_independent_providers。TRON 接受索引历史或支持的原始已固化原生区块 API;基础健康不证明扫描器兼容。定价失败会指出资产/币种。此操作不启用未接受资产,也不改变商店策略。5.4.0 之前版本遇到未知/非活动明确选择会失败。商店设置改变不会扩展现有账单方式。Lightning 必须单独选择。重放保留原方式;同一 Idempotency-Key 更改选择会返回 409。
- checkout_appearance 支持上方列出的全部显示设置。省略字段继承、数组替换、嵌套消息字段合并;空消息对象清除该范围。解析后的设计和图片保存于此账单,不编辑商店。读取公共结账 JSON 的 appearance 可查看结果。完整请求上限 32 KiB,解析设置上限 20 KiB。
- 同一 Idempotency-Key 修改 checkout_appearance 返回 409;重试须使用相同原始字节。外观不改变金额、汇率、接受资产、确认要求、真实状态或嵌入权限。不支持 HTML、CSS、脚本或远程图片获取。
- exchange_rate_spread_percent 为此账单覆盖商店默认值:省略或 null 继承,发送 "0" 关闭。现有账单报价绝不改变。
- 先加价再向上取整。费用仍按不含加价的原始账单法币金额计算。
- 始终发送返回的 expected_amount 或 expected_amount_atomic。采用向上取整,受资产精度、金额的 0.1% 及一个法币最小单位限制。
- 重试必须保持同一凭据、Idempotency-Key 和准确请求体字节。同一键修改加价返回 409 idempotency_conflict。
- 在新报价、回调 DNS 或地址准备前先检查准确重放。每次请求仍检查凭据范围和项目/商店授权。
- 有效 ipn_url 需要商店 IPN 签名密钥。拒绝未知请求体字段。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Idempotency-Key | 必需 | 唯一的 1–128 个无空白可见 ASCII 字符。 |
| Content-Type | 建议 | application/json。当前原始请求体处理器解析 JSON,但不强制媒体类型。 |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 从“项目 → 设置 → API ID”复制项目 API ID。必须已分配给凭据;不接受可读项目标识符。 |
| store_id | path UUID | 从“项目 → 商店 → 选择商店 → 基本设置 → API ID”复制商店 API ID。默认商店也必需;必须已启用且属于 project_id。 |
账单创建请求体
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| amount | string | 必需 | 无符号普通十进制字符串,不含正负号或指数,最多 48 位整数和 30 位小数。默认须为正数。商店可在“商店 → 账单”允许零金额账单;零总额无需收款、分配地址或处理费即结算。 |
| currency | string | null | 可选 | 支持的三字母法币代码,转为大写。省略或 null 继承商店账单币种。创建还需独立可用的计费换算汇率。 |
| payment_methods | InvoicePaymentSelection[] | null | 可选 | 为此账单选择商店已启用方式。商户版 5.4.0+ 忽略未知/非活动/未接受选择;无匹配时用商店默认。省略/null 也用默认;[] 无效。绝不启用方式或改变商店设置。见下方选择结构。 |
| order_id | string | null | 可选 | 商户订单参考,去除首尾空白后 1–128 字符;拒绝控制字符。 |
| string | null | 可选 | 仅商户可见的客户邮箱,规范为实用 ASCII 地址,最多 254 字符。省略或 null 不保存邮箱。 | |
| description | string | null | 可选 | 面向客户的说明,1–500 字符;允许换行和制表符。 |
| expires_in_seconds | integer | null | 可选 | 账单报价有效期 300–86,400 秒;省略或 null 继承商店策略。 |
| exchange_rate_spread_percent | decimal string | null | 可选 | 报价加价 0–100,最多两位小数。省略或 null 继承商店默认;"0" 为此账单关闭。向上取整前应用后锁定。不改变法币账单金额或处理费基准。 |
| underpayment_tolerance_percent | decimal string | null | 可选 | 接受少付比例 0–99.99,最多两位小数。省略或 null 继承商店默认。 |
| ipn_url | string | null | 可选 | 公共 HTTPS 回调,最多 2,048 字节,不含凭据或片段。覆盖商店默认;null/省略则继承。 |
| redirect_url | string | null | 可选 | 结算后使用的 HTTPS 成功 URL,最多 2,048 字节,不嵌入凭据。省略或 null 继承商店默认,不能将其清空。 |
| cancel_url | string | null | 可选 | 结账未成功付款结束时使用的 HTTPS 返回 URL。省略或 null 继承商店默认,不能清空。 |
| redirect_automatically | boolean | null | 可选 | 省略或 null 继承商店策略。true 需要有效的 redirect_url。 |
| language | string | null | 可选 | 英语或德语 BCP 47 标签,如 en、de 或 de-DE;省略或 null 继承商店策略。 |
| checkout_appearance | CheckoutAppearanceOverride | null | 可选 | 此账单的部分显示设置。省略/null 跟随商店当前设计。对象(包括 {})会在创建时冻结解析后的设计和图片。见下方覆盖结构;不允许财务设置、HTML、CSS、JavaScript 或远程图片 URL。 |
| metadata | object | null | 可选 | 仅商户可见的 JSON 对象;省略或 null 变为 {},编码后最多 4,096 字节,嵌套最多五层。firstname、lastname、street、street2、zip、city、country、countryiso2、company 和 vatid 会经过验证、规范化,并映射到客户摘要字段。 |
InvoicePaymentSelection · 选择商店链和资产
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| chain_slug | string | 必需 | 从“项目 → 商店 → 支付方式”复制 chain_slug,或从 GET /v1/projects/{project_id}/stores/{store_id}/payment-assets 读取,例如 ethereum、base 或 bitcoin。链/通道组合只能出现一次。 |
| asset_ids | UUID[] | null | 可选 | 链上 asset.id UUID,不是合约地址或账单支付方式 ID。此字段与 asset_tickers 二选一。两者都省略时选择该链全部活动且接受的资产。[] 及重复/空 UUID 无效。5.4.0+ 忽略此商店在此链上未活动/未接受的 ID;整个选择无匹配时使用商店默认。 |
| asset_tickers | string[] | null | 可选 | 商户版 5.3.0+。BTC、USDC、PEPE 等符号,限定 chain_slug 和此商店。1–64 个唯一代码;去首尾空白、不区分大小写,允许 1–40 个 ASCII 字母/数字/点/下划线/连字符。与 asset_ids 二选一。5.4.0+ 忽略未知/非活动/未接受代码。接受符号有歧义时仍失败,需用 asset_ids。活动选定方式需要有效钱包和定价;自 6.0.6 起临时链上扫描器故障不阻止创建。Lightning 可选代码仅为 BTC。 |
| payment_rail | onchain | lightning | 可选 | 默认 onchain。选择 Bitcoin Lightning 使用 {chain_slug: bitcoin, payment_rail: lightning},不提供 asset_ids;asset_tickers 可选为 [BTC]。Bitcoin 链上不包含 Lightning。商店 Lightning 连接须已启用且就绪。 |
CheckoutAppearanceOverride · 所有字段可选
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| inherit_default_store | boolean | 可选 | true 以项目默认商店设计为基础,否则使用目标商店有效设计。之后应用覆盖并独立保存;解析后的账单标记为 false。 |
| title | string | 可选 | 结账标题,最多 120 字符。空值使用标准标题。 |
| intro / outro | string | 可选 | 纯文本,各最多 2,000 字符。介绍显示在顶部,结尾在所有状态底部。保留换行;安全文本 URL 转为链接。空字符串清除。旧 customer_message 作为 intro 别名仍接受,不要同时发送两者。 |
| intro_font_size / outro_font_size | integer | 可选 | 像素:12、14、16、18、20 或 24。未继承其他值时默认 16。 |
| theme | system | light | dim | dark | 可选 | 跟随客户设备或使用固定主题。 |
| accent_color / background_color / card_color / button_color | string | 可选 | #RRGGBB。背景、卡片和按钮可为空以使用自动颜色。文字对比度自动处理。 |
| logo_size / logo_alignment | string | 可选 | small、medium 或 large;left 或 center。 |
| images | object | 可选 | 键为 logo_light、logo_dark、favicon。省略键保留基础图片,null 移除。对象 {store_id: UUID, kind?: logo_light|logo_dark|favicon} 复用同一项目内该商店有效上传图片。kind 默认为目标键。先在“商店 → 结账”上传;从“基本设置 → API ID”复制商店 API ID。图片缺失或跨项目 ID 返回 400。不接受外部 URL 或图片数据。 |
| show_order_id / show_description / details_expanded | boolean | 可选 | 在标题下显示订单 ID 详情和纯文本说明。details_expanded 默认展开订单 ID 详情。仅控制显示,不会隐藏数据。 |
| show_project_name / show_store_name | boolean | 可选 | 商户版 5.6.0+:在客户结账页眉显示或隐藏各名称。两者默认 true。也可在“商店 → 结账”设置;像其他外观设置一样继承并保存账单快照。仅控制显示,不是数据脱敏。 |
| featured_chains | string[] | 可选 | 有序链 slug,最多 60 个唯一值(小写字母、数字、连字符,最多 64 字符)。[] 清除。只重新排序可用账单方式。 |
| featured_asset_ids / default_asset_id | UUID[] / UUID|null | 可选 | 最多 100 个有序唯一资产 ID;[] 清除。默认资产可为 null。ID 来自 payment-assets,不是付款意图 ID。绝不启用方式;已收到付款和有效客户偏好优先。 |
| messages | object | 可选 | en/de 对象,含 waiting、confirming、paid、underpaid、expired 纯字符串(各 500 字符)。仅更改提供的语言/状态;{} 清除全部消息,{en:{}} 清除英语,空状态字符串清除此状态。英语作为回退。不替换真实状态。 |
| support_email | string | 可选 | ASCII 邮箱,最多 254 字符。空值清除。 |
| support_url / terms_url / privacy_url | string | 可选 | HTTPS URL,最多 2,048 字符,不含凭据。空值清除。链接在新窗口打开。 |
| return_button_text | string | 可选 | 标签最多 60 字符。账单行为使用顶层 redirect_url/cancel_url/redirect_automatically/language。 |
账单汇总
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 内部账单 UUID。不要用于商户详情或结账路径。 |
| invoice_id | UUID | 始终 | 用于商户详情和结账路径的公共账单 UUID。 |
| project_id | UUID | 始终 | 所属项目。 |
| store_id | UUID | 始终 | 所属商店。 |
| source | manual | api | 始终 | 账单的创建方式。 |
| order_id | string | null | 始终 | 商户订单参考。 |
| string | null | 始终 | 仅供商户查看的客户邮箱,公共结账绝不返回。 | |
| customer_name | string | null | 始终 | 从私密 firstname、lastname 和 company 元数据派生的显示名称。 |
| customer_address | string | null | 始终 | 从私密 company、street、street2、zip、city、country、countryiso2 和 vatid 元数据派生的单行商户地址。 |
| description | string | null | 始终 | 面向客户的说明。 |
| amount | decimal string | 始终 | 规范账单金额。 |
| currency | string | 始终 | 标准化的账单币种/资产代码。 |
| exchange_rate_spread_percent | decimal string | 始终 | 锁定报价加价:创建时覆盖值,省略时为商店默认值。向上取整前应用;此账单上绝不改变。 |
| underpayment_tolerance_percent | decimal string | 始终 | 创建账单时保存快照的不可变接受少付百分比。 |
| status | invoice status | 始终 | new、processing、settled、expired、invalid 或 cancelled。 |
| amount_status | amount status | 始终 | none、partial、paid 或 overpaid。明确允许的零金额账单以 none 结算,不包含支付方式。 |
| timing_status | timing status | 始终 | on_time 或 late。 |
| resolution | resolution | 始终 | automatic、manually_settled 或 manually_invalidated。 |
| sequence | integer | 始终 | 单调递增的账单状态序列,从 1 开始。 |
| winning_payment_intent_id | UUID | null | 始终 | 使账单完成结算的支付方式(已选定时)。 |
| expires_at | RFC 3339 timestamp | 始终 | 报价/付款截止时间。 |
| monitoring_expires_at | RFC 3339 timestamp | 始终 | 所有支付方式配置的最晚延迟监控截止时间。 |
| settled_at | timestamp | null | 始终 | 已结算时的结算时间。 |
| cancelled_at | timestamp | null | 始终 | 已取消时的取消时间。 |
| archived_at | timestamp | null | 始终 | 已归档时的归档时间。 |
| created_at | RFC 3339 timestamp | 始终 | 创建时间。 |
| updated_at | RFC 3339 timestamp | 始终 | 最近状态更新时间。 |
账单详情附加字段
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| ipn_url | string | null | 始终 | 每张账单的有效 IPN 目标。仅商户响应提供;公共结账省略。 |
| redirect_url | string | null | 始终 | 结算后使用的有效成功 URL。 |
| cancel_url | string | null | 始终 | 结账未成功付款结束时使用的有效返回 URL。 |
| redirect_automatically | boolean | 始终 | 成功后结账是否自动重定向。 |
| checkout_language | string | 始终 | 有效结账语言标签。 |
| metadata | object | 始终 | 商户元数据。公共结账绝不返回。 |
| payment_intents | PaymentIntent[] | 始终 | 已报价支付方式和监控状态。 |
PaymentIntent
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 付款意图标识符,也用作结账二维码 intent_id。 |
| payment_rail | onchain | lightning | 始终 | 账单传输方式。Bitcoin 链上和 Lightning 可共享 asset_id;请用意图 id 加此字段,不能只用符号。此字段不同于资产目录的扫描器 payment_rail。 |
| bolt11 | string | null | 始终 | Lightning 支付请求,其他情况为 null。使用 Lightning 钱包支付此请求,绝不要向支付哈希发送链上资金。 |
| asset_id | UUID | 始终 | 已配置支付资产标识符。 |
| asset_key | string | 始终 | 规范 CAIP 风格资产键。 |
| chain_slug | string | 始终 | Wholly Crypto 链标识符。 |
| network | string | 始终 | 配置的网络,受支持支付资产当前为 mainnet。 |
| caip_network_id | string | 始终 | 规范 CAIP-2 网络标识符。 |
| caip_asset_id | string | null | 始终 | 已注册时的规范 CAIP-19 标识符。 |
| symbol | string | 始终 | 资产符号。 |
| asset_decimals | integer | 始终 | 最小单位精度。Lightning BTC 为 11(毫聪),不是链上 Bitcoin 的 8。报价为整数聪;收款保留毫聪精度。 |
| status | intent status | 始终 | pending、partial、paid、overpaid、expired 或 invalid。 |
| finality_mode | confirmations | finalized | 始终 | 最终性策略。 |
| required_confirmations | integer | 始终 | 适用时所需确认数。 |
| quote_rate | decimal string | 始终 | 每一单位账单币种对应的资产单位数,包含锁定加价。例如每 USD 对应 1.02 USDC。不是反向汇率。 |
| quote_details | object | null | 始终 | 锁定报价来源:加价前 reference_rate、unrounded_payment_amount、rounding_adjustment、pricing_provider、asset_provider、pricing_fetched_at 和 asset_fetched_at。旧账单为 null;不会编造历史值。 |
| expected_amount | decimal string | 始终 | 包含加价和向上取整后应付的精确锁定资产金额。自 4.1.1 起,识别且验证的法币稳定币(如 USDC、USDT、DAI、USDS、EURC)向上取整到最多两位小数;1.321 变为 1.33,绝非 1.32。零容差时这仍是应付金额。其他资产保留自适应精度。现有账单绝不重新定价。 |
| expected_amount_atomic | integer string | 始终 | 以资产最小单位表示的精确金额。 |
| minimum_payment_amount | decimal string | 始终 | 应用账单容差后可接受为已付的最小金额。 |
| minimum_payment_amount_atomic | integer string | 始终 | 以资产最小单位表示的精确接受阈值。 |
| received_amount | decimal string | 始终 | 观察到的金额。 |
| received_amount_atomic | integer string | 始终 | 观察到的最小单位金额。 |
| confirmed_amount | decimal string | 始终 | 已确认/最终金额。 |
| confirmed_amount_atomic | integer string | 始终 | 已确认/最终的最小单位金额。 |
| destination_address | string | 始终 | 链上收款地址,Lightning 则为 64 字符支付哈希。Lightning 使用 bolt11 付款;其哈希不是 Bitcoin 地址。 |
| destination_tag | string | null | 始终 | 通道要求的公共付款参考:XRP destination tag、Stellar memo ID 或 TON 账单备注。唯一地址通道为 null。 |
| derivation_index | integer | 始终 | 预留钱包子索引,仅商户详情提供。 |
| quote_expires_at | RFC 3339 timestamp | 始终 | 报价到期时间。 |
| monitoring_expires_at | RFC 3339 timestamp | 始终 | 此方式的延迟监控截止时间。 |
| next_check_at | timestamp | null | 始终 | 下次计划链检查。 |
| last_checked_at | timestamp | null | 始终 | 上次链检查。 |
| last_chain_height | integer | null | 始终 | 监控器观察到的最后可信高度。 |
| last_anchor_hash | string | null | 始终 | 最后监控锚点/区块哈希。 |
| last_monitor_error | string | null | 始终 | 供运营者使用的安全监控诊断。 |
| first_payment_at | timestamp | null | 始终 | 首次观察到付款的时间。 |
| fully_paid_at | timestamp | null | 始终 | 首次达到可接受最小金额的时间。 |
| finalized_at | timestamp | null | 始终 | 付款满足最终性策略的时间。 |
PaymentMethodIssue
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | 已知时 | 标识受影响的链和资产。Lightning 可省略 asset_id。 |
| reason_code | string | 始终 | scanner_provider_quorum、scanner_not_checked、scanner_unavailable、wallet_missing、wallet_disabled、wallet_backup_required、wallet_key_unavailable、wallet_activation_required、monero_binding_unavailable、rate_unavailable、custom_rate_unavailable、lightning_unavailable、project_disabled、store_disabled、chain_disabled、asset_disabled 或 asset_not_accepted。 |
| message / action | string | 可用时 | 面向商户的说明和操作标识符:chain_connections、wallets、rates、payment_methods、project_settings 或 store_settings。不含凭据或私有服务商 URL。 |
| required_endpoint_role | string | null | 链上 | 首选扫描器 API 角色(旧字段)。完整兼容列表请使用 accepted_endpoint_roles。基础节点健康不能证明支持付款历史。 |
| accepted_endpoint_roles | string[] | null | 链上 | 兼容 API 方言,不证明端点历史或容量。原始 node-rpc 支持 BTC/BCH/LTC/DOGE/DASH 和透明 ZEC(完整解码区块,1–48 次确认)、已固化原生 TRX、经 algod 的原生 ALGO、经 Octez 的 XTZ、经 SCALE 元数据的已最终确认 Asset Hub DOT,以及通过 Stellar RPC 且带账单 memo ID 的原生 XLM。裁剪或不完整历史不合格。这些原始适配器不增加代币通道。索引 API 仍可选择,见下方通道表。混合原始/索引来源独立验证有界窗口;默认仍需两个独立服务商,不能是同一运营者的别名。基础节点高度、ORDnet 链信息及非 EVM 通道的 EVM 中继都不是收款证明。Monero 仍需绑定项目的只读 wallet-RPC。 |
| healthy_endpoints | integer | 链上 | 匹配的健康端点数量,不是独立服务商数量。 |
| usable_independent_providers / required_independent_providers | integer | 链上 | 可用验证名额,最多两个。required_independent_providers 是链设置:默认 2,管理员明确选择后可为 1。双服务商模式需要不同服务商键且不同主机。禁用、过时(超过十分钟)或冷却中的来源不占名额。Lightning 使用自己的连接规则。 |
| last_checked_at | timestamp | null | 链上 | 最新的匹配端点健康检查,与评估时间不同。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: order-1042-attempt-1' \
--header 'Content-Type: application/json' \
--data-raw '{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new invoice.
const body = `{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new invoice.
$body = <<<'JSON'
{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: order-1042-attempt-1", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new invoice.
headers = {
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 201 新账单;200 精确幂等重放
{
"data": {
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "new",
"amount_status": "none",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 1,
"winning_payment_intent_id": null,
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": null,
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:00:00Z",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"metadata": { "cart_id": "cart-681", "firstname": "Ada", "lastname": "Lovelace", "street": "12 Example Street", "street2": "Suite 2", "zip": "10115", "city": "Berlin", "country": "Germany", "countryiso2": "DE", "company": "Example GmbH", "vatid": "DE123456789" },
"payment_intents": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_id": "10000000-0000-4000-8000-000000000001",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"symbol": "BTC",
"asset_decimals": 8,
"status": "pending",
"finality_mode": "confirmations",
"required_confirmations": 1,
"quote_rate": "0.000009218",
"quote_details": null,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0",
"received_amount_atomic": "0",
"confirmed_amount": "0",
"confirmed_amount_atomic": "0",
"destination_address": "bc1q…example",
"destination_tag": null,
"derivation_index": 42,
"quote_expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"next_check_at": "2026-08-31T18:00:00Z",
"last_checked_at": null,
"last_chain_height": null,
"last_anchor_hash": null,
"last_monitor_error": null,
"first_payment_at": null,
"fully_paid_at": null,
"finalized_at": null
}
]
},
"links": {
"checkout": "https://pay.example.com/invoice/0a6a98db-d93d-48ee-8c3c-fd45f90c4a50"
}
}GET列出账单/v1/projects/{project_id}/invoices只读
返回限定范围的精简账单摘要分页,最新优先,包含仅商户可见的邮箱及从已识别元数据派生的客户字段。搜索、状态和商店筛选在服务器端执行;响应含 total 和 has_more,以便稳定分页。
- 按 created_at 降序,再按内部 id 降序。
- 列表项为 InvoiceSummary 对象;email、customer_name 和 customer_address 仅供商户查看。原始元数据和付款意图请调用详情。
- 仅当 has_more 为 true 时,将下一页 offset 设为 pagination.offset + pagination.limit。
- 总数和分页从同一个可重复读数据库快照读取;并发写入在之后请求中出现。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给凭据的已启用项目。 |
| store_id | query UUID | 可选的准确商店筛选。 |
| status | query enum | 可选值:new、processing、settled、expired、invalid 或 cancelled。 |
| search | query string | 可选:不区分大小写的发票 ID、订单 ID 或邮箱前缀;完整的发票 UUID;或描述及已识别客户字段中的子字符串。所有元数据键以及文本、数字和布尔值(包括嵌套对象/数组)也支持索引词前缀搜索:每个搜索词都必须匹配,标点符号视为分隔符。去除首尾空白后最多 100 个字符,不能包含控制字符。元数据匹配不会将原始元数据加入列表响应;请通过发票详情读取。 |
| limit | query integer | 可选,1–100;默认 50。 |
| offset | query integer | 可选,0–1,000,000;默认值为 0。 |
账单汇总
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 内部账单 UUID。不要用于商户详情或结账路径。 |
| invoice_id | UUID | 始终 | 用于商户详情和结账路径的公共账单 UUID。 |
| project_id | UUID | 始终 | 所属项目。 |
| store_id | UUID | 始终 | 所属商店。 |
| source | manual | api | 始终 | 账单的创建方式。 |
| order_id | string | null | 始终 | 商户订单参考。 |
| string | null | 始终 | 仅供商户查看的客户邮箱,公共结账绝不返回。 | |
| customer_name | string | null | 始终 | 从私密 firstname、lastname 和 company 元数据派生的显示名称。 |
| customer_address | string | null | 始终 | 从私密 company、street、street2、zip、city、country、countryiso2 和 vatid 元数据派生的单行商户地址。 |
| description | string | null | 始终 | 面向客户的说明。 |
| amount | decimal string | 始终 | 规范账单金额。 |
| currency | string | 始终 | 标准化的账单币种/资产代码。 |
| exchange_rate_spread_percent | decimal string | 始终 | 锁定报价加价:创建时覆盖值,省略时为商店默认值。向上取整前应用;此账单上绝不改变。 |
| underpayment_tolerance_percent | decimal string | 始终 | 创建账单时保存快照的不可变接受少付百分比。 |
| status | invoice status | 始终 | new、processing、settled、expired、invalid 或 cancelled。 |
| amount_status | amount status | 始终 | none、partial、paid 或 overpaid。明确允许的零金额账单以 none 结算,不包含支付方式。 |
| timing_status | timing status | 始终 | on_time 或 late。 |
| resolution | resolution | 始终 | automatic、manually_settled 或 manually_invalidated。 |
| sequence | integer | 始终 | 单调递增的账单状态序列,从 1 开始。 |
| winning_payment_intent_id | UUID | null | 始终 | 使账单完成结算的支付方式(已选定时)。 |
| expires_at | RFC 3339 timestamp | 始终 | 报价/付款截止时间。 |
| monitoring_expires_at | RFC 3339 timestamp | 始终 | 所有支付方式配置的最晚延迟监控截止时间。 |
| settled_at | timestamp | null | 始终 | 已结算时的结算时间。 |
| cancelled_at | timestamp | null | 始终 | 已取消时的取消时间。 |
| archived_at | timestamp | null | 始终 | 已归档时的归档时间。 |
| created_at | RFC 3339 timestamp | 始终 | 创建时间。 |
| updated_at | RFC 3339 timestamp | 始终 | 最近状态更新时间。 |
发票分页
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| limit | integer | 始终 | 实际每页条数,1–100。 |
| offset | integer | 始终 | 实际行偏移量,从零开始,范围为 0–1,000,000。 |
| total | integer | 始终 | 页面快照中符合项目、店铺、状态和搜索筛选条件的总行数。 |
| has_more | boolean | 始终 | 当偏移量加上返回行数小于总数时为 true。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": [
{
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "processing",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 3,
"winning_payment_intent_id": "33333333-3333-4333-8333-333333333333",
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": null,
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:04:10Z"
}
],
"pagination": {
"limit": 50,
"offset": 0,
"total": 143,
"has_more": true
}
}GET获取账单/v1/projects/{project_id}/invoices/{invoice_id}只读
返回完整的商户发票详情及当前有效的结账 URL。请使用此路由进行轮询和对账。
- 在限定范围的查询中,如果公开 ID 不属于已授权项目,会有意返回 invoice_not_found。
- links.checkout 使用“店铺 → 基本 → 店铺域名”:先选择此店铺的有效 pay 主机名,再选择其默认店铺的设置,最后使用系统主域名。已停用、草稿或服务类型不符的主机名会被忽略。创建响应和 MCP 响应也遵循此规则;链接在响应时解析,包括幂等重放。已签名回调中的链接在事件创建时固定,重试时不会重写。这些偏好设置仅生成链接,不会重定向流量或更改 IP 限制。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给凭据的已启用项目。 |
| invoice_id | path UUID | 创建或列表查询时返回的 invoice_id,而不是内部 id。 |
账单汇总
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 内部账单 UUID。不要用于商户详情或结账路径。 |
| invoice_id | UUID | 始终 | 用于商户详情和结账路径的公共账单 UUID。 |
| project_id | UUID | 始终 | 所属项目。 |
| store_id | UUID | 始终 | 所属商店。 |
| source | manual | api | 始终 | 账单的创建方式。 |
| order_id | string | null | 始终 | 商户订单参考。 |
| string | null | 始终 | 仅供商户查看的客户邮箱,公共结账绝不返回。 | |
| customer_name | string | null | 始终 | 从私密 firstname、lastname 和 company 元数据派生的显示名称。 |
| customer_address | string | null | 始终 | 从私密 company、street、street2、zip、city、country、countryiso2 和 vatid 元数据派生的单行商户地址。 |
| description | string | null | 始终 | 面向客户的说明。 |
| amount | decimal string | 始终 | 规范账单金额。 |
| currency | string | 始终 | 标准化的账单币种/资产代码。 |
| exchange_rate_spread_percent | decimal string | 始终 | 锁定报价加价:创建时覆盖值,省略时为商店默认值。向上取整前应用;此账单上绝不改变。 |
| underpayment_tolerance_percent | decimal string | 始终 | 创建账单时保存快照的不可变接受少付百分比。 |
| status | invoice status | 始终 | new、processing、settled、expired、invalid 或 cancelled。 |
| amount_status | amount status | 始终 | none、partial、paid 或 overpaid。明确允许的零金额账单以 none 结算,不包含支付方式。 |
| timing_status | timing status | 始终 | on_time 或 late。 |
| resolution | resolution | 始终 | automatic、manually_settled 或 manually_invalidated。 |
| sequence | integer | 始终 | 单调递增的账单状态序列,从 1 开始。 |
| winning_payment_intent_id | UUID | null | 始终 | 使账单完成结算的支付方式(已选定时)。 |
| expires_at | RFC 3339 timestamp | 始终 | 报价/付款截止时间。 |
| monitoring_expires_at | RFC 3339 timestamp | 始终 | 所有支付方式配置的最晚延迟监控截止时间。 |
| settled_at | timestamp | null | 始终 | 已结算时的结算时间。 |
| cancelled_at | timestamp | null | 始终 | 已取消时的取消时间。 |
| archived_at | timestamp | null | 始终 | 已归档时的归档时间。 |
| created_at | RFC 3339 timestamp | 始终 | 创建时间。 |
| updated_at | RFC 3339 timestamp | 始终 | 最近状态更新时间。 |
账单详情附加字段
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| ipn_url | string | null | 始终 | 每张账单的有效 IPN 目标。仅商户响应提供;公共结账省略。 |
| redirect_url | string | null | 始终 | 结算后使用的有效成功 URL。 |
| cancel_url | string | null | 始终 | 结账未成功付款结束时使用的有效返回 URL。 |
| redirect_automatically | boolean | 始终 | 成功后结账是否自动重定向。 |
| checkout_language | string | 始终 | 有效结账语言标签。 |
| metadata | object | 始终 | 商户元数据。公共结账绝不返回。 |
| payment_intents | PaymentIntent[] | 始终 | 已报价支付方式和监控状态。 |
PaymentIntent
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| id | UUID | 始终 | 付款意图标识符,也用作结账二维码 intent_id。 |
| payment_rail | onchain | lightning | 始终 | 账单传输方式。Bitcoin 链上和 Lightning 可共享 asset_id;请用意图 id 加此字段,不能只用符号。此字段不同于资产目录的扫描器 payment_rail。 |
| bolt11 | string | null | 始终 | Lightning 支付请求,其他情况为 null。使用 Lightning 钱包支付此请求,绝不要向支付哈希发送链上资金。 |
| asset_id | UUID | 始终 | 已配置支付资产标识符。 |
| asset_key | string | 始终 | 规范 CAIP 风格资产键。 |
| chain_slug | string | 始终 | Wholly Crypto 链标识符。 |
| network | string | 始终 | 配置的网络,受支持支付资产当前为 mainnet。 |
| caip_network_id | string | 始终 | 规范 CAIP-2 网络标识符。 |
| caip_asset_id | string | null | 始终 | 已注册时的规范 CAIP-19 标识符。 |
| symbol | string | 始终 | 资产符号。 |
| asset_decimals | integer | 始终 | 最小单位精度。Lightning BTC 为 11(毫聪),不是链上 Bitcoin 的 8。报价为整数聪;收款保留毫聪精度。 |
| status | intent status | 始终 | pending、partial、paid、overpaid、expired 或 invalid。 |
| finality_mode | confirmations | finalized | 始终 | 最终性策略。 |
| required_confirmations | integer | 始终 | 适用时所需确认数。 |
| quote_rate | decimal string | 始终 | 每一单位账单币种对应的资产单位数,包含锁定加价。例如每 USD 对应 1.02 USDC。不是反向汇率。 |
| quote_details | object | null | 始终 | 锁定报价来源:加价前 reference_rate、unrounded_payment_amount、rounding_adjustment、pricing_provider、asset_provider、pricing_fetched_at 和 asset_fetched_at。旧账单为 null;不会编造历史值。 |
| expected_amount | decimal string | 始终 | 包含加价和向上取整后应付的精确锁定资产金额。自 4.1.1 起,识别且验证的法币稳定币(如 USDC、USDT、DAI、USDS、EURC)向上取整到最多两位小数;1.321 变为 1.33,绝非 1.32。零容差时这仍是应付金额。其他资产保留自适应精度。现有账单绝不重新定价。 |
| expected_amount_atomic | integer string | 始终 | 以资产最小单位表示的精确金额。 |
| minimum_payment_amount | decimal string | 始终 | 应用账单容差后可接受为已付的最小金额。 |
| minimum_payment_amount_atomic | integer string | 始终 | 以资产最小单位表示的精确接受阈值。 |
| received_amount | decimal string | 始终 | 观察到的金额。 |
| received_amount_atomic | integer string | 始终 | 观察到的最小单位金额。 |
| confirmed_amount | decimal string | 始终 | 已确认/最终金额。 |
| confirmed_amount_atomic | integer string | 始终 | 已确认/最终的最小单位金额。 |
| destination_address | string | 始终 | 链上收款地址,Lightning 则为 64 字符支付哈希。Lightning 使用 bolt11 付款;其哈希不是 Bitcoin 地址。 |
| destination_tag | string | null | 始终 | 通道要求的公共付款参考:XRP destination tag、Stellar memo ID 或 TON 账单备注。唯一地址通道为 null。 |
| derivation_index | integer | 始终 | 预留钱包子索引,仅商户详情提供。 |
| quote_expires_at | RFC 3339 timestamp | 始终 | 报价到期时间。 |
| monitoring_expires_at | RFC 3339 timestamp | 始终 | 此方式的延迟监控截止时间。 |
| next_check_at | timestamp | null | 始终 | 下次计划链检查。 |
| last_checked_at | timestamp | null | 始终 | 上次链检查。 |
| last_chain_height | integer | null | 始终 | 监控器观察到的最后可信高度。 |
| last_anchor_hash | string | null | 始终 | 最后监控锚点/区块哈希。 |
| last_monitor_error | string | null | 始终 | 供运营者使用的安全监控诊断。 |
| first_payment_at | timestamp | null | 始终 | 首次观察到付款的时间。 |
| fully_paid_at | timestamp | null | 始终 | 首次达到可接受最小金额的时间。 |
| finalized_at | timestamp | null | 始终 | 付款满足最终性策略的时间。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": {
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "settled",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 4,
"winning_payment_intent_id": "33333333-3333-4333-8333-333333333333",
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": "2026-08-31T18:05:00Z",
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:05:00Z",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"metadata": { "cart_id": "cart-681", "firstname": "Ada", "lastname": "Lovelace", "street": "12 Example Street", "street2": "Suite 2", "zip": "10115", "city": "Berlin", "country": "Germany", "countryiso2": "DE", "company": "Example GmbH", "vatid": "DE123456789" },
"payment_intents": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_id": "10000000-0000-4000-8000-000000000001",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"symbol": "BTC",
"asset_decimals": 8,
"status": "paid",
"finality_mode": "confirmations",
"required_confirmations": 1,
"quote_rate": "0.000009218",
"quote_details": null,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0.0004554",
"received_amount_atomic": "45540",
"confirmed_amount": "0.0004554",
"confirmed_amount_atomic": "45540",
"destination_address": "bc1q…example",
"destination_tag": null,
"derivation_index": 42,
"quote_expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"next_check_at": null,
"last_checked_at": "2026-08-31T18:05:00Z",
"last_chain_height": 912345,
"last_anchor_hash": "000000000000000000example",
"last_monitor_error": null,
"first_payment_at": "2026-08-31T18:03:00Z",
"fully_paid_at": "2026-08-31T18:03:00Z",
"finalized_at": "2026-08-31T18:05:00Z"
}
]
},
"links": {
"checkout": "https://pay.example.com/invoice/0a6a98db-d93d-48ee-8c3c-fd45f90c4a50"
}
}GET列出账单付款/v1/projects/{project_id}/invoices/{invoice_id}/payments只读
完整的当前转账历史,包括已失效的观察记录。当回调标记 payments_truncated 时请使用此接口。这反映当前状态,不是对旧事件的重建。
- 一条观察记录可以是代币日志、UTXO 输出或其他支付通道的转账,不一定对应唯一的交易哈希。请按 payment_id 去重;transaction_id 与 event_index 共同标识链上转账。
- status 为 detected、confirming、final、reorged、replaced 或 invalid。只有 counts_towards_received 的观察记录才计入已收金额。切勿将不同资产的金额相加。
- Lightning 记录使用 payment_hash,transaction_id、确认数和区块浏览器链接为 null;BTC 精度为 11(毫聪)。不会公开原像、BOLT11 或钱包机密。
- 先按 observed_at 降序,再按 payment_id 降序排列。计数和当前页使用同一个可重复读快照;后续页面可能因新付款到达而变化。对实时发票分页时,请按 payment_id 去重。
- 现有的只读项目范围、IP 限制和每凭据速率限制仍然适用。除非回调中链接的源与您配置的 API 主机一致,否则切勿携带令牌访问该链接。
| 请求头 | 是否必需 | 规则 |
|---|---|---|
| Authorization | 必需 | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | 建议 | application/json |
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 分配给此凭据的项目。 |
| invoice_id | path UUID | 创建时返回的公开 invoice_id。 |
| payment_method_id | optional query UUID | 限定为发票的一种付款方式。 |
| limit | query integer | 1–100;默认值为 25。 |
| offset | query integer | 0–1,000,000;默认值为 0。 |
请求
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"invoice_id": "11111111-2222-4333-8444-555555555555",
"data": [{
"payment_id": "44444444-4444-4444-8444-444444444444",
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"transaction_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"payment_hash": null,
"event_index": 12,
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_id": "55555555-5555-4555-8555-555555555555",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"symbol": "USDC",
"asset_decimals": 6,
"amount": "58.17342",
"amount_atomic": "58173420",
"status": "final",
"counts_towards_received": true,
"confirmations": 2,
"block_height": 25975377,
"observed_at": "2026-09-14T12:03:00Z",
"chain_time": "2026-09-14T12:02:48Z",
"finalized_at": "2026-09-14T12:04:00Z",
"explorer_name": "Etherscan",
"explorer_url": "https://etherscan.io/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}],
"pagination": {"limit": 25, "offset": 0, "total": 1, "has_more": false}
}GET结账页面外壳/公开
托管结账主机的根路径,可提供结账应用,但不选择发票。客户集成通常应使用 links.checkout。
- 不需要 Bearer 令牌。
- 托管结账边缘服务允许 GET/HEAD,拒绝其他方法。
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/" \
--output 'checkout.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout.html").write_bytes(response.read())响应示例 · 200 text/html
<!doctype html>
<!-- Hosted Wholly Crypto checkout shell -->GET托管结账页面/invoice/{invoice_id}公开
面向客户的 HTML 结账页面。页面从同一结账主机获取结账所需的安全 JSON。除非店铺开启嵌入并明确允许父页面的 HTTPS 源,否则禁止嵌入。
- 不接受也不需要 Bearer 令牌。
- 即使发票不存在,HTML 外壳本身仍返回 200;随后发起的结账 JSON 请求会收到 invoice_not_found。
- 响应设置 no-store、noindex,并包含针对该发票的 frame-ancestors CSP。
- 项目或店铺被禁用,或发票未知时,不会公开结账数据。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| invoice_id | path UUID | 商户 API 返回的公开发票 UUID。 |
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID" \
--output 'checkout.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout.html").write_bytes(response.read())响应示例 · 200 text/html
<!doctype html>
<!-- Hosted Wholly Crypto checkout application -->GET可安全用于结账的账单数据/checkout-api/invoices/{invoice_id}公开
仅返回渲染结账页面所需的字段。有意省略内部 ID、客户邮箱和派生地址字段、IPN URL、商户元数据、钱包 ID、派生路径及监控诊断信息。
- 不需要 Bearer 令牌。
- Cache-Control 为 no-store,且禁用搜索索引。
- 将 invoice_id 视为可供客户访问资源的凭据数据;避免不必要地公开。
- asset_icon_url 是同源本地资源;客户结账页面无需联系 CoinGecko 即可显示图标。
- 当 destination_tag 不为 null 时,请在地址旁显示它,并一同提供复制功能:它是必填的 XRP 目标标签、Stellar memo ID 或 TON 发票备注,必须原样发送。
- 对于已验证代币,asset_kind 为 token,contract_address 标识准确的 ERC-20 合约或 SPL mint,token_standard 标识支付通道,payment_uri 包含该代币标识。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| invoice_id | path UUID | 公开发票 UUID。 |
公开结账发票
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| invoice_id | UUID | 始终 | 公开发票 UUID。 |
| order_id | string | null | 始终 | 商户订单参考。 |
| description | string | null | 始终 | 面向客户的说明。 |
| amount | decimal string | 始终 | 发票金额。 |
| currency | string | 始终 | 发票币种。 |
| exchange_rate_spread_percent | decimal string | 始终 | 创建时锁定的实际报价价差,包括单张发票的覆盖值。 |
| underpayment_tolerance_percent | decimal string | 始终 | 此发票可接受的欠付百分比。 |
| status | invoice status | 始终 | 当前发票状态。 |
| amount_status | amount status | 始终 | none、partial、paid 或 overpaid。明确允许的零金额账单以 none 结算,不包含支付方式。 |
| timing_status | timing status | 始终 | on_time 或 late。 |
| sequence | integer | 始终 | 当前状态序列号。 |
| active_payment_method_id | UUID | null | 始终 | 已收到资金的所列付款方式。结账页面会保持使用此方式,避免欠付后使用不兼容的资产继续付款。 |
| payment_method_locked | boolean | 始终 | 有效付款选定 active_payment_method_id 后为 true。 |
| server_time | RFC 3339 timestamp | 始终 | 为此响应记录的服务器时间;请结合 expires_at 使用,以避免客户设备时钟偏差。 |
| expires_at | RFC 3339 timestamp | 始终 | 发票截止时间。 |
| expires_in_seconds | integer | 始终 | 在 server_time 时剩余的整秒数,向上取整,最低为零。 |
| payment_open | boolean | 始终 | 仅当 new 或 processing 状态的发票尚未截止,且至少有一种可付款方式仍有待付金额时为 true。 |
| redirect_url | string | null | 始终 | 成功结算后的客户返回地址。 |
| cancel_url | string | null | 始终 | 未成功结算而离开时的客户返回地址。 |
| redirect_automatically | boolean | 始终 | 自动跳转策略。 |
| checkout_language | string | 始终 | 结账语言。 |
| project | object | 始终 | name、checkout_title、checkout_description、theme、accent_color 和 logo_url。 |
| store | object | 始终 | 公开店铺名称。 |
| appearance | CheckoutAppearance | 始终 | 实际显示样式:如果提供了单张发票的覆盖设置,则使用固定的覆盖值,否则使用店铺当前设计。绝不会更改财务字段或安全警告。 |
| payment_methods | CheckoutPaymentMethod[] | 始终 | 可安全用于结账页面的付款方式。 |
CheckoutAppearance
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| inherit_default_store | boolean | 始终 | 当外观由项目的默认店铺提供时为 true。独立店铺和固定的发票覆盖设置为 false。 |
| invoice_override | boolean | 始终 | 在创建发票时提供了 checkout_appearance,则为 true。省略或设为 null 时保持 false。 |
| title / intro / outro | string | 始终 | 商户标题、顶部消息和底部消息,均为纯文本。intro 取代 customer_message;旧的已存文案会保留。切勿作为标记语言执行。 |
| intro_font_size / outro_font_size | integer | 始终 | 字体大小,单位为像素:12、14、16、18、20 或 24。 |
| customer_message | string | 始终 | intro 的已弃用兼容别名。新集成请使用 intro。 |
| theme | system | light | dim | dark | 始终 | 使用客户设备偏好或固定主题。 |
| accent_color / background_color / card_color / button_color | string | 始终 | 严格使用 #RRGGBB 颜色格式。可选颜色留空时使用自动值;前景对比度会自动计算。 |
| logo_size / logo_alignment | string | 始终 | small、medium 或 large;left 或 center。图片完整容纳,不会裁剪。 |
| images | object | 始终 | 可选的 logo_light、logo_dark 和 favicon URL:限定范围、同源且已规范化的 PNG 图片。 |
| show_order_id / show_description / details_expanded | boolean | 始终 | 订单 ID 可见性、标题下方的描述,以及订单 ID 是否初始展开。金额始终可见;这些是显示控制,不是数据脱敏。 |
| show_project_name / show_store_name | boolean | 始终 | Merchant 5.6.0+:页头名称可见性。两项默认均为 true。项目和店铺标识仍会保留在 JSON 中。 |
| featured_chains / featured_asset_ids | array | 始终 | 有序偏好设置,仅适用于发票中已包含的付款方式。缺失或已禁用的方式会被忽略。 |
| default_asset_id | UUID | null | 始终 | 建议的初始付款方式。有效的已记住客户偏好或已收到资金的方式优先。 |
| messages | object | 始终 | 以 waiting、confirming、paid、underpaid 和 expired 为键的 en/de 纯文本。回退到英语。仅作补充,绝不替代实际状态。 |
| support_email / support_url / terms_url / privacy_url | string | 始终 | 可选的联系信息和 HTTPS 链接,URL 中不能含凭据。外部链接在新窗口打开。 |
| return_button_text | string | 始终 | 仅为可选标签。成功或取消返回地址及跳转策略仍属于发票设置。 |
CheckoutPaymentMethod
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| payment_rail | onchain | lightning | 始终 | Lightning 仍是一种 Bitcoin 付款方式,与链上 BTC 分开。请用支付意图 id 和支付通道识别选项,不要仅凭 asset_id。 |
| bolt11 | string | null | 始终 | 已签名的 Lightning 请求;链上方式为 null。payable 变为 false 后切勿付款。 |
| payment_hash | string | null | 始终 | 用于对账的 Lightning 付款哈希,不是收款地址。链上方式为 null。 |
| id | UUID | 始终 | 支付意图标识符。 |
| asset_id | UUID | 始终 | 外观偏好设置使用的资产 UUID;不同于此发票的支付意图 id。 |
| asset_key | string | 始终 | 规范资产键。 |
| chain_slug / chain_name | string | 始终 | 链的机器名称和显示名称。 |
| network | string | 始终 | 支付网络。 |
| caip_network_id | string | 始终 | 用于明确区分所选链的规范网络标识。 |
| caip_asset_id | string | null | 始终 | 规范且准确的资产标识;适用时包含已验证的代币合约或 mint。 |
| asset_name / symbol | string | 始终 | 支付资产的显示值。 |
| asset_icon_url | string | null | 始终 | 同源、已本地缓存的资产图标;不存在经过验证的 CoinGecko 映射时为 null。 |
| asset_kind | native | token | 始终 | 区分原生币与合约或 mint 付款。 |
| contract_address | string | null | 始终 | 代币的规范 ERC-20 合约或 SPL mint;原生币为 null。 |
| token_standard | erc20 | spl-token | null | 始终 | 已验证的代币运行时;原生币为 null。 |
| asset_decimals | integer | 始终 | 最小单位精度:Lightning BTC 毫聪为 11,链上 BTC 聪为 8。 |
| status | intent status | 始终 | 当前付款方式状态。 |
| payable | boolean | 始终 | 仅当此特定方式当前可接受付款时为 true;其他资产已收到资金后,非活动方式为 false。 |
| finality_mode / required_confirmations | string / integer | 始终 | 最终性策略。 |
| expected_amount / expected_amount_atomic | decimal / integer string | 始终 | 完整锁定报价,包含显示单位和实际链上单位。已识别的法币稳定币报价最多保留两位小数,并始终在应用价差后向上取整;其他资产使用自适应精度。实际代币小数位、已收资金和部分付款后的剩余金额保持精确。请原样使用返回的金额。 |
| minimum_payment_amount / minimum_payment_amount_atomic | decimal / integer string | 始终 | 应用欠付容差后的可接受结算门槛。 |
| received_amount / received_amount_atomic | decimal / integer string | 始终 | 观察到的金额。 |
| remaining_amount | decimal string | 始终 | 达到可接受门槛仍需支付的精确显示金额,最低为零。 |
| remaining_amount_atomic | integer string | 始终 | 距离可接受门槛的欠款,以最小单位表示。这不是要求支付的金额:容差只影响是否接受结算。 |
| confirmed_amount / confirmed_amount_atomic | decimal / integer string | 始终 | 已确认/最终金额。 |
| destination_address / destination_tag | string / string|null | 始终 | 链上目标地址和可选附加标识。Lightning 使用不带标签的付款哈希;请改用 bolt11/payment_uri 付款。 |
| quote_expires_at | RFC 3339 timestamp | 始终 | 报价到期时间。 |
| payment_uri | string | null | 始终 | 符合链要求的支付请求:ERC-681、Solana Pay、原生 URI 或 lightning:<bolt11>。含金额的请求使用完整预期金额减去已收资金,绝不使用容差门槛。payable 为 false 时为 null,包括欠付在容差范围内已被接受后。Lightning 二维码编码完整的 Lightning 请求,而不是付款哈希。 |
| qr_url | path | null | 始终 | 包含序列号和精确剩余金额修订版本的同源 SVG 二维码路径;payable 为 false 时为 null。SVG 使用 no-store。 |
| address_explorer_name / address_explorer_url | string|null | 始终 | 支持时使用经过验证的主网区块浏览器备用链接。 |
| transaction_count | integer | 始终 | 此付款方式已观察到的、不同的公开有效交易总数。 |
| transactions_truncated | boolean | 始终 | transaction_count 大于返回的近期交易列表长度时为 true。 |
| transactions | CheckoutTransaction[] | 始终 | 最多 10 笔最新的公开有效交易。精确的已收总额不受此显示上限影响。 |
CheckoutTransaction
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| transaction_id | string | 始终 | 观察到的交易标识符。 |
| status | detected | confirming | final | 始终 | 公开观察状态。 |
| confirmations | integer | 始终 | 观察到的确认数。 |
| block_height | integer | null | 始终 | 观察到的区块或账本高度。 |
| explorer_name | string | 返回时 | 经过验证的固定区块浏览器名称。 |
| explorer_url | string | 返回时 | 经过验证的固定主网区块浏览器 URL。 |
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": {
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"order_id": "order-1042",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "processing",
"amount_status": "partial",
"timing_status": "on_time",
"sequence": 3,
"active_payment_method_id": "33333333-3333-4333-8333-333333333333",
"payment_method_locked": true,
"server_time": "2026-08-31T18:10:00Z",
"expires_at": "2026-08-31T18:15:00Z",
"expires_in_seconds": 300,
"payment_open": true,
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"project": {
"name": "Example project",
"checkout_title": "Complete your payment",
"checkout_description": "Send the exact amount shown.",
"theme": "system",
"accent_color": "#42e39b",
"logo_url": "/checkout-api/invoices/…/logo/…/image.png"
},
"store": { "name": "Online shop" },
"payment_methods": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"chain_name": "Bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"asset_name": "Bitcoin",
"symbol": "BTC",
"asset_icon_url": "/assets/coingecko/bitcoin.png",
"asset_kind": "native",
"contract_address": null,
"token_standard": null,
"asset_decimals": 8,
"status": "partial",
"payable": true,
"finality_mode": "confirmations",
"required_confirmations": 1,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0.0002",
"received_amount_atomic": "20000",
"remaining_amount": "0.0002554",
"remaining_amount_atomic": "25540",
"confirmed_amount": "0",
"confirmed_amount_atomic": "0",
"destination_address": "bc1q…example",
"destination_tag": null,
"quote_expires_at": "2026-08-31T18:15:00Z",
"payment_uri": "bitcoin:bc1q…example?amount=0.00026",
"qr_url": "/checkout-api/invoices/…/payment-methods/…/qr.svg?sequence=3&amount_atomic=26000",
"address_explorer_name": "mempool.space",
"address_explorer_url": "https://mempool.space/address/…",
"transaction_count": 0,
"transactions_truncated": false,
"transactions": []
}
]
}
}GET商店结账预览/invoice/preview/{project_id}公开
使用示例金额和真实的已接受资产元数据,显示店铺保存的外观。无需创建付款即可切换 waiting、confirming、paid、underpaid 和 expired 示例。
- 预览仅用于品牌外观展示,绝不能作为付款请求发送给客户。
- 不含收款地址、可付款二维码、钱包操作、跳转或付款轮询。示例不会更改实际发票状态。
- 响应使用 no-store、noindex,且不能嵌入。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 已认证控制台复制到预览链接中的项目 UUID。 |
| store_id | query UUID, optional | 属于此项目的店铺。省略时使用第一个或默认店铺。 |
| state | query string, optional | waiting、confirming、paid、underpaid 或 expired。仅用于浏览器演示。 |
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming" \
--output 'checkout-preview.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-preview.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-preview.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-preview.html").write_bytes(response.read())响应示例 · 200 text/html
<!doctype html>
<!-- Hosted branding preview; no invoice is created -->GET结账预览数据/checkout-api/previews/{project_id}公开
返回实际店铺外观及可安全公开的已接受资产元数据。payment_methods 保持为空;preview_methods 不含付款地址、报价或钱包隐私数据。
- 不接受也不需要 Bearer 令牌。
- 不返回发票、目标地址、钱包、交易、IPN、Webhook 或商户元数据。
- 请从已认证控制台获取正确的 pay 域名预览链接。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 控制台预览链接中的项目 UUID。 |
| store_id | query UUID, optional | 必须属于此项目;ID 不匹配时返回 404。未知查询字段会被拒绝。 |
CheckoutAppearance
| 字段 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
| inherit_default_store | boolean | 始终 | 当外观由项目的默认店铺提供时为 true。独立店铺和固定的发票覆盖设置为 false。 |
| invoice_override | boolean | 始终 | 在创建发票时提供了 checkout_appearance,则为 true。省略或设为 null 时保持 false。 |
| title / intro / outro | string | 始终 | 商户标题、顶部消息和底部消息,均为纯文本。intro 取代 customer_message;旧的已存文案会保留。切勿作为标记语言执行。 |
| intro_font_size / outro_font_size | integer | 始终 | 字体大小,单位为像素:12、14、16、18、20 或 24。 |
| customer_message | string | 始终 | intro 的已弃用兼容别名。新集成请使用 intro。 |
| theme | system | light | dim | dark | 始终 | 使用客户设备偏好或固定主题。 |
| accent_color / background_color / card_color / button_color | string | 始终 | 严格使用 #RRGGBB 颜色格式。可选颜色留空时使用自动值;前景对比度会自动计算。 |
| logo_size / logo_alignment | string | 始终 | small、medium 或 large;left 或 center。图片完整容纳,不会裁剪。 |
| images | object | 始终 | 可选的 logo_light、logo_dark 和 favicon URL:限定范围、同源且已规范化的 PNG 图片。 |
| show_order_id / show_description / details_expanded | boolean | 始终 | 订单 ID 可见性、标题下方的描述,以及订单 ID 是否初始展开。金额始终可见;这些是显示控制,不是数据脱敏。 |
| show_project_name / show_store_name | boolean | 始终 | Merchant 5.6.0+:页头名称可见性。两项默认均为 true。项目和店铺标识仍会保留在 JSON 中。 |
| featured_chains / featured_asset_ids | array | 始终 | 有序偏好设置,仅适用于发票中已包含的付款方式。缺失或已禁用的方式会被忽略。 |
| default_asset_id | UUID | null | 始终 | 建议的初始付款方式。有效的已记住客户偏好或已收到资金的方式优先。 |
| messages | object | 始终 | 以 waiting、confirming、paid、underpaid 和 expired 为键的 en/de 纯文本。回退到英语。仅作补充,绝不替代实际状态。 |
| support_email / support_url / terms_url / privacy_url | string | 始终 | 可选的联系信息和 HTTPS 链接,URL 中不能含凭据。外部链接在新窗口打开。 |
| return_button_text | string | 始终 | 仅为可选标签。成功或取消返回地址及跳转策略仍属于发票设置。 |
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID", {
method: "GET",
headers: {
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))响应示例 · 200 application/json
{
"data": {
"preview": true,
"invoice_id": "YOUR_PROJECT_ID",
"amount": "100.00",
"currency": "USD",
"project": {
"name": "Example project",
"checkout_title": "Complete your payment",
"checkout_description": "Choose a network and send the exact amount shown.",
"theme": "system",
"accent_color": "#42e39b",
"logo_url": "/checkout-api/previews/…/logo/…/image.png"
},
"appearance": {"inherit_default_store": true, "theme": "system", "accent_color": "#42E39B", "images": {}},
"preview_methods": [],
"payment_methods": []
}
}GET商店结账图片/checkout-api/invoices/{invoice_id}/appearance-images/{kind}/{revision}/image.png公开
返回属于此发票的规范化店铺徽标或网站图标。请使用结账数据中的 appearance.images URL。
- 请使用结账 JSON 中的 appearance.images。即使来源店铺替换或移除了上传文件,固定的发票图片仍可使用。已明确删除、发票不符、类型不符或未知的修订版本返回 404;快照绝不会回退到店铺当前图片。
- 未设置发票覆盖值时,使用店铺当前生效图片,被替换或移除的修订版本返回 404。仅支持 PNG,使用 nosniff 和私有缓存。
- 已认证控制台中的店铺图片上传仅接受受大小限制的 PNG、JPEG 或 WebP;绝不接受 SVG、HTML 或远程图片 URL。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| invoice_id | path UUID | 公开发票 UUID。 |
| kind | path enum | logo_light、logo_dark 或 favicon。 |
| revision | path UUID | 当前图片修订版本。 |
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png" \
--output 'store-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("store-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("store-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("store-logo.png").write_bytes(response.read())响应示例 · 200 image/png
(binary PNG response)GET商店预览图片/checkout-api/previews/{project_id}/stores/{store_id}/appearance-images/{kind}/{revision}/image.png公开
仅在项目、店铺、类型和当前修订版本均匹配时返回规范化预览图片。
- 请使用预览数据中的 appearance.images。未知或不匹配的 ID 返回 404。不公开任何钱包或付款信息。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 项目 UUID。 |
| store_id | path UUID | 属于此项目的店铺。 |
| kind | path enum | logo_light、logo_dark 或 favicon。 |
| revision | path UUID | 当前图片修订版本。 |
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png" \
--output 'store-preview-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("store-preview-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("store-preview-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("store-preview-logo.png").write_bytes(response.read())响应示例 · 200 image/png
(binary PNG response)GET带版本的预览标志/checkout-api/previews/{project_id}/logo/{revision}/image.png公开
仅当项目与可安全缓存的徽标修订版本相匹配时,返回规范化项目徽标。请使用预览数据中的 project.logo_url,不要自行拼接此 URL。
- 未知项目和过期徽标修订版本均返回 invoice_not_found,不会透露缺少的是哪个部分。
- 成功返回的带修订版本图片不可变,可进行缓存。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| project_id | path UUID | 项目 UUID。 |
| revision | path UUID | project.logo_url 中返回的当前结账徽标修订版本。 |
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png" \
--output 'checkout-preview-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-preview-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-preview-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-preview-logo.png").write_bytes(response.read())响应示例 · 200 image/png
(binary PNG response)GET支付二维码图片/checkout-api/invoices/{invoice_id}/payment-methods/{intent_id}/qr.svg公开
根据发票付款方式的准确链专属支付载荷,生成 512×512 SVG 二维码。
- 不需要 Bearer 令牌。
- 请使用结账 JSON 返回的、带序列号和剩余金额修订版本的 qr_url;SVG 为私有资源,使用 no-store。
- 部分付款后,请求金额为精确的剩余金额,且保持锁定到该资产。
- 过期、完成或其他付款方式处于活动状态后返回 409;请求过大无法编码时,返回 payment_qr_unavailable(422)。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| invoice_id | path UUID | 公开发票 UUID。 |
| intent_id | path UUID | 结账 JSON 中的付款方式 id。 |
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg" \
--output 'payment-qr.svg'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("payment-qr.svg", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("payment-qr.svg", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("payment-qr.svg").write_bytes(response.read())响应示例 · 200 image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">…</svg>GET带版本的结账标志/checkout-api/invoices/{invoice_id}/logo/{revision}/image.png公开
仅当发票与当前徽标修订版本匹配时,返回规范化项目结账徽标。请优先使用结账 JSON 返回的 project.logo_url,不要自行拼接此路由。
- 不需要 Bearer 令牌。
- 公开缓存有效期为一年,并设置 immutable,因为修订版本使用内容寻址状态。
- 未知或不匹配的修订版本返回 invoice_not_found。
| 参数 | 类型 / 位置 | 规则 |
|---|---|---|
| invoice_id | path UUID | 公开发票 UUID。 |
| revision | path UUID | project.logo_url 中包含的当前结账徽标修订版本。 |
请求
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png" \
--output 'checkout-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-logo.png").write_bytes(response.read())响应示例 · 200 image/png
(binary PNG response)此参考文档适用于 Wholly Crypto 7.5.5。要查看您已安装版本的文档,请在控制台打开“设置 → API 访问 → 文档”。 查看版本.