Implement automated custodial crypto sweep logic (%1 platform, %99 merchant)

This commit is contained in:
2026-03-01 00:56:46 +03:00
parent 3562e10713
commit 6b40444639
7 changed files with 576 additions and 54 deletions

View File

@@ -0,0 +1,160 @@
'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('USDT');
const [depositAddress, setDepositAddress] = useState<string>('');
const [isVerifying, setIsVerifying] = useState(false);
const [copied, setCopied] = useState(false);
const [status, setStatus] = useState<'waiting' | 'verifying' | 'success' | 'error'>('waiting');
// Exchange rate mock: 1 USD = 35 TRY, 1 USDT = 1 USD
const cryptoAmount = (currency === 'TL' ? (amount / 35) : amount).toFixed(2);
useEffect(() => {
// Mock address generation
setDepositAddress('0x' + Math.random().toString(16).slice(2, 42));
}, [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: '0x123...MERCHANT_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">: Polygon (MATIC)</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 <b>Polygon (PoS)</b> ı üzerinden gönderim yapın. Yanlış üzerinden gönderilen varlıklar kurtarılamaz.
</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>
);
}