Skip to content

Commit

Permalink
Merge pull request #52 from kalyan-2005/stripe
Browse files Browse the repository at this point in the history
payment-gateway
  • Loading branch information
kalyan-2005 authored Mar 31, 2024
2 parents 32ebb77 + a8f96b4 commit 621def6
Show file tree
Hide file tree
Showing 22 changed files with 476 additions and 99 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ swe-worker-*
public/sw.*
public/workbox-*
public/swe-worker-*
public/sw.js
public/sw.js
package-lock.json
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"next": "14.0.4",
"next-auth": "^4.24.5",
"openai": "^4.29.2",
"razorpay": "^2.9.2",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.49.2",
Expand All @@ -31,6 +32,7 @@
"react-loading-skeleton": "^3.4.0",
"react-pdf": "^7.7.1",
"react-share": "^5.1.0",
"shortid": "^2.2.16",
"tailwind-scrollbar-hide": "^1.1.7",
"zod": "^3.22.4",
"zustand": "^4.4.7"
Expand Down
2 changes: 1 addition & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ model Friend {
@@unique([friendUserId, userId])
}

model Message {
model Message {
id String @id @default(auto()) @map("_id") @db.ObjectId
message String
Expand Down
Binary file added public/advanced.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/basic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/coin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/coins.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/popular.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/premium.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions src/app/api/(payment)/razorpay/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Razorpay from 'razorpay';
import { NextRequest, NextResponse } from 'next/server';

const razorpay = new Razorpay({
key_id: process.env.RAZORPAY_ID_KEY!,
key_secret: process.env.RAZORPAY_SECRET_KEY,
});

export async function POST(request: NextRequest) {
const { amount, currency } = (await request.json()) as {
amount: string;
currency: string;
};

var options = {
amount: amount,
currency: currency,
receipt: 'rcp1',
};
const order = await razorpay.orders.create(options);
console.log(order);
return NextResponse.json({ orderId: order.id }, { status: 200 });
}
37 changes: 37 additions & 0 deletions src/app/api/(payment)/verify/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';

const generatedSignature = (
razorpayOrderId: string,
razorpayPaymentId: string
) => {
const keySecret = process.env.RAZORPAY_SECRET_KEY;
if (!keySecret) {
throw new Error(
'Razorpay key secret is not defined in environment variables.'
);
}
const sig = crypto
.createHmac('sha256', keySecret)
.update(razorpayOrderId + '|' + razorpayPaymentId)
.digest('hex');
return sig;
};


export async function POST(request: NextRequest) {
const { orderCreationId, razorpayPaymentId, razorpaySignature } =
await request.json();

const signature = generatedSignature(orderCreationId, razorpayPaymentId);
if (signature !== razorpaySignature) {
return NextResponse.json(
{ message: 'payment verification failed', isOk: false },
{ status: 400 }
);
}
return NextResponse.json(
{ message: 'payment verified successfully', isOk: true },
{ status: 200 }
);
}
7 changes: 7 additions & 0 deletions src/app/api/(premium)/get-tokens/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import getCurrentUser from "@/actions/getCurrentUser";
import { NextResponse } from "next/server";

export async function GET() {
const currentUser = await getCurrentUser();
return NextResponse.json({token:currentUser?.tokens});
}
20 changes: 20 additions & 0 deletions src/app/api/(premium)/set-tokens/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import getCurrentUser from "@/actions/getCurrentUser";
import { db } from "@/libs/db";
import { NextResponse } from "next/server";

export async function POST(req:Request) {
const currentUser = await getCurrentUser();
// no.of tokens to be incremented
const token = await req.json();
const currtokens = currentUser?.tokens;
let total;
if(Number(token)===1) {
total = Number(currtokens)-1;
} else {
total = Number(token)+Number(currtokens);
}
// update in db
const updateToken = await db.user.update({where:{id:currentUser?.id},data:{tokens:total}})
// send response
return NextResponse.json({token:currentUser?.tokens});
}
213 changes: 213 additions & 0 deletions src/app/buy-tokens/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
"use client"
import Image from "next/image";
import { useEffect, useState } from "react";
import { FaRupeeSign } from "react-icons/fa";

import Script from 'next/script';

const Premium = () => {
const [mtokens, setMTokens] = useState(0);
const [tokens, setTokens] = useState(5);
let totalAmt = tokens * 5;
let amtWithDis = totalAmt - (tokens - 1) * 2;

// payment-gateway
const name = "kalyan";
const email = "[email protected]";
const [currency, setCurrency] = useState('INR');
const [amount, setAmount] = useState('5');
const createOrderId = async () => {
try {
const response = await fetch('/api/razorpay', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: parseFloat(amount) * 100,
}),
});

if (!response.ok) {
throw new Error('Network response was not ok');
}

const data = await response.json();
return data.orderId;
} catch (error) {
console.error('There was a problem with your fetch operation:', error);
}
};
const processPayment = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
const orderId: string = await createOrderId();
const options = {
key: process.env.key_id,
amount: parseFloat(amount) * 100,
currency: currency,
name: 'name',
description: 'description',
order_id: orderId,
handler: async function (response: any) {
const data = {
orderCreationId: orderId,
razorpayPaymentId: response.razorpay_payment_id,
razorpayOrderId: response.razorpay_order_id,
razorpaySignature: response.razorpay_signature,
};

const result = await fetch('/api/verify', {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});
const res = await result.json();
if (res.isOk) alert("payment succeed");
else {
alert(res.message);
}
},
prefill: {
name: name,
email: email,
},
theme: {
color: '#FEEBC8',
},
};
const paymentObject = new window.Razorpay(options);
paymentObject.on('payment.failed', function (response: any) {
alert(response.error.description);
});
paymentObject.open();
} catch (error) {
console.log(error);
}
};
const handleBasic = (e) => {
setMTokens(tokens);
setAmount((mtokens * 5 - (mtokens - 1) * 2).toString());
processPayment(e);
}
const handleBulk = (e) => {
setMTokens(20);
setAmount('50');
processPayment(e);
}
const handlePremium = (e) => {
setMTokens(-1);
setAmount('200');
processPayment(e);
}
return (
<>
<Script
id="razorpay-checkout-js"
src="https://checkout.razorpay.com/v1/checkout.js"
/>
<div className="flex gap-8 items-center m-8 mt-12">
<div className="w-1/3 rounded-lg flex flex-col justify-between border border-blue-600 h-[550px] bg-orange-50 p-5">
<div className="flex gap-2 items-center">
<Image src="/basic.png" alt="coin" width={40} height={40} />
<h1 className="text-2xl font-bold">Basic</h1>
</div>
<div className="flex gap-2 items-center">
<h1>Get a </h1>
<Image src="/coin.png" alt="coin" width={30} height={30} />
<h1 className="flex items-center">for &nbsp; <FaRupeeSign />5 each</h1>
</div>
<div className="text-md font-semibold">
<h1>Daily 3 tokens are additionally allocated</h1>
<h1>Custom quiz generation</h1>
<h1>Token purchase option</h1>
<h1>.</h1>
<h1>.</h1>
</div>
<div>
<input type="number" defaultValue={5} className="outline-none block m-auto rounded-xl w-20 text-center text-md px-4 py-2" onChange={(e) => setTokens(Number(e.target.value))} />
</div>
<div className="flex justify-center items-end gap-4">
{tokens &&
<div className="flex line-through items-center text-md">
<FaRupeeSign />
<h1>{totalAmt}</h1>
</div>
}
{tokens &&
<div className="flex items-center text-4xl text-blue-600 font-bold">
<FaRupeeSign />
<h1>{amtWithDis}</h1>
</div>
}
</div>
<div>
<button onClick={(e) => handleBasic(e)} className="w-full hover:font-semibold hover:bg-gradient-to-r from-yellow-200 to-orange-300 border border-black rounded text-center p-4">Proceed to Buy</button>
</div>
</div>
<div className="w-[40%] rounded-lg border border-blue-600 flex flex-col justify-between h-[600px] bg-orange-50 p-5">
<div className="flex justify-between items-center">
<div className="flex gap-2 items-center">
<Image src="/advanced.png" alt="coin" width={40} height={40} />
<h1 className="text-2xl font-bold">Advanced</h1>
</div>
<div className="flex items-center px-2 py-1 gap-2 rounded border border-black">
<Image src="/popular.png" alt="coin" width={20} height={10} />
<h1 className="">Most Popular</h1>
</div>
</div>
<div className="flex items-center justify-between">
<div className="text-md font-semibold">
<h1>Bulk token purchase</h1>
<h1>Custom quiz generation</h1>
<h1>.</h1>
<h1>.</h1>
<h1>.</h1>
</div>
<div className="">
<Image src="/coins.png" alt="coins" width={140} height={140} />
</div>
</div>
<div className="flex justify-around">
<div className="flex gap-2 items-end">
<h1 className="flex items-center text-4xl text-blue-600 font-bold">20</h1>
<h1 className="font-semibold text-xl">coins</h1>
</div>
<div className="flex gap-2 items-end">
<h1 className="flex items-center text-4xl text-blue-600 font-bold">50</h1>
<h1 className="font-semibold text-xl">Rupees</h1>
</div>
</div>
<div>
<button onClick={(e) => handleBulk(e)} className="hover:font-semibold hover:bg-gradient-to-r from-yellow-200 to-orange-300 w-full border border-black rounded text-center p-4">Proceed to Buy</button>
</div>
</div>
<div className="w-1/3 rounded-lg border flex flex-col border-blue-600 justify-between h-[550px] bg-orange-50 p-5">
<div className="flex gap-2 items-center">
<Image src="/premium.png" alt="coin" width={40} height={40} />
<h1 className="text-2xl font-bold">Premium</h1>
</div>
<div className="text-md font-semibold">
<h1>Unlimited quiz generation</h1>
<h1>No token limits</h1>
<h1>Value for money</h1>
<h1>.</h1>
<h1>.</h1>
</div>
<div className="flex items-end justify-center">
<div className="flex items-center text-4xl text-blue-600 font-bold">
<FaRupeeSign />
<h1>200</h1>
</div>
<h1 className="font-semibold text-xl">/monthly</h1>
</div>
<div>
<button onClick={(e) => handlePremium(e)} className="hover:font-semibold hover:bg-gradient-to-r from-yellow-200 to-orange-300 w-full border border-black rounded text-center p-4">Proceed to Buy</button>
</div>
</div>
</div>
</>
);
};

export default Premium;
Loading

0 comments on commit 621def6

Please sign in to comment.