API ที่เข้ากันได้กับ NOWPayments
พาธ เฮดเดอร์ x-api-key ฟิลด์คำขอและการตอบกลับ สถานะ และลายเซ็น IPN x-nowpayments-sig เป็นไปตามเอกสารของ NOWPayments โค้ดเดิมจึงมักต้องการเพียง base URL, API key และ IPN secret ใหม่ ย้ายมาจากที่อื่น? ดูภาพรวมการย้าย
ภายในเป็นใบแจ้งหนี้ blockwallets ปกติ: ที่อยู่รับเงินเป็นของคุณ หน้าชำระเงินคือหน้าชำระเงินที่เราโฮสต์ และการชำระจะนับก็ต่อเมื่อยืนยันบนเชนแล้วและตรวจไขว้กับโหนดที่สอง ต่อไปนี้: สามขั้นตอน จากนั้นสิ่งที่แต่ละ API รองรับและต่างจากต้นฉบับอย่างไร พารามิเตอร์ที่เราไม่ได้ทำจะตอบข้อผิดพลาด ไม่ถูกเมินไปเงียบ ๆ
สามขั้นตอน
- สมัครบัญชีร้านค้า (ขอใช้งานที่หน้าแรก) และเพิ่มโดเมนของคุณที่ “โดเมนเว็บไซต์” ใน “นักพัฒนา › การเชื่อมต่อและคีย์” ของแดชบอร์ด URL callback (
ipn_callback_url) และ URL เปลี่ยนเส้นทาง (success_url/cancel_url/redirect_url) ต้องอยู่ในโดเมนเหล่านั้นหรือโดเมนของ URL callback ของคุณ คีย์ที่รั่วไหลจึงชี้ Webhook ไปที่อื่นไม่ได้ - สร้างข้อมูลรับรองที่เข้ากันได้ในแดชบอร์ดร้านค้าที่ “นักพัฒนา › การเชื่อมต่อและคีย์” ชุดใช้งานจริงและชุดทดสอบอย่างละชุด: NOWPayments ได้ API key + IPN secret ส่ง Coinbase Commerce ได้ API key + webhook shared secret แสดงเพียงครั้งเดียว เราเก็บเฉพาะค่าแฮช / ข้อความเข้ารหัส
- ชี้โค้ดของคุณไปที่ base URL ด้านล่างและใช้คีย์ใหม่ รันบนเทสต์เน็ตด้วยข้อมูลรับรองทดสอบก่อน แล้วค่อยสลับเป็นใช้งานจริง
| ก่อน | หลัง | เฮดเดอร์การยืนยันตัวตน | ลายเซ็น 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 ที่รองรับ
| Endpoint | หมายเหตุ |
|---|---|
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 ทันที (white-label) |
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 endpoint นี้คืนโทเค็นตัวแทนที่ไม่ได้ตรวจสอบ เพื่อให้โค้ดแบบ “เข้าสู่ระบบก่อนแล้วค่อยดูรายการ” ยังทำงานได้ (ไม่ตรวจอีเมล / รหัสผ่าน) |
ไม่มี: การจ่ายออก การแปลงสกุล sub-partner แบบฝากเก็บ การสมัครสมาชิกแบบรายคาบ update-merchant-estimate การชำระด้วยเงินตรา เส้นทางเหล่านี้ตอบ 404
IPN callback
เมื่อการชำระถึงสถานะสุดท้าย เราส่ง POST ไปที่ ipn_callback_url ที่ระบุตอนสร้าง โดย x-nowpayments-sig = HMAC-SHA512(IPN secret, JSON ที่เรียงตามคีย์แบบเรียกซ้ำ) ซึ่งตรงกับโค้ดตรวจสอบของ NOWPayments ทุกประการ เนื้อหาที่เราส่งเรียงลำดับและกะทัดรัดอยู่แล้ว จึงใช้ HMAC บนเนื้อหาดิบก็ได้เช่นกัน (และปลอดภัยที่สุดใน PHP / Python ที่การเข้ารหัสใหม่ของทศนิยมที่เล็กมากอาจต่างจาก JS) callback ที่ล้มเหลวจะลองใหม่หลัง 1 นาที, 5 นาที, 30 นาที, … รวม 8 ครั้ง
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 | เมื่อ | IPN ที่ส่ง |
|---|---|---|
waiting | รอการชำระ (รวมใบแจ้งหนี้ที่ยังไม่ได้เลือกเหรียญ) | — |
confirming | พบการชำระบนเชน รอการยืนยัน | — (เห็นได้เมื่อค้นสถานะ) |
finished | ชำระแล้ว (รวมจ่ายเกิน actually_paid คือยอดที่เข้ามาจริง) | ✓ |
partially_paid | จ่ายขาด: ลูกค้ายังเติมเข้าที่อยู่เดิมได้ก่อนหมดอายุ (ส่งครั้งเดียวเมื่อพบการจ่ายขาดครั้งแรก) และส่งอีกครั้งหากหมดอายุแล้วยังขาด | ✓ |
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(และusdt/usdcที่ 1:1) เงินตราอื่นตอบ 400pay_amount(จำนวนคริปโตคงที่),payout_address/payout_currency/payout_extra_idและcustomer_emailไม่รองรับและตอบ 400is_fixed_rateรับได้แต่ไม่มีผล: เราล็อกอัตราเสมอ (BTC / LTC เป็นต้นด้วยค่ามัธยฐานจากหลายกระดานเทรด เป็นเวลา 30 นาที) เมื่อis_fee_paid_by_userเป็น true ค่าธรรมเนียมแพลตฟอร์มจะบวกเข้าpay_amount(ค่าธรรมเนียมแพลตฟอร์มคือ 1% ลดลงเมื่อยอดสูงขึ้น ดูราคา)- การชำระมีอายุได้สูงสุด 12 ชั่วโมง (NOWPayments: 7 วัน)
partially_paid_urlถูกเก็บและส่งกลับ แต่หน้าชำระเงินไม่เปลี่ยนเส้นทางไปที่นั่น payment_idเป็นตัวเลข 10 หลัก สำหรับใบแจ้งหนี้ที่สร้างด้วย/invoicepayment_idของการชำระเท่ากับidของใบแจ้งหนี้ และหนึ่งใบแจ้งหนี้มีการชำระเดียวพอดี (ไม่มีการฝากซ้ำแบบparent_payment_id)- ส่งเฉพาะ IPN ของสถานะสุดท้ายและ
partially_paid(ตารางด้านบน) ไม่มีwaiting/confirmingfee.serviceFeeคือค่าธรรมเนียมแพลตฟอร์ม (ยอดที่ได้รับ × อัตราตอนสร้างใบแจ้งหนี้ มาตรฐาน 1%) ค่าธรรมเนียมอื่นเป็น 0
โหมดทดสอบ
charge และการชำระที่สร้างด้วยข้อมูลรับรองทดสอบรันบนเทสต์เน็ตของแต่ละเชน และ Webhook ของมันเซ็นด้วย IPN secret / shared secret ของชุดทดสอบ แยกจากใช้งานจริงโดยสิ้นเชิง: คีย์ทดสอบมองไม่เห็นการชำระจริง
ส่วนที่เหลือทั้งหมด (หน้าชำระเงิน การยืนยัน เครือข่ายและยอดขั้นต่ำ การคืนเงิน) ทำงานเหมือนในเอกสารหลัก