rc 1.0
This commit is contained in:
@@ -1,13 +1,37 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import pool from '@/lib/db'
|
||||
|
||||
// GET /api/buyers - Get all buyers
|
||||
// GET /api/buyers - Get all buyers with referral counts
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const [rows] = await pool.execute(
|
||||
'SELECT id, username, email, created_at FROM buyers ORDER BY created_at DESC'
|
||||
`SELECT
|
||||
b.id,
|
||||
b.username,
|
||||
b.email,
|
||||
b.created_at,
|
||||
COALESCE(ref_counts.referral_count, 0) as referral_count
|
||||
FROM buyers b
|
||||
LEFT JOIN (
|
||||
SELECT referrer, COUNT(*) as referral_count
|
||||
FROM referrals
|
||||
GROUP BY referrer
|
||||
) ref_counts ON b.id = ref_counts.referrer
|
||||
ORDER BY b.created_at DESC`
|
||||
)
|
||||
return NextResponse.json(rows)
|
||||
|
||||
// Add access status for each buyer
|
||||
const buyersWithAccess = (rows as any[]).map((buyer: any) => {
|
||||
const referralCount = parseInt(buyer.referral_count) || 0
|
||||
return {
|
||||
...buyer,
|
||||
referral_count: referralCount,
|
||||
hasWholesaleAccess: referralCount >= 3,
|
||||
hasInnerCircleAccess: referralCount >= 10
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json(buyersWithAccess)
|
||||
} catch (error) {
|
||||
console.error('Error fetching buyers:', error)
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -3,6 +3,8 @@ import { cookies } from 'next/headers'
|
||||
import pool from '@/lib/db'
|
||||
import { getNowPaymentsConfig } from '@/lib/nowpayments'
|
||||
import { ALLOWED_PAYMENT_CURRENCIES, isAllowedCurrency } from '@/lib/payment-currencies'
|
||||
import { getCountryFromIp, calculateShippingFee } from '@/lib/geolocation'
|
||||
import { getCurrencyForCountry, convertPriceForCountry } from '@/lib/currency'
|
||||
|
||||
// POST /api/payments/create-invoice - Create a NOWPayments payment
|
||||
// Note: Endpoint name kept as "create-invoice" for backward compatibility
|
||||
@@ -129,23 +131,38 @@ export async function POST(request: NextRequest) {
|
||||
)
|
||||
}
|
||||
|
||||
// Check if user has unlocked wholesale prices
|
||||
const [referralRows] = await pool.execute(
|
||||
'SELECT COUNT(*) as count FROM referrals WHERE referrer = ?',
|
||||
[buyer_id]
|
||||
)
|
||||
const referralCount = (referralRows as any[])[0]?.count || 0
|
||||
const isWholesaleUnlocked = referralCount >= 3
|
||||
// Check if user has unlocked wholesale prices (use transaction connection to avoid connection leak)
|
||||
const [referralRows] = await connection.execute(
|
||||
'SELECT COUNT(*) as count FROM referrals WHERE referrer = ?',
|
||||
[buyer_id]
|
||||
)
|
||||
const referralCount = (referralRows as any[])[0]?.count || 0
|
||||
const isWholesaleUnlocked = referralCount >= 3
|
||||
|
||||
// Calculate price
|
||||
// ppu is stored as integer where 1000 = $1.00, so divide by 1000 to get actual price
|
||||
// Get country from IP to determine currency
|
||||
const countryCode = await getCountryFromIp(request)
|
||||
const currency = getCurrencyForCountry(countryCode)
|
||||
|
||||
// Calculate price in EUR (database stores prices in EUR)
|
||||
// ppu is stored as integer where 1000 = 1.00 EUR, so divide by 1000 to get actual price
|
||||
// Assuming ppu is per gram
|
||||
const pricePerGram = drop.ppu / 1000
|
||||
const priceToUse = isWholesaleUnlocked ? pricePerGram * 0.76 : pricePerGram
|
||||
const priceAmount = size * priceToUse
|
||||
const pricePerGramEur = drop.ppu / 1000
|
||||
const priceToUseEur = isWholesaleUnlocked ? pricePerGramEur * 0.76 : pricePerGramEur
|
||||
const priceAmountEur = size * priceToUseEur
|
||||
|
||||
// Convert price to user's currency (CHF for Swiss, EUR for others)
|
||||
const priceAmount = convertPriceForCountry(priceAmountEur, countryCode)
|
||||
|
||||
// Calculate shipping fee (already in correct currency: CHF for CH, EUR for others)
|
||||
const shippingFee = calculateShippingFee(countryCode)
|
||||
|
||||
// Add shipping fee to total price
|
||||
const totalPriceAmount = priceAmount + shippingFee
|
||||
|
||||
// Round to 2 decimal places
|
||||
const roundedPriceAmount = Math.round(priceAmount * 100) / 100
|
||||
const roundedPriceAmount = Math.round(totalPriceAmount * 100) / 100
|
||||
const roundedShippingFee = Math.round(shippingFee * 100) / 100
|
||||
const roundedSubtotal = Math.round(priceAmount * 100) / 100
|
||||
|
||||
// Generate order ID
|
||||
const orderId = `SALE-${Date.now()}-${drop_id}-${buyer_id}`
|
||||
@@ -158,6 +175,10 @@ export async function POST(request: NextRequest) {
|
||||
// Get NOWPayments config (testnet or production)
|
||||
const nowPaymentsConfig = getNowPaymentsConfig()
|
||||
|
||||
// Use currency based on user location (CHF for Swiss, EUR for others)
|
||||
// Override the default currency from config with user's currency
|
||||
const priceCurrency = currency.toLowerCase()
|
||||
|
||||
// Calculate expiration time (10 minutes from now)
|
||||
const expiresAt = new Date()
|
||||
expiresAt.setMinutes(expiresAt.getMinutes() + 10)
|
||||
@@ -180,7 +201,7 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
price_amount: roundedPriceAmount,
|
||||
price_currency: nowPaymentsConfig.currency,
|
||||
price_currency: priceCurrency, // CHF for Swiss users, EUR for others
|
||||
pay_currency: payCurrency, // Required: crypto currency (btc, eth, etc)
|
||||
order_id: orderId,
|
||||
order_description: `${drop.item} - ${size}g`,
|
||||
@@ -206,7 +227,7 @@ export async function POST(request: NextRequest) {
|
||||
// payment.payment_id is the NOWPayments payment ID
|
||||
await connection.execute(
|
||||
'INSERT INTO pending_orders (payment_id, order_id, drop_id, buyer_id, buyer_data_id, size, price_amount, price_currency, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[payment.payment_id, orderId, drop_id, buyer_id, buyer_data_id, size, roundedPriceAmount, nowPaymentsConfig.currency, expiresAt]
|
||||
[payment.payment_id, orderId, drop_id, buyer_id, buyer_data_id, size, roundedPriceAmount, priceCurrency, expiresAt]
|
||||
)
|
||||
|
||||
// Commit transaction - inventory is now reserved
|
||||
@@ -220,8 +241,10 @@ export async function POST(request: NextRequest) {
|
||||
pay_address: payment.pay_address, // Address where customer sends payment
|
||||
pay_amount: payment.pay_amount, // Amount in crypto to pay
|
||||
pay_currency: payment.pay_currency, // Crypto currency
|
||||
price_amount: payment.price_amount, // Price in fiat
|
||||
price_currency: payment.price_currency, // Fiat currency
|
||||
price_amount: payment.price_amount, // Total price in fiat (includes shipping)
|
||||
price_currency: payment.price_currency, // Fiat currency (CHF or EUR)
|
||||
shipping_fee: roundedShippingFee, // Shipping fee in user's currency
|
||||
subtotal: roundedSubtotal, // Product price without shipping in user's currency
|
||||
order_id: orderId,
|
||||
payin_extra_id: payment.payin_extra_id, // Memo/tag for certain currencies (XRP, XLM, etc)
|
||||
expiration_estimate_date: payment.expiration_estimate_date, // When payment expires
|
||||
|
||||
@@ -14,7 +14,17 @@ export async function GET(request: NextRequest) {
|
||||
referralCount: 0,
|
||||
isUnlocked: false,
|
||||
referralsNeeded: 3,
|
||||
referralsRemaining: 3
|
||||
referralsRemaining: 3,
|
||||
wholesaleTier: {
|
||||
referralsNeeded: 3,
|
||||
referralsRemaining: 3,
|
||||
isUnlocked: false
|
||||
},
|
||||
innerCircleTier: {
|
||||
referralsNeeded: 10,
|
||||
referralsRemaining: 10,
|
||||
isUnlocked: false
|
||||
}
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
@@ -29,15 +39,29 @@ export async function GET(request: NextRequest) {
|
||||
)
|
||||
|
||||
const referralCount = (referralRows as any[])[0]?.count || 0
|
||||
const isUnlocked = referralCount >= 3
|
||||
const referralsNeeded = 3
|
||||
const referralsRemaining = Math.max(0, referralsNeeded - referralCount)
|
||||
const isWholesaleUnlocked = referralCount >= 3
|
||||
const isInnerCircleUnlocked = referralCount >= 10
|
||||
|
||||
// Determine which tier to show
|
||||
const wholesaleTier = {
|
||||
referralsNeeded: 3,
|
||||
referralsRemaining: Math.max(0, 3 - referralCount),
|
||||
isUnlocked: isWholesaleUnlocked
|
||||
}
|
||||
|
||||
const innerCircleTier = {
|
||||
referralsNeeded: 10,
|
||||
referralsRemaining: Math.max(0, 10 - referralCount),
|
||||
isUnlocked: isInnerCircleUnlocked
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
referralCount,
|
||||
isUnlocked,
|
||||
referralsNeeded,
|
||||
referralsRemaining,
|
||||
isUnlocked: isWholesaleUnlocked, // Keep for backward compatibility
|
||||
referralsNeeded: isWholesaleUnlocked ? innerCircleTier.referralsNeeded : wholesaleTier.referralsNeeded,
|
||||
referralsRemaining: isWholesaleUnlocked ? innerCircleTier.referralsRemaining : wholesaleTier.referralsRemaining,
|
||||
wholesaleTier,
|
||||
innerCircleTier,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching referral status:', error)
|
||||
|
||||
27
app/api/shipping-fee/route.ts
Normal file
27
app/api/shipping-fee/route.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getCountryFromIp, calculateShippingFee } from '@/lib/geolocation'
|
||||
import { getCurrencyForCountry } from '@/lib/currency'
|
||||
|
||||
// GET /api/shipping-fee - Get shipping fee based on user's IP location
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const countryCode = await getCountryFromIp(request)
|
||||
const shippingFee = calculateShippingFee(countryCode)
|
||||
const currency = getCurrencyForCountry(countryCode)
|
||||
|
||||
return NextResponse.json({
|
||||
shipping_fee: shippingFee,
|
||||
country_code: countryCode,
|
||||
currency: currency,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error calculating shipping fee:', error)
|
||||
// Default to 40 EUR if detection fails
|
||||
return NextResponse.json({
|
||||
shipping_fee: 40,
|
||||
country_code: null,
|
||||
currency: 'EUR',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user