import { NextResponse } from 'next/server'; import { CryptoEngine } from '@/lib/crypto-engine'; import { db } from '@/lib/db'; export async function POST(request: Request) { try { const body = await request.json(); const { txId, network, token } = body; if (!txId) { return NextResponse.json({ success: false, error: "Transaction ID is required" }, { status: 400 }); } const selectedNetwork = network || 'POLYGON'; const selectedToken = token || 'USDT'; console.log(`[API] Processing sweep for TX: ${txId} on ${selectedNetwork} with ${selectedToken}`); // 1. Fetch the transaction from DB // Search by either the internal UUID (id) or the provider ID (stripe_pi_id) const result = await db.query('SELECT * FROM transactions WHERE id::text = $1 OR stripe_pi_id = $1', [txId]); if (result.rows.length === 0) { return NextResponse.json({ success: false, error: "Transaction not found" }, { status: 404 }); } const transaction = result.rows[0]; // 1.1 Check if already processed if (transaction.status === 'succeeded') { return NextResponse.json({ success: true, message: "Transaction already processed", status: 'succeeded' }); } const metadata = transaction.metadata || {}; const wallets = metadata.wallets || {}; // 2. Determine which wallet to use // In the database, metadata.wallets is an object: { EVM: {address, privateKey}, SOLANA: {...}, ... } const tempWalletConfig = wallets[selectedNetwork] || wallets['EVM']; if (!tempWalletConfig || (!tempWalletConfig.privateKey && !tempWalletConfig.address)) { return NextResponse.json({ success: false, error: `No temporary wallet found for ${selectedNetwork}` }, { status: 500 }); } const depositAddress = typeof tempWalletConfig === 'string' ? tempWalletConfig : tempWalletConfig.address; const depositPrivateKey = typeof tempWalletConfig === 'string' ? null : tempWalletConfig.privateKey; if (!depositPrivateKey) { return NextResponse.json({ success: false, error: "Private key not found for sweep" }, { status: 500 }); } // 3. Define Platform Addresses & Fee const settings = await (async () => { const result = await db.query('SELECT key, value FROM system_settings WHERE key IN (\'sol_platform_address\', \'evm_platform_address\', \'tron_platform_address\', \'btc_platform_address\', \'default_fee_percent\')'); const map: Record = {}; result.rows.forEach(r => map[r.key] = r.value); return { sol: map.sol_platform_address || process.env.SOL_PLATFORM_ADDRESS || "Ajr4nKieZJVu9q2d1eVF9pQPRCLoZ6v4tapB3iQn2SyQ", evm: map.evm_platform_address || process.env.EVM_PLATFORM_ADDRESS || "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", tron: map.tron_platform_address || process.env.TRON_PLATFORM_ADDRESS || "TLYpfG6rre8Gv9m8pYjR7yvX7S9rK6G1P", btc: map.btc_platform_address || process.env.BTC_PLATFORM_ADDRESS || "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfJH", fee: parseFloat(map.default_fee_percent || '1.0') }; })(); let platformAddress = settings.evm; if (selectedNetwork === 'SOLANA') platformAddress = settings.sol; else if (selectedNetwork === 'TRON') platformAddress = settings.tron; else if (selectedNetwork === 'BITCOIN') platformAddress = settings.btc; // 4. Define Merchant Address & Fee const merchantResult = await db.query('SELECT * FROM merchants WHERE id = $1', [transaction.merchant_id]); const merchant = merchantResult.rows[0]; const feePercent = merchant?.fee_percent !== undefined && merchant?.fee_percent !== null ? parseFloat(merchant.fee_percent) : settings.fee; // 5. Initialize Engine and Verify Payment first const cryptoEngine = new CryptoEngine(selectedNetwork); // 5.1 Convert Fiat amount to Crypto amount for verification let expectedCryptoAmount = transaction.amount.toString(); try { const coinIdMap: Record = { 'SOL': 'solana', 'USDC': 'usd-coin', 'USDT': 'tether', 'TRX': 'tron', 'BTC': 'bitcoin' }; const coinId = coinIdMap[selectedToken] || 'solana'; const priceUrl = `https://api.coingecko.com/api/v3/simple/price?ids=${coinId}&vs_currencies=usd,try`; const priceRes = await fetch(priceUrl); const priceData = await priceRes.json(); const currencyKey = (transaction.currency || 'TRY').toLowerCase(); const priceInCurrency = priceData[coinId][currencyKey] || priceData[coinId]['usd']; if (priceInCurrency) { // Apply a small tolerance (2%) and cross-convert if needed (simple assumption for demo) const price = currencyKey === 'try' ? priceInCurrency : (priceInCurrency * 32.5); // Fallback conversion const rawExpected = parseFloat(transaction.amount) / priceInCurrency; expectedCryptoAmount = (rawExpected * 0.98).toFixed(6); console.log(`[Sweep] ${transaction.amount} ${transaction.currency} => ~${rawExpected.toFixed(6)} ${selectedToken}`); } } catch (priceErr) { console.warn("[Sweep] Price fetch failed, using order amount as fallback"); } const verification = await cryptoEngine.verifyPayment( depositAddress, expectedCryptoAmount, selectedToken ); if (!verification.success) { return NextResponse.json({ success: false, error: `Ödeme henüz doğrulanmadı. Beklenen: ~${expectedCryptoAmount} ${selectedToken}`, status: 'waiting' }); } // 6. Proceed to Sweep (100% to platform treasury) const sweepResult = await cryptoEngine.sweepFunds( depositPrivateKey, platformAddress, selectedToken ); if (!sweepResult.success) { throw new Error("Süpürme işlemi başarısız oldu."); } // 6.1 Calculate Merchant's credit const grossAmount = parseFloat(transaction.amount); const feeAmount = (grossAmount * feePercent) / 100; const merchantNetCredit = grossAmount - feeAmount; // 6.2 Calculate crypto credit after fee const cryptoAmount = parseFloat(expectedCryptoAmount); const cryptoFee = (cryptoAmount * feePercent) / 100; const cryptoNetCredit = cryptoAmount - cryptoFee; // 6.3 Update Merchant's TRY balance (legacy) await db.query(`UPDATE merchants SET available_balance = available_balance + $1 WHERE id = $2`, [merchantNetCredit, transaction.merchant_id]); // 6.4 Update Merchant's per-network crypto balance await db.query(` INSERT INTO merchant_balances (merchant_id, network, token, balance, total_gross) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (merchant_id, network, token) DO UPDATE SET balance = merchant_balances.balance + $4, total_gross = merchant_balances.total_gross + $5 `, [transaction.merchant_id, selectedNetwork, selectedToken, cryptoNetCredit, cryptoAmount]); // 6.5 Update transaction status and recorded blockchain info await db.query(` UPDATE transactions SET status = 'succeeded', paid_network = $2, paid_token = $3, paid_amount_crypto = $4 WHERE id = $1`, [transaction.id, selectedNetwork, selectedToken, expectedCryptoAmount] ); // 7. Automated Webhook Notification if (transaction.callback_url) { try { fetch(transaction.callback_url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'success', txId: transaction.stripe_pi_id, orderRef: transaction.source_ref_id, hashes: { txHash: sweepResult.txHash } }) }).catch(e => console.error("Webhook fetch failed:", e.message)); } catch (webhookError) {} } return NextResponse.json({ success: true, message: `Ödeme ${selectedNetwork} ağında ${selectedToken} ile doğrulandı.`, hashes: { txHash: sweepResult.txHash }, merchantCredit: merchantNetCredit }); } catch (error: any) { console.error('[API Error]:', error.message); return NextResponse.json({ success: false, error: error.message }, { status: 500 }); } }