import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { wrap } from "@typeschema/valibot"; import { createTRPCRouter, protectedProcedure } from "../utils"; import { CreateCheckoutSessionSchema, CreatePortalSessionSchema, CancelSubscriptionSchema, ReactivateSubscriptionSchema, ListInvoicesSchema, } from "../schemas/billing"; import { getOrCreateCustomer, createCheckoutSession, createPortalSession, cancelSubscription, reactivateSubscription, listInvoices, } from "~/server/services/billing.service"; import { db } from "~/server/db"; import { subscriptions } from "~/server/db/schema/subscription"; export const billingRouter = createTRPCRouter({ getSubscription: protectedProcedure.query(async ({ ctx }) => { const sub = await db.query.subscriptions.findFirst({ where: eq(subscriptions.userId, ctx.user.id), }); return sub ?? null; }), createCheckoutSession: protectedProcedure .input(wrap(CreateCheckoutSessionSchema)) .mutation(async ({ ctx, input }) => { const allowedPrices = [ process.env.STRIPE_PRICE_BASIC, process.env.STRIPE_PRICE_PLUS, process.env.STRIPE_PRICE_PREMIUM, ].filter(Boolean); if (!allowedPrices.includes(input.priceId)) { throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid price ID", }); } return createCheckoutSession( ctx.user.id, ctx.user.email, input.priceId, input.successUrl, input.cancelUrl, ); }), createPortalSession: protectedProcedure .input(wrap(CreatePortalSessionSchema)) .mutation(async ({ ctx, input }) => { const user = ctx.user; const stripeCustomerId = user.stripeCustomerId; if (!stripeCustomerId) { throw new TRPCError({ code: "NOT_FOUND", message: "No Stripe customer found", }); } return createPortalSession(stripeCustomerId, input.returnUrl); }), cancelSubscription: protectedProcedure .input(wrap(CancelSubscriptionSchema)) .mutation(async ({ input }) => { return cancelSubscription(input.subscriptionId); }), reactivateSubscription: protectedProcedure .input(wrap(ReactivateSubscriptionSchema)) .mutation(async ({ input }) => { return reactivateSubscription(input.subscriptionId); }), listInvoices: protectedProcedure .input(wrap(ListInvoicesSchema)) .query(async ({ ctx, input }) => { const user = ctx.user; const stripeCustomerId = user.stripeCustomerId; if (!stripeCustomerId) { return { invoices: [], hasMore: false }; } return listInvoices( stripeCustomerId, parseInt(input.limit ?? "10", 10), input.startingAfter, ); }), });