diff --git a/package.json b/package.json index cced597..6c62aec 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "react-whatsapp": "^0.3.0", "sass": "^1.76.0", "sharp": "^0.33.4", + "stripe": "^17.5.0", "swiper": "^11.1.1", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", diff --git a/src/app/api/stripe/create/route.ts b/src/app/api/stripe/create/route.ts new file mode 100644 index 0000000..0cadd0d --- /dev/null +++ b/src/app/api/stripe/create/route.ts @@ -0,0 +1,9 @@ + +import { createStripeCheckout } from '@/lib/stripe/stripe'; +import { NextResponse } from 'next/server'; + +export async function POST(request: Request) { + const { gigId } = await request.json(); + const url = await createStripeCheckout(gigId); + return NextResponse.json({ url }); +} \ No newline at end of file diff --git a/src/app/api/stripe/verify/page.ts b/src/app/api/stripe/verify/page.ts new file mode 100644 index 0000000..2729ec5 --- /dev/null +++ b/src/app/api/stripe/verify/page.ts @@ -0,0 +1,23 @@ +import { NextResponse } from 'next/server'; +import { stripe } from '@/lib/stripe/stripe'; + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const sessionId = searchParams.get('session_id'); + + if (!sessionId) { + return NextResponse.json({ success: false }, { status: 400 }); + } + + try { + const session = await stripe.checkout.sessions.retrieve(sessionId); + + if (session.payment_status === 'paid') { + return NextResponse.json({ success: true, session }); + } else { + return NextResponse.json({ success: false }, { status: 400 }); + } + } catch (error) { + return NextResponse.json({ success: false }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/stripe/webhook/route.ts b/src/app/api/stripe/webhook/route.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/app/gigs/_utils/components/data/gig-list-data.tsx b/src/app/gigs/_utils/components/data/gig-list-data.tsx new file mode 100644 index 0000000..c7e709b --- /dev/null +++ b/src/app/gigs/_utils/components/data/gig-list-data.tsx @@ -0,0 +1,12 @@ +import { T__Gig } from "../types/gigs-data-types"; + +export const GigsData: T__Gig[] = [ + { + id: 1, + title: "Web Development", + description: "I will build a modern website for your business", + price: 50, + fiverrLink: "https://fiverr.com/your-gig", + image: "/web-dev.jpg", + }, +]; diff --git a/src/app/gigs/_utils/components/gig-card.tsx b/src/app/gigs/_utils/components/gig-card.tsx new file mode 100644 index 0000000..b10c6d8 --- /dev/null +++ b/src/app/gigs/_utils/components/gig-card.tsx @@ -0,0 +1,54 @@ +"use client" + +import Link from "next/link"; +import { T__Gig } from "../types/gigs-data-types"; + +export default function GigCard({ gig }: { gig: T__Gig }) { + return ( +
+ {gig.title} +
+

{gig.title}

+

{gig.description}

+
+ ${gig.price} +
+ + Buy on Fiverr + + +
+
+
+
+ ); +} + +async function handleStripePurchase(gigId: number) { + try { + const response = await fetch("/api/create", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ gigId }), + }); + + const { url } = await response.json(); + window.location.href = url; + } catch (error) { + console.error("Error:", error); + } +} diff --git a/src/app/gigs/_utils/components/gig-list.tsx b/src/app/gigs/_utils/components/gig-list.tsx new file mode 100644 index 0000000..3ce79fc --- /dev/null +++ b/src/app/gigs/_utils/components/gig-list.tsx @@ -0,0 +1,13 @@ +import { T__Gig } from "../types/gigs-data-types"; +import { GigsData } from "./data/gig-list-data"; +import GigCard from "./gig-card"; + +export default function GigList() { + return ( +
+ {GigsData.map((gig: T__Gig) => ( + + ))} +
+ ); +} diff --git a/src/app/gigs/_utils/types/gigs-data-types.ts b/src/app/gigs/_utils/types/gigs-data-types.ts new file mode 100644 index 0000000..138b4f6 --- /dev/null +++ b/src/app/gigs/_utils/types/gigs-data-types.ts @@ -0,0 +1,8 @@ +export type T__Gig = { + id: number, + title: string, + description: string, + price: number, + fiverrLink: string, + image: string, + } \ No newline at end of file diff --git a/src/app/gigs/page.tsx b/src/app/gigs/page.tsx new file mode 100644 index 0000000..1d04280 --- /dev/null +++ b/src/app/gigs/page.tsx @@ -0,0 +1,10 @@ +import GigList from './_utils/components/gig-list'; + +export default function GigsPage() { + return ( +
+

My Fiverr Gigs

+ +
+ ); +} \ No newline at end of file diff --git a/src/app/gigs/payment-verify/page.tsx b/src/app/gigs/payment-verify/page.tsx new file mode 100644 index 0000000..b55b564 --- /dev/null +++ b/src/app/gigs/payment-verify/page.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useSearchParams } from "next/navigation"; + +export default function PaymentVerificationPage() { + const searchParams = useSearchParams(); + const [paymentStatus, setPaymentStatus] = useState("verifying"); + + useEffect(() => { + const sessionId = searchParams.get("session_id"); + const success = searchParams.get("success"); + + if (sessionId && success) { + verifyPayment(sessionId); + } else { + setPaymentStatus("invalid"); + } + }, [searchParams]); + + async function verifyPayment(sessionId: string) { + try { + const response = await fetch( + `/api/verify-payment?session_id=${sessionId}` + ); + const data = await response.json(); + + if (data.success) { + setPaymentStatus("success"); + // You can show order details here + } else { + setPaymentStatus("failed"); + } + } catch (error) { + setPaymentStatus("error"); + } + } + + return ( +
+ {paymentStatus === "verifying" &&

Verifying payment...

} + {paymentStatus === "success" &&

Payment successful!

} + {paymentStatus === "failed" && ( +

Payment verification failed. Please contact support.

+ )} + {paymentStatus === "invalid" && ( +

Invalid session. Please contact support.

+ )} + {paymentStatus === "error" && ( +

Error verifying payment. Please contact support.

+ )} +
+ ); +} diff --git a/src/lib/stripe/stripe.ts b/src/lib/stripe/stripe.ts new file mode 100644 index 0000000..22811d4 --- /dev/null +++ b/src/lib/stripe/stripe.ts @@ -0,0 +1,33 @@ +import { GigsData } from '@/app/gigs/_utils/components/data/gig-list-data'; +import Stripe from 'stripe'; + +export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); + +export async function createStripeCheckout(gigId: number) { + const gig = GigsData.find(g => g.id === gigId); + const metadata = { + userId: "test_user_43634563", + gigId: gigId.toString(), + } + + const session = await stripe.checkout.sessions.create({ + payment_method_types: ['card'], + line_items: [{ + price_data: { + currency: 'usd', + product_data: { + name: gig.title, + }, + unit_amount: gig.price * 100, + }, + quantity: 1, + }], + mode: 'payment', + metadata, + payment_intent_data: { metadata }, + success_url: `${process.env.NEXT_PUBLIC_URL}/gigs/payment-verify?success=true`, + cancel_url: `${process.env.NEXT_PUBLIC_URL}/gigs/payment-verify?success=false`, + }); + + return session.url; +} \ No newline at end of file