npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bitpal/checkout

v0.3.0

Published

BitPal Checkout SDK — crypto payment sessions, external on-chain payment verification, gasless checkout (with PaymentAuthorization double-defense), refunds, fee preview.

Downloads

173

Readme

@bitpal/checkout

BitPal Checkout SDK — 크립토 결제 세션 생성, 외부 on-chain 결제 검증, gasless 결제, 환불, 수수료 미리보기를 위한 공식 TypeScript SDK.

설치

pnpm add @bitpal/checkout

⚠️ 금액 형식 (꼭 읽어주세요)

BitPal API는 모든 amount를 atomic μUSDC 정수 문자열로 받습니다 (USDC/USDT 6 decimals).

| 의미 | 보내는 값 | |------|-----------| | $1.00 | "1000000" | | $10.00 | "10000000" | | $29.00 | "29000000" | | $0.50 | "500000" |

"29.00"처럼 decimal 표기로 보내면 400 에러가 떨어집니다. SDK가 제공하는 toAtomicUSDC() 헬퍼를 쓰세요:

import { toAtomicUSDC, fromAtomicUSDC } from '@bitpal/checkout';

toAtomicUSDC('29.00');   // → '29000000'
toAtomicUSDC('0.5');     // → '500000'
fromAtomicUSDC('1500000'); // → '1.50'

이 정책은 부동소수 정밀도 손실 방지 때문이며, 모든 BitPal 라우트에 일관 적용됩니다.


환경 (Environments)

BitPal Checkout 은 두 개의 직교 (orthogonal) 축으로 환경이 결정됩니다.

| 축 | 값 | 결정 방식 | 영향 | |----|----|---------|------| | mode | TEST / LIVE | API 키 prefix (bp_test_* / bp_live_*) | 어떤 컨트랙트 / 정산 / webhook environment 가 사용될지 | | API host | production / local | BitPal({ baseUrl }) | 어떤 BitPal 백엔드를 호출할지 (실서비스 vs 본인 dev 머신) |

조합 매트릭스:

| 시나리오 | baseUrl | API 키 | 실제 체인 | |---------|-----------|--------|----------| | Local 개발 (anvil) | http://localhost:3100 | bp_test_* | 로컬 Hardhat/Anvil 체인 (http://127.0.0.1:8545) | | Testnet 통합 | https://api.bitpal.io | bp_test_* | Base Sepolia 등 퍼블릭 testnet | | Production | https://api.bitpal.io | bp_live_* | Base Mainnet |

import { BitPal } from '@bitpal/checkout';

// 1) Production / testnet (BitPal 호스티드 백엔드)
const bitpal = new BitPal({
  apiKey: process.env.BITPAL_API_KEY!, // bp_test_* (testnet) 또는 bp_live_* (mainnet)
});

// 2) Local 개발 (본인 머신의 BitPal API + anvil)
const bitpalLocal = new BitPal({
  apiKey: process.env.BITPAL_API_KEY!, // bp_test_* 필수
  baseUrl: 'http://localhost:3100',
});

어떤 체인이든 SDK 호출 시점에 chain: 'base' (또는 다른 지원 체인) 만 지정하면 됩니다. mode 가 mainnet/testnet/local 컨트랙트 주소 매핑을 결정하므로 클라이언트는 체인 이름 만 알면 충분합니다.

사용법

기본 설정

import { BitPal } from '@bitpal/checkout';

// API 키는 BitPal 콘솔 → Developers → API Keys 에서 발급
const bitpal = new BitPal({ apiKey: process.env.BITPAL_API_KEY! });

체크아웃 세션 생성

import { BitPal, toAtomicUSDC } from '@bitpal/checkout';

const bitpal = new BitPal({ apiKey: process.env.BITPAL_API_KEY! });

const { data: session } = await bitpal.checkout.createSession({
  line_items: [
    { name: 'Pro Plan', amount: toAtomicUSDC('29.00'), currency: 'USDC' },
  ],
  pay_chain: 'eip155:8453', // Base
  expires_in_seconds: 1800,
  success_url: 'https://yoursite.com/order-complete',
  cancel_url: 'https://yoursite.com/cart',
});

// buyer를 이 URL로 redirect → BitPal이 호스팅하는 결제 페이지
console.log(session.url);

환불 (auto — 에스크로 보관 중)

import { randomUUID } from 'node:crypto';
import { BitPal, toAtomicUSDC } from '@bitpal/checkout';

const bitpal = new BitPal(process.env.BITPAL_API_KEY!);

// 전액 환불 — amount 미지정
const { data: full } = await bitpal.checkout.refundSession(
  sessionId,
  { reason: 'customer_requested' },
  { idempotencyKey: randomUUID() },   // 동일 키 재시도 시 같은 결과 반환
);
console.log(full.refunded, full.txHash);  // true, '0x...' (또는 sim_xxx)

// 부분 환불 — amount 명시
const { data: partial } = await bitpal.checkout.refundSession(
  sessionId,
  { amount: toAtomicUSDC('5.00'), reason: 'partial_damage' },
  { idempotencyKey: randomUUID() },
);
console.log(partial.type);  // 'partial'

머천트가 frozen 상태면 환불은 허용되지만, suspended 상태에서는 차단된다 (merchant-status-guard).

환불 기록 (record — settled 후 manual offchain)

// 정산이 끝난 세션은 escrow에 자금이 없음 → 머천트가 외부 지갑에서 직접 송금하고
// tx_hash를 입력해 환불을 DB-only로 기록한다.
await bitpal.checkout.recordRefund(
  sessionId,
  {
    refund_amount: toAtomicUSDC('5.00'),
    tx_hash: '0x...실제 송금 tx hash',
    reason: 'manual_offchain_refund',
  },
  { idempotencyKey: randomUUID() },
);

환불 내역 조회

const { data: refunds } = await bitpal.checkout.listRefunds(sessionId);
// auto + record 통합 list, 시간 역순

수수료 미리보기

// $100 결제의 수수료 계산
const { data: fee } = await bitpal.checkout.previewFee(toAtomicUSDC('100'));
// fee.fee_amount === "1500000"  (1.5% = $1.50)
// fee.net_amount === "98500000" (= $98.50)

console.log(`수수료: $${fromAtomicUSDC(fee.fee_amount)}`);

Gasless external checkout payment

Use payWithAuthorization() when your merchant UI collects an EIP-3009 USDC authorization from the buyer and wants BitPal to submit the escrow deposit with the platform relayer.

const { data: session } = await bitpal.checkout.getSession(sessionId);
const cfg = session.external_payment_config;

// Sign EIP-3009 typed data in your wallet flow using cfg.token_address,
// cfg.escrow_contract_address, cfg.amount, and cfg.payment_id.
const authorization = {
  from: '0xBuyer',
  validAfter: '0',
  validBefore: '1778650000',
  authNonce: '0x...',
  v: 27,
  r: '0x...',
  s: '0x...',
};

const { data: payment } = await bitpal.checkout.payWithAuthorization(sessionId, {
  chain: cfg?.chain ?? 'base',
  session_token: session.session_token!,
  authorization,
});

console.log(payment.status, payment.tx_hash);

payWithAuthorization() calls POST /v1/checkout/sessions/:id/submit-payment. The buyer signs only the authorization; BitPal's relayer submits CheckoutEscrow.depositWithAuthorization() and pays gas.

SDK 의 반환값(status, tx_hash) 만으로 merchant 백엔드가 주문을 finalize 할 수 있습니다. webhook 등록은 선택 — SDK 호출 없이 자동 알림이 필요한 머천트 백엔드 (비동기 잡, 다른 마이크로서비스 fan-out 등) 만 등록하면 됩니다.

External on-chain payment verification (submitExternalPayment)

When the buyer deposits into CheckoutEscrow from their own wallet, merchant UI, or your own backend — outside the BitPal hosted page and outside payWithAuthorization() — submit the resulting tx_hash to BitPal for verification.

const { data: result } = await bitpal.checkout.submitExternalPayment(sessionId, {
  tx_hash: '0xb81f1eb2cc8a4b32a233865c595166df563f8f009e319048f8d079abd0f3953d',
  chain: 'base',
});

// result.validation_status: 'valid' | 'invalid' | 'underpaid' | 'overpaid' | 'duplicate'
// result.session_status:    'paid' | 'unresolved_underpaid' | 'unresolved_overpaid' | ...
// result.observation_id:    uuid of the checkout_payment_observations row
// result.from_address:      payer wallet (derived from PaymentDeposited event)
// result.amount:            atomic μUSDC received (derived from event)

Behaviour:

  • BitPal fetches the receipt, parses CheckoutEscrow.PaymentDeposited, and verifies paymentId == toBytes32(sessionId), merchant wallet, USDC token, and amount tolerance.
  • The same (chain_id, tx_hash, log_index) is rejected with validation_status: 'duplicate' if it has already been submitted for a different session.
  • Re-submitting the same tx_hash for the same session is idempotent — you get the same observation row back.
  • If you're using the Ponder Mode B indexer (apps/indexer) the indexer calls this endpoint automatically when it sees a PaymentDeposited event, so you do not need to submit manually.
// Read the on-chain verification history for a session
const { data: observations } = await bitpal.checkout.getExternalPaymentStatus(sessionId);
const latest = observations[0];
if (latest?.finality_status === 'final_confirmed') {
  // 12 confirmations reached — safe to fulfil orders that require finality
}

getExternalPaymentStatus() is the authenticated read counterpart for tenant-scoped clients. It returns the same payment_observations array that the merchant console session detail page shows under "On-chain Verification".

Choosing between SDK payment methods

| Method | Buyer signs | Who calls CheckoutEscrow.deposit* | Use when | |--------|------------|--------------------------------------|----------| | Hosted checkout (createSession → redirect to session.url) | EIP-3009 inside BitPal page | BitPal relayer | You want the simplest integration and don't need a buyer UI | | payWithAuthorization() | EIP-3009 inside your UI | BitPal relayer | Buyer stays in merchant UI, gasless for buyer | | submitExternalPayment() | Buyer signs the deposit tx in their wallet | Buyer wallet or your backend | Buyer pays their own gas, or you have a non-BitPal flow that emits PaymentDeposited |

All three paths land in the same CheckoutEscrow.PaymentDeposited event and trigger the same set of webhooks (checkout.session.paid, checkout.session.unresolved, checkout.payment.finalized, checkout.payment.revoked).

웹훅 검증

import express from 'express';
import { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from '@bitpal/checkout';

const app = express();

// raw body 필수 — express.json() 쓰지 말 것
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.header(WEBHOOK_HEADERS.signature) ?? '';   // 'X-Webhook-Signature-256'
  const isValid = await verifyWebhookSignature(
    req.body.toString(),
    signature,
    process.env.BITPAL_WEBHOOK_SECRET!,
  );
  if (!isValid) return res.status(400).send('invalid signature');

  const event = parseWebhookEvent(req.body.toString());
  switch (event.event) {
    case 'checkout.session.paid':
      // DB에 주문 완료 처리 (idempotent — at-least-once 전달)
      break;
    case 'checkout.payment.failed':
      // 실패 처리
      break;
  }
  res.status(200).send('ok');
});

Webhook 헤더 (canonical)

BitPal backend가 모든 webhook delivery에 보내는 표준 헤더:

| 헤더 | 값 | 용도 | |------|----|------| | X-Webhook-Signature-256 | sha256=<hex> | HMAC-SHA256 서명 (raw body 기준) | | X-Webhook-Timestamp | ISO 시각 | envelope.created_at과 동일 | | X-Webhook-Event | checkout.session.paid 등 | canonical event name | | X-Webhook-Id | evt_... | event_id (replay 시 보존) | | X-Webhook-Delivery-Id | dlv_... | delivery row id |

WEBHOOK_HEADERS 상수로 SDK에서 import 가능 — 헤더 이름 drift 방지.

⚠️ At-least-once delivery — 머천트 idempotency 책임

BitPal webhook은 at-least-once delivery다. 같은 이벤트가 재시도/replay/race로 인해 두 번 이상 도달할 수 있으며, 머천트 측에서 반드시 idempotent하게 처리해야 한다.

필수 패턴:

  1. signature 검증 후
  2. X-Webhook-Id (또는 X-Webhook-Delivery-Id) 기준으로 dedup
  3. 이미 처리한 event는 200 OK만 반환하고 부작용 없이 종료
import { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from '@bitpal/checkout';

app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  // 1. signature 검증
  const sig = req.header(WEBHOOK_HEADERS.signature) ?? '';
  const ok = await verifyWebhookSignature(req.body.toString(), sig, SECRET);
  if (!ok) return res.status(400).send('invalid signature');

  // 2. dedup — X-Webhook-Id 기준
  const eventId = req.header(WEBHOOK_HEADERS.eventId);
  const seen = await db.webhook_events.findUnique({ where: { event_id: eventId } });
  if (seen) return res.status(200).send('already processed');

  // 3. event 처리 + 처리 기록을 동일 트랜잭션에서
  const event = parseWebhookEvent(req.body.toString());
  await db.$transaction(async (tx) => {
    await applyEvent(tx, event);                 // 비즈니스 로직
    await tx.webhook_events.create({ data: { event_id: eventId } });  // dedup 키 기록
  });

  res.status(200).send('ok');
});

dedup 키로 X-Webhook-Id (event 단위) 또는 X-Webhook-Delivery-Id (delivery 단위) 둘 다 가능 — 이벤트 단위 dedup이 일반적으로 더 안전하다 (replay도 같은 event_id 보존).


설정 옵션

| 옵션 | 타입 | 기본값 | 설명 | |------|------|--------|------| | apiKey | string | (필수) | API 키 (bp_test_... 또는 bp_live_...) | | baseUrl | string | https://api.bitpal.io | API 기본 URL (로컬: http://localhost:3100) | | timeout | number | 30000 | 요청 타임아웃 (ms) |

웹훅 이벤트

SDK CHECKOUT_WEBHOOK_EVENTS 상수와 1:1 정합. 새 이벤트 추가 시 backend와 동시 갱신.

| 이벤트 | 설명 | |--------|------| | checkout.session.created | 세션 생성됨 | | checkout.session.paid | 결제 완료 (에스크로 락) | | checkout.session.settled | 정산 완료 (에스크로 해제) | | checkout.session.expired | 세션 만료 | | checkout.session.unresolved | 세션 미해결 (수동 정정 필요) | | checkout.session.release_deferred | 머천트 frozen/suspended로 release 보류 | | checkout.payment.failed | 결제 실패 | | checkout.payment.finalized | 외부 온체인 결제 최종 확인 | | checkout.payment.revoked | 외부 온체인 결제 reorg 취소 | | checkout.refund.recorded | 환불 기록 (settled 후 manual) | | checkout.refund.completed | 환불 완료 (전액) | | checkout.refund.partial | 부분 환불 |

통합 흐름

머천트 백엔드                BitPal API           Buyer
     │                            │                  │
     │ 1. createSession()          │                  │
     │ ──────────────────────────► │                  │
     │ 2. { url } 응답              │                  │
     │ ◄──────────────────────────┤                  │
     │                            │                  │
     │ 3. buyer를 url로 redirect ───────────────────► │
     │                            │                  │
     │                            │ 4. 결제 진행      │
     │                            │ ◄────────────────┤
     │                            │                  │
     │ 5. POST /merchant/webhook   │                  │
     │ ◄─── checkout.session.paid ─┤                  │
     │ 6. signature 검증            │                  │
     │ 7. DB에 주문 완료 처리       │                  │

실행 가능한 예제

examples/ 디렉토리에 통합 데모가 들어있습니다:

  • examples/create-session.mjs — 세션 생성 → 결제 URL 출력
  • examples/webhook-receiver.mjs — webhook 수신 + 서명 검증
cd examples
node create-session.mjs

빌드

pnpm --filter @bitpal/checkout build

라이선스

Private