@bitpal/checkout 0.8.0 → 0.10.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 +11 -1
- package/dist/index.cjs +34 -9
- package/dist/index.d.cts +23 -5
- package/dist/index.d.ts +23 -5
- package/dist/index.js +34 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -180,6 +180,7 @@ app.post(
|
|
|
180
180
|
| `checkout.session.expired` | Session expired unpaid |
|
|
181
181
|
| `checkout.session.unresolved` | Underpaid / overpaid / wrong token — needs review |
|
|
182
182
|
| `checkout.refund.recorded` | An on-chain auto-refund (over / under / late payment) was recorded |
|
|
183
|
+
| `x402.payment.resolved` | An x402 payment that `/settle` left unresolved was later settled on-chain (see below) |
|
|
183
184
|
|
|
184
185
|
### ⚠️ Replay defense + at-least-once delivery
|
|
185
186
|
|
|
@@ -208,7 +209,7 @@ app.get('/report', x402({
|
|
|
208
209
|
apiKey: process.env.BITPAL_API_KEY, // bp_test_… or bp_live_…
|
|
209
210
|
payTo: '0xYourPayoutWallet',
|
|
210
211
|
network: 'base', // test key → 'base-sepolia'
|
|
211
|
-
amount: '
|
|
212
|
+
amount: '1.00', // human-readable — no decimals math
|
|
212
213
|
}), (req, res) => {
|
|
213
214
|
res.json({ report: '…' }); // req.x402Payment has the txHash
|
|
214
215
|
});
|
|
@@ -227,6 +228,12 @@ handler with `X-PAYMENT-RESPONSE` set. The middleware also does what the API can
|
|
|
227
228
|
timeout — answers `503` and asks for a retry with the *same* header. Handing back a `402` there
|
|
228
229
|
would make the caller re-sign and pay twice.
|
|
229
230
|
|
|
231
|
+
When `/settle` answers `503`, nobody knows yet whether the money moved — so BitPal watches the chain
|
|
232
|
+
and tells you how it ended with a **`x402.payment.resolved`** webhook (`status`: `confirmed` |
|
|
233
|
+
`reverted` | `expired`, plus `payment_id` and `tx_hash`). It fires only on that path: the normal case
|
|
234
|
+
already returned the result to you, and you should not have to handle the same outcome twice. Verify
|
|
235
|
+
it exactly like a checkout webhook.
|
|
236
|
+
|
|
230
237
|
Not on Express? `X402Gate` is the framework-neutral core — give it the header and the resource URL,
|
|
231
238
|
it tells you whether to bill or serve. On a `settled` result, call `commit()` once you have delivered
|
|
232
239
|
the response, or `release()` if you could not.
|
|
@@ -267,6 +274,9 @@ if (res.status === 402) {
|
|
|
267
274
|
| Base | USDC | `base` |
|
|
268
275
|
| Base Sepolia | USDC | `base-sepolia` |
|
|
269
276
|
|
|
277
|
+
Amounts are human-readable (`'1.00'`), the same model as `line_items[].amount` — the server converts.
|
|
278
|
+
Pass `amountAtomic` instead if you already have raw units.
|
|
279
|
+
|
|
270
280
|
EOA signers only — smart-contract wallets cannot sign EIP-3009 authorizations. Amounts are exact:
|
|
271
281
|
over- and underpayment are both rejected, and this lane has no refund path. Authorizations are valid
|
|
272
282
|
for 90–3600 seconds (the server's floor is 60s; the extra 30s absorbs clock skew and round trips).
|
package/dist/index.cjs
CHANGED
|
@@ -315,12 +315,23 @@ var BitPalCheckoutClient = class {
|
|
|
315
315
|
);
|
|
316
316
|
}
|
|
317
317
|
/* ─── Fees ─── */
|
|
318
|
-
/**
|
|
318
|
+
/**
|
|
319
|
+
* 수수료 미리보기 — `/v1/checkout/fee-preview`.
|
|
320
|
+
*
|
|
321
|
+
* `amount` 는 **사람 표기**다("100", "0.5") — line item / Price / x402 와 같은 모델이라
|
|
322
|
+
* decimals 를 계산할 필요가 없다. raw μUSD 를 직접 넣으려면 `amountAtomic` 을 쓴다(상호 배타).
|
|
323
|
+
* 응답은 계속 atomic 이다(계산 결과라 정밀도가 곧 계약이다).
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* const { data } = await bp.checkout.previewFee('100'); // $100
|
|
327
|
+
* const { data } = await bp.checkout.previewFee({ amountAtomic: '100000000' });
|
|
328
|
+
*/
|
|
319
329
|
async previewFee(amount) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
330
|
+
const qs = typeof amount === "string" ? `amount=${encodeURIComponent(amount)}` : `amount_atomic=${encodeURIComponent(amount.amountAtomic)}`;
|
|
331
|
+
if (typeof amount === "string" && !/^(0|[1-9]\d*)(\.\d{1,6})?$/.test(amount)) {
|
|
332
|
+
throw new Error('[BitPal] previewFee: amount must be a decimal string like "100" or "0.5" (max 6 decimals). For raw \u03BCUSD use { amountAtomic }.');
|
|
333
|
+
}
|
|
334
|
+
return this.request("GET", `/v1/checkout/fee-preview?${qs}`);
|
|
324
335
|
}
|
|
325
336
|
};
|
|
326
337
|
var BitPalError = class extends Error {
|
|
@@ -340,7 +351,12 @@ var CHECKOUT_WEBHOOK_EVENTS = [
|
|
|
340
351
|
"checkout.session.settled",
|
|
341
352
|
"checkout.session.expired",
|
|
342
353
|
"checkout.session.unresolved",
|
|
343
|
-
"checkout.refund.recorded"
|
|
354
|
+
"checkout.refund.recorded",
|
|
355
|
+
// x402 결제의 **사후** 확정. 정상 경로(/settle 이 그 자리에서 결과를 반환)는 발행되지 않는다 —
|
|
356
|
+
// 이미 아는 결과를 중복으로 받지 않게 하기 위함이다. /settle 이 503 을 반환해 결과를 모른 채
|
|
357
|
+
// 남았을 때, reconciler 가 체인을 보고 종결시키면 이 이벤트가 온다.
|
|
358
|
+
// payload: { payment_id, status: 'confirmed'|'reverted'|'expired', tx_hash, resource, amount, … }
|
|
359
|
+
"x402.payment.resolved"
|
|
344
360
|
];
|
|
345
361
|
var WEBHOOK_HEADERS = {
|
|
346
362
|
signature: "X-Webhook-Signature-256",
|
|
@@ -476,8 +492,16 @@ var X402Gate = class {
|
|
|
476
492
|
if (!/^0x[a-fA-F0-9]{40}$/.test(opts.payTo)) {
|
|
477
493
|
throw new Error("[BitPal] x402: payTo must be a 0x EVM address (your registered payout wallet).");
|
|
478
494
|
}
|
|
479
|
-
|
|
480
|
-
|
|
495
|
+
const hasHuman = opts.amount != null;
|
|
496
|
+
const hasAtomic = opts.amountAtomic != null;
|
|
497
|
+
if (hasHuman === hasAtomic) {
|
|
498
|
+
throw new Error('[BitPal] x402: provide exactly one of `amount` ("0.1") or `amountAtomic` ("100000").');
|
|
499
|
+
}
|
|
500
|
+
if (hasHuman && !/^(0|[1-9]\d*)(\.\d{1,6})?$/.test(opts.amount)) {
|
|
501
|
+
throw new Error('[BitPal] x402: amount must be a decimal string like "0.1" or "29" (max 6 decimals).');
|
|
502
|
+
}
|
|
503
|
+
if (hasAtomic && !/^\d+$/.test(opts.amountAtomic)) {
|
|
504
|
+
throw new Error('[BitPal] x402: amountAtomic must be an integer string (USDC 6dp, $1 = "1000000").');
|
|
481
505
|
}
|
|
482
506
|
const timeoutSeconds = opts.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
|
|
483
507
|
if (timeoutSeconds < MIN_VALIDITY_SECONDS || timeoutSeconds > MAX_VALIDITY_SECONDS) {
|
|
@@ -495,7 +519,8 @@ var X402Gate = class {
|
|
|
495
519
|
async requirements(resource) {
|
|
496
520
|
return this.request("POST", "/v1/x402/requirements", {
|
|
497
521
|
payTo: this.opts.payTo,
|
|
498
|
-
|
|
522
|
+
// 사람 표기 → atomic 변환은 **서버가** 한다. SDK 가 decimals 를 알 필요가 없다.
|
|
523
|
+
...this.opts.amount != null ? { amount: this.opts.amount } : { amountAtomic: this.opts.amountAtomic },
|
|
499
524
|
resource,
|
|
500
525
|
...this.opts.network ? { network: this.opts.network } : {},
|
|
501
526
|
...this.opts.asset ? { asset: this.opts.asset } : {},
|
package/dist/index.d.cts
CHANGED
|
@@ -556,8 +556,20 @@ declare class BitPalCheckoutClient {
|
|
|
556
556
|
* 통지되므로, 서버 사이드 확정은 webhook 을 신뢰(폴링은 buyer UX 용).
|
|
557
557
|
*/
|
|
558
558
|
getStatus(sessionId: string): Promise<ApiResponse<SessionStatus>>;
|
|
559
|
-
/**
|
|
560
|
-
|
|
559
|
+
/**
|
|
560
|
+
* 수수료 미리보기 — `/v1/checkout/fee-preview`.
|
|
561
|
+
*
|
|
562
|
+
* `amount` 는 **사람 표기**다("100", "0.5") — line item / Price / x402 와 같은 모델이라
|
|
563
|
+
* decimals 를 계산할 필요가 없다. raw μUSD 를 직접 넣으려면 `amountAtomic` 을 쓴다(상호 배타).
|
|
564
|
+
* 응답은 계속 atomic 이다(계산 결과라 정밀도가 곧 계약이다).
|
|
565
|
+
*
|
|
566
|
+
* @example
|
|
567
|
+
* const { data } = await bp.checkout.previewFee('100'); // $100
|
|
568
|
+
* const { data } = await bp.checkout.previewFee({ amountAtomic: '100000000' });
|
|
569
|
+
*/
|
|
570
|
+
previewFee(amount: string | {
|
|
571
|
+
amountAtomic: string;
|
|
572
|
+
}): Promise<ApiResponse<FeePreview>>;
|
|
561
573
|
}
|
|
562
574
|
declare class BitPalError extends Error {
|
|
563
575
|
readonly statusCode: number;
|
|
@@ -572,7 +584,7 @@ declare class BitPalError extends Error {
|
|
|
572
584
|
/**
|
|
573
585
|
* BitPal 이 발송하는 webhook 이벤트 타입 전체 목록.
|
|
574
586
|
*/
|
|
575
|
-
declare const CHECKOUT_WEBHOOK_EVENTS: readonly ["checkout.session.created", "checkout.session.deposit_detected", "checkout.session.paid", "checkout.session.settled", "checkout.session.expired", "checkout.session.unresolved", "checkout.refund.recorded"];
|
|
587
|
+
declare const CHECKOUT_WEBHOOK_EVENTS: readonly ["checkout.session.created", "checkout.session.deposit_detected", "checkout.session.paid", "checkout.session.settled", "checkout.session.expired", "checkout.session.unresolved", "checkout.refund.recorded", "x402.payment.resolved"];
|
|
576
588
|
type CheckoutWebhookEvent = (typeof CHECKOUT_WEBHOOK_EVENTS)[number];
|
|
577
589
|
/**
|
|
578
590
|
* Canonical envelope (HTTP body).
|
|
@@ -785,8 +797,14 @@ interface X402GateOptions {
|
|
|
785
797
|
apiKey: string;
|
|
786
798
|
/** 정산 지갑. 콘솔에 등록한 payout 주소와 같은 값이어야 한다. */
|
|
787
799
|
payTo: string;
|
|
788
|
-
/**
|
|
789
|
-
|
|
800
|
+
/**
|
|
801
|
+
* 호출 1건당 받을 총액. **사람 표기 문자열**이다 — `'0.1'`, `'1'`, `'29.00'`.
|
|
802
|
+
* 체인·토큰 decimals 를 계산할 필요가 없다(체크아웃 `line_items[].amount` 와 같은 모델).
|
|
803
|
+
* 수수료는 이 금액에서 차감된다. raw atomic 을 직접 넣으려면 `amountAtomic` 을 쓴다(상호 배타).
|
|
804
|
+
*/
|
|
805
|
+
amount?: string;
|
|
806
|
+
/** raw atomic 정수 문자열(고급). USDC 6dp 기준 $1 = `'1000000'`. `amount` 와 상호 배타. */
|
|
807
|
+
amountAtomic?: string;
|
|
790
808
|
/** 생략 시 API 키 모드의 기본 자산(현재 Base / Base Sepolia). */
|
|
791
809
|
network?: string;
|
|
792
810
|
/** 기본 `'USDC'`. */
|
package/dist/index.d.ts
CHANGED
|
@@ -556,8 +556,20 @@ declare class BitPalCheckoutClient {
|
|
|
556
556
|
* 통지되므로, 서버 사이드 확정은 webhook 을 신뢰(폴링은 buyer UX 용).
|
|
557
557
|
*/
|
|
558
558
|
getStatus(sessionId: string): Promise<ApiResponse<SessionStatus>>;
|
|
559
|
-
/**
|
|
560
|
-
|
|
559
|
+
/**
|
|
560
|
+
* 수수료 미리보기 — `/v1/checkout/fee-preview`.
|
|
561
|
+
*
|
|
562
|
+
* `amount` 는 **사람 표기**다("100", "0.5") — line item / Price / x402 와 같은 모델이라
|
|
563
|
+
* decimals 를 계산할 필요가 없다. raw μUSD 를 직접 넣으려면 `amountAtomic` 을 쓴다(상호 배타).
|
|
564
|
+
* 응답은 계속 atomic 이다(계산 결과라 정밀도가 곧 계약이다).
|
|
565
|
+
*
|
|
566
|
+
* @example
|
|
567
|
+
* const { data } = await bp.checkout.previewFee('100'); // $100
|
|
568
|
+
* const { data } = await bp.checkout.previewFee({ amountAtomic: '100000000' });
|
|
569
|
+
*/
|
|
570
|
+
previewFee(amount: string | {
|
|
571
|
+
amountAtomic: string;
|
|
572
|
+
}): Promise<ApiResponse<FeePreview>>;
|
|
561
573
|
}
|
|
562
574
|
declare class BitPalError extends Error {
|
|
563
575
|
readonly statusCode: number;
|
|
@@ -572,7 +584,7 @@ declare class BitPalError extends Error {
|
|
|
572
584
|
/**
|
|
573
585
|
* BitPal 이 발송하는 webhook 이벤트 타입 전체 목록.
|
|
574
586
|
*/
|
|
575
|
-
declare const CHECKOUT_WEBHOOK_EVENTS: readonly ["checkout.session.created", "checkout.session.deposit_detected", "checkout.session.paid", "checkout.session.settled", "checkout.session.expired", "checkout.session.unresolved", "checkout.refund.recorded"];
|
|
587
|
+
declare const CHECKOUT_WEBHOOK_EVENTS: readonly ["checkout.session.created", "checkout.session.deposit_detected", "checkout.session.paid", "checkout.session.settled", "checkout.session.expired", "checkout.session.unresolved", "checkout.refund.recorded", "x402.payment.resolved"];
|
|
576
588
|
type CheckoutWebhookEvent = (typeof CHECKOUT_WEBHOOK_EVENTS)[number];
|
|
577
589
|
/**
|
|
578
590
|
* Canonical envelope (HTTP body).
|
|
@@ -785,8 +797,14 @@ interface X402GateOptions {
|
|
|
785
797
|
apiKey: string;
|
|
786
798
|
/** 정산 지갑. 콘솔에 등록한 payout 주소와 같은 값이어야 한다. */
|
|
787
799
|
payTo: string;
|
|
788
|
-
/**
|
|
789
|
-
|
|
800
|
+
/**
|
|
801
|
+
* 호출 1건당 받을 총액. **사람 표기 문자열**이다 — `'0.1'`, `'1'`, `'29.00'`.
|
|
802
|
+
* 체인·토큰 decimals 를 계산할 필요가 없다(체크아웃 `line_items[].amount` 와 같은 모델).
|
|
803
|
+
* 수수료는 이 금액에서 차감된다. raw atomic 을 직접 넣으려면 `amountAtomic` 을 쓴다(상호 배타).
|
|
804
|
+
*/
|
|
805
|
+
amount?: string;
|
|
806
|
+
/** raw atomic 정수 문자열(고급). USDC 6dp 기준 $1 = `'1000000'`. `amount` 와 상호 배타. */
|
|
807
|
+
amountAtomic?: string;
|
|
790
808
|
/** 생략 시 API 키 모드의 기본 자산(현재 Base / Base Sepolia). */
|
|
791
809
|
network?: string;
|
|
792
810
|
/** 기본 `'USDC'`. */
|
package/dist/index.js
CHANGED
|
@@ -256,12 +256,23 @@ var BitPalCheckoutClient = class {
|
|
|
256
256
|
);
|
|
257
257
|
}
|
|
258
258
|
/* ─── Fees ─── */
|
|
259
|
-
/**
|
|
259
|
+
/**
|
|
260
|
+
* 수수료 미리보기 — `/v1/checkout/fee-preview`.
|
|
261
|
+
*
|
|
262
|
+
* `amount` 는 **사람 표기**다("100", "0.5") — line item / Price / x402 와 같은 모델이라
|
|
263
|
+
* decimals 를 계산할 필요가 없다. raw μUSD 를 직접 넣으려면 `amountAtomic` 을 쓴다(상호 배타).
|
|
264
|
+
* 응답은 계속 atomic 이다(계산 결과라 정밀도가 곧 계약이다).
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* const { data } = await bp.checkout.previewFee('100'); // $100
|
|
268
|
+
* const { data } = await bp.checkout.previewFee({ amountAtomic: '100000000' });
|
|
269
|
+
*/
|
|
260
270
|
async previewFee(amount) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
271
|
+
const qs = typeof amount === "string" ? `amount=${encodeURIComponent(amount)}` : `amount_atomic=${encodeURIComponent(amount.amountAtomic)}`;
|
|
272
|
+
if (typeof amount === "string" && !/^(0|[1-9]\d*)(\.\d{1,6})?$/.test(amount)) {
|
|
273
|
+
throw new Error('[BitPal] previewFee: amount must be a decimal string like "100" or "0.5" (max 6 decimals). For raw \u03BCUSD use { amountAtomic }.');
|
|
274
|
+
}
|
|
275
|
+
return this.request("GET", `/v1/checkout/fee-preview?${qs}`);
|
|
265
276
|
}
|
|
266
277
|
};
|
|
267
278
|
var BitPalError = class extends Error {
|
|
@@ -281,7 +292,12 @@ var CHECKOUT_WEBHOOK_EVENTS = [
|
|
|
281
292
|
"checkout.session.settled",
|
|
282
293
|
"checkout.session.expired",
|
|
283
294
|
"checkout.session.unresolved",
|
|
284
|
-
"checkout.refund.recorded"
|
|
295
|
+
"checkout.refund.recorded",
|
|
296
|
+
// x402 결제의 **사후** 확정. 정상 경로(/settle 이 그 자리에서 결과를 반환)는 발행되지 않는다 —
|
|
297
|
+
// 이미 아는 결과를 중복으로 받지 않게 하기 위함이다. /settle 이 503 을 반환해 결과를 모른 채
|
|
298
|
+
// 남았을 때, reconciler 가 체인을 보고 종결시키면 이 이벤트가 온다.
|
|
299
|
+
// payload: { payment_id, status: 'confirmed'|'reverted'|'expired', tx_hash, resource, amount, … }
|
|
300
|
+
"x402.payment.resolved"
|
|
285
301
|
];
|
|
286
302
|
var WEBHOOK_HEADERS = {
|
|
287
303
|
signature: "X-Webhook-Signature-256",
|
|
@@ -417,8 +433,16 @@ var X402Gate = class {
|
|
|
417
433
|
if (!/^0x[a-fA-F0-9]{40}$/.test(opts.payTo)) {
|
|
418
434
|
throw new Error("[BitPal] x402: payTo must be a 0x EVM address (your registered payout wallet).");
|
|
419
435
|
}
|
|
420
|
-
|
|
421
|
-
|
|
436
|
+
const hasHuman = opts.amount != null;
|
|
437
|
+
const hasAtomic = opts.amountAtomic != null;
|
|
438
|
+
if (hasHuman === hasAtomic) {
|
|
439
|
+
throw new Error('[BitPal] x402: provide exactly one of `amount` ("0.1") or `amountAtomic` ("100000").');
|
|
440
|
+
}
|
|
441
|
+
if (hasHuman && !/^(0|[1-9]\d*)(\.\d{1,6})?$/.test(opts.amount)) {
|
|
442
|
+
throw new Error('[BitPal] x402: amount must be a decimal string like "0.1" or "29" (max 6 decimals).');
|
|
443
|
+
}
|
|
444
|
+
if (hasAtomic && !/^\d+$/.test(opts.amountAtomic)) {
|
|
445
|
+
throw new Error('[BitPal] x402: amountAtomic must be an integer string (USDC 6dp, $1 = "1000000").');
|
|
422
446
|
}
|
|
423
447
|
const timeoutSeconds = opts.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
|
|
424
448
|
if (timeoutSeconds < MIN_VALIDITY_SECONDS || timeoutSeconds > MAX_VALIDITY_SECONDS) {
|
|
@@ -436,7 +460,8 @@ var X402Gate = class {
|
|
|
436
460
|
async requirements(resource) {
|
|
437
461
|
return this.request("POST", "/v1/x402/requirements", {
|
|
438
462
|
payTo: this.opts.payTo,
|
|
439
|
-
|
|
463
|
+
// 사람 표기 → atomic 변환은 **서버가** 한다. SDK 가 decimals 를 알 필요가 없다.
|
|
464
|
+
...this.opts.amount != null ? { amount: this.opts.amount } : { amountAtomic: this.opts.amountAtomic },
|
|
440
465
|
resource,
|
|
441
466
|
...this.opts.network ? { network: this.opts.network } : {},
|
|
442
467
|
...this.opts.asset ? { asset: this.opts.asset } : {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitpal/checkout",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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": {
|