@bitpal/checkout 0.11.0 → 0.12.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/CHANGELOG.md +52 -0
- package/LICENSE +21 -21
- package/README.md +23 -11
- package/dist/index.cjs +62 -9
- package/dist/index.d.cts +77 -25
- package/dist/index.d.ts +77 -25
- package/dist/index.js +61 -9
- package/package.json +11 -11
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@bitpal/checkout`. Versions follow [semver](https://semver.org/) (pre-1.0: a minor bump may contain breaking changes, called out below).
|
|
4
|
+
|
|
5
|
+
Source of truth: the BitPal monorepo (`packages/checkout-sdk`). The public repo `superlabs-x/bitpal-checkout-sdk` mirrors it.
|
|
6
|
+
|
|
7
|
+
## 0.12.0 — 2026-11-06
|
|
8
|
+
|
|
9
|
+
Merges the x402 line (0.8.0–0.11.0, previously released from the standalone repo) with the checkout API alignment fixes made in the monorepo.
|
|
10
|
+
|
|
11
|
+
### Breaking (types)
|
|
12
|
+
- `CreatePriceParams.currency` removed. The API always rejected it — a Price's currency is derived from `token_symbol` (`'USDC' | 'USDT' | 'JPYC'`, default `'USDC'`). Code that passed `currency` got a `400`; pass `token_symbol` instead.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `PublicSession.pay_amount` — the exact amount the buyer must send, in the pay token's atomic units (`token_decimals`).
|
|
16
|
+
- `BitPalError` now carries structured fields parsed from the API error body: `code`, `apiMessage`, `retryable`, `traceId`. `error.message` is unchanged from 0.11 (it is still the body's `error` value), so existing `catch` code keeps working. Also exported: `parseApiErrorBody`, `BitPalErrorDetails`.
|
|
17
|
+
- `CreateSessionParams.expires_in_minutes` (1–60, default 15) — the session payment window.
|
|
18
|
+
- Non-JSON error responses (e.g. an HTML `502` from a gateway) now throw `BitPalError` with `HTTP <status>` instead of a `SyntaxError`.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Session expiry was silently ignored.** `expires_in_seconds` was never an API field, so every session used the 15-minute default. The SDK now sends `expires_in_minutes`; `expires_in_seconds` is deprecated and converted (rounded up to whole minutes, 60–3600 s). Out-of-range values throw before the request.
|
|
22
|
+
- `payer_wallet` and `pay_asset` on `createSession` are deprecated and no longer sent — the API never accepted them (it dropped them silently).
|
|
23
|
+
- A contract test checks every SDK request (method, path, query and body keys) against the API's generated OpenAPI spec, so SDK ↔ API drift like the two above fails CI.
|
|
24
|
+
|
|
25
|
+
### Fixed (docs / type comments)
|
|
26
|
+
- `amount_total` is **always canonical 6-decimal**, regardless of the pay token. Only `amount_received`, `pay_amount` and the issued `expectedAmount` use `token_decimals`. (0.11 said `amount_total` used token decimals — wrong for 18-decimal tokens.)
|
|
27
|
+
- Line item / Price `amount` is quoted in the line item's `currency` (`USDC`/`USDT` = USD, `JPYC` = JPY, no FX), not always USD.
|
|
28
|
+
- Browser use: only `getPublicSession` may be called from a merchant's browser (CORS). Call `issueDepositAddress` / `getStatus` through your backend.
|
|
29
|
+
- README: `previewFee` example now passes a human amount (`'29.00'`) — 0.11's README passed an atomic value, which the API reads as a human amount (1,000,000× too large).
|
|
30
|
+
- README: full payments after the deadline (up to 7 days) are accepted as late payments (`late: true` on `checkout.session.paid`), not refunded.
|
|
31
|
+
|
|
32
|
+
## 0.11.0 — 2026-09-17
|
|
33
|
+
- **Breaking (x402 payer):** standard x402 payload — one EIP-3009 authorization and one signature (`{ signature, authorization }`). Fee split is committed by the `payTo` forwarder address. Old two-signature headers get a fresh `402`.
|
|
34
|
+
- `paymentRequirements` carries the merchant wallet.
|
|
35
|
+
|
|
36
|
+
## 0.10.0 — 2026-09-17
|
|
37
|
+
- Webhook event `x402.payment.resolved` (an x402 payment left unresolved by `/settle` was later settled on-chain).
|
|
38
|
+
|
|
39
|
+
## 0.9.0 — 2026-09-17
|
|
40
|
+
- **Breaking:** `x402({ amount })` and `previewFee(amount)` take a human amount (`'1.00'`); use `amountAtomic` / `{ amountAtomic }` for raw values.
|
|
41
|
+
|
|
42
|
+
## 0.8.0 — 2026-09-16
|
|
43
|
+
- `x402()` per-call payment middleware, `createX402Payment()` payer helper, `X402ReplayStore` (memory default; pass a shared store when running several instances).
|
|
44
|
+
|
|
45
|
+
## 0.7.0 — 2026-07-30
|
|
46
|
+
- `allowed_assets: { chain, token }[]` as the standard way to set payment options.
|
|
47
|
+
|
|
48
|
+
## 0.6.0 — 2026-07-30
|
|
49
|
+
- Human `amount` input for line items and Prices; `paymentLinks` resource.
|
|
50
|
+
|
|
51
|
+
## 0.5.0 — 2026-07-23
|
|
52
|
+
- Initial public release: sessions, per-order deposit addresses, status polling, timestamped webhook signatures.
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 BitPal
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BitPal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -13,23 +13,28 @@ pnpm add @bitpal/checkout
|
|
|
13
13
|
|
|
14
14
|
## Amounts — just pass a human number
|
|
15
15
|
|
|
16
|
-
Use **`amount`** in line items — a plain
|
|
16
|
+
Use **`amount`** in line items — a plain decimal string like `"20"`, `"0.5"`, `"29.00"`. No decimals to compute.
|
|
17
17
|
|
|
18
18
|
```ts
|
|
19
19
|
line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }]
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
**The amount is
|
|
22
|
+
**The amount is quoted in the line item's `currency` — it does NOT depend on the chain or token decimals.** `currency: 'USDC'` / `'USDT'` means a USD price; `currency: 'JPYC'` means a JPY price (`amount: '1500'` → 1,500 JPYC — there is no FX conversion). Whether the buyer pays 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. (Prices via `prices.create` take the same `amount`; their currency comes from `token_symbol`.)
|
|
23
23
|
|
|
24
24
|
<details><summary>Advanced: raw atomic amounts</summary>
|
|
25
25
|
|
|
26
|
-
If you already hold **
|
|
26
|
+
If you already hold **canonical 6-decimal integer strings** (`"1"` = `"1000000"`), pass `amount_atomic` instead (mutually exclusive with `amount`):
|
|
27
27
|
|
|
28
28
|
```
|
|
29
|
-
|
|
29
|
+
1.00 → "1000000" 29.00 → "29000000" 0.50 → "500000"
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Reading amounts back:
|
|
33
|
+
|
|
34
|
+
- `amount_total` is **always canonical 6dp**, whatever token the buyer pays with — `fromAtomic(amount_total, 6)`.
|
|
35
|
+
- `amount_received`, `pay_amount` (the exact amount the buyer must send) and the issued `expectedAmount` are in the **pay token's units** — use `token_decimals` (6 for USDC/USDT, 18 for BNB USDT / JPYC). Never assume 6dp for these.
|
|
36
|
+
|
|
37
|
+
Helpers: `toAtomic(v, decimals)` / `fromAtomic(v, decimals)`; `toAtomicUSDC`/`fromAtomicUSDC` are 6dp shortcuts.
|
|
33
38
|
</details>
|
|
34
39
|
|
|
35
40
|
## Environments
|
|
@@ -73,7 +78,7 @@ const { data: session } = await bitpal.checkout.createSession({
|
|
|
73
78
|
{ chain: 'tron', token: 'USDT' },
|
|
74
79
|
{ chain: 'solana', token: 'USDC' },
|
|
75
80
|
],
|
|
76
|
-
|
|
81
|
+
expires_in_minutes: 30,
|
|
77
82
|
success_url: 'https://yourstore.com/thanks',
|
|
78
83
|
});
|
|
79
84
|
|
|
@@ -96,6 +101,10 @@ The buyer then sends the exact amount from **any wallet or exchange** — no wal
|
|
|
96
101
|
signature; they pay their own network gas. A watcher detects the deposit and a keeper sweeps it to
|
|
97
102
|
you.
|
|
98
103
|
|
|
104
|
+
> **Call this from your backend.** Browsers on your own domain can only call `getPublicSession`
|
|
105
|
+
> directly (CORS). Proxy `issueDepositAddress` / `getStatus` through your server — and never ship a
|
|
106
|
+
> secret key to the browser.
|
|
107
|
+
|
|
99
108
|
```ts
|
|
100
109
|
const { data } = await bitpal.checkout.issueDepositAddress(session.id, {
|
|
101
110
|
session_token: session.session_token!, // from createSession
|
|
@@ -106,11 +115,13 @@ const { data } = await bitpal.checkout.issueDepositAddress(session.id, {
|
|
|
106
115
|
});
|
|
107
116
|
|
|
108
117
|
console.log(data.depositAddress); // buyer sends the exact amount here
|
|
109
|
-
console.log(data.expectedAmount); // atomic
|
|
118
|
+
console.log(data.expectedAmount); // atomic, in the pay token's decimals — display this exact amount
|
|
110
119
|
```
|
|
111
120
|
|
|
112
|
-
> `refundAddress` is committed into the deposit address and **cannot change** after issuance
|
|
113
|
-
>
|
|
121
|
+
> `refundAddress` is committed into the deposit address and **cannot change** after issuance.
|
|
122
|
+
> Overpaid excess, wrong tokens, and underpayments left at expiry are refunded there on-chain. A full
|
|
123
|
+
> payment that arrives after the deadline (up to 7 days) is accepted as a **late payment** — the
|
|
124
|
+
> `checkout.session.paid` webhook carries `late: true` so you can decide how to fulfil it.
|
|
114
125
|
|
|
115
126
|
### Poll status (buyer UX)
|
|
116
127
|
|
|
@@ -124,8 +135,9 @@ Polling is for buyer-facing UX. For **server-side** confirmation, trust the webh
|
|
|
124
135
|
### Preview the fee
|
|
125
136
|
|
|
126
137
|
```ts
|
|
127
|
-
const { data: fee } = await bitpal.checkout.previewFee(
|
|
128
|
-
|
|
138
|
+
const { data: fee } = await bitpal.checkout.previewFee('29.00'); // human amount
|
|
139
|
+
// or: await bitpal.checkout.previewFee({ amountAtomic: '29000000' }); // raw 6dp
|
|
140
|
+
console.log(fromAtomicUSDC(fee.fee_amount)); // response is atomic — e.g. "0.43"
|
|
129
141
|
```
|
|
130
142
|
|
|
131
143
|
## Webhooks
|
package/dist/index.cjs
CHANGED
|
@@ -49,6 +49,7 @@ __export(index_exports, {
|
|
|
49
49
|
fromAtomic: () => fromAtomic,
|
|
50
50
|
fromAtomicUSDC: () => fromAtomicUSDC,
|
|
51
51
|
normalizeSignature: () => normalizeSignature,
|
|
52
|
+
parseApiErrorBody: () => parseApiErrorBody,
|
|
52
53
|
parseWebhookEvent: () => parseWebhookEvent,
|
|
53
54
|
toAtomic: () => toAtomic,
|
|
54
55
|
toAtomicUSDC: () => toAtomicUSDC,
|
|
@@ -138,12 +139,25 @@ var PaymentLinksResource = class {
|
|
|
138
139
|
};
|
|
139
140
|
|
|
140
141
|
// src/client.ts
|
|
142
|
+
var EXPIRY_MIN_MINUTES = 1;
|
|
143
|
+
var EXPIRY_MAX_MINUTES = 60;
|
|
144
|
+
function resolveExpiryMinutes(minutes, seconds) {
|
|
145
|
+
if (minutes !== void 0 && seconds !== void 0) {
|
|
146
|
+
throw new Error("[BitPal] pass only one of `expires_in_minutes` or `expires_in_seconds`.");
|
|
147
|
+
}
|
|
148
|
+
const resolved = seconds !== void 0 ? Math.ceil(seconds / 60) : minutes;
|
|
149
|
+
if (resolved === void 0) return void 0;
|
|
150
|
+
if (!Number.isInteger(resolved) || resolved < EXPIRY_MIN_MINUTES || resolved > EXPIRY_MAX_MINUTES) {
|
|
151
|
+
throw new Error(`[BitPal] session expiry must be ${EXPIRY_MIN_MINUTES}\u2013${EXPIRY_MAX_MINUTES} minutes (expires_in_minutes) \u2014 got ${seconds !== void 0 ? seconds + "s" : minutes}.`);
|
|
152
|
+
}
|
|
153
|
+
return resolved;
|
|
154
|
+
}
|
|
141
155
|
function validateLineItem(li) {
|
|
142
156
|
if ("price_id" in li) return li;
|
|
143
157
|
const hasHuman = li.amount != null;
|
|
144
158
|
const hasAtomic = li.amount_atomic != null;
|
|
145
159
|
if (hasHuman === hasAtomic) {
|
|
146
|
-
throw new Error('[BitPal] line item requires exactly one of `amount` (
|
|
160
|
+
throw new Error('[BitPal] line item requires exactly one of `amount` (canonical decimal number, e.g. "29") or `amount_atomic` (raw canonical 6dp integer, e.g. "29000000").');
|
|
147
161
|
}
|
|
148
162
|
return li;
|
|
149
163
|
}
|
|
@@ -186,10 +200,18 @@ var BitPalCheckoutClient = class {
|
|
|
186
200
|
body: body ? JSON.stringify(body) : void 0,
|
|
187
201
|
signal: controller.signal
|
|
188
202
|
});
|
|
189
|
-
const
|
|
203
|
+
const text = await res.text();
|
|
204
|
+
let json = null;
|
|
205
|
+
try {
|
|
206
|
+
json = text ? JSON.parse(text) : null;
|
|
207
|
+
} catch {
|
|
208
|
+
if (!res.ok) throw new BitPalError(`HTTP ${res.status}`, res.status);
|
|
209
|
+
throw new BitPalError(`Invalid JSON response (HTTP ${res.status})`, res.status);
|
|
210
|
+
}
|
|
190
211
|
if (!res.ok) {
|
|
191
|
-
const
|
|
192
|
-
|
|
212
|
+
const err = json && typeof json === "object" ? json.error : void 0;
|
|
213
|
+
const msg = typeof err === "string" && err.length > 0 ? err : `HTTP ${res.status}`;
|
|
214
|
+
throw new BitPalError(msg, res.status, parseApiErrorBody(json));
|
|
193
215
|
}
|
|
194
216
|
return json;
|
|
195
217
|
} finally {
|
|
@@ -200,14 +222,16 @@ var BitPalCheckoutClient = class {
|
|
|
200
222
|
/**
|
|
201
223
|
* 체크아웃 세션 생성.
|
|
202
224
|
*
|
|
203
|
-
* `line_items[].amount` 는 사람 표기
|
|
204
|
-
*
|
|
225
|
+
* `line_items[].amount` 는 사람 표기 canonical 6dp 문자열("29", "0.5") — decimal 계산 불필요.
|
|
226
|
+
* `currency`('USDC'/'USDT'/'JPYC')가 실제 결제 통화를 결정하고, amount 는 항상 그 통화 기준 값이다
|
|
227
|
+
* (USD 로 고정된 게 아님 — currency='JPYC' + amount='1500' → 1500엔/1500 JPYC, 환율 변환 없음).
|
|
228
|
+
* 이미 raw canonical 6dp 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
205
229
|
*
|
|
206
230
|
* @example
|
|
207
231
|
* import { BitPal } from '@bitpal/checkout';
|
|
208
232
|
* const bp = new BitPal('bp_test_...');
|
|
209
233
|
* const { data } = await bp.checkout.createSession({
|
|
210
|
-
* // amount: 사람 표기
|
|
234
|
+
* // amount: 사람 표기 canonical 6dp("29", "0.5"). 체인/토큰 decimal 은 발급 시 서버가 재스케일.
|
|
211
235
|
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
212
236
|
* });
|
|
213
237
|
* console.log(data.url); // → buyer를 redirect할 결제 페이지
|
|
@@ -224,8 +248,11 @@ var BitPalCheckoutClient = class {
|
|
|
224
248
|
if (params.pay_chain && params.allowed_pay_chains && params.allowed_pay_chains.length > 0) {
|
|
225
249
|
throw new Error("[BitPal] pay_chain and allowed_pay_chains are mutually exclusive \u2014 use only one.");
|
|
226
250
|
}
|
|
251
|
+
const { expires_in_seconds, payer_wallet: _payerWallet, pay_asset: _payAsset, ...rest } = params;
|
|
252
|
+
const expiresInMinutes = resolveExpiryMinutes(params.expires_in_minutes, expires_in_seconds);
|
|
227
253
|
const body = {
|
|
228
|
-
...
|
|
254
|
+
...rest,
|
|
255
|
+
...expiresInMinutes !== void 0 ? { expires_in_minutes: expiresInMinutes } : {},
|
|
229
256
|
line_items: params.line_items.map(validateLineItem),
|
|
230
257
|
...params.allowed_assets ? { allowed_assets: params.allowed_assets.map((a) => ({ caip2: a.chain, token: a.token })) } : {}
|
|
231
258
|
};
|
|
@@ -336,12 +363,37 @@ var BitPalCheckoutClient = class {
|
|
|
336
363
|
};
|
|
337
364
|
var BitPalError = class extends Error {
|
|
338
365
|
statusCode;
|
|
339
|
-
|
|
366
|
+
code;
|
|
367
|
+
apiMessage;
|
|
368
|
+
retryable;
|
|
369
|
+
traceId;
|
|
370
|
+
constructor(message, statusCode, details = {}) {
|
|
340
371
|
super(message);
|
|
341
372
|
this.name = "BitPalError";
|
|
342
373
|
this.statusCode = statusCode;
|
|
374
|
+
this.code = details.code;
|
|
375
|
+
this.apiMessage = details.apiMessage;
|
|
376
|
+
this.retryable = details.retryable;
|
|
377
|
+
this.traceId = details.traceId;
|
|
343
378
|
}
|
|
344
379
|
};
|
|
380
|
+
var ERROR_CODE_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
381
|
+
function parseApiErrorBody(body) {
|
|
382
|
+
if (!body || typeof body !== "object") return {};
|
|
383
|
+
const b = body;
|
|
384
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
385
|
+
const error = str(b.error);
|
|
386
|
+
const explicitCode = str(b.code);
|
|
387
|
+
const message = str(b.message);
|
|
388
|
+
const code = explicitCode ?? (error && ERROR_CODE_RE.test(error) ? error : void 0);
|
|
389
|
+
const apiMessage = message ?? (explicitCode && error && error !== explicitCode ? error : void 0);
|
|
390
|
+
return {
|
|
391
|
+
code,
|
|
392
|
+
apiMessage,
|
|
393
|
+
retryable: typeof b.retryable === "boolean" ? b.retryable : void 0,
|
|
394
|
+
traceId: str(b.trace_id)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
345
397
|
|
|
346
398
|
// src/webhook.ts
|
|
347
399
|
var CHECKOUT_WEBHOOK_EVENTS = [
|
|
@@ -978,6 +1030,7 @@ var BitPal = class {
|
|
|
978
1030
|
fromAtomic,
|
|
979
1031
|
fromAtomicUSDC,
|
|
980
1032
|
normalizeSignature,
|
|
1033
|
+
parseApiErrorBody,
|
|
981
1034
|
parseWebhookEvent,
|
|
982
1035
|
toAtomic,
|
|
983
1036
|
toAtomicUSDC,
|
package/dist/index.d.cts
CHANGED
|
@@ -9,9 +9,11 @@ interface BitPalConfig {
|
|
|
9
9
|
*
|
|
10
10
|
* BitPal 콘솔 → Developers → API Keys 에서 발급.
|
|
11
11
|
*
|
|
12
|
-
* **선택**: createSession/listSessions/products 등 서버 메서드는 필수.
|
|
13
|
-
* (getPublicSession/getStatus/issueDepositAddress)는
|
|
14
|
-
*
|
|
12
|
+
* **선택**: createSession/listSessions/products 등 서버 메서드는 필수. buyer-facing 공개 메서드
|
|
13
|
+
* (getPublicSession/getStatus/issueDepositAddress)는 apiKey 가 필요 없다(issueDepositAddress 는 session_token 필요).
|
|
14
|
+
* 단 **브라우저(CORS)** 에서 임의 머천트 도메인이 직접 부를 수 있는 것은 `getPublicSession` 뿐이다 —
|
|
15
|
+
* `issueDepositAddress`/`getStatus` 는 머천트 백엔드를 경유해 호출한다(자체 결제 UI = 헤드리스 checkout).
|
|
16
|
+
* 절대 secret 키를 브라우저에 노출하지 말 것.
|
|
15
17
|
*/
|
|
16
18
|
apiKey?: string;
|
|
17
19
|
/**
|
|
@@ -25,28 +27,30 @@ interface BitPalConfig {
|
|
|
25
27
|
/** 요청 타임아웃 (ms, 기본값: 30000) */
|
|
26
28
|
timeout?: number;
|
|
27
29
|
}
|
|
28
|
-
/** 응답/정규화된 line item — amount 는 항상 atomic
|
|
30
|
+
/** 응답/정규화된 line item — amount 는 항상 canonical 6dp atomic 정수(실제 결제 토큰 decimals 아님). */
|
|
29
31
|
interface LineItem {
|
|
30
32
|
name: string;
|
|
31
|
-
/**
|
|
33
|
+
/** canonical 6dp atomic 정수 문자열("1"="1000000", "29"="29000000"). */
|
|
32
34
|
amount: string;
|
|
33
35
|
currency: string;
|
|
34
36
|
quantity?: number;
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
37
39
|
* 세션 생성 입력용 line item. 금액은 둘 중 **하나**:
|
|
38
|
-
* - `amount`(권장): 사람 표기
|
|
39
|
-
* ※
|
|
40
|
-
*
|
|
40
|
+
* - `amount`(권장): 사람 표기 canonical 6dp 문자열(예 "29", "0.5", "29.00"). decimal 신경 쓸 필요 없음.
|
|
41
|
+
* ※ `currency`(예: 'USDC'/'USDT'/'JPYC')가 실제 결제 통화를 결정한다 — `amount`는 그 통화의 canonical
|
|
42
|
+
* 6dp 표기일 뿐 항상 USD 를 뜻하지 않는다(예: currency='JPYC' + amount="29" → 29 JPYC/29엔, 환율
|
|
43
|
+
* 변환 없음). 실제 결제 토큰 decimals(USDC 6dp / JPYC 18dp 등)로의 재스케일은 발급 시 서버가 처리.
|
|
44
|
+
* - `amount_atomic`: 이미 raw canonical 6dp 정수 문자열($1="1000000")인 경우(고급/기존 코드).
|
|
41
45
|
* 둘 다 지정하거나 둘 다 생략하면 SDK 가 에러를 throw 한다.
|
|
42
46
|
*/
|
|
43
47
|
interface LineItemInput {
|
|
44
48
|
name: string;
|
|
45
49
|
currency: string;
|
|
46
50
|
quantity?: number;
|
|
47
|
-
/** 사람 표기
|
|
51
|
+
/** 사람 표기 canonical 6dp 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
|
|
48
52
|
amount?: string;
|
|
49
|
-
/** raw
|
|
53
|
+
/** raw canonical 6dp 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
|
|
50
54
|
amount_atomic?: string;
|
|
51
55
|
}
|
|
52
56
|
interface PriceLineItem {
|
|
@@ -57,7 +61,7 @@ interface PriceLineItem {
|
|
|
57
61
|
type CheckoutLineItemInput = LineItemInput | PriceLineItem;
|
|
58
62
|
interface CreateSessionParams {
|
|
59
63
|
line_items: CheckoutLineItemInput[];
|
|
60
|
-
/**
|
|
64
|
+
/** @deprecated 서버가 사용하지 않는다(무시됨). 입금자 주소는 결제 시 온체인에서 기록된다. */
|
|
61
65
|
payer_wallet?: string;
|
|
62
66
|
/** 결제 체인 (예: eip155:8453). 단일 고정 — allowed_pay_chains와 상호 배타. */
|
|
63
67
|
pay_chain?: string;
|
|
@@ -78,11 +82,19 @@ interface CreateSessionParams {
|
|
|
78
82
|
chain: string;
|
|
79
83
|
token: string;
|
|
80
84
|
}>;
|
|
81
|
-
/**
|
|
85
|
+
/** @deprecated 서버가 사용하지 않는다(무시됨). 결제 자산은 `allowed_assets` 로 지정한다. */
|
|
82
86
|
pay_asset?: string;
|
|
83
87
|
/** 허용 오차 (%, 기본값: 2.00) */
|
|
84
88
|
tolerance_percent?: string;
|
|
85
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* 결제 대기 시간(분). 1~60, 기본 15. 이 시간이 지나면 세션이 만료된다
|
|
91
|
+
* (만료 후 도착한 정확 금액은 7일 안이면 늦은 결제로 인정 — `late: true`).
|
|
92
|
+
*/
|
|
93
|
+
expires_in_minutes?: number;
|
|
94
|
+
/**
|
|
95
|
+
* @deprecated `expires_in_minutes` 를 쓴다. 60~3600 초만 허용되고 분 단위로 올림해 보낸다.
|
|
96
|
+
* (0.11 까지는 서버가 이 필드를 몰라 무시했고, 항상 기본 15분이 적용됐다.)
|
|
97
|
+
*/
|
|
86
98
|
expires_in_seconds?: number;
|
|
87
99
|
/** 메타데이터 (모든 값은 string 이어야 함) */
|
|
88
100
|
metadata?: Record<string, string>;
|
|
@@ -141,9 +153,18 @@ interface PublicSession {
|
|
|
141
153
|
status: string;
|
|
142
154
|
mode: string | null;
|
|
143
155
|
currency: string;
|
|
144
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* 주문 총액 — **canonical μUSD(6dp) 정수 문자열**(가격 통화 기준, $1 = "1000000"). 결제 토큰 decimals 와 무관하다
|
|
158
|
+
* (18dp 토큰으로 결제해도 6dp). 온체인으로 보낼 토큰 단위 금액은 `pay_amount`(또는 발급 응답 `expectedAmount`).
|
|
159
|
+
*/
|
|
145
160
|
amount_total: string;
|
|
161
|
+
/** 지금까지 입금주소에 들어온 금액 — **결제 토큰 atomic**(`token_decimals` 기준). 미입금이면 null. */
|
|
146
162
|
amount_received?: string | null;
|
|
163
|
+
/**
|
|
164
|
+
* 선택된 결제 토큰 단위의 총액(atomic, `token_decimals` 기준) — 구매자가 송금할 정확 금액. 자산 미선택이면 null.
|
|
165
|
+
* = canonicalToTokenAtomic(amount_total, token_decimals). 부족액 = pay_amount − amount_received.
|
|
166
|
+
*/
|
|
167
|
+
pay_amount?: string | null;
|
|
147
168
|
pay_chain: string | null;
|
|
148
169
|
pay_asset: string | null;
|
|
149
170
|
/** 발급된 주문별 forwarder 입금주소(미발급이면 null). */
|
|
@@ -152,7 +173,7 @@ interface PublicSession {
|
|
|
152
173
|
payment_options: PaymentOption[];
|
|
153
174
|
chain_id: number | null;
|
|
154
175
|
token_address: string | null;
|
|
155
|
-
/** 선택된 pay 토큰 decimals(발급 후).
|
|
176
|
+
/** 선택된 pay 토큰 decimals(발급 후). amount_received / pay_amount 해석에 사용 — 6dp 가정 금지(amount_total 은 항상 6dp). */
|
|
156
177
|
token_decimals?: number | null;
|
|
157
178
|
tx_hash: string | null;
|
|
158
179
|
release_tx_hash?: string | null;
|
|
@@ -284,16 +305,23 @@ interface UpdateProductParams {
|
|
|
284
305
|
}
|
|
285
306
|
/**
|
|
286
307
|
* 가격 생성 입력. 금액은 둘 중 **하나** (line item 과 동일 모델):
|
|
287
|
-
* - `amount`(권장): 사람 표기
|
|
288
|
-
* - `amount_atomic`: raw
|
|
308
|
+
* - `amount`(권장): 사람 표기 canonical 6dp 문자열(예 "29", "0.5"). decimal 신경 쓸 필요 없음.
|
|
309
|
+
* - `amount_atomic`: raw canonical 6dp 정수($1="1000000") — 고급/기존.
|
|
289
310
|
* 둘 다 지정하거나 둘 다 생략하면 서버가 400 을 반환한다.
|
|
311
|
+
*
|
|
312
|
+
* `amount`/`amount_atomic`은 실제 결제 토큰의 decimals 가 아니라 **항상 canonical 6dp**다 — 서버가
|
|
313
|
+
* 결제 시점에 실제 토큰 decimals(예: JPYC=18dp)로 재스케일한다(1:1 가정, 환율 변환 없음). 즉
|
|
314
|
+
* `token_symbol: 'JPYC'`로 "29"를 등록하면 29 JPYC(≈29엔)로 결제되지, 29 USD 상당의 JPYC로
|
|
315
|
+
* 환산되지 않는다.
|
|
316
|
+
*
|
|
317
|
+
* `currency`는 입력받지 않는다 — 서버가 `token_symbol`에서 파생한다(불일치 방지).
|
|
290
318
|
*/
|
|
291
319
|
interface CreatePriceParams {
|
|
292
|
-
/** 사람 표기
|
|
320
|
+
/** 사람 표기 canonical 6dp 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
|
|
293
321
|
amount?: string;
|
|
294
|
-
/** raw
|
|
322
|
+
/** raw canonical 6dp 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
|
|
295
323
|
amount_atomic?: string;
|
|
296
|
-
currency
|
|
324
|
+
/** 'USDC' | 'USDT' | 'JPYC' — 미지정 시 'USDC'. 이 Price 의 유일한 결제 자산(currency 는 여기서 파생). */
|
|
297
325
|
token_symbol?: string;
|
|
298
326
|
billing_type?: 'one_time';
|
|
299
327
|
nickname?: string;
|
|
@@ -492,14 +520,16 @@ declare class BitPalCheckoutClient {
|
|
|
492
520
|
/**
|
|
493
521
|
* 체크아웃 세션 생성.
|
|
494
522
|
*
|
|
495
|
-
* `line_items[].amount` 는 사람 표기
|
|
496
|
-
*
|
|
523
|
+
* `line_items[].amount` 는 사람 표기 canonical 6dp 문자열("29", "0.5") — decimal 계산 불필요.
|
|
524
|
+
* `currency`('USDC'/'USDT'/'JPYC')가 실제 결제 통화를 결정하고, amount 는 항상 그 통화 기준 값이다
|
|
525
|
+
* (USD 로 고정된 게 아님 — currency='JPYC' + amount='1500' → 1500엔/1500 JPYC, 환율 변환 없음).
|
|
526
|
+
* 이미 raw canonical 6dp 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
497
527
|
*
|
|
498
528
|
* @example
|
|
499
529
|
* import { BitPal } from '@bitpal/checkout';
|
|
500
530
|
* const bp = new BitPal('bp_test_...');
|
|
501
531
|
* const { data } = await bp.checkout.createSession({
|
|
502
|
-
* // amount: 사람 표기
|
|
532
|
+
* // amount: 사람 표기 canonical 6dp("29", "0.5"). 체인/토큰 decimal 은 발급 시 서버가 재스케일.
|
|
503
533
|
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
504
534
|
* });
|
|
505
535
|
* console.log(data.url); // → buyer를 redirect할 결제 페이지
|
|
@@ -571,10 +601,32 @@ declare class BitPalCheckoutClient {
|
|
|
571
601
|
amountAtomic: string;
|
|
572
602
|
}): Promise<ApiResponse<FeePreview>>;
|
|
573
603
|
}
|
|
604
|
+
/** Structured fields parsed from an API error body. All optional — older endpoints omit some. */
|
|
605
|
+
interface BitPalErrorDetails {
|
|
606
|
+
/** Machine-readable error code, e.g. `"FEATURE_NOT_ENABLED"`. */
|
|
607
|
+
code?: string;
|
|
608
|
+
/** Human-readable message from the API, when it sent one separately from the code. */
|
|
609
|
+
apiMessage?: string;
|
|
610
|
+
/** Whether retrying the same request may succeed. */
|
|
611
|
+
retryable?: boolean;
|
|
612
|
+
/** Server trace id — include it when contacting support. */
|
|
613
|
+
traceId?: string;
|
|
614
|
+
}
|
|
574
615
|
declare class BitPalError extends Error {
|
|
575
616
|
readonly statusCode: number;
|
|
576
|
-
|
|
617
|
+
readonly code?: string;
|
|
618
|
+
readonly apiMessage?: string;
|
|
619
|
+
readonly retryable?: boolean;
|
|
620
|
+
readonly traceId?: string;
|
|
621
|
+
constructor(message: string, statusCode: number, details?: BitPalErrorDetails);
|
|
577
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* API 에러 body → BitPalError 상세. 두 가지 shape 를 모두 읽는다:
|
|
625
|
+
* - 표준: `{ error: <message>, code, retryable, trace_id }`
|
|
626
|
+
* - 일부 라우트: `{ error: <CODE>, message }`
|
|
627
|
+
* `message`(Error.message)는 0.11 과 같게 `body.error` 를 유지한다(하위 호환) — 추가 정보는 필드로만.
|
|
628
|
+
*/
|
|
629
|
+
declare function parseApiErrorBody(body: unknown): BitPalErrorDetails;
|
|
578
630
|
|
|
579
631
|
/**
|
|
580
632
|
* @bitpal/checkout — Webhook 서명 검증 + 이벤트 타입
|
|
@@ -1124,4 +1176,4 @@ declare class BitPal {
|
|
|
1124
1176
|
constructor(config: BitPalConfig | string);
|
|
1125
1177
|
}
|
|
1126
1178
|
|
|
1127
|
-
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 CreateX402PaymentOptions, type DepositAddress, type Eip3009Authorization, type Eip712Domain, 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, TRANSFER_WITH_AUTHORIZATION_TYPES, type UpdatePaymentLinkParams, type UpdatePriceParams, type UpdateProductParams, WEBHOOK_HEADERS, type WebhookPayload, type X402Accept, type X402ClaimState, type X402FeeBreakdown, X402Gate, type X402GateOptions, type X402GateResult, type X402MiddlewareOptions, type X402PaymentPayload, type X402PaymentResult, type X402ReplayStore, type X402Request, type X402Requirements, type X402Response, type X402SettledPayment, type X402Signer, type X402TypedDataRequest, X402_PAYMENT_HEADER, X402_PAYMENT_RESPONSE_HEADER, createMemoryReplayStore, createX402Payment, decodePaymentHeader, encodePaymentHeader, fromAtomic, fromAtomicUSDC, normalizeSignature, parseWebhookEvent, toAtomic, toAtomicUSDC, verifyWebhookSignature, x402 };
|
|
1179
|
+
export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, type BitPalErrorDetails, CHECKOUT_WEBHOOK_EVENTS, type CheckoutLineItemInput, type CheckoutPaymentLink, type CheckoutPrice, type CheckoutProduct, type CheckoutSession, type CheckoutWebhookEvent, type CreatePaymentLinkParams, type CreatePriceParams, type CreateProductParams, type CreateSessionParams, type CreateX402PaymentOptions, type DepositAddress, type Eip3009Authorization, type Eip712Domain, 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, TRANSFER_WITH_AUTHORIZATION_TYPES, type UpdatePaymentLinkParams, type UpdatePriceParams, type UpdateProductParams, WEBHOOK_HEADERS, type WebhookPayload, type X402Accept, type X402ClaimState, type X402FeeBreakdown, X402Gate, type X402GateOptions, type X402GateResult, type X402MiddlewareOptions, type X402PaymentPayload, type X402PaymentResult, type X402ReplayStore, type X402Request, type X402Requirements, type X402Response, type X402SettledPayment, type X402Signer, type X402TypedDataRequest, X402_PAYMENT_HEADER, X402_PAYMENT_RESPONSE_HEADER, createMemoryReplayStore, createX402Payment, decodePaymentHeader, encodePaymentHeader, fromAtomic, fromAtomicUSDC, normalizeSignature, parseApiErrorBody, parseWebhookEvent, toAtomic, toAtomicUSDC, verifyWebhookSignature, x402 };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,9 +9,11 @@ interface BitPalConfig {
|
|
|
9
9
|
*
|
|
10
10
|
* BitPal 콘솔 → Developers → API Keys 에서 발급.
|
|
11
11
|
*
|
|
12
|
-
* **선택**: createSession/listSessions/products 등 서버 메서드는 필수.
|
|
13
|
-
* (getPublicSession/getStatus/issueDepositAddress)는
|
|
14
|
-
*
|
|
12
|
+
* **선택**: createSession/listSessions/products 등 서버 메서드는 필수. buyer-facing 공개 메서드
|
|
13
|
+
* (getPublicSession/getStatus/issueDepositAddress)는 apiKey 가 필요 없다(issueDepositAddress 는 session_token 필요).
|
|
14
|
+
* 단 **브라우저(CORS)** 에서 임의 머천트 도메인이 직접 부를 수 있는 것은 `getPublicSession` 뿐이다 —
|
|
15
|
+
* `issueDepositAddress`/`getStatus` 는 머천트 백엔드를 경유해 호출한다(자체 결제 UI = 헤드리스 checkout).
|
|
16
|
+
* 절대 secret 키를 브라우저에 노출하지 말 것.
|
|
15
17
|
*/
|
|
16
18
|
apiKey?: string;
|
|
17
19
|
/**
|
|
@@ -25,28 +27,30 @@ interface BitPalConfig {
|
|
|
25
27
|
/** 요청 타임아웃 (ms, 기본값: 30000) */
|
|
26
28
|
timeout?: number;
|
|
27
29
|
}
|
|
28
|
-
/** 응답/정규화된 line item — amount 는 항상 atomic
|
|
30
|
+
/** 응답/정규화된 line item — amount 는 항상 canonical 6dp atomic 정수(실제 결제 토큰 decimals 아님). */
|
|
29
31
|
interface LineItem {
|
|
30
32
|
name: string;
|
|
31
|
-
/**
|
|
33
|
+
/** canonical 6dp atomic 정수 문자열("1"="1000000", "29"="29000000"). */
|
|
32
34
|
amount: string;
|
|
33
35
|
currency: string;
|
|
34
36
|
quantity?: number;
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
37
39
|
* 세션 생성 입력용 line item. 금액은 둘 중 **하나**:
|
|
38
|
-
* - `amount`(권장): 사람 표기
|
|
39
|
-
* ※
|
|
40
|
-
*
|
|
40
|
+
* - `amount`(권장): 사람 표기 canonical 6dp 문자열(예 "29", "0.5", "29.00"). decimal 신경 쓸 필요 없음.
|
|
41
|
+
* ※ `currency`(예: 'USDC'/'USDT'/'JPYC')가 실제 결제 통화를 결정한다 — `amount`는 그 통화의 canonical
|
|
42
|
+
* 6dp 표기일 뿐 항상 USD 를 뜻하지 않는다(예: currency='JPYC' + amount="29" → 29 JPYC/29엔, 환율
|
|
43
|
+
* 변환 없음). 실제 결제 토큰 decimals(USDC 6dp / JPYC 18dp 등)로의 재스케일은 발급 시 서버가 처리.
|
|
44
|
+
* - `amount_atomic`: 이미 raw canonical 6dp 정수 문자열($1="1000000")인 경우(고급/기존 코드).
|
|
41
45
|
* 둘 다 지정하거나 둘 다 생략하면 SDK 가 에러를 throw 한다.
|
|
42
46
|
*/
|
|
43
47
|
interface LineItemInput {
|
|
44
48
|
name: string;
|
|
45
49
|
currency: string;
|
|
46
50
|
quantity?: number;
|
|
47
|
-
/** 사람 표기
|
|
51
|
+
/** 사람 표기 canonical 6dp 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
|
|
48
52
|
amount?: string;
|
|
49
|
-
/** raw
|
|
53
|
+
/** raw canonical 6dp 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
|
|
50
54
|
amount_atomic?: string;
|
|
51
55
|
}
|
|
52
56
|
interface PriceLineItem {
|
|
@@ -57,7 +61,7 @@ interface PriceLineItem {
|
|
|
57
61
|
type CheckoutLineItemInput = LineItemInput | PriceLineItem;
|
|
58
62
|
interface CreateSessionParams {
|
|
59
63
|
line_items: CheckoutLineItemInput[];
|
|
60
|
-
/**
|
|
64
|
+
/** @deprecated 서버가 사용하지 않는다(무시됨). 입금자 주소는 결제 시 온체인에서 기록된다. */
|
|
61
65
|
payer_wallet?: string;
|
|
62
66
|
/** 결제 체인 (예: eip155:8453). 단일 고정 — allowed_pay_chains와 상호 배타. */
|
|
63
67
|
pay_chain?: string;
|
|
@@ -78,11 +82,19 @@ interface CreateSessionParams {
|
|
|
78
82
|
chain: string;
|
|
79
83
|
token: string;
|
|
80
84
|
}>;
|
|
81
|
-
/**
|
|
85
|
+
/** @deprecated 서버가 사용하지 않는다(무시됨). 결제 자산은 `allowed_assets` 로 지정한다. */
|
|
82
86
|
pay_asset?: string;
|
|
83
87
|
/** 허용 오차 (%, 기본값: 2.00) */
|
|
84
88
|
tolerance_percent?: string;
|
|
85
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* 결제 대기 시간(분). 1~60, 기본 15. 이 시간이 지나면 세션이 만료된다
|
|
91
|
+
* (만료 후 도착한 정확 금액은 7일 안이면 늦은 결제로 인정 — `late: true`).
|
|
92
|
+
*/
|
|
93
|
+
expires_in_minutes?: number;
|
|
94
|
+
/**
|
|
95
|
+
* @deprecated `expires_in_minutes` 를 쓴다. 60~3600 초만 허용되고 분 단위로 올림해 보낸다.
|
|
96
|
+
* (0.11 까지는 서버가 이 필드를 몰라 무시했고, 항상 기본 15분이 적용됐다.)
|
|
97
|
+
*/
|
|
86
98
|
expires_in_seconds?: number;
|
|
87
99
|
/** 메타데이터 (모든 값은 string 이어야 함) */
|
|
88
100
|
metadata?: Record<string, string>;
|
|
@@ -141,9 +153,18 @@ interface PublicSession {
|
|
|
141
153
|
status: string;
|
|
142
154
|
mode: string | null;
|
|
143
155
|
currency: string;
|
|
144
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* 주문 총액 — **canonical μUSD(6dp) 정수 문자열**(가격 통화 기준, $1 = "1000000"). 결제 토큰 decimals 와 무관하다
|
|
158
|
+
* (18dp 토큰으로 결제해도 6dp). 온체인으로 보낼 토큰 단위 금액은 `pay_amount`(또는 발급 응답 `expectedAmount`).
|
|
159
|
+
*/
|
|
145
160
|
amount_total: string;
|
|
161
|
+
/** 지금까지 입금주소에 들어온 금액 — **결제 토큰 atomic**(`token_decimals` 기준). 미입금이면 null. */
|
|
146
162
|
amount_received?: string | null;
|
|
163
|
+
/**
|
|
164
|
+
* 선택된 결제 토큰 단위의 총액(atomic, `token_decimals` 기준) — 구매자가 송금할 정확 금액. 자산 미선택이면 null.
|
|
165
|
+
* = canonicalToTokenAtomic(amount_total, token_decimals). 부족액 = pay_amount − amount_received.
|
|
166
|
+
*/
|
|
167
|
+
pay_amount?: string | null;
|
|
147
168
|
pay_chain: string | null;
|
|
148
169
|
pay_asset: string | null;
|
|
149
170
|
/** 발급된 주문별 forwarder 입금주소(미발급이면 null). */
|
|
@@ -152,7 +173,7 @@ interface PublicSession {
|
|
|
152
173
|
payment_options: PaymentOption[];
|
|
153
174
|
chain_id: number | null;
|
|
154
175
|
token_address: string | null;
|
|
155
|
-
/** 선택된 pay 토큰 decimals(발급 후).
|
|
176
|
+
/** 선택된 pay 토큰 decimals(발급 후). amount_received / pay_amount 해석에 사용 — 6dp 가정 금지(amount_total 은 항상 6dp). */
|
|
156
177
|
token_decimals?: number | null;
|
|
157
178
|
tx_hash: string | null;
|
|
158
179
|
release_tx_hash?: string | null;
|
|
@@ -284,16 +305,23 @@ interface UpdateProductParams {
|
|
|
284
305
|
}
|
|
285
306
|
/**
|
|
286
307
|
* 가격 생성 입력. 금액은 둘 중 **하나** (line item 과 동일 모델):
|
|
287
|
-
* - `amount`(권장): 사람 표기
|
|
288
|
-
* - `amount_atomic`: raw
|
|
308
|
+
* - `amount`(권장): 사람 표기 canonical 6dp 문자열(예 "29", "0.5"). decimal 신경 쓸 필요 없음.
|
|
309
|
+
* - `amount_atomic`: raw canonical 6dp 정수($1="1000000") — 고급/기존.
|
|
289
310
|
* 둘 다 지정하거나 둘 다 생략하면 서버가 400 을 반환한다.
|
|
311
|
+
*
|
|
312
|
+
* `amount`/`amount_atomic`은 실제 결제 토큰의 decimals 가 아니라 **항상 canonical 6dp**다 — 서버가
|
|
313
|
+
* 결제 시점에 실제 토큰 decimals(예: JPYC=18dp)로 재스케일한다(1:1 가정, 환율 변환 없음). 즉
|
|
314
|
+
* `token_symbol: 'JPYC'`로 "29"를 등록하면 29 JPYC(≈29엔)로 결제되지, 29 USD 상당의 JPYC로
|
|
315
|
+
* 환산되지 않는다.
|
|
316
|
+
*
|
|
317
|
+
* `currency`는 입력받지 않는다 — 서버가 `token_symbol`에서 파생한다(불일치 방지).
|
|
290
318
|
*/
|
|
291
319
|
interface CreatePriceParams {
|
|
292
|
-
/** 사람 표기
|
|
320
|
+
/** 사람 표기 canonical 6dp 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
|
|
293
321
|
amount?: string;
|
|
294
|
-
/** raw
|
|
322
|
+
/** raw canonical 6dp 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
|
|
295
323
|
amount_atomic?: string;
|
|
296
|
-
currency
|
|
324
|
+
/** 'USDC' | 'USDT' | 'JPYC' — 미지정 시 'USDC'. 이 Price 의 유일한 결제 자산(currency 는 여기서 파생). */
|
|
297
325
|
token_symbol?: string;
|
|
298
326
|
billing_type?: 'one_time';
|
|
299
327
|
nickname?: string;
|
|
@@ -492,14 +520,16 @@ declare class BitPalCheckoutClient {
|
|
|
492
520
|
/**
|
|
493
521
|
* 체크아웃 세션 생성.
|
|
494
522
|
*
|
|
495
|
-
* `line_items[].amount` 는 사람 표기
|
|
496
|
-
*
|
|
523
|
+
* `line_items[].amount` 는 사람 표기 canonical 6dp 문자열("29", "0.5") — decimal 계산 불필요.
|
|
524
|
+
* `currency`('USDC'/'USDT'/'JPYC')가 실제 결제 통화를 결정하고, amount 는 항상 그 통화 기준 값이다
|
|
525
|
+
* (USD 로 고정된 게 아님 — currency='JPYC' + amount='1500' → 1500엔/1500 JPYC, 환율 변환 없음).
|
|
526
|
+
* 이미 raw canonical 6dp 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
497
527
|
*
|
|
498
528
|
* @example
|
|
499
529
|
* import { BitPal } from '@bitpal/checkout';
|
|
500
530
|
* const bp = new BitPal('bp_test_...');
|
|
501
531
|
* const { data } = await bp.checkout.createSession({
|
|
502
|
-
* // amount: 사람 표기
|
|
532
|
+
* // amount: 사람 표기 canonical 6dp("29", "0.5"). 체인/토큰 decimal 은 발급 시 서버가 재스케일.
|
|
503
533
|
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
504
534
|
* });
|
|
505
535
|
* console.log(data.url); // → buyer를 redirect할 결제 페이지
|
|
@@ -571,10 +601,32 @@ declare class BitPalCheckoutClient {
|
|
|
571
601
|
amountAtomic: string;
|
|
572
602
|
}): Promise<ApiResponse<FeePreview>>;
|
|
573
603
|
}
|
|
604
|
+
/** Structured fields parsed from an API error body. All optional — older endpoints omit some. */
|
|
605
|
+
interface BitPalErrorDetails {
|
|
606
|
+
/** Machine-readable error code, e.g. `"FEATURE_NOT_ENABLED"`. */
|
|
607
|
+
code?: string;
|
|
608
|
+
/** Human-readable message from the API, when it sent one separately from the code. */
|
|
609
|
+
apiMessage?: string;
|
|
610
|
+
/** Whether retrying the same request may succeed. */
|
|
611
|
+
retryable?: boolean;
|
|
612
|
+
/** Server trace id — include it when contacting support. */
|
|
613
|
+
traceId?: string;
|
|
614
|
+
}
|
|
574
615
|
declare class BitPalError extends Error {
|
|
575
616
|
readonly statusCode: number;
|
|
576
|
-
|
|
617
|
+
readonly code?: string;
|
|
618
|
+
readonly apiMessage?: string;
|
|
619
|
+
readonly retryable?: boolean;
|
|
620
|
+
readonly traceId?: string;
|
|
621
|
+
constructor(message: string, statusCode: number, details?: BitPalErrorDetails);
|
|
577
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* API 에러 body → BitPalError 상세. 두 가지 shape 를 모두 읽는다:
|
|
625
|
+
* - 표준: `{ error: <message>, code, retryable, trace_id }`
|
|
626
|
+
* - 일부 라우트: `{ error: <CODE>, message }`
|
|
627
|
+
* `message`(Error.message)는 0.11 과 같게 `body.error` 를 유지한다(하위 호환) — 추가 정보는 필드로만.
|
|
628
|
+
*/
|
|
629
|
+
declare function parseApiErrorBody(body: unknown): BitPalErrorDetails;
|
|
578
630
|
|
|
579
631
|
/**
|
|
580
632
|
* @bitpal/checkout — Webhook 서명 검증 + 이벤트 타입
|
|
@@ -1124,4 +1176,4 @@ declare class BitPal {
|
|
|
1124
1176
|
constructor(config: BitPalConfig | string);
|
|
1125
1177
|
}
|
|
1126
1178
|
|
|
1127
|
-
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 CreateX402PaymentOptions, type DepositAddress, type Eip3009Authorization, type Eip712Domain, 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, TRANSFER_WITH_AUTHORIZATION_TYPES, type UpdatePaymentLinkParams, type UpdatePriceParams, type UpdateProductParams, WEBHOOK_HEADERS, type WebhookPayload, type X402Accept, type X402ClaimState, type X402FeeBreakdown, X402Gate, type X402GateOptions, type X402GateResult, type X402MiddlewareOptions, type X402PaymentPayload, type X402PaymentResult, type X402ReplayStore, type X402Request, type X402Requirements, type X402Response, type X402SettledPayment, type X402Signer, type X402TypedDataRequest, X402_PAYMENT_HEADER, X402_PAYMENT_RESPONSE_HEADER, createMemoryReplayStore, createX402Payment, decodePaymentHeader, encodePaymentHeader, fromAtomic, fromAtomicUSDC, normalizeSignature, parseWebhookEvent, toAtomic, toAtomicUSDC, verifyWebhookSignature, x402 };
|
|
1179
|
+
export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, type BitPalErrorDetails, CHECKOUT_WEBHOOK_EVENTS, type CheckoutLineItemInput, type CheckoutPaymentLink, type CheckoutPrice, type CheckoutProduct, type CheckoutSession, type CheckoutWebhookEvent, type CreatePaymentLinkParams, type CreatePriceParams, type CreateProductParams, type CreateSessionParams, type CreateX402PaymentOptions, type DepositAddress, type Eip3009Authorization, type Eip712Domain, 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, TRANSFER_WITH_AUTHORIZATION_TYPES, type UpdatePaymentLinkParams, type UpdatePriceParams, type UpdateProductParams, WEBHOOK_HEADERS, type WebhookPayload, type X402Accept, type X402ClaimState, type X402FeeBreakdown, X402Gate, type X402GateOptions, type X402GateResult, type X402MiddlewareOptions, type X402PaymentPayload, type X402PaymentResult, type X402ReplayStore, type X402Request, type X402Requirements, type X402Response, type X402SettledPayment, type X402Signer, type X402TypedDataRequest, X402_PAYMENT_HEADER, X402_PAYMENT_RESPONSE_HEADER, createMemoryReplayStore, createX402Payment, decodePaymentHeader, encodePaymentHeader, fromAtomic, fromAtomicUSDC, normalizeSignature, parseApiErrorBody, parseWebhookEvent, toAtomic, toAtomicUSDC, verifyWebhookSignature, x402 };
|
package/dist/index.js
CHANGED
|
@@ -79,12 +79,25 @@ var PaymentLinksResource = class {
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
// src/client.ts
|
|
82
|
+
var EXPIRY_MIN_MINUTES = 1;
|
|
83
|
+
var EXPIRY_MAX_MINUTES = 60;
|
|
84
|
+
function resolveExpiryMinutes(minutes, seconds) {
|
|
85
|
+
if (minutes !== void 0 && seconds !== void 0) {
|
|
86
|
+
throw new Error("[BitPal] pass only one of `expires_in_minutes` or `expires_in_seconds`.");
|
|
87
|
+
}
|
|
88
|
+
const resolved = seconds !== void 0 ? Math.ceil(seconds / 60) : minutes;
|
|
89
|
+
if (resolved === void 0) return void 0;
|
|
90
|
+
if (!Number.isInteger(resolved) || resolved < EXPIRY_MIN_MINUTES || resolved > EXPIRY_MAX_MINUTES) {
|
|
91
|
+
throw new Error(`[BitPal] session expiry must be ${EXPIRY_MIN_MINUTES}\u2013${EXPIRY_MAX_MINUTES} minutes (expires_in_minutes) \u2014 got ${seconds !== void 0 ? seconds + "s" : minutes}.`);
|
|
92
|
+
}
|
|
93
|
+
return resolved;
|
|
94
|
+
}
|
|
82
95
|
function validateLineItem(li) {
|
|
83
96
|
if ("price_id" in li) return li;
|
|
84
97
|
const hasHuman = li.amount != null;
|
|
85
98
|
const hasAtomic = li.amount_atomic != null;
|
|
86
99
|
if (hasHuman === hasAtomic) {
|
|
87
|
-
throw new Error('[BitPal] line item requires exactly one of `amount` (
|
|
100
|
+
throw new Error('[BitPal] line item requires exactly one of `amount` (canonical decimal number, e.g. "29") or `amount_atomic` (raw canonical 6dp integer, e.g. "29000000").');
|
|
88
101
|
}
|
|
89
102
|
return li;
|
|
90
103
|
}
|
|
@@ -127,10 +140,18 @@ var BitPalCheckoutClient = class {
|
|
|
127
140
|
body: body ? JSON.stringify(body) : void 0,
|
|
128
141
|
signal: controller.signal
|
|
129
142
|
});
|
|
130
|
-
const
|
|
143
|
+
const text = await res.text();
|
|
144
|
+
let json = null;
|
|
145
|
+
try {
|
|
146
|
+
json = text ? JSON.parse(text) : null;
|
|
147
|
+
} catch {
|
|
148
|
+
if (!res.ok) throw new BitPalError(`HTTP ${res.status}`, res.status);
|
|
149
|
+
throw new BitPalError(`Invalid JSON response (HTTP ${res.status})`, res.status);
|
|
150
|
+
}
|
|
131
151
|
if (!res.ok) {
|
|
132
|
-
const
|
|
133
|
-
|
|
152
|
+
const err = json && typeof json === "object" ? json.error : void 0;
|
|
153
|
+
const msg = typeof err === "string" && err.length > 0 ? err : `HTTP ${res.status}`;
|
|
154
|
+
throw new BitPalError(msg, res.status, parseApiErrorBody(json));
|
|
134
155
|
}
|
|
135
156
|
return json;
|
|
136
157
|
} finally {
|
|
@@ -141,14 +162,16 @@ var BitPalCheckoutClient = class {
|
|
|
141
162
|
/**
|
|
142
163
|
* 체크아웃 세션 생성.
|
|
143
164
|
*
|
|
144
|
-
* `line_items[].amount` 는 사람 표기
|
|
145
|
-
*
|
|
165
|
+
* `line_items[].amount` 는 사람 표기 canonical 6dp 문자열("29", "0.5") — decimal 계산 불필요.
|
|
166
|
+
* `currency`('USDC'/'USDT'/'JPYC')가 실제 결제 통화를 결정하고, amount 는 항상 그 통화 기준 값이다
|
|
167
|
+
* (USD 로 고정된 게 아님 — currency='JPYC' + amount='1500' → 1500엔/1500 JPYC, 환율 변환 없음).
|
|
168
|
+
* 이미 raw canonical 6dp 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
146
169
|
*
|
|
147
170
|
* @example
|
|
148
171
|
* import { BitPal } from '@bitpal/checkout';
|
|
149
172
|
* const bp = new BitPal('bp_test_...');
|
|
150
173
|
* const { data } = await bp.checkout.createSession({
|
|
151
|
-
* // amount: 사람 표기
|
|
174
|
+
* // amount: 사람 표기 canonical 6dp("29", "0.5"). 체인/토큰 decimal 은 발급 시 서버가 재스케일.
|
|
152
175
|
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
153
176
|
* });
|
|
154
177
|
* console.log(data.url); // → buyer를 redirect할 결제 페이지
|
|
@@ -165,8 +188,11 @@ var BitPalCheckoutClient = class {
|
|
|
165
188
|
if (params.pay_chain && params.allowed_pay_chains && params.allowed_pay_chains.length > 0) {
|
|
166
189
|
throw new Error("[BitPal] pay_chain and allowed_pay_chains are mutually exclusive \u2014 use only one.");
|
|
167
190
|
}
|
|
191
|
+
const { expires_in_seconds, payer_wallet: _payerWallet, pay_asset: _payAsset, ...rest } = params;
|
|
192
|
+
const expiresInMinutes = resolveExpiryMinutes(params.expires_in_minutes, expires_in_seconds);
|
|
168
193
|
const body = {
|
|
169
|
-
...
|
|
194
|
+
...rest,
|
|
195
|
+
...expiresInMinutes !== void 0 ? { expires_in_minutes: expiresInMinutes } : {},
|
|
170
196
|
line_items: params.line_items.map(validateLineItem),
|
|
171
197
|
...params.allowed_assets ? { allowed_assets: params.allowed_assets.map((a) => ({ caip2: a.chain, token: a.token })) } : {}
|
|
172
198
|
};
|
|
@@ -277,12 +303,37 @@ var BitPalCheckoutClient = class {
|
|
|
277
303
|
};
|
|
278
304
|
var BitPalError = class extends Error {
|
|
279
305
|
statusCode;
|
|
280
|
-
|
|
306
|
+
code;
|
|
307
|
+
apiMessage;
|
|
308
|
+
retryable;
|
|
309
|
+
traceId;
|
|
310
|
+
constructor(message, statusCode, details = {}) {
|
|
281
311
|
super(message);
|
|
282
312
|
this.name = "BitPalError";
|
|
283
313
|
this.statusCode = statusCode;
|
|
314
|
+
this.code = details.code;
|
|
315
|
+
this.apiMessage = details.apiMessage;
|
|
316
|
+
this.retryable = details.retryable;
|
|
317
|
+
this.traceId = details.traceId;
|
|
284
318
|
}
|
|
285
319
|
};
|
|
320
|
+
var ERROR_CODE_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
321
|
+
function parseApiErrorBody(body) {
|
|
322
|
+
if (!body || typeof body !== "object") return {};
|
|
323
|
+
const b = body;
|
|
324
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
325
|
+
const error = str(b.error);
|
|
326
|
+
const explicitCode = str(b.code);
|
|
327
|
+
const message = str(b.message);
|
|
328
|
+
const code = explicitCode ?? (error && ERROR_CODE_RE.test(error) ? error : void 0);
|
|
329
|
+
const apiMessage = message ?? (explicitCode && error && error !== explicitCode ? error : void 0);
|
|
330
|
+
return {
|
|
331
|
+
code,
|
|
332
|
+
apiMessage,
|
|
333
|
+
retryable: typeof b.retryable === "boolean" ? b.retryable : void 0,
|
|
334
|
+
traceId: str(b.trace_id)
|
|
335
|
+
};
|
|
336
|
+
}
|
|
286
337
|
|
|
287
338
|
// src/webhook.ts
|
|
288
339
|
var CHECKOUT_WEBHOOK_EVENTS = [
|
|
@@ -918,6 +969,7 @@ export {
|
|
|
918
969
|
fromAtomic,
|
|
919
970
|
fromAtomicUSDC,
|
|
920
971
|
normalizeSignature,
|
|
972
|
+
parseApiErrorBody,
|
|
921
973
|
parseWebhookEvent,
|
|
922
974
|
toAtomic,
|
|
923
975
|
toAtomicUSDC,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitpal/checkout",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "BitPal Checkout SDK — non-custodial crypto checkout: create sessions, issue per-order deposit addresses, poll status, verify signed webhooks, and charge per API call with x402.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"dist",
|
|
32
|
-
"README.md"
|
|
32
|
+
"README.md",
|
|
33
|
+
"CHANGELOG.md"
|
|
33
34
|
],
|
|
34
35
|
"keywords": [
|
|
35
36
|
"bitpal",
|
|
@@ -52,17 +53,16 @@
|
|
|
52
53
|
"engines": {
|
|
53
54
|
"node": ">=18"
|
|
54
55
|
},
|
|
55
|
-
"scripts": {
|
|
56
|
-
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
57
|
-
"lint": "tsc --noEmit",
|
|
58
|
-
"test": "vitest run",
|
|
59
|
-
"prepublishOnly": "npm test && npm run build",
|
|
60
|
-
"release": "npm publish --access public"
|
|
61
|
-
},
|
|
62
56
|
"devDependencies": {
|
|
63
|
-
"@types/node": "^20.11.0",
|
|
64
57
|
"tsup": "^8.5.1",
|
|
65
58
|
"typescript": "^5.5.0",
|
|
66
59
|
"vitest": "^2.1.9"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
63
|
+
"lint": "tsc --noEmit",
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"release": "pnpm publish --access public --no-git-checks",
|
|
66
|
+
"sync:mirror": "node scripts/sync-mirror.mjs"
|
|
67
67
|
}
|
|
68
|
-
}
|
|
68
|
+
}
|