180 lines
8.5 KiB
TypeScript
180 lines
8.5 KiB
TypeScript
'use client';
|
||
|
||
import React, { useState, useEffect } from 'react';
|
||
import {
|
||
Coins,
|
||
Copy,
|
||
CheckCircle2,
|
||
ExternalLink,
|
||
RefreshCw,
|
||
AlertCircle,
|
||
QrCode
|
||
} from 'lucide-react';
|
||
|
||
interface CryptoCheckoutProps {
|
||
amount: number;
|
||
currency: string;
|
||
txId: string;
|
||
onSuccess: (txHash: string) => void;
|
||
}
|
||
|
||
export default function CryptoCheckout({ amount, currency, txId, onSuccess }: CryptoCheckoutProps) {
|
||
const [selectedCoin, setSelectedCoin] = useState('SOL');
|
||
const [depositAddress, setDepositAddress] = useState<string>('');
|
||
const [isVerifying, setIsVerifying] = useState(false);
|
||
const [copied, setCopied] = useState(false);
|
||
const [status, setStatus] = useState<'waiting' | 'verifying' | 'success' | 'error'>('waiting');
|
||
const [cryptoAmount, setCryptoAmount] = useState<string>('Hesaplanıyor...');
|
||
|
||
useEffect(() => {
|
||
async function fetchExchangeRate() {
|
||
if (currency === 'TL' || currency === 'TRY') {
|
||
try {
|
||
const symbol = selectedCoin === 'SOL' ? 'SOLTRY' : 'USDTTRY';
|
||
const res = await fetch(`https://api.binance.com/api/v3/ticker/price?symbol=${symbol}`);
|
||
const data = await res.json();
|
||
const rate = parseFloat(data.price);
|
||
setCryptoAmount((amount / rate).toFixed(selectedCoin === 'SOL' ? 4 : 2));
|
||
} catch (error) {
|
||
// Fallback rate if API fails
|
||
setCryptoAmount((amount / 5500).toFixed(selectedCoin === 'SOL' ? 4 : 2));
|
||
}
|
||
} else {
|
||
// If already USD or USDT, 1:1 ratio
|
||
setCryptoAmount(amount.toFixed(2));
|
||
}
|
||
}
|
||
fetchExchangeRate();
|
||
}, [amount, currency]);
|
||
|
||
useEffect(() => {
|
||
// Use a real valid Solana test wallet so Phantom doesn't say "Invalid Address"
|
||
setDepositAddress('5pLH1tqZhx8p8WpZ18yr28N42KXB3FXVPzZ9ceCtpBVe');
|
||
}, [selectedCoin]);
|
||
|
||
const handleCopy = () => {
|
||
navigator.clipboard.writeText(depositAddress);
|
||
setCopied(true);
|
||
setTimeout(() => setCopied(false), 2000);
|
||
};
|
||
|
||
const verifyPayment = async () => {
|
||
setIsVerifying(true);
|
||
setStatus('verifying');
|
||
|
||
try {
|
||
const response = await fetch('/api/crypto-sweep', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
txId: txId,
|
||
merchantAddress: '5pLH1tqZhx8p8WpZ18yr28N42KXB3FXVPzZ9ceCtpBVe' // A placeholder valid Solana Devnet Wallet
|
||
})
|
||
});
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
setStatus('success');
|
||
onSuccess(data.hashes.merchant);
|
||
} else {
|
||
setStatus('error');
|
||
}
|
||
} catch (err) {
|
||
setStatus('error');
|
||
} finally {
|
||
setIsVerifying(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="bg-white p-8 lg:p-12 rounded-[40px] border border-gray-100 shadow-sm space-y-8 animate-in fade-in zoom-in duration-500 w-full max-w-lg">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<div className="p-3 bg-orange-50 rounded-2xl text-orange-600">
|
||
<Coins size={24} />
|
||
</div>
|
||
<div>
|
||
<h3 className="text-xl font-black text-gray-900 uppercase tracking-tight">Kripto Ödeme</h3>
|
||
<p className="text-[10px] text-gray-400 font-black uppercase tracking-widest">On-Chain Güvenli Transfer</p>
|
||
</div>
|
||
</div>
|
||
<div className="text-right">
|
||
<p className="text-2xl font-black text-gray-900">{cryptoAmount} <span className="text-xs text-gray-400">{selectedCoin}</span></p>
|
||
<p className="text-[10px] text-gray-400 font-bold uppercase">Ağ: Solana (Devnet)</p>
|
||
</div>
|
||
</div>
|
||
|
||
{status === 'success' ? (
|
||
<div className="py-10 text-center space-y-6">
|
||
<div className="w-20 h-20 bg-emerald-50 rounded-[32px] flex items-center justify-center text-emerald-500 mx-auto animate-bounce">
|
||
<CheckCircle2 size={40} />
|
||
</div>
|
||
<div className="space-y-2">
|
||
<h2 className="text-2xl font-black text-gray-900 uppercase tracking-tight">Ödeme Onaylandı!</h2>
|
||
<p className="text-gray-400 font-bold uppercase tracking-widest text-[10px]">İşleminiz başarıyla blokzincirine kaydedildi.</p>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<>
|
||
{/* QR Code Placeholder */}
|
||
<div className="bg-gray-50 aspect-square rounded-[32px] flex flex-col items-center justify-center border border-gray-100 relative group cursor-pointer">
|
||
<QrCode size={180} className="text-gray-200 group-hover:text-gray-400 transition-colors" />
|
||
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||
<span className="bg-white px-4 py-2 rounded-xl text-[10px] font-black uppercase shadow-lg border border-gray-100">Büyüt</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-1">Cüzdan Adresi</label>
|
||
<div className="flex items-center gap-2">
|
||
<div className="flex-1 bg-gray-50 p-4 rounded-2xl border border-gray-100 font-mono text-[10px] text-gray-600 break-all leading-tight">
|
||
{depositAddress}
|
||
</div>
|
||
<button
|
||
onClick={handleCopy}
|
||
className="p-4 bg-gray-900 text-white rounded-2xl hover:bg-black transition-all shadow-lg shadow-gray-200 active:scale-95"
|
||
>
|
||
{copied ? <CheckCircle2 size={18} /> : <Copy size={18} />}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-6 bg-blue-50/50 rounded-3xl border border-blue-100 space-y-2">
|
||
<div className="flex items-center gap-2 text-blue-600">
|
||
<AlertCircle size={14} />
|
||
<span className="text-[9px] font-black uppercase tracking-widest">Önemli Uyarı</span>
|
||
</div>
|
||
<p className="text-[10px] text-blue-800 leading-relaxed font-medium">
|
||
Lütfen sadece test amaçlı <b>Solana (Devnet)</b> ağı üzerinden test SOL'ü gönderimi yapın. Gerçek ağ veya USDT bu ortamda kabul edilmez.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
onClick={verifyPayment}
|
||
disabled={isVerifying}
|
||
className="w-full py-5 bg-gray-900 text-white rounded-2xl font-black text-xs uppercase tracking-[0.2em] hover:bg-black transition-all shadow-xl shadow-gray-200 flex items-center justify-center gap-3 disabled:opacity-50"
|
||
>
|
||
{isVerifying ? (
|
||
<>
|
||
<RefreshCw size={18} className="animate-spin" />
|
||
Doğrulanıyor...
|
||
</>
|
||
) : (
|
||
'Ödemeyi Doğrula'
|
||
)}
|
||
</button>
|
||
|
||
<div className="flex justify-center gap-6">
|
||
<div className="flex items-center gap-2 text-gray-400 hover:text-gray-600 cursor-pointer transition-colors group">
|
||
<ExternalLink size={14} />
|
||
<span className="text-[9px] font-black uppercase tracking-widest group-hover:underline">Explorer'da Gör</span>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|