From e33c05fffbadab724f91a392027f92b879462e4b Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 5 Oct 2024 22:56:56 +0530 Subject: [PATCH 01/17] Upgrade Plans --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ed05e61..b131932 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inspiregem", - "version": "0.1.0", + "version": "1.0.0", "private": true, "scripts": { "dev": "next dev", @@ -34,6 +34,7 @@ "@stripe/stripe-js": "^4.4.0", "next-auth": "^4.24.7", "react-toastify": "^10.0.5", + "micro": "^10.0.1", "stripe": "^16.10.0", "tailwindcss": "^3.4.10" }, From 1e90f597b80d09825306e624df82c2fcd3acdc7c Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 5 Oct 2024 23:00:51 +0530 Subject: [PATCH 02/17] Create route.ts --- app/api/webhook/route.ts | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 app/api/webhook/route.ts diff --git a/app/api/webhook/route.ts b/app/api/webhook/route.ts new file mode 100644 index 0000000..9af5864 --- /dev/null +++ b/app/api/webhook/route.ts @@ -0,0 +1,63 @@ +// app/api/webhook/route.ts + +import { buffer } from "micro"; +import { NextRequest, NextResponse } from "next/server"; +import Stripe from "stripe"; +import { adminDb } from "@/lib/firebaseAdmin"; // Import your firebase admin setup +import { headers } from "next/headers"; + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: "2024-06-20", +}); + +export const config = { + api: { + bodyParser: false, // Stripe requires the raw body to validate the signature + }, +}; + +export async function POST(req: NextRequest) { + const buf = await buffer(req); + const sig = headers().get("stripe-signature"); + + let event: Stripe.Event; + + try { + event = stripe.webhooks.constructEvent( + buf, + sig!, + process.env.STRIPE_WEBHOOK_SECRET! + ); + } catch (err) { + console.error(`⚠️ Webhook signature verification failed.`, err.message); + return NextResponse.json({ error: `Webhook Error: ${err.message}` }, { status: 400 }); + } + + switch (event.type) { + case "checkout.session.completed": + const session = event.data.object as Stripe.Checkout.Session; + await handleCheckoutSessionCompleted(session); + break; + default: + console.warn(`Unhandled event type ${event.type}`); + } + + return NextResponse.json({ received: true }, { status: 200 }); +} + +async function handleCheckoutSessionCompleted(session: Stripe.Checkout.Session) { + const email = session.customer_email; + const plan = session.metadata?.plan; + + if (email && plan) { + try { + const userRef = adminDb.collection("users").doc(email); + await userRef.update({ + plan, + }); + console.log(`Successfully updated plan for ${email} to ${plan}`); + } catch (error) { + console.error(`Failed to update user plan for ${email}:`, error); + } + } +} From aed6d45e10264bfcfd68c0206bea904c2f17a2d6 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 5 Oct 2024 23:01:50 +0530 Subject: [PATCH 03/17] Update stripe.ts --- lib/stripe.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/stripe.ts b/lib/stripe.ts index f8c9239..fd77f49 100644 --- a/lib/stripe.ts +++ b/lib/stripe.ts @@ -1,3 +1,5 @@ +// lib/stripe.ts + import Stripe from "stripe"; const stripeSecretKey = process.env.STRIPE_SECRET_KEY; @@ -6,6 +8,8 @@ if (!stripeSecretKey) { throw new Error("STRIPE_SECRET_KEY is not set"); } -const stripe = new Stripe(stripeSecretKey); +const stripe = new Stripe(stripeSecretKey, { + apiVersion: "2024-06-20", +}); export default stripe; From 5c5f6dc7cc2a46a09eaa92856d8a0c361407ea8b Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sat, 5 Oct 2024 23:02:36 +0530 Subject: [PATCH 04/17] Update stripe-js.ts --- lib/stripe-js.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/stripe-js.ts b/lib/stripe-js.ts index 2207124..c5e9277 100644 --- a/lib/stripe-js.ts +++ b/lib/stripe-js.ts @@ -1,14 +1,16 @@ +// lib/stripe-js.ts + import { loadStripe, Stripe } from "@stripe/stripe-js"; let stripePromise: Promise; -if (process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY === undefined) { - throw new Error("NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY is not set"); -} - const getStripe = (): Promise => { if (!stripePromise) { - stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!); + if (process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY === undefined) { + throw new Error("NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY is not set"); + } + + stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY); } return stripePromise; From 6d9d0afc53dd066e91f6030521919ae52141a3a4 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 11:39:30 +0530 Subject: [PATCH 05/17] Update route.ts --- app/api/webhook/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/webhook/route.ts b/app/api/webhook/route.ts index 9af5864..1c8b574 100644 --- a/app/api/webhook/route.ts +++ b/app/api/webhook/route.ts @@ -7,7 +7,7 @@ import { adminDb } from "@/lib/firebaseAdmin"; // Import your firebase admin set import { headers } from "next/headers"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { - apiVersion: "2024-06-20", + apiVersion: "2024-09-30.acacia", }); export const config = { From 2ad11b75250fcf4d47ba2239f1e2eb35b051bbc6 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 11:41:02 +0530 Subject: [PATCH 06/17] Update stripe.ts --- lib/stripe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stripe.ts b/lib/stripe.ts index fd77f49..1f178e5 100644 --- a/lib/stripe.ts +++ b/lib/stripe.ts @@ -9,7 +9,7 @@ if (!stripeSecretKey) { } const stripe = new Stripe(stripeSecretKey, { - apiVersion: "2024-06-20", + apiVersion: "2024-09-30.acacia", }); export default stripe; From d2ebb232c8e8d73816dacbf89b4c0bbb8ec9a394 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 11:46:01 +0530 Subject: [PATCH 07/17] Delete app/api/webhook directory --- app/api/webhook/route.ts | 63 ---------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 app/api/webhook/route.ts diff --git a/app/api/webhook/route.ts b/app/api/webhook/route.ts deleted file mode 100644 index 1c8b574..0000000 --- a/app/api/webhook/route.ts +++ /dev/null @@ -1,63 +0,0 @@ -// app/api/webhook/route.ts - -import { buffer } from "micro"; -import { NextRequest, NextResponse } from "next/server"; -import Stripe from "stripe"; -import { adminDb } from "@/lib/firebaseAdmin"; // Import your firebase admin setup -import { headers } from "next/headers"; - -const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { - apiVersion: "2024-09-30.acacia", -}); - -export const config = { - api: { - bodyParser: false, // Stripe requires the raw body to validate the signature - }, -}; - -export async function POST(req: NextRequest) { - const buf = await buffer(req); - const sig = headers().get("stripe-signature"); - - let event: Stripe.Event; - - try { - event = stripe.webhooks.constructEvent( - buf, - sig!, - process.env.STRIPE_WEBHOOK_SECRET! - ); - } catch (err) { - console.error(`⚠️ Webhook signature verification failed.`, err.message); - return NextResponse.json({ error: `Webhook Error: ${err.message}` }, { status: 400 }); - } - - switch (event.type) { - case "checkout.session.completed": - const session = event.data.object as Stripe.Checkout.Session; - await handleCheckoutSessionCompleted(session); - break; - default: - console.warn(`Unhandled event type ${event.type}`); - } - - return NextResponse.json({ received: true }, { status: 200 }); -} - -async function handleCheckoutSessionCompleted(session: Stripe.Checkout.Session) { - const email = session.customer_email; - const plan = session.metadata?.plan; - - if (email && plan) { - try { - const userRef = adminDb.collection("users").doc(email); - await userRef.update({ - plan, - }); - console.log(`Successfully updated plan for ${email} to ${plan}`); - } catch (error) { - console.error(`Failed to update user plan for ${email}:`, error); - } - } -} From c3360d539b6e2a6ed0617f29d135e0303a47d7c8 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 11:52:22 +0530 Subject: [PATCH 08/17] Create route.ts --- app/webhook/route.ts | 95 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 app/webhook/route.ts diff --git a/app/webhook/route.ts b/app/webhook/route.ts new file mode 100644 index 0000000..a82ac95 --- /dev/null +++ b/app/webhook/route.ts @@ -0,0 +1,95 @@ +import { adminDb } from "@/firebaseAdmin"; +import stripe from "@/lib/stripe"; +import { headers } from "next/headers"; +import { NextRequest, NextResponse } from "next/server"; +import Stripe from "stripe"; + +export async function POST(req: NextRequest) { + const headersList = headers(); + const body = await req.text(); // important: must be req.text() not req.json() + const signature = headersList.get("stripe-signature"); + + if (!signature) { + return new Response("No signature", { status: 400 }); + } + + if (!process.env.STRIPE_WEBHOOK_SECRET) { + console.log("⚠️ Stripe webhook secret is not set."); + return new NextResponse("Stripe webhook secret is not set", { + status: 400, + }); + } + + let event: Stripe.Event; + + try { + event = stripe.webhooks.constructEvent( + body, + signature, + process.env.STRIPE_WEBHOOK_SECRET + ); + } catch (err) { + console.error(`Webhook Error: ${err}`); + return new NextResponse(`Webhook Error: ${err}`, { status: 400 }); + } + + // Helper function to get user details + const getUserDetails = async (customerId: string) => { + const userDoc = await adminDb + .collection("users") + .where("stripeCustomerId", "==", customerId) + .limit(1) + .get(); + + if (!userDoc.empty) { + return userDoc.docs[0]; + } + return null; + }; + + // Handling different event types + switch (event.type) { + case "checkout.session.completed": + case "payment_intent.succeeded": { + const invoice = event.data.object as Stripe.PaymentIntent; + const customerId = invoice.customer as string; + + const userDetails = await getUserDetails(customerId); + if (!userDetails?.id) { + console.error("User not found for customerId:", customerId); + return new NextResponse("User not found", { status: 404 }); + } + + // Update the user's membership status + await adminDb.collection("users").doc(userDetails.id).update({ + hasActiveMembership: true, + plan: "pro", // Assuming they are on the Pro plan after payment + }); + + break; + } + case "customer.subscription.deleted": + case "subscription_schedule.canceled": { + const subscription = event.data.object as Stripe.Subscription; + const customerId = subscription.customer as string; + + const userDetails = await getUserDetails(customerId); + if (!userDetails?.id) { + console.error("User not found for customerId:", customerId); + return new NextResponse("User not found", { status: 404 }); + } + + // Update the user's membership status to false + await adminDb.collection("users").doc(userDetails.id).update({ + hasActiveMembership: false, + plan: "free", // Downgrade to free plan if subscription is canceled + }); + break; + } + + default: + console.log(`Unhandled event type: ${event.type}`); + } + + return NextResponse.json({ message: "Webhook received and processed" }); +} From bb10f65ec5e611b8875b93c65b6486a68d636800 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 11:58:05 +0530 Subject: [PATCH 09/17] Update stripe.ts --- lib/stripe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stripe.ts b/lib/stripe.ts index 1f178e5..fd77f49 100644 --- a/lib/stripe.ts +++ b/lib/stripe.ts @@ -9,7 +9,7 @@ if (!stripeSecretKey) { } const stripe = new Stripe(stripeSecretKey, { - apiVersion: "2024-09-30.acacia", + apiVersion: "2024-06-20", }); export default stripe; From ca45a9f08eea06a4c74044f8182dbe89116685f1 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 12:07:30 +0530 Subject: [PATCH 10/17] Create createCheckoutSession.ts --- actions/createCheckoutSession.ts | 54 ++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 actions/createCheckoutSession.ts diff --git a/actions/createCheckoutSession.ts b/actions/createCheckoutSession.ts new file mode 100644 index 0000000..8fcb521 --- /dev/null +++ b/actions/createCheckoutSession.ts @@ -0,0 +1,54 @@ +"use server"; + +import { UserDetails } from "@/app/dashboard/upgrade/page"; +import { adminDb } from "@/firebaseAdmin"; +import getBaseUrl from "@/lib/getBaseUrl"; +import stripe from "@/lib/stripe"; +import { auth } from "@clerk/nextjs/server"; + +export async function createCheckoutSession(userDetails: UserDetails) { + const { userId } = await auth(); + + if (!userId) { + throw new Error("User not found"); + } + + // Check if the user already has a Stripe customer ID in Firestore + const userDoc = await adminDb.collection("users").doc(userId).get(); + let stripeCustomerId = userDoc.data()?.stripeCustomerId; + + // If no Stripe customer ID, create a new Stripe customer + if (!stripeCustomerId) { + const customer = await stripe.customers.create({ + email: userDetails.email, + name: userDetails.name, + metadata: { + userId, + }, + }); + + // Save the customer ID in Firestore for future reference + await adminDb.collection("users").doc(userId).update({ + stripeCustomerId: customer.id, + }); + + stripeCustomerId = customer.id; + } + + // Create a new checkout session for subscription + const session = await stripe.checkout.sessions.create({ + payment_method_types: ["card"], + line_items: [ + { + price: "price_1PprpjSCr1Ne8DGFURTf2bSL", // Adjust the price ID based on InspireGem's pricing + quantity: 1, + }, + ], + mode: "subscription", + customer: stripeCustomerId, + success_url: `${getBaseUrl()}/dashboard?upgrade=true`, // Redirect user back to dashboard after successful upgrade + cancel_url: `${getBaseUrl()}/dashboard/upgrade`, // Cancel URL for failed payments + }); + + return session.id; // Return the session ID to be used in the frontend +} From 0ea65d4ede31ea0e340eb917e35cf80f1913fdba Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 12:09:04 +0530 Subject: [PATCH 11/17] Create createStripePortal.ts --- actions/createStripePortal.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 actions/createStripePortal.ts diff --git a/actions/createStripePortal.ts b/actions/createStripePortal.ts new file mode 100644 index 0000000..572f96c --- /dev/null +++ b/actions/createStripePortal.ts @@ -0,0 +1,32 @@ +"use server"; + +import { adminDb } from "@/firebaseAdmin"; +import getBaseUrl from "@/lib/getBaseUrl"; +import stripe from "@/lib/stripe"; +import { auth } from "@clerk/nextjs/server"; + +export async function createStripePortal() { + auth().protect(); + + const { userId } = await auth(); + + if (!userId) { + throw new Error("User not found"); + } + + // Get the Stripe customer ID from Firestore + const userDoc = await adminDb.collection("users").doc(userId).get(); + const stripeCustomerId = userDoc.data()?.stripeCustomerId; + + if (!stripeCustomerId) { + throw new Error("Stripe customer not found"); + } + + // Create a Stripe billing portal session + const session = await stripe.billingPortal.sessions.create({ + customer: stripeCustomerId, + return_url: `${getBaseUrl()}/dashboard`, // Redirect back to the dashboard after managing subscription + }); + + return session.url; // Return the URL to be used in the frontend for redirecting users +} From bad38843cc819fda33808d07edcb8c5bb27dc3d7 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 12:10:21 +0530 Subject: [PATCH 12/17] Update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b131932..4076454 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "axios": "^1.7.7", "bcryptjs": "^2.4.3", "classnames": "^2.5.1", + "@clerk/nextjs": "^5.2.6", "dotenv": "^16.4.5", "firebase": "^10.12.4", "firebase-admin": "^11.4.1", From a04b3b4ca33a06d7ca765aaa0ab4bcfae377b5ea Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 12:20:39 +0530 Subject: [PATCH 13/17] Update createCheckoutSession.ts --- actions/createCheckoutSession.ts | 37 +++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/actions/createCheckoutSession.ts b/actions/createCheckoutSession.ts index 8fcb521..7847bfb 100644 --- a/actions/createCheckoutSession.ts +++ b/actions/createCheckoutSession.ts @@ -6,19 +6,21 @@ import getBaseUrl from "@/lib/getBaseUrl"; import stripe from "@/lib/stripe"; import { auth } from "@clerk/nextjs/server"; -export async function createCheckoutSession(userDetails: UserDetails) { +export async function createCheckoutSession(userDetails: UserDetails, plan: string) { const { userId } = await auth(); if (!userId) { throw new Error("User not found"); } - // Check if the user already has a Stripe customer ID in Firestore - const userDoc = await adminDb.collection("users").doc(userId).get(); - let stripeCustomerId = userDoc.data()?.stripeCustomerId; + // first check if the user already has a stripeCustomerId + let stripeCustomerId; + + const user = await adminDb.collection("users").doc(userId).get(); + stripeCustomerId = user.data()?.stripeCustomerId; - // If no Stripe customer ID, create a new Stripe customer if (!stripeCustomerId) { + // create a new stripe customer const customer = await stripe.customers.create({ email: userDetails.email, name: userDetails.name, @@ -27,28 +29,39 @@ export async function createCheckoutSession(userDetails: UserDetails) { }, }); - // Save the customer ID in Firestore for future reference - await adminDb.collection("users").doc(userId).update({ + await adminDb.collection("users").doc(userId).set({ stripeCustomerId: customer.id, }); stripeCustomerId = customer.id; } - // Create a new checkout session for subscription + // Set priceId based on the selected plan + let priceId; + if (plan === "pro") { + priceId = "price_1Q0L3aSCr1Ne8DGFAI9n4GbW"; // Pro plan + } else if (plan === "enterprise") { + priceId = "price_1Q0L3aSCr1Ne8DGF3yD1iMnd"; // Enterprise plan + } else { + return NextResponse.json( + { error: "Invalid plan selected" }, + { status: 400 } + ); + } + const session = await stripe.checkout.sessions.create({ payment_method_types: ["card"], line_items: [ { - price: "price_1PprpjSCr1Ne8DGFURTf2bSL", // Adjust the price ID based on InspireGem's pricing + price: priceId, // Dynamic price ID quantity: 1, }, ], mode: "subscription", customer: stripeCustomerId, - success_url: `${getBaseUrl()}/dashboard?upgrade=true`, // Redirect user back to dashboard after successful upgrade - cancel_url: `${getBaseUrl()}/dashboard/upgrade`, // Cancel URL for failed payments + success_url: `${getBaseUrl()}/dashboard?upgrade=true`, + cancel_url: `${getBaseUrl()}/dashboard/upgrade`, }); - return session.id; // Return the session ID to be used in the frontend + return session.id; } From 6501af6ad0d9583ede94ff0718cd36c12566a02d Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 12:35:16 +0530 Subject: [PATCH 14/17] Create page.tsx --- app/dashboard/upgrade/page.tsx | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 app/dashboard/upgrade/page.tsx diff --git a/app/dashboard/upgrade/page.tsx b/app/dashboard/upgrade/page.tsx new file mode 100644 index 0000000..291b7f4 --- /dev/null +++ b/app/dashboard/upgrade/page.tsx @@ -0,0 +1,77 @@ +//app/dashboard/upgrade/page.tsx +"use client"; +import React from "react"; +import getStripe from "@/lib/stripe-js"; // Ensure this utility is set up correctly +import SEO from "@/components/SEO"; // Importing SEO component + +const UpgradePage: React.FC = () => { + // Function to handle fetching Stripe session and redirecting to checkout + const getPriceFn = (plan: string) => { + fetch(`/api/checkout?plan=${plan}`) + .then((data) => data.json()) + .then(async (body) => { + const sessionId = body.sessionId; + const stripe = await getStripe(); + await stripe?.redirectToCheckout({ sessionId }); + }); + }; + + return ( +
+ {/* SEO Component */} + + +
+ {/* Free Plan */} +
+

Free Plan

+

Up to 50 requests per month.

+

Basic AI content generation.

+

Community support.

+ +
+ + {/* Pro Plan */} +
+

Pro Plan

+

500 requests per month.

+

Advanced AI content generation.

+

Priority email support.

+ +
+ + {/* Enterprise Plan */} +
+

Enterprise Plan

+

Unlimited requests.

+

Access to all AI features.

+

24/7 premium support.

+ +
+
+
+ ); +}; + +export default UpgradePage; From 5c6146cdf2cc9aafc328aa47ef13b21f02d23109 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 12:45:55 +0530 Subject: [PATCH 15/17] Update createCheckoutSession.ts --- actions/createCheckoutSession.ts | 79 ++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/actions/createCheckoutSession.ts b/actions/createCheckoutSession.ts index 7847bfb..44f526e 100644 --- a/actions/createCheckoutSession.ts +++ b/actions/createCheckoutSession.ts @@ -1,19 +1,26 @@ "use server"; -import { UserDetails } from "@/app/dashboard/upgrade/page"; +import { getUserData, checkUserPlanLimit } from "@/firebaseFunctions"; // Import necessary functions from firebaseFunctions.ts import { adminDb } from "@/firebaseAdmin"; import getBaseUrl from "@/lib/getBaseUrl"; import stripe from "@/lib/stripe"; -import { auth } from "@clerk/nextjs/server"; -export async function createCheckoutSession(userDetails: UserDetails, plan: string) { - const { userId } = await auth(); +export const createCheckoutSession = async (userEmail: string, plan: string) => { + try { + // Fetch user data from Firestore + const userData = await getUserData(userEmail); - if (!userId) { - throw new Error("User not found"); - } + if (!userData) { + throw new Error("User data not found."); + } + + // Check if user has reached the request limit + const isWithinLimit = await checkUserPlanLimit(userEmail); - // first check if the user already has a stripeCustomerId + if (!isWithinLimit) { + throw new Error("You have reached your plan's request limit."); + } + // first check if the user already has a stripeCustomerId let stripeCustomerId; const user = await adminDb.collection("users").doc(userId).get(); @@ -35,33 +42,35 @@ export async function createCheckoutSession(userDetails: UserDetails, plan: stri stripeCustomerId = customer.id; } + // Stripe price IDs for plans + const priceId = plan === "pro" + ? "price_1Q0L3aSCr1Ne8DGFAI9n4GbW" // Pro Plan price ID + : plan === "enterprise" + ? "price_1Q0L3aSCr1Ne8DGF3yD1iMnd" // Enterprise Plan price ID + : null; - // Set priceId based on the selected plan - let priceId; - if (plan === "pro") { - priceId = "price_1Q0L3aSCr1Ne8DGFAI9n4GbW"; // Pro plan - } else if (plan === "enterprise") { - priceId = "price_1Q0L3aSCr1Ne8DGF3yD1iMnd"; // Enterprise plan - } else { - return NextResponse.json( - { error: "Invalid plan selected" }, - { status: 400 } - ); - } + if (!priceId) { + throw new Error("Invalid plan selected."); + } - const session = await stripe.checkout.sessions.create({ - payment_method_types: ["card"], - line_items: [ - { - price: priceId, // Dynamic price ID - quantity: 1, - }, - ], - mode: "subscription", - customer: stripeCustomerId, - success_url: `${getBaseUrl()}/dashboard?upgrade=true`, - cancel_url: `${getBaseUrl()}/dashboard/upgrade`, - }); + // Create a checkout session in Stripe + const session = await stripe.checkout.sessions.create({ + payment_method_types: ["card"], + mode: "subscription", + line_items: [ + { + price: priceId, + quantity: 1, + }, + ], + success_url: `${getBaseUrl()}/dashboard/upgrade/success?session_id={CHECKOUT_SESSION_ID}`, + cancel_url: `${getBaseUrl()}/dashboard/upgrade/cancel`, + }); - return session.id; -} + // Return the session ID for the Stripe checkout session + return session.id; + } catch (error) { + console.error("Error creating checkout session:", error); + throw new Error("Failed to create checkout session."); + } +}; From 12405340fafcc2a5c6456bbd5d25c1d742e12229 Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 12:48:39 +0530 Subject: [PATCH 16/17] Update createCheckoutSession.ts --- actions/createCheckoutSession.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/createCheckoutSession.ts b/actions/createCheckoutSession.ts index 44f526e..93c14d7 100644 --- a/actions/createCheckoutSession.ts +++ b/actions/createCheckoutSession.ts @@ -23,7 +23,7 @@ export const createCheckoutSession = async (userEmail: string, plan: string) => // first check if the user already has a stripeCustomerId let stripeCustomerId; - const user = await adminDb.collection("users").doc(userId).get(); + const user = await adminDb.collection("users").doc(user).get(); stripeCustomerId = user.data()?.stripeCustomerId; if (!stripeCustomerId) { From bc28aa55dbeaae0bb063de24d88e69075b1e3f9b Mon Sep 17 00:00:00 2001 From: Arhan Ansari Date: Sun, 6 Oct 2024 12:53:26 +0530 Subject: [PATCH 17/17] Update createCheckoutSession.ts --- actions/createCheckoutSession.ts | 50 +++++++++++++++++++------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/actions/createCheckoutSession.ts b/actions/createCheckoutSession.ts index 93c14d7..5786f84 100644 --- a/actions/createCheckoutSession.ts +++ b/actions/createCheckoutSession.ts @@ -20,28 +20,38 @@ export const createCheckoutSession = async (userEmail: string, plan: string) => if (!isWithinLimit) { throw new Error("You have reached your plan's request limit."); } - // first check if the user already has a stripeCustomerId - let stripeCustomerId; - - const user = await adminDb.collection("users").doc(user).get(); - stripeCustomerId = user.data()?.stripeCustomerId; - - if (!stripeCustomerId) { - // create a new stripe customer - const customer = await stripe.customers.create({ - email: userDetails.email, - name: userDetails.name, - metadata: { - userId, - }, - }); - await adminDb.collection("users").doc(userId).set({ - stripeCustomerId: customer.id, - }); + // First check if the user already has a stripeCustomerId + let stripeCustomerId; + + // Fetch the user document using userEmail + const userDoc = await adminDb.collection("users").doc(userEmail).get(); + const userDetails = userDoc.data(); + + if (!userDetails) { + throw new Error("User details not found."); + } + + stripeCustomerId = userDetails.stripeCustomerId; + + if (!stripeCustomerId) { + // Create a new stripe customer + const customer = await stripe.customers.create({ + email: userDetails.email, + name: userDetails.name, + metadata: { + userId: userEmail, // Using userEmail as userId + }, + }); + + // Update Firestore with the new Stripe customer ID + await adminDb.collection("users").doc(userEmail).set({ + stripeCustomerId: customer.id, + }, { merge: true }); // Use merge to avoid overwriting existing data + + stripeCustomerId = customer.id; + } - stripeCustomerId = customer.id; - } // Stripe price IDs for plans const priceId = plan === "pro" ? "price_1Q0L3aSCr1Ne8DGFAI9n4GbW" // Pro Plan price ID