Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { db } from '@/db' import { appSettings, type PricingConfig } from '@/db/schema/app-settings' import { withAuth } from '@/lib/auth/withAuth' import { getStripe, getActivePricing, FAMILY_MONTHLY_PRICE_ID, FAMILY_ANNUAL_PRICE_ID, } from '@/lib/stripe' /** * GET /api/admin/pricing * * Returns the current pricing configuration (from DB or env var fallback). */ export const GET = withAuth( async () => { try { const pricing = await getActivePricing() return NextResponse.json(pricing) } catch (error) { console.error('[admin/pricing] Error fetching pricing:', error) return NextResponse.json({ error: 'Failed to fetch pricing' }, { status: 500 }) } }, { role: 'admin' } ) /** * PATCH /api/admin/pricing * * Update pricing by creating new Stripe prices and archiving old ones. * * Body: { monthly: number, annual: number } (amounts in cents) */ export const PATCH = withAuth( async (request) => { try { const body = await request.json() const { monthly, annual } = body // Validate amounts if (typeof monthly !== 'number' || !Number.isInteger(monthly) || monthly <= 0) { return NextResponse.json( { error: 'monthly must be a positive integer (cents)' }, { status: 400 } ) } if (typeof annual !== 'number' || !Number.isInteger(annual) || annual <= 0) { return NextResponse.json( { error: 'annual must be a positive integer (cents)' }, { status: 400 } ) } const stripe = getStripe() const currentPricing = await getActivePricing() // Look up the product ID from the current monthly price const currentMonthlyPriceId = currentPricing.family.monthly.priceId || FAMILY_MONTHLY_PRICE_ID if (!currentMonthlyPriceId) { return NextResponse.json( { error: 'No existing Stripe price found to determine product ID' }, { status: 500 } ) } const existingPrice = await stripe.prices.retrieve(currentMonthlyPriceId) const productId = typeof existingPrice.product === 'string' ? existingPrice.product : existingPrice.product.id const newPricing: PricingConfig = { family: { monthly: { ...currentPricing.family.monthly }, annual: { ...currentPricing.family.annual }, }, } // Create new monthly price if amount changed if (monthly !== currentPricing.family.monthly.amount) { const newPrice = await stripe.prices.create({ product: productId, currency: 'usd', unit_amount: monthly, recurring: { interval: 'month' }, }) // Archive old price if (currentPricing.family.monthly.priceId) { await stripe.prices.update(currentPricing.family.monthly.priceId, { active: false }) } newPricing.family.monthly = { amount: monthly, priceId: newPrice.id } } // Create new annual price if amount changed if (annual !== currentPricing.family.annual.amount) { const newPrice = await stripe.prices.create({ product: productId, currency: 'usd', unit_amount: annual, recurring: { interval: 'year' }, }) // Archive old price if (currentPricing.family.annual.priceId) { await stripe.prices.update(currentPricing.family.annual.priceId, { active: false }) } newPricing.family.annual = { amount: annual, priceId: newPrice.id } } // Save to DB const pricingJson = JSON.stringify(newPricing) // Ensure default row exists const existing = await db .select() .from(appSettings) .where(eq(appSettings.id, 'default')) .limit(1) if (existing.length === 0) { await db.insert(appSettings).values({ id: 'default', pricing: pricingJson }) } else { await db .update(appSettings) .set({ pricing: pricingJson }) .where(eq(appSettings.id, 'default')) } return NextResponse.json(newPricing) } catch (error) { console.error('[admin/pricing] Error updating pricing:', error) return NextResponse.json({ error: 'Failed to update pricing' }, { status: 500 }) } }, { role: 'admin' } ) |