@bitpal/checkout 0.1.0 → 0.2.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/MIGRATION_v0.2.md +135 -0
- package/dist/index.d.ts +199 -3
- package/dist/index.js +87 -2
- package/package.json +11 -9
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# @bitpal/checkout — Migration Guide v0.1.x → v0.2.0
|
|
2
|
+
|
|
3
|
+
> Audit doc 11 §3 C-01 option 2 (PaymentAuthorization 이중방어) 적용으로 인한 SDK 메이저 bump.
|
|
4
|
+
|
|
5
|
+
## Why this change
|
|
6
|
+
|
|
7
|
+
v0.1.x 까지의 `payWithAuthorization` 흐름은 buyer 의 EIP-3009 USDC sig 만 받았다. 이 sig 는
|
|
8
|
+
`(from, to, value, validAfter, validBefore, nonce)` 만 바인딩하므로, **paymentId / merchant /
|
|
9
|
+
amount / fee / reserve / duration** 은 호출자(BitPal gateway)가 자유롭게 지정 가능했다.
|
|
10
|
+
|
|
11
|
+
→ gateway 키 탈취 시 공격자가 (a) buyer 의 EIP-3009 sig 입수 후 (b) 자기 주소를 merchant 로 설정,
|
|
12
|
+
(c) `depositWithAuthorization` 호출 → buyer 자금이 attacker merchant 로 흘러간다.
|
|
13
|
+
|
|
14
|
+
v0.2.0 는 buyer 가 **결제 의도 전체** 에 별도 EIP-712 서명 (`PaymentAuthorization`) 을 한다. gateway
|
|
15
|
+
키 탈취 시에도 buyer 의도 밖 결제 생성 불가 (이중방어).
|
|
16
|
+
|
|
17
|
+
## Breaking changes
|
|
18
|
+
|
|
19
|
+
### `BitPal.checkout.payWithAuthorization(sessionId, params)`
|
|
20
|
+
|
|
21
|
+
`params.payment_auth` 필드가 **필수** 가 됐다.
|
|
22
|
+
|
|
23
|
+
**Before (v0.1.x):**
|
|
24
|
+
```ts
|
|
25
|
+
await bp.checkout.payWithAuthorization(sessionId, {
|
|
26
|
+
session_token: 'xyz...',
|
|
27
|
+
chain: 'base',
|
|
28
|
+
authorization: {
|
|
29
|
+
from: buyer.address,
|
|
30
|
+
validAfter: '0',
|
|
31
|
+
validBefore: String(Math.floor(Date.now()/1000) + 3600),
|
|
32
|
+
authNonce: '0x...',
|
|
33
|
+
v: 27, r: '0x...', s: '0x...',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**After (v0.2.0):**
|
|
39
|
+
```ts
|
|
40
|
+
import {
|
|
41
|
+
buildPaymentAuthTypedData,
|
|
42
|
+
sessionIdToPaymentId,
|
|
43
|
+
} from '@bitpal/checkout';
|
|
44
|
+
|
|
45
|
+
// 1. PaymentAuthorization nonce 조회 (CheckoutEscrow.paymentAuthNonce)
|
|
46
|
+
const nonce = await escrow.read.paymentAuthNonce([buyer.address]);
|
|
47
|
+
|
|
48
|
+
// 2. PaymentAuthorization typed-data sig
|
|
49
|
+
const paymentAuthDeadline = Math.floor(Date.now()/1000) + 3600;
|
|
50
|
+
const paymentId = sessionIdToPaymentId(sessionId);
|
|
51
|
+
const paymentAuthTd = buildPaymentAuthTypedData({
|
|
52
|
+
escrowAddress: escrow.address,
|
|
53
|
+
chainId: 8453,
|
|
54
|
+
paymentId,
|
|
55
|
+
payer: buyer.address,
|
|
56
|
+
merchant: session.seller_wallet,
|
|
57
|
+
token: session.token_address,
|
|
58
|
+
amount: session.amount_total,
|
|
59
|
+
feeAmount: '0',
|
|
60
|
+
reserveAmount: '0',
|
|
61
|
+
duration: '0',
|
|
62
|
+
deadline: String(paymentAuthDeadline),
|
|
63
|
+
nonce: String(nonce),
|
|
64
|
+
});
|
|
65
|
+
const paymentAuthSig = await wallet.signTypedData(paymentAuthTd);
|
|
66
|
+
const paymentAuthParsed = parseSignature(paymentAuthSig); // viem helper
|
|
67
|
+
|
|
68
|
+
// 3. USDC EIP-3009 sig (변경 없음 — 기존 흐름 그대로)
|
|
69
|
+
const usdcSig = await wallet.signTypedData({ /* TransferWithAuthorization ... */ });
|
|
70
|
+
const usdcParsed = parseSignature(usdcSig);
|
|
71
|
+
|
|
72
|
+
// 4. payWithAuthorization 호출 — payment_auth 필드 추가
|
|
73
|
+
await bp.checkout.payWithAuthorization(sessionId, {
|
|
74
|
+
session_token: 'xyz...',
|
|
75
|
+
chain: 'base',
|
|
76
|
+
authorization: {
|
|
77
|
+
from: buyer.address,
|
|
78
|
+
validAfter: '0',
|
|
79
|
+
validBefore: String(Math.floor(Date.now()/1000) + 3600),
|
|
80
|
+
authNonce: '0x...',
|
|
81
|
+
v: Number(usdcParsed.v), r: usdcParsed.r, s: usdcParsed.s,
|
|
82
|
+
},
|
|
83
|
+
// ← 신규 필수
|
|
84
|
+
payment_auth: {
|
|
85
|
+
deadline: String(paymentAuthDeadline),
|
|
86
|
+
duration: '0',
|
|
87
|
+
v: Number(paymentAuthParsed.v),
|
|
88
|
+
r: paymentAuthParsed.r,
|
|
89
|
+
s: paymentAuthParsed.s,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 백엔드 응답에서 `payment_auth_nonce` 노출
|
|
95
|
+
|
|
96
|
+
서버는 `GET /v1/checkout/sessions/:id/public` 응답에 `payment_auth_nonce` 필드를 포함한다
|
|
97
|
+
(Q5 적용 후). buyer UI 가 컨트랙트를 직접 호출하지 않고도 nonce 를 얻을 수 있다. 단,
|
|
98
|
+
`merchant_wallet` (seller_wallet) 도 sig 에 들어가므로 public response 에서 확인할 수 있어야 한다.
|
|
99
|
+
|
|
100
|
+
## Non-breaking changes
|
|
101
|
+
|
|
102
|
+
다음 SDK 흐름은 변경 없음:
|
|
103
|
+
|
|
104
|
+
- `createSession()`, `getSession()`, `listSessions()`, `previewFee()`
|
|
105
|
+
- `refundSession()`, `recordRefund()`, `listRefunds()`
|
|
106
|
+
- `submitExternalPayment()` (Mode A — buyer 가 본인 지갑으로 직접 deposit)
|
|
107
|
+
- Webhook helpers (`verifyWebhookSignature`, `parseWebhookEvent`)
|
|
108
|
+
- Amount helpers (`toAtomicUSDC`, `fromAtomicUSDC`)
|
|
109
|
+
|
|
110
|
+
## Why `payment_auth` is required (no opt-out)
|
|
111
|
+
|
|
112
|
+
Q4 PR 적용 후 `CheckoutEscrow.depositWithAuthorization` 가 PaymentAuthorization sig 를 강제한다
|
|
113
|
+
(컨트랙트 require). SDK 가 sig 없이 호출 시 백엔드가 `PAYMENT_AUTH_REQUIRED` (400) 로 거부.
|
|
114
|
+
|
|
115
|
+
## Compatibility matrix
|
|
116
|
+
|
|
117
|
+
| SDK version | Backend (CheckoutEscrow contract) | Result |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| v0.1.x | post-Q5 (PaymentAuthorization 필수) | ❌ `PAYMENT_AUTH_REQUIRED` 400 |
|
|
120
|
+
| v0.2.0 | post-Q5 | ✅ OK |
|
|
121
|
+
| v0.2.0 | pre-Q5 (legacy 미배포 contract) | ❌ contract revert (signature 추가 인자 없음) |
|
|
122
|
+
|
|
123
|
+
→ SDK v0.2.0 와 컨트랙트 Q4+Q5 적용은 **동시 배포** 가 원칙. testnet 부터 함께 갈아끼움.
|
|
124
|
+
|
|
125
|
+
## New helpers
|
|
126
|
+
|
|
127
|
+
- `buildPaymentAuthTypedData(args)` — `signTypedData` 호환 객체 생성
|
|
128
|
+
- `buildConfirmReceiptTypedData(args)` — Q4 H-01 `confirmReceiptWithSignature` 용 (off-chain receipt confirmation 직접 구현 시 사용. 기본 흐름에서는 BitPal hosted page 가 처리)
|
|
129
|
+
- `sessionIdToPaymentId(sessionId)` — 32-byte hex 변환 (= 백엔드 `toBytes32()`)
|
|
130
|
+
|
|
131
|
+
## See also
|
|
132
|
+
|
|
133
|
+
- `docs/_dan/b2b-spec/2026-05-14-11-smart-contract-security-audit.md` §3 C-01
|
|
134
|
+
- `docs/_dan/b2b-spec/2026-05-18-14-post-audit-remaining-hardening-plan.md` §4 (PR-C 실행 가이드)
|
|
135
|
+
- README.md §"Gasless lane (Hosted / Gasless)"
|
package/dist/index.d.ts
CHANGED
|
@@ -145,6 +145,7 @@ interface SubmitExternalPaymentResult {
|
|
|
145
145
|
interface PayWithAuthorizationParams {
|
|
146
146
|
chain?: string;
|
|
147
147
|
session_token: string;
|
|
148
|
+
/** EIP-3009 TransferWithAuthorization sig (USDC). */
|
|
148
149
|
authorization: {
|
|
149
150
|
from: string;
|
|
150
151
|
validAfter?: string;
|
|
@@ -154,6 +155,39 @@ interface PayWithAuthorizationParams {
|
|
|
154
155
|
r: string;
|
|
155
156
|
s: string;
|
|
156
157
|
};
|
|
158
|
+
/**
|
|
159
|
+
* Q5 C-01 옵션 2 (SDK v0.2.0): PaymentAuthorization typed-data sig.
|
|
160
|
+
* Buyer 가 결제 의도 전체 (paymentId, merchant, amount, fee, reserve, duration) 에 서명.
|
|
161
|
+
* gateway 키 탈취 시에도 buyer 의도 밖 결제 생성 차단.
|
|
162
|
+
*
|
|
163
|
+
* 서명 생성: `buildPaymentAuthTypedData()` helper 또는 viem `signTypedData` 직접 호출.
|
|
164
|
+
* Required for SDK v0.2.x+ — gasless lane 모든 호출에 필수. v0.1.x 는 미지원.
|
|
165
|
+
*/
|
|
166
|
+
payment_auth: {
|
|
167
|
+
deadline: string;
|
|
168
|
+
duration: string;
|
|
169
|
+
v: number;
|
|
170
|
+
r: string;
|
|
171
|
+
s: string;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Q5 C-01 옵션 2: buyer 가 서명할 EIP-712 PaymentAuthorization typed-data 의 입력 인자.
|
|
176
|
+
* `buildPaymentAuthTypedData()` 가 이를 받아 viem 호환 객체를 반환.
|
|
177
|
+
*/
|
|
178
|
+
interface PaymentAuthTypedDataArgs {
|
|
179
|
+
escrowAddress: string;
|
|
180
|
+
chainId: number;
|
|
181
|
+
paymentId: string;
|
|
182
|
+
payer: string;
|
|
183
|
+
merchant: string;
|
|
184
|
+
token: string;
|
|
185
|
+
amount: string;
|
|
186
|
+
feeAmount: string;
|
|
187
|
+
reserveAmount: string;
|
|
188
|
+
duration: string;
|
|
189
|
+
deadline: string;
|
|
190
|
+
nonce: string;
|
|
157
191
|
}
|
|
158
192
|
interface CheckoutPaymentResult {
|
|
159
193
|
status: string;
|
|
@@ -344,10 +378,24 @@ declare class BitPalCheckoutClient {
|
|
|
344
378
|
/**
|
|
345
379
|
* Gasless checkout payment through the BitPal API relayer.
|
|
346
380
|
*
|
|
347
|
-
* The buyer signs
|
|
348
|
-
* `CheckoutEscrow.depositWithAuthorization()` with the platform relayer
|
|
381
|
+
* The buyer signs **two** EIP-712 typed-data structures and the API calls
|
|
382
|
+
* `CheckoutEscrow.depositWithAuthorization()` with the platform relayer:
|
|
383
|
+
*
|
|
384
|
+
* 1. **EIP-3009 USDC TransferWithAuthorization** (`params.authorization`) —
|
|
385
|
+
* authorizes USDC transfer from buyer to escrow.
|
|
386
|
+
* 2. **PaymentAuthorization** (`params.payment_auth`, since SDK v0.2.0) —
|
|
387
|
+
* binds the entire payment intent (paymentId, merchant, amount, fee,
|
|
388
|
+
* reserve, duration) to the buyer's signature. Without this, a compromised
|
|
389
|
+
* gateway key could redirect funds to an attacker-controlled merchant.
|
|
390
|
+
*
|
|
391
|
+
* Build the PaymentAuthorization typed-data with `buildPaymentAuthTypedData()`.
|
|
392
|
+
* The PaymentAuthorization nonce comes from `CheckoutEscrow.paymentAuthNonce(payer)`.
|
|
393
|
+
*
|
|
349
394
|
* Use the returned session/order state only as immediate UX feedback; the
|
|
350
395
|
* merchant backend should finalize fulfillment from signed webhooks.
|
|
396
|
+
*
|
|
397
|
+
* @breaking-change v0.2.0 — `payment_auth` field is now required. v0.1.x clients
|
|
398
|
+
* must upgrade their signing flow. See MIGRATION_v0.2.md.
|
|
351
399
|
*/
|
|
352
400
|
payWithAuthorization(sessionId: string, params: PayWithAuthorizationParams, opts?: IdempotencyOptions): Promise<ApiResponse<CheckoutPaymentResult>>;
|
|
353
401
|
/**
|
|
@@ -485,6 +533,154 @@ declare function toAtomicUSDC(decimal: string): string;
|
|
|
485
533
|
*/
|
|
486
534
|
declare function fromAtomicUSDC(atomic: string): string;
|
|
487
535
|
|
|
536
|
+
/**
|
|
537
|
+
* typed-data.ts — Q5 C-01 옵션 2 (SDK v0.2.0).
|
|
538
|
+
*
|
|
539
|
+
* BitPal Checkout 의 EIP-712 typed-data helper. SDK 가 모든 typed-data 작업을 직접
|
|
540
|
+
* 수행하지 않고 (서명은 머천트의 wallet provider 가) **typed-data 객체만 생성** 한다.
|
|
541
|
+
*
|
|
542
|
+
* 사용 예 (viem):
|
|
543
|
+
*
|
|
544
|
+
* ```ts
|
|
545
|
+
* import { buildPaymentAuthTypedData } from '@bitpal/checkout';
|
|
546
|
+
* import { signTypedData } from 'viem/accounts';
|
|
547
|
+
*
|
|
548
|
+
* const td = buildPaymentAuthTypedData({
|
|
549
|
+
* escrowAddress: '0x...',
|
|
550
|
+
* chainId: 8453,
|
|
551
|
+
* paymentId: '0x...', // = toBytes32(sessionId)
|
|
552
|
+
* payer: buyer.address,
|
|
553
|
+
* merchant: session.seller_wallet,
|
|
554
|
+
* token: session.token_address,
|
|
555
|
+
* amount: session.amount_total,
|
|
556
|
+
* feeAmount: '0',
|
|
557
|
+
* reserveAmount: '0',
|
|
558
|
+
* duration: '0',
|
|
559
|
+
* deadline: String(Math.floor(Date.now()/1000) + 3600),
|
|
560
|
+
* nonce: String(await escrow.read.paymentAuthNonce(buyer.address)),
|
|
561
|
+
* });
|
|
562
|
+
* const sig = await walletClient.signTypedData(td);
|
|
563
|
+
* ```
|
|
564
|
+
*
|
|
565
|
+
* 사용 예 (wagmi `useSignTypedData`):
|
|
566
|
+
*
|
|
567
|
+
* ```tsx
|
|
568
|
+
* const { signTypedDataAsync } = useSignTypedData();
|
|
569
|
+
* const td = buildPaymentAuthTypedData({ ... });
|
|
570
|
+
* const sig = await signTypedDataAsync(td);
|
|
571
|
+
* ```
|
|
572
|
+
*/
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Build EIP-712 typed-data object for PaymentAuthorization signing.
|
|
576
|
+
* Returns a viem-compatible structure (also compatible with wagmi/ethers v6 signTypedData).
|
|
577
|
+
*/
|
|
578
|
+
declare function buildPaymentAuthTypedData(args: PaymentAuthTypedDataArgs): {
|
|
579
|
+
readonly domain: {
|
|
580
|
+
readonly name: "CheckoutEscrow";
|
|
581
|
+
readonly version: "1";
|
|
582
|
+
readonly chainId: number;
|
|
583
|
+
readonly verifyingContract: `0x${string}`;
|
|
584
|
+
};
|
|
585
|
+
readonly types: {
|
|
586
|
+
readonly PaymentAuthorization: readonly [{
|
|
587
|
+
readonly name: "paymentId";
|
|
588
|
+
readonly type: "bytes32";
|
|
589
|
+
}, {
|
|
590
|
+
readonly name: "payer";
|
|
591
|
+
readonly type: "address";
|
|
592
|
+
}, {
|
|
593
|
+
readonly name: "merchant";
|
|
594
|
+
readonly type: "address";
|
|
595
|
+
}, {
|
|
596
|
+
readonly name: "token";
|
|
597
|
+
readonly type: "address";
|
|
598
|
+
}, {
|
|
599
|
+
readonly name: "amount";
|
|
600
|
+
readonly type: "uint256";
|
|
601
|
+
}, {
|
|
602
|
+
readonly name: "feeAmount";
|
|
603
|
+
readonly type: "uint256";
|
|
604
|
+
}, {
|
|
605
|
+
readonly name: "reserveAmount";
|
|
606
|
+
readonly type: "uint256";
|
|
607
|
+
}, {
|
|
608
|
+
readonly name: "duration";
|
|
609
|
+
readonly type: "uint256";
|
|
610
|
+
}, {
|
|
611
|
+
readonly name: "deadline";
|
|
612
|
+
readonly type: "uint256";
|
|
613
|
+
}, {
|
|
614
|
+
readonly name: "nonce";
|
|
615
|
+
readonly type: "uint256";
|
|
616
|
+
}];
|
|
617
|
+
};
|
|
618
|
+
readonly primaryType: "PaymentAuthorization";
|
|
619
|
+
readonly message: {
|
|
620
|
+
readonly paymentId: `0x${string}`;
|
|
621
|
+
readonly payer: `0x${string}`;
|
|
622
|
+
readonly merchant: `0x${string}`;
|
|
623
|
+
readonly token: `0x${string}`;
|
|
624
|
+
readonly amount: bigint;
|
|
625
|
+
readonly feeAmount: bigint;
|
|
626
|
+
readonly reserveAmount: bigint;
|
|
627
|
+
readonly duration: bigint;
|
|
628
|
+
readonly deadline: bigint;
|
|
629
|
+
readonly nonce: bigint;
|
|
630
|
+
};
|
|
631
|
+
};
|
|
632
|
+
/**
|
|
633
|
+
* Args for the EIP-712 ConfirmReceipt typed-data.
|
|
634
|
+
* Used when buyer signs an off-chain receipt confirmation that BitPal gateway relays
|
|
635
|
+
* on-chain via `confirmReceiptWithSignature` (Q4 H-01).
|
|
636
|
+
*/
|
|
637
|
+
interface ConfirmReceiptTypedDataArgs {
|
|
638
|
+
escrowAddress: string;
|
|
639
|
+
chainId: number;
|
|
640
|
+
paymentId: string;
|
|
641
|
+
payer: string;
|
|
642
|
+
deadline: string;
|
|
643
|
+
nonce: string;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Build EIP-712 typed-data object for ConfirmReceipt signing (Q4 H-01).
|
|
647
|
+
*/
|
|
648
|
+
declare function buildConfirmReceiptTypedData(args: ConfirmReceiptTypedDataArgs): {
|
|
649
|
+
readonly domain: {
|
|
650
|
+
readonly name: "CheckoutEscrow";
|
|
651
|
+
readonly version: "1";
|
|
652
|
+
readonly chainId: number;
|
|
653
|
+
readonly verifyingContract: `0x${string}`;
|
|
654
|
+
};
|
|
655
|
+
readonly types: {
|
|
656
|
+
readonly ConfirmReceipt: readonly [{
|
|
657
|
+
readonly name: "paymentId";
|
|
658
|
+
readonly type: "bytes32";
|
|
659
|
+
}, {
|
|
660
|
+
readonly name: "payer";
|
|
661
|
+
readonly type: "address";
|
|
662
|
+
}, {
|
|
663
|
+
readonly name: "deadline";
|
|
664
|
+
readonly type: "uint256";
|
|
665
|
+
}, {
|
|
666
|
+
readonly name: "nonce";
|
|
667
|
+
readonly type: "uint256";
|
|
668
|
+
}];
|
|
669
|
+
};
|
|
670
|
+
readonly primaryType: "ConfirmReceipt";
|
|
671
|
+
readonly message: {
|
|
672
|
+
readonly paymentId: `0x${string}`;
|
|
673
|
+
readonly payer: `0x${string}`;
|
|
674
|
+
readonly deadline: bigint;
|
|
675
|
+
readonly nonce: bigint;
|
|
676
|
+
};
|
|
677
|
+
};
|
|
678
|
+
/**
|
|
679
|
+
* Convert a session ID string into a 32-byte hex (`paymentId`) suitable for typed-data signing.
|
|
680
|
+
* Mirrors the backend `toBytes32(sessionId)` helper.
|
|
681
|
+
*/
|
|
682
|
+
declare function sessionIdToPaymentId(sessionId: string): `0x${string}`;
|
|
683
|
+
|
|
488
684
|
/**
|
|
489
685
|
* @bitpal/checkout — BitPal Checkout SDK
|
|
490
686
|
*
|
|
@@ -512,4 +708,4 @@ declare class BitPal {
|
|
|
512
708
|
constructor(config: BitPalConfig | string);
|
|
513
709
|
}
|
|
514
710
|
|
|
515
|
-
export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, CHECKOUT_WEBHOOK_EVENTS, type CheckoutPaymentResult, type CheckoutSession, type CheckoutWebhookEvent, type CreateSessionParams, type ExternalPaymentConfig, type FeePreview, type IdempotencyOptions, type LineItem, type ListResponse, type PayWithAuthorizationParams, type PaymentObservation, type RecordRefundParams, type RefundEntry, type RefundSessionParams, type RefundSessionResult, type SubmitExternalPaymentParams, type SubmitExternalPaymentResult, WEBHOOK_HEADERS, type WebhookPayload, fromAtomicUSDC, parseWebhookEvent, toAtomicUSDC, verifyWebhookSignature };
|
|
711
|
+
export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, CHECKOUT_WEBHOOK_EVENTS, type CheckoutPaymentResult, type CheckoutSession, type CheckoutWebhookEvent, type ConfirmReceiptTypedDataArgs, type CreateSessionParams, type ExternalPaymentConfig, type FeePreview, type IdempotencyOptions, type LineItem, type ListResponse, type PayWithAuthorizationParams, type PaymentAuthTypedDataArgs, type PaymentObservation, type RecordRefundParams, type RefundEntry, type RefundSessionParams, type RefundSessionResult, type SubmitExternalPaymentParams, type SubmitExternalPaymentResult, WEBHOOK_HEADERS, type WebhookPayload, buildConfirmReceiptTypedData, buildPaymentAuthTypedData, fromAtomicUSDC, parseWebhookEvent, sessionIdToPaymentId, toAtomicUSDC, verifyWebhookSignature };
|
package/dist/index.js
CHANGED
|
@@ -182,10 +182,24 @@ var BitPalCheckoutClient = class {
|
|
|
182
182
|
/**
|
|
183
183
|
* Gasless checkout payment through the BitPal API relayer.
|
|
184
184
|
*
|
|
185
|
-
* The buyer signs
|
|
186
|
-
* `CheckoutEscrow.depositWithAuthorization()` with the platform relayer
|
|
185
|
+
* The buyer signs **two** EIP-712 typed-data structures and the API calls
|
|
186
|
+
* `CheckoutEscrow.depositWithAuthorization()` with the platform relayer:
|
|
187
|
+
*
|
|
188
|
+
* 1. **EIP-3009 USDC TransferWithAuthorization** (`params.authorization`) —
|
|
189
|
+
* authorizes USDC transfer from buyer to escrow.
|
|
190
|
+
* 2. **PaymentAuthorization** (`params.payment_auth`, since SDK v0.2.0) —
|
|
191
|
+
* binds the entire payment intent (paymentId, merchant, amount, fee,
|
|
192
|
+
* reserve, duration) to the buyer's signature. Without this, a compromised
|
|
193
|
+
* gateway key could redirect funds to an attacker-controlled merchant.
|
|
194
|
+
*
|
|
195
|
+
* Build the PaymentAuthorization typed-data with `buildPaymentAuthTypedData()`.
|
|
196
|
+
* The PaymentAuthorization nonce comes from `CheckoutEscrow.paymentAuthNonce(payer)`.
|
|
197
|
+
*
|
|
187
198
|
* Use the returned session/order state only as immediate UX feedback; the
|
|
188
199
|
* merchant backend should finalize fulfillment from signed webhooks.
|
|
200
|
+
*
|
|
201
|
+
* @breaking-change v0.2.0 — `payment_auth` field is now required. v0.1.x clients
|
|
202
|
+
* must upgrade their signing flow. See MIGRATION_v0.2.md.
|
|
189
203
|
*/
|
|
190
204
|
async payWithAuthorization(sessionId, params, opts) {
|
|
191
205
|
return this.request(
|
|
@@ -304,6 +318,74 @@ function fromAtomicUSDC(atomic) {
|
|
|
304
318
|
return fracStr.length > 0 ? `${whole}.${fracStr.padEnd(2, "0")}` : `${whole}.00`;
|
|
305
319
|
}
|
|
306
320
|
|
|
321
|
+
// src/typed-data.ts
|
|
322
|
+
function buildPaymentAuthTypedData(args) {
|
|
323
|
+
return {
|
|
324
|
+
domain: {
|
|
325
|
+
name: "CheckoutEscrow",
|
|
326
|
+
version: "1",
|
|
327
|
+
chainId: args.chainId,
|
|
328
|
+
verifyingContract: args.escrowAddress
|
|
329
|
+
},
|
|
330
|
+
types: {
|
|
331
|
+
PaymentAuthorization: [
|
|
332
|
+
{ name: "paymentId", type: "bytes32" },
|
|
333
|
+
{ name: "payer", type: "address" },
|
|
334
|
+
{ name: "merchant", type: "address" },
|
|
335
|
+
{ name: "token", type: "address" },
|
|
336
|
+
{ name: "amount", type: "uint256" },
|
|
337
|
+
{ name: "feeAmount", type: "uint256" },
|
|
338
|
+
{ name: "reserveAmount", type: "uint256" },
|
|
339
|
+
{ name: "duration", type: "uint256" },
|
|
340
|
+
{ name: "deadline", type: "uint256" },
|
|
341
|
+
{ name: "nonce", type: "uint256" }
|
|
342
|
+
]
|
|
343
|
+
},
|
|
344
|
+
primaryType: "PaymentAuthorization",
|
|
345
|
+
message: {
|
|
346
|
+
paymentId: args.paymentId,
|
|
347
|
+
payer: args.payer,
|
|
348
|
+
merchant: args.merchant,
|
|
349
|
+
token: args.token,
|
|
350
|
+
amount: BigInt(args.amount),
|
|
351
|
+
feeAmount: BigInt(args.feeAmount),
|
|
352
|
+
reserveAmount: BigInt(args.reserveAmount),
|
|
353
|
+
duration: BigInt(args.duration),
|
|
354
|
+
deadline: BigInt(args.deadline),
|
|
355
|
+
nonce: BigInt(args.nonce)
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function buildConfirmReceiptTypedData(args) {
|
|
360
|
+
return {
|
|
361
|
+
domain: {
|
|
362
|
+
name: "CheckoutEscrow",
|
|
363
|
+
version: "1",
|
|
364
|
+
chainId: args.chainId,
|
|
365
|
+
verifyingContract: args.escrowAddress
|
|
366
|
+
},
|
|
367
|
+
types: {
|
|
368
|
+
ConfirmReceipt: [
|
|
369
|
+
{ name: "paymentId", type: "bytes32" },
|
|
370
|
+
{ name: "payer", type: "address" },
|
|
371
|
+
{ name: "deadline", type: "uint256" },
|
|
372
|
+
{ name: "nonce", type: "uint256" }
|
|
373
|
+
]
|
|
374
|
+
},
|
|
375
|
+
primaryType: "ConfirmReceipt",
|
|
376
|
+
message: {
|
|
377
|
+
paymentId: args.paymentId,
|
|
378
|
+
payer: args.payer,
|
|
379
|
+
deadline: BigInt(args.deadline),
|
|
380
|
+
nonce: BigInt(args.nonce)
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function sessionIdToPaymentId(sessionId) {
|
|
385
|
+
const hex = Array.from(new TextEncoder().encode(sessionId)).map((b) => b.toString(16).padStart(2, "0")).join("").padEnd(64, "0");
|
|
386
|
+
return `0x${hex}`;
|
|
387
|
+
}
|
|
388
|
+
|
|
307
389
|
// src/index.ts
|
|
308
390
|
var BitPal = class {
|
|
309
391
|
checkout;
|
|
@@ -318,8 +400,11 @@ export {
|
|
|
318
400
|
BitPalError,
|
|
319
401
|
CHECKOUT_WEBHOOK_EVENTS,
|
|
320
402
|
WEBHOOK_HEADERS,
|
|
403
|
+
buildConfirmReceiptTypedData,
|
|
404
|
+
buildPaymentAuthTypedData,
|
|
321
405
|
fromAtomicUSDC,
|
|
322
406
|
parseWebhookEvent,
|
|
407
|
+
sessionIdToPaymentId,
|
|
323
408
|
toAtomicUSDC,
|
|
324
409
|
verifyWebhookSignature
|
|
325
410
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitpal/checkout",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "BitPal Checkout SDK — crypto payment sessions, external on-chain payment verification, gasless checkout, refunds, fee preview.",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "BitPal Checkout SDK — crypto payment sessions, external on-chain payment verification, gasless checkout (with PaymentAuthorization double-defense), refunds, fee preview.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
|
-
"README.md"
|
|
17
|
+
"README.md",
|
|
18
|
+
"MIGRATION_v0.2.md"
|
|
18
19
|
],
|
|
19
20
|
"keywords": [
|
|
20
21
|
"bitpal",
|
|
@@ -34,14 +35,15 @@
|
|
|
34
35
|
"engines": {
|
|
35
36
|
"node": ">=18"
|
|
36
37
|
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
39
|
-
"lint": "tsc --noEmit",
|
|
40
|
-
"test": "vitest run"
|
|
41
|
-
},
|
|
42
38
|
"devDependencies": {
|
|
43
39
|
"tsup": "^8.5.1",
|
|
44
40
|
"typescript": "^5.5.0",
|
|
45
41
|
"vitest": "^2.1.9"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
45
|
+
"lint": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"release": "pnpm publish --access public --no-git-checks"
|
|
46
48
|
}
|
|
47
|
-
}
|
|
49
|
+
}
|