All files / web/src/app/api/admin/pricing route.ts

0% Statements 0/141
0% Branches 0/1
0% Functions 0/1
0% Lines 0/141

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' }
)