Skip to content

Commit

Permalink
feat: added payment
Browse files Browse the repository at this point in the history
  • Loading branch information
Srish-ty committed Nov 3, 2024
1 parent 0a55c37 commit 4e287e0
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ NEXT_PUBLIC_FIREBASE_APP_ID= ""
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID= ""

NEXT_PUBLIC_APOLLO_URI=""

PHONEPE_MERCHANT_ID=your_merchant_id
PHONEPE_SALT_KEY=your_salt_key
PHONEPE_SECRET_KEY=your_secret_key
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@react-three/fiber": "^8.17.7",
"ajv": "^8.17.1",
"axios": "^1.7.7",
"crypto": "^1.0.1",
"firebase": "^10.14.1",
"flowbite-react": "^0.10.2",
"framer-motion": "^11.5.6",
Expand All @@ -55,8 +56,8 @@
"swiper": "^11.1.14",
"three": "^0.168.0",
"twin.macro": "^3.4.1",
"yarn": "^1.22.22",
"uuidv4": "^6.2.13",
"yarn": "^1.22.22",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
31 changes: 31 additions & 0 deletions src/app/callback/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/router';

const CallbackPage = () => {
const router = useRouter();
const { transactionId } = router.query;

useEffect(() => {
const checkPaymentStatus = async () => {
if (transactionId) {
const response = await fetch('/api/phonepePaymentStatus', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transactionId }),
});
const data = await response.json();
if (data.status === 'SUCCESS') {
alert('Payment successful!');
} else {
alert('Payment failed. Please try again.');
}
}
};
checkPaymentStatus();
}, [transactionId]);

return <div>Processing your payment...</div>;
};

export default CallbackPage;
44 changes: 44 additions & 0 deletions src/app/payment/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';
import React, { useEffect, useState } from 'react';
import PayButton from './payButton';
import { PayContainer } from './payment.styles';
import Cookies from 'js-cookie';

const PaymentPage = () => {
const [isClient, setIsClient] = useState(false);
const [userEmail, setUserEmail] = useState('');

useEffect(() => {
setIsClient(true);
const userData = Cookies.get('userData');
const email = (userData && JSON.parse(userData).email) || '';
setUserEmail(email);
}, []);

const validEmails = [
'innovision2024.nitr@gmail.com',
'srishtymangutte@gmail.com',
'jaiswal2nikhil@gmail.com',
];

if (!isClient) {
return null;
}

return (
<div>
<PayContainer>
{validEmails.includes(userEmail) ? (
<>
<h1>Payment Page</h1>
<PayButton />
</>
) : (
<h1>404 page not found</h1>
)}
</PayContainer>
</div>
);
};

export default PaymentPage;
29 changes: 29 additions & 0 deletions src/app/payment/payButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client';
import { useState } from 'react';
import { SecondaryButton } from '@/components/shared/Typography/Buttons';

const PayButton = () => {
const handlePayment = async () => {
const response = await fetch('/api/phonepePayment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: 100,
orderId: 'unique_order_id_12345',
callbackUrl: `${window.location.origin}/callback`,
}),
});

const data = await response.json();

if (data.success) {
window.location.href = data.paymentUrl;
} else {
alert('Payment failed. Please try again.');
}
};

return <button onClick={handlePayment}>Pay with PhonePe</button>;
};

export default PayButton;
7 changes: 7 additions & 0 deletions src/app/payment/payment.styles.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import tw from 'twin.macro';
import styled from 'styled-components';

export const PayContainer = styled.div`
background-image: url('https://res.cloudinary.com/dhv234qct/image/upload/v1728888341/Inno2k24/yupqoznoucyhxwchhbv7.png');
${tw`w-full flex flex-col items-center justify-center bg-cover pt-36 `}
`;
42 changes: 42 additions & 0 deletions src/pages/api/phonepePayment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import crypto from 'crypto';

export default async function handler(req, res) {
if (req.method === 'POST') {
const { amount, orderId, callbackUrl } = req.body;

const payload = {
merchantId: process.env.NEXT_PUBLIC_PHONEPE_MERCHANT_ID,
transactionId: orderId,
amount: amount,
merchantTransactionId: orderId,
redirectUrl: callbackUrl,
};

const payloadString = JSON.stringify(payload);
const base64Payload = Buffer.from(payloadString).toString('base64');

const checksum = crypto
.createHmac('sha256', process.env.NEXT_PUBLIC_PHONEPE_API_KEY)
.update(base64Payload + '/pg/v1/pay' + process.env.NEXT_PUBLIC_PHONEPE_API_KEY)
.digest('base64');

try {
const response = await fetch(`${process.env.NEXT_PUBLIC_PHONEPE_API_URL}/pg/v1/pay`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-VERIFY': `${checksum}###${process.env.NEXT_PUBLIC_PHONEPE_API_KEY_INDEX}`,
},
body: payloadString,
});

const data = await response.json();
res.status(200).json(data);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Payment initiation failed' });
}
} else {
res.status(405).json({ error: 'Method not allowed' });
}
}
31 changes: 31 additions & 0 deletions src/pages/api/phonepePaymentStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import crypto from 'crypto';

export default async function handler(req, res) {
if (req.method === 'POST') {
const { transactionId } = req.body;

const url = `${process.env.NEXT_PUBLIC_PHONEPE_API_URL}/status/${process.env.NEXT_PUBLIC_PHONEPE_MERCHANT_ID}/${transactionId}`;
const checksum = crypto
.createHmac('sha256', process.env.NEXT_PUBLIC_PHONEPE_API_KEY)
.update(`${url}${process.env.NEXT_PUBLIC_PHONEPE_API_KEY_INDEX}`)
.digest('base64');

try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-VERIFY': `${checksum}###${process.env.NEXT_PUBLIC_PHONEPE_API_KEY_INDEX}`,
},
});

const data = await response.json();
res.status(200).json(data);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to verify payment status' });
}
} else {
res.status(405).json({ error: 'Method not allowed' });
}
}

0 comments on commit 4e287e0

Please sign in to comment.