coinley-test 0.0.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.
@@ -0,0 +1,787 @@
1
+ //src/CoinleyPayment.jsx
2
+ import React, { useState, useEffect, useRef } from 'react';
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import {
5
+ Wallet,
6
+ QrCode,
7
+ ChevronDown,
8
+ Copy,
9
+ Check,
10
+ X,
11
+ ArrowLeft,
12
+ Loader2,
13
+ ExternalLink,
14
+ AlertCircle,
15
+ CheckCircle2,
16
+ Zap,
17
+ Sparkles
18
+ } from 'lucide-react';
19
+ import QRCodeLib from 'qrcode';
20
+ import {
21
+ createCoinleyWalletConfig,
22
+ CoinleyWalletProvider,
23
+ ConnectButton,
24
+ WalletModal,
25
+ useWallet,
26
+ useWalletConnect,
27
+ useWalletTransaction,
28
+ useWalletModal
29
+ } from '@coinley/wallet-connect-core';
30
+ import { PaymentAPI } from './PaymentAPI';
31
+
32
+ // Wallet Integration Component
33
+ const WalletIntegration = ({
34
+ selectedNetwork,
35
+ selectedToken,
36
+ paymentData,
37
+ onTransactionSent,
38
+ onError,
39
+ isConnecting,
40
+ setIsConnecting
41
+ }) => {
42
+ const { isConnected, address } = useWallet();
43
+ const { connectWallet } = useWalletConnect();
44
+ const { sendTransaction } = useWalletTransaction();
45
+ const { openModal } = useWalletModal();
46
+
47
+ useEffect(() => {
48
+ if (isConnected && paymentData && selectedNetwork && selectedToken) {
49
+ handleSendTransaction();
50
+ }
51
+ }, [isConnected, paymentData]);
52
+
53
+ const handleConnectWallet = async () => {
54
+ try {
55
+ setIsConnecting(true);
56
+ openModal();
57
+ } catch (error) {
58
+ console.error('Wallet connection failed:', error);
59
+ onError(error.message || 'Failed to connect wallet');
60
+ setIsConnecting(false);
61
+ }
62
+ };
63
+
64
+ const handleSendTransaction = async () => {
65
+ if (!isConnected || !paymentData || !selectedToken) return;
66
+
67
+ try {
68
+ setIsConnecting(true);
69
+
70
+ const recipientAddress = paymentData.metadata?.recipientWallet;
71
+ if (!recipientAddress) {
72
+ throw new Error('Recipient wallet address not found');
73
+ }
74
+
75
+ let txHash;
76
+
77
+ if (selectedToken.contractAddress) {
78
+ // ERC-20 Token Transaction
79
+ const amount = (paymentData.totalAmount * Math.pow(10, selectedToken.decimals)).toString();
80
+ const transferData = `0xa9059cbb${recipientAddress.slice(2).padStart(64, '0')}${parseInt(amount).toString(16).padStart(64, '0')}`;
81
+
82
+ txHash = await sendTransaction({
83
+ to: selectedToken.contractAddress,
84
+ data: transferData,
85
+ value: '0x0'
86
+ });
87
+ } else {
88
+ // Native Token Transaction
89
+ const value = (paymentData.totalAmount * Math.pow(10, 18)).toString(16);
90
+
91
+ txHash = await sendTransaction({
92
+ to: recipientAddress,
93
+ value: `0x${value}`
94
+ });
95
+ }
96
+
97
+ if (txHash) {
98
+ onTransactionSent(txHash);
99
+ }
100
+ } catch (error) {
101
+ console.error('Transaction failed:', error);
102
+ onError(error.message || 'Transaction failed');
103
+ } finally {
104
+ setIsConnecting(false);
105
+ }
106
+ };
107
+
108
+ return (
109
+ <div className="text-center space-y-6">
110
+ <div className="space-y-4">
111
+ <div className="w-16 h-16 bg-[#7042D2] bg-opacity-20 rounded-full flex items-center justify-center mx-auto">
112
+ <Wallet className="w-8 h-8 text-[#7042D2]" />
113
+ </div>
114
+ <h3 className="text-lg font-semibold">
115
+ {isConnected ? 'Send Payment' : 'Connect Your Wallet'}
116
+ </h3>
117
+
118
+ {!isConnected ? (
119
+ <div className="space-y-4">
120
+ <p className="text-gray-600 dark:text-gray-400">
121
+ Connect your wallet to complete the payment
122
+ </p>
123
+ <ConnectButton
124
+ theme="gradient"
125
+ size="large"
126
+ customText="Connect Wallet"
127
+ />
128
+ </div>
129
+ ) : (
130
+ <div className="space-y-4">
131
+ <p className="text-green-600 dark:text-green-400">
132
+ ✅ Wallet Connected: {address?.slice(0, 6)}...{address?.slice(-4)}
133
+ </p>
134
+
135
+ {paymentData && (
136
+ <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 space-y-2">
137
+ <div className="flex justify-between items-center">
138
+ <span className="text-sm text-gray-600 dark:text-gray-400">Amount:</span>
139
+ <span className="font-medium">{paymentData.totalAmount} {selectedToken?.symbol}</span>
140
+ </div>
141
+ <div className="flex justify-between items-center">
142
+ <span className="text-sm text-gray-600 dark:text-gray-400">Network:</span>
143
+ <span className="font-medium">{selectedNetwork?.name}</span>
144
+ </div>
145
+ </div>
146
+ )}
147
+
148
+ {isConnecting && (
149
+ <div className="flex items-center justify-center space-x-2">
150
+ <Loader2 className="w-5 h-5 animate-spin text-[#7042D2]" />
151
+ <span>Processing transaction...</span>
152
+ </div>
153
+ )}
154
+ </div>
155
+ )}
156
+ </div>
157
+ </div>
158
+ );
159
+ };
160
+
161
+ const CoinleyPayment = ({
162
+ apiKey,
163
+ apiSecret,
164
+ apiUrl = 'http://localhost:9000',
165
+ onSuccess,
166
+ onError,
167
+ onClose,
168
+ isOpen,
169
+ config,
170
+ theme = 'light',
171
+ merchantName,
172
+ debug = false
173
+ }) => {
174
+ // State management
175
+ const [currentStep, setCurrentStep] = useState('select-method');
176
+ const [networks, setNetworks] = useState([]);
177
+ const [tokens, setTokens] = useState([]);
178
+ const [selectedNetwork, setSelectedNetwork] = useState(null);
179
+ const [selectedToken, setSelectedToken] = useState(null);
180
+ const [paymentMethod, setPaymentMethod] = useState(null);
181
+ const [qrCode, setQrCode] = useState('');
182
+ const [paymentData, setPaymentData] = useState(null);
183
+ const [loading, setLoading] = useState(false);
184
+ const [error, setError] = useState('');
185
+ const [copied, setCopied] = useState(false);
186
+ const [txHash, setTxHash] = useState('');
187
+ const [isConnecting, setIsConnecting] = useState(false);
188
+
189
+ // Services
190
+ const paymentAPI = useRef(new PaymentAPI(apiUrl, apiKey, apiSecret));
191
+
192
+ // Wallet configuration
193
+ const walletConfig = createCoinleyWalletConfig({
194
+ appName: merchantName || 'Coinley Payment',
195
+ appDescription: 'Crypto payment processing',
196
+ chains: ['ethereum', 'polygon', 'bsc', 'arbitrum']
197
+ });
198
+
199
+ // Initialize
200
+ useEffect(() => {
201
+ if (isOpen) {
202
+ initializePayment();
203
+ }
204
+ }, [isOpen]);
205
+
206
+ const initializePayment = async () => {
207
+ try {
208
+ setLoading(true);
209
+ setError('');
210
+
211
+ const [networksRes, tokensRes] = await Promise.all([
212
+ paymentAPI.current.getNetworks(),
213
+ paymentAPI.current.getStablecoins()
214
+ ]);
215
+
216
+ setNetworks(networksRes.networks || []);
217
+ setTokens(tokensRes.stablecoins || []);
218
+
219
+ if (debug) {
220
+ console.log('Initialized networks:', networksRes.networks);
221
+ console.log('Initialized tokens:', tokensRes.stablecoins);
222
+ }
223
+ } catch (err) {
224
+ console.error('Failed to initialize payment:', err);
225
+ setError('Failed to load payment options. Please try again.');
226
+ } finally {
227
+ setLoading(false);
228
+ }
229
+ };
230
+
231
+ const handleMethodSelect = (method) => {
232
+ setPaymentMethod(method);
233
+ setCurrentStep('select-network');
234
+ };
235
+
236
+ const handleNetworkSelect = async (network) => {
237
+ setSelectedNetwork(network);
238
+
239
+ const networkTokens = tokens.filter(token =>
240
+ token.Network?.shortName === network.shortName || token.networkId === network.id
241
+ );
242
+
243
+ if (networkTokens.length > 0) {
244
+ const preferredToken = networkTokens.find(t => t.symbol === 'USDT') || networkTokens[0];
245
+ setSelectedToken(preferredToken);
246
+ await initiatePayment(network, preferredToken);
247
+ }
248
+ };
249
+
250
+ const initiatePayment = async (network, token) => {
251
+ try {
252
+ setLoading(true);
253
+
254
+ const paymentPayload = {
255
+ amount: config.amount,
256
+ currency: token.symbol,
257
+ network: network.shortName,
258
+ customerEmail: config.customerEmail,
259
+ callbackUrl: config.callbackUrl,
260
+ metadata: {
261
+ ...config.metadata,
262
+ paymentMethod,
263
+ selectedNetwork: network.shortName,
264
+ selectedToken: token.symbol
265
+ }
266
+ };
267
+
268
+ const payment = await paymentAPI.current.createPayment(paymentPayload);
269
+ setPaymentData(payment.payment);
270
+
271
+ if (paymentMethod === 'wallet') {
272
+ setCurrentStep('wallet-connect');
273
+ } else {
274
+ await generateQRCode(payment.payment);
275
+ setCurrentStep('qr-code');
276
+ }
277
+ } catch (err) {
278
+ console.error('Payment initiation failed:', err);
279
+ setError(err.message || 'Failed to create payment');
280
+ setCurrentStep('error');
281
+ } finally {
282
+ setLoading(false);
283
+ }
284
+ };
285
+
286
+ const generateQRCode = async (payment) => {
287
+ try {
288
+ const recipientAddress = payment.metadata?.recipientWallet ||
289
+ config.merchantWalletAddresses?.[selectedNetwork?.shortName || ''];
290
+
291
+ if (!recipientAddress) {
292
+ throw new Error('Recipient wallet address not found');
293
+ }
294
+
295
+ let qrData = '';
296
+
297
+ if (selectedNetwork?.type === 'ethereum' || selectedNetwork?.type === 'bsc') {
298
+ qrData = selectedToken?.contractAddress
299
+ ? `ethereum:${selectedToken.contractAddress}/transfer?address=${recipientAddress}&uint256=${payment.totalAmount * Math.pow(10, selectedToken.decimals)}`
300
+ : `ethereum:${recipientAddress}?value=${payment.totalAmount}e18`;
301
+ } else if (selectedNetwork?.type === 'tron') {
302
+ qrData = `tron:${recipientAddress}?amount=${payment.totalAmount}`;
303
+ } else {
304
+ qrData = `${selectedToken?.symbol}:${recipientAddress}?amount=${payment.totalAmount}`;
305
+ }
306
+
307
+ const qrCodeDataURL = await QRCodeLib.toDataURL(qrData, {
308
+ width: 256,
309
+ margin: 2,
310
+ color: {
311
+ dark: theme === 'dark' ? '#FFFFFF' : '#000000',
312
+ light: theme === 'dark' ? '#000000' : '#FFFFFF'
313
+ }
314
+ });
315
+
316
+ setQrCode(qrCodeDataURL);
317
+ startPaymentVerification(payment.id);
318
+
319
+ } catch (err) {
320
+ console.error('QR code generation failed:', err);
321
+ setError('Failed to generate QR code');
322
+ setCurrentStep('error');
323
+ }
324
+ };
325
+
326
+ const startPaymentVerification = async (paymentId) => {
327
+ const maxAttempts = 60;
328
+ let attempts = 0;
329
+
330
+ const checkPayment = async () => {
331
+ try {
332
+ const result = await paymentAPI.current.verifyQRPayment(paymentId);
333
+
334
+ if (result.verified && result.payment?.transactionHash) {
335
+ setTxHash(result.payment.transactionHash);
336
+ await handlePaymentSuccess(paymentId, result.payment.transactionHash);
337
+ return true;
338
+ }
339
+
340
+ attempts++;
341
+ if (attempts < maxAttempts) {
342
+ setTimeout(checkPayment, 5000);
343
+ } else {
344
+ setError('Payment verification timeout. Please check your transaction.');
345
+ setCurrentStep('error');
346
+ }
347
+
348
+ return false;
349
+ } catch (err) {
350
+ console.error('Payment verification error:', err);
351
+ attempts++;
352
+ if (attempts < maxAttempts) {
353
+ setTimeout(checkPayment, 5000);
354
+ } else {
355
+ setError('Payment verification failed');
356
+ setCurrentStep('error');
357
+ }
358
+ return false;
359
+ }
360
+ };
361
+
362
+ setTimeout(checkPayment, 2000);
363
+ };
364
+
365
+ const handleTransactionSent = async (transactionHash) => {
366
+ setTxHash(transactionHash);
367
+ setCurrentStep('processing');
368
+
369
+ try {
370
+ await paymentAPI.current.processPayment(
371
+ paymentData.id,
372
+ transactionHash,
373
+ selectedNetwork?.shortName || '',
374
+ ''
375
+ );
376
+
377
+ await handlePaymentSuccess(paymentData.id, transactionHash);
378
+ } catch (error) {
379
+ console.error('Payment processing failed:', error);
380
+ await handlePaymentSuccess(paymentData.id, transactionHash);
381
+ }
382
+ };
383
+
384
+ const handlePaymentSuccess = async (paymentId, transactionHash) => {
385
+ setCurrentStep('success');
386
+
387
+ if (onSuccess) {
388
+ onSuccess(paymentId, transactionHash, {
389
+ network: selectedNetwork?.name,
390
+ token: selectedToken?.symbol,
391
+ amount: paymentData?.totalAmount,
392
+ method: paymentMethod
393
+ });
394
+ }
395
+ };
396
+
397
+ const copyToClipboard = async (text) => {
398
+ try {
399
+ await navigator.clipboard.writeText(text);
400
+ setCopied(true);
401
+ setTimeout(() => setCopied(false), 2000);
402
+ } catch (err) {
403
+ console.error('Failed to copy:', err);
404
+ }
405
+ };
406
+
407
+ const handleClose = () => {
408
+ setCurrentStep('select-method');
409
+ setPaymentMethod(null);
410
+ setSelectedNetwork(null);
411
+ setSelectedToken(null);
412
+ setQrCode('');
413
+ setPaymentData(null);
414
+ setError('');
415
+ setTxHash('');
416
+
417
+ if (onClose) {
418
+ onClose();
419
+ }
420
+ };
421
+
422
+ const goBack = () => {
423
+ if (currentStep === 'select-network') {
424
+ setCurrentStep('select-method');
425
+ } else if (currentStep === 'wallet-connect' || currentStep === 'qr-code') {
426
+ setCurrentStep('select-network');
427
+ }
428
+ };
429
+
430
+ if (!isOpen) return null;
431
+
432
+ return (
433
+ <CoinleyWalletProvider config={walletConfig} walletConfig={{ appName: merchantName || 'Coinley Payment' }}>
434
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 backdrop-blur-sm">
435
+ <motion.div
436
+ initial={{ scale: 0.9, opacity: 0 }}
437
+ animate={{ scale: 1, opacity: 1 }}
438
+ exit={{ scale: 0.9, opacity: 0 }}
439
+ className={`relative w-full max-w-md mx-4 rounded-2xl shadow-2xl overflow-hidden ${
440
+ theme === 'dark' ? 'bg-gray-900 text-white' : 'bg-white text-gray-900'
441
+ }`}
442
+ >
443
+ {/* Header */}
444
+ <div className="relative p-6 border-b border-gray-200 dark:border-gray-700">
445
+ {(currentStep !== 'select-method' && currentStep !== 'success' && currentStep !== 'error') && (
446
+ <button
447
+ onClick={goBack}
448
+ className="absolute left-4 top-6 p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
449
+ >
450
+ <ArrowLeft className="w-5 h-5" />
451
+ </button>
452
+ )}
453
+
454
+ <button
455
+ onClick={handleClose}
456
+ className="absolute right-4 top-6 p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
457
+ >
458
+ <X className="w-5 h-5" />
459
+ </button>
460
+
461
+ <div className="text-center">
462
+ <div className="flex items-center justify-center space-x-2 mb-2">
463
+ <Sparkles className="w-5 h-5 text-[#7042D2]" />
464
+ <h2 className="text-xl font-bold">Pay with Crypto</h2>
465
+ <Sparkles className="w-5 h-5 text-[#7042D2]" />
466
+ </div>
467
+ {merchantName && (
468
+ <p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
469
+ {merchantName}
470
+ </p>
471
+ )}
472
+ <div className="mt-2">
473
+ <span className="text-2xl font-bold text-[#7042D2]">
474
+ ${config.amount.toFixed(2)}
475
+ </span>
476
+ </div>
477
+ </div>
478
+ </div>
479
+
480
+ {/* Content */}
481
+ <div className="p-6 min-h-[400px]">
482
+ <AnimatePresence mode="wait">
483
+ {/* Step 1: Select Payment Method */}
484
+ {currentStep === 'select-method' && (
485
+ <motion.div
486
+ key="select-method"
487
+ initial={{ x: 20, opacity: 0 }}
488
+ animate={{ x: 0, opacity: 1 }}
489
+ exit={{ x: -20, opacity: 0 }}
490
+ className="space-y-4"
491
+ >
492
+ <h3 className="text-lg font-semibold text-center mb-6">
493
+ Choose Payment Method
494
+ </h3>
495
+
496
+ <motion.button
497
+ whileHover={{ scale: 1.02 }}
498
+ whileTap={{ scale: 0.98 }}
499
+ onClick={() => handleMethodSelect('wallet')}
500
+ className="w-full p-4 border-2 border-gray-200 dark:border-gray-700 rounded-xl hover:border-[#7042D2] transition-colors group"
501
+ >
502
+ <div className="flex items-center space-x-4">
503
+ <div className="p-3 bg-[#7042D2] bg-opacity-10 rounded-lg group-hover:bg-opacity-20 transition-colors">
504
+ <Wallet className="w-6 h-6 text-[#7042D2]" />
505
+ </div>
506
+ <div className="text-left">
507
+ <div className="font-semibold">Connect Wallet</div>
508
+ <div className="text-sm text-gray-600 dark:text-gray-400">
509
+ MetaMask, WalletConnect & more
510
+ </div>
511
+ </div>
512
+ <Zap className="w-5 h-5 text-[#7042D2] ml-auto" />
513
+ </div>
514
+ </motion.button>
515
+
516
+ <motion.button
517
+ whileHover={{ scale: 1.02 }}
518
+ whileTap={{ scale: 0.98 }}
519
+ onClick={() => handleMethodSelect('qr')}
520
+ className="w-full p-4 border-2 border-gray-200 dark:border-gray-700 rounded-xl hover:border-[#7042D2] transition-colors group"
521
+ >
522
+ <div className="flex items-center space-x-4">
523
+ <div className="p-3 bg-[#7042D2] bg-opacity-10 rounded-lg group-hover:bg-opacity-20 transition-colors">
524
+ <QrCode className="w-6 h-6 text-[#7042D2]" />
525
+ </div>
526
+ <div className="text-left">
527
+ <div className="font-semibold">QR Code</div>
528
+ <div className="text-sm text-gray-600 dark:text-gray-400">
529
+ Scan with mobile wallet
530
+ </div>
531
+ </div>
532
+ </div>
533
+ </motion.button>
534
+ </motion.div>
535
+ )}
536
+
537
+ {/* Step 2: Select Network */}
538
+ {currentStep === 'select-network' && (
539
+ <motion.div
540
+ key="select-network"
541
+ initial={{ x: 20, opacity: 0 }}
542
+ animate={{ x: 0, opacity: 1 }}
543
+ exit={{ x: -20, opacity: 0 }}
544
+ className="space-y-4"
545
+ >
546
+ <h3 className="text-lg font-semibold text-center mb-6">
547
+ Select Network
548
+ </h3>
549
+
550
+ {loading ? (
551
+ <div className="flex justify-center py-8">
552
+ <Loader2 className="w-8 h-8 animate-spin text-[#7042D2]" />
553
+ </div>
554
+ ) : (
555
+ <div className="space-y-3">
556
+ {networks.map((network) => (
557
+ <motion.button
558
+ key={network.id}
559
+ whileHover={{ scale: 1.02 }}
560
+ whileTap={{ scale: 0.98 }}
561
+ onClick={() => handleNetworkSelect(network)}
562
+ className="w-full p-4 border-2 border-gray-200 dark:border-gray-700 rounded-xl hover:border-[#7042D2] transition-colors group"
563
+ >
564
+ <div className="flex items-center space-x-4">
565
+ {network.logo ? (
566
+ <img
567
+ src={network.logo}
568
+ alt={network.name}
569
+ className="w-8 h-8 rounded-full"
570
+ />
571
+ ) : (
572
+ <div className="w-8 h-8 bg-[#7042D2] bg-opacity-20 rounded-full flex items-center justify-center">
573
+ <span className="text-sm font-bold text-[#7042D2]">
574
+ {network.shortName.charAt(0).toUpperCase()}
575
+ </span>
576
+ </div>
577
+ )}
578
+ <div className="text-left flex-1">
579
+ <div className="font-semibold">{network.name}</div>
580
+ <div className="text-sm text-gray-600 dark:text-gray-400">
581
+ {network.shortName.toUpperCase()}
582
+ </div>
583
+ </div>
584
+ <ChevronDown className="w-5 h-5 text-gray-400 transform -rotate-90" />
585
+ </div>
586
+ </motion.button>
587
+ ))}
588
+ </div>
589
+ )}
590
+ </motion.div>
591
+ )}
592
+
593
+ {/* Wallet Connect */}
594
+ {currentStep === 'wallet-connect' && (
595
+ <motion.div
596
+ key="wallet-connect"
597
+ initial={{ x: 20, opacity: 0 }}
598
+ animate={{ x: 0, opacity: 1 }}
599
+ exit={{ x: -20, opacity: 0 }}
600
+ >
601
+ <WalletIntegration
602
+ selectedNetwork={selectedNetwork}
603
+ selectedToken={selectedToken}
604
+ paymentData={paymentData}
605
+ onTransactionSent={handleTransactionSent}
606
+ onError={setError}
607
+ isConnecting={isConnecting}
608
+ setIsConnecting={setIsConnecting}
609
+ />
610
+ </motion.div>
611
+ )}
612
+
613
+ {/* QR Code */}
614
+ {currentStep === 'qr-code' && (
615
+ <motion.div
616
+ key="qr-code"
617
+ initial={{ x: 20, opacity: 0 }}
618
+ animate={{ x: 0, opacity: 1 }}
619
+ exit={{ x: -20, opacity: 0 }}
620
+ className="text-center space-y-6"
621
+ >
622
+ <div className="space-y-4">
623
+ <h3 className="text-lg font-semibold">Scan QR Code</h3>
624
+ <p className="text-gray-600 dark:text-gray-400">
625
+ Use your mobile wallet to scan and pay
626
+ </p>
627
+ </div>
628
+
629
+ {qrCode && (
630
+ <div className="bg-white p-4 rounded-xl mx-auto inline-block">
631
+ <img src={qrCode} alt="Payment QR Code" className="w-48 h-48" />
632
+ </div>
633
+ )}
634
+
635
+ {paymentData && (
636
+ <div className="space-y-3">
637
+ <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 space-y-2">
638
+ <div className="flex justify-between items-center">
639
+ <span className="text-sm text-gray-600 dark:text-gray-400">Network:</span>
640
+ <span className="font-medium">{selectedNetwork?.name}</span>
641
+ </div>
642
+ <div className="flex justify-between items-center">
643
+ <span className="text-sm text-gray-600 dark:text-gray-400">Amount:</span>
644
+ <span className="font-medium">{paymentData.totalAmount} {selectedToken?.symbol}</span>
645
+ </div>
646
+ </div>
647
+
648
+ <div className="flex items-center justify-center space-x-2 text-sm text-gray-600 dark:text-gray-400">
649
+ <Loader2 className="w-4 h-4 animate-spin" />
650
+ <span>Waiting for payment...</span>
651
+ </div>
652
+ </div>
653
+ )}
654
+ </motion.div>
655
+ )}
656
+
657
+ {/* Processing */}
658
+ {currentStep === 'processing' && (
659
+ <motion.div
660
+ key="processing"
661
+ initial={{ scale: 0.8, opacity: 0 }}
662
+ animate={{ scale: 1, opacity: 1 }}
663
+ className="text-center space-y-6"
664
+ >
665
+ <div className="w-16 h-16 bg-[#7042D2] bg-opacity-20 rounded-full flex items-center justify-center mx-auto">
666
+ <Loader2 className="w-8 h-8 animate-spin text-[#7042D2]" />
667
+ </div>
668
+ <div>
669
+ <h3 className="text-lg font-semibold text-[#7042D2]">Processing Payment...</h3>
670
+ <p className="text-gray-600 dark:text-gray-400 mt-2">
671
+ Your transaction is being processed
672
+ </p>
673
+ </div>
674
+ </motion.div>
675
+ )}
676
+
677
+ {/* Success */}
678
+ {currentStep === 'success' && (
679
+ <motion.div
680
+ key="success"
681
+ initial={{ scale: 0.8, opacity: 0 }}
682
+ animate={{ scale: 1, opacity: 1 }}
683
+ className="text-center space-y-6"
684
+ >
685
+ <div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto">
686
+ <CheckCircle2 className="w-8 h-8 text-green-500" />
687
+ </div>
688
+ <div>
689
+ <h3 className="text-lg font-semibold text-green-600">Payment Successful!</h3>
690
+ <p className="text-gray-600 dark:text-gray-400 mt-2">
691
+ Your payment has been processed successfully
692
+ </p>
693
+ </div>
694
+
695
+ {txHash && (
696
+ <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-4">
697
+ <div className="flex justify-between items-center">
698
+ <span className="text-sm text-gray-600 dark:text-gray-400">Transaction:</span>
699
+ <div className="flex items-center space-x-2">
700
+ <span className="font-mono text-sm">
701
+ {txHash.slice(0, 6)}...{txHash.slice(-4)}
702
+ </span>
703
+ <button
704
+ onClick={() => copyToClipboard(txHash)}
705
+ className="p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded"
706
+ >
707
+ <Copy className="w-4 h-4" />
708
+ </button>
709
+ {selectedNetwork?.explorerUrl && (
710
+ <a
711
+ href={`${selectedNetwork.explorerUrl}/tx/${txHash}`}
712
+ target="_blank"
713
+ rel="noopener noreferrer"
714
+ className="p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded"
715
+ >
716
+ <ExternalLink className="w-4 h-4" />
717
+ </a>
718
+ )}
719
+ </div>
720
+ </div>
721
+ </div>
722
+ )}
723
+
724
+ <button
725
+ onClick={handleClose}
726
+ className="w-full bg-[#7042D2] text-white py-3 rounded-lg font-semibold hover:bg-[#7042D2]/90 transition-colors"
727
+ >
728
+ Close
729
+ </button>
730
+ </motion.div>
731
+ )}
732
+
733
+ {/* Error */}
734
+ {currentStep === 'error' && (
735
+ <motion.div
736
+ key="error"
737
+ initial={{ scale: 0.8, opacity: 0 }}
738
+ animate={{ scale: 1, opacity: 1 }}
739
+ className="text-center space-y-6"
740
+ >
741
+ <div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto">
742
+ <AlertCircle className="w-8 h-8 text-red-500" />
743
+ </div>
744
+ <div>
745
+ <h3 className="text-lg font-semibold text-red-600">Payment Failed</h3>
746
+ <p className="text-gray-600 dark:text-gray-400 mt-2">
747
+ {error || 'Something went wrong. Please try again.'}
748
+ </p>
749
+ </div>
750
+
751
+ <div className="space-y-3">
752
+ <button
753
+ onClick={() => {
754
+ setCurrentStep('select-method');
755
+ setError('');
756
+ }}
757
+ className="w-full bg-[#7042D2] text-white py-3 rounded-lg font-semibold hover:bg-[#7042D2]/90 transition-colors"
758
+ >
759
+ Try Again
760
+ </button>
761
+ <button
762
+ onClick={handleClose}
763
+ className="w-full border border-gray-300 dark:border-gray-600 py-3 rounded-lg font-semibold hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
764
+ >
765
+ Close
766
+ </button>
767
+ </div>
768
+ </motion.div>
769
+ )}
770
+ </AnimatePresence>
771
+ </div>
772
+
773
+ {/* Footer */}
774
+ <div className="px-6 py-4 border-t border-gray-200 dark:border-gray-700 text-center">
775
+ <p className="text-xs text-gray-500">
776
+ Powered by <span className="font-semibold text-[#7042D2]">Coinley</span>
777
+ </p>
778
+ </div>
779
+ </motion.div>
780
+ </div>
781
+
782
+ <WalletModal />
783
+ </CoinleyWalletProvider>
784
+ );
785
+ };
786
+
787
+ export default CoinleyPayment;