# blockwallets Cryptomus 호환 API

> blockwallets의 Cryptomus 호환 API: 청구서와 고정 지갑, 서명, 웹훅과 검증, 테스트 모드, 네트워크, 오류, 도메인만 바꿔 Cryptomus에서 이전하기.
>
> HTML: https://blockwallets.io/docs/cryptomus?lang=ko · English: https://blockwallets.io/docs/cryptomus.md?lang=en

## Cryptomus 호환 API

blockwallets로 USDT와 USDC, 그리고 BTC, LTC, DOGE, BCH와 각 체인의 네이티브 코인(고정 환율의 미국 달러로 정산)을 받을 수 있습니다. 고객이 결제하면 저희가 체인에서 확인하고 당신에게 알려 드립니다. API, 서명, 웹훅은 **Cryptomus**와 같아서, Cryptomus를 연동해 두었다면 도메인만 바꾸면 끝입니다. 먼저 동작 방식을, 그다음 API를 설명합니다.

### 돈을 받는 두 가지 방법

둘 다 **금액에 제한이 없어** 고정 가격 주문도, 고객이 금액을 직접 입력하는 충전도 받을 수 있습니다. 결제가 끝나면 상품을 발송하거나 고객의 잔액을 충전하세요. 차이는 **결제마다 새 주소를 쓰느냐** 하나뿐입니다.

권장 · 대부분의 연동**청구서**
고객이 당신의 페이지에서 상품을 고르거나 금액을 입력하면 청구서를 만들고, 고객은 결제 페이지에 표시된 금액을 보냅니다. 청구서마다 새 주소와 카운트다운이 있고, 입금되면 실제로 받은 금액과 함께 알려 드립니다.

`POST /v1/payment` → 웹훅 `type: payment`

선택**고정 지갑**
금액을 미리 정하지 않습니다. 고객마다 네트워크별로 영구 주소를 하나씩 주고, 고객은 그 주소를 지갑에 저장해 두었다가 언제든 보낼 수 있습니다. 송금할 때마다 웹훅이 한 번씩 갑니다.

`POST /v1/wallet` → 웹훅 `type: wallet`

잘 모르겠다면 청구서를 쓰세요. 코드 없이 쓰려면 관리 화면의 [결제 링크](https://blockwallets.io/docs/cryptomus?lang=ko#links)가 대신 청구서를 만들어 줍니다. 아직 가맹점이 아니라면 [홈페이지](https://blockwallets.io/?lang=ko#join)에서 이용을 신청하세요. 승인되면 [가맹점 관리 화면](https://blockwallets.io/dashboard)에 로그인해 먼저 관리 화면 홈의 “한 건 시험 결제”로 소액을 보내 보세요(돈은 당신의 잔액으로 들어갑니다).

### 청구서의 흐름

결제에는 고객, 당신의 사이트, blockwallets, 블록체인 네 주체가 참여합니다. 다이어그램은 위에서 아래로 시간순이며, 오른쪽 열은 각 단계에서의 청구서 상태입니다.

요약하면 다섯 단계입니다.

- 청구서를 만들고(결제 링크가 대신 해 주거나 서버가 `/v1/payment`를 호출), 결제 페이지 `url`을 받아 고객을 그곳으로 보냅니다.

- 고객은 결제 페이지에서 코인과 네트워크를 고르고, 전용 주소와 금액을 받아 지갑이나 거래소에서 송금합니다.

- 체인에서 송금을 확인하고 컨펌이 충분히 쌓일 때까지 기다린 뒤, 다른 노드 제공자로 한 번 더 대조합니다(노드 하나가 거짓 데이터를 줄 수 없게).

- 청구서가 `paid`가 되면 즉시 서명된 웹훅을 `url_callback`으로 POST하고, 몇 초 뒤 결제 페이지가 고객을 `url_success`로 돌려보냅니다.

- 돈이 잔액에 들어오고, 가맹점 관리 화면에서 내 지갑으로 출금할 수 있습니다.

#### 청구서 상태

| status | 의미 | 할 일 |
| --- | --- | --- |
| `check` | 결제 대기 | — |
| `confirm_check` | 송금이 보이고 컨펌을 기다리는 중 | — |
| `wrong_amount_waiting` | 받았지만 부족함. 고객이 같은 주소로 추가 송금 가능(`is_payment_multiple`이 true일 때, 알림은 한 번) | — (추가 송금이 끝나면 `paid`가 옵니다) |
| `paid` | 결제 완료 | `payment_amount_usd` 기준으로 발송 / 충전 |
| `paid_over` | 결제 완료, 초과 결제 | 동일(`payment_amount_usd`는 받은 금액) |
| `wrong_amount` | 전액을 받지 못한 채 만료됨(`is_payment_multiple`이 false면 처음 부족하게 송금한 시점) | 충전형 서비스는 받은 `payment_amount_usd`만큼 충전할 수 있고, 쇼핑몰은 고객에게 나머지 결제를 안내 |
| `cancel` | 받은 금액 없이 만료됨 | — (나중에 도착한 돈도 입금 처리되고 알림이 갑니다) |

`is_final`이 `true`이면 상태가 더 바뀌지 않습니다(늦게 도착한 결제 제외).

### 결제 링크 (코드 없음)

가맹점 관리 화면 → “결제 › 결제 링크” → 설명과 금액 입력(네트워크를 고정하거나 고객이 고르게 함) → 만들기. 링크마다 URL과 QR 코드가 생깁니다.

- 링크를 열 때마다 새 청구서가 만들어지므로 링크 하나를 계속 재사용할 수 있습니다(예: “회원권 $99”).

- 모든 결제는 “결제 › 결제 내역” 아래에 표시되고, “자금 › 잔액과 출금”에서 내 지갑으로 출금합니다.

- “개발자 › 연동 및 키”에서 자격 증명과 웹훅 URL을 만들어 두었다면, 링크로 들어온 결제도 아래 형식으로 알려 드립니다.

- 받기를 멈추려면 “비활성화”를 누르세요. 링크는 즉시 작동을 멈춥니다.

### API 연동

#### 1. 자격 증명과 서명

가맹점 관리 화면 → “개발자 › 연동 및 키” → **merchant**(UUID)와 **API key**를 생성합니다. Live용과 Test용이 각각 한 세트씩 있습니다. Live는 실제 돈을 받고 Test는 테스트넷에서 동작합니다. **웹사이트 도메인**도 등록하세요. `url_callback`, `url_return`, `url_success`는 이 도메인에 속해야 합니다. API key는 한 번만 표시되므로 안전하게 보관하고 서버에서만 사용하세요.

기본 URL은 `https://blockwallets.io`입니다. 모든 엔드포인트는 `POST` + JSON이고 다음 헤더를 보냅니다.

- `merchant`: 가맹점 UUID(Live인지 Test인지가 여기서 결정됩니다)

- `sign`: `md5(base64(요청 본문) + API key)`. 본문은 보내는 JSON 문자열 그대로입니다

성공하면 `{"state": 0, "result": {…}}`, 실패하면 `{"state": 1, "message": "…"}`를 돌려줍니다([오류](https://blockwallets.io/docs/cryptomus?lang=ko#errors) 참고).

아래 주요 엔드포인트에는 “직접 해보기” 패널이 있습니다. Test용 merchant와 API key를 입력하면 페이지가 브라우저에서 요청에 서명하고 엔드포인트를 호출한 뒤 요청과 응답 전체를 보여 줍니다(자격 증명은 페이지 메모리에만 있습니다). 서명이 맞지 않으면 [서명 검사기](https://blockwallets.io/docs/sign?lang=ko)로 단계별로 대조해 보세요.

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

async function call(path, data) {
  const body = JSON.stringify(data);
  const sign = crypto.createHash("md5").update(Buffer.from(body).toString("base64") + process.env.BW_API_KEY).digest("hex");
  const res = await fetch("https://blockwallets.io" + path, {
    method: "POST",
    headers: { merchant: process.env.BW_MERCHANT, sign, "Content-Type": "application/json" },
    body,
  });
  return res.json();
}
```

```php
function bw_call(string $path, array $data): array {
    $body = json_encode($data, JSON_UNESCAPED_UNICODE);
    $sign = md5(base64_encode($body) . getenv('BW_API_KEY'));
    $ch = curl_init('https://blockwallets.io' . $path);
    curl_setopt_array($ch, [
        CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['merchant: ' . getenv('BW_MERCHANT'), 'sign: ' . $sign, 'Content-Type: application/json'],
    ]);
    return json_decode(curl_exec($ch), true);
}
```

```python
import base64, hashlib, json, os, requests

def bw_call(path: str, data: dict) -> dict:
    body = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
    sign = hashlib.md5((base64.b64encode(body.encode()).decode() + os.environ["BW_API_KEY"]).encode()).hexdigest()
    headers = {"merchant": os.environ["BW_MERCHANT"], "sign": sign, "Content-Type": "application/json"}
    return requests.post("https://blockwallets.io" + path, data=body.encode(), headers=headers).json()
```

#### 2. 청구서 만들기 POST /v1/payment

고객이 주문하거나 충전 페이지에서 금액을 입력하면 `https://blockwallets.io/v1/payment`를 호출하고, 응답의 `result.url`로 고객을 보냅니다.

```json
{
  "amount": "42.00",
  "currency": "USD",
  "order_id": "order_1042",
  "url_callback": "https://shop.example/webhooks/crypto",
  "url_return": "https://shop.example/cart",
  "url_success": "https://shop.example/thanks?order=1042"
}
```

| 매개변수 | 필수 | 설명 |
| --- | --- | --- |
| `amount` | 예 | 문자열로 된 금액, 소수점 둘째 자리까지: `"42.00"`. 당신이 정한 가격 또는 고객이 입력한 숫자이며, 선택한 네트워크의 최소 금액보다 작으면 안 됩니다([네트워크](https://blockwallets.io/docs/cryptomus?lang=ko#networks) 참고) |
| `currency` | 예 | `USD`, `USDT`, `USDC`, 모두 1:1 |
| `order_id` | 예 | 주문 번호: 영문자, 숫자, `-`, `_`, 100자 이하. 같은 order_id로 다시 호출하면 기존 청구서를 돌려줍니다(주문이 중복 생성되지 않음) |
| `url_callback` | 아니요 | 웹훅 URL. 생략하면 웹훅을 보내지 않습니다 |
| `url_return` | 아니요 | 결제 전에 “뒤로”를 누르면 이동할 주소 |
| `url_success` | 아니요 | 결제 후 고객이 이동할 주소 |
| `network` | 아니요 | 네트워크를 미리 지정: `tron` / `bsc` / `eth` / `base` / `polygon` / `arbitrum` / `optimism` / `avalanche` / `xlayer` / `btc` / `ltc` / `doge` / `bch` / `sol` / `ton`. 생략하면 고객이 결제 페이지에서 고릅니다 |
| `to_currency` | 아니요 | 해당 네트워크의 코인: `USDT` / `USDC`. `btc` / `ltc` / `doge` / `bch`에서는 `BTC` / `LTC` / `DOGE` / `BCH`(선택) |
| `lifetime` | 아니요 | 청구서 유효 시간(초), 300 – 43200, 기본값 3600. 600 미만은 600으로 처리됩니다 |
| `accuracy_payment_percent` | 아니요 | 부족 결제를 어디까지 결제 완료로 볼지, 0 – 5, 기본값 0. `100` = 도착하는 대로 인정: 컨펌되자마자 `paid`(더 받았으면 `paid_over`)이며, 웹훅의 `payment_amount_usd` 기준으로 충전하면 됩니다. 충전형 서비스에 알맞습니다. 생략하면 관리 화면 “설정 › 결제 규칙 및 수수료율”의 “수취 금액 규칙”이 적용되고, “도착한 만큼 충전”은 100을 뜻합니다 |
| `subtract` | 아니요 | 플랫폼 수수료 중 고객이 내는 비율, 0 – 100. `payer_amount`에 더해집니다(플랫폼 수수료는 1%이며 거래액이 많을수록 낮아집니다. 1%일 때 100으로 설정하면 $100 청구서의 고객 결제액은 $101.00. [요금](https://blockwallets.io/pricing?lang=ko) 참고). 생략하면 관리 화면 “설정 › 결제 규칙 및 수수료율”의 “수수료와 부족 결제” 설정이 적용됩니다(기본값 0) |
| `is_payment_multiple` | 아니요 | 부족 결제에 추가 송금을 허용할지, 기본값 true: 상태는 `wrong_amount_waiting`이며 나머지를 같은 주소로 보내면 `paid`가 됩니다(처음 부족하게 송금한 시점에 남은 시간이 30분 미만이면 만료 시각이 그때부터 30분 뒤로 늦춰짐). false: 처음 부족하게 송금하는 즉시 `wrong_amount`가 됩니다. 생략하면 관리 화면 설정이 적용됩니다 |
| `additional_data` | 아니요 | 임의의 문자열(255자 이하), 조회와 웹훅에서 그대로 돌려줍니다 |

```json
{
  "state": 0,
  "result": {
    "uuid": "6f1c2a7e-…",
    "order_id": "order_1042",
    "amount": "42.00",
    "currency": "USD",
    "network": null,
    "address": null,
    "payer_amount": null,
    "payment_amount": null,
    "url": "https://blockwallets.io/pay/inv_01J8…",
    "expired_at": 1760965200,
    "status": "check",
    "is_final": false
  }
}
```

`network`, `address`, `payer_amount`는 고객이 네트워크를 고른 뒤에 채워집니다.

#### 3. 청구서 조회

- `POST /v1/payment/info` `{"order_id": "…"}` 또는 `{"uuid": "…"}`: 청구서 한 건을 가져오며 그 과정에서 체인도 확인합니다(10초에 최대 한 번).

- `POST /v1/payment/list`: 목록, 페이지당 15건, `date_from` / `date_to`는 선택. 다음 페이지는 응답의 `paginate.nextCursor`로 요청합니다(`?cursor=…`).

- `POST /v1/payment/services`: 사용 가능한 네트워크와 네트워크별 최소 금액.

- `POST /v1/payment/resend` `{"order_id": "…"}`: 결제 완료 / 부족 결제된 청구서의 웹훅을 다시 보냅니다.

웹훅이 기본 경로이고, 조회는 보조 수단입니다(예: 서버가 꺼져 있어 웹훅을 놓쳤을 때).

#### 4. 고정 지갑 POST /v1/wallet

고객 한 명에게 네트워크 하나당 영구 주소를 만들어 줍니다. 같은 `order_id`(보통 당신의 사용자 id) + 네트워크는 항상 같은 주소를 돌려줍니다. 모든 EVM 체인(BNB Chain, Ethereum, Base, Polygon, Arbitrum, Optimism, Avalanche, X Layer)은 주소 하나를 공유하고, TRON, BTC, LTC, DOGE, BCH, Solana, TON은 각각 자기 주소가 있습니다. 고객이 보내는 금액은 모두 입금 처리되고 송금마다 웹훅이 한 번씩 갑니다. BTC / LTC / DOGE / BCH / TRX / BNB / ETH / POL / AVAX / OKB / SOL / TON 송금은 각 송금이 확인된 시점의 환율로 미국 달러로 환산됩니다(`payment_amount_usd`).

```json
{
  "order_id": "user_42",
  "currency": "USDT",
  "network": "tron",
  "url_callback": "https://shop.example/webhooks/crypto"
}
```

```json
{
  "state": 0,
  "result": {
    "wallet_uuid": "0b9d…",
    "uuid": "5e2f…",
    "order_id": "user_42",
    "currency": "USDT",
    "network": "tron",
    "address": "TXy4…8kQ2",
    "url": "https://blockwallets.io/deposit/dac_01J8…"
  }
}
```

`result.url`은 고객에게 건넬 수 있는 입금 전용 페이지입니다(주소 + QR 코드 + 최근 입금 내역). 해당 네트워크의 최소 금액(보통 $1, BTC $5) 미만 송금은 자동으로 입금 처리되지 않습니다. 입금 처리가 필요하면 문의해 주세요. 주소 차단(`/v1/wallet/block-address`)은 아직 지원하지 않습니다.

### 웹훅

청구서 상태가 바뀔 때(`paid`, `paid_over`, `wrong_amount_waiting`, `wrong_amount`, `cancel`)와 고정 지갑으로 송금이 들어올 때마다 `url_callback`으로 JSON을 POST합니다. 2xx 응답이면 전달된 것으로 보고, 그렇지 않으면 1분, 5분, 30분, 2시간, 12시간, 24시간, 24시간 뒤에 다시 시도합니다. 관리 화면이나 `/v1/payment/resend`로 다시 보낼 수도 있습니다.

```json
{
  "type": "payment",
  "uuid": "6f1c2a7e-…",
  "order_id": "order_1042",
  "amount": "42.00",
  "payment_amount": "42",
  "payment_amount_usd": "42.00",
  "merchant_amount": "41.58",
  "commission": "0.42",
  "is_final": true,
  "status": "paid",
  "from": "TQ8m…",
  "wallet_address_uuid": null,
  "network": "tron",
  "currency": "USD",
  "payer_currency": "USDT",
  "payer_amount": "42",
  "payer_amount_exchange_rate": "1",
  "additional_data": null,
  "txid": "9c1e…",
  "sign": "5f0c9b…"
}
```

| type | 언제 | 입금 처리 |
| --- | --- | --- |
| `payment` | 청구서 상태가 바뀜 — `status` 참고 | `paid` / `paid_over`일 때 `payment_amount_usd`만큼 충전하고, `uuid`로 중복을 제거합니다 |
| `wallet` | 고정 지갑으로 송금이 들어옴(`status`는 항상 `paid`, `order_id`는 지갑을 만들 때 쓴 값) | `payment_amount_usd`만큼 충전합니다. 송금마다 고유한 `uuid`가 있으니 그것으로 중복을 제거하세요 |

#### 서명 검증 (필수)

서명은 웹훅 본문의 `sign` 필드입니다. `sign`을 제거하고 나머지를 PHP `json_encode`처럼 다시 인코딩한 뒤(공백 없이, `/`는 `\/`로 이스케이프, 비 ASCII 문자는 이스케이프하지 않음) `md5(base64(인코딩한 문자열) + API key)`가 `sign`과 같아야 합니다. 같은 모드의 API key를 쓰세요. 같은 웹훅이 두 번 이상 올 수 있으니 `uuid` + `status`로 중복을 제거하세요.

검증에 실패하면 받은 웹훅 본문 원문을 [서명 검사기](https://blockwallets.io/docs/sign?lang=ko)에 붙여 넣으세요. 모든 단계의 결과를 보여 주고 어디서 달라지는지 표시해 줍니다(전부 브라우저에서 실행되며 API key는 업로드되지 않습니다). 관리 화면 “개발자 › 웹훅 로그”의 모든 웹훅은 한 번의 클릭으로 검사기에서 열립니다.

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

function verify(rawBody, apiKey) {
  const { sign, ...data } = JSON.parse(rawBody);
  const json = JSON.stringify(data).replaceAll("/", "\\/");
  const expected = crypto.createHash("md5").update(Buffer.from(json).toString("base64") + apiKey).digest("hex");
  return typeof sign === "string" && sign.length === expected.length && crypto.timingSafeEqual(Buffer.from(sign), Buffer.from(expected));
}
```

```php
$data = json_decode(file_get_contents('php://input'), true);
$sign = $data['sign'] ?? '';
unset($data['sign']);
$expected = md5(base64_encode(json_encode($data, JSON_UNESCAPED_UNICODE)) . $apiKey);
if (!hash_equals($expected, $sign)) {
    http_response_code(401); exit;
}
```

```python
import base64, hashlib, hmac, json

def verify(raw_body: bytes, api_key: str) -> bool:
    data = json.loads(raw_body)
    sign = data.pop("sign", "")
    encoded = json.dumps(data, separators=(",", ":"), ensure_ascii=False).replace("/", "\\/")
    expected = hashlib.md5((base64.b64encode(encoded.encode()).decode() + api_key).encode()).hexdigest()
    return hmac.compare_digest(expected, sign)
```

### 테스트 모드

Test용 merchant와 API key로 만든 청구서는 테스트넷에서 동작하며 실제 돈이 오가지 않습니다. 결제 페이지에 “결제 시뮬레이션” 버튼(전액 / 절반 / 초과)이 표시되므로 테스트넷 코인 없이도 상태 변화와 웹훅을 확인할 수 있습니다.

- `POST /v1/test-webhook/payment`, `/v1/test-webhook/wallet`: 서명된 샘플 웹훅을 `url_callback`으로 보냅니다(필수인 `url_callback`(등록한 웹사이트 도메인의 주소)과 `currency`, `network`를 넘기고, 선택으로 `status`, `order_id`). 검증 코드를 디버깅할 때 쓰세요.

- 관리 화면 “개발자 › 연동 및 키” → “테스트 알림 보내기”: 웹훅 URL로 샘플을 보냅니다.

- 관리 화면 “개발자 › 웹훅 로그”: 전달 시도마다 시각, 대상 URL, HTTP 상태, 소요 시간, 요청 본문, 서버의 응답(처음 4 KB)을 보여 주며 주문이나 상태로 필터링하고 한 번의 클릭으로 다시 보낼 수 있습니다. 30일간 보관합니다.

### Cryptomus에서 이전하기

- 관리 화면 “개발자 › 연동 및 키” → merchant와 API key를 생성합니다.

- 코드의 `https://api.cryptomus.com`을 `https://blockwallets.io`로 바꾸고 새 merchant와 API key를 사용합니다.

- “웹사이트 도메인”에 도메인을 추가합니다(`url_callback` / `url_return` / `url_success`는 이 도메인만 허용).

지원: `/v1/payment`, `/v1/payment/info`, `/v1/payment/list`, `/v1/payment/services`, `/v1/payment/resend`, `/v1/wallet`, `/v1/test-webhook/payment`, `/v1/test-webhook/wallet`. 차이점: 청구서는 USD / USDT / USDC로만 가격을 매기며(모두 1:1, 환율 스프레드 없음), `commission`은 저희 플랫폼 수수료(받은 금액 × 청구서를 만든 시점의 가맹점 요율. 표준 1%, 거래액에 따라 최저 0.5%까지. 고객이 부담한 부분에 수수료가 두 번 붙지는 않습니다)이고 `merchant_amount` = 받은 금액 − `commission`입니다. 고객이 BTC / LTC / DOGE / BCH / TRX / BNB / ETH / POL / AVAX / OKB / SOL / TON로 결제하면 여러 거래소의 중간값으로 견적을 내 30분간 고정합니다. `payer_amount`는 보낼 코인 수량, `payer_amount_exchange_rate`는 코인 1개의 USD 가치이며, 결제 금액은 고정된 환율로 `payment_amount_usd`로 환산됩니다(코인은 저희가 보유하며 따로 교환하지 않습니다). 네트워크는 `tron` / `bsc` / `eth` / `base` / `polygon` / `arbitrum` / `optimism` / `avalanche` / `xlayer` / `btc` / `ltc` / `doge` / `bch` / `sol` / `ton`. 청구서 최소 유효 시간은 10분. `is_refresh`는 없고 고정 지갑 차단도 없습니다. 웹훅은 기본적으로 바뀌는 IP에서 오며, IP로 허용 목록을 쓴다면 관리 화면 “개발자 › 연동 및 키”에서 “고정 IP 웹훅”을 켜세요. 그러면 웹훅은 `18.163.145.41`에서만 옵니다. 허용 목록은 추가 보호 장치일 뿐이니 `sign` 검증은 계속 하세요.

**NOWPayments**나 **Coinbase Commerce**에서 오셨나요? 둘 다 호환 API가 있으니 기본 URL과 키만 바꾸면 됩니다. [이전 가이드](https://blockwallets.io/docs/migrate?lang=ko)를 참고하세요.

### 네트워크와 금액

| 코인 | 네트워크 | network 매개변수 | 최소 금액 | 도착 시간 |
| --- | --- | --- | --- | --- |
| USDT | TRON | `tron` | $1 | 약 1분 |
| USDT | BNB Chain | `bsc` | $1 | 약 1분 |
| USDC | BNB Chain | `bsc` | $1 | 약 1분 |
| USDT | Ethereum | `eth` | $5 | 약 1분 |
| USDC | Ethereum | `eth` | $5 | 약 1분 |
| USDC | Base | `base` | $1 | 약 1분 |
| USDT | Polygon | `polygon` | $1 | 약 1분 |
| USDC | Polygon | `polygon` | $1 | 약 1분 |
| USDT | Arbitrum | `arbitrum` | $1 | 약 1분 |
| USDC | Arbitrum | `arbitrum` | $1 | 약 1분 |
| USDT | Optimism | `optimism` | $1 | 약 1분 |
| USDC | Optimism | `optimism` | $1 | 약 1분 |
| USDT | Avalanche | `avalanche` | $1 | 약 1분 |
| USDC | Avalanche | `avalanche` | $1 | 약 1분 |
| USDT | X Layer | `xlayer` | $1 | 약 1분 |
| USDC | X Layer | `xlayer` | $1 | 약 1분 |
| USD1 | BNB Chain | `bsc` | $1 | 약 1분 |
| FDUSD | BNB Chain | `bsc` | $1 | 약 1분 |
| DAI | Ethereum | `eth` | $5 | 약 1분 |
| DAI | Arbitrum | `arbitrum` | $1 | 약 1분 |
| DAI | Base | `base` | $1 | 약 1분 |
| BTC | Bitcoin | `btc` | $5 | 약 10분 |
| LTC | Litecoin | `ltc` | $1 | 약 3분 |
| DOGE | Dogecoin | `doge` | $1 | 약 1분 |
| BCH | Bitcoin Cash | `bch` | $1 | 약 10분 |
| TRX | TRON | `tron` | $1 | 약 1분 |
| BNB | BNB Chain | `bsc` | $1 | 약 1분 |
| ETH | Ethereum | `eth` | $5 | 약 1분 |
| ETH | Base | `base` | $1 | 약 1분 |
| ETH | Arbitrum | `arbitrum` | $1 | 약 1분 |
| ETH | Optimism | `optimism` | $1 | 약 1분 |
| POL | Polygon | `polygon` | $1 | 약 1분 |
| AVAX | Avalanche | `avalanche` | $1 | 약 1분 |
| OKB | X Layer | `xlayer` | $1 | 약 1분 |
| USDT | Solana | `sol` | $1 | 약 1분 |
| USDC | Solana | `sol` | $1 | 약 1분 |
| SOL | Solana | `sol` | $1 | 약 1분 |
| USDT | TON | `ton` | $1 | 약 1분 |
| TON | TON | `ton` | $1 | 약 1분 |

ERC-20은 수수료가 높아 최소 금액이 $5이고, 비트코인도 수수료가 낮지 않아 최소 $5입니다. 나머지는 모두 $1입니다. 고객은 반드시 결제 페이지에 표시된 네트워크로 보내야 합니다. 다른 네트워크로 보낸 돈은 자동으로 입금 처리되지 않습니다. BTC / LTC / DOGE / BCH는 멤풀에 들어오면 곧바로 “감지됨”으로 표시되고, 컨펌이 충분히 쌓여야 인정됩니다. 교체(RBF)되거나 사라진 트랜잭션은 인정되지 않습니다. 네이티브 코인(TRX, BNB, ETH, POL, AVAX, OKB, SOL, TON)은 BTC / LTC / DOGE / BCH처럼 고정 환율로 견적을 내고 미국 달러로 입금 처리됩니다. 네트워크와 코인을 모두 지정하세요. 청구서는 `network: "bsc", to_currency: "BNB"`, 고정 지갑은 `network: "bsc", currency: "BNB"`입니다. 네트워크만 지정하면 해당 체인의 USDT가 기본값입니다(Base는 USDC). 컨트랙트가 보낸 송금(멀티시그, 일괄 지급)도 받을 수 있습니다. 일부 체인에서는 트랜잭션 해시를 알 수 없어 웹훅의 `txid`가 `null`일 수 있습니다.

### 오류

```json
{ "state": 1, "message": "amount must be a number like 10.28" }
```

| HTTP | 흔한 원인 |
| --- | --- |
| 422 | 매개변수가 없거나 형식이 잘못됨, 금액이 네트워크 최소 금액 미만, 콜백 / 리디렉션 URL이 등록한 도메인에 속하지 않음, `/v1/payment`에서 지원하지 않는 코인 / 네트워크를 지정함 |
| 401 | 잘못된 merchant, 잘못된 `sign`(`Invalid Sign.`), 또는 재발급되어 바뀐 API key |
| 404 | 청구서가 없거나 당신의 것이 아님(`Payment not found`), `/v1/wallet`에서 지원하지 않는 코인 / 네트워크 조합(`The service was not found`) |
| 503 | 네트워크를 일시적으로 쓸 수 없음 — 다른 네트워크를 고르거나 미리 지정하지 마세요 |

서명이 틀리면(`Invalid Sign.`) 응답에 `debug` 객체가 들어갑니다. `body_base64`는 저희가 받은 요청 본문의 base64(당신이 보낸 내용이며 API key는 절대 포함되지 않음)이고 `body_bytes`는 그 길이입니다. 서명할 때 만든 base64와 비교해 보세요. 다르면 보낸 본문이 서명한 문자열과 다른 것입니다(프레임워크가 다시 직렬화했거나, 프록시가 인코딩을 바꿨거나, 끝에 줄바꿈이 붙은 경우). 같다면 API key가 틀린 것입니다. [서명 검사기](https://blockwallets.io/docs/sign?lang=ko)에 붙여 넣으면 처음 달라지는 곳을 볼 수 있습니다.

```json
{
  "state": 1,
  "message": "Invalid Sign.",
  "debug": {
    "body_base64": "eyJhbW91bnQiOiI0Mi4wMCIsImN1cnJlbmN5IjoiVVNEIiwi…",
    "body_bytes": 158,
    "sign_formula": "md5(body_base64 + API key)"
  }
}
```

### 자주 묻는 질문

#### 고객이 원하는 금액을 충전하게 하려면 어느 방식이 맞나요?

청구서입니다. 충전 페이지에서 고객이 금액을 입력하게 하고 그 금액으로 청구서를 만든 뒤, 도착하면 `payment_amount_usd`만큼 충전하세요. 고객이 주소 하나를 저장해 두고 반복해서 보내게 하고 싶을 때만 고정 지갑을 쓰세요.

#### 고객이 부족하게 결제하면 어떻게 되나요?

만료 전에 고객이 결제 페이지에서 차액을 보낼 수 있습니다(남은 금액이 표시되며 상태는 `wrong_amount_waiting`이고, 처음 부족하게 송금한 시점에 남은 시간이 30분 미만이면 만료 시각이 그때부터 30분 뒤로 늦춰집니다). 만료 시점에도 부족하면 청구서는 `wrong_amount`가 되고 웹훅의 `payment_amount_usd`는 받은 금액입니다. 충전형 서비스는 그 금액만큼 충전하면 됩니다.

#### 고객이 늦게 결제하거나 두 번 결제하면요?

만료 후에 도착한 돈도 입금 처리되고 알림이 갑니다. 청구서가 전액 결제된 뒤에 도착한 돈도 기록되어 관리 화면에서 볼 수 있습니다.

#### 고객이 다른 네트워크로 보내면요?

예를 들어 청구서는 TRON인데 BNB Chain에서 보낸 경우입니다. 자동으로 입금 처리되지 않으니 저희에게 연락하시면 직접 처리해 드립니다.

#### 비용은 얼마인가요?

받은 결제 건당 1%이며 지난달 거래액에 따라 자동으로 내려갑니다($50,000 이상 0.9%, $100,000 이상 0.8%, $250,000 이상 0.7%, $500,000 이상 0.6% 및 $1,000,000 이상 0.5%). 월 이용료와 가입비는 없습니다. 네트워크 수수료는 고객이 송금할 때 부담합니다. 고객에게 수수료를 넘기려면 청구서를 만들 때 `subtract`를 설정하거나 관리 화면에서 기본값을 정하세요. 출금에는 추가 수수료가 없습니다. [요금](https://blockwallets.io/pricing?lang=ko)을 참고하세요.

#### 돈은 언제 받나요?

결제가 컨펌되는 즉시 잔액에 들어옵니다. “자금 › 잔액과 출금”에서 내 지갑으로 출금을 신청하면, 저희가 승인한 뒤 지급하고 체인상 트랜잭션이 출금 내역에 표시됩니다.

자동 출금도 켤 수 있습니다. 출금 가능한 잔액이 정해 둔 금액에 도달하거나 매일 00:00 UTC가 되면 출금 가능한 잔액 전체를 출금합니다. 자동 출금도 같은 방식으로 검토하며, 설정을 바꿀 때마다 이메일로 알림이 갑니다.
