@bitpal/checkout 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -20
- package/dist/index.cjs +77 -20
- package/dist/index.d.cts +184 -42
- package/dist/index.d.ts +184 -42
- package/dist/index.js +74 -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,7 +63,7 @@ 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' }, // just the USD number
|
|
65
67
|
],
|
|
66
68
|
pay_chain: 'eip155:84532', // Base Sepolia (test mode). Live mode: 'eip155:8453' (Base mainnet).
|
|
67
69
|
// Or use allowed_pay_chains for a buyer-picked chain.
|
|
@@ -85,7 +87,7 @@ you.
|
|
|
85
87
|
```ts
|
|
86
88
|
const { data } = await bitpal.checkout.issueDepositAddress(session.id, {
|
|
87
89
|
session_token: session.session_token!, // from createSession
|
|
88
|
-
refundAddress: '0xBuyerWallet...', //
|
|
90
|
+
refundAddress: '0xBuyerWallet...', // VM-matched (EVM 0x… / Tron T… / Solana base58); committed, immutable after issuance
|
|
89
91
|
chain: 'eip155:84532', // match the session's chain (test: Base Sepolia / live: eip155:8453).
|
|
90
92
|
// only needed for multi-option (allowed_pay_chains) sessions
|
|
91
93
|
token: 'USDC',
|
|
@@ -195,10 +197,9 @@ new BitPal({ apiKey, baseUrl: 'https://…' }); // config object — override ba
|
|
|
195
197
|
|
|
196
198
|
## Runnable examples
|
|
197
199
|
|
|
198
|
-
The
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
webhook verification + idempotency.
|
|
200
|
+
The source repo's `examples/` directory has runnable references (not shipped in the npm
|
|
201
|
+
package): `create-session.mjs`, `webhook-receiver.mjs`, and a `full-integration/` Express
|
|
202
|
+
store wiring session creation + webhook verification + idempotency.
|
|
202
203
|
|
|
203
204
|
## Build
|
|
204
205
|
|
|
@@ -213,7 +214,7 @@ pnpm build # tsup → dist/ (ESM + .d.ts)
|
|
|
213
214
|
`verifyWebhookSignature(rawBody, sigHeader, secret, { timestamp: tsHeader })`. A 3-argument call now
|
|
214
215
|
returns `false`. The signed content changed from `body` to `` `${timestamp}.${body}` ``.
|
|
215
216
|
- Deduplicate on the signed `event.id`, not the `X-Webhook-Id` header.
|
|
216
|
-
- Removed
|
|
217
|
+
- Removed 안1 methods (`payWithAuthorization`, `submitExternalPayment`) and escrow refund helpers —
|
|
217
218
|
checkout is non-custodial deposit-address (see Usage above).
|
|
218
219
|
|
|
219
220
|
## 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,11 @@ 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 = { ...params, line_items: params.line_items.map(validateLineItem) };
|
|
178
218
|
const res = await this.request(
|
|
179
219
|
"POST",
|
|
180
220
|
"/v1/checkout/sessions",
|
|
181
|
-
|
|
221
|
+
body
|
|
182
222
|
);
|
|
183
223
|
if (res.data) {
|
|
184
224
|
if (res.data.checkout_url) {
|
|
@@ -208,7 +248,7 @@ var BitPalCheckoutClient = class {
|
|
|
208
248
|
`/v1/checkout/sessions${query ? `?${query}` : ""}`
|
|
209
249
|
);
|
|
210
250
|
}
|
|
211
|
-
/* ─── 비수탁 결제 (deposit-address) ─── */
|
|
251
|
+
/* ─── 안2 비수탁 결제 (deposit-address) ─── */
|
|
212
252
|
/**
|
|
213
253
|
* 공개 세션 조회 (`GET /v1/checkout/sessions/:id/public`) — 인증 불요.
|
|
214
254
|
*
|
|
@@ -224,7 +264,7 @@ var BitPalCheckoutClient = class {
|
|
|
224
264
|
/**
|
|
225
265
|
* 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
|
|
226
266
|
*
|
|
227
|
-
*
|
|
267
|
+
* 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
|
|
228
268
|
* 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
|
|
229
269
|
* 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
|
|
230
270
|
*
|
|
@@ -329,26 +369,40 @@ function parseWebhookEvent(body) {
|
|
|
329
369
|
}
|
|
330
370
|
|
|
331
371
|
// src/amount.ts
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
372
|
+
function toAtomic(decimal, decimals) {
|
|
373
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
|
|
374
|
+
throw new Error(`toAtomic: invalid decimals '${decimals}'`);
|
|
375
|
+
}
|
|
376
|
+
const fracPattern = decimals > 0 ? `(\\.\\d{1,${decimals}})?` : "";
|
|
377
|
+
if (typeof decimal !== "string" || !new RegExp(`^\\d+${fracPattern}$`).test(decimal)) {
|
|
378
|
+
throw new Error(`toAtomic: invalid decimal '${decimal}' (max ${decimals} fractional digits, no negatives)`);
|
|
337
379
|
}
|
|
380
|
+
const scale = 10n ** BigInt(decimals);
|
|
338
381
|
const [whole = "0", frac = ""] = decimal.split(".");
|
|
339
|
-
const fracPadded = frac.padEnd(
|
|
340
|
-
return (BigInt(whole) *
|
|
382
|
+
const fracPadded = frac.padEnd(decimals, "0");
|
|
383
|
+
return (BigInt(whole) * scale + BigInt(fracPadded || "0")).toString();
|
|
341
384
|
}
|
|
342
|
-
function
|
|
385
|
+
function fromAtomic(atomic, decimals) {
|
|
386
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
|
|
387
|
+
throw new Error(`fromAtomic: invalid decimals '${decimals}'`);
|
|
388
|
+
}
|
|
343
389
|
if (typeof atomic !== "string" || !/^\d+$/.test(atomic)) {
|
|
344
|
-
throw new Error(`
|
|
390
|
+
throw new Error(`fromAtomic: invalid atomic '${atomic}' (must be non-negative integer string)`);
|
|
345
391
|
}
|
|
392
|
+
const scale = 10n ** BigInt(decimals);
|
|
346
393
|
const big = BigInt(atomic);
|
|
347
|
-
const whole = big /
|
|
348
|
-
const frac = big %
|
|
349
|
-
|
|
394
|
+
const whole = big / scale;
|
|
395
|
+
const frac = big % scale;
|
|
396
|
+
if (decimals === 0) return `${whole}`;
|
|
397
|
+
const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
350
398
|
return fracStr.length > 0 ? `${whole}.${fracStr.padEnd(2, "0")}` : `${whole}.00`;
|
|
351
399
|
}
|
|
400
|
+
function toAtomicUSDC(decimal) {
|
|
401
|
+
return toAtomic(decimal, 6);
|
|
402
|
+
}
|
|
403
|
+
function fromAtomicUSDC(atomic) {
|
|
404
|
+
return fromAtomic(atomic, 6);
|
|
405
|
+
}
|
|
352
406
|
|
|
353
407
|
// src/index.ts
|
|
354
408
|
var BitPal = class {
|
|
@@ -364,11 +418,14 @@ var BitPal = class {
|
|
|
364
418
|
BitPalCheckoutClient,
|
|
365
419
|
BitPalError,
|
|
366
420
|
CHECKOUT_WEBHOOK_EVENTS,
|
|
421
|
+
PaymentLinksResource,
|
|
367
422
|
PricesResource,
|
|
368
423
|
ProductsResource,
|
|
369
424
|
WEBHOOK_HEADERS,
|
|
425
|
+
fromAtomic,
|
|
370
426
|
fromAtomicUSDC,
|
|
371
427
|
parseWebhookEvent,
|
|
428
|
+
toAtomic,
|
|
372
429
|
toAtomicUSDC,
|
|
373
430
|
verifyWebhookSignature
|
|
374
431
|
});
|
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
|
/** 고객 지갑 주소 (선택) */
|
|
@@ -83,7 +96,7 @@ interface CheckoutSession {
|
|
|
83
96
|
settled_at: string | null;
|
|
84
97
|
tx_hash: string | null;
|
|
85
98
|
created_at: string;
|
|
86
|
-
/** 'hosted' — 결제 경로. hosted deposit-address 단일 경로. */
|
|
99
|
+
/** 'hosted' — 결제 경로. 안2 는 hosted deposit-address 단일 경로. */
|
|
87
100
|
payment_source?: string;
|
|
88
101
|
/** 호스티드 결제 페이지 URL (token 쿼리 포함). backend 응답 그대로 사용 — buyer를 이 URL로 redirect. */
|
|
89
102
|
checkout_url?: string;
|
|
@@ -93,15 +106,19 @@ interface CheckoutSession {
|
|
|
93
106
|
url: string;
|
|
94
107
|
}
|
|
95
108
|
/**
|
|
96
|
-
* 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
|
|
109
|
+
* 안2 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
|
|
97
110
|
* `issueDepositAddress({ chain, token })` 로 입금주소를 발급받는다. 단일-자산 세션은 1개.
|
|
98
111
|
*/
|
|
99
112
|
interface PaymentOption {
|
|
100
|
-
/** CAIP-2 (예: eip155:8453) */
|
|
113
|
+
/** CAIP-2 (예: eip155:8453, tron:0x2b6653dc, solana:5eykt4…). 체인 식별의 canonical. */
|
|
101
114
|
caip2: string;
|
|
102
|
-
|
|
115
|
+
/** EVM numeric chainId. 비-EVM(Tron/Solana)은 null — 식별은 caip2 사용. */
|
|
116
|
+
chain_id: number | null;
|
|
103
117
|
token_symbol: string;
|
|
118
|
+
/** 토큰 컨트랙트/mint 주소(EVM 0x / Tron base58 / Solana base58 mint). */
|
|
104
119
|
token_address: string;
|
|
120
|
+
/** 토큰 decimals(USDC/USDT=6, BSC-USD=18). atomic↔decimal 변환에 필수 — 6dp 가정 금지. */
|
|
121
|
+
decimals: number;
|
|
105
122
|
}
|
|
106
123
|
/**
|
|
107
124
|
* `getPublicSession()` (`GET /v1/checkout/sessions/:id/public`) 응답 — 인증 불요(공개).
|
|
@@ -124,6 +141,8 @@ interface PublicSession {
|
|
|
124
141
|
payment_options: PaymentOption[];
|
|
125
142
|
chain_id: number | null;
|
|
126
143
|
token_address: string | null;
|
|
144
|
+
/** 선택된 pay 토큰 decimals(발급 후). amount_total/amount_received atomic 해석에 사용 — 6dp 가정 금지. */
|
|
145
|
+
token_decimals?: number | null;
|
|
127
146
|
tx_hash: string | null;
|
|
128
147
|
release_tx_hash?: string | null;
|
|
129
148
|
expires_at: string | null;
|
|
@@ -145,7 +164,11 @@ interface PublicSession {
|
|
|
145
164
|
interface IssueDepositAddressParams {
|
|
146
165
|
/** 결제 페이지 접근 증명(HMAC). createSession 응답의 session_token. */
|
|
147
166
|
session_token: string;
|
|
148
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* 환불 주소. 필수. 선택한 체인의 VM 형식이어야 한다:
|
|
169
|
+
* EVM=0x…, Tron=base58(T…), Solana=base58. 결제와 같은 네트워크의 본인 주소.
|
|
170
|
+
* 금액부족/잘못된토큰/만료 시 이 주소로 환불되며, 입금주소에 커밋되어 발급 후 변경 불가.
|
|
171
|
+
*/
|
|
149
172
|
refundAddress: string;
|
|
150
173
|
/** 선택 자산의 chain(CAIP-2 또는 short-name). 멀티옵션 세션 필수. */
|
|
151
174
|
chain?: string;
|
|
@@ -158,7 +181,7 @@ interface IssueDepositAddressParams {
|
|
|
158
181
|
interface DepositAddress {
|
|
159
182
|
sessionId: string;
|
|
160
183
|
chain: string;
|
|
161
|
-
/** 주문별 CREATE2 forwarder
|
|
184
|
+
/** 주문별 결정적 입금주소(EVM/Tron=CREATE2 forwarder, Solana=order PDA 의 deposit ATA). */
|
|
162
185
|
depositAddress: string;
|
|
163
186
|
expectedToken: string;
|
|
164
187
|
/** Atomic 정수 문자열. */
|
|
@@ -248,9 +271,17 @@ interface UpdateProductParams {
|
|
|
248
271
|
metadata?: Record<string, unknown>;
|
|
249
272
|
status?: 'active' | 'inactive';
|
|
250
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* 가격 생성 입력. 금액은 둘 중 **하나** (line item 과 동일 모델):
|
|
276
|
+
* - `amount`(권장): 사람 표기 USD 문자열(예 "29", "0.5"). decimal 신경 쓸 필요 없음.
|
|
277
|
+
* - `amount_atomic`: raw μUSD 정수($1="1000000") — 고급/기존.
|
|
278
|
+
* 둘 다 지정하거나 둘 다 생략하면 서버가 400 을 반환한다.
|
|
279
|
+
*/
|
|
251
280
|
interface CreatePriceParams {
|
|
252
|
-
/**
|
|
253
|
-
|
|
281
|
+
/** 사람 표기 USD 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
|
|
282
|
+
amount?: string;
|
|
283
|
+
/** raw μUSD 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
|
|
284
|
+
amount_atomic?: string;
|
|
254
285
|
currency?: string;
|
|
255
286
|
token_symbol?: string;
|
|
256
287
|
billing_type?: 'one_time';
|
|
@@ -274,6 +305,80 @@ interface ListPricesParams {
|
|
|
274
305
|
limit?: number;
|
|
275
306
|
offset?: number;
|
|
276
307
|
}
|
|
308
|
+
interface CheckoutPaymentLink {
|
|
309
|
+
/** plink_... */
|
|
310
|
+
id: string;
|
|
311
|
+
/** 고엔트로피 공개 토큰 — 호스티드 URL `{checkoutBase}/plink/{share_token}` 에 사용 */
|
|
312
|
+
share_token: string;
|
|
313
|
+
/** price_... — 이 링크가 파는 가격(생성 후 고정) */
|
|
314
|
+
price_id: string;
|
|
315
|
+
title: string | null;
|
|
316
|
+
description: string | null;
|
|
317
|
+
collect_email: boolean;
|
|
318
|
+
email_required: boolean;
|
|
319
|
+
/** CAIP-2 목록 또는 null(legacy) */
|
|
320
|
+
allowed_pay_chains: string[] | null;
|
|
321
|
+
/** chain×token cell 목록 또는 null */
|
|
322
|
+
allowed_assets: Array<{
|
|
323
|
+
caip2: string;
|
|
324
|
+
token: string;
|
|
325
|
+
}> | null;
|
|
326
|
+
success_url: string | null;
|
|
327
|
+
cancel_url: string | null;
|
|
328
|
+
/** 'active' | 'inactive' */
|
|
329
|
+
status: string;
|
|
330
|
+
max_uses: number | null;
|
|
331
|
+
paid_use_count: number;
|
|
332
|
+
expires_at: string | null;
|
|
333
|
+
mode: string;
|
|
334
|
+
created_at: string;
|
|
335
|
+
updated_at: string;
|
|
336
|
+
}
|
|
337
|
+
interface CreatePaymentLinkParams {
|
|
338
|
+
/** price_... — bitpal.checkout.prices.create() 로 만든 가격. 필수. */
|
|
339
|
+
price_id: string;
|
|
340
|
+
title?: string;
|
|
341
|
+
description?: string;
|
|
342
|
+
/** buyer 이메일 수집(기본 true) */
|
|
343
|
+
collect_email?: boolean;
|
|
344
|
+
/** buyer 이메일 필수(기본 true). true 면 collect_email 도 true 여야 함. */
|
|
345
|
+
email_required?: boolean;
|
|
346
|
+
/** 허용 결제 체인(CAIP-2 또는 short-name). allowed_assets 가 있으면 그쪽 우선. */
|
|
347
|
+
allowed_pay_chains?: string[];
|
|
348
|
+
/** chain×token cell 허용 목록 — allowed_pay_chains 보다 우선. */
|
|
349
|
+
allowed_assets?: Array<{
|
|
350
|
+
chain: string;
|
|
351
|
+
token: string;
|
|
352
|
+
}>;
|
|
353
|
+
success_url?: string;
|
|
354
|
+
cancel_url?: string;
|
|
355
|
+
max_uses?: number;
|
|
356
|
+
/** ISO-8601 datetime */
|
|
357
|
+
expires_at?: string;
|
|
358
|
+
metadata?: Record<string, unknown>;
|
|
359
|
+
}
|
|
360
|
+
interface UpdatePaymentLinkParams {
|
|
361
|
+
title?: string | null;
|
|
362
|
+
description?: string | null;
|
|
363
|
+
collect_email?: boolean;
|
|
364
|
+
email_required?: boolean;
|
|
365
|
+
allowed_pay_chains?: string[] | null;
|
|
366
|
+
allowed_assets?: Array<{
|
|
367
|
+
chain: string;
|
|
368
|
+
token: string;
|
|
369
|
+
}> | null;
|
|
370
|
+
success_url?: string | null;
|
|
371
|
+
cancel_url?: string | null;
|
|
372
|
+
status?: 'active' | 'inactive';
|
|
373
|
+
max_uses?: number | null;
|
|
374
|
+
expires_at?: string | null;
|
|
375
|
+
metadata?: Record<string, unknown>;
|
|
376
|
+
}
|
|
377
|
+
interface ListPaymentLinksParams {
|
|
378
|
+
status?: 'active' | 'inactive';
|
|
379
|
+
limit?: number;
|
|
380
|
+
offset?: number;
|
|
381
|
+
}
|
|
277
382
|
|
|
278
383
|
/**
|
|
279
384
|
* catalog.ts — Product / immutable Price 관리 리소스 (PROD-LINK-3a-SDK)
|
|
@@ -325,6 +430,33 @@ declare class PricesResource {
|
|
|
325
430
|
*/
|
|
326
431
|
update(priceId: string, params: UpdatePriceParams): Promise<ApiResponse<CheckoutPrice>>;
|
|
327
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Payment Link 리소스 — 대시보드 없이 코드로 결제 링크 생성/관리 (PROD-LINK-4).
|
|
435
|
+
*
|
|
436
|
+
* 흐름: product 생성 → price 생성 → payment link 생성. link↔price binding 은 생성 후 고정.
|
|
437
|
+
*
|
|
438
|
+
* ```ts
|
|
439
|
+
* const { data: product } = await bitpal.checkout.products.create({ name: 'Pro Plan' });
|
|
440
|
+
* const { data: price } = await bitpal.checkout.prices.create(product.id, { amount: '29' });
|
|
441
|
+
* const { data: link } = await bitpal.checkout.paymentLinks.create({
|
|
442
|
+
* price_id: price.id,
|
|
443
|
+
* allowed_pay_chains: ['eip155:8453', 'solana:5eykt4Us…'],
|
|
444
|
+
* });
|
|
445
|
+
* // 호스티드 결제 페이지 URL: `{checkoutBase}/plink/${link.share_token}`
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
declare class PaymentLinksResource {
|
|
449
|
+
private readonly request;
|
|
450
|
+
constructor(request: CatalogRequester);
|
|
451
|
+
/** POST /v1/checkout/payment-links */
|
|
452
|
+
create(params: CreatePaymentLinkParams): Promise<ApiResponse<CheckoutPaymentLink>>;
|
|
453
|
+
/** GET /v1/checkout/payment-links */
|
|
454
|
+
list(params?: ListPaymentLinksParams): Promise<ListResponse<CheckoutPaymentLink>>;
|
|
455
|
+
/** GET /v1/checkout/payment-links/:id */
|
|
456
|
+
retrieve(linkId: string): Promise<ApiResponse<CheckoutPaymentLink>>;
|
|
457
|
+
/** PATCH /v1/checkout/payment-links/:id */
|
|
458
|
+
update(linkId: string, params: UpdatePaymentLinkParams): Promise<ApiResponse<CheckoutPaymentLink>>;
|
|
459
|
+
}
|
|
328
460
|
|
|
329
461
|
/**
|
|
330
462
|
* @bitpal/checkout SDK — 클라이언트
|
|
@@ -342,25 +474,29 @@ declare class BitPalCheckoutClient {
|
|
|
342
474
|
readonly products: ProductsResource;
|
|
343
475
|
/** PROD-LINK-3a-SDK: immutable Price 관리 — 가격 변경은 비활성화 + 새 price 생성 */
|
|
344
476
|
readonly prices: PricesResource;
|
|
477
|
+
/** PROD-LINK-4-SDK: Payment Link 관리 — 대시보드 없이 코드로 결제 링크 생성 (server-side 전용) */
|
|
478
|
+
readonly paymentLinks: PaymentLinksResource;
|
|
345
479
|
constructor(config: BitPalConfig);
|
|
346
480
|
private request;
|
|
347
481
|
/**
|
|
348
482
|
* 체크아웃 세션 생성.
|
|
349
483
|
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
484
|
+
* `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
|
|
485
|
+
* 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
352
486
|
*
|
|
353
487
|
* @example
|
|
354
|
-
* import { BitPal
|
|
488
|
+
* import { BitPal } from '@bitpal/checkout';
|
|
355
489
|
* const bp = new BitPal('bp_test_...');
|
|
356
490
|
* const { data } = await bp.checkout.createSession({
|
|
357
|
-
*
|
|
491
|
+
* // amount: 사람 표기 USD("29", "0.5"). 금액은 USD 가격이라 체인/토큰 decimal 무관.
|
|
492
|
+
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
358
493
|
* });
|
|
359
494
|
* console.log(data.url); // → buyer를 redirect할 결제 페이지
|
|
360
495
|
*
|
|
361
496
|
* // 멀티체인 허용 — 구매자가 Base/Optimism 중 선택(하나뿐이면 자동 선택):
|
|
497
|
+
* // 어떤 체인·토큰이든 amount 그대로 — 서버가 발급 시 토큰 decimals 로 재스케일.
|
|
362
498
|
* await bp.checkout.createSession({
|
|
363
|
-
* line_items: [{ name: 'Pro', amount:
|
|
499
|
+
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
364
500
|
* allowed_pay_chains: ['eip155:84532', 'eip155:11155420'],
|
|
365
501
|
* });
|
|
366
502
|
* // 단일 체인 고정은 pay_chain 사용(둘은 상호 배타).
|
|
@@ -384,7 +520,7 @@ declare class BitPalCheckoutClient {
|
|
|
384
520
|
/**
|
|
385
521
|
* 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
|
|
386
522
|
*
|
|
387
|
-
*
|
|
523
|
+
* 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
|
|
388
524
|
* 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
|
|
389
525
|
* 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
|
|
390
526
|
*
|
|
@@ -503,35 +639,41 @@ declare function verifyWebhookSignature(payload: string, signature: string, secr
|
|
|
503
639
|
declare function parseWebhookEvent(body: string): WebhookPayload;
|
|
504
640
|
|
|
505
641
|
/**
|
|
506
|
-
* amount.ts — atomic
|
|
642
|
+
* amount.ts — atomic ↔ decimal 변환 헬퍼 (임의 decimals + USDC 하위호환)
|
|
507
643
|
*
|
|
508
|
-
* BitPal API는 모든 amount를 atomic 정수 문자열로
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
* $0.5 → "500000"
|
|
644
|
+
* BitPal API는 모든 amount를 atomic 정수 문자열로 받는다. decimals 는 토큰마다 다르다:
|
|
645
|
+
* USDC/USDT(Base/Arb/Tron/Solana) = 6, BSC-USD(BNB USDT) = 18. → payment_options[].decimals /
|
|
646
|
+
* PublicSession.token_decimals 로 서버가 내려준다. 6dp 를 하드코딩하면 non-6dp 토큰에서 금액이 틀린다.
|
|
512
647
|
*
|
|
513
648
|
* 정밀도 손실 방지를 위해 BigInt 연산만 사용한다 (Number 변환 금지 — CLAUDE.md 정책).
|
|
649
|
+
* $1(6dp) → "1000000" $1(18dp) → "1000000000000000000"
|
|
514
650
|
*/
|
|
515
651
|
/**
|
|
516
|
-
* decimal 문자열 → atomic
|
|
517
|
-
*
|
|
518
|
-
* @example
|
|
519
|
-
* toAtomicUSDC('29.00') // → '29000000'
|
|
520
|
-
* toAtomicUSDC('0.5') // → '500000'
|
|
521
|
-
* toAtomicUSDC('1000') // → '1000000000'
|
|
652
|
+
* decimal 문자열 → atomic 문자열 (임의 decimals).
|
|
522
653
|
*
|
|
523
|
-
* @
|
|
654
|
+
* @param decimal 소수 문자열(음수 불가, 소수 자리 ≤ decimals)
|
|
655
|
+
* @param decimals 토큰 decimals (예: 6, 18). payment_options[].decimals 사용.
|
|
656
|
+
* @throws {Error} 음수, decimals 초과 소수 자리, 형식 오류
|
|
657
|
+
* @example toAtomic('29.00', 6) // '29000000' toAtomic('1.5', 18) // '1500000000000000000'
|
|
524
658
|
*/
|
|
525
|
-
declare function
|
|
659
|
+
declare function toAtomic(decimal: string, decimals: number): string;
|
|
526
660
|
/**
|
|
527
|
-
* atomic
|
|
528
|
-
*
|
|
529
|
-
* @example
|
|
530
|
-
* fromAtomicUSDC('29000000') // → '29.00'
|
|
531
|
-
* fromAtomicUSDC('500000') // → '0.50'
|
|
532
|
-
* fromAtomicUSDC('1500000') // → '1.50'
|
|
661
|
+
* atomic 문자열 → 사람이 읽기 좋은 decimal 문자열 (임의 decimals).
|
|
662
|
+
* 최소 2자리 소수를 유지하되(예: '29.00'), decimals 만큼만 노출.
|
|
533
663
|
*
|
|
534
664
|
* @throws {Error} 정수 문자열이 아니면
|
|
665
|
+
* @example fromAtomic('29000000', 6) // '29.00' fromAtomic('1500000000000000000', 18) // '1.50'
|
|
666
|
+
*/
|
|
667
|
+
declare function fromAtomic(atomic: string, decimals: number): string;
|
|
668
|
+
/**
|
|
669
|
+
* decimal → atomic μUSDC (6dp). 하위호환 별칭 — 새 코드는 `toAtomic(decimal, decimals)` 권장
|
|
670
|
+
* (BNB USDT=18dp 등 non-6dp 토큰은 이 헬퍼로 처리 불가).
|
|
671
|
+
* @example toAtomicUSDC('29.00') // '29000000'
|
|
672
|
+
*/
|
|
673
|
+
declare function toAtomicUSDC(decimal: string): string;
|
|
674
|
+
/**
|
|
675
|
+
* atomic μUSDC(6dp) → decimal. 하위호환 별칭 — 새 코드는 `fromAtomic(atomic, decimals)` 권장.
|
|
676
|
+
* @example fromAtomicUSDC('29000000') // '29.00'
|
|
535
677
|
*/
|
|
536
678
|
declare function fromAtomicUSDC(atomic: string): string;
|
|
537
679
|
|
|
@@ -547,8 +689,8 @@ declare function fromAtomicUSDC(atomic: string): string;
|
|
|
547
689
|
* const bitpal = new BitPal({ apiKey: 'bp_test_...' });
|
|
548
690
|
*
|
|
549
691
|
* const { data: session } = await bitpal.checkout.createSession({
|
|
550
|
-
* // amount
|
|
551
|
-
* line_items: [{ name: 'Pro Plan', amount:
|
|
692
|
+
* // amount: 사람 표기 USD("29","0.5"). decimal 계산 불필요 — 체인/토큰 decimal 무관.
|
|
693
|
+
* line_items: [{ name: 'Pro Plan', amount: '29', currency: 'USDC' }],
|
|
552
694
|
* });
|
|
553
695
|
*
|
|
554
696
|
* console.log(session.url); // 호스티드 결제 페이지 URL (checkout_url 별칭)
|
|
@@ -563,4 +705,4 @@ declare class BitPal {
|
|
|
563
705
|
constructor(config: BitPalConfig | string);
|
|
564
706
|
}
|
|
565
707
|
|
|
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 };
|
|
708
|
+
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
|
/** 고객 지갑 주소 (선택) */
|
|
@@ -83,7 +96,7 @@ interface CheckoutSession {
|
|
|
83
96
|
settled_at: string | null;
|
|
84
97
|
tx_hash: string | null;
|
|
85
98
|
created_at: string;
|
|
86
|
-
/** 'hosted' — 결제 경로. hosted deposit-address 단일 경로. */
|
|
99
|
+
/** 'hosted' — 결제 경로. 안2 는 hosted deposit-address 단일 경로. */
|
|
87
100
|
payment_source?: string;
|
|
88
101
|
/** 호스티드 결제 페이지 URL (token 쿼리 포함). backend 응답 그대로 사용 — buyer를 이 URL로 redirect. */
|
|
89
102
|
checkout_url?: string;
|
|
@@ -93,15 +106,19 @@ interface CheckoutSession {
|
|
|
93
106
|
url: string;
|
|
94
107
|
}
|
|
95
108
|
/**
|
|
96
|
-
* 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
|
|
109
|
+
* 안2 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
|
|
97
110
|
* `issueDepositAddress({ chain, token })` 로 입금주소를 발급받는다. 단일-자산 세션은 1개.
|
|
98
111
|
*/
|
|
99
112
|
interface PaymentOption {
|
|
100
|
-
/** CAIP-2 (예: eip155:8453) */
|
|
113
|
+
/** CAIP-2 (예: eip155:8453, tron:0x2b6653dc, solana:5eykt4…). 체인 식별의 canonical. */
|
|
101
114
|
caip2: string;
|
|
102
|
-
|
|
115
|
+
/** EVM numeric chainId. 비-EVM(Tron/Solana)은 null — 식별은 caip2 사용. */
|
|
116
|
+
chain_id: number | null;
|
|
103
117
|
token_symbol: string;
|
|
118
|
+
/** 토큰 컨트랙트/mint 주소(EVM 0x / Tron base58 / Solana base58 mint). */
|
|
104
119
|
token_address: string;
|
|
120
|
+
/** 토큰 decimals(USDC/USDT=6, BSC-USD=18). atomic↔decimal 변환에 필수 — 6dp 가정 금지. */
|
|
121
|
+
decimals: number;
|
|
105
122
|
}
|
|
106
123
|
/**
|
|
107
124
|
* `getPublicSession()` (`GET /v1/checkout/sessions/:id/public`) 응답 — 인증 불요(공개).
|
|
@@ -124,6 +141,8 @@ interface PublicSession {
|
|
|
124
141
|
payment_options: PaymentOption[];
|
|
125
142
|
chain_id: number | null;
|
|
126
143
|
token_address: string | null;
|
|
144
|
+
/** 선택된 pay 토큰 decimals(발급 후). amount_total/amount_received atomic 해석에 사용 — 6dp 가정 금지. */
|
|
145
|
+
token_decimals?: number | null;
|
|
127
146
|
tx_hash: string | null;
|
|
128
147
|
release_tx_hash?: string | null;
|
|
129
148
|
expires_at: string | null;
|
|
@@ -145,7 +164,11 @@ interface PublicSession {
|
|
|
145
164
|
interface IssueDepositAddressParams {
|
|
146
165
|
/** 결제 페이지 접근 증명(HMAC). createSession 응답의 session_token. */
|
|
147
166
|
session_token: string;
|
|
148
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* 환불 주소. 필수. 선택한 체인의 VM 형식이어야 한다:
|
|
169
|
+
* EVM=0x…, Tron=base58(T…), Solana=base58. 결제와 같은 네트워크의 본인 주소.
|
|
170
|
+
* 금액부족/잘못된토큰/만료 시 이 주소로 환불되며, 입금주소에 커밋되어 발급 후 변경 불가.
|
|
171
|
+
*/
|
|
149
172
|
refundAddress: string;
|
|
150
173
|
/** 선택 자산의 chain(CAIP-2 또는 short-name). 멀티옵션 세션 필수. */
|
|
151
174
|
chain?: string;
|
|
@@ -158,7 +181,7 @@ interface IssueDepositAddressParams {
|
|
|
158
181
|
interface DepositAddress {
|
|
159
182
|
sessionId: string;
|
|
160
183
|
chain: string;
|
|
161
|
-
/** 주문별 CREATE2 forwarder
|
|
184
|
+
/** 주문별 결정적 입금주소(EVM/Tron=CREATE2 forwarder, Solana=order PDA 의 deposit ATA). */
|
|
162
185
|
depositAddress: string;
|
|
163
186
|
expectedToken: string;
|
|
164
187
|
/** Atomic 정수 문자열. */
|
|
@@ -248,9 +271,17 @@ interface UpdateProductParams {
|
|
|
248
271
|
metadata?: Record<string, unknown>;
|
|
249
272
|
status?: 'active' | 'inactive';
|
|
250
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* 가격 생성 입력. 금액은 둘 중 **하나** (line item 과 동일 모델):
|
|
276
|
+
* - `amount`(권장): 사람 표기 USD 문자열(예 "29", "0.5"). decimal 신경 쓸 필요 없음.
|
|
277
|
+
* - `amount_atomic`: raw μUSD 정수($1="1000000") — 고급/기존.
|
|
278
|
+
* 둘 다 지정하거나 둘 다 생략하면 서버가 400 을 반환한다.
|
|
279
|
+
*/
|
|
251
280
|
interface CreatePriceParams {
|
|
252
|
-
/**
|
|
253
|
-
|
|
281
|
+
/** 사람 표기 USD 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
|
|
282
|
+
amount?: string;
|
|
283
|
+
/** raw μUSD 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
|
|
284
|
+
amount_atomic?: string;
|
|
254
285
|
currency?: string;
|
|
255
286
|
token_symbol?: string;
|
|
256
287
|
billing_type?: 'one_time';
|
|
@@ -274,6 +305,80 @@ interface ListPricesParams {
|
|
|
274
305
|
limit?: number;
|
|
275
306
|
offset?: number;
|
|
276
307
|
}
|
|
308
|
+
interface CheckoutPaymentLink {
|
|
309
|
+
/** plink_... */
|
|
310
|
+
id: string;
|
|
311
|
+
/** 고엔트로피 공개 토큰 — 호스티드 URL `{checkoutBase}/plink/{share_token}` 에 사용 */
|
|
312
|
+
share_token: string;
|
|
313
|
+
/** price_... — 이 링크가 파는 가격(생성 후 고정) */
|
|
314
|
+
price_id: string;
|
|
315
|
+
title: string | null;
|
|
316
|
+
description: string | null;
|
|
317
|
+
collect_email: boolean;
|
|
318
|
+
email_required: boolean;
|
|
319
|
+
/** CAIP-2 목록 또는 null(legacy) */
|
|
320
|
+
allowed_pay_chains: string[] | null;
|
|
321
|
+
/** chain×token cell 목록 또는 null */
|
|
322
|
+
allowed_assets: Array<{
|
|
323
|
+
caip2: string;
|
|
324
|
+
token: string;
|
|
325
|
+
}> | null;
|
|
326
|
+
success_url: string | null;
|
|
327
|
+
cancel_url: string | null;
|
|
328
|
+
/** 'active' | 'inactive' */
|
|
329
|
+
status: string;
|
|
330
|
+
max_uses: number | null;
|
|
331
|
+
paid_use_count: number;
|
|
332
|
+
expires_at: string | null;
|
|
333
|
+
mode: string;
|
|
334
|
+
created_at: string;
|
|
335
|
+
updated_at: string;
|
|
336
|
+
}
|
|
337
|
+
interface CreatePaymentLinkParams {
|
|
338
|
+
/** price_... — bitpal.checkout.prices.create() 로 만든 가격. 필수. */
|
|
339
|
+
price_id: string;
|
|
340
|
+
title?: string;
|
|
341
|
+
description?: string;
|
|
342
|
+
/** buyer 이메일 수집(기본 true) */
|
|
343
|
+
collect_email?: boolean;
|
|
344
|
+
/** buyer 이메일 필수(기본 true). true 면 collect_email 도 true 여야 함. */
|
|
345
|
+
email_required?: boolean;
|
|
346
|
+
/** 허용 결제 체인(CAIP-2 또는 short-name). allowed_assets 가 있으면 그쪽 우선. */
|
|
347
|
+
allowed_pay_chains?: string[];
|
|
348
|
+
/** chain×token cell 허용 목록 — allowed_pay_chains 보다 우선. */
|
|
349
|
+
allowed_assets?: Array<{
|
|
350
|
+
chain: string;
|
|
351
|
+
token: string;
|
|
352
|
+
}>;
|
|
353
|
+
success_url?: string;
|
|
354
|
+
cancel_url?: string;
|
|
355
|
+
max_uses?: number;
|
|
356
|
+
/** ISO-8601 datetime */
|
|
357
|
+
expires_at?: string;
|
|
358
|
+
metadata?: Record<string, unknown>;
|
|
359
|
+
}
|
|
360
|
+
interface UpdatePaymentLinkParams {
|
|
361
|
+
title?: string | null;
|
|
362
|
+
description?: string | null;
|
|
363
|
+
collect_email?: boolean;
|
|
364
|
+
email_required?: boolean;
|
|
365
|
+
allowed_pay_chains?: string[] | null;
|
|
366
|
+
allowed_assets?: Array<{
|
|
367
|
+
chain: string;
|
|
368
|
+
token: string;
|
|
369
|
+
}> | null;
|
|
370
|
+
success_url?: string | null;
|
|
371
|
+
cancel_url?: string | null;
|
|
372
|
+
status?: 'active' | 'inactive';
|
|
373
|
+
max_uses?: number | null;
|
|
374
|
+
expires_at?: string | null;
|
|
375
|
+
metadata?: Record<string, unknown>;
|
|
376
|
+
}
|
|
377
|
+
interface ListPaymentLinksParams {
|
|
378
|
+
status?: 'active' | 'inactive';
|
|
379
|
+
limit?: number;
|
|
380
|
+
offset?: number;
|
|
381
|
+
}
|
|
277
382
|
|
|
278
383
|
/**
|
|
279
384
|
* catalog.ts — Product / immutable Price 관리 리소스 (PROD-LINK-3a-SDK)
|
|
@@ -325,6 +430,33 @@ declare class PricesResource {
|
|
|
325
430
|
*/
|
|
326
431
|
update(priceId: string, params: UpdatePriceParams): Promise<ApiResponse<CheckoutPrice>>;
|
|
327
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Payment Link 리소스 — 대시보드 없이 코드로 결제 링크 생성/관리 (PROD-LINK-4).
|
|
435
|
+
*
|
|
436
|
+
* 흐름: product 생성 → price 생성 → payment link 생성. link↔price binding 은 생성 후 고정.
|
|
437
|
+
*
|
|
438
|
+
* ```ts
|
|
439
|
+
* const { data: product } = await bitpal.checkout.products.create({ name: 'Pro Plan' });
|
|
440
|
+
* const { data: price } = await bitpal.checkout.prices.create(product.id, { amount: '29' });
|
|
441
|
+
* const { data: link } = await bitpal.checkout.paymentLinks.create({
|
|
442
|
+
* price_id: price.id,
|
|
443
|
+
* allowed_pay_chains: ['eip155:8453', 'solana:5eykt4Us…'],
|
|
444
|
+
* });
|
|
445
|
+
* // 호스티드 결제 페이지 URL: `{checkoutBase}/plink/${link.share_token}`
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
declare class PaymentLinksResource {
|
|
449
|
+
private readonly request;
|
|
450
|
+
constructor(request: CatalogRequester);
|
|
451
|
+
/** POST /v1/checkout/payment-links */
|
|
452
|
+
create(params: CreatePaymentLinkParams): Promise<ApiResponse<CheckoutPaymentLink>>;
|
|
453
|
+
/** GET /v1/checkout/payment-links */
|
|
454
|
+
list(params?: ListPaymentLinksParams): Promise<ListResponse<CheckoutPaymentLink>>;
|
|
455
|
+
/** GET /v1/checkout/payment-links/:id */
|
|
456
|
+
retrieve(linkId: string): Promise<ApiResponse<CheckoutPaymentLink>>;
|
|
457
|
+
/** PATCH /v1/checkout/payment-links/:id */
|
|
458
|
+
update(linkId: string, params: UpdatePaymentLinkParams): Promise<ApiResponse<CheckoutPaymentLink>>;
|
|
459
|
+
}
|
|
328
460
|
|
|
329
461
|
/**
|
|
330
462
|
* @bitpal/checkout SDK — 클라이언트
|
|
@@ -342,25 +474,29 @@ declare class BitPalCheckoutClient {
|
|
|
342
474
|
readonly products: ProductsResource;
|
|
343
475
|
/** PROD-LINK-3a-SDK: immutable Price 관리 — 가격 변경은 비활성화 + 새 price 생성 */
|
|
344
476
|
readonly prices: PricesResource;
|
|
477
|
+
/** PROD-LINK-4-SDK: Payment Link 관리 — 대시보드 없이 코드로 결제 링크 생성 (server-side 전용) */
|
|
478
|
+
readonly paymentLinks: PaymentLinksResource;
|
|
345
479
|
constructor(config: BitPalConfig);
|
|
346
480
|
private request;
|
|
347
481
|
/**
|
|
348
482
|
* 체크아웃 세션 생성.
|
|
349
483
|
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
484
|
+
* `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
|
|
485
|
+
* 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
352
486
|
*
|
|
353
487
|
* @example
|
|
354
|
-
* import { BitPal
|
|
488
|
+
* import { BitPal } from '@bitpal/checkout';
|
|
355
489
|
* const bp = new BitPal('bp_test_...');
|
|
356
490
|
* const { data } = await bp.checkout.createSession({
|
|
357
|
-
*
|
|
491
|
+
* // amount: 사람 표기 USD("29", "0.5"). 금액은 USD 가격이라 체인/토큰 decimal 무관.
|
|
492
|
+
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
358
493
|
* });
|
|
359
494
|
* console.log(data.url); // → buyer를 redirect할 결제 페이지
|
|
360
495
|
*
|
|
361
496
|
* // 멀티체인 허용 — 구매자가 Base/Optimism 중 선택(하나뿐이면 자동 선택):
|
|
497
|
+
* // 어떤 체인·토큰이든 amount 그대로 — 서버가 발급 시 토큰 decimals 로 재스케일.
|
|
362
498
|
* await bp.checkout.createSession({
|
|
363
|
-
* line_items: [{ name: 'Pro', amount:
|
|
499
|
+
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
364
500
|
* allowed_pay_chains: ['eip155:84532', 'eip155:11155420'],
|
|
365
501
|
* });
|
|
366
502
|
* // 단일 체인 고정은 pay_chain 사용(둘은 상호 배타).
|
|
@@ -384,7 +520,7 @@ declare class BitPalCheckoutClient {
|
|
|
384
520
|
/**
|
|
385
521
|
* 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
|
|
386
522
|
*
|
|
387
|
-
*
|
|
523
|
+
* 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
|
|
388
524
|
* 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
|
|
389
525
|
* 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
|
|
390
526
|
*
|
|
@@ -503,35 +639,41 @@ declare function verifyWebhookSignature(payload: string, signature: string, secr
|
|
|
503
639
|
declare function parseWebhookEvent(body: string): WebhookPayload;
|
|
504
640
|
|
|
505
641
|
/**
|
|
506
|
-
* amount.ts — atomic
|
|
642
|
+
* amount.ts — atomic ↔ decimal 변환 헬퍼 (임의 decimals + USDC 하위호환)
|
|
507
643
|
*
|
|
508
|
-
* BitPal API는 모든 amount를 atomic 정수 문자열로
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
* $0.5 → "500000"
|
|
644
|
+
* BitPal API는 모든 amount를 atomic 정수 문자열로 받는다. decimals 는 토큰마다 다르다:
|
|
645
|
+
* USDC/USDT(Base/Arb/Tron/Solana) = 6, BSC-USD(BNB USDT) = 18. → payment_options[].decimals /
|
|
646
|
+
* PublicSession.token_decimals 로 서버가 내려준다. 6dp 를 하드코딩하면 non-6dp 토큰에서 금액이 틀린다.
|
|
512
647
|
*
|
|
513
648
|
* 정밀도 손실 방지를 위해 BigInt 연산만 사용한다 (Number 변환 금지 — CLAUDE.md 정책).
|
|
649
|
+
* $1(6dp) → "1000000" $1(18dp) → "1000000000000000000"
|
|
514
650
|
*/
|
|
515
651
|
/**
|
|
516
|
-
* decimal 문자열 → atomic
|
|
517
|
-
*
|
|
518
|
-
* @example
|
|
519
|
-
* toAtomicUSDC('29.00') // → '29000000'
|
|
520
|
-
* toAtomicUSDC('0.5') // → '500000'
|
|
521
|
-
* toAtomicUSDC('1000') // → '1000000000'
|
|
652
|
+
* decimal 문자열 → atomic 문자열 (임의 decimals).
|
|
522
653
|
*
|
|
523
|
-
* @
|
|
654
|
+
* @param decimal 소수 문자열(음수 불가, 소수 자리 ≤ decimals)
|
|
655
|
+
* @param decimals 토큰 decimals (예: 6, 18). payment_options[].decimals 사용.
|
|
656
|
+
* @throws {Error} 음수, decimals 초과 소수 자리, 형식 오류
|
|
657
|
+
* @example toAtomic('29.00', 6) // '29000000' toAtomic('1.5', 18) // '1500000000000000000'
|
|
524
658
|
*/
|
|
525
|
-
declare function
|
|
659
|
+
declare function toAtomic(decimal: string, decimals: number): string;
|
|
526
660
|
/**
|
|
527
|
-
* atomic
|
|
528
|
-
*
|
|
529
|
-
* @example
|
|
530
|
-
* fromAtomicUSDC('29000000') // → '29.00'
|
|
531
|
-
* fromAtomicUSDC('500000') // → '0.50'
|
|
532
|
-
* fromAtomicUSDC('1500000') // → '1.50'
|
|
661
|
+
* atomic 문자열 → 사람이 읽기 좋은 decimal 문자열 (임의 decimals).
|
|
662
|
+
* 최소 2자리 소수를 유지하되(예: '29.00'), decimals 만큼만 노출.
|
|
533
663
|
*
|
|
534
664
|
* @throws {Error} 정수 문자열이 아니면
|
|
665
|
+
* @example fromAtomic('29000000', 6) // '29.00' fromAtomic('1500000000000000000', 18) // '1.50'
|
|
666
|
+
*/
|
|
667
|
+
declare function fromAtomic(atomic: string, decimals: number): string;
|
|
668
|
+
/**
|
|
669
|
+
* decimal → atomic μUSDC (6dp). 하위호환 별칭 — 새 코드는 `toAtomic(decimal, decimals)` 권장
|
|
670
|
+
* (BNB USDT=18dp 등 non-6dp 토큰은 이 헬퍼로 처리 불가).
|
|
671
|
+
* @example toAtomicUSDC('29.00') // '29000000'
|
|
672
|
+
*/
|
|
673
|
+
declare function toAtomicUSDC(decimal: string): string;
|
|
674
|
+
/**
|
|
675
|
+
* atomic μUSDC(6dp) → decimal. 하위호환 별칭 — 새 코드는 `fromAtomic(atomic, decimals)` 권장.
|
|
676
|
+
* @example fromAtomicUSDC('29000000') // '29.00'
|
|
535
677
|
*/
|
|
536
678
|
declare function fromAtomicUSDC(atomic: string): string;
|
|
537
679
|
|
|
@@ -547,8 +689,8 @@ declare function fromAtomicUSDC(atomic: string): string;
|
|
|
547
689
|
* const bitpal = new BitPal({ apiKey: 'bp_test_...' });
|
|
548
690
|
*
|
|
549
691
|
* const { data: session } = await bitpal.checkout.createSession({
|
|
550
|
-
* // amount
|
|
551
|
-
* line_items: [{ name: 'Pro Plan', amount:
|
|
692
|
+
* // amount: 사람 표기 USD("29","0.5"). decimal 계산 불필요 — 체인/토큰 decimal 무관.
|
|
693
|
+
* line_items: [{ name: 'Pro Plan', amount: '29', currency: 'USDC' }],
|
|
552
694
|
* });
|
|
553
695
|
*
|
|
554
696
|
* console.log(session.url); // 호스티드 결제 페이지 URL (checkout_url 별칭)
|
|
@@ -563,4 +705,4 @@ declare class BitPal {
|
|
|
563
705
|
constructor(config: BitPalConfig | string);
|
|
564
706
|
}
|
|
565
707
|
|
|
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 };
|
|
708
|
+
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,11 @@ 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 = { ...params, line_items: params.line_items.map(validateLineItem) };
|
|
132
169
|
const res = await this.request(
|
|
133
170
|
"POST",
|
|
134
171
|
"/v1/checkout/sessions",
|
|
135
|
-
|
|
172
|
+
body
|
|
136
173
|
);
|
|
137
174
|
if (res.data) {
|
|
138
175
|
if (res.data.checkout_url) {
|
|
@@ -162,7 +199,7 @@ var BitPalCheckoutClient = class {
|
|
|
162
199
|
`/v1/checkout/sessions${query ? `?${query}` : ""}`
|
|
163
200
|
);
|
|
164
201
|
}
|
|
165
|
-
/* ─── 비수탁 결제 (deposit-address) ─── */
|
|
202
|
+
/* ─── 안2 비수탁 결제 (deposit-address) ─── */
|
|
166
203
|
/**
|
|
167
204
|
* 공개 세션 조회 (`GET /v1/checkout/sessions/:id/public`) — 인증 불요.
|
|
168
205
|
*
|
|
@@ -178,7 +215,7 @@ var BitPalCheckoutClient = class {
|
|
|
178
215
|
/**
|
|
179
216
|
* 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
|
|
180
217
|
*
|
|
181
|
-
*
|
|
218
|
+
* 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
|
|
182
219
|
* 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
|
|
183
220
|
* 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
|
|
184
221
|
*
|
|
@@ -283,26 +320,40 @@ function parseWebhookEvent(body) {
|
|
|
283
320
|
}
|
|
284
321
|
|
|
285
322
|
// src/amount.ts
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
323
|
+
function toAtomic(decimal, decimals) {
|
|
324
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
|
|
325
|
+
throw new Error(`toAtomic: invalid decimals '${decimals}'`);
|
|
326
|
+
}
|
|
327
|
+
const fracPattern = decimals > 0 ? `(\\.\\d{1,${decimals}})?` : "";
|
|
328
|
+
if (typeof decimal !== "string" || !new RegExp(`^\\d+${fracPattern}$`).test(decimal)) {
|
|
329
|
+
throw new Error(`toAtomic: invalid decimal '${decimal}' (max ${decimals} fractional digits, no negatives)`);
|
|
291
330
|
}
|
|
331
|
+
const scale = 10n ** BigInt(decimals);
|
|
292
332
|
const [whole = "0", frac = ""] = decimal.split(".");
|
|
293
|
-
const fracPadded = frac.padEnd(
|
|
294
|
-
return (BigInt(whole) *
|
|
333
|
+
const fracPadded = frac.padEnd(decimals, "0");
|
|
334
|
+
return (BigInt(whole) * scale + BigInt(fracPadded || "0")).toString();
|
|
295
335
|
}
|
|
296
|
-
function
|
|
336
|
+
function fromAtomic(atomic, decimals) {
|
|
337
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
|
|
338
|
+
throw new Error(`fromAtomic: invalid decimals '${decimals}'`);
|
|
339
|
+
}
|
|
297
340
|
if (typeof atomic !== "string" || !/^\d+$/.test(atomic)) {
|
|
298
|
-
throw new Error(`
|
|
341
|
+
throw new Error(`fromAtomic: invalid atomic '${atomic}' (must be non-negative integer string)`);
|
|
299
342
|
}
|
|
343
|
+
const scale = 10n ** BigInt(decimals);
|
|
300
344
|
const big = BigInt(atomic);
|
|
301
|
-
const whole = big /
|
|
302
|
-
const frac = big %
|
|
303
|
-
|
|
345
|
+
const whole = big / scale;
|
|
346
|
+
const frac = big % scale;
|
|
347
|
+
if (decimals === 0) return `${whole}`;
|
|
348
|
+
const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
304
349
|
return fracStr.length > 0 ? `${whole}.${fracStr.padEnd(2, "0")}` : `${whole}.00`;
|
|
305
350
|
}
|
|
351
|
+
function toAtomicUSDC(decimal) {
|
|
352
|
+
return toAtomic(decimal, 6);
|
|
353
|
+
}
|
|
354
|
+
function fromAtomicUSDC(atomic) {
|
|
355
|
+
return fromAtomic(atomic, 6);
|
|
356
|
+
}
|
|
306
357
|
|
|
307
358
|
// src/index.ts
|
|
308
359
|
var BitPal = class {
|
|
@@ -317,11 +368,14 @@ export {
|
|
|
317
368
|
BitPalCheckoutClient,
|
|
318
369
|
BitPalError,
|
|
319
370
|
CHECKOUT_WEBHOOK_EVENTS,
|
|
371
|
+
PaymentLinksResource,
|
|
320
372
|
PricesResource,
|
|
321
373
|
ProductsResource,
|
|
322
374
|
WEBHOOK_HEADERS,
|
|
375
|
+
fromAtomic,
|
|
323
376
|
fromAtomicUSDC,
|
|
324
377
|
parseWebhookEvent,
|
|
378
|
+
toAtomic,
|
|
325
379
|
toAtomicUSDC,
|
|
326
380
|
verifyWebhookSignature
|
|
327
381
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitpal/checkout",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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": {
|