@bitpal/checkout 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,15 +1,114 @@
1
+ // src/catalog.ts
2
+ function toQuery(params) {
3
+ if (!params) return "";
4
+ const entries = Object.entries(params).filter(([, v]) => v !== void 0);
5
+ if (entries.length === 0) return "";
6
+ const qs = new URLSearchParams(entries.map(([k, v]) => [k, String(v)]));
7
+ return `?${qs.toString()}`;
8
+ }
9
+ var ProductsResource = class {
10
+ constructor(request) {
11
+ this.request = request;
12
+ }
13
+ request;
14
+ /** POST /v1/checkout/products */
15
+ create(params) {
16
+ return this.request("POST", "/v1/checkout/products", params);
17
+ }
18
+ /** GET /v1/checkout/products */
19
+ list(params) {
20
+ return this.request("GET", `/v1/checkout/products${toQuery(params)}`);
21
+ }
22
+ /** GET /v1/checkout/products/:id */
23
+ retrieve(productId) {
24
+ return this.request("GET", `/v1/checkout/products/${encodeURIComponent(productId)}`);
25
+ }
26
+ /** PATCH /v1/checkout/products/:id */
27
+ update(productId, params) {
28
+ return this.request("PATCH", `/v1/checkout/products/${encodeURIComponent(productId)}`, params);
29
+ }
30
+ };
31
+ var PricesResource = class {
32
+ constructor(request) {
33
+ this.request = request;
34
+ }
35
+ request;
36
+ /** POST /v1/checkout/products/:id/prices */
37
+ create(productId, params) {
38
+ return this.request("POST", `/v1/checkout/products/${encodeURIComponent(productId)}/prices`, params);
39
+ }
40
+ /** GET /v1/checkout/products/:id/prices */
41
+ list(productId, params) {
42
+ return this.request("GET", `/v1/checkout/products/${encodeURIComponent(productId)}/prices${toQuery(params)}`);
43
+ }
44
+ /** GET /v1/checkout/prices/:id */
45
+ retrieve(priceId) {
46
+ return this.request("GET", `/v1/checkout/prices/${encodeURIComponent(priceId)}`);
47
+ }
48
+ /**
49
+ * PATCH /v1/checkout/prices/:id — nickname(표시 메타)만.
50
+ * 금액류 필드(amount_atomic 등)는 immutable — 서버가 PRICE_IMMUTABLE_FIELD로 거부한다.
51
+ * 가격 변경은 `prices.create()`로 새 price를 만들 것. "그만 팔기"는 해당 Price 를 쓰는
52
+ * Payment Link 를 inactive 로 한다(Price 자체에는 active/inactive 개념이 없다).
53
+ */
54
+ update(priceId, params) {
55
+ return this.request("PATCH", `/v1/checkout/prices/${encodeURIComponent(priceId)}`, params);
56
+ }
57
+ };
58
+ var PaymentLinksResource = class {
59
+ constructor(request) {
60
+ this.request = request;
61
+ }
62
+ request;
63
+ /** POST /v1/checkout/payment-links */
64
+ create(params) {
65
+ return this.request("POST", "/v1/checkout/payment-links", params);
66
+ }
67
+ /** GET /v1/checkout/payment-links */
68
+ list(params) {
69
+ return this.request("GET", `/v1/checkout/payment-links${toQuery(params)}`);
70
+ }
71
+ /** GET /v1/checkout/payment-links/:id */
72
+ retrieve(linkId) {
73
+ return this.request("GET", `/v1/checkout/payment-links/${encodeURIComponent(linkId)}`);
74
+ }
75
+ /** PATCH /v1/checkout/payment-links/:id */
76
+ update(linkId, params) {
77
+ return this.request("PATCH", `/v1/checkout/payment-links/${encodeURIComponent(linkId)}`, params);
78
+ }
79
+ };
80
+
1
81
  // src/client.ts
82
+ function validateLineItem(li) {
83
+ if ("price_id" in li) return li;
84
+ const hasHuman = li.amount != null;
85
+ const hasAtomic = li.amount_atomic != null;
86
+ if (hasHuman === hasAtomic) {
87
+ throw new Error('[BitPal] line item requires exactly one of `amount` (USD number, e.g. "29") or `amount_atomic` (raw \u03BCUSD integer, e.g. "29000000").');
88
+ }
89
+ return li;
90
+ }
2
91
  var DEFAULT_BASE_URL = "https://api.bitpal.io";
3
92
  var DEFAULT_TIMEOUT = 3e4;
4
93
  var BitPalCheckoutClient = class {
94
+ /** 서버 메서드용 secret 키. 공개(buyer-facing) 메서드는 없이도 동작. */
5
95
  apiKey;
6
96
  baseUrl;
7
97
  timeout;
98
+ /** PROD-LINK-3a-SDK: Product 카탈로그 관리 (server-side 전용) */
99
+ products;
100
+ /** PROD-LINK-3a-SDK: immutable Price 관리 — 가격 변경은 비활성화 + 새 price 생성 */
101
+ prices;
102
+ /** PROD-LINK-4-SDK: Payment Link 관리 — 대시보드 없이 코드로 결제 링크 생성 (server-side 전용) */
103
+ paymentLinks;
8
104
  constructor(config) {
9
- if (!config.apiKey) throw new Error("apiKey is required");
10
105
  this.apiKey = config.apiKey;
11
106
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
12
107
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
108
+ const requester = (method, path, body) => this.request(method, path, body);
109
+ this.products = new ProductsResource(requester);
110
+ this.prices = new PricesResource(requester);
111
+ this.paymentLinks = new PaymentLinksResource(requester);
13
112
  }
14
113
  /* ─── 내부 HTTP ─── */
15
114
  async request(method, path, body, extraHeaders) {
@@ -21,7 +120,8 @@ var BitPalCheckoutClient = class {
21
120
  method,
22
121
  headers: {
23
122
  "Content-Type": "application/json",
24
- Authorization: `Bearer ${this.apiKey}`,
123
+ // 공개 메서드(session_token 기반)는 apiKey 없이 호출 가능 — 있을 때만 Authorization 첨부.
124
+ ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
25
125
  ...extraHeaders ?? {}
26
126
  },
27
127
  body: body ? JSON.stringify(body) : void 0,
@@ -41,22 +141,35 @@ var BitPalCheckoutClient = class {
41
141
  /**
42
142
  * 체크아웃 세션 생성.
43
143
  *
44
- * 모든 `line_items[].amount`는 atomic μUSDC 정수 문자열이어야 한다 ($1 = "1000000").
45
- * decimal 표기는 거부된다 — `toAtomicUSDC()` 헬퍼 사용 권장.
144
+ * `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
145
+ * 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
46
146
  *
47
147
  * @example
48
- * import { BitPal, toAtomicUSDC } from '@bitpal/checkout';
148
+ * import { BitPal } from '@bitpal/checkout';
49
149
  * const bp = new BitPal('bp_test_...');
50
150
  * const { data } = await bp.checkout.createSession({
51
- * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
151
+ * // amount: 사람 표기 USD("29", "0.5"). 금액은 USD 가격이라 체인/토큰 decimal 무관.
152
+ * line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
52
153
  * });
53
154
  * console.log(data.url); // → buyer를 redirect할 결제 페이지
155
+ *
156
+ * // 멀티체인 허용 — 구매자가 Base/Optimism 중 선택(하나뿐이면 자동 선택):
157
+ * // 어떤 체인·토큰이든 amount 그대로 — 서버가 발급 시 토큰 decimals 로 재스케일.
158
+ * await bp.checkout.createSession({
159
+ * line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
160
+ * allowed_pay_chains: ['eip155:84532', 'eip155:11155420'],
161
+ * });
162
+ * // 단일 체인 고정은 pay_chain 사용(둘은 상호 배타).
54
163
  */
55
164
  async createSession(params) {
165
+ if (params.pay_chain && params.allowed_pay_chains && params.allowed_pay_chains.length > 0) {
166
+ throw new Error("[BitPal] pay_chain and allowed_pay_chains are mutually exclusive \u2014 use only one.");
167
+ }
168
+ const body = { ...params, line_items: params.line_items.map(validateLineItem) };
56
169
  const res = await this.request(
57
170
  "POST",
58
171
  "/v1/checkout/sessions",
59
- params
172
+ body
60
173
  );
61
174
  if (res.data) {
62
175
  if (res.data.checkout_url) {
@@ -86,148 +199,56 @@ var BitPalCheckoutClient = class {
86
199
  `/v1/checkout/sessions${query ? `?${query}` : ""}`
87
200
  );
88
201
  }
89
- /* ─── Refunds ─── */
90
- /**
91
- * Auto refund — escrow 보관 중일 때 컨트랙트가 buyer에게 직접 transfer.
92
- *
93
- * `params.amount` 미지정 시 backend가 잔액 전액(`type: 'full'`). 명시 시 `type: 'partial'`.
94
- * **Idempotency-Key 권장** — 같은 키로 재시도 시 backend가 같은 결과 반환.
95
- *
96
- * 머천트가 `frozen` 상태면 환불은 허용되지만, `suspended` 상태에서는 차단된다.
97
- *
98
- * @example
99
- * ```ts
100
- * import { randomUUID } from 'node:crypto';
101
- * const { data } = await bitpal.checkout.refundSession(
102
- * sessionId,
103
- * { amount: toAtomicUSDC('5.00'), reason: 'customer_requested' },
104
- * { idempotencyKey: randomUUID() },
105
- * );
106
- * console.log(data.txHash); // on-chain tx hash (또는 simulation 환경에서 sim_xxx)
107
- * ```
108
- */
109
- async refundSession(sessionId, params = {}, opts) {
110
- return this.request(
111
- "POST",
112
- `/v1/checkout/sessions/${encodeURIComponent(sessionId)}/refund`,
113
- params,
114
- opts?.idempotencyKey ? { "Idempotency-Key": opts.idempotencyKey } : void 0
115
- );
116
- }
202
+ /* ─── 안2 비수탁 결제 (deposit-address) ─── */
117
203
  /**
118
- * Record refund — `status='settled'` 후 머천트가 외부 지갑에서 직접 송금하고 tx_hash를 입력.
119
- * 컨트랙트 호출 없음 (DB-only).
204
+ * 공개 세션 조회 (`GET /v1/checkout/sessions/:id/public`) — 인증 불요.
120
205
  *
121
- * **Idempotency-Key 권장**.
122
- *
123
- * @example
124
- * ```ts
125
- * await bitpal.checkout.recordRefund(
126
- * sessionId,
127
- * {
128
- * refund_amount: toAtomicUSDC('5.00'),
129
- * tx_hash: '0xabc...',
130
- * reason: 'manual_offchain_refund',
131
- * },
132
- * { idempotencyKey: randomUUID() },
133
- * );
134
- * ```
206
+ * buyer-facing 결제 페이지가 표시/자산선택에 쓰는 서버 권위 뷰. `payment_options` 로
207
+ * 구매자 선택 가능한 chain×token 을 얻고, 발급 후에는 `deposit_address` 가 채워진다.
135
208
  */
136
- async recordRefund(sessionId, params, opts) {
137
- return this.request(
138
- "POST",
139
- `/v1/checkout/sessions/${encodeURIComponent(sessionId)}/record-refund`,
140
- params,
141
- opts?.idempotencyKey ? { "Idempotency-Key": opts.idempotencyKey } : void 0
142
- );
143
- }
144
- /** 세션별 환불 내역 (auto + record 통합) */
145
- async listRefunds(sessionId) {
209
+ async getPublicSession(sessionId) {
146
210
  return this.request(
147
211
  "GET",
148
- `/v1/checkout/sessions/${encodeURIComponent(sessionId)}/refunds`
212
+ `/v1/checkout/sessions/${encodeURIComponent(sessionId)}/public`
149
213
  );
150
214
  }
151
- /* ─── External On-chain Payments ─── */
152
215
  /**
153
- * CheckoutEscrow에 직접 입금한 외부 결제를 BitPal에 제출·검증.
216
+ * 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
154
217
  *
155
- * Buyer가 merchant UI/SDK에서 `CheckoutEscrow.deposit*()` 함수를 직접 호출한 뒤
156
- * 결과 tx_hash를 이 메서드로 제출한다. BitPal은 receipt를 fetch하여
157
- * `PaymentDeposited` 이벤트를 검증하고 세션 상태를 갱신한다.
218
+ * 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
219
+ * 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
220
+ * 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
158
221
  *
159
- * **인증 방식:**
160
- * - Merchant server (API key): `Authorization: Bearer bp_test_*` 헤더 자동 첨부
161
- * - Buyer 측 (session_token): `params.session_token` 전달
222
+ * refundAddress 는 입금주소에 커밋되어 발급 후 변경 불가. session_token 은 createSession 응답값.
162
223
  *
163
224
  * @example
164
225
  * ```ts
165
- * // Merchant server — API key 인증
166
- * const { data } = await bitpal.checkout.submitExternalPayment(sessionId, {
167
- * tx_hash: '0xabc...',
168
- * chain: 'base',
226
+ * const { data } = await bitpal.checkout.issueDepositAddress(sessionId, {
227
+ * session_token: session.session_token!,
228
+ * refundAddress: '0xBuyerWallet...',
229
+ * chain: 'eip155:8453', // 멀티옵션 세션만 필요
230
+ * token: 'USDC',
169
231
  * });
170
- * console.log(data.validation_status); // 'valid' | 'underpaid' | 'overpaid' | ...
171
- * console.log(data.session_status); // 'paid' | 'unresolved_underpaid' | ...
232
+ * console.log(data.depositAddress, data.expectedAmount); // 이 주소로 정확 금액 송금
172
233
  * ```
173
234
  */
174
- async submitExternalPayment(sessionId, params, opts) {
235
+ async issueDepositAddress(sessionId, params) {
175
236
  return this.request(
176
237
  "POST",
177
- `/v1/checkout/sessions/${encodeURIComponent(sessionId)}/submit-external-payment`,
178
- params,
179
- opts?.idempotencyKey ? { "Idempotency-Key": opts.idempotencyKey } : void 0
180
- );
181
- }
182
- /**
183
- * Gasless checkout payment through the BitPal API relayer.
184
- *
185
- * The buyer signs **two** EIP-712 typed-data structures and the API calls
186
- * `CheckoutEscrow.depositWithAuthorization()` with the platform relayer:
187
- *
188
- * 1. **EIP-3009 USDC TransferWithAuthorization** (`params.authorization`) —
189
- * authorizes USDC transfer from buyer to escrow.
190
- * 2. **PaymentAuthorization** (`params.payment_auth`, since SDK v0.2.0) —
191
- * binds the entire payment intent (paymentId, merchant, amount, fee,
192
- * reserve, duration) to the buyer's signature. Without this, a compromised
193
- * gateway key could redirect funds to an attacker-controlled merchant.
194
- *
195
- * Build the PaymentAuthorization typed-data with `buildPaymentAuthTypedData()`.
196
- * The PaymentAuthorization nonce comes from `CheckoutEscrow.paymentAuthNonce(payer)`.
197
- *
198
- * Use the returned session/order state only as immediate UX feedback; the
199
- * merchant backend should finalize fulfillment from signed webhooks.
200
- *
201
- * @breaking-change v0.2.0 — `payment_auth` field is now required. v0.1.x clients
202
- * must upgrade their signing flow. See MIGRATION_v0.2.md.
203
- */
204
- async payWithAuthorization(sessionId, params, opts) {
205
- return this.request(
206
- "POST",
207
- `/v1/checkout/sessions/${encodeURIComponent(sessionId)}/submit-payment`,
208
- params,
209
- opts?.idempotencyKey ? { "Idempotency-Key": opts.idempotencyKey } : void 0
238
+ `/v1/checkout/sessions/${encodeURIComponent(sessionId)}/deposit-address`,
239
+ params
210
240
  );
211
241
  }
212
242
  /**
213
- * 세션의 external on-chain 결제 검증 기록 목록 조회.
214
- *
215
- * `getSession()`의 `payment_observations` 필드와 동일한 데이터를 반환하지만
216
- * 검증 기록만 명시적으로 조회할 때 사용한다.
243
+ * 경량 상태 폴링 (`GET /v1/checkout/sessions/:id/status`) — 인증 불요.
217
244
  *
218
- * @example
219
- * ```ts
220
- * const { data } = await bitpal.checkout.getExternalPaymentStatus(sessionId);
221
- * const latest = data[0];
222
- * if (latest?.finality_status === 'final_confirmed') {
223
- * // 12 confirmations 완료 — 결제 확정
224
- * }
225
- * ```
245
+ * status: awaiting_deposit → deposit_detected → paid_confirmed/swept. 확정/정산은 webhook 으로도
246
+ * 통지되므로, 서버 사이드 확정은 webhook 을 신뢰(폴링은 buyer UX 용).
226
247
  */
227
- async getExternalPaymentStatus(sessionId) {
248
+ async getStatus(sessionId) {
228
249
  return this.request(
229
250
  "GET",
230
- `/v1/checkout/sessions/${encodeURIComponent(sessionId)}/payment-observations`
251
+ `/v1/checkout/sessions/${encodeURIComponent(sessionId)}/status`
231
252
  );
232
253
  }
233
254
  /* ─── Fees ─── */
@@ -251,17 +272,12 @@ var BitPalError = class extends Error {
251
272
  // src/webhook.ts
252
273
  var CHECKOUT_WEBHOOK_EVENTS = [
253
274
  "checkout.session.created",
275
+ "checkout.session.deposit_detected",
254
276
  "checkout.session.paid",
255
277
  "checkout.session.settled",
256
278
  "checkout.session.expired",
257
279
  "checkout.session.unresolved",
258
- "checkout.session.release_deferred",
259
- "checkout.payment.failed",
260
- "checkout.payment.finalized",
261
- "checkout.payment.revoked",
262
- "checkout.refund.recorded",
263
- "checkout.refund.completed",
264
- "checkout.refund.partial"
280
+ "checkout.refund.recorded"
265
281
  ];
266
282
  var WEBHOOK_HEADERS = {
267
283
  signature: "X-Webhook-Signature-256",
@@ -271,14 +287,21 @@ var WEBHOOK_HEADERS = {
271
287
  deliveryId: "X-Webhook-Delivery-Id"
272
288
  };
273
289
  var SIGNATURE_PREFIX = "sha256=";
274
- async function verifyWebhookSignature(payload, signature, secret) {
290
+ async function verifyWebhookSignature(payload, signature, secret, opts) {
275
291
  if (!payload || !signature || !secret) return false;
276
292
  if (!signature.startsWith(SIGNATURE_PREFIX)) return false;
293
+ const timestamp = opts?.timestamp;
294
+ if (!timestamp) return false;
295
+ const parsedMs = Date.parse(timestamp);
296
+ if (!Number.isFinite(parsedMs)) return false;
297
+ if (new Date(parsedMs).toISOString() !== timestamp) return false;
298
+ const toleranceMs = (opts?.toleranceSec ?? 300) * 1e3;
299
+ if (Math.abs(Date.now() - parsedMs) > toleranceMs) return false;
277
300
  const sigHex = signature.slice(SIGNATURE_PREFIX.length);
278
- if (!/^[0-9a-f]+$/.test(sigHex)) return false;
301
+ if (!/^[0-9a-f]{64}$/.test(sigHex)) return false;
279
302
  try {
280
303
  const crypto = await import("crypto");
281
- const expectedHex = crypto.createHmac("sha256", secret).update(payload, "utf8").digest("hex");
304
+ const expectedHex = crypto.createHmac("sha256", secret).update(`${timestamp}.${payload}`, "utf8").digest("hex");
282
305
  if (sigHex.length !== expectedHex.length) return false;
283
306
  return crypto.timingSafeEqual(
284
307
  Buffer.from(sigHex, "hex"),
@@ -297,93 +320,39 @@ function parseWebhookEvent(body) {
297
320
  }
298
321
 
299
322
  // src/amount.ts
300
- var USDC_DECIMALS = 6n;
301
- var USDC_SCALE = 10n ** USDC_DECIMALS;
302
- function toAtomicUSDC(decimal) {
303
- if (typeof decimal !== "string" || !/^\d+(\.\d{1,6})?$/.test(decimal)) {
304
- throw new Error(`toAtomicUSDC: invalid decimal '${decimal}' (max 6 fractional digits, no negatives)`);
323
+ function toAtomic(decimal, decimals) {
324
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
325
+ throw new Error(`toAtomic: invalid decimals '${decimals}'`);
305
326
  }
306
- const [whole, frac = ""] = decimal.split(".");
307
- const fracPadded = frac.padEnd(Number(USDC_DECIMALS), "0");
308
- return (BigInt(whole) * USDC_SCALE + BigInt(fracPadded || "0")).toString();
327
+ const fracPattern = decimals > 0 ? `(\\.\\d{1,${decimals}})?` : "";
328
+ if (typeof decimal !== "string" || !new RegExp(`^\\d+${fracPattern}$`).test(decimal)) {
329
+ throw new Error(`toAtomic: invalid decimal '${decimal}' (max ${decimals} fractional digits, no negatives)`);
330
+ }
331
+ const scale = 10n ** BigInt(decimals);
332
+ const [whole = "0", frac = ""] = decimal.split(".");
333
+ const fracPadded = frac.padEnd(decimals, "0");
334
+ return (BigInt(whole) * scale + BigInt(fracPadded || "0")).toString();
309
335
  }
310
- function fromAtomicUSDC(atomic) {
336
+ function fromAtomic(atomic, decimals) {
337
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
338
+ throw new Error(`fromAtomic: invalid decimals '${decimals}'`);
339
+ }
311
340
  if (typeof atomic !== "string" || !/^\d+$/.test(atomic)) {
312
- throw new Error(`fromAtomicUSDC: invalid atomic '${atomic}' (must be non-negative integer string)`);
341
+ throw new Error(`fromAtomic: invalid atomic '${atomic}' (must be non-negative integer string)`);
313
342
  }
343
+ const scale = 10n ** BigInt(decimals);
314
344
  const big = BigInt(atomic);
315
- const whole = big / USDC_SCALE;
316
- const frac = big % USDC_SCALE;
317
- const fracStr = frac.toString().padStart(Number(USDC_DECIMALS), "0").replace(/0+$/, "");
345
+ const whole = big / scale;
346
+ const frac = big % scale;
347
+ if (decimals === 0) return `${whole}`;
348
+ const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
318
349
  return fracStr.length > 0 ? `${whole}.${fracStr.padEnd(2, "0")}` : `${whole}.00`;
319
350
  }
320
-
321
- // src/typed-data.ts
322
- function buildPaymentAuthTypedData(args) {
323
- return {
324
- domain: {
325
- name: "CheckoutEscrow",
326
- version: "1",
327
- chainId: args.chainId,
328
- verifyingContract: args.escrowAddress
329
- },
330
- types: {
331
- PaymentAuthorization: [
332
- { name: "paymentId", type: "bytes32" },
333
- { name: "payer", type: "address" },
334
- { name: "merchant", type: "address" },
335
- { name: "token", type: "address" },
336
- { name: "amount", type: "uint256" },
337
- { name: "feeAmount", type: "uint256" },
338
- { name: "reserveAmount", type: "uint256" },
339
- { name: "duration", type: "uint256" },
340
- { name: "deadline", type: "uint256" },
341
- { name: "nonce", type: "uint256" }
342
- ]
343
- },
344
- primaryType: "PaymentAuthorization",
345
- message: {
346
- paymentId: args.paymentId,
347
- payer: args.payer,
348
- merchant: args.merchant,
349
- token: args.token,
350
- amount: BigInt(args.amount),
351
- feeAmount: BigInt(args.feeAmount),
352
- reserveAmount: BigInt(args.reserveAmount),
353
- duration: BigInt(args.duration),
354
- deadline: BigInt(args.deadline),
355
- nonce: BigInt(args.nonce)
356
- }
357
- };
358
- }
359
- function buildConfirmReceiptTypedData(args) {
360
- return {
361
- domain: {
362
- name: "CheckoutEscrow",
363
- version: "1",
364
- chainId: args.chainId,
365
- verifyingContract: args.escrowAddress
366
- },
367
- types: {
368
- ConfirmReceipt: [
369
- { name: "paymentId", type: "bytes32" },
370
- { name: "payer", type: "address" },
371
- { name: "deadline", type: "uint256" },
372
- { name: "nonce", type: "uint256" }
373
- ]
374
- },
375
- primaryType: "ConfirmReceipt",
376
- message: {
377
- paymentId: args.paymentId,
378
- payer: args.payer,
379
- deadline: BigInt(args.deadline),
380
- nonce: BigInt(args.nonce)
381
- }
382
- };
351
+ function toAtomicUSDC(decimal) {
352
+ return toAtomic(decimal, 6);
383
353
  }
384
- function sessionIdToPaymentId(sessionId) {
385
- const hex = Array.from(new TextEncoder().encode(sessionId)).map((b) => b.toString(16).padStart(2, "0")).join("").padEnd(64, "0");
386
- return `0x${hex}`;
354
+ function fromAtomicUSDC(atomic) {
355
+ return fromAtomic(atomic, 6);
387
356
  }
388
357
 
389
358
  // src/index.ts
@@ -399,12 +368,14 @@ export {
399
368
  BitPalCheckoutClient,
400
369
  BitPalError,
401
370
  CHECKOUT_WEBHOOK_EVENTS,
371
+ PaymentLinksResource,
372
+ PricesResource,
373
+ ProductsResource,
402
374
  WEBHOOK_HEADERS,
403
- buildConfirmReceiptTypedData,
404
- buildPaymentAuthTypedData,
375
+ fromAtomic,
405
376
  fromAtomicUSDC,
406
377
  parseWebhookEvent,
407
- sessionIdToPaymentId,
378
+ toAtomic,
408
379
  toAtomicUSDC,
409
380
  verifyWebhookSignature
410
381
  };
package/package.json CHANGED
@@ -1,21 +1,35 @@
1
1
  {
2
2
  "name": "@bitpal/checkout",
3
- "version": "0.3.0",
4
- "description": "BitPal Checkout SDK — crypto payment sessions, external on-chain payment verification, gasless checkout (with PaymentAuthorization double-defense), refunds, fee preview.",
5
- "license": "UNLICENSED",
3
+ "version": "0.6.0",
4
+ "description": "BitPal Checkout SDK — non-custodial crypto checkout: create sessions, issue per-order deposit addresses, poll status, verify signed webhooks.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/superlabs-x/bitpal-checkout-sdk.git"
9
+ },
10
+ "homepage": "https://github.com/superlabs-x/bitpal-checkout-sdk#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/superlabs-x/bitpal-checkout-sdk/issues"
13
+ },
6
14
  "type": "module",
7
- "main": "dist/index.js",
8
- "types": "dist/index.d.ts",
15
+ "main": "./dist/index.cjs",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
9
18
  "exports": {
10
19
  ".": {
11
- "import": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
20
+ "import": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.cts",
26
+ "default": "./dist/index.cjs"
27
+ }
13
28
  }
14
29
  },
15
30
  "files": [
16
31
  "dist",
17
- "README.md",
18
- "MIGRATION_v0.2.md"
32
+ "README.md"
19
33
  ],
20
34
  "keywords": [
21
35
  "bitpal",
@@ -27,7 +41,7 @@
27
41
  "evm",
28
42
  "base",
29
43
  "stablecoin",
30
- "escrow"
44
+ "non-custodial"
31
45
  ],
32
46
  "publishConfig": {
33
47
  "access": "public"
@@ -35,15 +49,17 @@
35
49
  "engines": {
36
50
  "node": ">=18"
37
51
  },
52
+ "scripts": {
53
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
54
+ "lint": "tsc --noEmit",
55
+ "test": "vitest run",
56
+ "prepublishOnly": "npm test && npm run build",
57
+ "release": "npm publish --access public"
58
+ },
38
59
  "devDependencies": {
60
+ "@types/node": "^20.11.0",
39
61
  "tsup": "^8.5.1",
40
62
  "typescript": "^5.5.0",
41
63
  "vitest": "^2.1.9"
42
- },
43
- "scripts": {
44
- "build": "tsup src/index.ts --format esm --dts --clean",
45
- "lint": "tsc --noEmit",
46
- "test": "vitest run",
47
- "release": "pnpm publish --access public --no-git-checks"
48
64
  }
49
- }
65
+ }