@bitpal/checkout 0.3.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/LICENSE +21 -0
- package/README.md +222 -392
- package/dist/index.cjs +431 -0
- package/dist/index.d.cts +708 -0
- package/dist/index.d.ts +430 -433
- package/dist/index.js +187 -216
- package/package.json +33 -17
- package/MIGRATION_v0.2.md +0 -135
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
BitPal: () => BitPal,
|
|
34
|
+
BitPalCheckoutClient: () => BitPalCheckoutClient,
|
|
35
|
+
BitPalError: () => BitPalError,
|
|
36
|
+
CHECKOUT_WEBHOOK_EVENTS: () => CHECKOUT_WEBHOOK_EVENTS,
|
|
37
|
+
PaymentLinksResource: () => PaymentLinksResource,
|
|
38
|
+
PricesResource: () => PricesResource,
|
|
39
|
+
ProductsResource: () => ProductsResource,
|
|
40
|
+
WEBHOOK_HEADERS: () => WEBHOOK_HEADERS,
|
|
41
|
+
fromAtomic: () => fromAtomic,
|
|
42
|
+
fromAtomicUSDC: () => fromAtomicUSDC,
|
|
43
|
+
parseWebhookEvent: () => parseWebhookEvent,
|
|
44
|
+
toAtomic: () => toAtomic,
|
|
45
|
+
toAtomicUSDC: () => toAtomicUSDC,
|
|
46
|
+
verifyWebhookSignature: () => verifyWebhookSignature
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/catalog.ts
|
|
51
|
+
function toQuery(params) {
|
|
52
|
+
if (!params) return "";
|
|
53
|
+
const entries = Object.entries(params).filter(([, v]) => v !== void 0);
|
|
54
|
+
if (entries.length === 0) return "";
|
|
55
|
+
const qs = new URLSearchParams(entries.map(([k, v]) => [k, String(v)]));
|
|
56
|
+
return `?${qs.toString()}`;
|
|
57
|
+
}
|
|
58
|
+
var ProductsResource = class {
|
|
59
|
+
constructor(request) {
|
|
60
|
+
this.request = request;
|
|
61
|
+
}
|
|
62
|
+
request;
|
|
63
|
+
/** POST /v1/checkout/products */
|
|
64
|
+
create(params) {
|
|
65
|
+
return this.request("POST", "/v1/checkout/products", params);
|
|
66
|
+
}
|
|
67
|
+
/** GET /v1/checkout/products */
|
|
68
|
+
list(params) {
|
|
69
|
+
return this.request("GET", `/v1/checkout/products${toQuery(params)}`);
|
|
70
|
+
}
|
|
71
|
+
/** GET /v1/checkout/products/:id */
|
|
72
|
+
retrieve(productId) {
|
|
73
|
+
return this.request("GET", `/v1/checkout/products/${encodeURIComponent(productId)}`);
|
|
74
|
+
}
|
|
75
|
+
/** PATCH /v1/checkout/products/:id */
|
|
76
|
+
update(productId, params) {
|
|
77
|
+
return this.request("PATCH", `/v1/checkout/products/${encodeURIComponent(productId)}`, params);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var PricesResource = class {
|
|
81
|
+
constructor(request) {
|
|
82
|
+
this.request = request;
|
|
83
|
+
}
|
|
84
|
+
request;
|
|
85
|
+
/** POST /v1/checkout/products/:id/prices */
|
|
86
|
+
create(productId, params) {
|
|
87
|
+
return this.request("POST", `/v1/checkout/products/${encodeURIComponent(productId)}/prices`, params);
|
|
88
|
+
}
|
|
89
|
+
/** GET /v1/checkout/products/:id/prices */
|
|
90
|
+
list(productId, params) {
|
|
91
|
+
return this.request("GET", `/v1/checkout/products/${encodeURIComponent(productId)}/prices${toQuery(params)}`);
|
|
92
|
+
}
|
|
93
|
+
/** GET /v1/checkout/prices/:id */
|
|
94
|
+
retrieve(priceId) {
|
|
95
|
+
return this.request("GET", `/v1/checkout/prices/${encodeURIComponent(priceId)}`);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* PATCH /v1/checkout/prices/:id — nickname(표시 메타)만.
|
|
99
|
+
* 금액류 필드(amount_atomic 등)는 immutable — 서버가 PRICE_IMMUTABLE_FIELD로 거부한다.
|
|
100
|
+
* 가격 변경은 `prices.create()`로 새 price를 만들 것. "그만 팔기"는 해당 Price 를 쓰는
|
|
101
|
+
* Payment Link 를 inactive 로 한다(Price 자체에는 active/inactive 개념이 없다).
|
|
102
|
+
*/
|
|
103
|
+
update(priceId, params) {
|
|
104
|
+
return this.request("PATCH", `/v1/checkout/prices/${encodeURIComponent(priceId)}`, params);
|
|
105
|
+
}
|
|
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
|
+
};
|
|
129
|
+
|
|
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
|
+
}
|
|
140
|
+
var DEFAULT_BASE_URL = "https://api.bitpal.io";
|
|
141
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
142
|
+
var BitPalCheckoutClient = class {
|
|
143
|
+
/** 서버 메서드용 secret 키. 공개(buyer-facing) 메서드는 없이도 동작. */
|
|
144
|
+
apiKey;
|
|
145
|
+
baseUrl;
|
|
146
|
+
timeout;
|
|
147
|
+
/** PROD-LINK-3a-SDK: Product 카탈로그 관리 (server-side 전용) */
|
|
148
|
+
products;
|
|
149
|
+
/** PROD-LINK-3a-SDK: immutable Price 관리 — 가격 변경은 비활성화 + 새 price 생성 */
|
|
150
|
+
prices;
|
|
151
|
+
/** PROD-LINK-4-SDK: Payment Link 관리 — 대시보드 없이 코드로 결제 링크 생성 (server-side 전용) */
|
|
152
|
+
paymentLinks;
|
|
153
|
+
constructor(config) {
|
|
154
|
+
this.apiKey = config.apiKey;
|
|
155
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
156
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
157
|
+
const requester = (method, path, body) => this.request(method, path, body);
|
|
158
|
+
this.products = new ProductsResource(requester);
|
|
159
|
+
this.prices = new PricesResource(requester);
|
|
160
|
+
this.paymentLinks = new PaymentLinksResource(requester);
|
|
161
|
+
}
|
|
162
|
+
/* ─── 내부 HTTP ─── */
|
|
163
|
+
async request(method, path, body, extraHeaders) {
|
|
164
|
+
const url = `${this.baseUrl}${path}`;
|
|
165
|
+
const controller = new AbortController();
|
|
166
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
167
|
+
try {
|
|
168
|
+
const res = await fetch(url, {
|
|
169
|
+
method,
|
|
170
|
+
headers: {
|
|
171
|
+
"Content-Type": "application/json",
|
|
172
|
+
// 공개 메서드(session_token 기반)는 apiKey 없이 호출 가능 — 있을 때만 Authorization 첨부.
|
|
173
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
|
|
174
|
+
...extraHeaders ?? {}
|
|
175
|
+
},
|
|
176
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
177
|
+
signal: controller.signal
|
|
178
|
+
});
|
|
179
|
+
const json = await res.json();
|
|
180
|
+
if (!res.ok) {
|
|
181
|
+
const msg = json.error ?? `HTTP ${res.status}`;
|
|
182
|
+
throw new BitPalError(String(msg), res.status);
|
|
183
|
+
}
|
|
184
|
+
return json;
|
|
185
|
+
} finally {
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/* ─── Sessions ─── */
|
|
190
|
+
/**
|
|
191
|
+
* 체크아웃 세션 생성.
|
|
192
|
+
*
|
|
193
|
+
* `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
|
|
194
|
+
* 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* import { BitPal } from '@bitpal/checkout';
|
|
198
|
+
* const bp = new BitPal('bp_test_...');
|
|
199
|
+
* const { data } = await bp.checkout.createSession({
|
|
200
|
+
* // amount: 사람 표기 USD("29", "0.5"). 금액은 USD 가격이라 체인/토큰 decimal 무관.
|
|
201
|
+
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
202
|
+
* });
|
|
203
|
+
* console.log(data.url); // → buyer를 redirect할 결제 페이지
|
|
204
|
+
*
|
|
205
|
+
* // 멀티체인 허용 — 구매자가 Base/Optimism 중 선택(하나뿐이면 자동 선택):
|
|
206
|
+
* // 어떤 체인·토큰이든 amount 그대로 — 서버가 발급 시 토큰 decimals 로 재스케일.
|
|
207
|
+
* await bp.checkout.createSession({
|
|
208
|
+
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
209
|
+
* allowed_pay_chains: ['eip155:84532', 'eip155:11155420'],
|
|
210
|
+
* });
|
|
211
|
+
* // 단일 체인 고정은 pay_chain 사용(둘은 상호 배타).
|
|
212
|
+
*/
|
|
213
|
+
async createSession(params) {
|
|
214
|
+
if (params.pay_chain && params.allowed_pay_chains && params.allowed_pay_chains.length > 0) {
|
|
215
|
+
throw new Error("[BitPal] pay_chain and allowed_pay_chains are mutually exclusive \u2014 use only one.");
|
|
216
|
+
}
|
|
217
|
+
const body = { ...params, line_items: params.line_items.map(validateLineItem) };
|
|
218
|
+
const res = await this.request(
|
|
219
|
+
"POST",
|
|
220
|
+
"/v1/checkout/sessions",
|
|
221
|
+
body
|
|
222
|
+
);
|
|
223
|
+
if (res.data) {
|
|
224
|
+
if (res.data.checkout_url) {
|
|
225
|
+
res.data.url = res.data.checkout_url;
|
|
226
|
+
} else if (!res.data.url) {
|
|
227
|
+
res.data.url = `${this.baseUrl.replace("api.", "")}/pay/${res.data.id}`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return res;
|
|
231
|
+
}
|
|
232
|
+
/** 세션 조회 */
|
|
233
|
+
async getSession(sessionId) {
|
|
234
|
+
return this.request(
|
|
235
|
+
"GET",
|
|
236
|
+
`/v1/checkout/sessions/${sessionId}`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
/** 세션 목록 조회 */
|
|
240
|
+
async listSessions(params) {
|
|
241
|
+
const qs = new URLSearchParams();
|
|
242
|
+
if (params?.status) qs.set("status", params.status);
|
|
243
|
+
if (params?.limit) qs.set("limit", String(params.limit));
|
|
244
|
+
if (params?.offset) qs.set("offset", String(params.offset));
|
|
245
|
+
const query = qs.toString();
|
|
246
|
+
return this.request(
|
|
247
|
+
"GET",
|
|
248
|
+
`/v1/checkout/sessions${query ? `?${query}` : ""}`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
/* ─── 안2 비수탁 결제 (deposit-address) ─── */
|
|
252
|
+
/**
|
|
253
|
+
* 공개 세션 조회 (`GET /v1/checkout/sessions/:id/public`) — 인증 불요.
|
|
254
|
+
*
|
|
255
|
+
* buyer-facing 결제 페이지가 표시/자산선택에 쓰는 서버 권위 뷰. `payment_options` 로
|
|
256
|
+
* 구매자 선택 가능한 chain×token 을 얻고, 발급 후에는 `deposit_address` 가 채워진다.
|
|
257
|
+
*/
|
|
258
|
+
async getPublicSession(sessionId) {
|
|
259
|
+
return this.request(
|
|
260
|
+
"GET",
|
|
261
|
+
`/v1/checkout/sessions/${encodeURIComponent(sessionId)}/public`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
|
|
266
|
+
*
|
|
267
|
+
* 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
|
|
268
|
+
* 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
|
|
269
|
+
* 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
|
|
270
|
+
*
|
|
271
|
+
* refundAddress 는 입금주소에 커밋되어 발급 후 변경 불가. session_token 은 createSession 응답값.
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```ts
|
|
275
|
+
* const { data } = await bitpal.checkout.issueDepositAddress(sessionId, {
|
|
276
|
+
* session_token: session.session_token!,
|
|
277
|
+
* refundAddress: '0xBuyerWallet...',
|
|
278
|
+
* chain: 'eip155:8453', // 멀티옵션 세션만 필요
|
|
279
|
+
* token: 'USDC',
|
|
280
|
+
* });
|
|
281
|
+
* console.log(data.depositAddress, data.expectedAmount); // 이 주소로 정확 금액 송금
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
async issueDepositAddress(sessionId, params) {
|
|
285
|
+
return this.request(
|
|
286
|
+
"POST",
|
|
287
|
+
`/v1/checkout/sessions/${encodeURIComponent(sessionId)}/deposit-address`,
|
|
288
|
+
params
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* 경량 상태 폴링 (`GET /v1/checkout/sessions/:id/status`) — 인증 불요.
|
|
293
|
+
*
|
|
294
|
+
* status: awaiting_deposit → deposit_detected → paid_confirmed/swept. 확정/정산은 webhook 으로도
|
|
295
|
+
* 통지되므로, 서버 사이드 확정은 webhook 을 신뢰(폴링은 buyer UX 용).
|
|
296
|
+
*/
|
|
297
|
+
async getStatus(sessionId) {
|
|
298
|
+
return this.request(
|
|
299
|
+
"GET",
|
|
300
|
+
`/v1/checkout/sessions/${encodeURIComponent(sessionId)}/status`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
/* ─── Fees ─── */
|
|
304
|
+
/** 수수료 미리보기 — `/v1/checkout/fee-preview` (Phase 1 credit system 폐기 후 canonical path) */
|
|
305
|
+
async previewFee(amount) {
|
|
306
|
+
return this.request(
|
|
307
|
+
"GET",
|
|
308
|
+
`/v1/checkout/fee-preview?amount=${encodeURIComponent(amount)}`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
var BitPalError = class extends Error {
|
|
313
|
+
statusCode;
|
|
314
|
+
constructor(message, statusCode) {
|
|
315
|
+
super(message);
|
|
316
|
+
this.name = "BitPalError";
|
|
317
|
+
this.statusCode = statusCode;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// src/webhook.ts
|
|
322
|
+
var CHECKOUT_WEBHOOK_EVENTS = [
|
|
323
|
+
"checkout.session.created",
|
|
324
|
+
"checkout.session.deposit_detected",
|
|
325
|
+
"checkout.session.paid",
|
|
326
|
+
"checkout.session.settled",
|
|
327
|
+
"checkout.session.expired",
|
|
328
|
+
"checkout.session.unresolved",
|
|
329
|
+
"checkout.refund.recorded"
|
|
330
|
+
];
|
|
331
|
+
var WEBHOOK_HEADERS = {
|
|
332
|
+
signature: "X-Webhook-Signature-256",
|
|
333
|
+
timestamp: "X-Webhook-Timestamp",
|
|
334
|
+
event: "X-Webhook-Event",
|
|
335
|
+
eventId: "X-Webhook-Id",
|
|
336
|
+
deliveryId: "X-Webhook-Delivery-Id"
|
|
337
|
+
};
|
|
338
|
+
var SIGNATURE_PREFIX = "sha256=";
|
|
339
|
+
async function verifyWebhookSignature(payload, signature, secret, opts) {
|
|
340
|
+
if (!payload || !signature || !secret) return false;
|
|
341
|
+
if (!signature.startsWith(SIGNATURE_PREFIX)) return false;
|
|
342
|
+
const timestamp = opts?.timestamp;
|
|
343
|
+
if (!timestamp) return false;
|
|
344
|
+
const parsedMs = Date.parse(timestamp);
|
|
345
|
+
if (!Number.isFinite(parsedMs)) return false;
|
|
346
|
+
if (new Date(parsedMs).toISOString() !== timestamp) return false;
|
|
347
|
+
const toleranceMs = (opts?.toleranceSec ?? 300) * 1e3;
|
|
348
|
+
if (Math.abs(Date.now() - parsedMs) > toleranceMs) return false;
|
|
349
|
+
const sigHex = signature.slice(SIGNATURE_PREFIX.length);
|
|
350
|
+
if (!/^[0-9a-f]{64}$/.test(sigHex)) return false;
|
|
351
|
+
try {
|
|
352
|
+
const crypto = await import("crypto");
|
|
353
|
+
const expectedHex = crypto.createHmac("sha256", secret).update(`${timestamp}.${payload}`, "utf8").digest("hex");
|
|
354
|
+
if (sigHex.length !== expectedHex.length) return false;
|
|
355
|
+
return crypto.timingSafeEqual(
|
|
356
|
+
Buffer.from(sigHex, "hex"),
|
|
357
|
+
Buffer.from(expectedHex, "hex")
|
|
358
|
+
);
|
|
359
|
+
} catch {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function parseWebhookEvent(body) {
|
|
364
|
+
const parsed = JSON.parse(body);
|
|
365
|
+
if (typeof parsed.id !== "string" || typeof parsed.event !== "string" || typeof parsed.created_at !== "string" || typeof parsed.data !== "object" || parsed.data === null) {
|
|
366
|
+
throw new Error("Invalid webhook envelope \u2014 expected {id, event, created_at, data}");
|
|
367
|
+
}
|
|
368
|
+
return parsed;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/amount.ts
|
|
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)`);
|
|
379
|
+
}
|
|
380
|
+
const scale = 10n ** BigInt(decimals);
|
|
381
|
+
const [whole = "0", frac = ""] = decimal.split(".");
|
|
382
|
+
const fracPadded = frac.padEnd(decimals, "0");
|
|
383
|
+
return (BigInt(whole) * scale + BigInt(fracPadded || "0")).toString();
|
|
384
|
+
}
|
|
385
|
+
function fromAtomic(atomic, decimals) {
|
|
386
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
|
|
387
|
+
throw new Error(`fromAtomic: invalid decimals '${decimals}'`);
|
|
388
|
+
}
|
|
389
|
+
if (typeof atomic !== "string" || !/^\d+$/.test(atomic)) {
|
|
390
|
+
throw new Error(`fromAtomic: invalid atomic '${atomic}' (must be non-negative integer string)`);
|
|
391
|
+
}
|
|
392
|
+
const scale = 10n ** BigInt(decimals);
|
|
393
|
+
const big = BigInt(atomic);
|
|
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+$/, "");
|
|
398
|
+
return fracStr.length > 0 ? `${whole}.${fracStr.padEnd(2, "0")}` : `${whole}.00`;
|
|
399
|
+
}
|
|
400
|
+
function toAtomicUSDC(decimal) {
|
|
401
|
+
return toAtomic(decimal, 6);
|
|
402
|
+
}
|
|
403
|
+
function fromAtomicUSDC(atomic) {
|
|
404
|
+
return fromAtomic(atomic, 6);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/index.ts
|
|
408
|
+
var BitPal = class {
|
|
409
|
+
checkout;
|
|
410
|
+
constructor(config) {
|
|
411
|
+
const cfg = typeof config === "string" ? { apiKey: config } : config;
|
|
412
|
+
this.checkout = new BitPalCheckoutClient(cfg);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
416
|
+
0 && (module.exports = {
|
|
417
|
+
BitPal,
|
|
418
|
+
BitPalCheckoutClient,
|
|
419
|
+
BitPalError,
|
|
420
|
+
CHECKOUT_WEBHOOK_EVENTS,
|
|
421
|
+
PaymentLinksResource,
|
|
422
|
+
PricesResource,
|
|
423
|
+
ProductsResource,
|
|
424
|
+
WEBHOOK_HEADERS,
|
|
425
|
+
fromAtomic,
|
|
426
|
+
fromAtomicUSDC,
|
|
427
|
+
parseWebhookEvent,
|
|
428
|
+
toAtomic,
|
|
429
|
+
toAtomicUSDC,
|
|
430
|
+
verifyWebhookSignature
|
|
431
|
+
});
|