NOWPayments 호환 API
경로, x-api-key 헤더, 요청과 응답 필드, 상태, x-nowpayments-sig IPN 서명이 NOWPayments 문서를 따르므로 기존 코드는 대개 새 기본 URL, API key, IPN secret만 있으면 됩니다. 다른 곳에서 오셨나요? 이전 개요를 참고하세요.
내부적으로는 일반 blockwallets 청구서입니다. 수신 주소는 당신의 것이고, 결제 페이지는 저희가 호스팅하는 결제 페이지이며, 결제는 체인에서 컨펌되고 두 번째 노드와 교차 확인된 뒤에야 인정됩니다. 아래에서 세 단계를 설명하고, 이어서 각 API가 지원하는 범위와 원래 서비스와 어떻게 다른지를 설명합니다. 구현하지 않은 매개변수는 조용히 무시하지 않고 오류를 돌려줍니다.
세 단계
- 가맹점 계정을 만들고(홈페이지에서 신청) 관리 화면 “개발자 › 연동 및 키”의 “웹사이트 도메인”에 도메인을 추가합니다. 콜백 URL(
ipn_callback_url)과 리디렉션 URL(success_url/cancel_url/redirect_url)은 이 도메인이나 콜백 URL의 도메인에 속해야 합니다. 키가 유출되어도 웹훅을 다른 곳으로 보낼 수 없습니다. - 가맹점 관리 화면의 “개발자 › 연동 및 키”에서 호환 자격 증명을 만듭니다. 실서비스용 한 세트와 테스트용 한 세트입니다. NOWPayments는 API key + IPN secret, Coinbase Commerce는 API key + 웹훅 공유 시크릿을 받습니다. 한 번만 표시되며 저희는 해시 / 암호문만 보관합니다.
- 코드가 아래 기본 URL을 가리키게 하고 새 키를 사용합니다. 먼저 테스트 자격 증명으로 테스트넷에서 실행해 본 다음 실서비스로 전환하세요.
| 이전 | 이후 | 인증 헤더 | 웹훅 서명 |
|---|---|---|---|
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();지원하는 엔드포인트
| 엔드포인트 | 비고 |
|---|---|
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는 필요 없습니다. 이 엔드포인트는 검증하지 않는 자리표시자 토큰을 돌려주어 “로그인 후 목록 조회” 코드가 계속 동작하게 합니다(이메일 / 비밀번호는 검사하지 않음) |
제공하지 않음: 지급(payout), 환전, 수탁 하위 파트너, 구독, update-merchant-estimate, 법정화폐 결제. 이 경로는 404를 돌려줍니다.
IPN 콜백
결제가 최종 상태에 도달하면 생성할 때 지정한 ipn_callback_url로 POST하며, x-nowpayments-sig = HMAC-SHA512(IPN secret, 키 기준으로 재귀 정렬한 JSON)입니다. NOWPayments의 검증 코드와 똑같습니다. 저희가 보내는 본문은 이미 정렬된 compact JSON이므로 원문 본문에 대한 HMAC도 통합니다(아주 작은 소수를 다시 인코딩하면 JS와 결과가 다를 수 있는 PHP / Python에서는 이쪽이 가장 안전합니다). 실패한 콜백은 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로 추가 허용). 다른 법정화폐는 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%이며 거래액이 많을수록 낮아집니다. 요금 참고).- 결제 유효 시간은 최대 12시간입니다(NOWPayments는 7일).
partially_paid_url은 저장해 두었다가 그대로 돌려주지만 결제 페이지가 그곳으로 리디렉션하지는 않습니다. payment_id는 10자리 숫자입니다./invoice로 만든 인보이스에서는 결제의payment_id가 인보이스id와 같고, 인보이스에는 결제가 정확히 하나만 있습니다(parent_payment_id재입금 없음).- 최종 상태와
partially_paidIPN만 보냅니다(위 표).waiting/confirming은 보내지 않습니다.fee.serviceFee는 플랫폼 수수료(받은 금액 × 청구서를 만든 시점의 요율, 표준 1%)이고 다른 수수료는 0입니다.
테스트 모드
테스트 자격 증명으로 만든 charge와 결제는 각 체인의 테스트넷에서 동작하며 웹훅은 테스트 IPN secret / 공유 시크릿으로 서명됩니다. 실서비스와 완전히 분리되어 있어 테스트 키로는 실서비스 결제를 볼 수 없습니다.
나머지(결제 페이지, 컨펌, 네트워크와 최소 금액, 환불)는 메인 문서와 같습니다.