@bitpal/checkout 0.2.0 → 0.5.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.
@@ -0,0 +1,566 @@
1
+ /**
2
+ * @bitpal/checkout SDK — 타입 정의
3
+ */
4
+ interface BitPalConfig {
5
+ /**
6
+ * API 키(서버 전용, secret). prefix 가 mode 를 결정합니다:
7
+ * - `bp_test_...` → TEST mode (testnet 또는 local anvil)
8
+ * - `bp_live_...` → LIVE mode (mainnet)
9
+ *
10
+ * BitPal 콘솔 → Developers → API Keys 에서 발급.
11
+ *
12
+ * **선택**: createSession/listSessions/products 등 서버 메서드는 필수. 그러나 buyer-facing 공개 메서드
13
+ * (getPublicSession/getStatus/issueDepositAddress)는 인증 불요(session_token 기반)이므로 브라우저에서
14
+ * apiKey 없이 쓸 수 있다 — 절대 secret 키를 브라우저에 노출하지 말 것.
15
+ */
16
+ apiKey?: string;
17
+ /**
18
+ * BitPal API 기본 URL. 환경별로 다릅니다:
19
+ * - production: `https://api.bitpal.io` (기본값) — 실제 testnet/mainnet 컨트랙트
20
+ * - local dev: `http://localhost:3100` — anvil 로컬 체인 + 로컬 BitPal API
21
+ *
22
+ * mode (TEST/LIVE) 와 baseUrl 은 직교 (orthogonal): 어떤 baseUrl 이든 키 prefix 가 mode 를 결정.
23
+ */
24
+ baseUrl?: string;
25
+ /** 요청 타임아웃 (ms, 기본값: 30000) */
26
+ timeout?: number;
27
+ }
28
+ interface LineItem {
29
+ name: string;
30
+ /**
31
+ * Atomic μUSDC 정수 문자열 (USDC/USDT 6 decimals).
32
+ * $1 = "1000000", $29 = "29000000".
33
+ * decimal 표기는 거부된다 — `toAtomicUSDC('29.00')` 헬퍼 사용 권장.
34
+ */
35
+ amount: string;
36
+ currency: string;
37
+ quantity?: number;
38
+ }
39
+ interface PriceLineItem {
40
+ /** price_... — bitpal.checkout.prices.create()로 만든 가격 */
41
+ price_id: string;
42
+ quantity?: number;
43
+ }
44
+ type CheckoutLineItemInput = LineItem | PriceLineItem;
45
+ interface CreateSessionParams {
46
+ line_items: CheckoutLineItemInput[];
47
+ /** 고객 지갑 주소 (선택) */
48
+ payer_wallet?: string;
49
+ /** 결제 체인 (예: eip155:8453). 단일 고정 — allowed_pay_chains와 상호 배타. */
50
+ pay_chain?: string;
51
+ /**
52
+ * 허용 결제 체인 목록 (CAIP-2). 구매자가 이 중에서 선택하며, 하나뿐이면 자동 선택된다.
53
+ * pay_chain(단일 고정)과 **상호 배타** — 동시 지정 시 SDK가 에러를 throw한다.
54
+ * 예: ['eip155:84532', 'eip155:11155420', 'eip155:80002', 'eip155:421614']
55
+ */
56
+ allowed_pay_chains?: string[];
57
+ /** 결제 자산 (예: USDC) */
58
+ pay_asset?: string;
59
+ /** 허용 오차 (%, 기본값: 2.00) */
60
+ tolerance_percent?: string;
61
+ /** 만료 시간 (초, 기본값: 1800) */
62
+ expires_in_seconds?: number;
63
+ /** 메타데이터 (모든 값은 string 이어야 함) */
64
+ metadata?: Record<string, string>;
65
+ /** 결제 완료 후 buyer 가 redirect 될 URL (선택) */
66
+ success_url?: string;
67
+ /** 결제 취소 시 buyer 가 redirect 될 URL (선택) */
68
+ cancel_url?: string;
69
+ }
70
+ interface CheckoutSession {
71
+ id: string;
72
+ status: string;
73
+ amount_total: string;
74
+ currency: string;
75
+ line_items: LineItem[];
76
+ pay_chain: string | null;
77
+ pay_asset: string | null;
78
+ payer_wallet: string | null;
79
+ seller_wallet: string;
80
+ tolerance_percent: string;
81
+ expires_at: string | null;
82
+ paid_at: string | null;
83
+ settled_at: string | null;
84
+ tx_hash: string | null;
85
+ created_at: string;
86
+ /** 'hosted' — 결제 경로. hosted deposit-address 단일 경로. */
87
+ payment_source?: string;
88
+ /** 호스티드 결제 페이지 URL (token 쿼리 포함). backend 응답 그대로 사용 — buyer를 이 URL로 redirect. */
89
+ checkout_url?: string;
90
+ /** 1회용 session token. `checkout_url`에 이미 query로 박혀 있어 별도 사용 불필요. */
91
+ session_token?: string;
92
+ /** `checkout_url`의 별칭 (backwards compat). buyer redirect 시 이 필드 또는 `checkout_url` 사용. */
93
+ url: string;
94
+ }
95
+ /**
96
+ * 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
97
+ * `issueDepositAddress({ chain, token })` 로 입금주소를 발급받는다. 단일-자산 세션은 1개.
98
+ */
99
+ interface PaymentOption {
100
+ /** CAIP-2 (예: eip155:8453) */
101
+ caip2: string;
102
+ chain_id: number;
103
+ token_symbol: string;
104
+ token_address: string;
105
+ }
106
+ /**
107
+ * `getPublicSession()` (`GET /v1/checkout/sessions/:id/public`) 응답 — 인증 불요(공개).
108
+ * buyer-facing 결제 페이지가 표시/폴링에 쓰는 서버 권위 뷰.
109
+ */
110
+ interface PublicSession {
111
+ id: string;
112
+ /** created | awaiting_deposit | deposit_detected | paid_confirmed | swept | unresolved_* | wrong_token | expired ... */
113
+ status: string;
114
+ mode: string | null;
115
+ currency: string;
116
+ /** Atomic 정수 문자열(토큰 decimals 기준). */
117
+ amount_total: string;
118
+ amount_received?: string | null;
119
+ pay_chain: string | null;
120
+ pay_asset: string | null;
121
+ /** 발급된 주문별 forwarder 입금주소(미발급이면 null). */
122
+ deposit_address?: string | null;
123
+ /** 구매자 선택 가능한 결제 옵션(서버 권위). 단일-자산이면 1개. */
124
+ payment_options: PaymentOption[];
125
+ chain_id: number | null;
126
+ token_address: string | null;
127
+ tx_hash: string | null;
128
+ release_tx_hash?: string | null;
129
+ expires_at: string | null;
130
+ success_url: string | null;
131
+ cancel_url: string | null;
132
+ fee_amount: string | null;
133
+ net_amount: string | null;
134
+ seller_wallet: string;
135
+ paid_at: string | null;
136
+ settled_at: string | null;
137
+ created_at: string;
138
+ line_items: LineItem[];
139
+ }
140
+ /**
141
+ * `issueDepositAddress()` 요청 파라미터.
142
+ * chain/token 은 멀티옵션 세션에서만 필요(단일-자산 세션은 생략). refundAddress 는 필수 —
143
+ * 금액부족/잘못된토큰/만료 시 이 주소로 환불되며, 입금주소에 커밋되어 발급 후 변경 불가.
144
+ */
145
+ interface IssueDepositAddressParams {
146
+ /** 결제 페이지 접근 증명(HMAC). createSession 응답의 session_token. */
147
+ session_token: string;
148
+ /** 환불 주소(0x EVM). 필수. */
149
+ refundAddress: string;
150
+ /** 선택 자산의 chain(CAIP-2 또는 short-name). 멀티옵션 세션 필수. */
151
+ chain?: string;
152
+ /** 선택 자산의 token symbol(예: USDC). 멀티옵션 세션 필수. */
153
+ token?: string;
154
+ }
155
+ /**
156
+ * `issueDepositAddress()` 응답 — 이 주소로 정확 금액을 송금하면 watcher 가 감지, keeper 가 정산한다.
157
+ */
158
+ interface DepositAddress {
159
+ sessionId: string;
160
+ chain: string;
161
+ /** 주문별 CREATE2 forwarder 입금주소. */
162
+ depositAddress: string;
163
+ expectedToken: string;
164
+ /** Atomic 정수 문자열. */
165
+ expectedAmount: string;
166
+ feeAmount: string;
167
+ refundAddress: string;
168
+ /** Unix seconds — on-chain deadline. */
169
+ deadline: string;
170
+ expiresAt: string | null;
171
+ }
172
+ /**
173
+ * `getStatus()` (`GET /v1/checkout/sessions/:id/status`) 응답 — 경량 폴링용(인증 불요).
174
+ */
175
+ interface SessionStatus {
176
+ status: string;
177
+ paid_at: string | null;
178
+ }
179
+ /**
180
+ * 수수료 미리보기 응답.
181
+ * amount, fee_amount, net_amount는 모두 atomic μUSDC 문자열 (e.g. "1500000" = $1.50).
182
+ * fee_rate는 비율 문자열 (e.g. "0.015" = 1.5%).
183
+ */
184
+ interface FeePreview {
185
+ amount: string;
186
+ fee_rate: string;
187
+ fee_amount: string;
188
+ net_amount: string;
189
+ }
190
+ /**
191
+ * 환불은 온체인 자동 경로 — SDK 에 수동 환불 API 는 없다.
192
+ * 금액부족/잘못된토큰/만료 시 issueDepositAddress 에 지정한 refundAddress 로 자동 환불되고,
193
+ * 세션 status(expired/unresolved) + webhook 으로 통지된다.
194
+ */
195
+ /**
196
+ * 멱등성 키 — 동일 키로 재시도 시 backend가 같은 결과 반환.
197
+ */
198
+ interface IdempotencyOptions {
199
+ idempotencyKey?: string;
200
+ }
201
+ interface ApiResponse<T> {
202
+ data: T;
203
+ error?: string;
204
+ }
205
+ interface ListResponse<T> {
206
+ data: T[];
207
+ total?: number;
208
+ }
209
+ interface CheckoutProduct {
210
+ /** prod_... */
211
+ id: string;
212
+ name: string;
213
+ description: string | null;
214
+ image_url: string | null;
215
+ /** 'active' | 'inactive' */
216
+ status: string;
217
+ metadata: Record<string, unknown>;
218
+ mode: string;
219
+ created_at: string;
220
+ updated_at: string;
221
+ }
222
+ interface CheckoutPrice {
223
+ /** price_... */
224
+ id: string;
225
+ /** prod_... */
226
+ product_id: string;
227
+ /** 원자 단위 정수 문자열 ($1 = "1000000") — 불변 */
228
+ amount_atomic: string;
229
+ currency: string;
230
+ token_symbol: string;
231
+ /** v1: 'one_time' */
232
+ billing_type: string;
233
+ nickname: string | null;
234
+ lookup_key: string | null;
235
+ mode: string;
236
+ created_at: string;
237
+ }
238
+ interface CreateProductParams {
239
+ name: string;
240
+ description?: string;
241
+ image_url?: string;
242
+ metadata?: Record<string, unknown>;
243
+ }
244
+ interface UpdateProductParams {
245
+ name?: string;
246
+ description?: string | null;
247
+ image_url?: string | null;
248
+ metadata?: Record<string, unknown>;
249
+ status?: 'active' | 'inactive';
250
+ }
251
+ interface CreatePriceParams {
252
+ /** 원자 단위 정수 문자열 ($1 = "1000000"). `toAtomicUSDC('1.00')` 헬퍼 사용 권장 */
253
+ amount_atomic: string;
254
+ currency?: string;
255
+ token_symbol?: string;
256
+ billing_type?: 'one_time';
257
+ nickname?: string;
258
+ lookup_key?: string;
259
+ }
260
+ /**
261
+ * Price는 lifecycle 없는 immutable record — 변경 가능한 것은 nickname(표시 메타)뿐.
262
+ * 금액류 필드(amount_atomic 등)는 immutable, "그만 팔기"는 Price 가 아니라 그 Price 를 쓰는
263
+ * Payment Link 를 inactive 로 한다.
264
+ */
265
+ interface UpdatePriceParams {
266
+ nickname?: string | null;
267
+ }
268
+ interface ListProductsParams {
269
+ status?: 'active' | 'inactive';
270
+ limit?: number;
271
+ offset?: number;
272
+ }
273
+ interface ListPricesParams {
274
+ limit?: number;
275
+ offset?: number;
276
+ }
277
+
278
+ /**
279
+ * catalog.ts — Product / immutable Price 관리 리소스 (PROD-LINK-3a-SDK)
280
+ *
281
+ * server-side SDK 전용 (secret API key). 브라우저에서 사용 금지.
282
+ *
283
+ * 사용 예:
284
+ * ```ts
285
+ * const bitpal = new BitPal({ apiKey: 'bp_test_...' });
286
+ * const { data: product } = await bitpal.checkout.products.create({ name: 'Pro Plan' });
287
+ * const { data: price } = await bitpal.checkout.prices.create(product.id, {
288
+ * amount_atomic: toAtomicUSDC('29.00'),
289
+ * });
290
+ * ```
291
+ *
292
+ * Price immutable 원칙: 가격 변경은 update가 아니라 "새 price 생성"이다.
293
+ * `prices.update`는 nickname(표시 메타)만 받는다 — 금액류 필드 변경 helper는 의도적으로 없다.
294
+ * "그만 팔기"는 Price 를 끄는 게 아니라 그 Price 를 쓰는 Payment Link 를 inactive 로 한다.
295
+ */
296
+
297
+ /** client.ts의 private request를 리소스에 주입하기 위한 시그니처 */
298
+ type CatalogRequester = <T>(method: string, path: string, body?: unknown) => Promise<T>;
299
+ declare class ProductsResource {
300
+ private readonly request;
301
+ constructor(request: CatalogRequester);
302
+ /** POST /v1/checkout/products */
303
+ create(params: CreateProductParams): Promise<ApiResponse<CheckoutProduct>>;
304
+ /** GET /v1/checkout/products */
305
+ list(params?: ListProductsParams): Promise<ListResponse<CheckoutProduct>>;
306
+ /** GET /v1/checkout/products/:id */
307
+ retrieve(productId: string): Promise<ApiResponse<CheckoutProduct>>;
308
+ /** PATCH /v1/checkout/products/:id */
309
+ update(productId: string, params: UpdateProductParams): Promise<ApiResponse<CheckoutProduct>>;
310
+ }
311
+ declare class PricesResource {
312
+ private readonly request;
313
+ constructor(request: CatalogRequester);
314
+ /** POST /v1/checkout/products/:id/prices */
315
+ create(productId: string, params: CreatePriceParams): Promise<ApiResponse<CheckoutPrice>>;
316
+ /** GET /v1/checkout/products/:id/prices */
317
+ list(productId: string, params?: ListPricesParams): Promise<ListResponse<CheckoutPrice>>;
318
+ /** GET /v1/checkout/prices/:id */
319
+ retrieve(priceId: string): Promise<ApiResponse<CheckoutPrice>>;
320
+ /**
321
+ * PATCH /v1/checkout/prices/:id — nickname(표시 메타)만.
322
+ * 금액류 필드(amount_atomic 등)는 immutable — 서버가 PRICE_IMMUTABLE_FIELD로 거부한다.
323
+ * 가격 변경은 `prices.create()`로 새 price를 만들 것. "그만 팔기"는 해당 Price 를 쓰는
324
+ * Payment Link 를 inactive 로 한다(Price 자체에는 active/inactive 개념이 없다).
325
+ */
326
+ update(priceId: string, params: UpdatePriceParams): Promise<ApiResponse<CheckoutPrice>>;
327
+ }
328
+
329
+ /**
330
+ * @bitpal/checkout SDK — 클라이언트
331
+ *
332
+ * Phase 1 범위: 세션 생성/조회, 결제 링크 생성/조회, 수수료 미리보기.
333
+ * 크레딧 잔액/충전은 BitPal non-custodial 모델에서 제거됨.
334
+ */
335
+
336
+ declare class BitPalCheckoutClient {
337
+ /** 서버 메서드용 secret 키. 공개(buyer-facing) 메서드는 없이도 동작. */
338
+ private readonly apiKey;
339
+ private readonly baseUrl;
340
+ private readonly timeout;
341
+ /** PROD-LINK-3a-SDK: Product 카탈로그 관리 (server-side 전용) */
342
+ readonly products: ProductsResource;
343
+ /** PROD-LINK-3a-SDK: immutable Price 관리 — 가격 변경은 비활성화 + 새 price 생성 */
344
+ readonly prices: PricesResource;
345
+ constructor(config: BitPalConfig);
346
+ private request;
347
+ /**
348
+ * 체크아웃 세션 생성.
349
+ *
350
+ * 모든 `line_items[].amount`는 atomic μUSDC 정수 문자열이어야 한다 ($1 = "1000000").
351
+ * decimal 표기는 거부된다 — `toAtomicUSDC()` 헬퍼 사용 권장.
352
+ *
353
+ * @example
354
+ * import { BitPal, toAtomicUSDC } from '@bitpal/checkout';
355
+ * const bp = new BitPal('bp_test_...');
356
+ * const { data } = await bp.checkout.createSession({
357
+ * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
358
+ * });
359
+ * console.log(data.url); // → buyer를 redirect할 결제 페이지
360
+ *
361
+ * // 멀티체인 허용 — 구매자가 Base/Optimism 중 선택(하나뿐이면 자동 선택):
362
+ * await bp.checkout.createSession({
363
+ * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
364
+ * allowed_pay_chains: ['eip155:84532', 'eip155:11155420'],
365
+ * });
366
+ * // 단일 체인 고정은 pay_chain 사용(둘은 상호 배타).
367
+ */
368
+ createSession(params: CreateSessionParams): Promise<ApiResponse<CheckoutSession>>;
369
+ /** 세션 조회 */
370
+ getSession(sessionId: string): Promise<ApiResponse<CheckoutSession>>;
371
+ /** 세션 목록 조회 */
372
+ listSessions(params?: {
373
+ status?: string;
374
+ limit?: number;
375
+ offset?: number;
376
+ }): Promise<ListResponse<CheckoutSession>>;
377
+ /**
378
+ * 공개 세션 조회 (`GET /v1/checkout/sessions/:id/public`) — 인증 불요.
379
+ *
380
+ * buyer-facing 결제 페이지가 표시/자산선택에 쓰는 서버 권위 뷰. `payment_options` 로
381
+ * 구매자 선택 가능한 chain×token 을 얻고, 발급 후에는 `deposit_address` 가 채워진다.
382
+ */
383
+ getPublicSession(sessionId: string): Promise<ApiResponse<PublicSession>>;
384
+ /**
385
+ * 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
386
+ *
387
+ * 비수탁 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
388
+ * 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
389
+ * 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
390
+ *
391
+ * refundAddress 는 입금주소에 커밋되어 발급 후 변경 불가. session_token 은 createSession 응답값.
392
+ *
393
+ * @example
394
+ * ```ts
395
+ * const { data } = await bitpal.checkout.issueDepositAddress(sessionId, {
396
+ * session_token: session.session_token!,
397
+ * refundAddress: '0xBuyerWallet...',
398
+ * chain: 'eip155:8453', // 멀티옵션 세션만 필요
399
+ * token: 'USDC',
400
+ * });
401
+ * console.log(data.depositAddress, data.expectedAmount); // 이 주소로 정확 금액 송금
402
+ * ```
403
+ */
404
+ issueDepositAddress(sessionId: string, params: IssueDepositAddressParams): Promise<ApiResponse<DepositAddress>>;
405
+ /**
406
+ * 경량 상태 폴링 (`GET /v1/checkout/sessions/:id/status`) — 인증 불요.
407
+ *
408
+ * status: awaiting_deposit → deposit_detected → paid_confirmed/swept. 확정/정산은 webhook 으로도
409
+ * 통지되므로, 서버 사이드 확정은 webhook 을 신뢰(폴링은 buyer UX 용).
410
+ */
411
+ getStatus(sessionId: string): Promise<ApiResponse<SessionStatus>>;
412
+ /** 수수료 미리보기 — `/v1/checkout/fee-preview` (Phase 1 credit system 폐기 후 canonical path) */
413
+ previewFee(amount: string): Promise<ApiResponse<FeePreview>>;
414
+ }
415
+ declare class BitPalError extends Error {
416
+ readonly statusCode: number;
417
+ constructor(message: string, statusCode: number);
418
+ }
419
+
420
+ /**
421
+ * @bitpal/checkout — Webhook 서명 검증 + 이벤트 타입
422
+ *
423
+ * BitPal 이 보내는 canonical webhook contract(envelope/서명/헤더)와 1:1 정합한다.
424
+ */
425
+ /**
426
+ * BitPal 이 발송하는 webhook 이벤트 타입 전체 목록.
427
+ */
428
+ declare const CHECKOUT_WEBHOOK_EVENTS: readonly ["checkout.session.created", "checkout.session.deposit_detected", "checkout.session.paid", "checkout.session.settled", "checkout.session.expired", "checkout.session.unresolved", "checkout.refund.recorded"];
429
+ type CheckoutWebhookEvent = (typeof CHECKOUT_WEBHOOK_EVENTS)[number];
430
+ /**
431
+ * Canonical envelope (HTTP body).
432
+ * Backend `CanonicalWebhookEnvelope`와 동일 shape.
433
+ */
434
+ interface WebhookPayload {
435
+ /** event_id (replay 시 원본 보존) */
436
+ id: string;
437
+ /** canonical event name */
438
+ event: CheckoutWebhookEvent;
439
+ /** envelope 생성 시각 (ISO) */
440
+ created_at: string;
441
+ /** 이벤트별 payload */
442
+ data: Record<string, unknown>;
443
+ }
444
+ /** Backend가 보내는 canonical 헤더 이름 */
445
+ declare const WEBHOOK_HEADERS: {
446
+ readonly signature: "X-Webhook-Signature-256";
447
+ readonly timestamp: "X-Webhook-Timestamp";
448
+ readonly event: "X-Webhook-Event";
449
+ readonly eventId: "X-Webhook-Id";
450
+ readonly deliveryId: "X-Webhook-Delivery-Id";
451
+ };
452
+ /**
453
+ * Webhook signature 검증.
454
+ *
455
+ * Contract:
456
+ * - Backend는 `X-Webhook-Signature-256: sha256=<hex>` 형식으로 보냄
457
+ * - 서명 대상은 raw HTTP body (envelope JSON string, byte 단위 동일)
458
+ * - HMAC-SHA256(body, secret) hex와 timing-safe compare
459
+ *
460
+ * 본 함수는 두 형식 모두 허용하지 않고 canonical(`sha256=<hex>`)만 처리한다.
461
+ * raw hex만 받는 형식은 폐기됨 (backend 단일 contract).
462
+ *
463
+ * @param payload - HTTP request raw body (string)
464
+ * @param signature - `X-Webhook-Signature-256` 헤더 값 (`sha256=<hex>`)
465
+ * @param secret - webhook 등록 시 1회 노출된 raw secret (`whsec_...`)
466
+ * @returns true if signature matches
467
+ *
468
+ * @example
469
+ * ```ts
470
+ * import express from 'express';
471
+ * import { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from '@bitpal/checkout';
472
+ *
473
+ * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
474
+ * const raw = req.body.toString('utf8'); // raw body — parse/re-serialize 하면 서명 깨짐
475
+ * const ok = await verifyWebhookSignature(
476
+ * raw,
477
+ * req.header(WEBHOOK_HEADERS.signature) ?? '',
478
+ * process.env.BITPAL_WEBHOOK_SECRET!,
479
+ * { timestamp: req.header(WEBHOOK_HEADERS.timestamp) ?? '' }, // replay 방어 — 필수
480
+ * );
481
+ * if (!ok) return res.status(400).send('invalid signature');
482
+ * const event = parseWebhookEvent(raw);
483
+ * // dedup 은 서명된 body 의 event.id 로 (헤더 X-Webhook-Id 는 미서명)
484
+ * if (await alreadyProcessed(event.id)) return res.status(200).send('dup');
485
+ * if (event.event === 'checkout.session.paid') { ... }
486
+ * res.status(200).send('ok');
487
+ * });
488
+ * ```
489
+ */
490
+ interface VerifyWebhookOptions {
491
+ /** `X-Webhook-Timestamp` 헤더 값(ISO UTC). 서명 대상에 포함되며 freshness 검사에 쓰인다. 필수. */
492
+ timestamp: string;
493
+ /** freshness 허용 오차(초). 기본 300(5분). |now - timestamp| 초과 시 replay 로 간주해 거부. */
494
+ toleranceSec?: number;
495
+ }
496
+ declare function verifyWebhookSignature(payload: string, signature: string, secret: string, opts?: VerifyWebhookOptions): Promise<boolean>;
497
+ /**
498
+ * Webhook envelope 파싱.
499
+ * 형식 검증 후 `WebhookPayload` 타입으로 캐스팅.
500
+ *
501
+ * @throws Error 형식이 envelope이 아닐 때
502
+ */
503
+ declare function parseWebhookEvent(body: string): WebhookPayload;
504
+
505
+ /**
506
+ * amount.ts — atomic μUSDC 변환 헬퍼
507
+ *
508
+ * BitPal API는 모든 amount를 atomic 정수 문자열로 받는다 (USDC/USDT 6 decimals).
509
+ * $1 → "1000000"
510
+ * $10 → "10000000"
511
+ * $0.5 → "500000"
512
+ *
513
+ * 정밀도 손실 방지를 위해 BigInt 연산만 사용한다 (Number 변환 금지 — CLAUDE.md 정책).
514
+ */
515
+ /**
516
+ * decimal 문자열 → atomic μUSDC 문자열
517
+ *
518
+ * @example
519
+ * toAtomicUSDC('29.00') // → '29000000'
520
+ * toAtomicUSDC('0.5') // → '500000'
521
+ * toAtomicUSDC('1000') // → '1000000000'
522
+ *
523
+ * @throws {Error} 음수, 6자리 초과 소수, 형식 오류
524
+ */
525
+ declare function toAtomicUSDC(decimal: string): string;
526
+ /**
527
+ * atomic μUSDC 문자열 → 사람이 읽기 좋은 decimal 문자열
528
+ *
529
+ * @example
530
+ * fromAtomicUSDC('29000000') // → '29.00'
531
+ * fromAtomicUSDC('500000') // → '0.50'
532
+ * fromAtomicUSDC('1500000') // → '1.50'
533
+ *
534
+ * @throws {Error} 정수 문자열이 아니면
535
+ */
536
+ declare function fromAtomicUSDC(atomic: string): string;
537
+
538
+ /**
539
+ * @bitpal/checkout — BitPal Checkout SDK
540
+ *
541
+ * 체크아웃 세션 생성, 결제 링크 관리, 수수료 미리보기를 위한 공식 SDK
542
+ *
543
+ * @example
544
+ * ```ts
545
+ * import { BitPal } from '@bitpal/checkout';
546
+ *
547
+ * const bitpal = new BitPal({ apiKey: 'bp_test_...' });
548
+ *
549
+ * const { data: session } = await bitpal.checkout.createSession({
550
+ * // amount 는 원자 단위 μUSDC 문자열(6 decimals). $29.00 → '29000000'. toAtomicUSDC 사용 권장.
551
+ * line_items: [{ name: 'Pro Plan', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
552
+ * });
553
+ *
554
+ * console.log(session.url); // 호스티드 결제 페이지 URL (checkout_url 별칭)
555
+ * ```
556
+ */
557
+
558
+ /**
559
+ * BitPal SDK 메인 엔트리
560
+ */
561
+ declare class BitPal {
562
+ readonly checkout: BitPalCheckoutClient;
563
+ constructor(config: BitPalConfig | string);
564
+ }
565
+
566
+ export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, CHECKOUT_WEBHOOK_EVENTS, type CheckoutLineItemInput, type CheckoutPrice, type CheckoutProduct, type CheckoutSession, type CheckoutWebhookEvent, type CreatePriceParams, type CreateProductParams, type CreateSessionParams, type DepositAddress, type FeePreview, type IdempotencyOptions, type IssueDepositAddressParams, type LineItem, type ListPricesParams, type ListProductsParams, type ListResponse, type PaymentOption, type PriceLineItem, PricesResource, ProductsResource, type PublicSession, type SessionStatus, type UpdatePriceParams, type UpdateProductParams, WEBHOOK_HEADERS, type WebhookPayload, fromAtomicUSDC, parseWebhookEvent, toAtomicUSDC, verifyWebhookSignature };