feat: implement platform treasury model with merchant balance tracking and API docs
This commit is contained in:
@@ -163,6 +163,22 @@ export default function MerchantsPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Balance Section */}
|
||||
<div className="mb-6 p-4 bg-emerald-50/50 rounded-2xl border border-emerald-100 flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-[10px] font-black text-emerald-600 uppercase tracking-widest pl-1">İçerideki Bakiye</p>
|
||||
<p className="text-xl font-black text-emerald-700">
|
||||
{new Intl.NumberFormat('tr-TR', { style: 'currency', currency: 'TRY' }).format(m.available_balance || 0)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Toplam Ödenen</p>
|
||||
<p className="text-xs font-bold text-gray-600">
|
||||
{new Intl.NumberFormat('tr-TR', { style: 'currency', currency: 'TRY' }).format(m.withdrawn_balance || 0)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* API Key Section */}
|
||||
<div className="p-4 bg-gray-50 rounded-2xl space-y-2 border border-transparent hover:border-gray-100 transition-colors">
|
||||
|
||||
@@ -95,27 +95,37 @@ export async function POST(request: Request) {
|
||||
});
|
||||
}
|
||||
|
||||
// 6. Proceed to Sweep
|
||||
// 6. Proceed to Sweep (100% to platform treasury)
|
||||
const sweepResult = await cryptoEngine.sweepFunds(
|
||||
tempWalletConfig.privateKey,
|
||||
merchantAddress,
|
||||
platformAddress,
|
||||
selectedToken,
|
||||
feePercent
|
||||
selectedToken
|
||||
);
|
||||
|
||||
if (!sweepResult.success) {
|
||||
throw new Error("Süpürme işlemi başarısız oldu.");
|
||||
}
|
||||
|
||||
// 6. Update transaction status
|
||||
// 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 {
|
||||
// In production, sign this payload and use a more robust delivery system
|
||||
fetch(transaction.callback_url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -123,7 +133,7 @@ export async function POST(request: Request) {
|
||||
status: 'success',
|
||||
txId: transaction.stripe_pi_id,
|
||||
orderRef: transaction.source_ref_id,
|
||||
hashes: sweepResult
|
||||
hashes: { txHash: sweepResult.txHash }
|
||||
})
|
||||
}).catch(e => console.error("Webhook fetch failed:", e.message));
|
||||
} catch (webhookError: any) {
|
||||
@@ -133,11 +143,11 @@ export async function POST(request: Request) {
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `Ödeme ${selectedNetwork} ağında ${selectedToken} ile başarıyla doğrulandı ve süpürüldü.`,
|
||||
message: `Ödeme ${selectedNetwork} ağında ${selectedToken} ile başarıyla doğrulandı, hazineye aktarıldı ve merchant bakiyesi güncellendi.`,
|
||||
hashes: {
|
||||
platform: sweepResult.platformTx,
|
||||
merchant: sweepResult.merchantTx
|
||||
}
|
||||
txHash: sweepResult.txHash
|
||||
},
|
||||
merchantCredit: merchantNetCredit
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -51,10 +51,14 @@ export async function POST(req: NextRequest) {
|
||||
// Generate Temporary Wallets for Crypto fallback
|
||||
const evmWallet = await CryptoEngine.createTemporaryWallet('POLYGON');
|
||||
const solWallet = await CryptoEngine.createTemporaryWallet('SOLANA');
|
||||
const tronWallet = await CryptoEngine.createTemporaryWallet('TRON');
|
||||
const btcWallet = await CryptoEngine.createTemporaryWallet('BITCOIN');
|
||||
|
||||
const cryptoWallets = {
|
||||
EVM: { address: evmWallet.address, privateKey: evmWallet.privateKey },
|
||||
SOLANA: { address: solWallet.address, privateKey: solWallet.privateKey }
|
||||
SOLANA: { address: solWallet.address, privateKey: solWallet.privateKey },
|
||||
TRON: { address: tronWallet.address, privateKey: tronWallet.privateKey },
|
||||
BITCOIN: { address: btcWallet.address, privateKey: btcWallet.privateKey }
|
||||
};
|
||||
|
||||
if (useMock) {
|
||||
@@ -114,7 +118,9 @@ export async function POST(req: NextRequest) {
|
||||
status: 'pending',
|
||||
wallets: {
|
||||
EVM: evmWallet.address,
|
||||
SOLANA: solWallet.address
|
||||
SOLANA: solWallet.address,
|
||||
TRON: tronWallet.address,
|
||||
BITCOIN: btcWallet.address
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user