@bitpal/checkout 0.5.0 → 0.7.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/README.md CHANGED
@@ -11,25 +11,27 @@ pnpm add @bitpal/checkout
11
11
  # npm i @bitpal/checkout / yarn add @bitpal/checkout
12
12
  ```
13
13
 
14
- ## ⚠️ Amount format (read this first)
14
+ ## Amounts — just pass a human number
15
15
 
16
- USDC / USDT have **6 decimals**. All amounts are **atomic μUSDC integer strings** — never floats.
16
+ Use **`amount`** in line items — a plain USD string like `"20"`, `"0.5"`, `"29.00"`. No decimals to compute.
17
17
 
18
- ```
19
- $1.00 → "1000000"
20
- $29.00 → "29000000"
21
- $0.50 → "500000"
18
+ ```ts
19
+ line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }]
22
20
  ```
23
21
 
24
- Passing a decimal string like `"29.00"` returns **400**. Use the helpers:
22
+ **The amount is a USD price — it does NOT depend on the chain or token decimals.** Whether the buyer pays with USDC (6dp) on Base or USDT (18dp) on BNB, you pass the same `amount`. BitPal rescales to the buyer's chosen token at deposit time. So you never compute per-token decimals. (Prices via `prices.create` take the same `amount`.)
25
23
 
26
- ```ts
27
- import { toAtomicUSDC, fromAtomicUSDC } from '@bitpal/checkout';
24
+ <details><summary>Advanced: raw atomic amounts</summary>
28
25
 
29
- toAtomicUSDC('29.00'); // → "29000000"
30
- fromAtomicUSDC('29000000'); // → "29.00"
26
+ If you already hold **atomic μUSD integer strings** ($1 = `"1000000"`), pass `amount_atomic` instead (mutually exclusive with `amount`):
27
+
28
+ ```
29
+ $1.00 → "1000000" $29.00 → "29000000" $0.50 → "500000"
31
30
  ```
32
31
 
32
+ Helpers for atomic conversion (e.g. reading `amount_total` back): `toAtomic(v, decimals)` / `fromAtomic(v, decimals)` — read `decimals` from `payment_options[]`/`token_decimals` (6 for USDC/USDT, 18 for BNB USDT). `toAtomicUSDC`/`fromAtomicUSDC` are 6dp shortcuts.
33
+ </details>
34
+
33
35
  ## Environments
34
36
 
35
37
  API keys are environment-scoped:
@@ -45,8 +47,8 @@ there is no separate flag.
45
47
  ### Setup
46
48
 
47
49
  ```ts
48
- // helpers (toAtomicUSDC / fromAtomicUSDC) are used in the snippets below
49
- import { BitPal, toAtomicUSDC, fromAtomicUSDC } from '@bitpal/checkout';
50
+ import { BitPal } from '@bitpal/checkout';
51
+ // (optional atomic helpers if you need them: import { toAtomic, fromAtomic } from '@bitpal/checkout')
50
52
 
51
53
  const bitpal = new BitPal(process.env.BITPAL_API_KEY!); // bp_test_... / bp_live_...
52
54
  ```
@@ -61,10 +63,16 @@ shows the per-order deposit address + QR, and confirms the payment on-chain.
61
63
  ```ts
62
64
  const { data: session } = await bitpal.checkout.createSession({
63
65
  line_items: [
64
- { name: 'Pro Plan', amount: toAtomicUSDC('29.00'), currency: 'USDC' },
66
+ { name: 'Pro Plan', amount: '29', currency: 'USDC' }, // `currency` = price denomination (USD via USDC)
67
+ ],
68
+ // Payment options the buyer may choose — (chain, token) pairs. Mix tokens freely.
69
+ // `chain` accepts a short-name ('base' | 'arbitrum' | 'bnb' | 'tron' | 'solana') or a CAIP-2 id;
70
+ // it resolves to the right testnet/mainnet chain for your mode (test vs live key) automatically.
71
+ allowed_assets: [
72
+ { chain: 'base', token: 'USDC' },
73
+ { chain: 'tron', token: 'USDT' },
74
+ { chain: 'solana', token: 'USDC' },
65
75
  ],
66
- pay_chain: 'eip155:84532', // Base Sepolia (test mode). Live mode: 'eip155:8453' (Base mainnet).
67
- // Or use allowed_pay_chains for a buyer-picked chain.
68
76
  expires_in_seconds: 1800,
69
77
  success_url: 'https://yourstore.com/thanks',
70
78
  });
@@ -73,7 +81,13 @@ const { data: session } = await bitpal.checkout.createSession({
73
81
  redirect(session.checkout_url!);
74
82
  ```
75
83
 
76
- `pay_chain` (single fixed chain) and `allowed_pay_chains` (buyer picks) are mutually exclusive.
84
+ **Setting the payment options** (precedence, highest first):
85
+
86
+ - **`allowed_assets`** — `(chain, token)` cells. The standard, most explicit way; the buyer picks any listed pair (lets you mix tokens, e.g. USDC on Base + USDT on Tron).
87
+ - `allowed_pay_chains: string[]` — shorthand for "the line-item `currency` across these chains" (one token, many chains).
88
+ - `pay_chain: string` — a single fixed chain, no buyer choice.
89
+
90
+ `pay_chain` and `allowed_pay_chains` are mutually exclusive; `allowed_assets` takes precedence over both. Omit all three and it defaults to **one chain × the line-item currency**. `currency` on the line item is always the **price denomination** (what the amount is quoted in) — independent of which token the buyer pays with.
77
91
 
78
92
  ### Self-hosted flow: issue a deposit address yourself
79
93
 
@@ -85,9 +99,9 @@ you.
85
99
  ```ts
86
100
  const { data } = await bitpal.checkout.issueDepositAddress(session.id, {
87
101
  session_token: session.session_token!, // from createSession
88
- refundAddress: '0xBuyerWallet...', // committed to the address, immutable after issuance
102
+ refundAddress: '0xBuyerWallet...', // VM-matched (EVM 0x… / Tron T… / Solana base58); committed, immutable after issuance
89
103
  chain: 'eip155:84532', // match the session's chain (test: Base Sepolia / live: eip155:8453).
90
- // only needed for multi-option (allowed_pay_chains) sessions
104
+ // only needed for multi-option (allowed_assets / allowed_pay_chains) sessions
91
105
  token: 'USDC',
92
106
  });
93
107
 
@@ -195,10 +209,9 @@ new BitPal({ apiKey, baseUrl: 'https://…' }); // config object — override ba
195
209
 
196
210
  ## Runnable examples
197
211
 
198
- The [source repo](https://github.com/superlabs-x/bitpal-checkout-sdk)'s `examples/` directory
199
- has runnable references (not shipped in the npm package): `create-session.mjs`,
200
- `webhook-receiver.mjs`, and a `full-integration/` Express store wiring session creation +
201
- webhook verification + idempotency.
212
+ The source repo's `examples/` directory has runnable references (not shipped in the npm
213
+ package): `create-session.mjs`, `webhook-receiver.mjs`, and a `full-integration/` Express
214
+ store wiring session creation + webhook verification + idempotency.
202
215
 
203
216
  ## Build
204
217
 
@@ -213,7 +226,7 @@ pnpm build # tsup → dist/ (ESM + .d.ts)
213
226
  `verifyWebhookSignature(rawBody, sigHeader, secret, { timestamp: tsHeader })`. A 3-argument call now
214
227
  returns `false`. The signed content changed from `body` to `` `${timestamp}.${body}` ``.
215
228
  - Deduplicate on the signed `event.id`, not the `X-Webhook-Id` header.
216
- - Removed legacy pre-release methods (`payWithAuthorization`, `submitExternalPayment`) and escrow refund helpers —
229
+ - Removed 안1 methods (`payWithAuthorization`, `submitExternalPayment`) and escrow refund helpers —
217
230
  checkout is non-custodial deposit-address (see Usage above).
218
231
 
219
232
  ## License
package/dist/index.cjs CHANGED
@@ -34,11 +34,14 @@ __export(index_exports, {
34
34
  BitPalCheckoutClient: () => BitPalCheckoutClient,
35
35
  BitPalError: () => BitPalError,
36
36
  CHECKOUT_WEBHOOK_EVENTS: () => CHECKOUT_WEBHOOK_EVENTS,
37
+ PaymentLinksResource: () => PaymentLinksResource,
37
38
  PricesResource: () => PricesResource,
38
39
  ProductsResource: () => ProductsResource,
39
40
  WEBHOOK_HEADERS: () => WEBHOOK_HEADERS,
41
+ fromAtomic: () => fromAtomic,
40
42
  fromAtomicUSDC: () => fromAtomicUSDC,
41
43
  parseWebhookEvent: () => parseWebhookEvent,
44
+ toAtomic: () => toAtomic,
42
45
  toAtomicUSDC: () => toAtomicUSDC,
43
46
  verifyWebhookSignature: () => verifyWebhookSignature
44
47
  });
@@ -101,8 +104,39 @@ var PricesResource = class {
101
104
  return this.request("PATCH", `/v1/checkout/prices/${encodeURIComponent(priceId)}`, params);
102
105
  }
103
106
  };
107
+ var PaymentLinksResource = class {
108
+ constructor(request) {
109
+ this.request = request;
110
+ }
111
+ request;
112
+ /** POST /v1/checkout/payment-links */
113
+ create(params) {
114
+ return this.request("POST", "/v1/checkout/payment-links", params);
115
+ }
116
+ /** GET /v1/checkout/payment-links */
117
+ list(params) {
118
+ return this.request("GET", `/v1/checkout/payment-links${toQuery(params)}`);
119
+ }
120
+ /** GET /v1/checkout/payment-links/:id */
121
+ retrieve(linkId) {
122
+ return this.request("GET", `/v1/checkout/payment-links/${encodeURIComponent(linkId)}`);
123
+ }
124
+ /** PATCH /v1/checkout/payment-links/:id */
125
+ update(linkId, params) {
126
+ return this.request("PATCH", `/v1/checkout/payment-links/${encodeURIComponent(linkId)}`, params);
127
+ }
128
+ };
104
129
 
105
130
  // src/client.ts
131
+ function validateLineItem(li) {
132
+ if ("price_id" in li) return li;
133
+ const hasHuman = li.amount != null;
134
+ const hasAtomic = li.amount_atomic != null;
135
+ if (hasHuman === hasAtomic) {
136
+ 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").');
137
+ }
138
+ return li;
139
+ }
106
140
  var DEFAULT_BASE_URL = "https://api.bitpal.io";
107
141
  var DEFAULT_TIMEOUT = 3e4;
108
142
  var BitPalCheckoutClient = class {
@@ -114,6 +148,8 @@ var BitPalCheckoutClient = class {
114
148
  products;
115
149
  /** PROD-LINK-3a-SDK: immutable Price 관리 — 가격 변경은 비활성화 + 새 price 생성 */
116
150
  prices;
151
+ /** PROD-LINK-4-SDK: Payment Link 관리 — 대시보드 없이 코드로 결제 링크 생성 (server-side 전용) */
152
+ paymentLinks;
117
153
  constructor(config) {
118
154
  this.apiKey = config.apiKey;
119
155
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
@@ -121,6 +157,7 @@ var BitPalCheckoutClient = class {
121
157
  const requester = (method, path, body) => this.request(method, path, body);
122
158
  this.products = new ProductsResource(requester);
123
159
  this.prices = new PricesResource(requester);
160
+ this.paymentLinks = new PaymentLinksResource(requester);
124
161
  }
125
162
  /* ─── 내부 HTTP ─── */
126
163
  async request(method, path, body, extraHeaders) {
@@ -153,20 +190,22 @@ var BitPalCheckoutClient = class {
153
190
  /**
154
191
  * 체크아웃 세션 생성.
155
192
  *
156
- * 모든 `line_items[].amount`는 atomic μUSDC 정수 문자열이어야 한다 ($1 = "1000000").
157
- * decimal 표기는 거부된다 — `toAtomicUSDC()` 헬퍼 사용 권장.
193
+ * `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
194
+ * 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
158
195
  *
159
196
  * @example
160
- * import { BitPal, toAtomicUSDC } from '@bitpal/checkout';
197
+ * import { BitPal } from '@bitpal/checkout';
161
198
  * const bp = new BitPal('bp_test_...');
162
199
  * const { data } = await bp.checkout.createSession({
163
- * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
200
+ * // amount: 사람 표기 USD("29", "0.5"). 금액은 USD 가격이라 체인/토큰 decimal 무관.
201
+ * line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
164
202
  * });
165
203
  * console.log(data.url); // → buyer를 redirect할 결제 페이지
166
204
  *
167
205
  * // 멀티체인 허용 — 구매자가 Base/Optimism 중 선택(하나뿐이면 자동 선택):
206
+ * // 어떤 체인·토큰이든 amount 그대로 — 서버가 발급 시 토큰 decimals 로 재스케일.
168
207
  * await bp.checkout.createSession({
169
- * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
208
+ * line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
170
209
  * allowed_pay_chains: ['eip155:84532', 'eip155:11155420'],
171
210
  * });
172
211
  * // 단일 체인 고정은 pay_chain 사용(둘은 상호 배타).
@@ -175,10 +214,15 @@ var BitPalCheckoutClient = class {
175
214
  if (params.pay_chain && params.allowed_pay_chains && params.allowed_pay_chains.length > 0) {
176
215
  throw new Error("[BitPal] pay_chain and allowed_pay_chains are mutually exclusive \u2014 use only one.");
177
216
  }
217
+ const body = {
218
+ ...params,
219
+ line_items: params.line_items.map(validateLineItem),
220
+ ...params.allowed_assets ? { allowed_assets: params.allowed_assets.map((a) => ({ caip2: a.chain, token: a.token })) } : {}
221
+ };
178
222
  const res = await this.request(
179
223
  "POST",
180
224
  "/v1/checkout/sessions",
181
- params
225
+ body
182
226
  );
183
227
  if (res.data) {
184
228
  if (res.data.checkout_url) {
@@ -208,7 +252,7 @@ var BitPalCheckoutClient = class {
208
252
  `/v1/checkout/sessions${query ? `?${query}` : ""}`
209
253
  );
210
254
  }
211
- /* ─── 비수탁 결제 (deposit-address) ─── */
255
+ /* ─── 안2 비수탁 결제 (deposit-address) ─── */
212
256
  /**
213
257
  * 공개 세션 조회 (`GET /v1/checkout/sessions/:id/public`) — 인증 불요.
214
258
  *
@@ -224,7 +268,7 @@ var BitPalCheckoutClient = class {
224
268
  /**
225
269
  * 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
226
270
  *
227
- * 비수탁 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
271
+ * 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
228
272
  * 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
229
273
  * 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
230
274
  *
@@ -329,26 +373,40 @@ function parseWebhookEvent(body) {
329
373
  }
330
374
 
331
375
  // src/amount.ts
332
- var USDC_DECIMALS = 6n;
333
- var USDC_SCALE = 10n ** USDC_DECIMALS;
334
- function toAtomicUSDC(decimal) {
335
- if (typeof decimal !== "string" || !/^\d+(\.\d{1,6})?$/.test(decimal)) {
336
- throw new Error(`toAtomicUSDC: invalid decimal '${decimal}' (max 6 fractional digits, no negatives)`);
376
+ function toAtomic(decimal, decimals) {
377
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
378
+ throw new Error(`toAtomic: invalid decimals '${decimals}'`);
379
+ }
380
+ const fracPattern = decimals > 0 ? `(\\.\\d{1,${decimals}})?` : "";
381
+ if (typeof decimal !== "string" || !new RegExp(`^\\d+${fracPattern}$`).test(decimal)) {
382
+ throw new Error(`toAtomic: invalid decimal '${decimal}' (max ${decimals} fractional digits, no negatives)`);
337
383
  }
384
+ const scale = 10n ** BigInt(decimals);
338
385
  const [whole = "0", frac = ""] = decimal.split(".");
339
- const fracPadded = frac.padEnd(Number(USDC_DECIMALS), "0");
340
- return (BigInt(whole) * USDC_SCALE + BigInt(fracPadded || "0")).toString();
386
+ const fracPadded = frac.padEnd(decimals, "0");
387
+ return (BigInt(whole) * scale + BigInt(fracPadded || "0")).toString();
341
388
  }
342
- function fromAtomicUSDC(atomic) {
389
+ function fromAtomic(atomic, decimals) {
390
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
391
+ throw new Error(`fromAtomic: invalid decimals '${decimals}'`);
392
+ }
343
393
  if (typeof atomic !== "string" || !/^\d+$/.test(atomic)) {
344
- throw new Error(`fromAtomicUSDC: invalid atomic '${atomic}' (must be non-negative integer string)`);
394
+ throw new Error(`fromAtomic: invalid atomic '${atomic}' (must be non-negative integer string)`);
345
395
  }
396
+ const scale = 10n ** BigInt(decimals);
346
397
  const big = BigInt(atomic);
347
- const whole = big / USDC_SCALE;
348
- const frac = big % USDC_SCALE;
349
- const fracStr = frac.toString().padStart(Number(USDC_DECIMALS), "0").replace(/0+$/, "");
398
+ const whole = big / scale;
399
+ const frac = big % scale;
400
+ if (decimals === 0) return `${whole}`;
401
+ const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
350
402
  return fracStr.length > 0 ? `${whole}.${fracStr.padEnd(2, "0")}` : `${whole}.00`;
351
403
  }
404
+ function toAtomicUSDC(decimal) {
405
+ return toAtomic(decimal, 6);
406
+ }
407
+ function fromAtomicUSDC(atomic) {
408
+ return fromAtomic(atomic, 6);
409
+ }
352
410
 
353
411
  // src/index.ts
354
412
  var BitPal = class {
@@ -364,11 +422,14 @@ var BitPal = class {
364
422
  BitPalCheckoutClient,
365
423
  BitPalError,
366
424
  CHECKOUT_WEBHOOK_EVENTS,
425
+ PaymentLinksResource,
367
426
  PricesResource,
368
427
  ProductsResource,
369
428
  WEBHOOK_HEADERS,
429
+ fromAtomic,
370
430
  fromAtomicUSDC,
371
431
  parseWebhookEvent,
432
+ toAtomic,
372
433
  toAtomicUSDC,
373
434
  verifyWebhookSignature
374
435
  });
package/dist/index.d.cts CHANGED
@@ -25,23 +25,36 @@ interface BitPalConfig {
25
25
  /** 요청 타임아웃 (ms, 기본값: 30000) */
26
26
  timeout?: number;
27
27
  }
28
+ /** 응답/정규화된 line item — amount 는 항상 atomic μUSD 정수. */
28
29
  interface LineItem {
29
30
  name: string;
30
- /**
31
- * Atomic μUSDC 정수 문자열 (USDC/USDT 6 decimals).
32
- * $1 = "1000000", $29 = "29000000".
33
- * decimal 표기는 거부된다 — `toAtomicUSDC('29.00')` 헬퍼 사용 권장.
34
- */
31
+ /** Atomic μUSD 정수 문자열($1="1000000", $29="29000000"). */
35
32
  amount: string;
36
33
  currency: string;
37
34
  quantity?: number;
38
35
  }
36
+ /**
37
+ * 세션 생성 입력용 line item. 금액은 둘 중 **하나**:
38
+ * - `amount`(권장): 사람 표기 USD 문자열(예 "29", "0.5", "29.00"). decimal 신경 쓸 필요 없음.
39
+ * ※ 금액은 **USD 가격**이다(체인/토큰 decimal 무관). 토큰(USDC 6dp / BNB USDT 18dp)별 스케일은 발급 시 서버가 처리.
40
+ * - `amount_atomic`: 이미 raw μUSD 정수 문자열($1="1000000")인 경우(고급/기존 코드).
41
+ * 둘 다 지정하거나 둘 다 생략하면 SDK 가 에러를 throw 한다.
42
+ */
43
+ interface LineItemInput {
44
+ name: string;
45
+ currency: string;
46
+ quantity?: number;
47
+ /** 사람 표기 USD 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
48
+ amount?: string;
49
+ /** raw μUSD 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
50
+ amount_atomic?: string;
51
+ }
39
52
  interface PriceLineItem {
40
53
  /** price_... — bitpal.checkout.prices.create()로 만든 가격 */
41
54
  price_id: string;
42
55
  quantity?: number;
43
56
  }
44
- type CheckoutLineItemInput = LineItem | PriceLineItem;
57
+ type CheckoutLineItemInput = LineItemInput | PriceLineItem;
45
58
  interface CreateSessionParams {
46
59
  line_items: CheckoutLineItemInput[];
47
60
  /** 고객 지갑 주소 (선택) */
@@ -54,6 +67,17 @@ interface CreateSessionParams {
54
67
  * 예: ['eip155:84532', 'eip155:11155420', 'eip155:80002', 'eip155:421614']
55
68
  */
56
69
  allowed_pay_chains?: string[];
70
+ /**
71
+ * chain×token cell 허용 목록(**결제 수단** 표준 입력) — buyer 가 (체인, 토큰) 조합을 고른다.
72
+ * allowed_pay_chains(×단일 currency shorthand)보다 우선. 서로 다른 토큰을 섞을 때 사용.
73
+ * `chain` 은 short-name('base','tron','solana') 또는 CAIP-2 둘 다 허용(서버가 mode-relative 로 정규화).
74
+ * paymentLinks.allowed_assets 와 동일 형태(SDK 일관).
75
+ * 예: [{ chain: 'base', token: 'USDC' }, { chain: 'tron', token: 'USDT' }]
76
+ */
77
+ allowed_assets?: Array<{
78
+ chain: string;
79
+ token: string;
80
+ }>;
57
81
  /** 결제 자산 (예: USDC) */
58
82
  pay_asset?: string;
59
83
  /** 허용 오차 (%, 기본값: 2.00) */
@@ -83,7 +107,7 @@ interface CheckoutSession {
83
107
  settled_at: string | null;
84
108
  tx_hash: string | null;
85
109
  created_at: string;
86
- /** 'hosted' — 결제 경로. hosted deposit-address 단일 경로. */
110
+ /** 'hosted' — 결제 경로. 안2 는 hosted deposit-address 단일 경로. */
87
111
  payment_source?: string;
88
112
  /** 호스티드 결제 페이지 URL (token 쿼리 포함). backend 응답 그대로 사용 — buyer를 이 URL로 redirect. */
89
113
  checkout_url?: string;
@@ -93,15 +117,19 @@ interface CheckoutSession {
93
117
  url: string;
94
118
  }
95
119
  /**
96
- * 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
120
+ * 안2 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
97
121
  * `issueDepositAddress({ chain, token })` 로 입금주소를 발급받는다. 단일-자산 세션은 1개.
98
122
  */
99
123
  interface PaymentOption {
100
- /** CAIP-2 (예: eip155:8453) */
124
+ /** CAIP-2 (예: eip155:8453, tron:0x2b6653dc, solana:5eykt4…). 체인 식별의 canonical. */
101
125
  caip2: string;
102
- chain_id: number;
126
+ /** EVM numeric chainId. 비-EVM(Tron/Solana)은 null — 식별은 caip2 사용. */
127
+ chain_id: number | null;
103
128
  token_symbol: string;
129
+ /** 토큰 컨트랙트/mint 주소(EVM 0x / Tron base58 / Solana base58 mint). */
104
130
  token_address: string;
131
+ /** 토큰 decimals(USDC/USDT=6, BSC-USD=18). atomic↔decimal 변환에 필수 — 6dp 가정 금지. */
132
+ decimals: number;
105
133
  }
106
134
  /**
107
135
  * `getPublicSession()` (`GET /v1/checkout/sessions/:id/public`) 응답 — 인증 불요(공개).
@@ -124,6 +152,8 @@ interface PublicSession {
124
152
  payment_options: PaymentOption[];
125
153
  chain_id: number | null;
126
154
  token_address: string | null;
155
+ /** 선택된 pay 토큰 decimals(발급 후). amount_total/amount_received atomic 해석에 사용 — 6dp 가정 금지. */
156
+ token_decimals?: number | null;
127
157
  tx_hash: string | null;
128
158
  release_tx_hash?: string | null;
129
159
  expires_at: string | null;
@@ -145,7 +175,11 @@ interface PublicSession {
145
175
  interface IssueDepositAddressParams {
146
176
  /** 결제 페이지 접근 증명(HMAC). createSession 응답의 session_token. */
147
177
  session_token: string;
148
- /** 환불 주소(0x EVM). 필수. */
178
+ /**
179
+ * 환불 주소. 필수. 선택한 체인의 VM 형식이어야 한다:
180
+ * EVM=0x…, Tron=base58(T…), Solana=base58. 결제와 같은 네트워크의 본인 주소.
181
+ * 금액부족/잘못된토큰/만료 시 이 주소로 환불되며, 입금주소에 커밋되어 발급 후 변경 불가.
182
+ */
149
183
  refundAddress: string;
150
184
  /** 선택 자산의 chain(CAIP-2 또는 short-name). 멀티옵션 세션 필수. */
151
185
  chain?: string;
@@ -158,7 +192,7 @@ interface IssueDepositAddressParams {
158
192
  interface DepositAddress {
159
193
  sessionId: string;
160
194
  chain: string;
161
- /** 주문별 CREATE2 forwarder 입금주소. */
195
+ /** 주문별 결정적 입금주소(EVM/Tron=CREATE2 forwarder, Solana=order PDA 의 deposit ATA). */
162
196
  depositAddress: string;
163
197
  expectedToken: string;
164
198
  /** Atomic 정수 문자열. */
@@ -248,9 +282,17 @@ interface UpdateProductParams {
248
282
  metadata?: Record<string, unknown>;
249
283
  status?: 'active' | 'inactive';
250
284
  }
285
+ /**
286
+ * 가격 생성 입력. 금액은 둘 중 **하나** (line item 과 동일 모델):
287
+ * - `amount`(권장): 사람 표기 USD 문자열(예 "29", "0.5"). decimal 신경 쓸 필요 없음.
288
+ * - `amount_atomic`: raw μUSD 정수($1="1000000") — 고급/기존.
289
+ * 둘 다 지정하거나 둘 다 생략하면 서버가 400 을 반환한다.
290
+ */
251
291
  interface CreatePriceParams {
252
- /** 원자 단위 정수 문자열 ($1 = "1000000"). `toAtomicUSDC('1.00')` 헬퍼 사용 권장 */
253
- amount_atomic: string;
292
+ /** 사람 표기 USD 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
293
+ amount?: string;
294
+ /** raw μUSD 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
295
+ amount_atomic?: string;
254
296
  currency?: string;
255
297
  token_symbol?: string;
256
298
  billing_type?: 'one_time';
@@ -274,6 +316,80 @@ interface ListPricesParams {
274
316
  limit?: number;
275
317
  offset?: number;
276
318
  }
319
+ interface CheckoutPaymentLink {
320
+ /** plink_... */
321
+ id: string;
322
+ /** 고엔트로피 공개 토큰 — 호스티드 URL `{checkoutBase}/plink/{share_token}` 에 사용 */
323
+ share_token: string;
324
+ /** price_... — 이 링크가 파는 가격(생성 후 고정) */
325
+ price_id: string;
326
+ title: string | null;
327
+ description: string | null;
328
+ collect_email: boolean;
329
+ email_required: boolean;
330
+ /** CAIP-2 목록 또는 null(legacy) */
331
+ allowed_pay_chains: string[] | null;
332
+ /** chain×token cell 목록 또는 null */
333
+ allowed_assets: Array<{
334
+ caip2: string;
335
+ token: string;
336
+ }> | null;
337
+ success_url: string | null;
338
+ cancel_url: string | null;
339
+ /** 'active' | 'inactive' */
340
+ status: string;
341
+ max_uses: number | null;
342
+ paid_use_count: number;
343
+ expires_at: string | null;
344
+ mode: string;
345
+ created_at: string;
346
+ updated_at: string;
347
+ }
348
+ interface CreatePaymentLinkParams {
349
+ /** price_... — bitpal.checkout.prices.create() 로 만든 가격. 필수. */
350
+ price_id: string;
351
+ title?: string;
352
+ description?: string;
353
+ /** buyer 이메일 수집(기본 true) */
354
+ collect_email?: boolean;
355
+ /** buyer 이메일 필수(기본 true). true 면 collect_email 도 true 여야 함. */
356
+ email_required?: boolean;
357
+ /** 허용 결제 체인(CAIP-2 또는 short-name). allowed_assets 가 있으면 그쪽 우선. */
358
+ allowed_pay_chains?: string[];
359
+ /** chain×token cell 허용 목록 — allowed_pay_chains 보다 우선. */
360
+ allowed_assets?: Array<{
361
+ chain: string;
362
+ token: string;
363
+ }>;
364
+ success_url?: string;
365
+ cancel_url?: string;
366
+ max_uses?: number;
367
+ /** ISO-8601 datetime */
368
+ expires_at?: string;
369
+ metadata?: Record<string, unknown>;
370
+ }
371
+ interface UpdatePaymentLinkParams {
372
+ title?: string | null;
373
+ description?: string | null;
374
+ collect_email?: boolean;
375
+ email_required?: boolean;
376
+ allowed_pay_chains?: string[] | null;
377
+ allowed_assets?: Array<{
378
+ chain: string;
379
+ token: string;
380
+ }> | null;
381
+ success_url?: string | null;
382
+ cancel_url?: string | null;
383
+ status?: 'active' | 'inactive';
384
+ max_uses?: number | null;
385
+ expires_at?: string | null;
386
+ metadata?: Record<string, unknown>;
387
+ }
388
+ interface ListPaymentLinksParams {
389
+ status?: 'active' | 'inactive';
390
+ limit?: number;
391
+ offset?: number;
392
+ }
277
393
 
278
394
  /**
279
395
  * catalog.ts — Product / immutable Price 관리 리소스 (PROD-LINK-3a-SDK)
@@ -325,6 +441,33 @@ declare class PricesResource {
325
441
  */
326
442
  update(priceId: string, params: UpdatePriceParams): Promise<ApiResponse<CheckoutPrice>>;
327
443
  }
444
+ /**
445
+ * Payment Link 리소스 — 대시보드 없이 코드로 결제 링크 생성/관리 (PROD-LINK-4).
446
+ *
447
+ * 흐름: product 생성 → price 생성 → payment link 생성. link↔price binding 은 생성 후 고정.
448
+ *
449
+ * ```ts
450
+ * const { data: product } = await bitpal.checkout.products.create({ name: 'Pro Plan' });
451
+ * const { data: price } = await bitpal.checkout.prices.create(product.id, { amount: '29' });
452
+ * const { data: link } = await bitpal.checkout.paymentLinks.create({
453
+ * price_id: price.id,
454
+ * allowed_pay_chains: ['eip155:8453', 'solana:5eykt4Us…'],
455
+ * });
456
+ * // 호스티드 결제 페이지 URL: `{checkoutBase}/plink/${link.share_token}`
457
+ * ```
458
+ */
459
+ declare class PaymentLinksResource {
460
+ private readonly request;
461
+ constructor(request: CatalogRequester);
462
+ /** POST /v1/checkout/payment-links */
463
+ create(params: CreatePaymentLinkParams): Promise<ApiResponse<CheckoutPaymentLink>>;
464
+ /** GET /v1/checkout/payment-links */
465
+ list(params?: ListPaymentLinksParams): Promise<ListResponse<CheckoutPaymentLink>>;
466
+ /** GET /v1/checkout/payment-links/:id */
467
+ retrieve(linkId: string): Promise<ApiResponse<CheckoutPaymentLink>>;
468
+ /** PATCH /v1/checkout/payment-links/:id */
469
+ update(linkId: string, params: UpdatePaymentLinkParams): Promise<ApiResponse<CheckoutPaymentLink>>;
470
+ }
328
471
 
329
472
  /**
330
473
  * @bitpal/checkout SDK — 클라이언트
@@ -342,25 +485,29 @@ declare class BitPalCheckoutClient {
342
485
  readonly products: ProductsResource;
343
486
  /** PROD-LINK-3a-SDK: immutable Price 관리 — 가격 변경은 비활성화 + 새 price 생성 */
344
487
  readonly prices: PricesResource;
488
+ /** PROD-LINK-4-SDK: Payment Link 관리 — 대시보드 없이 코드로 결제 링크 생성 (server-side 전용) */
489
+ readonly paymentLinks: PaymentLinksResource;
345
490
  constructor(config: BitPalConfig);
346
491
  private request;
347
492
  /**
348
493
  * 체크아웃 세션 생성.
349
494
  *
350
- * 모든 `line_items[].amount`는 atomic μUSDC 정수 문자열이어야 한다 ($1 = "1000000").
351
- * decimal 표기는 거부된다 — `toAtomicUSDC()` 헬퍼 사용 권장.
495
+ * `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
496
+ * 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
352
497
  *
353
498
  * @example
354
- * import { BitPal, toAtomicUSDC } from '@bitpal/checkout';
499
+ * import { BitPal } from '@bitpal/checkout';
355
500
  * const bp = new BitPal('bp_test_...');
356
501
  * const { data } = await bp.checkout.createSession({
357
- * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
502
+ * // amount: 사람 표기 USD("29", "0.5"). 금액은 USD 가격이라 체인/토큰 decimal 무관.
503
+ * line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
358
504
  * });
359
505
  * console.log(data.url); // → buyer를 redirect할 결제 페이지
360
506
  *
361
507
  * // 멀티체인 허용 — 구매자가 Base/Optimism 중 선택(하나뿐이면 자동 선택):
508
+ * // 어떤 체인·토큰이든 amount 그대로 — 서버가 발급 시 토큰 decimals 로 재스케일.
362
509
  * await bp.checkout.createSession({
363
- * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
510
+ * line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
364
511
  * allowed_pay_chains: ['eip155:84532', 'eip155:11155420'],
365
512
  * });
366
513
  * // 단일 체인 고정은 pay_chain 사용(둘은 상호 배타).
@@ -384,7 +531,7 @@ declare class BitPalCheckoutClient {
384
531
  /**
385
532
  * 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
386
533
  *
387
- * 비수탁 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
534
+ * 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
388
535
  * 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
389
536
  * 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
390
537
  *
@@ -503,35 +650,41 @@ declare function verifyWebhookSignature(payload: string, signature: string, secr
503
650
  declare function parseWebhookEvent(body: string): WebhookPayload;
504
651
 
505
652
  /**
506
- * amount.ts — atomic μUSDC 변환 헬퍼
653
+ * amount.ts — atomic ↔ decimal 변환 헬퍼 (임의 decimals + USDC 하위호환)
507
654
  *
508
- * BitPal API는 모든 amount를 atomic 정수 문자열로 받는다 (USDC/USDT 6 decimals).
509
- * $1 → "1000000"
510
- * $10 → "10000000"
511
- * $0.5 → "500000"
655
+ * BitPal API는 모든 amount를 atomic 정수 문자열로 받는다. decimals 는 토큰마다 다르다:
656
+ * USDC/USDT(Base/Arb/Tron/Solana) = 6, BSC-USD(BNB USDT) = 18. → payment_options[].decimals /
657
+ * PublicSession.token_decimals 로 서버가 내려준다. 6dp 를 하드코딩하면 non-6dp 토큰에서 금액이 틀린다.
512
658
  *
513
659
  * 정밀도 손실 방지를 위해 BigInt 연산만 사용한다 (Number 변환 금지 — CLAUDE.md 정책).
660
+ * $1(6dp) → "1000000" $1(18dp) → "1000000000000000000"
514
661
  */
515
662
  /**
516
- * decimal 문자열 → atomic μUSDC 문자열
517
- *
518
- * @example
519
- * toAtomicUSDC('29.00') // → '29000000'
520
- * toAtomicUSDC('0.5') // → '500000'
521
- * toAtomicUSDC('1000') // → '1000000000'
663
+ * decimal 문자열 → atomic 문자열 (임의 decimals).
522
664
  *
523
- * @throws {Error} 음수, 6자리 초과 소수, 형식 오류
665
+ * @param decimal 소수 문자열(음수 불가, 소수 자리 ≤ decimals)
666
+ * @param decimals 토큰 decimals (예: 6, 18). payment_options[].decimals 사용.
667
+ * @throws {Error} 음수, decimals 초과 소수 자리, 형식 오류
668
+ * @example toAtomic('29.00', 6) // '29000000' toAtomic('1.5', 18) // '1500000000000000000'
524
669
  */
525
- declare function toAtomicUSDC(decimal: string): string;
670
+ declare function toAtomic(decimal: string, decimals: number): string;
526
671
  /**
527
- * atomic μUSDC 문자열 → 사람이 읽기 좋은 decimal 문자열
528
- *
529
- * @example
530
- * fromAtomicUSDC('29000000') // → '29.00'
531
- * fromAtomicUSDC('500000') // → '0.50'
532
- * fromAtomicUSDC('1500000') // → '1.50'
672
+ * atomic 문자열 → 사람이 읽기 좋은 decimal 문자열 (임의 decimals).
673
+ * 최소 2자리 소수를 유지하되(예: '29.00'), decimals 만큼만 노출.
533
674
  *
534
675
  * @throws {Error} 정수 문자열이 아니면
676
+ * @example fromAtomic('29000000', 6) // '29.00' fromAtomic('1500000000000000000', 18) // '1.50'
677
+ */
678
+ declare function fromAtomic(atomic: string, decimals: number): string;
679
+ /**
680
+ * decimal → atomic μUSDC (6dp). 하위호환 별칭 — 새 코드는 `toAtomic(decimal, decimals)` 권장
681
+ * (BNB USDT=18dp 등 non-6dp 토큰은 이 헬퍼로 처리 불가).
682
+ * @example toAtomicUSDC('29.00') // '29000000'
683
+ */
684
+ declare function toAtomicUSDC(decimal: string): string;
685
+ /**
686
+ * atomic μUSDC(6dp) → decimal. 하위호환 별칭 — 새 코드는 `fromAtomic(atomic, decimals)` 권장.
687
+ * @example fromAtomicUSDC('29000000') // '29.00'
535
688
  */
536
689
  declare function fromAtomicUSDC(atomic: string): string;
537
690
 
@@ -547,8 +700,8 @@ declare function fromAtomicUSDC(atomic: string): string;
547
700
  * const bitpal = new BitPal({ apiKey: 'bp_test_...' });
548
701
  *
549
702
  * 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' }],
703
+ * // amount: 사람 표기 USD("29","0.5"). decimal 계산 불필요 — 체인/토큰 decimal 무관.
704
+ * line_items: [{ name: 'Pro Plan', amount: '29', currency: 'USDC' }],
552
705
  * });
553
706
  *
554
707
  * console.log(session.url); // 호스티드 결제 페이지 URL (checkout_url 별칭)
@@ -563,4 +716,4 @@ declare class BitPal {
563
716
  constructor(config: BitPalConfig | string);
564
717
  }
565
718
 
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 };
719
+ export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, CHECKOUT_WEBHOOK_EVENTS, type CheckoutLineItemInput, type CheckoutPaymentLink, type CheckoutPrice, type CheckoutProduct, type CheckoutSession, type CheckoutWebhookEvent, type CreatePaymentLinkParams, type CreatePriceParams, type CreateProductParams, type CreateSessionParams, type DepositAddress, type FeePreview, type IdempotencyOptions, type IssueDepositAddressParams, type LineItem, type ListPaymentLinksParams, type ListPricesParams, type ListProductsParams, type ListResponse, PaymentLinksResource, type PaymentOption, type PriceLineItem, PricesResource, ProductsResource, type PublicSession, type SessionStatus, type UpdatePaymentLinkParams, type UpdatePriceParams, type UpdateProductParams, WEBHOOK_HEADERS, type WebhookPayload, fromAtomic, fromAtomicUSDC, parseWebhookEvent, toAtomic, toAtomicUSDC, verifyWebhookSignature };
package/dist/index.d.ts CHANGED
@@ -25,23 +25,36 @@ interface BitPalConfig {
25
25
  /** 요청 타임아웃 (ms, 기본값: 30000) */
26
26
  timeout?: number;
27
27
  }
28
+ /** 응답/정규화된 line item — amount 는 항상 atomic μUSD 정수. */
28
29
  interface LineItem {
29
30
  name: string;
30
- /**
31
- * Atomic μUSDC 정수 문자열 (USDC/USDT 6 decimals).
32
- * $1 = "1000000", $29 = "29000000".
33
- * decimal 표기는 거부된다 — `toAtomicUSDC('29.00')` 헬퍼 사용 권장.
34
- */
31
+ /** Atomic μUSD 정수 문자열($1="1000000", $29="29000000"). */
35
32
  amount: string;
36
33
  currency: string;
37
34
  quantity?: number;
38
35
  }
36
+ /**
37
+ * 세션 생성 입력용 line item. 금액은 둘 중 **하나**:
38
+ * - `amount`(권장): 사람 표기 USD 문자열(예 "29", "0.5", "29.00"). decimal 신경 쓸 필요 없음.
39
+ * ※ 금액은 **USD 가격**이다(체인/토큰 decimal 무관). 토큰(USDC 6dp / BNB USDT 18dp)별 스케일은 발급 시 서버가 처리.
40
+ * - `amount_atomic`: 이미 raw μUSD 정수 문자열($1="1000000")인 경우(고급/기존 코드).
41
+ * 둘 다 지정하거나 둘 다 생략하면 SDK 가 에러를 throw 한다.
42
+ */
43
+ interface LineItemInput {
44
+ name: string;
45
+ currency: string;
46
+ quantity?: number;
47
+ /** 사람 표기 USD 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
48
+ amount?: string;
49
+ /** raw μUSD 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
50
+ amount_atomic?: string;
51
+ }
39
52
  interface PriceLineItem {
40
53
  /** price_... — bitpal.checkout.prices.create()로 만든 가격 */
41
54
  price_id: string;
42
55
  quantity?: number;
43
56
  }
44
- type CheckoutLineItemInput = LineItem | PriceLineItem;
57
+ type CheckoutLineItemInput = LineItemInput | PriceLineItem;
45
58
  interface CreateSessionParams {
46
59
  line_items: CheckoutLineItemInput[];
47
60
  /** 고객 지갑 주소 (선택) */
@@ -54,6 +67,17 @@ interface CreateSessionParams {
54
67
  * 예: ['eip155:84532', 'eip155:11155420', 'eip155:80002', 'eip155:421614']
55
68
  */
56
69
  allowed_pay_chains?: string[];
70
+ /**
71
+ * chain×token cell 허용 목록(**결제 수단** 표준 입력) — buyer 가 (체인, 토큰) 조합을 고른다.
72
+ * allowed_pay_chains(×단일 currency shorthand)보다 우선. 서로 다른 토큰을 섞을 때 사용.
73
+ * `chain` 은 short-name('base','tron','solana') 또는 CAIP-2 둘 다 허용(서버가 mode-relative 로 정규화).
74
+ * paymentLinks.allowed_assets 와 동일 형태(SDK 일관).
75
+ * 예: [{ chain: 'base', token: 'USDC' }, { chain: 'tron', token: 'USDT' }]
76
+ */
77
+ allowed_assets?: Array<{
78
+ chain: string;
79
+ token: string;
80
+ }>;
57
81
  /** 결제 자산 (예: USDC) */
58
82
  pay_asset?: string;
59
83
  /** 허용 오차 (%, 기본값: 2.00) */
@@ -83,7 +107,7 @@ interface CheckoutSession {
83
107
  settled_at: string | null;
84
108
  tx_hash: string | null;
85
109
  created_at: string;
86
- /** 'hosted' — 결제 경로. hosted deposit-address 단일 경로. */
110
+ /** 'hosted' — 결제 경로. 안2 는 hosted deposit-address 단일 경로. */
87
111
  payment_source?: string;
88
112
  /** 호스티드 결제 페이지 URL (token 쿼리 포함). backend 응답 그대로 사용 — buyer를 이 URL로 redirect. */
89
113
  checkout_url?: string;
@@ -93,15 +117,19 @@ interface CheckoutSession {
93
117
  url: string;
94
118
  }
95
119
  /**
96
- * 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
120
+ * 안2 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
97
121
  * `issueDepositAddress({ chain, token })` 로 입금주소를 발급받는다. 단일-자산 세션은 1개.
98
122
  */
99
123
  interface PaymentOption {
100
- /** CAIP-2 (예: eip155:8453) */
124
+ /** CAIP-2 (예: eip155:8453, tron:0x2b6653dc, solana:5eykt4…). 체인 식별의 canonical. */
101
125
  caip2: string;
102
- chain_id: number;
126
+ /** EVM numeric chainId. 비-EVM(Tron/Solana)은 null — 식별은 caip2 사용. */
127
+ chain_id: number | null;
103
128
  token_symbol: string;
129
+ /** 토큰 컨트랙트/mint 주소(EVM 0x / Tron base58 / Solana base58 mint). */
104
130
  token_address: string;
131
+ /** 토큰 decimals(USDC/USDT=6, BSC-USD=18). atomic↔decimal 변환에 필수 — 6dp 가정 금지. */
132
+ decimals: number;
105
133
  }
106
134
  /**
107
135
  * `getPublicSession()` (`GET /v1/checkout/sessions/:id/public`) 응답 — 인증 불요(공개).
@@ -124,6 +152,8 @@ interface PublicSession {
124
152
  payment_options: PaymentOption[];
125
153
  chain_id: number | null;
126
154
  token_address: string | null;
155
+ /** 선택된 pay 토큰 decimals(발급 후). amount_total/amount_received atomic 해석에 사용 — 6dp 가정 금지. */
156
+ token_decimals?: number | null;
127
157
  tx_hash: string | null;
128
158
  release_tx_hash?: string | null;
129
159
  expires_at: string | null;
@@ -145,7 +175,11 @@ interface PublicSession {
145
175
  interface IssueDepositAddressParams {
146
176
  /** 결제 페이지 접근 증명(HMAC). createSession 응답의 session_token. */
147
177
  session_token: string;
148
- /** 환불 주소(0x EVM). 필수. */
178
+ /**
179
+ * 환불 주소. 필수. 선택한 체인의 VM 형식이어야 한다:
180
+ * EVM=0x…, Tron=base58(T…), Solana=base58. 결제와 같은 네트워크의 본인 주소.
181
+ * 금액부족/잘못된토큰/만료 시 이 주소로 환불되며, 입금주소에 커밋되어 발급 후 변경 불가.
182
+ */
149
183
  refundAddress: string;
150
184
  /** 선택 자산의 chain(CAIP-2 또는 short-name). 멀티옵션 세션 필수. */
151
185
  chain?: string;
@@ -158,7 +192,7 @@ interface IssueDepositAddressParams {
158
192
  interface DepositAddress {
159
193
  sessionId: string;
160
194
  chain: string;
161
- /** 주문별 CREATE2 forwarder 입금주소. */
195
+ /** 주문별 결정적 입금주소(EVM/Tron=CREATE2 forwarder, Solana=order PDA 의 deposit ATA). */
162
196
  depositAddress: string;
163
197
  expectedToken: string;
164
198
  /** Atomic 정수 문자열. */
@@ -248,9 +282,17 @@ interface UpdateProductParams {
248
282
  metadata?: Record<string, unknown>;
249
283
  status?: 'active' | 'inactive';
250
284
  }
285
+ /**
286
+ * 가격 생성 입력. 금액은 둘 중 **하나** (line item 과 동일 모델):
287
+ * - `amount`(권장): 사람 표기 USD 문자열(예 "29", "0.5"). decimal 신경 쓸 필요 없음.
288
+ * - `amount_atomic`: raw μUSD 정수($1="1000000") — 고급/기존.
289
+ * 둘 다 지정하거나 둘 다 생략하면 서버가 400 을 반환한다.
290
+ */
251
291
  interface CreatePriceParams {
252
- /** 원자 단위 정수 문자열 ($1 = "1000000"). `toAtomicUSDC('1.00')` 헬퍼 사용 권장 */
253
- amount_atomic: string;
292
+ /** 사람 표기 USD 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
293
+ amount?: string;
294
+ /** raw μUSD 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
295
+ amount_atomic?: string;
254
296
  currency?: string;
255
297
  token_symbol?: string;
256
298
  billing_type?: 'one_time';
@@ -274,6 +316,80 @@ interface ListPricesParams {
274
316
  limit?: number;
275
317
  offset?: number;
276
318
  }
319
+ interface CheckoutPaymentLink {
320
+ /** plink_... */
321
+ id: string;
322
+ /** 고엔트로피 공개 토큰 — 호스티드 URL `{checkoutBase}/plink/{share_token}` 에 사용 */
323
+ share_token: string;
324
+ /** price_... — 이 링크가 파는 가격(생성 후 고정) */
325
+ price_id: string;
326
+ title: string | null;
327
+ description: string | null;
328
+ collect_email: boolean;
329
+ email_required: boolean;
330
+ /** CAIP-2 목록 또는 null(legacy) */
331
+ allowed_pay_chains: string[] | null;
332
+ /** chain×token cell 목록 또는 null */
333
+ allowed_assets: Array<{
334
+ caip2: string;
335
+ token: string;
336
+ }> | null;
337
+ success_url: string | null;
338
+ cancel_url: string | null;
339
+ /** 'active' | 'inactive' */
340
+ status: string;
341
+ max_uses: number | null;
342
+ paid_use_count: number;
343
+ expires_at: string | null;
344
+ mode: string;
345
+ created_at: string;
346
+ updated_at: string;
347
+ }
348
+ interface CreatePaymentLinkParams {
349
+ /** price_... — bitpal.checkout.prices.create() 로 만든 가격. 필수. */
350
+ price_id: string;
351
+ title?: string;
352
+ description?: string;
353
+ /** buyer 이메일 수집(기본 true) */
354
+ collect_email?: boolean;
355
+ /** buyer 이메일 필수(기본 true). true 면 collect_email 도 true 여야 함. */
356
+ email_required?: boolean;
357
+ /** 허용 결제 체인(CAIP-2 또는 short-name). allowed_assets 가 있으면 그쪽 우선. */
358
+ allowed_pay_chains?: string[];
359
+ /** chain×token cell 허용 목록 — allowed_pay_chains 보다 우선. */
360
+ allowed_assets?: Array<{
361
+ chain: string;
362
+ token: string;
363
+ }>;
364
+ success_url?: string;
365
+ cancel_url?: string;
366
+ max_uses?: number;
367
+ /** ISO-8601 datetime */
368
+ expires_at?: string;
369
+ metadata?: Record<string, unknown>;
370
+ }
371
+ interface UpdatePaymentLinkParams {
372
+ title?: string | null;
373
+ description?: string | null;
374
+ collect_email?: boolean;
375
+ email_required?: boolean;
376
+ allowed_pay_chains?: string[] | null;
377
+ allowed_assets?: Array<{
378
+ chain: string;
379
+ token: string;
380
+ }> | null;
381
+ success_url?: string | null;
382
+ cancel_url?: string | null;
383
+ status?: 'active' | 'inactive';
384
+ max_uses?: number | null;
385
+ expires_at?: string | null;
386
+ metadata?: Record<string, unknown>;
387
+ }
388
+ interface ListPaymentLinksParams {
389
+ status?: 'active' | 'inactive';
390
+ limit?: number;
391
+ offset?: number;
392
+ }
277
393
 
278
394
  /**
279
395
  * catalog.ts — Product / immutable Price 관리 리소스 (PROD-LINK-3a-SDK)
@@ -325,6 +441,33 @@ declare class PricesResource {
325
441
  */
326
442
  update(priceId: string, params: UpdatePriceParams): Promise<ApiResponse<CheckoutPrice>>;
327
443
  }
444
+ /**
445
+ * Payment Link 리소스 — 대시보드 없이 코드로 결제 링크 생성/관리 (PROD-LINK-4).
446
+ *
447
+ * 흐름: product 생성 → price 생성 → payment link 생성. link↔price binding 은 생성 후 고정.
448
+ *
449
+ * ```ts
450
+ * const { data: product } = await bitpal.checkout.products.create({ name: 'Pro Plan' });
451
+ * const { data: price } = await bitpal.checkout.prices.create(product.id, { amount: '29' });
452
+ * const { data: link } = await bitpal.checkout.paymentLinks.create({
453
+ * price_id: price.id,
454
+ * allowed_pay_chains: ['eip155:8453', 'solana:5eykt4Us…'],
455
+ * });
456
+ * // 호스티드 결제 페이지 URL: `{checkoutBase}/plink/${link.share_token}`
457
+ * ```
458
+ */
459
+ declare class PaymentLinksResource {
460
+ private readonly request;
461
+ constructor(request: CatalogRequester);
462
+ /** POST /v1/checkout/payment-links */
463
+ create(params: CreatePaymentLinkParams): Promise<ApiResponse<CheckoutPaymentLink>>;
464
+ /** GET /v1/checkout/payment-links */
465
+ list(params?: ListPaymentLinksParams): Promise<ListResponse<CheckoutPaymentLink>>;
466
+ /** GET /v1/checkout/payment-links/:id */
467
+ retrieve(linkId: string): Promise<ApiResponse<CheckoutPaymentLink>>;
468
+ /** PATCH /v1/checkout/payment-links/:id */
469
+ update(linkId: string, params: UpdatePaymentLinkParams): Promise<ApiResponse<CheckoutPaymentLink>>;
470
+ }
328
471
 
329
472
  /**
330
473
  * @bitpal/checkout SDK — 클라이언트
@@ -342,25 +485,29 @@ declare class BitPalCheckoutClient {
342
485
  readonly products: ProductsResource;
343
486
  /** PROD-LINK-3a-SDK: immutable Price 관리 — 가격 변경은 비활성화 + 새 price 생성 */
344
487
  readonly prices: PricesResource;
488
+ /** PROD-LINK-4-SDK: Payment Link 관리 — 대시보드 없이 코드로 결제 링크 생성 (server-side 전용) */
489
+ readonly paymentLinks: PaymentLinksResource;
345
490
  constructor(config: BitPalConfig);
346
491
  private request;
347
492
  /**
348
493
  * 체크아웃 세션 생성.
349
494
  *
350
- * 모든 `line_items[].amount`는 atomic μUSDC 정수 문자열이어야 한다 ($1 = "1000000").
351
- * decimal 표기는 거부된다 — `toAtomicUSDC()` 헬퍼 사용 권장.
495
+ * `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
496
+ * 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
352
497
  *
353
498
  * @example
354
- * import { BitPal, toAtomicUSDC } from '@bitpal/checkout';
499
+ * import { BitPal } from '@bitpal/checkout';
355
500
  * const bp = new BitPal('bp_test_...');
356
501
  * const { data } = await bp.checkout.createSession({
357
- * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
502
+ * // amount: 사람 표기 USD("29", "0.5"). 금액은 USD 가격이라 체인/토큰 decimal 무관.
503
+ * line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
358
504
  * });
359
505
  * console.log(data.url); // → buyer를 redirect할 결제 페이지
360
506
  *
361
507
  * // 멀티체인 허용 — 구매자가 Base/Optimism 중 선택(하나뿐이면 자동 선택):
508
+ * // 어떤 체인·토큰이든 amount 그대로 — 서버가 발급 시 토큰 decimals 로 재스케일.
362
509
  * await bp.checkout.createSession({
363
- * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
510
+ * line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
364
511
  * allowed_pay_chains: ['eip155:84532', 'eip155:11155420'],
365
512
  * });
366
513
  * // 단일 체인 고정은 pay_chain 사용(둘은 상호 배타).
@@ -384,7 +531,7 @@ declare class BitPalCheckoutClient {
384
531
  /**
385
532
  * 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
386
533
  *
387
- * 비수탁 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
534
+ * 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
388
535
  * 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
389
536
  * 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
390
537
  *
@@ -503,35 +650,41 @@ declare function verifyWebhookSignature(payload: string, signature: string, secr
503
650
  declare function parseWebhookEvent(body: string): WebhookPayload;
504
651
 
505
652
  /**
506
- * amount.ts — atomic μUSDC 변환 헬퍼
653
+ * amount.ts — atomic ↔ decimal 변환 헬퍼 (임의 decimals + USDC 하위호환)
507
654
  *
508
- * BitPal API는 모든 amount를 atomic 정수 문자열로 받는다 (USDC/USDT 6 decimals).
509
- * $1 → "1000000"
510
- * $10 → "10000000"
511
- * $0.5 → "500000"
655
+ * BitPal API는 모든 amount를 atomic 정수 문자열로 받는다. decimals 는 토큰마다 다르다:
656
+ * USDC/USDT(Base/Arb/Tron/Solana) = 6, BSC-USD(BNB USDT) = 18. → payment_options[].decimals /
657
+ * PublicSession.token_decimals 로 서버가 내려준다. 6dp 를 하드코딩하면 non-6dp 토큰에서 금액이 틀린다.
512
658
  *
513
659
  * 정밀도 손실 방지를 위해 BigInt 연산만 사용한다 (Number 변환 금지 — CLAUDE.md 정책).
660
+ * $1(6dp) → "1000000" $1(18dp) → "1000000000000000000"
514
661
  */
515
662
  /**
516
- * decimal 문자열 → atomic μUSDC 문자열
517
- *
518
- * @example
519
- * toAtomicUSDC('29.00') // → '29000000'
520
- * toAtomicUSDC('0.5') // → '500000'
521
- * toAtomicUSDC('1000') // → '1000000000'
663
+ * decimal 문자열 → atomic 문자열 (임의 decimals).
522
664
  *
523
- * @throws {Error} 음수, 6자리 초과 소수, 형식 오류
665
+ * @param decimal 소수 문자열(음수 불가, 소수 자리 ≤ decimals)
666
+ * @param decimals 토큰 decimals (예: 6, 18). payment_options[].decimals 사용.
667
+ * @throws {Error} 음수, decimals 초과 소수 자리, 형식 오류
668
+ * @example toAtomic('29.00', 6) // '29000000' toAtomic('1.5', 18) // '1500000000000000000'
524
669
  */
525
- declare function toAtomicUSDC(decimal: string): string;
670
+ declare function toAtomic(decimal: string, decimals: number): string;
526
671
  /**
527
- * atomic μUSDC 문자열 → 사람이 읽기 좋은 decimal 문자열
528
- *
529
- * @example
530
- * fromAtomicUSDC('29000000') // → '29.00'
531
- * fromAtomicUSDC('500000') // → '0.50'
532
- * fromAtomicUSDC('1500000') // → '1.50'
672
+ * atomic 문자열 → 사람이 읽기 좋은 decimal 문자열 (임의 decimals).
673
+ * 최소 2자리 소수를 유지하되(예: '29.00'), decimals 만큼만 노출.
533
674
  *
534
675
  * @throws {Error} 정수 문자열이 아니면
676
+ * @example fromAtomic('29000000', 6) // '29.00' fromAtomic('1500000000000000000', 18) // '1.50'
677
+ */
678
+ declare function fromAtomic(atomic: string, decimals: number): string;
679
+ /**
680
+ * decimal → atomic μUSDC (6dp). 하위호환 별칭 — 새 코드는 `toAtomic(decimal, decimals)` 권장
681
+ * (BNB USDT=18dp 등 non-6dp 토큰은 이 헬퍼로 처리 불가).
682
+ * @example toAtomicUSDC('29.00') // '29000000'
683
+ */
684
+ declare function toAtomicUSDC(decimal: string): string;
685
+ /**
686
+ * atomic μUSDC(6dp) → decimal. 하위호환 별칭 — 새 코드는 `fromAtomic(atomic, decimals)` 권장.
687
+ * @example fromAtomicUSDC('29000000') // '29.00'
535
688
  */
536
689
  declare function fromAtomicUSDC(atomic: string): string;
537
690
 
@@ -547,8 +700,8 @@ declare function fromAtomicUSDC(atomic: string): string;
547
700
  * const bitpal = new BitPal({ apiKey: 'bp_test_...' });
548
701
  *
549
702
  * 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' }],
703
+ * // amount: 사람 표기 USD("29","0.5"). decimal 계산 불필요 — 체인/토큰 decimal 무관.
704
+ * line_items: [{ name: 'Pro Plan', amount: '29', currency: 'USDC' }],
552
705
  * });
553
706
  *
554
707
  * console.log(session.url); // 호스티드 결제 페이지 URL (checkout_url 별칭)
@@ -563,4 +716,4 @@ declare class BitPal {
563
716
  constructor(config: BitPalConfig | string);
564
717
  }
565
718
 
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 };
719
+ export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, CHECKOUT_WEBHOOK_EVENTS, type CheckoutLineItemInput, type CheckoutPaymentLink, type CheckoutPrice, type CheckoutProduct, type CheckoutSession, type CheckoutWebhookEvent, type CreatePaymentLinkParams, type CreatePriceParams, type CreateProductParams, type CreateSessionParams, type DepositAddress, type FeePreview, type IdempotencyOptions, type IssueDepositAddressParams, type LineItem, type ListPaymentLinksParams, type ListPricesParams, type ListProductsParams, type ListResponse, PaymentLinksResource, type PaymentOption, type PriceLineItem, PricesResource, ProductsResource, type PublicSession, type SessionStatus, type UpdatePaymentLinkParams, type UpdatePriceParams, type UpdateProductParams, WEBHOOK_HEADERS, type WebhookPayload, fromAtomic, fromAtomicUSDC, parseWebhookEvent, toAtomic, toAtomicUSDC, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -55,8 +55,39 @@ var PricesResource = class {
55
55
  return this.request("PATCH", `/v1/checkout/prices/${encodeURIComponent(priceId)}`, params);
56
56
  }
57
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
+ };
58
80
 
59
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
+ }
60
91
  var DEFAULT_BASE_URL = "https://api.bitpal.io";
61
92
  var DEFAULT_TIMEOUT = 3e4;
62
93
  var BitPalCheckoutClient = class {
@@ -68,6 +99,8 @@ var BitPalCheckoutClient = class {
68
99
  products;
69
100
  /** PROD-LINK-3a-SDK: immutable Price 관리 — 가격 변경은 비활성화 + 새 price 생성 */
70
101
  prices;
102
+ /** PROD-LINK-4-SDK: Payment Link 관리 — 대시보드 없이 코드로 결제 링크 생성 (server-side 전용) */
103
+ paymentLinks;
71
104
  constructor(config) {
72
105
  this.apiKey = config.apiKey;
73
106
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
@@ -75,6 +108,7 @@ var BitPalCheckoutClient = class {
75
108
  const requester = (method, path, body) => this.request(method, path, body);
76
109
  this.products = new ProductsResource(requester);
77
110
  this.prices = new PricesResource(requester);
111
+ this.paymentLinks = new PaymentLinksResource(requester);
78
112
  }
79
113
  /* ─── 내부 HTTP ─── */
80
114
  async request(method, path, body, extraHeaders) {
@@ -107,20 +141,22 @@ var BitPalCheckoutClient = class {
107
141
  /**
108
142
  * 체크아웃 세션 생성.
109
143
  *
110
- * 모든 `line_items[].amount`는 atomic μUSDC 정수 문자열이어야 한다 ($1 = "1000000").
111
- * decimal 표기는 거부된다 — `toAtomicUSDC()` 헬퍼 사용 권장.
144
+ * `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
145
+ * 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
112
146
  *
113
147
  * @example
114
- * import { BitPal, toAtomicUSDC } from '@bitpal/checkout';
148
+ * import { BitPal } from '@bitpal/checkout';
115
149
  * const bp = new BitPal('bp_test_...');
116
150
  * const { data } = await bp.checkout.createSession({
117
- * 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' }],
118
153
  * });
119
154
  * console.log(data.url); // → buyer를 redirect할 결제 페이지
120
155
  *
121
156
  * // 멀티체인 허용 — 구매자가 Base/Optimism 중 선택(하나뿐이면 자동 선택):
157
+ * // 어떤 체인·토큰이든 amount 그대로 — 서버가 발급 시 토큰 decimals 로 재스케일.
122
158
  * await bp.checkout.createSession({
123
- * line_items: [{ name: 'Pro', amount: toAtomicUSDC('29.00'), currency: 'USDC' }],
159
+ * line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
124
160
  * allowed_pay_chains: ['eip155:84532', 'eip155:11155420'],
125
161
  * });
126
162
  * // 단일 체인 고정은 pay_chain 사용(둘은 상호 배타).
@@ -129,10 +165,15 @@ var BitPalCheckoutClient = class {
129
165
  if (params.pay_chain && params.allowed_pay_chains && params.allowed_pay_chains.length > 0) {
130
166
  throw new Error("[BitPal] pay_chain and allowed_pay_chains are mutually exclusive \u2014 use only one.");
131
167
  }
168
+ const body = {
169
+ ...params,
170
+ line_items: params.line_items.map(validateLineItem),
171
+ ...params.allowed_assets ? { allowed_assets: params.allowed_assets.map((a) => ({ caip2: a.chain, token: a.token })) } : {}
172
+ };
132
173
  const res = await this.request(
133
174
  "POST",
134
175
  "/v1/checkout/sessions",
135
- params
176
+ body
136
177
  );
137
178
  if (res.data) {
138
179
  if (res.data.checkout_url) {
@@ -162,7 +203,7 @@ var BitPalCheckoutClient = class {
162
203
  `/v1/checkout/sessions${query ? `?${query}` : ""}`
163
204
  );
164
205
  }
165
- /* ─── 비수탁 결제 (deposit-address) ─── */
206
+ /* ─── 안2 비수탁 결제 (deposit-address) ─── */
166
207
  /**
167
208
  * 공개 세션 조회 (`GET /v1/checkout/sessions/:id/public`) — 인증 불요.
168
209
  *
@@ -178,7 +219,7 @@ var BitPalCheckoutClient = class {
178
219
  /**
179
220
  * 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
180
221
  *
181
- * 비수탁 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
222
+ * 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
182
223
  * 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
183
224
  * 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
184
225
  *
@@ -283,26 +324,40 @@ function parseWebhookEvent(body) {
283
324
  }
284
325
 
285
326
  // src/amount.ts
286
- var USDC_DECIMALS = 6n;
287
- var USDC_SCALE = 10n ** USDC_DECIMALS;
288
- function toAtomicUSDC(decimal) {
289
- if (typeof decimal !== "string" || !/^\d+(\.\d{1,6})?$/.test(decimal)) {
290
- throw new Error(`toAtomicUSDC: invalid decimal '${decimal}' (max 6 fractional digits, no negatives)`);
327
+ function toAtomic(decimal, decimals) {
328
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
329
+ throw new Error(`toAtomic: invalid decimals '${decimals}'`);
330
+ }
331
+ const fracPattern = decimals > 0 ? `(\\.\\d{1,${decimals}})?` : "";
332
+ if (typeof decimal !== "string" || !new RegExp(`^\\d+${fracPattern}$`).test(decimal)) {
333
+ throw new Error(`toAtomic: invalid decimal '${decimal}' (max ${decimals} fractional digits, no negatives)`);
291
334
  }
335
+ const scale = 10n ** BigInt(decimals);
292
336
  const [whole = "0", frac = ""] = decimal.split(".");
293
- const fracPadded = frac.padEnd(Number(USDC_DECIMALS), "0");
294
- return (BigInt(whole) * USDC_SCALE + BigInt(fracPadded || "0")).toString();
337
+ const fracPadded = frac.padEnd(decimals, "0");
338
+ return (BigInt(whole) * scale + BigInt(fracPadded || "0")).toString();
295
339
  }
296
- function fromAtomicUSDC(atomic) {
340
+ function fromAtomic(atomic, decimals) {
341
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
342
+ throw new Error(`fromAtomic: invalid decimals '${decimals}'`);
343
+ }
297
344
  if (typeof atomic !== "string" || !/^\d+$/.test(atomic)) {
298
- throw new Error(`fromAtomicUSDC: invalid atomic '${atomic}' (must be non-negative integer string)`);
345
+ throw new Error(`fromAtomic: invalid atomic '${atomic}' (must be non-negative integer string)`);
299
346
  }
347
+ const scale = 10n ** BigInt(decimals);
300
348
  const big = BigInt(atomic);
301
- const whole = big / USDC_SCALE;
302
- const frac = big % USDC_SCALE;
303
- const fracStr = frac.toString().padStart(Number(USDC_DECIMALS), "0").replace(/0+$/, "");
349
+ const whole = big / scale;
350
+ const frac = big % scale;
351
+ if (decimals === 0) return `${whole}`;
352
+ const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
304
353
  return fracStr.length > 0 ? `${whole}.${fracStr.padEnd(2, "0")}` : `${whole}.00`;
305
354
  }
355
+ function toAtomicUSDC(decimal) {
356
+ return toAtomic(decimal, 6);
357
+ }
358
+ function fromAtomicUSDC(atomic) {
359
+ return fromAtomic(atomic, 6);
360
+ }
306
361
 
307
362
  // src/index.ts
308
363
  var BitPal = class {
@@ -317,11 +372,14 @@ export {
317
372
  BitPalCheckoutClient,
318
373
  BitPalError,
319
374
  CHECKOUT_WEBHOOK_EVENTS,
375
+ PaymentLinksResource,
320
376
  PricesResource,
321
377
  ProductsResource,
322
378
  WEBHOOK_HEADERS,
379
+ fromAtomic,
323
380
  fromAtomicUSDC,
324
381
  parseWebhookEvent,
382
+ toAtomic,
325
383
  toAtomicUSDC,
326
384
  verifyWebhookSignature
327
385
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitpal/checkout",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "BitPal Checkout SDK — non-custodial crypto checkout: create sessions, issue per-order deposit addresses, poll status, verify signed webhooks.",
5
5
  "license": "MIT",
6
6
  "repository": {