feat: pricing plan + usage page
This commit is contained in:
1 parent
14c3ca6bf5
commit
0f9e0c9c76
39 files changed
+7847
-402
No files matched your search
@@ -0,0 +1,175 @@
|
||||
import { auth, clerkClient } from "@clerk/nextjs";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { stripe } from "../../../../../server/stripe";
|
||||
import { NextResponse } from "next/server";
|
||||
import { db } from "@/db/db";
|
||||
import { subscriptionStatusTable } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const secret = process.env.STRIPE_WEBHOOK_SECRET || "";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.text();
|
||||
|
||||
const signature = headers().get("stripe-signature");
|
||||
|
||||
if (!signature)
|
||||
return new NextResponse("Signature not found.", {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const event = stripe.webhooks.constructEvent(body, signature, secret);
|
||||
|
||||
console.log(event);
|
||||
|
||||
if (event.type === "checkout.session.completed") {
|
||||
if (!event.data.object.customer_details?.email) {
|
||||
throw new Error(`missing user email, ${event.id}`);
|
||||
}
|
||||
|
||||
const userId = event.data.object.metadata?.userId;
|
||||
const plan = event.data.object.metadata?.plan;
|
||||
if (!userId) {
|
||||
throw new Error(`missing itinerary_id on metadata, ${event.id}`);
|
||||
}
|
||||
if (!plan) {
|
||||
throw new Error(`missing plan on metadata, ${event.id}`);
|
||||
}
|
||||
|
||||
const orgId = event.data.object.metadata?.orgId;
|
||||
|
||||
const customerId =
|
||||
typeof event.data.object.customer == "string"
|
||||
? event.data.object.customer
|
||||
: event.data.object.customer?.id;
|
||||
const subscriptionId =
|
||||
typeof event.data.object.subscription == "string"
|
||||
? event.data.object.subscription
|
||||
: event.data.object.subscription?.id;
|
||||
|
||||
if (!customerId) {
|
||||
throw new Error(`missing customerId, ${event.id}`);
|
||||
}
|
||||
if (!subscriptionId) {
|
||||
throw new Error(`missing subscriptionId, ${event.id}`);
|
||||
}
|
||||
|
||||
const items = await stripe.subscriptionItems.list({
|
||||
subscription: subscriptionId,
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
// getting the subscription item id for the api plan
|
||||
const subscription_item_api_id = items.data.find(
|
||||
(x) => x.price.id === process.env.STRIPE_PR_API,
|
||||
)?.id;
|
||||
|
||||
// the plan could be either pro or enterprise
|
||||
const subscription_item_plan_id =
|
||||
items.data.find((x) => x.price.id === process.env.STRIPE_PR_PRO)?.id ??
|
||||
items.data.find((x) => x.price.id === process.env.STRIPE_PR_ENTERPRISE)
|
||||
?.id;
|
||||
|
||||
if (!subscription_item_api_id) {
|
||||
throw new Error(
|
||||
`missing plan on subscription_item_api_id, ${event.id}`,
|
||||
);
|
||||
}
|
||||
if (!subscription_item_plan_id) {
|
||||
throw new Error(
|
||||
`missing plan on subscription_item_plan_id, ${event.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(items);
|
||||
|
||||
await db
|
||||
.insert(subscriptionStatusTable)
|
||||
.values({
|
||||
stripe_customer_id: customerId,
|
||||
org_id: orgId,
|
||||
user_id: userId,
|
||||
subscription_id: subscriptionId,
|
||||
plan: plan as "pro" | "enterprise" | "basic",
|
||||
status: "active",
|
||||
subscription_item_api_id: subscription_item_api_id,
|
||||
subscription_item_plan_id: subscription_item_plan_id,
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
|
||||
// updateDatabase(event.data.object.metadata.itinerary_id);
|
||||
// sendEmail(event.data.object.customer_details.email);
|
||||
} else if (event.type === "customer.subscription.paused") {
|
||||
const customerId =
|
||||
typeof event.data.object.customer == "string"
|
||||
? event.data.object.customer
|
||||
: event.data.object.customer?.id;
|
||||
|
||||
if (!customerId) {
|
||||
throw new Error(`missing customerId, ${event.id}`);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(subscriptionStatusTable)
|
||||
.set({ status: "paused" })
|
||||
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
|
||||
} else if (event.type === "customer.subscription.resumed") {
|
||||
const customerId =
|
||||
typeof event.data.object.customer == "string"
|
||||
? event.data.object.customer
|
||||
: event.data.object.customer?.id;
|
||||
|
||||
if (!customerId) {
|
||||
throw new Error(`missing customerId, ${event.id}`);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(subscriptionStatusTable)
|
||||
.set({ status: "active" })
|
||||
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
|
||||
} else if (event.type === "customer.subscription.deleted") {
|
||||
const customerId =
|
||||
typeof event.data.object.customer == "string"
|
||||
? event.data.object.customer
|
||||
: event.data.object.customer?.id;
|
||||
|
||||
if (!customerId) {
|
||||
throw new Error(`missing customerId, ${event.id}`);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(subscriptionStatusTable)
|
||||
.set({ status: "deleted" })
|
||||
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
|
||||
} else if (event.type === "customer.subscription.updated") {
|
||||
const customerId =
|
||||
typeof event.data.object.customer == "string"
|
||||
? event.data.object.customer
|
||||
: event.data.object.customer?.id;
|
||||
|
||||
if (!customerId) {
|
||||
throw new Error(`missing customerId, ${event.id}`);
|
||||
}
|
||||
|
||||
const cancel_at_period_end = event.data.object.cancel_at_period_end;
|
||||
|
||||
await db
|
||||
.update(subscriptionStatusTable)
|
||||
.set({ cancel_at_period_end: cancel_at_period_end })
|
||||
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
|
||||
}
|
||||
|
||||
return NextResponse.json({ result: event, ok: true });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: `Something went wrong: ${error}`,
|
||||
ok: false,
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { stripe } from "@/server/stripe";
|
||||
import { createCheckout } from "@/server/linkToPricing";
|
||||
import { auth, clerkClient } from "@clerk/nextjs";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getUrlServerSide } from "@/server/getUrlServerSide";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const { userId, orgId } = auth();
|
||||
|
||||
const plan = new URL(req.url).searchParams.get("plan");
|
||||
|
||||
if (!userId) return redirect("/");
|
||||
if (!plan) return redirect("/pricing");
|
||||
|
||||
const user = await clerkClient.users.getUser(userId);
|
||||
|
||||
const mapping = {
|
||||
pro: process.env.STRIPE_PR_PRO,
|
||||
enterprise: process.env.STRIPE_PR_ENTERPRISE,
|
||||
};
|
||||
|
||||
const api = process.env.STRIPE_PR_API;
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
success_url: getUrlServerSide(),
|
||||
line_items: [
|
||||
{
|
||||
price: mapping[plan as "pro" | "enterprise"],
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
price: api,
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
userId: userId,
|
||||
orgId: orgId ?? null,
|
||||
plan: plan,
|
||||
},
|
||||
client_reference_id: orgId ?? userId,
|
||||
customer_email: user.emailAddresses[0].emailAddress,
|
||||
mode: "subscription",
|
||||
});
|
||||
|
||||
if (session.url) redirect(session.url);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { stripe } from "@/server/stripe";
|
||||
import { createCheckout } from "@/server/linkToPricing";
|
||||
import { auth, clerkClient } from "@clerk/nextjs";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getUrlServerSide } from "@/server/getUrlServerSide";
|
||||
import { db } from "@/db/db";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { subscriptionStatusTable } from "@/db/schema";
|
||||
import { getCurrentPlan } from "@/server/getCurrentPlan";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const { userId, orgId } = auth();
|
||||
|
||||
if (!userId) return redirect("/");
|
||||
|
||||
const change = new URL(req.url).searchParams.get("change");
|
||||
|
||||
const sub = await getCurrentPlan({
|
||||
org_id: orgId,
|
||||
user_id: userId,
|
||||
});
|
||||
|
||||
if (!sub) return redirect("/pricing");
|
||||
|
||||
const session = await stripe.billingPortal.sessions.create({
|
||||
customer: sub.stripe_customer_id,
|
||||
return_url: getUrlServerSide() + "/pricing",
|
||||
// flow_data:
|
||||
// change === "true" && sub.subscription_id
|
||||
// ? {
|
||||
// type: "subscription_update",
|
||||
// subscription_update: {
|
||||
// subscription: sub.subscription_id,
|
||||
// },
|
||||
// }
|
||||
// : undefined,
|
||||
});
|
||||
|
||||
redirect(session.url);
|
||||
// if (session.url) redirect(session.url);
|
||||
}
|
||||
Reference in new issue
Block a user