@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 +37 -24
- package/dist/index.cjs +81 -20
- package/dist/index.d.cts +195 -42
- package/dist/index.d.ts +195 -42
- package/dist/index.js +78 -20
- package/package.json +1 -1
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
|
-
##
|
|
14
|
+
## Amounts — just pass a human number
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Use **`amount`** in line items — a plain USD string like `"20"`, `"0.5"`, `"29.00"`. No decimals to compute.
|
|
17
17
|
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
$29.00 → "29000000"
|
|
21
|
-
$0.50 → "500000"
|
|
18
|
+
```ts
|
|
19
|
+
line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }]
|
|
22
20
|
```
|
|
23
21
|
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
import { toAtomicUSDC, fromAtomicUSDC } from '@bitpal/checkout';
|
|
24
|
+
<details><summary>Advanced: raw atomic amounts</summary>
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
49
|
-
import {
|
|
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:
|
|
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
|
-
|
|
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...', //
|
|
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
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
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
|
-
*
|
|
157
|
-
*
|
|
193
|
+
* `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
|
|
194
|
+
* 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
158
195
|
*
|
|
159
196
|
* @example
|
|
160
|
-
* import { BitPal
|
|
197
|
+
* import { BitPal } from '@bitpal/checkout';
|
|
161
198
|
* const bp = new BitPal('bp_test_...');
|
|
162
199
|
* const { data } = await bp.checkout.createSession({
|
|
163
|
-
*
|
|
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:
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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(
|
|
340
|
-
return (BigInt(whole) *
|
|
386
|
+
const fracPadded = frac.padEnd(decimals, "0");
|
|
387
|
+
return (BigInt(whole) * scale + BigInt(fracPadded || "0")).toString();
|
|
341
388
|
}
|
|
342
|
-
function
|
|
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(`
|
|
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 /
|
|
348
|
-
const frac = big %
|
|
349
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
253
|
-
|
|
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
|
-
*
|
|
351
|
-
*
|
|
495
|
+
* `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
|
|
496
|
+
* 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
352
497
|
*
|
|
353
498
|
* @example
|
|
354
|
-
* import { BitPal
|
|
499
|
+
* import { BitPal } from '@bitpal/checkout';
|
|
355
500
|
* const bp = new BitPal('bp_test_...');
|
|
356
501
|
* const { data } = await bp.checkout.createSession({
|
|
357
|
-
*
|
|
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:
|
|
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
|
-
*
|
|
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
|
|
653
|
+
* amount.ts — atomic ↔ decimal 변환 헬퍼 (임의 decimals + USDC 하위호환)
|
|
507
654
|
*
|
|
508
|
-
* BitPal API는 모든 amount를 atomic 정수 문자열로
|
|
509
|
-
*
|
|
510
|
-
*
|
|
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
|
|
517
|
-
*
|
|
518
|
-
* @example
|
|
519
|
-
* toAtomicUSDC('29.00') // → '29000000'
|
|
520
|
-
* toAtomicUSDC('0.5') // → '500000'
|
|
521
|
-
* toAtomicUSDC('1000') // → '1000000000'
|
|
663
|
+
* decimal 문자열 → atomic 문자열 (임의 decimals).
|
|
522
664
|
*
|
|
523
|
-
* @
|
|
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
|
|
670
|
+
declare function toAtomic(decimal: string, decimals: number): string;
|
|
526
671
|
/**
|
|
527
|
-
* atomic
|
|
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
|
|
551
|
-
* line_items: [{ name: 'Pro Plan', amount:
|
|
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 =
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
253
|
-
|
|
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
|
-
*
|
|
351
|
-
*
|
|
495
|
+
* `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
|
|
496
|
+
* 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
352
497
|
*
|
|
353
498
|
* @example
|
|
354
|
-
* import { BitPal
|
|
499
|
+
* import { BitPal } from '@bitpal/checkout';
|
|
355
500
|
* const bp = new BitPal('bp_test_...');
|
|
356
501
|
* const { data } = await bp.checkout.createSession({
|
|
357
|
-
*
|
|
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:
|
|
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
|
-
*
|
|
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
|
|
653
|
+
* amount.ts — atomic ↔ decimal 변환 헬퍼 (임의 decimals + USDC 하위호환)
|
|
507
654
|
*
|
|
508
|
-
* BitPal API는 모든 amount를 atomic 정수 문자열로
|
|
509
|
-
*
|
|
510
|
-
*
|
|
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
|
|
517
|
-
*
|
|
518
|
-
* @example
|
|
519
|
-
* toAtomicUSDC('29.00') // → '29000000'
|
|
520
|
-
* toAtomicUSDC('0.5') // → '500000'
|
|
521
|
-
* toAtomicUSDC('1000') // → '1000000000'
|
|
663
|
+
* decimal 문자열 → atomic 문자열 (임의 decimals).
|
|
522
664
|
*
|
|
523
|
-
* @
|
|
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
|
|
670
|
+
declare function toAtomic(decimal: string, decimals: number): string;
|
|
526
671
|
/**
|
|
527
|
-
* atomic
|
|
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
|
|
551
|
-
* line_items: [{ name: 'Pro Plan', amount:
|
|
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
|
-
*
|
|
111
|
-
*
|
|
144
|
+
* `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
|
|
145
|
+
* 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
112
146
|
*
|
|
113
147
|
* @example
|
|
114
|
-
* import { BitPal
|
|
148
|
+
* import { BitPal } from '@bitpal/checkout';
|
|
115
149
|
* const bp = new BitPal('bp_test_...');
|
|
116
150
|
* const { data } = await bp.checkout.createSession({
|
|
117
|
-
*
|
|
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:
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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(
|
|
294
|
-
return (BigInt(whole) *
|
|
337
|
+
const fracPadded = frac.padEnd(decimals, "0");
|
|
338
|
+
return (BigInt(whole) * scale + BigInt(fracPadded || "0")).toString();
|
|
295
339
|
}
|
|
296
|
-
function
|
|
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(`
|
|
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 /
|
|
302
|
-
const frac = big %
|
|
303
|
-
|
|
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.
|
|
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": {
|