get to prod tasks

This commit is contained in:
2026-05-26 16:06:34 -04:00
parent 04e839640f
commit 5214412fff
105 changed files with 7447 additions and 38 deletions

View File

@@ -8,6 +8,8 @@ import {
CancelSubscriptionSchema,
ReactivateSubscriptionSchema,
ListInvoicesSchema,
RequestFeatureTrialSchema,
UpgradeFromTrialSchema,
} from "../schemas/billing";
import {
getOrCreateCustomer,
@@ -16,18 +18,86 @@ import {
cancelSubscription,
reactivateSubscription,
listInvoices,
mapStripeProductToTier,
} from "~/server/services/billing.service";
import { db } from "~/server/db";
import { subscriptions } from "~/server/db/schema/subscription";
import { stripe } from "~/server/stripe";
import {
getEffectiveTier,
getActiveTrials,
createFeatureTrial,
} from "~/server/lib/tier";
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;
if (!sub) return null;
const trials = await getActiveTrials(ctx.user.id);
return {
...sub,
effectiveTier: getEffectiveTier(sub.tier as "basic" | "plus" | "premium", sub.status as "active" | "trialing"),
isTrialing: sub.status === "trialing",
trials,
};
}),
requestFeatureTrial: protectedProcedure
.input(wrap(RequestFeatureTrialSchema))
.mutation(async ({ ctx, input }) => {
const sub = await db.query.subscriptions.findFirst({
where: eq(subscriptions.userId, ctx.user.id),
});
if (!sub || sub.status !== "active" || sub.tier !== "basic") {
throw new TRPCError({
code: "FORBIDDEN",
message: "Feature trials are available for active Basic subscribers",
});
}
const trial = await createFeatureTrial(ctx.user.id, input.feature, 7);
return { trial };
}),
upgradeFromTrial: protectedProcedure
.input(wrap(UpgradeFromTrialSchema))
.mutation(async ({ ctx, input }) => {
const sub = await db.query.subscriptions.findFirst({
where: eq(subscriptions.userId, ctx.user.id),
});
if (!sub || sub.status !== "trialing") {
throw new TRPCError({
code: "FORBIDDEN",
message: "No active trial to upgrade from",
});
}
const priceMap: Record<string, string | undefined> = {
basic: process.env.STRIPE_PRICE_BASIC,
plus: process.env.STRIPE_PRICE_PLUS,
premium: process.env.STRIPE_PRICE_PREMIUM,
};
const priceId = priceMap[input.plan];
if (!priceId) {
throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid plan" });
}
// Cancel current trial subscription
await stripe.subscriptions.cancel(sub.stripeId!);
return createCheckoutSession(
ctx.user.id,
ctx.user.email,
priceId,
input.returnUrl,
);
}),
createCheckoutSession: protectedProcedure
.input(wrap(CreateCheckoutSessionSchema))
.mutation(async ({ ctx, input }) => {

View File

@@ -21,3 +21,12 @@ export const ListInvoicesSchema = object({
limit: optional(string(), "10"),
startingAfter: optional(string()),
});
export const RequestFeatureTrialSchema = object({
feature: picklist(["voiceprint", "hometitle", "removebrokers"]),
});
export const UpgradeFromTrialSchema = object({
plan: picklist(["basic", "plus", "premium"]),
returnUrl: string([url()]),
});