rc
This commit is contained in:
216
app/api/referral-points/redeem/route.ts
Normal file
216
app/api/referral-points/redeem/route.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { cookies } from 'next/headers'
|
||||
import pool from '@/lib/db'
|
||||
import { ALLOWED_PAYMENT_CURRENCIES, isAllowedCurrency } from '@/lib/payment-currencies'
|
||||
|
||||
// POST /api/referral-points/redeem - Redeem referral points to crypto
|
||||
export async function POST(request: NextRequest) {
|
||||
const connection = await pool.getConnection()
|
||||
|
||||
try {
|
||||
const cookieStore = await cookies()
|
||||
const buyerIdCookie = cookieStore.get('buyer_id')?.value
|
||||
|
||||
if (!buyerIdCookie) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Authentication required' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const buyer_id = parseInt(buyerIdCookie, 10)
|
||||
|
||||
const body = await request.json()
|
||||
const { points, crypto_currency, wallet_address } = body
|
||||
|
||||
// Validate required fields
|
||||
if (!points || !crypto_currency || !wallet_address) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing required fields: points, crypto_currency, wallet_address' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const pointsToRedeem = parseFloat(points)
|
||||
const normalizedCryptoCurrency = crypto_currency.toLowerCase().trim()
|
||||
const normalizedWalletAddress = wallet_address.trim()
|
||||
|
||||
// Validate points amount
|
||||
if (isNaN(pointsToRedeem) || pointsToRedeem <= 0) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Points must be a positive number' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Validate crypto currency
|
||||
if (!isAllowedCurrency(normalizedCryptoCurrency)) {
|
||||
return NextResponse.json(
|
||||
{ error: `Unsupported cryptocurrency. Allowed: ${ALLOWED_PAYMENT_CURRENCIES.join(', ')}` },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Basic wallet address validation (non-empty, reasonable length)
|
||||
if (normalizedWalletAddress.length < 10 || normalizedWalletAddress.length > 255) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid wallet address format' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
await connection.beginTransaction()
|
||||
|
||||
try {
|
||||
// Get buyer's current points balance
|
||||
const [buyerRows] = await connection.execute(
|
||||
'SELECT referral_points FROM buyers WHERE id = ? FOR UPDATE',
|
||||
[buyer_id]
|
||||
)
|
||||
const buyers = buyerRows as any[]
|
||||
|
||||
if (buyers.length === 0) {
|
||||
await connection.rollback()
|
||||
return NextResponse.json(
|
||||
{ error: 'Buyer not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
const currentPoints = parseFloat(buyers[0].referral_points) || 0
|
||||
|
||||
// Get redemption settings
|
||||
const [settingsRows] = await connection.execute(
|
||||
'SELECT setting_key, setting_value FROM referral_settings'
|
||||
)
|
||||
const settings = settingsRows as any[]
|
||||
|
||||
const pointsToCryptoChf = parseFloat(
|
||||
settings.find(s => s.setting_key === 'points_to_crypto_chf')?.setting_value || '100'
|
||||
)
|
||||
const minRedemptionPoints = parseFloat(
|
||||
settings.find(s => s.setting_key === 'min_redemption_points')?.setting_value || '1000'
|
||||
)
|
||||
|
||||
// Validate minimum redemption amount
|
||||
if (pointsToRedeem < minRedemptionPoints) {
|
||||
await connection.rollback()
|
||||
return NextResponse.json(
|
||||
{ error: `Minimum redemption is ${minRedemptionPoints} points` },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Validate user has enough points
|
||||
if (currentPoints < pointsToRedeem) {
|
||||
await connection.rollback()
|
||||
return NextResponse.json(
|
||||
{ error: 'Insufficient points' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate CHF value of points
|
||||
const chfValue = pointsToRedeem / pointsToCryptoChf
|
||||
|
||||
// TODO: Get current crypto exchange rate
|
||||
// For now, we'll use a placeholder that would need to be replaced with actual exchange rate API
|
||||
// This should fetch the current rate from an exchange API (e.g., CoinGecko, Binance, etc.)
|
||||
const cryptoExchangeRate = await getCryptoExchangeRate(normalizedCryptoCurrency, 'chf')
|
||||
|
||||
if (!cryptoExchangeRate) {
|
||||
await connection.rollback()
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch exchange rate. Please try again later.' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate crypto amount
|
||||
const cryptoAmount = chfValue / cryptoExchangeRate
|
||||
|
||||
// Deduct points from buyer's balance
|
||||
const newBalance = currentPoints - pointsToRedeem
|
||||
await connection.execute(
|
||||
'UPDATE buyers SET referral_points = ? WHERE id = ?',
|
||||
[newBalance, buyer_id]
|
||||
)
|
||||
|
||||
// Create redemption record
|
||||
const [redemptionResult] = await connection.execute(
|
||||
`INSERT INTO point_redemptions
|
||||
(buyer_id, points, crypto_currency, wallet_address, crypto_amount, status)
|
||||
VALUES (?, ?, ?, ?, ?, 'pending')`,
|
||||
[buyer_id, pointsToRedeem, normalizedCryptoCurrency, normalizedWalletAddress, cryptoAmount]
|
||||
)
|
||||
const redemptionId = (redemptionResult as any).insertId
|
||||
|
||||
// Record transaction
|
||||
await connection.execute(
|
||||
`INSERT INTO referral_point_transactions
|
||||
(buyer_id, points, type, description)
|
||||
VALUES (?, ?, 'redeemed', ?)`,
|
||||
[
|
||||
buyer_id,
|
||||
pointsToRedeem,
|
||||
`Points redeemed to ${normalizedCryptoCurrency.toUpperCase()} (Redemption #${redemptionId})`
|
||||
]
|
||||
)
|
||||
|
||||
await connection.commit()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
redemption_id: redemptionId,
|
||||
points_redeemed: pointsToRedeem,
|
||||
crypto_currency: normalizedCryptoCurrency,
|
||||
crypto_amount: cryptoAmount,
|
||||
chf_value: chfValue,
|
||||
new_balance: newBalance,
|
||||
message: 'Redemption request created successfully. Your crypto will be sent within 24-48 hours.'
|
||||
})
|
||||
} catch (error) {
|
||||
await connection.rollback()
|
||||
throw error
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error redeeming points:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to redeem points' },
|
||||
{ status: 500 }
|
||||
)
|
||||
} finally {
|
||||
connection.release()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get crypto exchange rate
|
||||
// TODO: Replace with actual exchange rate API integration
|
||||
async function getCryptoExchangeRate(crypto: string, fiat: string): Promise<number | null> {
|
||||
try {
|
||||
// Placeholder: In production, this should call a real exchange rate API
|
||||
// Examples: CoinGecko, Binance, Coinbase, etc.
|
||||
|
||||
// For now, return a mock rate (this should be replaced)
|
||||
// In production, you would do something like:
|
||||
// const response = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${crypto}&vs_currencies=${fiat}`)
|
||||
// const data = await response.json()
|
||||
// return data[crypto][fiat]
|
||||
|
||||
// Mock rates (CHF per 1 unit of crypto) - REPLACE WITH REAL API
|
||||
const mockRates: Record<string, number> = {
|
||||
'btc': 85000,
|
||||
'eth': 2500,
|
||||
'sol': 100,
|
||||
'xrp': 0.6,
|
||||
'bnbbsc': 300,
|
||||
'usdterc20': 0.9, // Approximate CHF per USDT
|
||||
}
|
||||
|
||||
return mockRates[crypto.toLowerCase()] || null
|
||||
} catch (error) {
|
||||
console.error('Error fetching exchange rate:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user