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.
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ủ) 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
- 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.
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)$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_currencychỉ làusd(cùngusdt/usdctheo 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_idvàcustomer_emailkhô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). Khiis_fee_paid_by_userlà true, phí nền tảng được cộng vàopay_amount(phí nền tảng là 1%, thấp hơn khi doanh số cao hơn; xem bảng giá).- 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_idlà số 10 chữ số; với hóa đơn tạo bằng/invoice,payment_idcủa khoản thanh toán bằngidcủ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ểuparent_payment_id).- Chỉ gửi IPN của trạng thái cuối và
partially_paid(bảng trên), không gửiwaiting/confirming;fee.serviceFeelà 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.