Files
Pay2Gateway/app/api/crypto-sweep/route.ts

158 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 to get the temporary wallet private key
const result = await db.query('SELECT * FROM transactions WHERE 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 (EVM or SOLANA)
const walletType = selectedNetwork === 'SOLANA' ? 'SOLANA' : 'EVM';
const tempWalletConfig = wallets[walletType];
if (!tempWalletConfig || !tempWalletConfig.privateKey) {
return NextResponse.json({ success: false, error: `No temporary wallet found for ${walletType}` }, { status: 500 });
}
// 3. Define Platform Address & Fee (Fetch from dynamic settings)
const settings = await (async () => {
const result = await db.query('SELECT key, value FROM system_settings WHERE key IN (\'sol_platform_address\', \'evm_platform_address\', \'default_fee_percent\')');
const map: Record<string, string> = {};
result.rows.forEach(r => map[r.key] = r.value);
return {
sol: map.sol_platform_address || process.env.SOL_PLATFORM_ADDRESS || "5pLH1tqZhx8p8WpZ18yr28N42KXB3FXVPzZ9ceCtpBVe",
evm: map.evm_platform_address || process.env.EVM_PLATFORM_ADDRESS || "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
fee: parseFloat(map.default_fee_percent || '1.0')
};
})();
const platformAddress = selectedNetwork === 'SOLANA' ? settings.sol : settings.evm;
// 4. Define Merchant Address (Fetch from transaction's merchant)
const merchantResult = await db.query('SELECT * FROM merchants WHERE id = $1', [transaction.merchant_id]);
const merchant = merchantResult.rows[0];
// Prioritize merchant's specific fee, fallback to global setting
const feePercent = merchant?.fee_percent !== undefined && merchant?.fee_percent !== null
? parseFloat(merchant.fee_percent)
: settings.fee;
let merchantAddress = merchant?.wallet_address || platformAddress;
// Use merchant's specific vault if available
if (selectedNetwork === 'SOLANA') {
if (merchant?.sol_vault_address) merchantAddress = merchant.sol_vault_address;
} else {
if (merchant?.evm_vault_address) merchantAddress = merchant.evm_vault_address;
}
console.log(`[Sweep] Destination for merchant: ${merchantAddress}`);
// 5. Initialize Engine and Verify Payment first
const cryptoEngine = new CryptoEngine(selectedNetwork);
const verification = await cryptoEngine.verifyPayment(
tempWalletConfig.address,
transaction.amount.toString(),
selectedToken
);
if (!verification.success) {
return NextResponse.json({
success: false,
error: "Henüz ödeme algılanmadı (On-chain bakiye yetersiz)",
status: 'waiting'
});
}
// 6. Proceed to Sweep (100% to platform treasury)
const sweepResult = await cryptoEngine.sweepFunds(
tempWalletConfig.privateKey,
platformAddress,
selectedToken
);
if (!sweepResult.success) {
throw new Error("Süpürme işlemi başarısız oldu.");
}
// 6.1 Calculate Merchant's credit (Amount - Platform Fee)
// Note: transaction.amount is the gross amount paid by customer
const grossAmount = parseFloat(transaction.amount);
const feeAmount = (grossAmount * feePercent) / 100;
const merchantNetCredit = grossAmount - feeAmount;
// 6.2 Update Merchant's virtual balance in DB
await db.query(`
UPDATE merchants
SET available_balance = available_balance + $1
WHERE id = $2
`, [merchantNetCredit, transaction.merchant_id]);
// 6.3 Update transaction status
await db.query(`UPDATE transactions SET status = 'succeeded' WHERE id = $1`, [transaction.id]);
// 7. Automated Webhook Notification
if (transaction.callback_url) {
console.log(`[Webhook] Notifying merchant at ${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: any) {
console.error("[Webhook Error]:", webhookError.message);
}
}
return NextResponse.json({
success: true,
message: `Ödeme ${selectedNetwork}ında ${selectedToken} ile başarıyla doğrulandı, hazineye aktarıldı ve merchant bakiyesi güncellendi.`,
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 });
}
}