diff --git a/actions/createCheckoutSession.ts b/actions/createCheckoutSession.ts
new file mode 100644
index 0000000..5786f84
--- /dev/null
+++ b/actions/createCheckoutSession.ts
@@ -0,0 +1,86 @@
+"use server";
+
+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";
+
+export const createCheckoutSession = async (userEmail: string, plan: string) => {
+ try {
+ // Fetch user data from Firestore
+ const userData = await getUserData(userEmail);
+
+ if (!userData) {
+ throw new Error("User data not found.");
+ }
+
+ // Check if user has reached the request limit
+ const isWithinLimit = await checkUserPlanLimit(userEmail);
+
+ if (!isWithinLimit) {
+ throw new Error("You have reached your plan's request limit.");
+ }
+
+ // 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;
+ }
+
+ // Stripe price IDs for plans
+ const priceId = plan === "pro"
+ ? "price_1Q0L3aSCr1Ne8DGFAI9n4GbW" // Pro Plan price ID
+ : plan === "enterprise"
+ ? "price_1Q0L3aSCr1Ne8DGF3yD1iMnd" // Enterprise Plan price ID
+ : null;
+
+ if (!priceId) {
+ throw new Error("Invalid plan selected.");
+ }
+
+ // 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 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.");
+ }
+};
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
+}
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;
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" });
+}
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;
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;
diff --git a/package.json b/package.json
index ed05e61..4076454 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",
@@ -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",
@@ -34,6 +35,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"
},