-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added course payment and auth system
- Loading branch information
1 parent
612fa7f
commit 2cab054
Showing
22 changed files
with
981 additions
and
195 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
-- AlterTable | ||
ALTER TABLE "Course" ADD COLUMN "price" INTEGER NOT NULL DEFAULT 1000; | ||
|
||
-- CreateTable | ||
CREATE TABLE "Receipt" ( | ||
"id" TEXT NOT NULL, | ||
"userId" TEXT NOT NULL, | ||
"courseId" INTEGER NOT NULL DEFAULT 0, | ||
"razorpayOrderId" TEXT, | ||
|
||
CONSTRAINT "Receipt_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateTable | ||
CREATE TABLE "Purchase" ( | ||
"id" TEXT NOT NULL, | ||
"receiptId" TEXT NOT NULL, | ||
"razorpay_payment_id" TEXT NOT NULL, | ||
"razorpay_order_id" TEXT NOT NULL, | ||
"razorpay_signature" TEXT NOT NULL, | ||
"purchasedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
"paymentVerified" BOOLEAN NOT NULL DEFAULT false, | ||
"purchasedCourseId" INTEGER NOT NULL, | ||
"purchasedById" TEXT NOT NULL, | ||
|
||
CONSTRAINT "Purchase_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "Receipt_razorpayOrderId_key" ON "Receipt"("razorpayOrderId"); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "Purchase_receiptId_key" ON "Purchase"("receiptId"); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "Purchase_razorpay_payment_id_key" ON "Purchase"("razorpay_payment_id"); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "Purchase_razorpay_order_id_key" ON "Purchase"("razorpay_order_id"); | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "Receipt" ADD CONSTRAINT "Receipt_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "Receipt" ADD CONSTRAINT "Receipt_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE; | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "Purchase" ADD CONSTRAINT "Purchase_receiptId_fkey" FOREIGN KEY ("receiptId") REFERENCES "Receipt"("id") ON DELETE RESTRICT ON UPDATE CASCADE; | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "Purchase" ADD CONSTRAINT "Purchase_purchasedCourseId_fkey" FOREIGN KEY ("purchasedCourseId") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE; | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "Purchase" ADD CONSTRAINT "Purchase_purchasedById_fkey" FOREIGN KEY ("purchasedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { NextRequest, NextResponse } from 'next/server'; | ||
import db from '@/db'; | ||
import bcrypt from 'bcrypt'; | ||
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; | ||
|
||
export async function POST(req: NextRequest) { | ||
try { | ||
const { name, email, password } = await req.json(); | ||
|
||
const hashedPassword = await bcrypt.hash(password, 10); | ||
await db.user.create({ | ||
data: { | ||
email, | ||
name, | ||
password: hashedPassword, | ||
}, | ||
}); | ||
|
||
return NextResponse.json( | ||
{ message: 'Account created sucessfully' }, | ||
{ status: 201 }, | ||
); | ||
} catch (e) { | ||
if (e instanceof PrismaClientKnownRequestError) { | ||
return NextResponse.json( | ||
{ error: 'Email already taken.' }, | ||
{ status: 400 }, | ||
); | ||
} | ||
console.log(e); | ||
return NextResponse.json( | ||
{ error: 'Failed to parse JSON input' }, | ||
{ status: 400 }, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { NextRequest, NextResponse } from 'next/server'; | ||
import Razorpay from 'razorpay'; | ||
import db from '@/db'; | ||
import { z } from 'zod'; | ||
|
||
const schema = z.object({ | ||
courseId: z.number(), | ||
userId: z.string(), | ||
}); | ||
|
||
const razorpay = new Razorpay({ | ||
key_id: process.env.RAZORPAY_KEY_ID!, | ||
key_secret: process.env.RAZORPAY_SECRET!, | ||
}); | ||
|
||
export async function POST(req: NextRequest) { | ||
const reqBody = await req.json(); | ||
const body = schema.safeParse(reqBody); | ||
|
||
if (!body.success) { | ||
return NextResponse.json( | ||
{ | ||
error: 'Error parsing the body of the request', | ||
}, | ||
{ | ||
status: 422, | ||
}, | ||
); | ||
} | ||
|
||
const course = await db.course.findFirst({ | ||
where: { | ||
id: body.data.courseId, | ||
}, | ||
}); | ||
|
||
if (!course) | ||
return NextResponse.json({ error: 'Course Not Found' }, { status: 404 }); | ||
|
||
const receipt = await db.receipt.create({ | ||
data: { | ||
userId: body.data.userId, | ||
courseId: body.data.courseId, | ||
}, | ||
}); | ||
|
||
const payment_capture = 1; | ||
const amount = course.price; | ||
const options = { | ||
amount: (amount * 100).toString(), | ||
currency: 'INR', | ||
receipt: receipt.id, | ||
payment_capture, | ||
notes: { | ||
userId: body.data.userId, | ||
courseId: body.data.courseId, | ||
receipt: receipt.id, | ||
}, | ||
}; | ||
|
||
try { | ||
const response = await razorpay.orders.create(options); | ||
|
||
await db.receipt.update({ | ||
where: { | ||
id: receipt.id, | ||
}, | ||
data: { | ||
razorpayOrderId: response.id, | ||
}, | ||
}); | ||
|
||
return NextResponse.json({ | ||
id: response.id, | ||
currency: response.currency, | ||
amount: response.amount, | ||
razorPayKey: process.env.RAZORPAY_KEY_ID, | ||
}); | ||
} catch (err) { | ||
console.log(err); | ||
return NextResponse.json(err); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { NextRequest, NextResponse } from 'next/server'; | ||
import { validatePaymentVerification } from 'razorpay/dist/utils/razorpay-utils'; | ||
import { z } from 'zod'; | ||
import db from '@/db'; | ||
|
||
const razorPayZodSchema = z.object({ | ||
userId: z.string(), | ||
courseId: z.number(), | ||
razorpay_order_id: z.string(), | ||
razorpay_payment_id: z.string(), | ||
razorpay_signature: z.string(), | ||
}); | ||
|
||
export async function POST(req: NextRequest) { | ||
const jsonBody = await req.json(); | ||
|
||
const body = razorPayZodSchema.safeParse(jsonBody); | ||
|
||
if (!body.success) { | ||
return NextResponse.json( | ||
{ | ||
error: 'Invalid Body', | ||
}, | ||
{ status: 422 }, | ||
); | ||
} | ||
|
||
const { | ||
razorpay_order_id, | ||
razorpay_payment_id, | ||
razorpay_signature, | ||
courseId, | ||
userId, | ||
} = body.data; | ||
|
||
const isPaymentValid = validatePaymentVerification( | ||
{ | ||
order_id: razorpay_order_id, | ||
payment_id: razorpay_payment_id, | ||
}, | ||
razorpay_signature, | ||
process.env.RAZORPAY_SECRET!, | ||
); | ||
|
||
if (!isPaymentValid) | ||
return NextResponse.json( | ||
{ error: 'Payment not verified' }, | ||
{ status: 404 }, | ||
); | ||
|
||
const receipt = await db.receipt.findFirst({ | ||
where: { | ||
razorpayOrderId: razorpay_order_id, | ||
}, | ||
}); | ||
|
||
if (!receipt) | ||
return NextResponse.json({ error: 'Receipt not found' }, { status: 404 }); | ||
|
||
await db.purchase.create({ | ||
data: { | ||
receiptId: receipt.id, | ||
razorpay_order_id, | ||
razorpay_payment_id, | ||
razorpay_signature, | ||
paymentVerified: isPaymentValid, | ||
purchasedById: userId, | ||
purchasedCourseId: courseId, | ||
}, | ||
}); | ||
|
||
await db.userPurchases.create({ | ||
data: { | ||
userId, | ||
courseId, | ||
}, | ||
}); | ||
|
||
return NextResponse.json({ message: 'Purchase Successful' }, { status: 200 }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { RazorPayComponent } from '@/components/razorpay/Razorpay'; | ||
import { Button } from '@/components/ui/button'; | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardFooter, | ||
CardHeader, | ||
CardTitle, | ||
} from '@/components/ui/card'; | ||
import Image from 'next/image'; | ||
import db from '@/db'; | ||
import { authOptions } from '@/lib/auth'; | ||
import { getServerSession } from 'next-auth'; | ||
import { redirect } from 'next/navigation'; | ||
|
||
export default async function CourseDetails({ | ||
params, | ||
}: { | ||
params: { courseId: string[] }; | ||
}) { | ||
const session = await getServerSession(authOptions); | ||
if (!session?.user) { | ||
redirect('/signin'); | ||
} | ||
const course = await db.course.findFirst({ | ||
where: { | ||
id: Number(params.courseId), | ||
}, | ||
}); | ||
if (!course) return <div>Course does not exist</div>; | ||
|
||
const ifPurchasedByUser = | ||
(await db.userPurchases.count({ | ||
where: { | ||
userId: session.user.id, | ||
courseId: course.id, | ||
}, | ||
})) > 0 | ||
? true | ||
: false; | ||
|
||
return ( | ||
<Card className="w-500" key={course.id}> | ||
<CardContent className="flex justify-center"> | ||
<Image src={'/harkirat.png'} alt="" width={200} height={100} /> | ||
</CardContent> | ||
<CardHeader> | ||
<CardTitle>{course.title}</CardTitle> | ||
<CardDescription>{course.description}</CardDescription> | ||
</CardHeader> | ||
<CardFooter className="flex justify-between"> | ||
<Button variant="outline">INR {course.price}</Button> | ||
{ifPurchasedByUser ? ( | ||
<button>View Receipt</button> | ||
) : ( | ||
<RazorPayComponent | ||
userId={session.user.id} | ||
courseId={course.id} | ||
key={course.id} | ||
/> | ||
)} | ||
</CardFooter> | ||
</Card> | ||
); | ||
} |
Oops, something went wrong.