feat: allow multiple subscriptions

This commit is contained in:
Henry
2025-09-08 23:20:02 +01:00
parent 38803f6696
commit 7f496adf63
3 changed files with 65 additions and 28 deletions

View File

@@ -3,6 +3,7 @@ import { env } from "next-runtime-env";
import { z } from "zod";
import { createNextApiContext } from "@kan/api/trpc";
import * as subscriptionRepo from "@kan/db/repository/subscription.repo";
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
import { createStripeClient } from "@kan/stripe";
@@ -40,17 +41,22 @@ export default async function handler(
const body = req.body as CheckoutSessionRequest;
const { successUrl, cancelUrl, slug, workspacePublicId } = body;
if (!successUrl || !cancelUrl || !slug || !workspacePublicId) {
if (!successUrl || !cancelUrl || !workspacePublicId) {
return res.status(400).json({ error: "Missing required fields" });
}
const slugResult = workspaceSlugSchema.safeParse(slug);
if (slug) {
const slugResult = workspaceSlugSchema.safeParse(slug);
if (!slugResult.success) {
return new Response(JSON.stringify({ error: "Invalid workspace slug" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
if (!slugResult.success) {
return new Response(
JSON.stringify({ error: "Invalid workspace slug" }),
{
status: 400,
headers: { "Content-Type": "application/json" },
},
);
}
}
const workspace = await workspaceRepo.getAllByUserId(db, user.id);
@@ -66,6 +72,20 @@ export default async function handler(
});
}
const subscription = await subscriptionRepo.create(db, {
plan: "pro",
referenceId: workspacePublicId,
userId: user.id,
stripeCustomerId: user.stripeCustomerId ?? "",
status: "incomplete",
});
const subscriptionId = subscription?.id;
if (!subscriptionId) {
return res.status(500).json({ error: "Error creating subscription" });
}
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [
@@ -76,10 +96,13 @@ export default async function handler(
],
success_url: `${env("NEXT_PUBLIC_BASE_URL")}${successUrl}`,
cancel_url: `${env("NEXT_PUBLIC_BASE_URL")}${cancelUrl}`,
client_reference_id: workspacePublicId,
customer: user.stripeCustomerId ?? undefined,
metadata: {
workspaceSlug: slug,
...(slug && { workspaceSlug: slug }),
workspacePublicId,
userId: user.id,
subscriptionId,
},
});

View File

@@ -1,14 +1,11 @@
import { t } from "@lingui/core/macro";
import { HiBolt, HiCheckBadge } from "react-icons/hi2";
import { authClient } from "@kan/auth/client";
import Button from "~/components/Button";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
export function UpgradeToProConfirmation({
userId,
workspacePublicId,
}: {
userId: string;
@@ -18,25 +15,28 @@ export function UpgradeToProConfirmation({
const { showPopup } = usePopup();
const handleUpgrade = async () => {
const { data, error } = await authClient.subscription.upgrade({
plan: "pro",
referenceId: workspacePublicId,
metadata: {
userId,
workspacePublicId,
...(entityId && { workspaceSlug: entityId }),
},
successUrl: "/settings",
cancelUrl: "/settings",
returnUrl: "/settings",
disableRedirect: true,
});
try {
const response = await fetch("/api/stripe/create_checkout_session", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
...(entityId && { workspaceSlug: entityId }),
workspacePublicId: workspacePublicId,
cancelUrl: "/settings",
successUrl: "/settings",
}),
});
if (data?.url) {
window.location.href = data.url;
}
const { url } = (await response.json()) as { url: string };
if (url) {
window.location.href = url;
}
} catch (error) {
console.error("Error creating checkout session:", error);
if (error) {
showPopup({
header: t`Error upgrading subscription`,
message: t`Please try again later, or contact customer support.`,

View File

@@ -75,3 +75,17 @@ export const getByReferenceId = async (db: dbClient, referenceId: string) => {
where: eq(subscription.referenceId, referenceId),
});
};
export const create = async (
db: dbClient,
data: {
plan: string;
referenceId: string;
userId: string;
stripeCustomerId: string;
status: string;
},
) => {
const [result] = await db.insert(subscription).values(data).returning();
return result;
};