# API tương thích NOWPayments

> Chuyển từ NOWPayments sang blockwallets chỉ bằng cách đổi URL gốc và khóa: các endpoint được hỗ trợ, callback và chữ ký IPN, mã coin và các điểm khác biệt.
>
> HTML: https://blockwallets.io/docs/nowpayments?lang=vi · English: https://blockwallets.io/docs/nowpayments.md?lang=en

## API tương thích NOWPayments

Đường dẫn, header `x-api-key`, trường yêu cầu và phản hồi, trạng thái cùng chữ ký IPN `x-nowpayments-sig` đều theo tài liệu của NOWPayments, nên code hiện có thường chỉ cần URL gốc, API key và IPN secret mới. Đến từ nơi khác? Xem [tổng quan chuyển đổi](https://blockwallets.io/docs/migrate?lang=vi).

Bên dưới đó là một hóa đơn blockwallets bình thường: địa chỉ nhận tiền là của bạn, trang thanh toán là trang thanh toán do chúng tôi lưu trữ, và một khoản thanh toán chỉ được tính khi đã được xác nhận trên chuỗi và đối chiếu với node thứ hai. Phần dưới gồm: ba bước, rồi từng API hỗ trợ gì và **khác bản gốc ở đâu** — tham số chúng tôi không triển khai sẽ trả về lỗi chứ không bị bỏ qua âm thầm.

### Ba bước

- Có tài khoản người bán ([đăng ký dùng ở trang chủ](https://blockwallets.io/?lang=vi#join)) và thêm tên miền của bạn trong “Tên miền website” ở “Nhà phát triển › Tích hợp & khóa” trong trang quản trị. URL callback (`ipn_callback_url`) và URL chuyển hướng (`success_url` / `cancel_url` / `redirect_url`) phải thuộc các tên miền đó hoặc tên miền của URL callback của bạn — khóa bị lộ cũng không thể trỏ webhook sang nơi khác.

- Tạo thông tin xác thực tương thích trong trang quản trị ở mục “Nhà phát triển › Tích hợp & khóa”, một bộ cho live và một bộ cho thử nghiệm: NOWPayments dùng **API key + IPN secret**, Coinbase Commerce dùng **API key + webhook shared secret**. Chúng chỉ hiển thị một lần; chúng tôi chỉ lưu giá trị băm / bản mã hóa.

- Trỏ code của bạn đến URL gốc bên dưới và dùng khóa mới. Chạy thử trên testnet bằng thông tin xác thực thử nghiệm trước, rồi mới chuyển sang live.

| Trước | Sau | Header xác thực | Chữ ký 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();
```

#### Endpoint được hỗ trợ

| Endpoint | Ghi chú |
| --- | --- |
| `GET /status` | `{"message":"OK"}`, không cần khóa |
| `GET /currencies` · `/merchant/coins` · `/full-currencies` | Các coin bạn có thể nhận ngay bây giờ (mã coin ở bên dưới); `fixed_rate` bị bỏ qua |
| `GET /min-amount` · `GET /estimate` | Mức tối thiểu và ước tính; chỉ định giá bằng USD (USDT / USDC tính 1:1) |
| `POST /payment` | Tạo thanh toán; trả về ngay `pay_address` / `pay_amount` (white-label) |
| `POST /invoice` | Tạo hóa đơn; `invoice_url` là trang thanh toán do chúng tôi lưu trữ, nơi khách chọn coin và mạng |
| `POST /invoice-payment` | Chọn coin cho một hóa đơn, trả về khoản thanh toán |
| `GET /payment/:id` | Trạng thái thanh toán |
| `GET /payment/` | Danh sách: `limit` (1–500) / `page` / `orderBy` / `dateFrom` / `dateTo` / `invoiceId`; `sortBy` chỉ có `created_at` |
| `POST /auth` | Danh sách chỉ cần `x-api-key`, không cần JWT; endpoint này trả về một token giữ chỗ không được kiểm tra để code kiểu “đăng nhập rồi liệt kê” vẫn chạy (email / mật khẩu không được kiểm tra) |

Không có: chi trả (payout), quy đổi, đối tác con lưu ký, đăng ký định kỳ, `update-merchant-estimate`, thanh toán bằng tiền pháp định. Các đường dẫn này trả về 404.

#### Callback IPN

Khi một khoản thanh toán đạt trạng thái cuối, chúng tôi POST đến `ipn_callback_url` đã cho lúc tạo, với `x-nowpayments-sig` = HMAC-SHA512(IPN secret, JSON được sắp xếp đệ quy theo khóa) — đúng như code xác minh của NOWPayments. Nội dung chúng tôi gửi đã là JSON gọn và đã sắp xếp, nên tính HMAC trên nội dung thô cũng được (và là lựa chọn an toàn nhất trong PHP / Python, vì chúng mã hóa lại các số thập phân rất nhỏ có thể khác JS). Callback thất bại được thử lại sau 1 phút, 5 phút, 30 phút, … — tổng cộng 8 lần.

```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 | Khi nào | IPN được gửi |
| --- | --- | --- |
| `waiting` | Chờ thanh toán (kể cả hóa đơn chưa chọn coin) | — |
| `confirming` | Đã thấy thanh toán trên chuỗi, đang chờ xác nhận | — (thấy được khi truy vấn) |
| `finished` | Đã thanh toán (kể cả trả dư; `actually_paid` là số tiền đã đến) | ✓ |
| `partially_paid` | Trả thiếu: khách vẫn có thể nạp thêm vào cùng địa chỉ trước khi hết hạn (gửi một lần ở khoản thiếu đầu tiên), và gửi lại nếu đến hạn vẫn thiếu | ✓ |
| `expired` | Hết hạn chưa thanh toán | ✓ |
| `refunded` | Bạn đã hoàn toàn bộ khoản thanh toán từ trang quản trị và đã gửi đi | — (thấy được khi truy vấn) |

`confirmed`, `sending` và `failed` không bao giờ xuất hiện: sau khi được xác nhận là `finished` và được ghi có vào số dư của bạn; không có gì được chuyển tiếp.

#### Mã coin

| Coin | Mạng | pay_currency | network | Tối thiểu |
| --- | --- | --- | --- | --- |
| 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 |

#### Khác biệt so với NOWPayments

- `price_currency` chỉ là `usd` (cùng `usdt` / `usdc` theo 1:1); tiền pháp định khác trả về 400.

- `pay_amount` (số tiền mã hóa cố định), `payout_address` / `payout_currency` / `payout_extra_id` và `customer_email` không được hỗ trợ và trả về 400.

- `is_fixed_rate` được chấp nhận nhưng không có tác dụng: chúng tôi luôn chốt tỷ giá (BTC / LTC v.v. theo trung vị của nhiều sàn, trong 30 phút). Khi `is_fee_paid_by_user` là true, phí nền tảng được cộng vào `pay_amount` (phí nền tảng là 1%, thấp hơn khi doanh số cao hơn; xem [bảng giá](https://blockwallets.io/pricing?lang=vi)).

- Khoản thanh toán có hiệu lực tối đa 12 giờ (NOWPayments: 7 ngày). `partially_paid_url` được lưu và trả lại nguyên vẹn, nhưng trang thanh toán không chuyển hướng đến đó.

- `payment_id` là số 10 chữ số; với hóa đơn tạo bằng `/invoice`, `payment_id` của khoản thanh toán bằng `id` của hóa đơn, và một hóa đơn chỉ có đúng một khoản thanh toán (không có nạp lại kiểu `parent_payment_id`).

- Chỉ gửi IPN của trạng thái cuối và `partially_paid` (bảng trên), không gửi `waiting` / `confirming`; `fee.serviceFee` là phí nền tảng (số tiền đã nhận × mức phí lúc tạo hóa đơn, 1% tiêu chuẩn), các phí khác bằng 0.

### Chế độ thử nghiệm

Charge và thanh toán tạo bằng thông tin xác thực thử nghiệm chạy trên testnet của từng chuỗi và webhook của chúng được ký bằng IPN secret / shared secret thử nghiệm — tách biệt hoàn toàn với live: khóa thử nghiệm không thấy được các khoản thanh toán live.

Mọi thứ còn lại (trang thanh toán, xác nhận, mạng và mức tối thiểu, hoàn tiền) hoạt động như trong [tài liệu chính](https://blockwallets.io/docs/cryptomus?lang=vi).
