# Coinbase Commerce 호환 API

> SDK 기본 URL과 키만 바꿔 Coinbase Commerce에서 blockwallets로 옮기세요: 지원하는 charge 엔드포인트, 웹훅과 서명, 차이점.
>
> HTML: https://blockwallets.io/docs/coinbase-commerce?lang=ko · English: https://blockwallets.io/docs/coinbase-commerce.md?lang=en

## Coinbase Commerce 호환 API

charge 엔드포인트, `X-CC-Api-Key` 헤더, charge 객체, `X-CC-Webhook-Signature`로 서명되는 웹훅이 Coinbase Commerce 문서를 따르므로 공식 SDK는 한 줄만 바꾸면 동작합니다. Coinbase Commerce는 2026-03-31부터 미국과 싱가포르 밖의 가맹점에 서비스를 제공하지 않습니다. 다른 곳에서 오셨나요? [이전 개요](https://blockwallets.io/docs/migrate?lang=ko)를 참고하세요.

내부적으로는 일반 blockwallets 청구서입니다. 수신 주소는 당신의 것이고, 결제 페이지는 저희가 호스팅하는 결제 페이지이며, 결제는 체인에서 컨펌되고 두 번째 노드와 교차 확인된 뒤에야 인정됩니다. 아래에서 세 단계를 설명하고, 이어서 각 API가 지원하는 범위와 **원래 서비스와 어떻게 다른지**를 설명합니다. 구현하지 않은 매개변수는 조용히 무시하지 않고 오류를 돌려줍니다.

### 세 단계

- 가맹점 계정을 만들고([홈페이지에서 신청](https://blockwallets.io/?lang=ko#join)) 관리 화면 “개발자 › 연동 및 키”의 “웹사이트 도메인”에 도메인을 추가합니다. 콜백 URL(`ipn_callback_url`)과 리디렉션 URL(`success_url` / `cancel_url` / `redirect_url`)은 이 도메인이나 콜백 URL의 도메인에 속해야 합니다. 키가 유출되어도 웹훅을 다른 곳으로 보낼 수 없습니다.

- 가맹점 관리 화면의 “개발자 › 연동 및 키”에서 호환 자격 증명을 만듭니다. 실서비스용 한 세트와 테스트용 한 세트입니다. NOWPayments는 **API key + IPN secret**, Coinbase Commerce는 **API key + 웹훅 공유 시크릿**을 받습니다. 한 번만 표시되며 저희는 해시 / 암호문만 보관합니다.

- 코드가 아래 기본 URL을 가리키게 하고 새 키를 사용합니다. 먼저 테스트 자격 증명으로 테스트넷에서 실행해 본 다음 실서비스로 전환하세요.

| 이전 | 이후 | 인증 헤더 | 웹훅 서명 |
| --- | --- | --- | --- |
| `https://api.commerce.coinbase.com` | `https://blockwallets.io/coinbase/` | `X-CC-Api-Key` | `X-CC-Webhook-Signature` (HMAC-SHA256) |

### Coinbase Commerce

공식 SDK는 모두 기본 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 — 부족 결제된 charge는 관리 화면에서 처리), `/checkouts`, `/events`, `/invoices`(404).

#### 웹훅

Coinbase 웹훅은 계정 단위이고 저희도 마찬가지입니다. 관리 화면에 설정한 **콜백 URL**로 갑니다(테스트 자격 증명으로 만든 charge는 테스트 콜백 URL로 감). 본문은 Coinbase 형식의 `{id, scheduled_for, attempt_number, event: {id, type, api_version, created_at, data}}`이며 `data`에 charge 전체가 들어 있습니다. `X-CC-Webhook-Signature` = HMAC-SHA256(공유 시크릿, 원문 본문)이므로 SDK의 `Webhook.verifyEventBody`를 그대로 쓸 수 있습니다. 재시도할 때 `event.id`는 그대로이고 `attempt_number`만 올라가니 `event.id`로 중복을 제거하세요.

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

// Same as the SDK's Webhook.verifySigHeader: HMAC-SHA256(shared secret, raw 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)
// or with the official SDK:Webhook.verifyEventBody(rawBody, signature, sharedSecret)
```

| 이벤트 | 언제 | 마지막 타임라인 항목 |
| --- | --- | --- |
| `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).

- 환불은 타임라인에 나타나지 않으니 관리 화면에서 확인하세요.

### 테스트 모드

테스트 자격 증명으로 만든 charge와 결제는 각 체인의 테스트넷에서 동작하며 웹훅은 테스트 IPN secret / 공유 시크릿으로 서명됩니다. 실서비스와 완전히 분리되어 있어 테스트 키로는 실서비스 결제를 볼 수 없습니다.

나머지(결제 페이지, 컨펌, 네트워크와 최소 금액, 환불)는 [메인 문서](https://blockwallets.io/docs/cryptomus?lang=ko)와 같습니다.
