Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions app/api/checkout/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import DodoPayments from "dodopayments";
import { NextRequest, NextResponse } from "next/server";
const dodopayments = new DodoPayments({
environment: "test_mode",
bearerToken: process.env.DODOPAYMENTS_API_KEY!,
});


export async function POST(req: NextRequest) {
try {
// Generate checkout URL
const body = await req.json();

const regex = new RegExp("^[^@]+@[^@]+.[^@]+$");

if (!body.name)
return NextResponse.json(
{
message: "Please provide a valid name",
},
{ status: 400 }
);

if (!body.email && !regex.test(body.email))
return NextResponse.json(
{
message: "Please provide a valid email",
},
{ status: 400 }
);

const customerName = body.name;
const customerEmail = body.email;

const checkout = await dodopayments.checkoutSessions.create({
product_cart: [
{
product_id: "pdt_zL2ZTxrs7MFg42hZemySH",
quantity: 1,
},
],
customer: {
name: customerName,
email: customerEmail,
},
return_url: "http://localhost:3000/",
});

return NextResponse.json({
message: "Checkout URL created successfully",
url: checkout.checkout_url,
});
} catch (err) {
console.error(err);
return NextResponse.json(
{
message: "Internal server error",
},
{
status: 500,
}
);
}
}
88 changes: 88 additions & 0 deletions app/api/webhook/dodopayments/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { NextRequest, NextResponse } from "next/server";
import { Webhook } from "standardwebhooks";

const webhookSecret = process.env.DODO_PAYMENTS_WEBHOOK_KEY!;

export async function POST(req: NextRequest) {
try {
// Get webhook headers
const webhookId = req.headers.get("webhook-id");
const webhookSignature = req.headers.get("webhook-signature");
const webhookTimestamp = req.headers.get("webhook-timestamp");

if (!webhookId || !webhookSignature || !webhookTimestamp) {
return NextResponse.json(
{ error: "Missing webhook headers" },
{ status: 400 }
);
}

// Get raw body
const body = await req.text();

// Verify webhook signature
const webhook = new Webhook(webhookSecret);

try {
await webhook.verify(body, {
"webhook-id": webhookId,
"webhook-signature": webhookSignature,
"webhook-timestamp": webhookTimestamp,
});
} catch (err) {
console.error("Webhook verification failed:", err);
return NextResponse.json(
{ error: "Invalid webhook signature" },
{ status: 400 }
);
}

// Parse the verified payload
const payload = JSON.parse(body);
console.log("WEBHOOK", payload.data);

// Handle different webhook events
switch (payload.type) {
case "payment.succeeded":
console.log("Payment succeeded:", payload.data);
// Handle successful payment
// Update your database, send confirmation email, etc.
break;

case "payment.failed":
console.log("Payment failed:", payload.data);
// Handle failed payment
break;

case "subscription.created":
console.log("Subscription created:", payload.data);
// Handle new subscription
break;

case "subscription.cancelled":
console.log("Subscription cancelled:", payload.data);
// Handle subscription cancellation
break;

case "subscription.updated":
console.log("Subscription updated:", payload.data);
// Handle subscription update
break;

default:
console.log("Unhandled webhook event:", payload.type);
}

// Return success response
return NextResponse.json(
{ received: true, type: payload.type },
{ status: 200 }
);
} catch (error) {
console.error("Webhook processing error:", error);
return NextResponse.json(
{ error: "Webhook processing failed" },
{ status: 500 }
);
}
}
57 changes: 57 additions & 0 deletions components/CheckButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";

import { createClient } from "@/lib/supabase/client";
import { Button } from "./ui/button";
import { useEffect, useState } from "react";
import type { User as SupabaseUser } from "@supabase/supabase-js";

export function CheckButton({ className }: { className?: string }) {
const supabase = createClient();
const [user, setUser] = useState<SupabaseUser | null>(null);

const handleClick = async () => {
try {
const response = await fetch("/api/checkout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: user?.user_metadata.full_name,
email: user?.email,
}),
});

const data = await response.json();

if (response.ok) {
window.location.href = data.url;
} else {
throw new Error(data.message || "Something went wrong");
}
} catch (err) {
throw new Error((err as Error).message || "Something went wrong");
}
};
useEffect(() => {
const getUser = async () => {
const {
data: { user },
} = await supabase.auth.getUser();
setUser(user);
};

getUser();

const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
});

return () => subscription.unsubscribe();
}, [supabase.auth]);

console.log(user,'the user ');
return <Button className={className} onClick={handleClick}>Subscribe</Button>;
}
4 changes: 4 additions & 0 deletions components/UserModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { motion, AnimatePresence } from "framer-motion";
import type { User as SupabaseUser } from "@supabase/supabase-js";
import { CheckButton } from "./CheckButton";

interface UserModalProps {
isOpen: boolean;
Expand Down Expand Up @@ -209,6 +210,9 @@ export function UserModal({ isOpen, onClose, user }: UserModalProps) {
</>
)}
</Button>
<div className="flex items-center gap-2 my-2 w">
<CheckButton className="w-full" />{" "}
</div>
</div>
</motion.div>
</motion.div>
Expand Down
Loading