@bitpal/checkout 0.3.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.
package/dist/index.d.ts CHANGED
@@ -3,13 +3,17 @@
3
3
  */
4
4
  interface BitPalConfig {
5
5
  /**
6
- * API 키. prefix 가 mode 를 결정합니다:
6
+ * API 키(서버 전용, secret). prefix 가 mode 를 결정합니다:
7
7
  * - `bp_test_...` → TEST mode (testnet 또는 local anvil)
8
8
  * - `bp_live_...` → LIVE mode (mainnet)
9
9
  *
10
10
  * BitPal 콘솔 → Developers → API Keys 에서 발급.
11
+ *
12
+ * **선택**: createSession/listSessions/products 등 서버 메서드는 필수. 그러나 buyer-facing 공개 메서드
13
+ * (getPublicSession/getStatus/issueDepositAddress)는 인증 불요(session_token 기반)이므로 브라우저에서
14
+ * apiKey 없이 쓸 수 있다 — 절대 secret 키를 브라우저에 노출하지 말 것.
11
15
  */
12
- apiKey: string;
16
+ apiKey?: string;
13
17
  /**
14
18
  * BitPal API 기본 URL. 환경별로 다릅니다:
15
19
  * - production: `https://api.bitpal.io` (기본값) — 실제 testnet/mainnet 컨트랙트
@@ -32,12 +36,24 @@ interface LineItem {
32
36
  currency: string;
33
37
  quantity?: number;
34
38
  }
39
+ interface PriceLineItem {
40
+ /** price_... — bitpal.checkout.prices.create()로 만든 가격 */
41
+ price_id: string;
42
+ quantity?: number;
43
+ }
44
+ type CheckoutLineItemInput = LineItem | PriceLineItem;
35
45
  interface CreateSessionParams {
36
- line_items: LineItem[];
46
+ line_items: CheckoutLineItemInput[];
37
47
  /** 고객 지갑 주소 (선택) */
38
48
  payer_wallet?: string;
39
- /** 결제 체인 (예: eip155:8453) */
49
+ /** 결제 체인 (예: eip155:8453). 단일 고정 — allowed_pay_chains와 상호 배타. */
40
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[];
41
57
  /** 결제 자산 (예: USDC) */
42
58
  pay_asset?: string;
43
59
  /** 허용 오차 (%, 기본값: 2.00) */
@@ -67,12 +83,8 @@ interface CheckoutSession {
67
83
  settled_at: string | null;
68
84
  tx_hash: string | null;
69
85
  created_at: string;
70
- /** 'hosted' | 'external' — 결제 경로 구분 */
86
+ /** 'hosted' — 결제 경로. hosted deposit-address 단일 경로. */
71
87
  payment_source?: string;
72
- /** External on-chain 결제 검증 기록 목록 */
73
- payment_observations?: PaymentObservation[];
74
- /** External/gasless checkout payment config supplied by BitPal API. */
75
- external_payment_config?: ExternalPaymentConfig;
76
88
  /** 호스티드 결제 페이지 URL (token 쿼리 포함). backend 응답 그대로 사용 — buyer를 이 URL로 redirect. */
77
89
  checkout_url?: string;
78
90
  /** 1회용 session token. `checkout_url`에 이미 query로 박혀 있어 별도 사용 불필요. */
@@ -80,119 +92,89 @@ interface CheckoutSession {
80
92
  /** `checkout_url`의 별칭 (backwards compat). buyer redirect 시 이 필드 또는 `checkout_url` 사용. */
81
93
  url: string;
82
94
  }
83
- interface ExternalPaymentConfig {
84
- chain: string;
85
- chain_id: string;
86
- payment_id: string;
87
- escrow_contract_address: string | null;
88
- token_address: string | null;
89
- merchant_wallet: string;
90
- amount: string;
91
- fee_amount: string;
92
- reserve_amount: string;
93
- asset: string;
94
- decimals: number;
95
- supported_methods: Array<'authorization_relay'>;
96
- }
97
95
  /**
98
- * CheckoutEscrow.PaymentDeposited 이벤트 단위 검증 기록.
99
- * `GET /v1/checkout/sessions/:id` 응답의 `payment_observations` 배열 원소.
96
+ * 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
97
+ * `issueDepositAddress({ chain, token })` 로 입금주소를 발급받는다. 단일-자산 세션은 1개.
100
98
  */
101
- interface PaymentObservation {
102
- id: string;
103
- tx_hash: string;
104
- chain_id: string;
105
- from_address: string | null;
106
- to_address: string;
107
- /** Atomic μUSDC 정수 문자열 */
108
- amount: string;
109
- /** 'pending' | 'valid' | 'invalid' | 'duplicate' | 'underpaid' | 'overpaid' */
110
- validation_status: string;
111
- /** 'detected' | 'soft_confirmed' | 'final_confirmed' | 'reorg_revoked' */
112
- finality_status: string;
113
- validation_error: string | null;
114
- block_number: number | null;
115
- confirmations: number;
116
- created_at: string;
99
+ interface PaymentOption {
100
+ /** CAIP-2 (예: eip155:8453) */
101
+ caip2: string;
102
+ chain_id: number;
103
+ token_symbol: string;
104
+ token_address: string;
117
105
  }
118
106
  /**
119
- * `submitExternalPayment` 요청 파라미터.
120
- * 검증에 필요한 값은 tx_hash + chain만으로 충분 — payer_wallet, asset 등은 on-chain 이벤트에서 도출.
107
+ * `getPublicSession()` (`GET /v1/checkout/sessions/:id/public`) 응답 — 인증 불요(공개).
108
+ * buyer-facing 결제 페이지가 표시/폴링에 쓰는 서버 권위 뷰.
121
109
  */
122
- interface SubmitExternalPaymentParams {
123
- /** CheckoutEscrow 입금 tx hash (0x...) */
124
- tx_hash: string;
125
- /** 체인 식별자 (기본값: 'base'). 예: 'base', 'bnb', 'polygon' */
126
- chain?: string;
127
- /** Public session token — buyer 측 제출 시 사용. API key 인증 시 불필요. */
128
- session_token?: string;
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[];
129
139
  }
130
140
  /**
131
- * `submitExternalPayment` 응답.
141
+ * `issueDepositAddress()` 요청 파라미터.
142
+ * chain/token 은 멀티옵션 세션에서만 필요(단일-자산 세션은 생략). refundAddress 는 필수 —
143
+ * 금액부족/잘못된토큰/만료 시 이 주소로 환불되며, 입금주소에 커밋되어 발급 후 변경 불가.
132
144
  */
133
- interface SubmitExternalPaymentResult {
134
- observation_id: string;
135
- /** 'valid' | 'invalid' | 'underpaid' | 'overpaid' | 'duplicate' */
136
- validation_status: string;
137
- /** 검증 후 세션 상태 */
138
- session_status: string;
139
- /** 입금한 지갑 주소 (on-chain 이벤트에서 도출) */
140
- from_address: string | null;
141
- /** Atomic μUSDC 정수 문자열 */
142
- amount: string;
143
- message: string;
144
- }
145
- interface PayWithAuthorizationParams {
146
- chain?: string;
145
+ interface IssueDepositAddressParams {
146
+ /** 결제 페이지 접근 증명(HMAC). createSession 응답의 session_token. */
147
147
  session_token: string;
148
- /** EIP-3009 TransferWithAuthorization sig (USDC). */
149
- authorization: {
150
- from: string;
151
- validAfter?: string;
152
- validBefore: string;
153
- authNonce: string;
154
- v: number;
155
- r: string;
156
- s: string;
157
- };
158
- /**
159
- * Q5 C-01 옵션 2 (SDK v0.2.0): PaymentAuthorization typed-data sig.
160
- * Buyer 가 결제 의도 전체 (paymentId, merchant, amount, fee, reserve, duration) 에 서명.
161
- * gateway 키 탈취 시에도 buyer 의도 밖 결제 생성 차단.
162
- *
163
- * 서명 생성: `buildPaymentAuthTypedData()` helper 또는 viem `signTypedData` 직접 호출.
164
- * Required for SDK v0.2.x+ — gasless lane 모든 호출에 필수. v0.1.x 는 미지원.
165
- */
166
- payment_auth: {
167
- deadline: string;
168
- duration: string;
169
- v: number;
170
- r: string;
171
- s: string;
172
- };
148
+ /** 환불 주소(0x EVM). 필수. */
149
+ refundAddress: string;
150
+ /** 선택 자산의 chain(CAIP-2 또는 short-name). 멀티옵션 세션 필수. */
151
+ chain?: string;
152
+ /** 선택 자산의 token symbol(예: USDC). 멀티옵션 세션 필수. */
153
+ token?: string;
173
154
  }
174
155
  /**
175
- * Q5 C-01 옵션 2: buyer 가 서명할 EIP-712 PaymentAuthorization typed-data 의 입력 인자.
176
- * `buildPaymentAuthTypedData()` 가 이를 받아 viem 호환 객체를 반환.
156
+ * `issueDepositAddress()` 응답 — 이 주소로 정확 금액을 송금하면 watcher 가 감지, keeper 가 정산한다.
177
157
  */
178
- interface PaymentAuthTypedDataArgs {
179
- escrowAddress: string;
180
- chainId: number;
181
- paymentId: string;
182
- payer: string;
183
- merchant: string;
184
- token: string;
185
- amount: string;
158
+ interface DepositAddress {
159
+ sessionId: string;
160
+ chain: string;
161
+ /** 주문별 CREATE2 forwarder 입금주소. */
162
+ depositAddress: string;
163
+ expectedToken: string;
164
+ /** Atomic 정수 문자열. */
165
+ expectedAmount: string;
186
166
  feeAmount: string;
187
- reserveAmount: string;
188
- duration: string;
167
+ refundAddress: string;
168
+ /** Unix seconds — on-chain deadline. */
189
169
  deadline: string;
190
- nonce: string;
170
+ expiresAt: string | null;
191
171
  }
192
- interface CheckoutPaymentResult {
172
+ /**
173
+ * `getStatus()` (`GET /v1/checkout/sessions/:id/status`) 응답 — 경량 폴링용(인증 불요).
174
+ */
175
+ interface SessionStatus {
193
176
  status: string;
194
- tx_hash: string;
195
- message?: string;
177
+ paid_at: string | null;
196
178
  }
197
179
  /**
198
180
  * 수수료 미리보기 응답.
@@ -206,65 +188,16 @@ interface FeePreview {
206
188
  net_amount: string;
207
189
  }
208
190
  /**
209
- * Auto refund 파라미터 — escrow 보관 중(`escrow_status` pending|disputed)일 때 동작.
210
- * 컨트랙트가 buyer에게 직접 토큰 transfer.
211
- *
212
- * `amount` 미지정 시 backend가 잔액 전액(full refund)을 시도. 명시 시 atomic μUSDC.
213
- */
214
- interface RefundSessionParams {
215
- /** Atomic μUSDC 정수 문자열. 미지정 시 전액. */
216
- amount?: string;
217
- /** 환불 사유 코드 (선택, 머천트 대시보드/audit log 분류용) */
218
- reason?: string;
219
- /** 환불 사유 상세 (선택, 자유 텍스트) */
220
- reason_detail?: string;
221
- }
222
- interface RefundSessionResult {
223
- refunded: boolean;
224
- refundId: string;
225
- /** Atomic μUSDC 정수 문자열 */
226
- amount: string;
227
- type: 'full' | 'partial';
228
- /** on-chain transaction hash. simulation/escrow 미배포 환경에서는 sim_xxx */
229
- txHash: string | null;
230
- }
231
- /**
232
- * Record refund 파라미터 — `status='settled'` 후 머천트가 외부 지갑에서 직접 송금하고
233
- * tx_hash를 입력해 환불을 DB-only로 기록한다. 컨트랙트 호출 없음.
191
+ * 환불은 온체인 자동 경로 — SDK 에 수동 환불 API 는 없다.
192
+ * 금액부족/잘못된토큰/만료 시 issueDepositAddress 에 지정한 refundAddress 로 자동 환불되고,
193
+ * 세션 status(expired/unresolved) + webhook 으로 통지된다.
234
194
  */
235
- interface RecordRefundParams {
236
- /** Atomic μUSDC 정수 문자열 (필수) */
237
- refund_amount: string;
238
- /** 외부 송금 transaction hash (필수, 형식 검증 안 함) */
239
- tx_hash: string;
240
- /** 환불 사유 (선택) */
241
- reason?: string;
242
- }
243
195
  /**
244
196
  * 멱등성 키 — 동일 키로 재시도 시 backend가 같은 결과 반환.
245
- * Phase 1 SDK는 사용자가 명시적으로 전달 (UUID 권장).
246
197
  */
247
198
  interface IdempotencyOptions {
248
199
  idempotencyKey?: string;
249
200
  }
250
- /**
251
- * `GET /v1/checkout/sessions/:id/refunds` 응답 row.
252
- * 필드는 backend `listSessionRefunds` 반환과 동일 — Phase 1에서는 통합 타입 미고정 (스냅샷 시점에 따라 컬럼 변동).
253
- */
254
- interface RefundEntry {
255
- id: string;
256
- session_id: string;
257
- /** 'auto' | 'record' (escrow refund vs DB-only manual) */
258
- refund_mode?: string;
259
- amount: string;
260
- /** 'pending' | 'completed' | 'failed' 등 */
261
- status?: string;
262
- tx_hash: string | null;
263
- reason?: string | null;
264
- reason_detail?: string | null;
265
- created_at: string;
266
- [key: string]: unknown;
267
- }
268
201
  interface ApiResponse<T> {
269
202
  data: T;
270
203
  error?: string;
@@ -273,6 +206,125 @@ interface ListResponse<T> {
273
206
  data: T[];
274
207
  total?: number;
275
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
+ }
276
328
 
277
329
  /**
278
330
  * @bitpal/checkout SDK — 클라이언트
@@ -282,9 +334,14 @@ interface ListResponse<T> {
282
334
  */
283
335
 
284
336
  declare class BitPalCheckoutClient {
337
+ /** 서버 메서드용 secret 키. 공개(buyer-facing) 메서드는 없이도 동작. */
285
338
  private readonly apiKey;
286
339
  private readonly baseUrl;
287
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;
288
345
  constructor(config: BitPalConfig);
289
346
  private request;
290
347
  /**
@@ -300,6 +357,13 @@ declare class BitPalCheckoutClient {
300
357
  * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
301
358
  * });
302
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 사용(둘은 상호 배타).
303
367
  */
304
368
  createSession(params: CreateSessionParams): Promise<ApiResponse<CheckoutSession>>;
305
369
  /** 세션 조회 */
@@ -311,109 +375,40 @@ declare class BitPalCheckoutClient {
311
375
  offset?: number;
312
376
  }): Promise<ListResponse<CheckoutSession>>;
313
377
  /**
314
- * Auto refund — escrow 보관 중일 때 컨트랙트가 buyer에게 직접 transfer.
315
- *
316
- * `params.amount` 미지정 시 backend가 잔액 전액(`type: 'full'`). 명시 시 `type: 'partial'`.
317
- * **Idempotency-Key 권장** — 같은 키로 재시도 시 backend가 같은 결과 반환.
318
- *
319
- * 머천트가 `frozen` 상태면 환불은 허용되지만, `suspended` 상태에서는 차단된다.
320
- *
321
- * @example
322
- * ```ts
323
- * import { randomUUID } from 'node:crypto';
324
- * const { data } = await bitpal.checkout.refundSession(
325
- * sessionId,
326
- * { amount: toAtomicUSDC('5.00'), reason: 'customer_requested' },
327
- * { idempotencyKey: randomUUID() },
328
- * );
329
- * console.log(data.txHash); // on-chain tx hash (또는 simulation 환경에서 sim_xxx)
330
- * ```
331
- */
332
- refundSession(sessionId: string, params?: RefundSessionParams, opts?: IdempotencyOptions): Promise<ApiResponse<RefundSessionResult>>;
333
- /**
334
- * Record refund — `status='settled'` 후 머천트가 외부 지갑에서 직접 송금하고 tx_hash를 입력.
335
- * 컨트랙트 호출 없음 (DB-only).
336
- *
337
- * **Idempotency-Key 권장**.
378
+ * 공개 세션 조회 (`GET /v1/checkout/sessions/:id/public`) — 인증 불요.
338
379
  *
339
- * @example
340
- * ```ts
341
- * await bitpal.checkout.recordRefund(
342
- * sessionId,
343
- * {
344
- * refund_amount: toAtomicUSDC('5.00'),
345
- * tx_hash: '0xabc...',
346
- * reason: 'manual_offchain_refund',
347
- * },
348
- * { idempotencyKey: randomUUID() },
349
- * );
350
- * ```
380
+ * buyer-facing 결제 페이지가 표시/자산선택에 쓰는 서버 권위 뷰. `payment_options` 로
381
+ * 구매자 선택 가능한 chain×token 을 얻고, 발급 후에는 `deposit_address` 가 채워진다.
351
382
  */
352
- recordRefund(sessionId: string, params: RecordRefundParams, opts?: IdempotencyOptions): Promise<ApiResponse<RefundEntry>>;
353
- /** 세션별 환불 내역 (auto + record 통합) */
354
- listRefunds(sessionId: string): Promise<ListResponse<RefundEntry>>;
383
+ getPublicSession(sessionId: string): Promise<ApiResponse<PublicSession>>;
355
384
  /**
356
- * CheckoutEscrow에 직접 입금한 외부 결제를 BitPal에 제출·검증.
385
+ * 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
357
386
  *
358
- * Buyer가 merchant UI/SDK에서 `CheckoutEscrow.deposit*()` 함수를 직접 호출한 뒤
359
- * 결과 tx_hash를 이 메서드로 제출한다. BitPal은 receipt를 fetch하여
360
- * `PaymentDeposited` 이벤트를 검증하고 세션 상태를 갱신한다.
387
+ * 비수탁 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
388
+ * 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
389
+ * 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
361
390
  *
362
- * **인증 방식:**
363
- * - Merchant server (API key): `Authorization: Bearer bp_test_*` 헤더 자동 첨부
364
- * - Buyer 측 (session_token): `params.session_token` 전달
391
+ * refundAddress 는 입금주소에 커밋되어 발급 후 변경 불가. session_token 은 createSession 응답값.
365
392
  *
366
393
  * @example
367
394
  * ```ts
368
- * // Merchant server — API key 인증
369
- * const { data } = await bitpal.checkout.submitExternalPayment(sessionId, {
370
- * tx_hash: '0xabc...',
371
- * chain: 'base',
395
+ * const { data } = await bitpal.checkout.issueDepositAddress(sessionId, {
396
+ * session_token: session.session_token!,
397
+ * refundAddress: '0xBuyerWallet...',
398
+ * chain: 'eip155:8453', // 멀티옵션 세션만 필요
399
+ * token: 'USDC',
372
400
  * });
373
- * console.log(data.validation_status); // 'valid' | 'underpaid' | 'overpaid' | ...
374
- * console.log(data.session_status); // 'paid' | 'unresolved_underpaid' | ...
401
+ * console.log(data.depositAddress, data.expectedAmount); // 이 주소로 정확 금액 송금
375
402
  * ```
376
403
  */
377
- submitExternalPayment(sessionId: string, params: SubmitExternalPaymentParams, opts?: IdempotencyOptions): Promise<ApiResponse<SubmitExternalPaymentResult>>;
404
+ issueDepositAddress(sessionId: string, params: IssueDepositAddressParams): Promise<ApiResponse<DepositAddress>>;
378
405
  /**
379
- * Gasless checkout payment through the BitPal API relayer.
380
- *
381
- * The buyer signs **two** EIP-712 typed-data structures and the API calls
382
- * `CheckoutEscrow.depositWithAuthorization()` with the platform relayer:
383
- *
384
- * 1. **EIP-3009 USDC TransferWithAuthorization** (`params.authorization`) —
385
- * authorizes USDC transfer from buyer to escrow.
386
- * 2. **PaymentAuthorization** (`params.payment_auth`, since SDK v0.2.0) —
387
- * binds the entire payment intent (paymentId, merchant, amount, fee,
388
- * reserve, duration) to the buyer's signature. Without this, a compromised
389
- * gateway key could redirect funds to an attacker-controlled merchant.
390
- *
391
- * Build the PaymentAuthorization typed-data with `buildPaymentAuthTypedData()`.
392
- * The PaymentAuthorization nonce comes from `CheckoutEscrow.paymentAuthNonce(payer)`.
393
- *
394
- * Use the returned session/order state only as immediate UX feedback; the
395
- * merchant backend should finalize fulfillment from signed webhooks.
406
+ * 경량 상태 폴링 (`GET /v1/checkout/sessions/:id/status`) — 인증 불요.
396
407
  *
397
- * @breaking-change v0.2.0 — `payment_auth` field is now required. v0.1.x clients
398
- * must upgrade their signing flow. See MIGRATION_v0.2.md.
408
+ * status: awaiting_deposit → deposit_detected → paid_confirmed/swept. 확정/정산은 webhook 으로도
409
+ * 통지되므로, 서버 사이드 확정은 webhook 을 신뢰(폴링은 buyer UX 용).
399
410
  */
400
- payWithAuthorization(sessionId: string, params: PayWithAuthorizationParams, opts?: IdempotencyOptions): Promise<ApiResponse<CheckoutPaymentResult>>;
401
- /**
402
- * 세션의 external on-chain 결제 검증 기록 목록 조회.
403
- *
404
- * `getSession()`의 `payment_observations` 필드와 동일한 데이터를 반환하지만
405
- * 검증 기록만 명시적으로 조회할 때 사용한다.
406
- *
407
- * @example
408
- * ```ts
409
- * const { data } = await bitpal.checkout.getExternalPaymentStatus(sessionId);
410
- * const latest = data[0];
411
- * if (latest?.finality_status === 'final_confirmed') {
412
- * // 12 confirmations 완료 — 결제 확정
413
- * }
414
- * ```
415
- */
416
- getExternalPaymentStatus(sessionId: string): Promise<ApiResponse<PaymentObservation[]>>;
411
+ getStatus(sessionId: string): Promise<ApiResponse<SessionStatus>>;
417
412
  /** 수수료 미리보기 — `/v1/checkout/fee-preview` (Phase 1 credit system 폐기 후 canonical path) */
418
413
  previewFee(amount: string): Promise<ApiResponse<FeePreview>>;
419
414
  }
@@ -425,18 +420,12 @@ declare class BitPalError extends Error {
425
420
  /**
426
421
  * @bitpal/checkout — Webhook 서명 검증 + 이벤트 타입
427
422
  *
428
- * BitPal backend의 canonical webhook contract와 1:1 정합:
429
- * apps/api/src/services/webhook-canonical-delivery.ts
430
- * apps/api/src/__tests__/webhook-canonical-delivery.test.ts
431
- *
432
- * 이벤트 명/서명 형식이 backend와 drift하면 머천트 검증이 깨진다.
423
+ * BitPal 이 보내는 canonical webhook contract(envelope/서명/헤더)와 1:1 정합한다.
433
424
  */
434
425
  /**
435
- * 발송되는 webhook 이벤트 타입.
436
- * Backend `services/checkout-webhook.ts:CHECKOUT_WEBHOOK_EVENTS`와 동일 — drift 시
437
- * 머천트 코드의 분기 매칭이 실패한다.
426
+ * BitPal 이 발송하는 webhook 이벤트 타입 전체 목록.
438
427
  */
439
- declare const CHECKOUT_WEBHOOK_EVENTS: readonly ["checkout.session.created", "checkout.session.paid", "checkout.session.settled", "checkout.session.expired", "checkout.session.unresolved", "checkout.session.release_deferred", "checkout.payment.failed", "checkout.payment.finalized", "checkout.payment.revoked", "checkout.refund.recorded", "checkout.refund.completed", "checkout.refund.partial"];
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"];
440
429
  type CheckoutWebhookEvent = (typeof CHECKOUT_WEBHOOK_EVENTS)[number];
441
430
  /**
442
431
  * Canonical envelope (HTTP body).
@@ -482,16 +471,29 @@ declare const WEBHOOK_HEADERS: {
482
471
  * import { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from '@bitpal/checkout';
483
472
  *
484
473
  * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
485
- * const sig = req.header(WEBHOOK_HEADERS.signature) ?? '';
486
- * const ok = await verifyWebhookSignature(req.body.toString(), sig, process.env.BITPAL_WEBHOOK_SECRET!);
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
+ * );
487
481
  * if (!ok) return res.status(400).send('invalid signature');
488
- * const event = parseWebhookEvent(req.body.toString());
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');
489
485
  * if (event.event === 'checkout.session.paid') { ... }
490
486
  * res.status(200).send('ok');
491
487
  * });
492
488
  * ```
493
489
  */
494
- declare function verifyWebhookSignature(payload: string, signature: string, secret: string): Promise<boolean>;
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>;
495
497
  /**
496
498
  * Webhook envelope 파싱.
497
499
  * 형식 검증 후 `WebhookPayload` 타입으로 캐스팅.
@@ -533,154 +535,6 @@ declare function toAtomicUSDC(decimal: string): string;
533
535
  */
534
536
  declare function fromAtomicUSDC(atomic: string): string;
535
537
 
536
- /**
537
- * typed-data.ts — Q5 C-01 옵션 2 (SDK v0.2.0).
538
- *
539
- * BitPal Checkout 의 EIP-712 typed-data helper. SDK 가 모든 typed-data 작업을 직접
540
- * 수행하지 않고 (서명은 머천트의 wallet provider 가) **typed-data 객체만 생성** 한다.
541
- *
542
- * 사용 예 (viem):
543
- *
544
- * ```ts
545
- * import { buildPaymentAuthTypedData } from '@bitpal/checkout';
546
- * import { signTypedData } from 'viem/accounts';
547
- *
548
- * const td = buildPaymentAuthTypedData({
549
- * escrowAddress: '0x...',
550
- * chainId: 8453,
551
- * paymentId: '0x...', // = toBytes32(sessionId)
552
- * payer: buyer.address,
553
- * merchant: session.seller_wallet,
554
- * token: session.token_address,
555
- * amount: session.amount_total,
556
- * feeAmount: '0',
557
- * reserveAmount: '0',
558
- * duration: '0',
559
- * deadline: String(Math.floor(Date.now()/1000) + 3600),
560
- * nonce: String(await escrow.read.paymentAuthNonce(buyer.address)),
561
- * });
562
- * const sig = await walletClient.signTypedData(td);
563
- * ```
564
- *
565
- * 사용 예 (wagmi `useSignTypedData`):
566
- *
567
- * ```tsx
568
- * const { signTypedDataAsync } = useSignTypedData();
569
- * const td = buildPaymentAuthTypedData({ ... });
570
- * const sig = await signTypedDataAsync(td);
571
- * ```
572
- */
573
-
574
- /**
575
- * Build EIP-712 typed-data object for PaymentAuthorization signing.
576
- * Returns a viem-compatible structure (also compatible with wagmi/ethers v6 signTypedData).
577
- */
578
- declare function buildPaymentAuthTypedData(args: PaymentAuthTypedDataArgs): {
579
- readonly domain: {
580
- readonly name: "CheckoutEscrow";
581
- readonly version: "1";
582
- readonly chainId: number;
583
- readonly verifyingContract: `0x${string}`;
584
- };
585
- readonly types: {
586
- readonly PaymentAuthorization: readonly [{
587
- readonly name: "paymentId";
588
- readonly type: "bytes32";
589
- }, {
590
- readonly name: "payer";
591
- readonly type: "address";
592
- }, {
593
- readonly name: "merchant";
594
- readonly type: "address";
595
- }, {
596
- readonly name: "token";
597
- readonly type: "address";
598
- }, {
599
- readonly name: "amount";
600
- readonly type: "uint256";
601
- }, {
602
- readonly name: "feeAmount";
603
- readonly type: "uint256";
604
- }, {
605
- readonly name: "reserveAmount";
606
- readonly type: "uint256";
607
- }, {
608
- readonly name: "duration";
609
- readonly type: "uint256";
610
- }, {
611
- readonly name: "deadline";
612
- readonly type: "uint256";
613
- }, {
614
- readonly name: "nonce";
615
- readonly type: "uint256";
616
- }];
617
- };
618
- readonly primaryType: "PaymentAuthorization";
619
- readonly message: {
620
- readonly paymentId: `0x${string}`;
621
- readonly payer: `0x${string}`;
622
- readonly merchant: `0x${string}`;
623
- readonly token: `0x${string}`;
624
- readonly amount: bigint;
625
- readonly feeAmount: bigint;
626
- readonly reserveAmount: bigint;
627
- readonly duration: bigint;
628
- readonly deadline: bigint;
629
- readonly nonce: bigint;
630
- };
631
- };
632
- /**
633
- * Args for the EIP-712 ConfirmReceipt typed-data.
634
- * Used when buyer signs an off-chain receipt confirmation that BitPal gateway relays
635
- * on-chain via `confirmReceiptWithSignature` (Q4 H-01).
636
- */
637
- interface ConfirmReceiptTypedDataArgs {
638
- escrowAddress: string;
639
- chainId: number;
640
- paymentId: string;
641
- payer: string;
642
- deadline: string;
643
- nonce: string;
644
- }
645
- /**
646
- * Build EIP-712 typed-data object for ConfirmReceipt signing (Q4 H-01).
647
- */
648
- declare function buildConfirmReceiptTypedData(args: ConfirmReceiptTypedDataArgs): {
649
- readonly domain: {
650
- readonly name: "CheckoutEscrow";
651
- readonly version: "1";
652
- readonly chainId: number;
653
- readonly verifyingContract: `0x${string}`;
654
- };
655
- readonly types: {
656
- readonly ConfirmReceipt: readonly [{
657
- readonly name: "paymentId";
658
- readonly type: "bytes32";
659
- }, {
660
- readonly name: "payer";
661
- readonly type: "address";
662
- }, {
663
- readonly name: "deadline";
664
- readonly type: "uint256";
665
- }, {
666
- readonly name: "nonce";
667
- readonly type: "uint256";
668
- }];
669
- };
670
- readonly primaryType: "ConfirmReceipt";
671
- readonly message: {
672
- readonly paymentId: `0x${string}`;
673
- readonly payer: `0x${string}`;
674
- readonly deadline: bigint;
675
- readonly nonce: bigint;
676
- };
677
- };
678
- /**
679
- * Convert a session ID string into a 32-byte hex (`paymentId`) suitable for typed-data signing.
680
- * Mirrors the backend `toBytes32(sessionId)` helper.
681
- */
682
- declare function sessionIdToPaymentId(sessionId: string): `0x${string}`;
683
-
684
538
  /**
685
539
  * @bitpal/checkout — BitPal Checkout SDK
686
540
  *
@@ -693,10 +547,11 @@ declare function sessionIdToPaymentId(sessionId: string): `0x${string}`;
693
547
  * const bitpal = new BitPal({ apiKey: 'bp_test_...' });
694
548
  *
695
549
  * const { data: session } = await bitpal.checkout.createSession({
696
- * line_items: [{ name: 'Pro Plan', amount: '29.00', currency: 'USDC' }],
550
+ * // amount 는 원자 단위 μUSDC 문자열(6 decimals). $29.00 → '29000000'. toAtomicUSDC 사용 권장.
551
+ * line_items: [{ name: 'Pro Plan', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
697
552
  * });
698
553
  *
699
- * console.log(session.url); // 호스티드 결제 페이지 URL
554
+ * console.log(session.url); // 호스티드 결제 페이지 URL (checkout_url 별칭)
700
555
  * ```
701
556
  */
702
557
 
@@ -708,4 +563,4 @@ declare class BitPal {
708
563
  constructor(config: BitPalConfig | string);
709
564
  }
710
565
 
711
- export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, CHECKOUT_WEBHOOK_EVENTS, type CheckoutPaymentResult, type CheckoutSession, type CheckoutWebhookEvent, type ConfirmReceiptTypedDataArgs, type CreateSessionParams, type ExternalPaymentConfig, type FeePreview, type IdempotencyOptions, type LineItem, type ListResponse, type PayWithAuthorizationParams, type PaymentAuthTypedDataArgs, type PaymentObservation, type RecordRefundParams, type RefundEntry, type RefundSessionParams, type RefundSessionResult, type SubmitExternalPaymentParams, type SubmitExternalPaymentResult, WEBHOOK_HEADERS, type WebhookPayload, buildConfirmReceiptTypedData, buildPaymentAuthTypedData, fromAtomicUSDC, parseWebhookEvent, sessionIdToPaymentId, toAtomicUSDC, verifyWebhookSignature };
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 };