import type Stripe from 'stripe'; import { prisma } from '@/lib/prisma'; import { priceIdToTier } from '@/lib/billing/stripe-prices'; import { SubscriptionStatus, SubscriptionTier } from '@prisma/client'; function mapStripeStatus(stripeStatus: Stripe.Subscription.Status): SubscriptionStatus { switch (stripeStatus) { case 'active': return SubscriptionStatus.ACTIVE; case 'trialing': return SubscriptionStatus.TRIALING; case 'past_due': return SubscriptionStatus.PAST_DUE; case 'canceled': case 'unpaid': return SubscriptionStatus.CANCELED; case 'incomplete': case 'incomplete_expired': return SubscriptionStatus.INACTIVE; default: return SubscriptionStatus.INACTIVE; } } export async function syncSubscriptionFromStripe( subscription: Stripe.Subscription, userId: string, ): Promise { const priceId = subscription.items.data[0]?.price?.id ?? null; const resolvedTier = priceId ? await priceIdToTier(priceId) : null; const tierFromMetadata = (subscription.metadata?.tier as string | undefined) ?? (subscription.metadata?.tier as string | undefined); let tier: SubscriptionTier; if (resolvedTier) { tier = resolvedTier as SubscriptionTier; } else if (tierFromMetadata === 'PRO' || tierFromMetadata === 'BUSINESS' || tierFromMetadata === 'ENTERPRISE') { tier = tierFromMetadata as SubscriptionTier; } else { tier = SubscriptionTier.BASIC; } const status = mapStripeStatus(subscription.status); const currentPeriodStartTimestamp = (subscription as any).current_period_start ?? (subscription as any).items?.data?.[0]?.current_period_start ?? (subscription as any).start_date ?? Math.floor(Date.now() / 1000); const currentPeriodEndTimestamp = (subscription as any).current_period_end ?? (subscription as any).items?.data?.[0]?.current_period_end ?? (currentPeriodStartTimestamp + 30 * 24 * 3600); const currentPeriodStart = new Date(currentPeriodStartTimestamp * 1000); const currentPeriodEnd = new Date(currentPeriodEndTimestamp * 1000); await prisma.subscription.upsert({ where: { stripeSubscriptionId: subscription.id }, update: { tier, status, stripeCustomerId: typeof subscription.customer === 'string' ? subscription.customer : subscription.customer.id, stripePriceId: priceId, currentPeriodStart, currentPeriodEnd, cancelAtPeriodEnd: subscription.cancel_at_period_end, canceledAt: subscription.canceled_at ? new Date(subscription.canceled_at * 1000) : null, trialEndsAt: subscription.trial_end ? new Date(subscription.trial_end * 1000) : null, updatedAt: new Date(), }, create: { userId, tier, status, stripeCustomerId: typeof subscription.customer === 'string' ? subscription.customer : subscription.customer.id, stripeSubscriptionId: subscription.id, stripePriceId: priceId, currentPeriodStart, currentPeriodEnd, cancelAtPeriodEnd: subscription.cancel_at_period_end, canceledAt: subscription.canceled_at ? new Date(subscription.canceled_at * 1000) : null, trialEndsAt: subscription.trial_end ? new Date(subscription.trial_end * 1000) : null, }, }); } export async function handleSubscriptionDeleted(subscription: Stripe.Subscription): Promise { const existing = await prisma.subscription.findUnique({ where: { stripeSubscriptionId: subscription.id }, }); if (!existing) return; await prisma.subscription.update({ where: { stripeSubscriptionId: subscription.id }, data: { status: SubscriptionStatus.CANCELED, cancelAtPeriodEnd: false, canceledAt: subscription.canceled_at ? new Date(subscription.canceled_at * 1000) : new Date(), updatedAt: new Date(), }, }); } export async function resolveUserIdFromStripeEvent( subscription: Stripe.Subscription, ): Promise { const metaUserId = subscription.metadata?.userId as string | undefined; if (metaUserId) return metaUserId; const customerId = typeof subscription.customer === 'string' ? subscription.customer : subscription.customer.id; const existing = await prisma.subscription.findFirst({ where: { stripeCustomerId: customerId }, select: { userId: true }, }); return existing?.userId ?? null; }