stablezact-pay 0.66.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 +36 -0
- package/dist/CoinleyPayment-BA2ym_mw.js +31105 -0
- package/dist/Logomark.png +0 -0
- package/dist/QRCodePayment-C50CNuFu.js +860 -0
- package/dist/appKitEVM--bpTC7Do.js +138 -0
- package/dist/appKitSolana-DUo_eFgJ.js +309 -0
- package/dist/coinley-vanilla.min.js +18324 -0
- package/dist/index-BTn5xw-W.js +302 -0
- package/dist/index-DYkC2Bui.js +24126 -0
- package/dist/index.esm.js +33 -0
- package/dist/index.umd.js +23 -0
- package/dist/polygon-C-1Q5KDX.js +2778 -0
- package/dist/style.css +1 -0
- package/dist/vite.svg +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect } from "react";
|
|
3
|
+
import QRCode from "qrcode";
|
|
4
|
+
import { createPublicClient, http, formatUnits, getContract } from "viem";
|
|
5
|
+
import { a as avalanche, o as optimism, b as arbitrum, p as polygon, c as bsc, m as mainnet } from "./polygon-C-1Q5KDX.js";
|
|
6
|
+
const NETWORK_CHAINS = {
|
|
7
|
+
ethereum: mainnet,
|
|
8
|
+
bsc,
|
|
9
|
+
polygon,
|
|
10
|
+
arbitrum,
|
|
11
|
+
optimism,
|
|
12
|
+
avalanche
|
|
13
|
+
};
|
|
14
|
+
const ERC20_ABI = [
|
|
15
|
+
{
|
|
16
|
+
constant: true,
|
|
17
|
+
inputs: [{ name: "_owner", type: "address" }],
|
|
18
|
+
name: "balanceOf",
|
|
19
|
+
outputs: [{ name: "balance", type: "uint256" }],
|
|
20
|
+
type: "function"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
constant: true,
|
|
24
|
+
inputs: [],
|
|
25
|
+
name: "decimals",
|
|
26
|
+
outputs: [{ name: "", type: "uint8" }],
|
|
27
|
+
type: "function"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
constant: true,
|
|
31
|
+
inputs: [],
|
|
32
|
+
name: "symbol",
|
|
33
|
+
outputs: [{ name: "", type: "string" }],
|
|
34
|
+
type: "function"
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
class BalanceChecker {
|
|
38
|
+
constructor() {
|
|
39
|
+
this.clients = /* @__PURE__ */ new Map();
|
|
40
|
+
}
|
|
41
|
+
// Get or create viem client for network
|
|
42
|
+
getClient(networkShortName) {
|
|
43
|
+
if (!this.clients.has(networkShortName)) {
|
|
44
|
+
const chain = NETWORK_CHAINS[networkShortName];
|
|
45
|
+
if (!chain) {
|
|
46
|
+
throw new Error(`Unsupported network: ${networkShortName}`);
|
|
47
|
+
}
|
|
48
|
+
const client = createPublicClient({
|
|
49
|
+
chain,
|
|
50
|
+
transport: http()
|
|
51
|
+
});
|
|
52
|
+
this.clients.set(networkShortName, client);
|
|
53
|
+
}
|
|
54
|
+
return this.clients.get(networkShortName);
|
|
55
|
+
}
|
|
56
|
+
// Check native token balance (ETH, BNB, MATIC, etc.)
|
|
57
|
+
async checkNativeBalance(walletAddress, networkShortName) {
|
|
58
|
+
try {
|
|
59
|
+
const client = this.getClient(networkShortName);
|
|
60
|
+
const balanceWei = await client.getBalance({
|
|
61
|
+
address: walletAddress
|
|
62
|
+
});
|
|
63
|
+
const balanceEther = formatUnits(balanceWei, 18);
|
|
64
|
+
return {
|
|
65
|
+
balance: parseFloat(balanceEther),
|
|
66
|
+
balanceWei: balanceWei.toString(),
|
|
67
|
+
symbol: this.getNativeTokenSymbol(networkShortName),
|
|
68
|
+
decimals: 18
|
|
69
|
+
};
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw new Error(`Failed to check native balance: ${error.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Check ERC-20 token balance
|
|
75
|
+
async checkTokenBalance(walletAddress, tokenAddress, networkShortName) {
|
|
76
|
+
try {
|
|
77
|
+
const client = this.getClient(networkShortName);
|
|
78
|
+
const contract = getContract({
|
|
79
|
+
address: tokenAddress,
|
|
80
|
+
abi: ERC20_ABI,
|
|
81
|
+
client
|
|
82
|
+
});
|
|
83
|
+
const [balanceWei, decimals, symbol] = await Promise.all([
|
|
84
|
+
contract.read.balanceOf([walletAddress]),
|
|
85
|
+
contract.read.decimals().catch(() => 18),
|
|
86
|
+
// Default to 18 if fails
|
|
87
|
+
contract.read.symbol().catch(() => "TOKEN")
|
|
88
|
+
]);
|
|
89
|
+
const balance = parseFloat(formatUnits(balanceWei, decimals));
|
|
90
|
+
return {
|
|
91
|
+
balance,
|
|
92
|
+
balanceWei: balanceWei.toString(),
|
|
93
|
+
symbol,
|
|
94
|
+
decimals,
|
|
95
|
+
tokenAddress
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
throw new Error(`Failed to check token balance: ${error.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Check if user has sufficient balance for transaction
|
|
102
|
+
async validateBalance(walletAddress, token, requiredAmount, networkShortName, includeGasFee = true) {
|
|
103
|
+
try {
|
|
104
|
+
let tokenBalance;
|
|
105
|
+
let nativeBalance = null;
|
|
106
|
+
if (token.contractAddress) {
|
|
107
|
+
tokenBalance = await this.checkTokenBalance(walletAddress, token.contractAddress, networkShortName);
|
|
108
|
+
} else {
|
|
109
|
+
tokenBalance = await this.checkNativeBalance(walletAddress, networkShortName);
|
|
110
|
+
}
|
|
111
|
+
if (token.contractAddress && includeGasFee) {
|
|
112
|
+
nativeBalance = await this.checkNativeBalance(walletAddress, networkShortName);
|
|
113
|
+
}
|
|
114
|
+
const hasEnoughTokens = tokenBalance.balance >= requiredAmount;
|
|
115
|
+
const tokenShortfall = hasEnoughTokens ? 0 : requiredAmount - tokenBalance.balance;
|
|
116
|
+
let estimatedGasFee = 0;
|
|
117
|
+
let hasEnoughForGas = true;
|
|
118
|
+
let gasShortfall = 0;
|
|
119
|
+
if (includeGasFee) {
|
|
120
|
+
estimatedGasFee = this.estimateGasFee(networkShortName, !!token.contractAddress);
|
|
121
|
+
if (token.contractAddress) {
|
|
122
|
+
hasEnoughForGas = nativeBalance.balance >= estimatedGasFee;
|
|
123
|
+
gasShortfall = hasEnoughForGas ? 0 : estimatedGasFee - nativeBalance.balance;
|
|
124
|
+
} else {
|
|
125
|
+
const totalRequired = requiredAmount + estimatedGasFee;
|
|
126
|
+
hasEnoughForGas = tokenBalance.balance >= totalRequired;
|
|
127
|
+
gasShortfall = hasEnoughForGas ? 0 : totalRequired - tokenBalance.balance;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const validationResult = {
|
|
131
|
+
isValid: hasEnoughTokens && hasEnoughForGas,
|
|
132
|
+
tokenBalance,
|
|
133
|
+
nativeBalance,
|
|
134
|
+
requiredAmount,
|
|
135
|
+
estimatedGasFee,
|
|
136
|
+
hasEnoughTokens,
|
|
137
|
+
hasEnoughForGas,
|
|
138
|
+
tokenShortfall,
|
|
139
|
+
gasShortfall,
|
|
140
|
+
errors: []
|
|
141
|
+
};
|
|
142
|
+
if (!hasEnoughTokens) {
|
|
143
|
+
validationResult.errors.push(
|
|
144
|
+
`Insufficient ${token.symbol} balance. Need ${requiredAmount} ${token.symbol}, have ${tokenBalance.balance.toFixed(6)} ${token.symbol}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (!hasEnoughForGas) {
|
|
148
|
+
const gasTokenSymbol = (nativeBalance == null ? void 0 : nativeBalance.symbol) || this.getNativeTokenSymbol(networkShortName);
|
|
149
|
+
if (token.contractAddress) {
|
|
150
|
+
validationResult.errors.push(
|
|
151
|
+
`Insufficient ${gasTokenSymbol} for gas fees. Need ~${estimatedGasFee.toFixed(6)} ${gasTokenSymbol}, have ${nativeBalance.balance.toFixed(6)} ${gasTokenSymbol}`
|
|
152
|
+
);
|
|
153
|
+
} else {
|
|
154
|
+
validationResult.errors.push(
|
|
155
|
+
`Insufficient ${gasTokenSymbol} for payment + gas. Need ${(requiredAmount + estimatedGasFee).toFixed(6)} ${gasTokenSymbol} total, have ${tokenBalance.balance.toFixed(6)} ${gasTokenSymbol}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return validationResult;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return {
|
|
162
|
+
isValid: false,
|
|
163
|
+
errors: [`Failed to check balance: ${error.message}`],
|
|
164
|
+
tokenBalance: null,
|
|
165
|
+
nativeBalance: null
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Estimate gas fee for the transaction
|
|
170
|
+
estimateGasFee(networkShortName, isERC20 = false) {
|
|
171
|
+
const gasEstimates = {
|
|
172
|
+
ethereum: isERC20 ? 1e-3 : 5e-4,
|
|
173
|
+
// ETH - higher for ERC-20
|
|
174
|
+
bsc: isERC20 ? 5e-4 : 3e-4,
|
|
175
|
+
// BNB - cheaper than ETH
|
|
176
|
+
polygon: isERC20 ? 0.01 : 5e-3,
|
|
177
|
+
// MATIC - very cheap
|
|
178
|
+
arbitrum: isERC20 ? 3e-4 : 2e-4,
|
|
179
|
+
// ETH on L2 - very cheap
|
|
180
|
+
optimism: isERC20 ? 3e-4 : 2e-4,
|
|
181
|
+
// ETH on L2 - very cheap
|
|
182
|
+
avalanche: isERC20 ? 1e-3 : 5e-4
|
|
183
|
+
// AVAX - moderate fees
|
|
184
|
+
};
|
|
185
|
+
return gasEstimates[networkShortName] || 1e-3;
|
|
186
|
+
}
|
|
187
|
+
// Get native token symbol for network
|
|
188
|
+
getNativeTokenSymbol(networkShortName) {
|
|
189
|
+
const nativeTokens = {
|
|
190
|
+
ethereum: "ETH",
|
|
191
|
+
bsc: "BNB",
|
|
192
|
+
polygon: "MATIC",
|
|
193
|
+
arbitrum: "ETH",
|
|
194
|
+
optimism: "ETH",
|
|
195
|
+
avalanche: "AVAX"
|
|
196
|
+
};
|
|
197
|
+
return nativeTokens[networkShortName] || "ETH";
|
|
198
|
+
}
|
|
199
|
+
// Quick balance check (just returns balance, no validation)
|
|
200
|
+
async getBalance(walletAddress, token, networkShortName) {
|
|
201
|
+
if (token.contractAddress) {
|
|
202
|
+
return await this.checkTokenBalance(walletAddress, token.contractAddress, networkShortName);
|
|
203
|
+
} else {
|
|
204
|
+
return await this.checkNativeBalance(walletAddress, networkShortName);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Format balance for display
|
|
208
|
+
formatBalance(balance, symbol, decimals = 6) {
|
|
209
|
+
if (balance === null || balance === void 0) return "Unknown";
|
|
210
|
+
if (balance === 0) return `0 ${symbol}`;
|
|
211
|
+
if (balance < 1e-3) {
|
|
212
|
+
return `${balance.toFixed(8)} ${symbol}`;
|
|
213
|
+
} else if (balance < 1) {
|
|
214
|
+
return `${balance.toFixed(6)} ${symbol}`;
|
|
215
|
+
} else {
|
|
216
|
+
return `${balance.toFixed(4)} ${symbol}`;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Check multiple token balances at once
|
|
220
|
+
async checkMultipleBalances(walletAddress, tokens, networkShortName) {
|
|
221
|
+
try {
|
|
222
|
+
const balancePromises = tokens.map(
|
|
223
|
+
(token) => this.getBalance(walletAddress, token, networkShortName).catch((error) => ({
|
|
224
|
+
error: error.message,
|
|
225
|
+
symbol: token.symbol,
|
|
226
|
+
balance: 0
|
|
227
|
+
}))
|
|
228
|
+
);
|
|
229
|
+
const results = await Promise.all(balancePromises);
|
|
230
|
+
return results;
|
|
231
|
+
} catch (error) {
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const balanceChecker = new BalanceChecker();
|
|
237
|
+
class SimplePaymentAPI {
|
|
238
|
+
constructor(baseURL, apiKey, apiSecret) {
|
|
239
|
+
this.baseURL = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
|
|
240
|
+
this.apiKey = apiKey;
|
|
241
|
+
this.apiSecret = apiSecret;
|
|
242
|
+
}
|
|
243
|
+
async request(endpoint, options = {}) {
|
|
244
|
+
const url = `${this.baseURL}${endpoint}`;
|
|
245
|
+
const headers = {
|
|
246
|
+
"Content-Type": "application/json",
|
|
247
|
+
"X-API-Key": this.apiKey,
|
|
248
|
+
"X-API-Secret": this.apiSecret,
|
|
249
|
+
...options.headers
|
|
250
|
+
};
|
|
251
|
+
const response = await fetch(url, {
|
|
252
|
+
...options,
|
|
253
|
+
headers
|
|
254
|
+
});
|
|
255
|
+
if (!response.ok) {
|
|
256
|
+
const error = await response.json().catch(() => ({}));
|
|
257
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
258
|
+
}
|
|
259
|
+
return response.json();
|
|
260
|
+
}
|
|
261
|
+
async getNetworks() {
|
|
262
|
+
try {
|
|
263
|
+
return await this.request("/api/networks");
|
|
264
|
+
} catch (error) {
|
|
265
|
+
return {
|
|
266
|
+
networks: [
|
|
267
|
+
{ id: "1", name: "Ethereum", shortName: "ethereum", chainId: "0x1", type: "ethereum" },
|
|
268
|
+
{ id: "56", name: "BSC", shortName: "bsc", chainId: "0x38", type: "bsc" },
|
|
269
|
+
{ id: "137", name: "Polygon", shortName: "polygon", chainId: "0x89", type: "polygon" },
|
|
270
|
+
{ id: "42161", name: "Arbitrum", shortName: "arbitrum", chainId: "0xa4b1", type: "arbitrum" },
|
|
271
|
+
{ id: "10", name: "Optimism", shortName: "optimism", chainId: "0xa", type: "optimism" },
|
|
272
|
+
{ id: "43114", name: "Avalanche", shortName: "avalanche", chainId: "0xa86a", type: "avalanche" },
|
|
273
|
+
{ id: "42220", name: "Celo", shortName: "celo", chainId: "0xa4ec", type: "celo" },
|
|
274
|
+
{ id: "8453", name: "Base", shortName: "base", chainId: "0x2105", type: "base" }
|
|
275
|
+
]
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async getTokens() {
|
|
280
|
+
try {
|
|
281
|
+
return await this.request("/api/networks/stablecoins");
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return {
|
|
284
|
+
stablecoins: [
|
|
285
|
+
{
|
|
286
|
+
id: "1",
|
|
287
|
+
name: "Tether USD",
|
|
288
|
+
symbol: "USDT",
|
|
289
|
+
contractAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
290
|
+
decimals: 6,
|
|
291
|
+
networkId: "1",
|
|
292
|
+
Network: { shortName: "ethereum", name: "Ethereum" }
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: "2",
|
|
296
|
+
name: "USD Coin",
|
|
297
|
+
symbol: "USDC",
|
|
298
|
+
contractAddress: "0xA0b86a33E641a3FA62e50A9da1CE0647a2bFBC5A",
|
|
299
|
+
decimals: 6,
|
|
300
|
+
networkId: "1",
|
|
301
|
+
Network: { shortName: "ethereum", name: "Ethereum" }
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: "3",
|
|
305
|
+
name: "Binance USD",
|
|
306
|
+
symbol: "BUSD",
|
|
307
|
+
contractAddress: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56",
|
|
308
|
+
decimals: 18,
|
|
309
|
+
networkId: "56",
|
|
310
|
+
Network: { shortName: "bsc", name: "BSC" }
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
id: "4",
|
|
314
|
+
name: "USD Coin",
|
|
315
|
+
symbol: "USDC",
|
|
316
|
+
contractAddress: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
|
|
317
|
+
decimals: 18,
|
|
318
|
+
networkId: "56",
|
|
319
|
+
Network: { shortName: "bsc", name: "BSC" }
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "5",
|
|
323
|
+
name: "USD Coin",
|
|
324
|
+
symbol: "USDC",
|
|
325
|
+
contractAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
|
|
326
|
+
decimals: 6,
|
|
327
|
+
networkId: "137",
|
|
328
|
+
Network: { shortName: "polygon", name: "Polygon" }
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: "6",
|
|
332
|
+
name: "Tether USD",
|
|
333
|
+
symbol: "USDT",
|
|
334
|
+
contractAddress: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
|
|
335
|
+
decimals: 6,
|
|
336
|
+
networkId: "137",
|
|
337
|
+
Network: { shortName: "polygon", name: "Polygon" }
|
|
338
|
+
},
|
|
339
|
+
// Arbitrum Network
|
|
340
|
+
{
|
|
341
|
+
id: "7",
|
|
342
|
+
name: "Tether USD",
|
|
343
|
+
symbol: "USDT",
|
|
344
|
+
contractAddress: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
|
|
345
|
+
decimals: 6,
|
|
346
|
+
networkId: "42161",
|
|
347
|
+
Network: { shortName: "arbitrum", name: "Arbitrum" }
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
id: "8",
|
|
351
|
+
name: "USD Coin",
|
|
352
|
+
symbol: "USDC",
|
|
353
|
+
contractAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
354
|
+
decimals: 6,
|
|
355
|
+
networkId: "42161",
|
|
356
|
+
Network: { shortName: "arbitrum", name: "Arbitrum" }
|
|
357
|
+
},
|
|
358
|
+
// Optimism Network
|
|
359
|
+
{
|
|
360
|
+
id: "9",
|
|
361
|
+
name: "Tether USD",
|
|
362
|
+
symbol: "USDT",
|
|
363
|
+
contractAddress: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
|
|
364
|
+
decimals: 6,
|
|
365
|
+
networkId: "10",
|
|
366
|
+
Network: { shortName: "optimism", name: "Optimism" }
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: "10",
|
|
370
|
+
name: "USD Coin",
|
|
371
|
+
symbol: "USDC",
|
|
372
|
+
contractAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
373
|
+
decimals: 6,
|
|
374
|
+
networkId: "10",
|
|
375
|
+
Network: { shortName: "optimism", name: "Optimism" }
|
|
376
|
+
},
|
|
377
|
+
// Avalanche Network
|
|
378
|
+
{
|
|
379
|
+
id: "11",
|
|
380
|
+
name: "Tether USD",
|
|
381
|
+
symbol: "USDT",
|
|
382
|
+
contractAddress: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7",
|
|
383
|
+
decimals: 6,
|
|
384
|
+
networkId: "43114",
|
|
385
|
+
Network: { shortName: "avalanche", name: "Avalanche" }
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: "12",
|
|
389
|
+
name: "USD Coin",
|
|
390
|
+
symbol: "USDC",
|
|
391
|
+
contractAddress: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
392
|
+
decimals: 6,
|
|
393
|
+
networkId: "43114",
|
|
394
|
+
Network: { shortName: "avalanche", name: "Avalanche" }
|
|
395
|
+
},
|
|
396
|
+
// Celo Network
|
|
397
|
+
{
|
|
398
|
+
id: "13",
|
|
399
|
+
name: "Tether USD",
|
|
400
|
+
symbol: "USDT",
|
|
401
|
+
contractAddress: "0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e",
|
|
402
|
+
decimals: 6,
|
|
403
|
+
networkId: "42220",
|
|
404
|
+
Network: { shortName: "celo", name: "Celo" }
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: "14",
|
|
408
|
+
name: "USD Coin",
|
|
409
|
+
symbol: "USDC",
|
|
410
|
+
contractAddress: "0xcebA9300f2b948710d2653dD7B07f33A8B32118C",
|
|
411
|
+
decimals: 6,
|
|
412
|
+
networkId: "42220",
|
|
413
|
+
Network: { shortName: "celo", name: "Celo" }
|
|
414
|
+
},
|
|
415
|
+
// Base Network
|
|
416
|
+
{
|
|
417
|
+
id: "15",
|
|
418
|
+
name: "Tether USD",
|
|
419
|
+
symbol: "USDT",
|
|
420
|
+
contractAddress: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
|
|
421
|
+
decimals: 6,
|
|
422
|
+
networkId: "8453",
|
|
423
|
+
Network: { shortName: "base", name: "Base" }
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
id: "16",
|
|
427
|
+
name: "USD Coin",
|
|
428
|
+
symbol: "USDC",
|
|
429
|
+
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
430
|
+
decimals: 6,
|
|
431
|
+
networkId: "8453",
|
|
432
|
+
Network: { shortName: "base", name: "Base" }
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
async createPayment(data) {
|
|
439
|
+
return await this.request("/api/payments/create", {
|
|
440
|
+
method: "POST",
|
|
441
|
+
body: JSON.stringify(data)
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async updatePaymentStatus(paymentId, status, transactionHash = null) {
|
|
445
|
+
return await this.request(`/api/payments/${paymentId}/status`, {
|
|
446
|
+
method: "PUT",
|
|
447
|
+
body: JSON.stringify({ status, transactionHash })
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const QRCodePayment = ({
|
|
452
|
+
apiKey,
|
|
453
|
+
apiSecret,
|
|
454
|
+
apiUrl,
|
|
455
|
+
config,
|
|
456
|
+
onSuccess,
|
|
457
|
+
onError,
|
|
458
|
+
onClose,
|
|
459
|
+
isOpen,
|
|
460
|
+
theme = "light",
|
|
461
|
+
mode = "splitpayment",
|
|
462
|
+
// Always use split payment mode for QR codes
|
|
463
|
+
splitPaymentUrl = "https://payment.coinley.io/"
|
|
464
|
+
}) => {
|
|
465
|
+
var _a;
|
|
466
|
+
const [currentStep, setCurrentStep] = useState("network");
|
|
467
|
+
const [networks, setNetworks] = useState([]);
|
|
468
|
+
const [tokens, setTokens] = useState([]);
|
|
469
|
+
const [selectedNetwork, setSelectedNetwork] = useState(null);
|
|
470
|
+
const [selectedToken, setSelectedToken] = useState(null);
|
|
471
|
+
const [paymentData, setPaymentData] = useState(null);
|
|
472
|
+
const [loading, setLoading] = useState(false);
|
|
473
|
+
const [error, setError] = useState("");
|
|
474
|
+
const [qrCodeData, setQrCodeData] = useState("");
|
|
475
|
+
const [qrCodeImage, setQrCodeImage] = useState("");
|
|
476
|
+
const [connectionId, setConnectionId] = useState("");
|
|
477
|
+
const [walletConnected, setWalletConnected] = useState(false);
|
|
478
|
+
const [walletAddress, setWalletAddress] = useState("");
|
|
479
|
+
const [checkingPayment, setCheckingPayment] = useState(false);
|
|
480
|
+
const [balanceValidation, setBalanceValidation] = useState(null);
|
|
481
|
+
const api = useRef(new SimplePaymentAPI(apiUrl, apiKey, apiSecret));
|
|
482
|
+
const checkInterval = useRef(null);
|
|
483
|
+
useEffect(() => {
|
|
484
|
+
if (isOpen) {
|
|
485
|
+
loadData();
|
|
486
|
+
resetState();
|
|
487
|
+
}
|
|
488
|
+
return () => {
|
|
489
|
+
if (checkInterval.current) {
|
|
490
|
+
clearInterval(checkInterval.current);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}, [isOpen]);
|
|
494
|
+
const resetState = () => {
|
|
495
|
+
setCurrentStep("network");
|
|
496
|
+
setSelectedNetwork(null);
|
|
497
|
+
setSelectedToken(null);
|
|
498
|
+
setPaymentData(null);
|
|
499
|
+
setError("");
|
|
500
|
+
setQrCodeData("");
|
|
501
|
+
setQrCodeImage("");
|
|
502
|
+
setConnectionId("");
|
|
503
|
+
setWalletConnected(false);
|
|
504
|
+
setWalletAddress("");
|
|
505
|
+
setCheckingPayment(false);
|
|
506
|
+
setBalanceValidation(null);
|
|
507
|
+
if (checkInterval.current) {
|
|
508
|
+
clearInterval(checkInterval.current);
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
const loadData = async () => {
|
|
512
|
+
try {
|
|
513
|
+
setLoading(true);
|
|
514
|
+
setError("");
|
|
515
|
+
const [networksRes, tokensRes] = await Promise.all([
|
|
516
|
+
api.current.getNetworks(),
|
|
517
|
+
api.current.getTokens()
|
|
518
|
+
]);
|
|
519
|
+
setNetworks(networksRes.networks || []);
|
|
520
|
+
setTokens(tokensRes.stablecoins || []);
|
|
521
|
+
} catch (err) {
|
|
522
|
+
setError("Failed to load payment options.");
|
|
523
|
+
} finally {
|
|
524
|
+
setLoading(false);
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
const generateSplitPaymentQRDirect = async (token) => {
|
|
528
|
+
var _a2, _b;
|
|
529
|
+
try {
|
|
530
|
+
setLoading(true);
|
|
531
|
+
setError("");
|
|
532
|
+
const paymentPayload = {
|
|
533
|
+
amount: config.amount,
|
|
534
|
+
currency: token.symbol,
|
|
535
|
+
network: selectedNetwork.shortName,
|
|
536
|
+
customerEmail: config.customerEmail,
|
|
537
|
+
callbackUrl: config.callbackUrl,
|
|
538
|
+
metadata: {
|
|
539
|
+
...config.metadata,
|
|
540
|
+
merchantWalletAddresses: config.merchantWalletAddresses,
|
|
541
|
+
paymentMethod: "split_payment_qr",
|
|
542
|
+
selectedNetwork: selectedNetwork.shortName,
|
|
543
|
+
selectedToken: token.symbol
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
const payment = await api.current.createPayment(paymentPayload);
|
|
547
|
+
setPaymentData(payment.payment);
|
|
548
|
+
const contractAddress = ((_a2 = config.merchantWalletAddresses) == null ? void 0 : _a2.splitPaymentContract) || ((_b = config.merchantWalletAddresses) == null ? void 0 : _b[selectedNetwork.shortName]);
|
|
549
|
+
if (!contractAddress) {
|
|
550
|
+
throw new Error("Split payment contract address not found in config");
|
|
551
|
+
}
|
|
552
|
+
const params = new URLSearchParams({
|
|
553
|
+
merchant: config.merchantName || "Coinley Merchant",
|
|
554
|
+
amount: payment.payment.totalAmount.toString(),
|
|
555
|
+
productAmount: (payment.payment.totalAmount * 0.95).toFixed(2),
|
|
556
|
+
platformFee: (payment.payment.totalAmount * 0.03).toFixed(2),
|
|
557
|
+
networkFee: (payment.payment.totalAmount * 0.02).toFixed(2),
|
|
558
|
+
token: token.symbol,
|
|
559
|
+
network: selectedNetwork.name,
|
|
560
|
+
merchantAddress: contractAddress,
|
|
561
|
+
paymentId: payment.payment.id
|
|
562
|
+
// Include payment ID for backend tracking
|
|
563
|
+
});
|
|
564
|
+
const splitPaymentUri = splitPaymentUrl + "?" + params.toString();
|
|
565
|
+
setQrCodeData(splitPaymentUri);
|
|
566
|
+
const qrImage = await QRCode.toDataURL(splitPaymentUri, {
|
|
567
|
+
width: 256,
|
|
568
|
+
margin: 2,
|
|
569
|
+
color: {
|
|
570
|
+
dark: "#7c3aed",
|
|
571
|
+
light: "#ffffff"
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
setQrCodeImage(qrImage);
|
|
575
|
+
setCurrentStep("scan");
|
|
576
|
+
} catch (error2) {
|
|
577
|
+
setError(error2.message || "Failed to generate split payment QR code");
|
|
578
|
+
} finally {
|
|
579
|
+
setLoading(false);
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
const simulateWalletScan = async () => {
|
|
583
|
+
try {
|
|
584
|
+
setLoading(true);
|
|
585
|
+
setError("");
|
|
586
|
+
const mockWalletAddress = "0x742d35Cc6634C0532925a3b8D9a5C7C47C77E4";
|
|
587
|
+
setWalletAddress(mockWalletAddress);
|
|
588
|
+
setWalletConnected(true);
|
|
589
|
+
await checkWalletBalance(mockWalletAddress);
|
|
590
|
+
setCurrentStep("confirm");
|
|
591
|
+
} catch (error2) {
|
|
592
|
+
setError("Failed to simulate wallet connection");
|
|
593
|
+
} finally {
|
|
594
|
+
setLoading(false);
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
const checkWalletBalance = async (address) => {
|
|
598
|
+
if (!selectedToken || !selectedNetwork || !paymentData) {
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
const validation = await balanceChecker.validateBalance(
|
|
603
|
+
address,
|
|
604
|
+
selectedToken,
|
|
605
|
+
paymentData.totalAmount,
|
|
606
|
+
selectedNetwork.shortName,
|
|
607
|
+
true
|
|
608
|
+
);
|
|
609
|
+
setBalanceValidation(validation);
|
|
610
|
+
if (!validation.isValid) {
|
|
611
|
+
setError(`Insufficient balance: ${validation.errors.join(", ")}`);
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
return true;
|
|
615
|
+
} catch (error2) {
|
|
616
|
+
setError(`Failed to check balance: ${error2.message}`);
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
const simulateTransaction = async () => {
|
|
621
|
+
try {
|
|
622
|
+
setCheckingPayment(true);
|
|
623
|
+
setError("");
|
|
624
|
+
const mockTxHash = `0x${Math.random().toString(16).substring(2, 66)}`;
|
|
625
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
626
|
+
setCurrentStep("success");
|
|
627
|
+
onSuccess == null ? void 0 : onSuccess(paymentData.id, mockTxHash, {
|
|
628
|
+
network: selectedNetwork.name,
|
|
629
|
+
token: selectedToken.symbol,
|
|
630
|
+
amount: paymentData.totalAmount,
|
|
631
|
+
method: "qr_code",
|
|
632
|
+
walletAddress
|
|
633
|
+
});
|
|
634
|
+
} catch (error2) {
|
|
635
|
+
setError("Transaction confirmation failed");
|
|
636
|
+
} finally {
|
|
637
|
+
setCheckingPayment(false);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
if (!isOpen) return null;
|
|
641
|
+
const isDark = theme === "dark";
|
|
642
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50", children: /* @__PURE__ */ jsxs("div", { className: `rounded-lg shadow-xl max-w-md w-full mx-4 ${isDark ? "bg-gray-900 text-white" : "bg-white text-gray-900"}`, children: [
|
|
643
|
+
/* @__PURE__ */ jsxs("div", { className: `p-4 border-b flex justify-between items-center ${isDark ? "border-gray-700" : "border-gray-200"}`, children: [
|
|
644
|
+
/* @__PURE__ */ jsxs("div", { className: "text-center flex-1", children: [
|
|
645
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: mode === "splitpayment" ? "Split Payment QR" : "QR Code Payment" }),
|
|
646
|
+
config.amount && /* @__PURE__ */ jsxs("p", { className: "text-2xl font-bold text-purple-600", children: [
|
|
647
|
+
"$",
|
|
648
|
+
config.amount.toFixed(2)
|
|
649
|
+
] }),
|
|
650
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-blue-600", children: mode === "splitpayment" ? "๐ฑ Scan to redirect to payment screen" : "๐ฑ Scan with mobile wallet" })
|
|
651
|
+
] }),
|
|
652
|
+
/* @__PURE__ */ jsx(
|
|
653
|
+
"button",
|
|
654
|
+
{
|
|
655
|
+
onClick: onClose,
|
|
656
|
+
className: `ml-4 text-gray-500 hover:text-gray-700 ${isDark ? "hover:text-gray-300" : ""}`,
|
|
657
|
+
children: "โ"
|
|
658
|
+
}
|
|
659
|
+
)
|
|
660
|
+
] }),
|
|
661
|
+
/* @__PURE__ */ jsxs("div", { className: "p-4 min-h-[400px]", children: [
|
|
662
|
+
error && /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 bg-red-100 text-red-700 rounded border border-red-200", children: [
|
|
663
|
+
error,
|
|
664
|
+
/* @__PURE__ */ jsx(
|
|
665
|
+
"button",
|
|
666
|
+
{
|
|
667
|
+
onClick: () => setError(""),
|
|
668
|
+
className: "ml-2 text-red-500 hover:text-red-700",
|
|
669
|
+
children: "โ"
|
|
670
|
+
}
|
|
671
|
+
)
|
|
672
|
+
] }),
|
|
673
|
+
currentStep === "network" && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
674
|
+
/* @__PURE__ */ jsx("h3", { className: "font-medium text-center", children: "Select Network" }),
|
|
675
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: networks.map((network) => {
|
|
676
|
+
var _a2;
|
|
677
|
+
const hasAddress = (_a2 = config.merchantWalletAddresses) == null ? void 0 : _a2[network.shortName];
|
|
678
|
+
return /* @__PURE__ */ jsxs(
|
|
679
|
+
"button",
|
|
680
|
+
{
|
|
681
|
+
onClick: () => {
|
|
682
|
+
setSelectedNetwork(network);
|
|
683
|
+
setCurrentStep("token");
|
|
684
|
+
},
|
|
685
|
+
disabled: !hasAddress,
|
|
686
|
+
className: `w-full p-3 border rounded-lg text-left transition-colors ${hasAddress ? `hover:border-purple-500 ${isDark ? "border-gray-600 hover:bg-gray-800" : "border-gray-200 hover:bg-gray-50"}` : `opacity-50 cursor-not-allowed ${isDark ? "border-gray-700" : "border-gray-100"}`}`,
|
|
687
|
+
children: [
|
|
688
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium", children: network.name }),
|
|
689
|
+
/* @__PURE__ */ jsxs("div", { className: `text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`, children: [
|
|
690
|
+
network.shortName.toUpperCase(),
|
|
691
|
+
!hasAddress && " (No merchant address configured)"
|
|
692
|
+
] })
|
|
693
|
+
]
|
|
694
|
+
},
|
|
695
|
+
network.id
|
|
696
|
+
);
|
|
697
|
+
}) })
|
|
698
|
+
] }),
|
|
699
|
+
currentStep === "token" && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
700
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
701
|
+
/* @__PURE__ */ jsx(
|
|
702
|
+
"button",
|
|
703
|
+
{
|
|
704
|
+
onClick: () => setCurrentStep("network"),
|
|
705
|
+
className: `${isDark ? "text-gray-400 hover:text-gray-200" : "text-gray-500 hover:text-gray-700"}`,
|
|
706
|
+
children: "โ"
|
|
707
|
+
}
|
|
708
|
+
),
|
|
709
|
+
/* @__PURE__ */ jsxs("h3", { className: "font-medium", children: [
|
|
710
|
+
"Select Token on ",
|
|
711
|
+
selectedNetwork == null ? void 0 : selectedNetwork.name
|
|
712
|
+
] })
|
|
713
|
+
] }),
|
|
714
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: tokens.filter((token) => {
|
|
715
|
+
var _a2;
|
|
716
|
+
return ((_a2 = token.Network) == null ? void 0 : _a2.shortName) === (selectedNetwork == null ? void 0 : selectedNetwork.shortName);
|
|
717
|
+
}).map((token) => /* @__PURE__ */ jsxs(
|
|
718
|
+
"button",
|
|
719
|
+
{
|
|
720
|
+
onClick: () => {
|
|
721
|
+
setSelectedToken(token);
|
|
722
|
+
generateSplitPaymentQRDirect(token);
|
|
723
|
+
},
|
|
724
|
+
disabled: loading,
|
|
725
|
+
className: `w-full p-3 border rounded-lg hover:border-purple-500 text-left transition-colors disabled:opacity-50 ${isDark ? "border-gray-600 hover:bg-gray-800" : "border-gray-200 hover:bg-gray-50"}`,
|
|
726
|
+
children: [
|
|
727
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium", children: token.name }),
|
|
728
|
+
/* @__PURE__ */ jsx("div", { className: `text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`, children: token.symbol })
|
|
729
|
+
]
|
|
730
|
+
},
|
|
731
|
+
token.id
|
|
732
|
+
)) })
|
|
733
|
+
] }),
|
|
734
|
+
currentStep === "scan" && /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
735
|
+
/* @__PURE__ */ jsx("h3", { className: "font-medium mb-4", children: mode === "splitpayment" ? "Scan QR Code to Open Payment Screen" : "Scan QR Code with Mobile Wallet" }),
|
|
736
|
+
qrCodeImage ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center space-y-4", children: [
|
|
737
|
+
/* @__PURE__ */ jsx("div", { className: `p-4 rounded-lg ${isDark ? "bg-gray-800" : "bg-gray-50"}`, children: /* @__PURE__ */ jsx(
|
|
738
|
+
"img",
|
|
739
|
+
{
|
|
740
|
+
src: qrCodeImage,
|
|
741
|
+
alt: "Payment QR Code",
|
|
742
|
+
className: "mx-auto",
|
|
743
|
+
style: { width: "200px", height: "200px" }
|
|
744
|
+
}
|
|
745
|
+
) }),
|
|
746
|
+
/* @__PURE__ */ jsxs("div", { className: `text-sm space-y-1 ${isDark ? "text-gray-300" : "text-gray-600"}`, children: [
|
|
747
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
748
|
+
/* @__PURE__ */ jsx("span", { children: "Amount:" }),
|
|
749
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
750
|
+
paymentData == null ? void 0 : paymentData.totalAmount,
|
|
751
|
+
" ",
|
|
752
|
+
selectedToken == null ? void 0 : selectedToken.symbol
|
|
753
|
+
] })
|
|
754
|
+
] }),
|
|
755
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
756
|
+
/* @__PURE__ */ jsx("span", { children: "Network:" }),
|
|
757
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: selectedNetwork == null ? void 0 : selectedNetwork.name })
|
|
758
|
+
] }),
|
|
759
|
+
mode === "splitpayment" && /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
760
|
+
/* @__PURE__ */ jsx("span", { children: "Merchant:" }),
|
|
761
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: config.merchantName || "Coinley Merchant" })
|
|
762
|
+
] })
|
|
763
|
+
] }),
|
|
764
|
+
/* @__PURE__ */ jsx("div", { className: `p-3 rounded-lg text-xs ${isDark ? "bg-blue-900 text-blue-200" : "bg-blue-50 text-blue-600"}`, children: mode === "splitpayment" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
765
|
+
"๐ฑ Scan this QR code with your mobile wallet to open the payment screen.",
|
|
766
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
767
|
+
"The payment screen will auto-connect your wallet and execute the split payment."
|
|
768
|
+
] }) : "๐ฑ Open your mobile wallet and scan this QR code to connect and pay" }),
|
|
769
|
+
mode === "splitpayment" ? /* @__PURE__ */ jsx(
|
|
770
|
+
"button",
|
|
771
|
+
{
|
|
772
|
+
onClick: () => window.open(qrCodeData, "_blank"),
|
|
773
|
+
className: "w-full bg-purple-600 text-white py-2 px-4 rounded hover:bg-purple-700 text-sm",
|
|
774
|
+
children: "๐ Test Payment Screen (Opens in new tab)"
|
|
775
|
+
}
|
|
776
|
+
) : /* @__PURE__ */ jsx(
|
|
777
|
+
"button",
|
|
778
|
+
{
|
|
779
|
+
onClick: simulateWalletScan,
|
|
780
|
+
disabled: loading,
|
|
781
|
+
className: "w-full bg-gray-600 text-white py-2 px-4 rounded hover:bg-gray-700 disabled:opacity-50 text-sm",
|
|
782
|
+
children: loading ? "Connecting..." : "๐งช Simulate Wallet Scan (Test)"
|
|
783
|
+
}
|
|
784
|
+
)
|
|
785
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center py-8", children: [
|
|
786
|
+
/* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" }),
|
|
787
|
+
/* @__PURE__ */ jsx("span", { className: "ml-2", children: "Generating QR Code..." })
|
|
788
|
+
] })
|
|
789
|
+
] }) }),
|
|
790
|
+
currentStep === "confirm" && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
791
|
+
/* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
792
|
+
/* @__PURE__ */ jsx("div", { className: "w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4", children: /* @__PURE__ */ jsx("span", { className: "text-2xl", children: "๐ฑ" }) }),
|
|
793
|
+
/* @__PURE__ */ jsx("h3", { className: "font-medium", children: "Mobile Wallet Connected" }),
|
|
794
|
+
/* @__PURE__ */ jsx("p", { className: `text-sm mt-2 ${isDark ? "text-gray-300" : "text-gray-600"}`, children: "Ready to complete payment" })
|
|
795
|
+
] }),
|
|
796
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
797
|
+
/* @__PURE__ */ jsxs("div", { className: "p-3 bg-green-50 rounded-lg border border-green-200", children: [
|
|
798
|
+
/* @__PURE__ */ jsx("p", { className: "text-green-600 text-sm font-medium", children: "โ
Wallet Connected" }),
|
|
799
|
+
/* @__PURE__ */ jsxs("p", { className: "font-mono text-xs text-green-700 mt-1", children: [
|
|
800
|
+
walletAddress == null ? void 0 : walletAddress.slice(0, 6),
|
|
801
|
+
"...",
|
|
802
|
+
walletAddress == null ? void 0 : walletAddress.slice(-4)
|
|
803
|
+
] })
|
|
804
|
+
] }),
|
|
805
|
+
balanceValidation && /* @__PURE__ */ jsx("div", { className: `p-3 rounded-lg border ${balanceValidation.isValid ? "bg-green-50 border-green-200" : "bg-red-50 border-red-200"}`, children: /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-sm", children: [
|
|
806
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
807
|
+
/* @__PURE__ */ jsx("span", { children: "Required:" }),
|
|
808
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
809
|
+
paymentData == null ? void 0 : paymentData.totalAmount,
|
|
810
|
+
" ",
|
|
811
|
+
selectedToken == null ? void 0 : selectedToken.symbol
|
|
812
|
+
] })
|
|
813
|
+
] }),
|
|
814
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
815
|
+
/* @__PURE__ */ jsx("span", { children: "Your Balance:" }),
|
|
816
|
+
/* @__PURE__ */ jsx("span", { className: `font-medium ${balanceValidation.hasEnoughTokens ? "text-green-600" : "text-red-600"}`, children: balanceChecker.formatBalance(
|
|
817
|
+
(_a = balanceValidation.tokenBalance) == null ? void 0 : _a.balance,
|
|
818
|
+
selectedToken == null ? void 0 : selectedToken.symbol
|
|
819
|
+
) })
|
|
820
|
+
] }),
|
|
821
|
+
balanceValidation.nativeBalance && /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
822
|
+
/* @__PURE__ */ jsx("span", { children: "Gas Balance:" }),
|
|
823
|
+
/* @__PURE__ */ jsx("span", { className: `font-medium ${balanceValidation.hasEnoughForGas ? "text-green-600" : "text-red-600"}`, children: balanceChecker.formatBalance(
|
|
824
|
+
balanceValidation.nativeBalance.balance,
|
|
825
|
+
balanceValidation.nativeBalance.symbol
|
|
826
|
+
) })
|
|
827
|
+
] })
|
|
828
|
+
] }) }),
|
|
829
|
+
/* @__PURE__ */ jsx(
|
|
830
|
+
"button",
|
|
831
|
+
{
|
|
832
|
+
onClick: simulateTransaction,
|
|
833
|
+
disabled: checkingPayment || !(balanceValidation == null ? void 0 : balanceValidation.isValid),
|
|
834
|
+
className: "w-full bg-green-600 text-white py-3 px-4 rounded-lg hover:bg-green-700 disabled:opacity-50 font-medium",
|
|
835
|
+
children: checkingPayment ? "Processing Payment..." : !(balanceValidation == null ? void 0 : balanceValidation.isValid) ? "Insufficient Balance" : `Confirm Payment of ${paymentData == null ? void 0 : paymentData.totalAmount} ${selectedToken == null ? void 0 : selectedToken.symbol}`
|
|
836
|
+
}
|
|
837
|
+
)
|
|
838
|
+
] })
|
|
839
|
+
] }),
|
|
840
|
+
currentStep === "success" && /* @__PURE__ */ jsxs("div", { className: "text-center space-y-4", children: [
|
|
841
|
+
/* @__PURE__ */ jsx("div", { className: "w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto", children: /* @__PURE__ */ jsx("span", { className: "text-2xl", children: "โ
" }) }),
|
|
842
|
+
/* @__PURE__ */ jsx("h3", { className: "font-medium text-green-600", children: "Payment Successful!" }),
|
|
843
|
+
/* @__PURE__ */ jsx("p", { className: `text-sm ${isDark ? "text-gray-300" : "text-gray-600"}`, children: "Transaction completed via QR code scan" }),
|
|
844
|
+
/* @__PURE__ */ jsx(
|
|
845
|
+
"button",
|
|
846
|
+
{
|
|
847
|
+
onClick: onClose,
|
|
848
|
+
className: "w-full bg-green-600 text-white py-3 px-4 rounded-lg hover:bg-green-700 font-medium",
|
|
849
|
+
children: "Close"
|
|
850
|
+
}
|
|
851
|
+
)
|
|
852
|
+
] }),
|
|
853
|
+
loading && currentStep !== "scan" && currentStep !== "confirm" && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" }) })
|
|
854
|
+
] })
|
|
855
|
+
] }) });
|
|
856
|
+
};
|
|
857
|
+
export {
|
|
858
|
+
QRCodePayment as default
|
|
859
|
+
};
|
|
860
|
+
//# sourceMappingURL=QRCodePayment-C50CNuFu.js.map
|