diff --git a/apps/web/client/src/server/api/routers/subscription/subscription.ts b/apps/web/client/src/server/api/routers/subscription/subscription.ts index f959bc7519..40f68e1abf 100644 --- a/apps/web/client/src/server/api/routers/subscription/subscription.ts +++ b/apps/web/client/src/server/api/routers/subscription/subscription.ts @@ -1,6 +1,6 @@ import { Routes } from '@/utils/constants'; import { legacySubscriptions, prices, subscriptions, toSubscription, users } from '@onlook/db'; -import { createBillingPortalSession, createCheckoutSession, createCustomer, isTierUpgrade, PriceKey, releaseSubscriptionSchedule, SubscriptionStatus, updateSubscription, updateSubscriptionNextPeriod } from '@onlook/stripe'; +import { createBillingPortalSession, createCheckoutSession, createCustomer, isTierUpgrade, PriceKey, releaseSubscriptionSchedule, SubscriptionStatus, updateSubscriptionNextPeriod, upgradeSubscription } from '@onlook/stripe'; import { and, eq, isNull } from 'drizzle-orm'; import { headers } from 'next/headers'; import { z } from 'zod'; @@ -162,7 +162,7 @@ export const subscriptionRouter = createTRPCRouter({ const isUpgrade = isTierUpgrade(currentPrice, newPrice); if (isUpgrade) { // If the new price is higher, we invoice the customer immediately. - await updateSubscription({ + await upgradeSubscription({ subscriptionId: stripeSubscriptionId, subscriptionItemId: stripeSubscriptionItemId, priceId: stripePriceId, diff --git a/apps/web/template/next.config.mjs b/apps/web/template/next.config.mjs index a70c1f2392..7ab81fef41 100644 --- a/apps/web/template/next.config.mjs +++ b/apps/web/template/next.config.mjs @@ -4,5 +4,11 @@ const nextConfig = { devIndicators: { buildActivity: false, }, + eslint: { + ignoreDuringBuilds: true, + }, + typescript: { + ignoreBuildErrors: true, + }, }; export default nextConfig; diff --git a/apps/web/template/next.config.ts b/apps/web/template/next.config.ts deleted file mode 100644 index f557dcc4af..0000000000 --- a/apps/web/template/next.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { NextConfig } from 'next'; - -const nextConfig: NextConfig = { - output: 'standalone', - devIndicators: { - buildActivity: false, - }, - eslint: { - ignoreDuringBuilds: true, - }, - typescript: { - ignoreBuildErrors: true, - }, -}; - -export default nextConfig; diff --git a/packages/stripe/src/functions.ts b/packages/stripe/src/functions.ts index 4fcc6f566d..5d6bf6e3c7 100644 --- a/packages/stripe/src/functions.ts +++ b/packages/stripe/src/functions.ts @@ -109,6 +109,84 @@ export const updateSubscription = async ({ }); }; +export const upgradeSubscription = async ({ + subscriptionId, + subscriptionItemId, + priceId, +}: { + subscriptionId: string; + subscriptionItemId: string; + priceId: string; +}) => { + const stripe = createStripeClient(); + const currentSubscription = await stripe.subscriptions.retrieve(subscriptionId); + if (!currentSubscription) { + throw new Error('Subscription not found'); + } + + const currentItem = currentSubscription.items.data.find((item) => item.id === subscriptionItemId); + if (!currentItem) { + throw new Error('Subscription item not found'); + } + + const currentPrice = currentItem.price.id; + if (currentPrice === priceId) { + throw new Error('New price is the same as the current price'); + } + + const currentPriceAmount = currentItem.price.unit_amount; + if (currentPriceAmount == null) { + throw new Error('Current price amount not found'); + } + + const updatedSubscription = await stripe.subscriptions.update(subscriptionId, { + items: [ + { + id: subscriptionItemId, + price: priceId, + }, + ], + // We don't want to prorate the price difference because it would be based on time remaining in the current period + proration_behavior: 'none', + }); + + const newItem = + updatedSubscription.items.data.find((i) => i.id === subscriptionItemId) + ?? updatedSubscription.items.data[0]; + if (!newItem) { + throw new Error('Subscription item not found on updated subscription'); + } + const newPriceAmount = newItem.price?.unit_amount; + if (newPriceAmount == null) { + throw new Error('New price amount not found'); + } + + const quantity = newItem.quantity ?? 1; + const priceDifferenceAmount = (newPriceAmount - currentPriceAmount) * quantity; + + // Create a one-off invoice item for the price difference if the new price is higher + if (priceDifferenceAmount > 0) { + await stripe.invoiceItems.create({ + customer: updatedSubscription.customer as string, + amount: priceDifferenceAmount, + description: 'Onlook subscription upgrade', + }); + + // Create invoice immediately + const invoice = await stripe.invoices.create({ + customer: updatedSubscription.customer as string, + auto_advance: true, + }); + + if (!invoice.id) { + throw new Error('Invoice not created'); + } + await stripe.invoices.pay(invoice.id); + } + + return updatedSubscription; +}; + export const updateSubscriptionNextPeriod = async ({ subscriptionId, priceId,