218 lines
7.1 KiB
TypeScript
218 lines
7.1 KiB
TypeScript
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 - only USDT (SOL) is allowed for redemption
|
|
if (normalizedCryptoCurrency !== 'usdtsol') {
|
|
return NextResponse.json(
|
|
{ error: 'Only USDT (SOL) is supported for point redemption' },
|
|
{ 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
|
|
'usdtsol': 0.9, // Approximate CHF per USDT on Solana
|
|
}
|
|
|
|
return mockRates[crypto.toLowerCase()] || null
|
|
} catch (error) {
|
|
console.error('Error fetching exchange rate:', error)
|
|
return null
|
|
}
|
|
}
|
|
|