# Coinbase Commerce 兼容接口

> 从 Coinbase Commerce 换到 blockwallets,只改 SDK 的 base URL 和密钥:支持的 charge 接口、webhook 与签名、与原版的差异。
>
> HTML: https://blockwallets.io/docs/coinbase-commerce · English: https://blockwallets.io/docs/coinbase-commerce.md?lang=en

## Coinbase Commerce 兼容接口

charge 接口、`X-CC-Api-Key` 请求头、charge 对象和用 `X-CC-Webhook-Signature` 签名的 webhook 都照 Coinbase Commerce 官方文档,官方 SDK 改一行就能用。Coinbase Commerce 已于 2026-03-31 停止服务美国、新加坡以外的商户。从别家迁过来?看[迁移总览](https://blockwallets.io/docs/migrate?lang=zh)。

底层就是 blockwallets 的账单:收款地址是你自己的,付款页是我们的收银台,到账后链上确认、再找第二家节点核对一遍才算付款成功。下面先讲三步接入,再分别列出两家的支持范围和**与官方接口的差异**——没实现的参数会明确报错,不会悄悄忽略。

### 三步接入

- 开通商户([首页申请](https://blockwallets.io/#join)),在商户后台「开发者 › 接入与密钥」的「网站域名」里填上你的域名。回调地址(`ipn_callback_url`)和跳转地址(`success_url` / `cancel_url` / `redirect_url`)只接受这些域名和回调地址的域名——即使密钥泄露,也没法把回调指到别处。

- 在商户后台「开发者 › 接入与密钥」页生成兼容凭据(live 和 test 各一套):NOWPayments 是 **API key + IPN secret**,Coinbase Commerce 是 **API key + webhook shared secret**。密钥只显示一次,我们这边只存哈希 / 密文。

- 把代码里的 base URL 换成下面的地址,换上新密钥。先用 test 凭据在测试网走一遍,再换 live。

| 原来 | 换成 | 鉴权头 | 回调签名 |
| --- | --- | --- | --- |
| `https://api.commerce.coinbase.com` | `https://blockwallets.io/coinbase/` | `X-CC-Api-Key` | `X-CC-Webhook-Signature`(HMAC-SHA256) |

### Coinbase Commerce

官方 SDK 都能配 base URL,改一行即可(注意结尾的 `/`):

```sdk
// Node(coinbase-commerce-node)
const { Client } = require("coinbase-commerce-node");
- Client.init(process.env.COINBASE_COMMERCE_API_KEY);
+ Client.init(process.env.COINBASE_COMMERCE_API_KEY, "https://blockwallets.io/coinbase/");

# Python(coinbase-commerce)
- client = Client(api_key=API_KEY)
+ client = Client(api_key=API_KEY, base_api_uri="https://blockwallets.io/coinbase/")

// PHP(coinbase/coinbase-commerce)
- ApiClient::init($apiKey);
+ ApiClient::init($apiKey, 'https://blockwallets.io/coinbase/');
```

```curl
curl -X POST https://blockwallets.io/coinbase/charges \
  -H "X-CC-Api-Key: $COINBASE_COMMERCE_API_KEY" -H "X-CC-Version: 2018-03-22" \
  -H "content-type: application/json" \
  -d '{"name":"Order #12345","description":"Pro plan","pricing_type":"fixed_price",
       "local_price":{"amount":"100.00","currency":"USD"},
       "redirect_url":"https://shop.example/success","cancel_url":"https://shop.example/cancel",
       "metadata":{"order_id":"12345"}}'
# → 201 {"data": {"id": "…", "code": "7C7V5ECK", "hosted_url": "https://blockwallets.io/pay/inv_…", "timeline": [{"status": "NEW", …}], …}}
```

#### 支持的接口

| 接口 | 说明 |
| --- | --- |
| `POST /charges` | 建 charge,`hosted_url` 是我们的收银台;有效 1 小时 |
| `GET /charges/:code_or_id` | 按 8 位 code 或 id 查 |
| `GET /charges` | 列表:`limit`(≤100)/ `starting_after` / `ending_before` / `order`,返回同样的 `pagination` 对象 |
| `POST /charges/:code_or_id/cancel` | 取消还没见到付款的 charge |

没有:`/charges/:code/resolve`(返回 400,少付的单在商户后台处理)、`/checkouts`、`/events`、`/invoices`(返回 404)。

#### Webhook

Coinbase 的 webhook 地址是账户级的,我们也一样:发往你在商户后台填的**回调地址**(test 凭据建的单发往 test 回调地址)。body 是 Coinbase 的 `{id, scheduled_for, attempt_number, event: {id, type, api_version, created_at, data}}`,`data` 是完整的 charge;头 `X-CC-Webhook-Signature` = HMAC-SHA256(shared secret, 原始 body),官方 SDK 的 `Webhook.verifyEventBody` 直接能用。重投时 `event.id` 不变、`attempt_number` 加一,按 `event.id` 去重即可。

```nodejs
import crypto from "node:crypto";

// 和官方 SDK 的 Webhook.verifySigHeader 一样:HMAC-SHA256(shared secret, 原始 body)
function verifyWebhook(rawBody, sig, sharedSecret) {
  const hmac = crypto.createHmac("sha256", sharedSecret).update(rawBody, "utf8").digest("hex");
  return crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(sig || ""));
}
// verifyWebhook(rawBody, req.headers["x-cc-webhook-signature"], process.env.COINBASE_WEBHOOK_SECRET)
// 官方 SDK 照用:Webhook.verifyEventBody(rawBody, signature, sharedSecret)
```

| 事件 | 什么时候 | timeline 最后一项 |
| --- | --- | --- |
| `charge:confirmed` | 已收款(链上确认并核对过;多付也算) | `COMPLETED` |
| `charge:failed` | 到期没付 / 你取消了 / 到期时收到的不够 | `EXPIRED` / `CANCELED` / `UNRESOLVED`(context `UNDERPAID`) |

查单时还能看到 `NEW`(等待付款)和 `PENDING`(链上见到付款、在等确认)。**请按 `charge:confirmed` 发货**:我们不发 `charge:created`、`charge:pending`、`charge:delayed`、`charge:resolved`。

#### 与 Coinbase Commerce 的差异

- `pricing_type` 只支持 `fixed_price`;`local_price.currency` 只支持 USD(USDC / USDT 按 1:1)。`no_price` 和其他法币返回 400。

- 付款人在收银台选币种和网络后才有地址:`addresses` 只有选中的那一个网络,`pricing` 只有 `local` 和选中后的 `settlement`;没有 `exchange_rates`、`checkout`、品牌设置。

- `metadata` 原样保存、原样返回(最多 4 KB)。

- 退款不出现在 timeline 里,在商户后台看。

### 测试模式

test 凭据建的单走各链的测试网,回调用 test 的 IPN secret / shared secret 签名,和 live 完全隔离:test 的 key 查不到 live 的单。

其余细节(收银台、确认数、网络与最低金额、退款)和 [主文档](https://blockwallets.io/docs/cryptomus?lang=zh) 一样。
