# NOWPayments 互換 API

> NOWPayments から blockwallets へ、ベース URL とキーの変更だけで切り替え:対応エンドポイント、IPN コールバックと署名、通貨ティッカー、相違点。
>
> HTML: https://blockwallets.io/docs/nowpayments?lang=ja · English: https://blockwallets.io/docs/nowpayments.md?lang=en

## NOWPayments 互換 API

パス、`x-api-key` ヘッダー、リクエストとレスポンスのフィールド、ステータス、`x-nowpayments-sig` の IPN 署名は NOWPayments のドキュメントに従うため、既存のコードは通常、新しいベース URL、API key、IPN secret を用意するだけで動きます。ほかのサービスからの移行ですか?[移行の概要](https://blockwallets.io/docs/migrate?lang=ja)をご覧ください。

内部では通常の blockwallets の請求です。受取アドレスはあなたのもの、決済ページは当社のホスト型決済ページで、支払いはオンチェーンで承認され、2つ目のノードでも照合されて初めて有効になります。以下では、3つのステップに続けて、各 API の対応範囲と**元のサービスとの違い**を説明します。実装していないパラメーターは、黙って無視されることなくエラーを返します。

### 3つのステップ

- 加盟店アカウントを取得し([トップページから申し込み](https://blockwallets.io/?lang=ja#join))、管理画面「開発者 › 連携とキー」の「サイトのドメイン」にドメインを追加します。コールバック URL(`ipn_callback_url`)とリダイレクト URL(`success_url` / `cancel_url` / `redirect_url`)は、これらのドメイン上、またはコールバック URL のドメイン上である必要があります。キーが漏えいしても、Webhook を別の場所に向けることはできません。

- 加盟店管理画面の「開発者 › 連携とキー」で互換の認証情報を発行します。本番用とテスト用が1組ずつあり、NOWPayments は **API key + IPN secret**、Coinbase Commerce は **API key + webhook shared secret** です。表示は一度だけで、当社にはハッシュ / 暗号文だけを保存します。

- コードを下記のベース URL に向け、新しいキーを使います。まずテスト用の認証情報でテストネット上で動かし、そのあと本番に切り替えてください。

| 移行前 | 移行後 | 認証ヘッダー | Webhook 署名 |
| --- | --- | --- | --- |
| `https://api.nowpayments.io/v1` | `https://blockwallets.io/nowpayments/v1` | `x-api-key` | `x-nowpayments-sig`(HMAC-SHA512) |

### NOWPayments

```javascript
- const NP = "https://api.nowpayments.io/v1";
+ const NP = "https://blockwallets.io/nowpayments/v1";

const res = await fetch(`${NP}/payment`, {
  method: "POST",
  headers: { "x-api-key": process.env.NOWPAYMENTS_API_KEY, "content-type": "application/json" },
  body: JSON.stringify({
    price_amount: 49.9, price_currency: "usd", pay_currency: "usdttrc20",
    order_id: "A-1001", order_description: "Pro plan",
    ipn_callback_url: "https://shop.example/ipn/nowpayments",
  }),
});
const { payment_id, pay_address, pay_amount } = await res.json();
```

#### 対応エンドポイント

| エンドポイント | 備考 |
| --- | --- |
| `GET /status` | `{"message":"OK"}`、キー不要 |
| `GET /currencies` · `/merchant/coins` · `/full-currencies` | 現在受け付けられる通貨(ティッカーは下記)。`fixed_rate` は無視されます |
| `GET /min-amount` · `GET /estimate` | 最低額と見積もり。価格指定は USD のみ(USDT / USDC は 1:1) |
| `POST /payment` | 決済を作成。`pay_address` / `pay_amount` をすぐ返します(ホワイトラベル) |
| `POST /invoice` | インボイスを作成。`invoice_url` は当社のホスト型決済ページで、お客様が通貨とネットワークを選びます |
| `POST /invoice-payment` | インボイスの通貨を選び、決済を返します |
| `GET /payment/:id` | 決済の状態 |
| `GET /payment/` | 一覧:`limit`(1〜500)/ `page` / `orderBy` / `dateFrom` / `dateTo` / `invoiceId`。`sortBy` は `created_at` のみ |
| `POST /auth` | 一覧に必要なのは `x-api-key` だけで JWT は不要です。この呼び出しは検証されないプレースホルダーのトークンを返すので、「ログインしてから一覧」というコードもそのまま動きます(メールアドレス / パスワードは検証しません) |

非対応:払い出し、換算、カストディのサブパートナー、サブスクリプション、`update-merchant-estimate`、法定通貨での決済。これらのパスは 404 を返します。

#### IPN コールバック

決済が最終ステータスになると、作成時に指定された `ipn_callback_url` に POST します。`x-nowpayments-sig` = HMAC-SHA512(IPN secret, キーで再帰的に並べ替えた JSON) で、NOWPayments の検証コードとまったく同じです。送信するボディはすでに並べ替え済みのコンパクトな JSON なので、生のボディに対する HMAC でも検証できます(PHP / Python では、非常に小さい小数の再エンコードが JS と異なることがあるため、こちらが最も安全です)。失敗したコールバックは 1 分後、5 分後、30 分後、…と再試行し、合計 8 回です。

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

// Same as the NOWPayments docs (our body is already sorted, so hashing the raw body works too)
function sortObject(obj) {
  return Object.keys(obj).sort().reduce((r, k) => {
    r[k] = obj[k] && typeof obj[k] === "object" ? sortObject(obj[k]) : obj[k];
    return r;
  }, {});
}
function verifyIpn(params, sig, ipnSecret) {
  const hmac = crypto.createHmac("sha512", ipnSecret).update(JSON.stringify(sortObject(params))).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(sig || ""));
}
// verifyIpn(req.body, req.headers["x-nowpayments-sig"], process.env.NOWPAYMENTS_IPN_SECRET)
```

```php
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
tksort($data);  // the recursive ksort from the NOWPayments docs
$hmac = hash_hmac('sha512', json_encode($data, JSON_UNESCAPED_SLASHES), trim($ipnSecret));
$ok = hash_equals($hmac, $_SERVER['HTTP_X_NOWPAYMENTS_SIG'] ?? '');
```

| payment_status | タイミング | 送信される IPN |
| --- | --- | --- |
| `waiting` | 支払い待ち(通貨がまだ選ばれていないインボイスも含む) | — |
| `confirming` | オンチェーンで支払いを検知、承認待ち | —(照会すると確認できます) |
| `finished` | 支払済み(超過払いを含む。`actually_paid` は実際に届いた額) | ✓ |
| `partially_paid` | 不足:期限前なら、お客様は同じアドレスに追加で送金できます(最初の不足時に1回送信)。期限になってもなお不足している場合は、もう一度送信します | ✓ |
| `expired` | 未払いのまま期限切れ | ✓ |
| `refunded` | 管理画面から決済の全額を返金し、送金済み | —(照会すると確認できます) |

`confirmed`、`sending`、`failed` は送信されません。承認されると `finished` になって残高に計上され、転送は行いません。

#### 通貨ティッカー

| 通貨 | ネットワーク | pay_currency | network | 最低額 |
| --- | --- | --- | --- | --- |
| USDT | TRON | `usdttrc20` | `trx` | $1 |
| USDT | BNB Chain | `usdtbsc` usdtbep20 | `bsc` | $1 |
| USDC | BNB Chain | `usdcbsc` usdcbep20 | `bsc` | $1 |
| USDT | Ethereum | `usdterc20` | `eth` | $5 |
| USDC | Ethereum | `usdc` usdcerc20 | `eth` | $5 |
| USDC | Base | `usdcbase` | `base` | $1 |
| USDT | Polygon | `usdtmatic` usdtpolygon | `matic` | $1 |
| USDC | Polygon | `usdcmatic` usdcpolygon | `matic` | $1 |
| USDT | Arbitrum | `usdtarb` | `arbitrum` | $1 |
| USDC | Arbitrum | `usdcarb` | `arbitrum` | $1 |
| USDT | Optimism | `usdtop` | `op` | $1 |
| USDC | Optimism | `usdcop` | `op` | $1 |
| USDT | Avalanche | `usdtarc20` usdtavax / usdtavaxc | `avaxc` | $1 |
| USDC | Avalanche | `usdcarc20` usdcavax / usdcavaxc | `avaxc` | $1 |
| USDT | X Layer | `usdtxlayer` | `xlayer` | $1 |
| USDC | X Layer | `usdcxlayer` | `xlayer` | $1 |
| USD1 | BNB Chain | `usd1bsc` | `bsc` | $1 |
| FDUSD | BNB Chain | `fdusdbsc` | `bsc` | $1 |
| DAI | Ethereum | `dai` daierc20 | `eth` | $5 |
| DAI | Arbitrum | `daiarb` | `arbitrum` | $1 |
| DAI | Base | `daibase` | `base` | $1 |
| BTC | Bitcoin | `btc` | `btc` | $5 |
| LTC | Litecoin | `ltc` | `ltc` | $1 |
| DOGE | Dogecoin | `doge` | `doge` | $1 |
| BCH | Bitcoin Cash | `bch` | `bch` | $1 |
| TRX | TRON | `trx` | `trx` | $1 |
| BNB | BNB Chain | `bnbbsc` bnb | `bsc` | $1 |
| ETH | Ethereum | `eth` | `eth` | $5 |
| ETH | Base | `ethbase` | `base` | $1 |
| ETH | Arbitrum | `etharb` | `arbitrum` | $1 |
| ETH | Optimism | `ethop` | `op` | $1 |
| POL | Polygon | `maticmainnet` pol / polmainnet / matic | `matic` | $1 |
| AVAX | Avalanche | `avaxc` avax | `avaxc` | $1 |
| OKB | X Layer | `okbxlayer` | `xlayer` | $1 |
| USDT | Solana | `usdtsol` | `sol` | $1 |
| USDC | Solana | `usdcsol` | `sol` | $1 |
| SOL | Solana | `sol` | `sol` | $1 |
| USDT | TON | `usdtton` | `ton` | $1 |
| TON | TON | `ton` | `ton` | $1 |

#### NOWPayments との相違点

- `price_currency` は `usd` のみ(加えて 1:1 の `usdt` / `usdc`)。ほかの法定通貨は 400 を返します。

- `pay_amount`(暗号資産の固定額)、`payout_address` / `payout_currency` / `payout_extra_id`、`customer_email` には対応しておらず、400 を返します。

- `is_fixed_rate` は受け付けますが効果はありません。レートは常に固定します(BTC / LTC などは複数の取引所の中央値で、30 分間)。`is_fee_paid_by_user` が true の場合、プラットフォーム手数料が `pay_amount` に上乗せされます(プラットフォーム手数料は 1%、取扱高が増えると下がります。[料金](https://blockwallets.io/pricing?lang=ja)を参照)。

- 決済の有効期間は最長 12 時間です(NOWPayments は 7 日)。`partially_paid_url` は保存されてそのまま返されますが、決済ページはそこへリダイレクトしません。

- `payment_id` は 10 桁の数字です。`/invoice` で作成したインボイスでは、決済の `payment_id` がインボイスの `id` と同じで、インボイスにはちょうど1件の決済があります(`parent_payment_id` による再入金はありません)。

- 送信されるのは最終ステータスと `partially_paid` の IPN のみです(上の表)。`waiting` / `confirming` は送信しません。`fee.serviceFee` はプラットフォーム手数料(受取額 × 請求作成時の料率、標準 1%)で、ほかの手数料は 0 です。

### テストモード

テスト用の認証情報で作成したチャージと決済は各チェーンのテストネットで動き、その Webhook はテスト用の IPN secret / shared secret で署名されます。本番とは完全に分離されており、テスト用のキーから本番の決済は見えません。

そのほか(決済ページ、承認、ネットワークと最低額、返金)は[メインのドキュメント](https://blockwallets.io/docs/cryptomus?lang=ja)のとおりに動作します。
