@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.d.ts
CHANGED
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
interface BitPalConfig {
|
|
5
5
|
/**
|
|
6
|
-
* API
|
|
6
|
+
* API 키(서버 전용, secret). prefix 가 mode 를 결정합니다:
|
|
7
7
|
* - `bp_test_...` → TEST mode (testnet 또는 local anvil)
|
|
8
8
|
* - `bp_live_...` → LIVE mode (mainnet)
|
|
9
9
|
*
|
|
10
10
|
* BitPal 콘솔 → Developers → API Keys 에서 발급.
|
|
11
|
+
*
|
|
12
|
+
* **선택**: createSession/listSessions/products 등 서버 메서드는 필수. 그러나 buyer-facing 공개 메서드
|
|
13
|
+
* (getPublicSession/getStatus/issueDepositAddress)는 인증 불요(session_token 기반)이므로 브라우저에서
|
|
14
|
+
* apiKey 없이 쓸 수 있다 — 절대 secret 키를 브라우저에 노출하지 말 것.
|
|
11
15
|
*/
|
|
12
|
-
apiKey
|
|
16
|
+
apiKey?: string;
|
|
13
17
|
/**
|
|
14
18
|
* BitPal API 기본 URL. 환경별로 다릅니다:
|
|
15
19
|
* - production: `https://api.bitpal.io` (기본값) — 실제 testnet/mainnet 컨트랙트
|
|
@@ -21,23 +25,48 @@ interface BitPalConfig {
|
|
|
21
25
|
/** 요청 타임아웃 (ms, 기본값: 30000) */
|
|
22
26
|
timeout?: number;
|
|
23
27
|
}
|
|
28
|
+
/** 응답/정규화된 line item — amount 는 항상 atomic μUSD 정수. */
|
|
24
29
|
interface LineItem {
|
|
25
30
|
name: string;
|
|
26
|
-
/**
|
|
27
|
-
* Atomic μUSDC 정수 문자열 (USDC/USDT 6 decimals).
|
|
28
|
-
* $1 = "1000000", $29 = "29000000".
|
|
29
|
-
* decimal 표기는 거부된다 — `toAtomicUSDC('29.00')` 헬퍼 사용 권장.
|
|
30
|
-
*/
|
|
31
|
+
/** Atomic μUSD 정수 문자열($1="1000000", $29="29000000"). */
|
|
31
32
|
amount: string;
|
|
32
33
|
currency: string;
|
|
33
34
|
quantity?: number;
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 세션 생성 입력용 line item. 금액은 둘 중 **하나**:
|
|
38
|
+
* - `amount`(권장): 사람 표기 USD 문자열(예 "29", "0.5", "29.00"). decimal 신경 쓸 필요 없음.
|
|
39
|
+
* ※ 금액은 **USD 가격**이다(체인/토큰 decimal 무관). 토큰(USDC 6dp / BNB USDT 18dp)별 스케일은 발급 시 서버가 처리.
|
|
40
|
+
* - `amount_atomic`: 이미 raw μUSD 정수 문자열($1="1000000")인 경우(고급/기존 코드).
|
|
41
|
+
* 둘 다 지정하거나 둘 다 생략하면 SDK 가 에러를 throw 한다.
|
|
42
|
+
*/
|
|
43
|
+
interface LineItemInput {
|
|
44
|
+
name: string;
|
|
45
|
+
currency: string;
|
|
46
|
+
quantity?: number;
|
|
47
|
+
/** 사람 표기 USD 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
|
|
48
|
+
amount?: string;
|
|
49
|
+
/** raw μUSD 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
|
|
50
|
+
amount_atomic?: string;
|
|
51
|
+
}
|
|
52
|
+
interface PriceLineItem {
|
|
53
|
+
/** price_... — bitpal.checkout.prices.create()로 만든 가격 */
|
|
54
|
+
price_id: string;
|
|
55
|
+
quantity?: number;
|
|
56
|
+
}
|
|
57
|
+
type CheckoutLineItemInput = LineItemInput | PriceLineItem;
|
|
35
58
|
interface CreateSessionParams {
|
|
36
|
-
line_items:
|
|
59
|
+
line_items: CheckoutLineItemInput[];
|
|
37
60
|
/** 고객 지갑 주소 (선택) */
|
|
38
61
|
payer_wallet?: string;
|
|
39
|
-
/** 결제 체인 (예: eip155:8453) */
|
|
62
|
+
/** 결제 체인 (예: eip155:8453). 단일 고정 — allowed_pay_chains와 상호 배타. */
|
|
40
63
|
pay_chain?: string;
|
|
64
|
+
/**
|
|
65
|
+
* 허용 결제 체인 목록 (CAIP-2). 구매자가 이 중에서 선택하며, 하나뿐이면 자동 선택된다.
|
|
66
|
+
* pay_chain(단일 고정)과 **상호 배타** — 동시 지정 시 SDK가 에러를 throw한다.
|
|
67
|
+
* 예: ['eip155:84532', 'eip155:11155420', 'eip155:80002', 'eip155:421614']
|
|
68
|
+
*/
|
|
69
|
+
allowed_pay_chains?: string[];
|
|
41
70
|
/** 결제 자산 (예: USDC) */
|
|
42
71
|
pay_asset?: string;
|
|
43
72
|
/** 허용 오차 (%, 기본값: 2.00) */
|
|
@@ -67,12 +96,8 @@ interface CheckoutSession {
|
|
|
67
96
|
settled_at: string | null;
|
|
68
97
|
tx_hash: string | null;
|
|
69
98
|
created_at: string;
|
|
70
|
-
/** 'hosted'
|
|
99
|
+
/** 'hosted' — 결제 경로. 안2 는 hosted deposit-address 단일 경로. */
|
|
71
100
|
payment_source?: string;
|
|
72
|
-
/** External on-chain 결제 검증 기록 목록 */
|
|
73
|
-
payment_observations?: PaymentObservation[];
|
|
74
|
-
/** External/gasless checkout payment config supplied by BitPal API. */
|
|
75
|
-
external_payment_config?: ExternalPaymentConfig;
|
|
76
101
|
/** 호스티드 결제 페이지 URL (token 쿼리 포함). backend 응답 그대로 사용 — buyer를 이 URL로 redirect. */
|
|
77
102
|
checkout_url?: string;
|
|
78
103
|
/** 1회용 session token. `checkout_url`에 이미 query로 박혀 있어 별도 사용 불필요. */
|
|
@@ -80,119 +105,99 @@ interface CheckoutSession {
|
|
|
80
105
|
/** `checkout_url`의 별칭 (backwards compat). buyer redirect 시 이 필드 또는 `checkout_url` 사용. */
|
|
81
106
|
url: string;
|
|
82
107
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
/**
|
|
109
|
+
* 안2 결제 옵션(chain×token). `/public` 이 서버 권위로 내려주며, buyer 가 하나를 골라
|
|
110
|
+
* `issueDepositAddress({ chain, token })` 로 입금주소를 발급받는다. 단일-자산 세션은 1개.
|
|
111
|
+
*/
|
|
112
|
+
interface PaymentOption {
|
|
113
|
+
/** CAIP-2 (예: eip155:8453, tron:0x2b6653dc, solana:5eykt4…). 체인 식별의 canonical. */
|
|
114
|
+
caip2: string;
|
|
115
|
+
/** EVM numeric chainId. 비-EVM(Tron/Solana)은 null — 식별은 caip2 사용. */
|
|
116
|
+
chain_id: number | null;
|
|
117
|
+
token_symbol: string;
|
|
118
|
+
/** 토큰 컨트랙트/mint 주소(EVM 0x / Tron base58 / Solana base58 mint). */
|
|
119
|
+
token_address: string;
|
|
120
|
+
/** 토큰 decimals(USDC/USDT=6, BSC-USD=18). atomic↔decimal 변환에 필수 — 6dp 가정 금지. */
|
|
94
121
|
decimals: number;
|
|
95
|
-
supported_methods: Array<'authorization_relay'>;
|
|
96
122
|
}
|
|
97
123
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
124
|
+
* `getPublicSession()` (`GET /v1/checkout/sessions/:id/public`) 응답 — 인증 불요(공개).
|
|
125
|
+
* buyer-facing 결제 페이지가 표시/폴링에 쓰는 서버 권위 뷰.
|
|
100
126
|
*/
|
|
101
|
-
interface
|
|
127
|
+
interface PublicSession {
|
|
102
128
|
id: string;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
/** Atomic
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
/** created | awaiting_deposit | deposit_detected | paid_confirmed | swept | unresolved_* | wrong_token | expired ... */
|
|
130
|
+
status: string;
|
|
131
|
+
mode: string | null;
|
|
132
|
+
currency: string;
|
|
133
|
+
/** Atomic 정수 문자열(토큰 decimals 기준). */
|
|
134
|
+
amount_total: string;
|
|
135
|
+
amount_received?: string | null;
|
|
136
|
+
pay_chain: string | null;
|
|
137
|
+
pay_asset: string | null;
|
|
138
|
+
/** 발급된 주문별 forwarder 입금주소(미발급이면 null). */
|
|
139
|
+
deposit_address?: string | null;
|
|
140
|
+
/** 구매자 선택 가능한 결제 옵션(서버 권위). 단일-자산이면 1개. */
|
|
141
|
+
payment_options: PaymentOption[];
|
|
142
|
+
chain_id: number | null;
|
|
143
|
+
token_address: string | null;
|
|
144
|
+
/** 선택된 pay 토큰 decimals(발급 후). amount_total/amount_received atomic 해석에 사용 — 6dp 가정 금지. */
|
|
145
|
+
token_decimals?: number | null;
|
|
146
|
+
tx_hash: string | null;
|
|
147
|
+
release_tx_hash?: string | null;
|
|
148
|
+
expires_at: string | null;
|
|
149
|
+
success_url: string | null;
|
|
150
|
+
cancel_url: string | null;
|
|
151
|
+
fee_amount: string | null;
|
|
152
|
+
net_amount: string | null;
|
|
153
|
+
seller_wallet: string;
|
|
154
|
+
paid_at: string | null;
|
|
155
|
+
settled_at: string | null;
|
|
116
156
|
created_at: string;
|
|
157
|
+
line_items: LineItem[];
|
|
117
158
|
}
|
|
118
159
|
/**
|
|
119
|
-
* `
|
|
120
|
-
*
|
|
121
|
-
|
|
122
|
-
interface SubmitExternalPaymentParams {
|
|
123
|
-
/** CheckoutEscrow 입금 tx hash (0x...) */
|
|
124
|
-
tx_hash: string;
|
|
125
|
-
/** 체인 식별자 (기본값: 'base'). 예: 'base', 'bnb', 'polygon' */
|
|
126
|
-
chain?: string;
|
|
127
|
-
/** Public session token — buyer 측 제출 시 사용. API key 인증 시 불필요. */
|
|
128
|
-
session_token?: string;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* `submitExternalPayment` 응답.
|
|
160
|
+
* `issueDepositAddress()` 요청 파라미터.
|
|
161
|
+
* chain/token 은 멀티옵션 세션에서만 필요(단일-자산 세션은 생략). refundAddress 는 필수 —
|
|
162
|
+
* 금액부족/잘못된토큰/만료 시 이 주소로 환불되며, 입금주소에 커밋되어 발급 후 변경 불가.
|
|
132
163
|
*/
|
|
133
|
-
interface
|
|
134
|
-
|
|
135
|
-
/** 'valid' | 'invalid' | 'underpaid' | 'overpaid' | 'duplicate' */
|
|
136
|
-
validation_status: string;
|
|
137
|
-
/** 검증 후 세션 상태 */
|
|
138
|
-
session_status: string;
|
|
139
|
-
/** 입금한 지갑 주소 (on-chain 이벤트에서 도출) */
|
|
140
|
-
from_address: string | null;
|
|
141
|
-
/** Atomic μUSDC 정수 문자열 */
|
|
142
|
-
amount: string;
|
|
143
|
-
message: string;
|
|
144
|
-
}
|
|
145
|
-
interface PayWithAuthorizationParams {
|
|
146
|
-
chain?: string;
|
|
164
|
+
interface IssueDepositAddressParams {
|
|
165
|
+
/** 결제 페이지 접근 증명(HMAC). createSession 응답의 session_token. */
|
|
147
166
|
session_token: string;
|
|
148
|
-
/** EIP-3009 TransferWithAuthorization sig (USDC). */
|
|
149
|
-
authorization: {
|
|
150
|
-
from: string;
|
|
151
|
-
validAfter?: string;
|
|
152
|
-
validBefore: string;
|
|
153
|
-
authNonce: string;
|
|
154
|
-
v: number;
|
|
155
|
-
r: string;
|
|
156
|
-
s: string;
|
|
157
|
-
};
|
|
158
167
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* 서명 생성: `buildPaymentAuthTypedData()` helper 또는 viem `signTypedData` 직접 호출.
|
|
164
|
-
* Required for SDK v0.2.x+ — gasless lane 모든 호출에 필수. v0.1.x 는 미지원.
|
|
168
|
+
* 환불 주소. 필수. 선택한 체인의 VM 형식이어야 한다:
|
|
169
|
+
* EVM=0x…, Tron=base58(T…), Solana=base58. 결제와 같은 네트워크의 본인 주소.
|
|
170
|
+
* 금액부족/잘못된토큰/만료 시 이 주소로 환불되며, 입금주소에 커밋되어 발급 후 변경 불가.
|
|
165
171
|
*/
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
s: string;
|
|
172
|
-
};
|
|
172
|
+
refundAddress: string;
|
|
173
|
+
/** 선택 자산의 chain(CAIP-2 또는 short-name). 멀티옵션 세션 필수. */
|
|
174
|
+
chain?: string;
|
|
175
|
+
/** 선택 자산의 token symbol(예: USDC). 멀티옵션 세션 필수. */
|
|
176
|
+
token?: string;
|
|
173
177
|
}
|
|
174
178
|
/**
|
|
175
|
-
*
|
|
176
|
-
* `buildPaymentAuthTypedData()` 가 이를 받아 viem 호환 객체를 반환.
|
|
179
|
+
* `issueDepositAddress()` 응답 — 이 주소로 정확 금액을 송금하면 watcher 가 감지, keeper 가 정산한다.
|
|
177
180
|
*/
|
|
178
|
-
interface
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
181
|
+
interface DepositAddress {
|
|
182
|
+
sessionId: string;
|
|
183
|
+
chain: string;
|
|
184
|
+
/** 주문별 결정적 입금주소(EVM/Tron=CREATE2 forwarder, Solana=order PDA 의 deposit ATA). */
|
|
185
|
+
depositAddress: string;
|
|
186
|
+
expectedToken: string;
|
|
187
|
+
/** Atomic 정수 문자열. */
|
|
188
|
+
expectedAmount: string;
|
|
186
189
|
feeAmount: string;
|
|
187
|
-
|
|
188
|
-
|
|
190
|
+
refundAddress: string;
|
|
191
|
+
/** Unix seconds — on-chain deadline. */
|
|
189
192
|
deadline: string;
|
|
190
|
-
|
|
193
|
+
expiresAt: string | null;
|
|
191
194
|
}
|
|
192
|
-
|
|
195
|
+
/**
|
|
196
|
+
* `getStatus()` (`GET /v1/checkout/sessions/:id/status`) 응답 — 경량 폴링용(인증 불요).
|
|
197
|
+
*/
|
|
198
|
+
interface SessionStatus {
|
|
193
199
|
status: string;
|
|
194
|
-
|
|
195
|
-
message?: string;
|
|
200
|
+
paid_at: string | null;
|
|
196
201
|
}
|
|
197
202
|
/**
|
|
198
203
|
* 수수료 미리보기 응답.
|
|
@@ -206,65 +211,16 @@ interface FeePreview {
|
|
|
206
211
|
net_amount: string;
|
|
207
212
|
}
|
|
208
213
|
/**
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* `amount` 미지정 시 backend가 잔액 전액(full refund)을 시도. 명시 시 atomic μUSDC.
|
|
213
|
-
*/
|
|
214
|
-
interface RefundSessionParams {
|
|
215
|
-
/** Atomic μUSDC 정수 문자열. 미지정 시 전액. */
|
|
216
|
-
amount?: string;
|
|
217
|
-
/** 환불 사유 코드 (선택, 머천트 대시보드/audit log 분류용) */
|
|
218
|
-
reason?: string;
|
|
219
|
-
/** 환불 사유 상세 (선택, 자유 텍스트) */
|
|
220
|
-
reason_detail?: string;
|
|
221
|
-
}
|
|
222
|
-
interface RefundSessionResult {
|
|
223
|
-
refunded: boolean;
|
|
224
|
-
refundId: string;
|
|
225
|
-
/** Atomic μUSDC 정수 문자열 */
|
|
226
|
-
amount: string;
|
|
227
|
-
type: 'full' | 'partial';
|
|
228
|
-
/** on-chain transaction hash. simulation/escrow 미배포 환경에서는 sim_xxx */
|
|
229
|
-
txHash: string | null;
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Record refund 파라미터 — `status='settled'` 후 머천트가 외부 지갑에서 직접 송금하고
|
|
233
|
-
* tx_hash를 입력해 환불을 DB-only로 기록한다. 컨트랙트 호출 없음.
|
|
214
|
+
* 환불은 온체인 자동 경로 — SDK 에 수동 환불 API 는 없다.
|
|
215
|
+
* 금액부족/잘못된토큰/만료 시 issueDepositAddress 에 지정한 refundAddress 로 자동 환불되고,
|
|
216
|
+
* 세션 status(expired/unresolved) + webhook 으로 통지된다.
|
|
234
217
|
*/
|
|
235
|
-
interface RecordRefundParams {
|
|
236
|
-
/** Atomic μUSDC 정수 문자열 (필수) */
|
|
237
|
-
refund_amount: string;
|
|
238
|
-
/** 외부 송금 transaction hash (필수, 형식 검증 안 함) */
|
|
239
|
-
tx_hash: string;
|
|
240
|
-
/** 환불 사유 (선택) */
|
|
241
|
-
reason?: string;
|
|
242
|
-
}
|
|
243
218
|
/**
|
|
244
219
|
* 멱등성 키 — 동일 키로 재시도 시 backend가 같은 결과 반환.
|
|
245
|
-
* Phase 1 SDK는 사용자가 명시적으로 전달 (UUID 권장).
|
|
246
220
|
*/
|
|
247
221
|
interface IdempotencyOptions {
|
|
248
222
|
idempotencyKey?: string;
|
|
249
223
|
}
|
|
250
|
-
/**
|
|
251
|
-
* `GET /v1/checkout/sessions/:id/refunds` 응답 row.
|
|
252
|
-
* 필드는 backend `listSessionRefunds` 반환과 동일 — Phase 1에서는 통합 타입 미고정 (스냅샷 시점에 따라 컬럼 변동).
|
|
253
|
-
*/
|
|
254
|
-
interface RefundEntry {
|
|
255
|
-
id: string;
|
|
256
|
-
session_id: string;
|
|
257
|
-
/** 'auto' | 'record' (escrow refund vs DB-only manual) */
|
|
258
|
-
refund_mode?: string;
|
|
259
|
-
amount: string;
|
|
260
|
-
/** 'pending' | 'completed' | 'failed' 등 */
|
|
261
|
-
status?: string;
|
|
262
|
-
tx_hash: string | null;
|
|
263
|
-
reason?: string | null;
|
|
264
|
-
reason_detail?: string | null;
|
|
265
|
-
created_at: string;
|
|
266
|
-
[key: string]: unknown;
|
|
267
|
-
}
|
|
268
224
|
interface ApiResponse<T> {
|
|
269
225
|
data: T;
|
|
270
226
|
error?: string;
|
|
@@ -273,6 +229,234 @@ interface ListResponse<T> {
|
|
|
273
229
|
data: T[];
|
|
274
230
|
total?: number;
|
|
275
231
|
}
|
|
232
|
+
interface CheckoutProduct {
|
|
233
|
+
/** prod_... */
|
|
234
|
+
id: string;
|
|
235
|
+
name: string;
|
|
236
|
+
description: string | null;
|
|
237
|
+
image_url: string | null;
|
|
238
|
+
/** 'active' | 'inactive' */
|
|
239
|
+
status: string;
|
|
240
|
+
metadata: Record<string, unknown>;
|
|
241
|
+
mode: string;
|
|
242
|
+
created_at: string;
|
|
243
|
+
updated_at: string;
|
|
244
|
+
}
|
|
245
|
+
interface CheckoutPrice {
|
|
246
|
+
/** price_... */
|
|
247
|
+
id: string;
|
|
248
|
+
/** prod_... */
|
|
249
|
+
product_id: string;
|
|
250
|
+
/** 원자 단위 정수 문자열 ($1 = "1000000") — 불변 */
|
|
251
|
+
amount_atomic: string;
|
|
252
|
+
currency: string;
|
|
253
|
+
token_symbol: string;
|
|
254
|
+
/** v1: 'one_time' */
|
|
255
|
+
billing_type: string;
|
|
256
|
+
nickname: string | null;
|
|
257
|
+
lookup_key: string | null;
|
|
258
|
+
mode: string;
|
|
259
|
+
created_at: string;
|
|
260
|
+
}
|
|
261
|
+
interface CreateProductParams {
|
|
262
|
+
name: string;
|
|
263
|
+
description?: string;
|
|
264
|
+
image_url?: string;
|
|
265
|
+
metadata?: Record<string, unknown>;
|
|
266
|
+
}
|
|
267
|
+
interface UpdateProductParams {
|
|
268
|
+
name?: string;
|
|
269
|
+
description?: string | null;
|
|
270
|
+
image_url?: string | null;
|
|
271
|
+
metadata?: Record<string, unknown>;
|
|
272
|
+
status?: 'active' | 'inactive';
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 가격 생성 입력. 금액은 둘 중 **하나** (line item 과 동일 모델):
|
|
276
|
+
* - `amount`(권장): 사람 표기 USD 문자열(예 "29", "0.5"). decimal 신경 쓸 필요 없음.
|
|
277
|
+
* - `amount_atomic`: raw μUSD 정수($1="1000000") — 고급/기존.
|
|
278
|
+
* 둘 다 지정하거나 둘 다 생략하면 서버가 400 을 반환한다.
|
|
279
|
+
*/
|
|
280
|
+
interface CreatePriceParams {
|
|
281
|
+
/** 사람 표기 USD 금액(예 "29", "0.5") — 권장. amount_atomic 과 상호배타. */
|
|
282
|
+
amount?: string;
|
|
283
|
+
/** raw μUSD 정수($1="1000000") — 고급/기존. amount 와 상호배타. */
|
|
284
|
+
amount_atomic?: string;
|
|
285
|
+
currency?: string;
|
|
286
|
+
token_symbol?: string;
|
|
287
|
+
billing_type?: 'one_time';
|
|
288
|
+
nickname?: string;
|
|
289
|
+
lookup_key?: string;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Price는 lifecycle 없는 immutable record — 변경 가능한 것은 nickname(표시 메타)뿐.
|
|
293
|
+
* 금액류 필드(amount_atomic 등)는 immutable, "그만 팔기"는 Price 가 아니라 그 Price 를 쓰는
|
|
294
|
+
* Payment Link 를 inactive 로 한다.
|
|
295
|
+
*/
|
|
296
|
+
interface UpdatePriceParams {
|
|
297
|
+
nickname?: string | null;
|
|
298
|
+
}
|
|
299
|
+
interface ListProductsParams {
|
|
300
|
+
status?: 'active' | 'inactive';
|
|
301
|
+
limit?: number;
|
|
302
|
+
offset?: number;
|
|
303
|
+
}
|
|
304
|
+
interface ListPricesParams {
|
|
305
|
+
limit?: number;
|
|
306
|
+
offset?: number;
|
|
307
|
+
}
|
|
308
|
+
interface CheckoutPaymentLink {
|
|
309
|
+
/** plink_... */
|
|
310
|
+
id: string;
|
|
311
|
+
/** 고엔트로피 공개 토큰 — 호스티드 URL `{checkoutBase}/plink/{share_token}` 에 사용 */
|
|
312
|
+
share_token: string;
|
|
313
|
+
/** price_... — 이 링크가 파는 가격(생성 후 고정) */
|
|
314
|
+
price_id: string;
|
|
315
|
+
title: string | null;
|
|
316
|
+
description: string | null;
|
|
317
|
+
collect_email: boolean;
|
|
318
|
+
email_required: boolean;
|
|
319
|
+
/** CAIP-2 목록 또는 null(legacy) */
|
|
320
|
+
allowed_pay_chains: string[] | null;
|
|
321
|
+
/** chain×token cell 목록 또는 null */
|
|
322
|
+
allowed_assets: Array<{
|
|
323
|
+
caip2: string;
|
|
324
|
+
token: string;
|
|
325
|
+
}> | null;
|
|
326
|
+
success_url: string | null;
|
|
327
|
+
cancel_url: string | null;
|
|
328
|
+
/** 'active' | 'inactive' */
|
|
329
|
+
status: string;
|
|
330
|
+
max_uses: number | null;
|
|
331
|
+
paid_use_count: number;
|
|
332
|
+
expires_at: string | null;
|
|
333
|
+
mode: string;
|
|
334
|
+
created_at: string;
|
|
335
|
+
updated_at: string;
|
|
336
|
+
}
|
|
337
|
+
interface CreatePaymentLinkParams {
|
|
338
|
+
/** price_... — bitpal.checkout.prices.create() 로 만든 가격. 필수. */
|
|
339
|
+
price_id: string;
|
|
340
|
+
title?: string;
|
|
341
|
+
description?: string;
|
|
342
|
+
/** buyer 이메일 수집(기본 true) */
|
|
343
|
+
collect_email?: boolean;
|
|
344
|
+
/** buyer 이메일 필수(기본 true). true 면 collect_email 도 true 여야 함. */
|
|
345
|
+
email_required?: boolean;
|
|
346
|
+
/** 허용 결제 체인(CAIP-2 또는 short-name). allowed_assets 가 있으면 그쪽 우선. */
|
|
347
|
+
allowed_pay_chains?: string[];
|
|
348
|
+
/** chain×token cell 허용 목록 — allowed_pay_chains 보다 우선. */
|
|
349
|
+
allowed_assets?: Array<{
|
|
350
|
+
chain: string;
|
|
351
|
+
token: string;
|
|
352
|
+
}>;
|
|
353
|
+
success_url?: string;
|
|
354
|
+
cancel_url?: string;
|
|
355
|
+
max_uses?: number;
|
|
356
|
+
/** ISO-8601 datetime */
|
|
357
|
+
expires_at?: string;
|
|
358
|
+
metadata?: Record<string, unknown>;
|
|
359
|
+
}
|
|
360
|
+
interface UpdatePaymentLinkParams {
|
|
361
|
+
title?: string | null;
|
|
362
|
+
description?: string | null;
|
|
363
|
+
collect_email?: boolean;
|
|
364
|
+
email_required?: boolean;
|
|
365
|
+
allowed_pay_chains?: string[] | null;
|
|
366
|
+
allowed_assets?: Array<{
|
|
367
|
+
chain: string;
|
|
368
|
+
token: string;
|
|
369
|
+
}> | null;
|
|
370
|
+
success_url?: string | null;
|
|
371
|
+
cancel_url?: string | null;
|
|
372
|
+
status?: 'active' | 'inactive';
|
|
373
|
+
max_uses?: number | null;
|
|
374
|
+
expires_at?: string | null;
|
|
375
|
+
metadata?: Record<string, unknown>;
|
|
376
|
+
}
|
|
377
|
+
interface ListPaymentLinksParams {
|
|
378
|
+
status?: 'active' | 'inactive';
|
|
379
|
+
limit?: number;
|
|
380
|
+
offset?: number;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* catalog.ts — Product / immutable Price 관리 리소스 (PROD-LINK-3a-SDK)
|
|
385
|
+
*
|
|
386
|
+
* server-side SDK 전용 (secret API key). 브라우저에서 사용 금지.
|
|
387
|
+
*
|
|
388
|
+
* 사용 예:
|
|
389
|
+
* ```ts
|
|
390
|
+
* const bitpal = new BitPal({ apiKey: 'bp_test_...' });
|
|
391
|
+
* const { data: product } = await bitpal.checkout.products.create({ name: 'Pro Plan' });
|
|
392
|
+
* const { data: price } = await bitpal.checkout.prices.create(product.id, {
|
|
393
|
+
* amount_atomic: toAtomicUSDC('29.00'),
|
|
394
|
+
* });
|
|
395
|
+
* ```
|
|
396
|
+
*
|
|
397
|
+
* Price immutable 원칙: 가격 변경은 update가 아니라 "새 price 생성"이다.
|
|
398
|
+
* `prices.update`는 nickname(표시 메타)만 받는다 — 금액류 필드 변경 helper는 의도적으로 없다.
|
|
399
|
+
* "그만 팔기"는 Price 를 끄는 게 아니라 그 Price 를 쓰는 Payment Link 를 inactive 로 한다.
|
|
400
|
+
*/
|
|
401
|
+
|
|
402
|
+
/** client.ts의 private request를 리소스에 주입하기 위한 시그니처 */
|
|
403
|
+
type CatalogRequester = <T>(method: string, path: string, body?: unknown) => Promise<T>;
|
|
404
|
+
declare class ProductsResource {
|
|
405
|
+
private readonly request;
|
|
406
|
+
constructor(request: CatalogRequester);
|
|
407
|
+
/** POST /v1/checkout/products */
|
|
408
|
+
create(params: CreateProductParams): Promise<ApiResponse<CheckoutProduct>>;
|
|
409
|
+
/** GET /v1/checkout/products */
|
|
410
|
+
list(params?: ListProductsParams): Promise<ListResponse<CheckoutProduct>>;
|
|
411
|
+
/** GET /v1/checkout/products/:id */
|
|
412
|
+
retrieve(productId: string): Promise<ApiResponse<CheckoutProduct>>;
|
|
413
|
+
/** PATCH /v1/checkout/products/:id */
|
|
414
|
+
update(productId: string, params: UpdateProductParams): Promise<ApiResponse<CheckoutProduct>>;
|
|
415
|
+
}
|
|
416
|
+
declare class PricesResource {
|
|
417
|
+
private readonly request;
|
|
418
|
+
constructor(request: CatalogRequester);
|
|
419
|
+
/** POST /v1/checkout/products/:id/prices */
|
|
420
|
+
create(productId: string, params: CreatePriceParams): Promise<ApiResponse<CheckoutPrice>>;
|
|
421
|
+
/** GET /v1/checkout/products/:id/prices */
|
|
422
|
+
list(productId: string, params?: ListPricesParams): Promise<ListResponse<CheckoutPrice>>;
|
|
423
|
+
/** GET /v1/checkout/prices/:id */
|
|
424
|
+
retrieve(priceId: string): Promise<ApiResponse<CheckoutPrice>>;
|
|
425
|
+
/**
|
|
426
|
+
* PATCH /v1/checkout/prices/:id — nickname(표시 메타)만.
|
|
427
|
+
* 금액류 필드(amount_atomic 등)는 immutable — 서버가 PRICE_IMMUTABLE_FIELD로 거부한다.
|
|
428
|
+
* 가격 변경은 `prices.create()`로 새 price를 만들 것. "그만 팔기"는 해당 Price 를 쓰는
|
|
429
|
+
* Payment Link 를 inactive 로 한다(Price 자체에는 active/inactive 개념이 없다).
|
|
430
|
+
*/
|
|
431
|
+
update(priceId: string, params: UpdatePriceParams): Promise<ApiResponse<CheckoutPrice>>;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Payment Link 리소스 — 대시보드 없이 코드로 결제 링크 생성/관리 (PROD-LINK-4).
|
|
435
|
+
*
|
|
436
|
+
* 흐름: product 생성 → price 생성 → payment link 생성. link↔price binding 은 생성 후 고정.
|
|
437
|
+
*
|
|
438
|
+
* ```ts
|
|
439
|
+
* const { data: product } = await bitpal.checkout.products.create({ name: 'Pro Plan' });
|
|
440
|
+
* const { data: price } = await bitpal.checkout.prices.create(product.id, { amount: '29' });
|
|
441
|
+
* const { data: link } = await bitpal.checkout.paymentLinks.create({
|
|
442
|
+
* price_id: price.id,
|
|
443
|
+
* allowed_pay_chains: ['eip155:8453', 'solana:5eykt4Us…'],
|
|
444
|
+
* });
|
|
445
|
+
* // 호스티드 결제 페이지 URL: `{checkoutBase}/plink/${link.share_token}`
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
declare class PaymentLinksResource {
|
|
449
|
+
private readonly request;
|
|
450
|
+
constructor(request: CatalogRequester);
|
|
451
|
+
/** POST /v1/checkout/payment-links */
|
|
452
|
+
create(params: CreatePaymentLinkParams): Promise<ApiResponse<CheckoutPaymentLink>>;
|
|
453
|
+
/** GET /v1/checkout/payment-links */
|
|
454
|
+
list(params?: ListPaymentLinksParams): Promise<ListResponse<CheckoutPaymentLink>>;
|
|
455
|
+
/** GET /v1/checkout/payment-links/:id */
|
|
456
|
+
retrieve(linkId: string): Promise<ApiResponse<CheckoutPaymentLink>>;
|
|
457
|
+
/** PATCH /v1/checkout/payment-links/:id */
|
|
458
|
+
update(linkId: string, params: UpdatePaymentLinkParams): Promise<ApiResponse<CheckoutPaymentLink>>;
|
|
459
|
+
}
|
|
276
460
|
|
|
277
461
|
/**
|
|
278
462
|
* @bitpal/checkout SDK — 클라이언트
|
|
@@ -282,24 +466,40 @@ interface ListResponse<T> {
|
|
|
282
466
|
*/
|
|
283
467
|
|
|
284
468
|
declare class BitPalCheckoutClient {
|
|
469
|
+
/** 서버 메서드용 secret 키. 공개(buyer-facing) 메서드는 없이도 동작. */
|
|
285
470
|
private readonly apiKey;
|
|
286
471
|
private readonly baseUrl;
|
|
287
472
|
private readonly timeout;
|
|
473
|
+
/** PROD-LINK-3a-SDK: Product 카탈로그 관리 (server-side 전용) */
|
|
474
|
+
readonly products: ProductsResource;
|
|
475
|
+
/** PROD-LINK-3a-SDK: immutable Price 관리 — 가격 변경은 비활성화 + 새 price 생성 */
|
|
476
|
+
readonly prices: PricesResource;
|
|
477
|
+
/** PROD-LINK-4-SDK: Payment Link 관리 — 대시보드 없이 코드로 결제 링크 생성 (server-side 전용) */
|
|
478
|
+
readonly paymentLinks: PaymentLinksResource;
|
|
288
479
|
constructor(config: BitPalConfig);
|
|
289
480
|
private request;
|
|
290
481
|
/**
|
|
291
482
|
* 체크아웃 세션 생성.
|
|
292
483
|
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
484
|
+
* `line_items[].amount` 는 사람 표기 USD 문자열("29", "0.5") — decimal 계산 불필요.
|
|
485
|
+
* 이미 raw μUSD 정수라면 `amount_atomic`("29000000") 사용(고급). 둘은 상호 배타.
|
|
295
486
|
*
|
|
296
487
|
* @example
|
|
297
|
-
* import { BitPal
|
|
488
|
+
* import { BitPal } from '@bitpal/checkout';
|
|
298
489
|
* const bp = new BitPal('bp_test_...');
|
|
299
490
|
* const { data } = await bp.checkout.createSession({
|
|
300
|
-
*
|
|
491
|
+
* // amount: 사람 표기 USD("29", "0.5"). 금액은 USD 가격이라 체인/토큰 decimal 무관.
|
|
492
|
+
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
301
493
|
* });
|
|
302
494
|
* console.log(data.url); // → buyer를 redirect할 결제 페이지
|
|
495
|
+
*
|
|
496
|
+
* // 멀티체인 허용 — 구매자가 Base/Optimism 중 선택(하나뿐이면 자동 선택):
|
|
497
|
+
* // 어떤 체인·토큰이든 amount 그대로 — 서버가 발급 시 토큰 decimals 로 재스케일.
|
|
498
|
+
* await bp.checkout.createSession({
|
|
499
|
+
* line_items: [{ name: 'Pro', amount: '29', currency: 'USDC' }],
|
|
500
|
+
* allowed_pay_chains: ['eip155:84532', 'eip155:11155420'],
|
|
501
|
+
* });
|
|
502
|
+
* // 단일 체인 고정은 pay_chain 사용(둘은 상호 배타).
|
|
303
503
|
*/
|
|
304
504
|
createSession(params: CreateSessionParams): Promise<ApiResponse<CheckoutSession>>;
|
|
305
505
|
/** 세션 조회 */
|
|
@@ -311,109 +511,40 @@ declare class BitPalCheckoutClient {
|
|
|
311
511
|
offset?: number;
|
|
312
512
|
}): Promise<ListResponse<CheckoutSession>>;
|
|
313
513
|
/**
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* `params.amount` 미지정 시 backend가 잔액 전액(`type: 'full'`). 명시 시 `type: 'partial'`.
|
|
317
|
-
* **Idempotency-Key 권장** — 같은 키로 재시도 시 backend가 같은 결과 반환.
|
|
318
|
-
*
|
|
319
|
-
* 머천트가 `frozen` 상태면 환불은 허용되지만, `suspended` 상태에서는 차단된다.
|
|
320
|
-
*
|
|
321
|
-
* @example
|
|
322
|
-
* ```ts
|
|
323
|
-
* import { randomUUID } from 'node:crypto';
|
|
324
|
-
* const { data } = await bitpal.checkout.refundSession(
|
|
325
|
-
* sessionId,
|
|
326
|
-
* { amount: toAtomicUSDC('5.00'), reason: 'customer_requested' },
|
|
327
|
-
* { idempotencyKey: randomUUID() },
|
|
328
|
-
* );
|
|
329
|
-
* console.log(data.txHash); // on-chain tx hash (또는 simulation 환경에서 sim_xxx)
|
|
330
|
-
* ```
|
|
331
|
-
*/
|
|
332
|
-
refundSession(sessionId: string, params?: RefundSessionParams, opts?: IdempotencyOptions): Promise<ApiResponse<RefundSessionResult>>;
|
|
333
|
-
/**
|
|
334
|
-
* Record refund — `status='settled'` 후 머천트가 외부 지갑에서 직접 송금하고 tx_hash를 입력.
|
|
335
|
-
* 컨트랙트 호출 없음 (DB-only).
|
|
336
|
-
*
|
|
337
|
-
* **Idempotency-Key 권장**.
|
|
514
|
+
* 공개 세션 조회 (`GET /v1/checkout/sessions/:id/public`) — 인증 불요.
|
|
338
515
|
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
* await bitpal.checkout.recordRefund(
|
|
342
|
-
* sessionId,
|
|
343
|
-
* {
|
|
344
|
-
* refund_amount: toAtomicUSDC('5.00'),
|
|
345
|
-
* tx_hash: '0xabc...',
|
|
346
|
-
* reason: 'manual_offchain_refund',
|
|
347
|
-
* },
|
|
348
|
-
* { idempotencyKey: randomUUID() },
|
|
349
|
-
* );
|
|
350
|
-
* ```
|
|
516
|
+
* buyer-facing 결제 페이지가 표시/자산선택에 쓰는 서버 권위 뷰. `payment_options` 로
|
|
517
|
+
* 구매자 선택 가능한 chain×token 을 얻고, 발급 후에는 `deposit_address` 가 채워진다.
|
|
351
518
|
*/
|
|
352
|
-
|
|
353
|
-
/** 세션별 환불 내역 (auto + record 통합) */
|
|
354
|
-
listRefunds(sessionId: string): Promise<ListResponse<RefundEntry>>;
|
|
519
|
+
getPublicSession(sessionId: string): Promise<ApiResponse<PublicSession>>;
|
|
355
520
|
/**
|
|
356
|
-
*
|
|
521
|
+
* 주문별 비수탁 입금주소 발급 (`POST /v1/checkout/sessions/:id/deposit-address`).
|
|
357
522
|
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
523
|
+
* 안2 결제 진입점. 구매자가 자산(멀티옵션이면 chain/token)과 환불 주소를 확정하면 서버가
|
|
524
|
+
* 그 주문 전용 CREATE2 forwarder 주소를 돌려준다. 구매자는 아무 지갑/거래소에서 **정확한 금액**을
|
|
525
|
+
* 그 주소로 일반 송금하면 된다(서명·연결 불필요). watcher 가 감지 → keeper 가 정산.
|
|
361
526
|
*
|
|
362
|
-
*
|
|
363
|
-
* - Merchant server (API key): `Authorization: Bearer bp_test_*` 헤더 자동 첨부
|
|
364
|
-
* - Buyer 측 (session_token): `params.session_token` 전달
|
|
527
|
+
* refundAddress 는 입금주소에 커밋되어 발급 후 변경 불가. session_token 은 createSession 응답값.
|
|
365
528
|
*
|
|
366
529
|
* @example
|
|
367
530
|
* ```ts
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
* chain: '
|
|
531
|
+
* const { data } = await bitpal.checkout.issueDepositAddress(sessionId, {
|
|
532
|
+
* session_token: session.session_token!,
|
|
533
|
+
* refundAddress: '0xBuyerWallet...',
|
|
534
|
+
* chain: 'eip155:8453', // 멀티옵션 세션만 필요
|
|
535
|
+
* token: 'USDC',
|
|
372
536
|
* });
|
|
373
|
-
* console.log(data.
|
|
374
|
-
* console.log(data.session_status); // 'paid' | 'unresolved_underpaid' | ...
|
|
537
|
+
* console.log(data.depositAddress, data.expectedAmount); // 이 주소로 정확 금액 송금
|
|
375
538
|
* ```
|
|
376
539
|
*/
|
|
377
|
-
|
|
540
|
+
issueDepositAddress(sessionId: string, params: IssueDepositAddressParams): Promise<ApiResponse<DepositAddress>>;
|
|
378
541
|
/**
|
|
379
|
-
*
|
|
542
|
+
* 경량 상태 폴링 (`GET /v1/checkout/sessions/:id/status`) — 인증 불요.
|
|
380
543
|
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
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
|
-
*
|
|
394
|
-
* Use the returned session/order state only as immediate UX feedback; the
|
|
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.
|
|
399
|
-
*/
|
|
400
|
-
payWithAuthorization(sessionId: string, params: PayWithAuthorizationParams, opts?: IdempotencyOptions): Promise<ApiResponse<CheckoutPaymentResult>>;
|
|
401
|
-
/**
|
|
402
|
-
* 세션의 external on-chain 결제 검증 기록 목록 조회.
|
|
403
|
-
*
|
|
404
|
-
* `getSession()`의 `payment_observations` 필드와 동일한 데이터를 반환하지만
|
|
405
|
-
* 검증 기록만 명시적으로 조회할 때 사용한다.
|
|
406
|
-
*
|
|
407
|
-
* @example
|
|
408
|
-
* ```ts
|
|
409
|
-
* const { data } = await bitpal.checkout.getExternalPaymentStatus(sessionId);
|
|
410
|
-
* const latest = data[0];
|
|
411
|
-
* if (latest?.finality_status === 'final_confirmed') {
|
|
412
|
-
* // 12 confirmations 완료 — 결제 확정
|
|
413
|
-
* }
|
|
414
|
-
* ```
|
|
544
|
+
* status: awaiting_deposit → deposit_detected → paid_confirmed/swept. 확정/정산은 webhook 으로도
|
|
545
|
+
* 통지되므로, 서버 사이드 확정은 webhook 을 신뢰(폴링은 buyer UX 용).
|
|
415
546
|
*/
|
|
416
|
-
|
|
547
|
+
getStatus(sessionId: string): Promise<ApiResponse<SessionStatus>>;
|
|
417
548
|
/** 수수료 미리보기 — `/v1/checkout/fee-preview` (Phase 1 credit system 폐기 후 canonical path) */
|
|
418
549
|
previewFee(amount: string): Promise<ApiResponse<FeePreview>>;
|
|
419
550
|
}
|
|
@@ -425,18 +556,12 @@ declare class BitPalError extends Error {
|
|
|
425
556
|
/**
|
|
426
557
|
* @bitpal/checkout — Webhook 서명 검증 + 이벤트 타입
|
|
427
558
|
*
|
|
428
|
-
* BitPal
|
|
429
|
-
* apps/api/src/services/webhook-canonical-delivery.ts
|
|
430
|
-
* apps/api/src/__tests__/webhook-canonical-delivery.test.ts
|
|
431
|
-
*
|
|
432
|
-
* 이벤트 명/서명 형식이 backend와 drift하면 머천트 검증이 깨진다.
|
|
559
|
+
* BitPal 이 보내는 canonical webhook contract(envelope/서명/헤더)와 1:1 정합한다.
|
|
433
560
|
*/
|
|
434
561
|
/**
|
|
435
|
-
*
|
|
436
|
-
* Backend `services/checkout-webhook.ts:CHECKOUT_WEBHOOK_EVENTS`와 동일 — drift 시
|
|
437
|
-
* 머천트 코드의 분기 매칭이 실패한다.
|
|
562
|
+
* BitPal 이 발송하는 webhook 이벤트 타입 전체 목록.
|
|
438
563
|
*/
|
|
439
|
-
declare const CHECKOUT_WEBHOOK_EVENTS: readonly ["checkout.session.created", "checkout.session.
|
|
564
|
+
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"];
|
|
440
565
|
type CheckoutWebhookEvent = (typeof CHECKOUT_WEBHOOK_EVENTS)[number];
|
|
441
566
|
/**
|
|
442
567
|
* Canonical envelope (HTTP body).
|
|
@@ -482,16 +607,29 @@ declare const WEBHOOK_HEADERS: {
|
|
|
482
607
|
* import { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from '@bitpal/checkout';
|
|
483
608
|
*
|
|
484
609
|
* app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
485
|
-
* const
|
|
486
|
-
* const ok = await verifyWebhookSignature(
|
|
610
|
+
* const raw = req.body.toString('utf8'); // raw body — parse/re-serialize 하면 서명 깨짐
|
|
611
|
+
* const ok = await verifyWebhookSignature(
|
|
612
|
+
* raw,
|
|
613
|
+
* req.header(WEBHOOK_HEADERS.signature) ?? '',
|
|
614
|
+
* process.env.BITPAL_WEBHOOK_SECRET!,
|
|
615
|
+
* { timestamp: req.header(WEBHOOK_HEADERS.timestamp) ?? '' }, // replay 방어 — 필수
|
|
616
|
+
* );
|
|
487
617
|
* if (!ok) return res.status(400).send('invalid signature');
|
|
488
|
-
* const event = parseWebhookEvent(
|
|
618
|
+
* const event = parseWebhookEvent(raw);
|
|
619
|
+
* // dedup 은 서명된 body 의 event.id 로 (헤더 X-Webhook-Id 는 미서명)
|
|
620
|
+
* if (await alreadyProcessed(event.id)) return res.status(200).send('dup');
|
|
489
621
|
* if (event.event === 'checkout.session.paid') { ... }
|
|
490
622
|
* res.status(200).send('ok');
|
|
491
623
|
* });
|
|
492
624
|
* ```
|
|
493
625
|
*/
|
|
494
|
-
|
|
626
|
+
interface VerifyWebhookOptions {
|
|
627
|
+
/** `X-Webhook-Timestamp` 헤더 값(ISO UTC). 서명 대상에 포함되며 freshness 검사에 쓰인다. 필수. */
|
|
628
|
+
timestamp: string;
|
|
629
|
+
/** freshness 허용 오차(초). 기본 300(5분). |now - timestamp| 초과 시 replay 로 간주해 거부. */
|
|
630
|
+
toleranceSec?: number;
|
|
631
|
+
}
|
|
632
|
+
declare function verifyWebhookSignature(payload: string, signature: string, secret: string, opts?: VerifyWebhookOptions): Promise<boolean>;
|
|
495
633
|
/**
|
|
496
634
|
* Webhook envelope 파싱.
|
|
497
635
|
* 형식 검증 후 `WebhookPayload` 타입으로 캐스팅.
|
|
@@ -501,185 +639,43 @@ declare function verifyWebhookSignature(payload: string, signature: string, secr
|
|
|
501
639
|
declare function parseWebhookEvent(body: string): WebhookPayload;
|
|
502
640
|
|
|
503
641
|
/**
|
|
504
|
-
* amount.ts — atomic
|
|
642
|
+
* amount.ts — atomic ↔ decimal 변환 헬퍼 (임의 decimals + USDC 하위호환)
|
|
505
643
|
*
|
|
506
|
-
* BitPal API는 모든 amount를 atomic 정수 문자열로
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
* $0.5 → "500000"
|
|
644
|
+
* BitPal API는 모든 amount를 atomic 정수 문자열로 받는다. decimals 는 토큰마다 다르다:
|
|
645
|
+
* USDC/USDT(Base/Arb/Tron/Solana) = 6, BSC-USD(BNB USDT) = 18. → payment_options[].decimals /
|
|
646
|
+
* PublicSession.token_decimals 로 서버가 내려준다. 6dp 를 하드코딩하면 non-6dp 토큰에서 금액이 틀린다.
|
|
510
647
|
*
|
|
511
648
|
* 정밀도 손실 방지를 위해 BigInt 연산만 사용한다 (Number 변환 금지 — CLAUDE.md 정책).
|
|
649
|
+
* $1(6dp) → "1000000" $1(18dp) → "1000000000000000000"
|
|
512
650
|
*/
|
|
513
651
|
/**
|
|
514
|
-
* decimal 문자열 → atomic
|
|
515
|
-
*
|
|
516
|
-
* @example
|
|
517
|
-
* toAtomicUSDC('29.00') // → '29000000'
|
|
518
|
-
* toAtomicUSDC('0.5') // → '500000'
|
|
519
|
-
* toAtomicUSDC('1000') // → '1000000000'
|
|
652
|
+
* decimal 문자열 → atomic 문자열 (임의 decimals).
|
|
520
653
|
*
|
|
521
|
-
* @
|
|
654
|
+
* @param decimal 소수 문자열(음수 불가, 소수 자리 ≤ decimals)
|
|
655
|
+
* @param decimals 토큰 decimals (예: 6, 18). payment_options[].decimals 사용.
|
|
656
|
+
* @throws {Error} 음수, decimals 초과 소수 자리, 형식 오류
|
|
657
|
+
* @example toAtomic('29.00', 6) // '29000000' toAtomic('1.5', 18) // '1500000000000000000'
|
|
522
658
|
*/
|
|
523
|
-
declare function
|
|
659
|
+
declare function toAtomic(decimal: string, decimals: number): string;
|
|
524
660
|
/**
|
|
525
|
-
* atomic
|
|
526
|
-
*
|
|
527
|
-
* @example
|
|
528
|
-
* fromAtomicUSDC('29000000') // → '29.00'
|
|
529
|
-
* fromAtomicUSDC('500000') // → '0.50'
|
|
530
|
-
* fromAtomicUSDC('1500000') // → '1.50'
|
|
661
|
+
* atomic 문자열 → 사람이 읽기 좋은 decimal 문자열 (임의 decimals).
|
|
662
|
+
* 최소 2자리 소수를 유지하되(예: '29.00'), decimals 만큼만 노출.
|
|
531
663
|
*
|
|
532
664
|
* @throws {Error} 정수 문자열이 아니면
|
|
665
|
+
* @example fromAtomic('29000000', 6) // '29.00' fromAtomic('1500000000000000000', 18) // '1.50'
|
|
533
666
|
*/
|
|
534
|
-
declare function
|
|
535
|
-
|
|
667
|
+
declare function fromAtomic(atomic: string, decimals: number): string;
|
|
536
668
|
/**
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
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).
|
|
669
|
+
* decimal → atomic μUSDC (6dp). 하위호환 별칭 — 새 코드는 `toAtomic(decimal, decimals)` 권장
|
|
670
|
+
* (BNB USDT=18dp 등 non-6dp 토큰은 이 헬퍼로 처리 불가).
|
|
671
|
+
* @example toAtomicUSDC('29.00') // '29000000'
|
|
577
672
|
*/
|
|
578
|
-
declare function
|
|
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
|
-
};
|
|
673
|
+
declare function toAtomicUSDC(decimal: string): string;
|
|
678
674
|
/**
|
|
679
|
-
*
|
|
680
|
-
*
|
|
675
|
+
* atomic μUSDC(6dp) → decimal. 하위호환 별칭 — 새 코드는 `fromAtomic(atomic, decimals)` 권장.
|
|
676
|
+
* @example fromAtomicUSDC('29000000') // '29.00'
|
|
681
677
|
*/
|
|
682
|
-
declare function
|
|
678
|
+
declare function fromAtomicUSDC(atomic: string): string;
|
|
683
679
|
|
|
684
680
|
/**
|
|
685
681
|
* @bitpal/checkout — BitPal Checkout SDK
|
|
@@ -693,10 +689,11 @@ declare function sessionIdToPaymentId(sessionId: string): `0x${string}`;
|
|
|
693
689
|
* const bitpal = new BitPal({ apiKey: 'bp_test_...' });
|
|
694
690
|
*
|
|
695
691
|
* const { data: session } = await bitpal.checkout.createSession({
|
|
696
|
-
*
|
|
692
|
+
* // amount: 사람 표기 USD("29","0.5"). decimal 계산 불필요 — 체인/토큰 decimal 무관.
|
|
693
|
+
* line_items: [{ name: 'Pro Plan', amount: '29', currency: 'USDC' }],
|
|
697
694
|
* });
|
|
698
695
|
*
|
|
699
|
-
* console.log(session.url); // 호스티드 결제 페이지 URL
|
|
696
|
+
* console.log(session.url); // 호스티드 결제 페이지 URL (checkout_url 별칭)
|
|
700
697
|
* ```
|
|
701
698
|
*/
|
|
702
699
|
|
|
@@ -708,4 +705,4 @@ declare class BitPal {
|
|
|
708
705
|
constructor(config: BitPalConfig | string);
|
|
709
706
|
}
|
|
710
707
|
|
|
711
|
-
export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, CHECKOUT_WEBHOOK_EVENTS, type
|
|
708
|
+
export { type ApiResponse, BitPal, BitPalCheckoutClient, type BitPalConfig, BitPalError, CHECKOUT_WEBHOOK_EVENTS, type CheckoutLineItemInput, type CheckoutPaymentLink, type CheckoutPrice, type CheckoutProduct, type CheckoutSession, type CheckoutWebhookEvent, type CreatePaymentLinkParams, type CreatePriceParams, type CreateProductParams, type CreateSessionParams, type DepositAddress, type FeePreview, type IdempotencyOptions, type IssueDepositAddressParams, type LineItem, type ListPaymentLinksParams, type ListPricesParams, type ListProductsParams, type ListResponse, PaymentLinksResource, type PaymentOption, type PriceLineItem, PricesResource, ProductsResource, type PublicSession, type SessionStatus, type UpdatePaymentLinkParams, type UpdatePriceParams, type UpdateProductParams, WEBHOOK_HEADERS, type WebhookPayload, fromAtomic, fromAtomicUSDC, parseWebhookEvent, toAtomic, toAtomicUSDC, verifyWebhookSignature };
|