Skip to content

Commit 3d42f59

Browse files
committed
feat: integrate Razorpay payment gateway for web and mobile
1 parent 83a7ec5 commit 3d42f59

File tree

11 files changed

+347
-13
lines changed

11 files changed

+347
-13
lines changed

backend/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ PORT=5001
22
MONGO_URI=your_mongodb_atlas_uri_here
33
JWT_SECRET=your_super_secret_key_here
44
NODE_ENV=development
5+
RAZORPAY_KEY_ID=rzp_test_xxxxxxxxxxxxxx
6+
RAZORPAY_KEY_SECRET=your_key_secret_here

backend/package-lock.json

Lines changed: 145 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"helmet": "^8.1.0",
2929
"jsonwebtoken": "^9.0.3",
3030
"mongoose": "^9.3.3",
31-
"morgan": "^1.10.1"
31+
"morgan": "^1.10.1",
32+
"razorpay": "^2.9.6"
3233
},
3334
"devDependencies": {
3435
"nodemon": "^3.1.14"

backend/routes/paymentRoutes.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const express = require('express');
2+
const router = express.Router();
3+
const Razorpay = require('razorpay');
4+
const crypto = require('crypto');
5+
6+
const razorpay = new Razorpay({
7+
key_id: process.env.RAZORPAY_KEY_ID,
8+
key_secret: process.env.RAZORPAY_KEY_SECRET,
9+
});
10+
11+
router.post('/create-order', async (req, res) => {
12+
try {
13+
const { amount } = req.body;
14+
const order = await razorpay.orders.create({
15+
amount: amount * 100,
16+
currency: 'INR',
17+
receipt: 'receipt_' + Date.now(),
18+
});
19+
res.json({ orderId: order.id, amount: order.amount, currency: order.currency });
20+
} catch (err) {
21+
res.status(500).json({ message: 'Failed to create order', error: err.message });
22+
}
23+
});
24+
25+
router.post('/verify', (req, res) => {
26+
const { razorpay_order_id, razorpay_payment_id, razorpay_signature } = req.body;
27+
const body = razorpay_order_id + '|' + razorpay_payment_id;
28+
const expectedSignature = crypto
29+
.createHmac('sha256', process.env.RAZORPAY_KEY_SECRET)
30+
.update(body)
31+
.digest('hex');
32+
33+
if (expectedSignature === razorpay_signature) {
34+
res.json({ success: true, paymentId: razorpay_payment_id });
35+
} else {
36+
res.status(400).json({ success: false, message: 'Invalid signature' });
37+
}
38+
});
39+
40+
module.exports = router;

backend/server.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const morgan = require('morgan');
66
const connectDB = require('./config/db');
77
const userRoutes = require('./routes/userRoutes');
88
const productRoutes = require('./routes/productRoutes');
9+
const paymentRoutes = require('./routes/paymentRoutes');
910

1011
dotenv.config();
1112

@@ -23,6 +24,7 @@ if (process.env.NODE_ENV === 'development') {
2324

2425
app.use('/api/users', userRoutes);
2526
app.use('/api/products', productRoutes);
27+
app.use('/api/payment', paymentRoutes);
2628

2729
app.get('/', (req, res) => {
2830
res.send("API is running...");

frontend/src/components/Checkout.jsx

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,67 @@ const Checkout = ({ cartItems, onClose, onOrderPlaced }) => {
1414
const gst = Math.round(cartTotal * 0.05);
1515
const shipping = cartTotal > 999 ? 0 : 99;
1616

17-
const handlePlaceOrder = () => {
18-
if (onOrderPlaced) onOrderPlaced(cartItems);
19-
setOrderPlaced(true);
17+
const loadRazorpayScript = () =>
18+
new Promise((resolve) => {
19+
if (document.getElementById('razorpay-script')) return resolve(true);
20+
const script = document.createElement('script');
21+
script.id = 'razorpay-script';
22+
script.src = 'https://checkout.razorpay.com/v1/checkout.js';
23+
script.onload = () => resolve(true);
24+
script.onerror = () => resolve(false);
25+
document.body.appendChild(script);
26+
});
27+
28+
const handlePlaceOrder = async () => {
29+
if (paymentMethod === 'cod') {
30+
if (onOrderPlaced) onOrderPlaced(cartItems);
31+
setOrderPlaced(true);
32+
return;
33+
}
34+
35+
const totalAmount = cartTotal + shipping;
36+
const loaded = await loadRazorpayScript();
37+
if (!loaded) { alert('Razorpay failed to load. Check your internet.'); return; }
38+
39+
try {
40+
const res = await fetch(`${import.meta.env.VITE_API_URL}/api/payment/create-order`, {
41+
method: 'POST',
42+
headers: { 'Content-Type': 'application/json' },
43+
body: JSON.stringify({ amount: totalAmount }),
44+
});
45+
const data = await res.json();
46+
47+
const options = {
48+
key: import.meta.env.VITE_RAZORPAY_KEY_ID,
49+
amount: data.amount,
50+
currency: data.currency,
51+
name: 'Zappify',
52+
description: 'Shoe Purchase',
53+
order_id: data.orderId,
54+
handler: async (response) => {
55+
const verifyRes = await fetch(`${import.meta.env.VITE_API_URL}/api/payment/verify`, {
56+
method: 'POST',
57+
headers: { 'Content-Type': 'application/json' },
58+
body: JSON.stringify(response),
59+
});
60+
const verifyData = await verifyRes.json();
61+
if (verifyData.success) {
62+
if (onOrderPlaced) onOrderPlaced(cartItems);
63+
setOrderPlaced(true);
64+
} else {
65+
alert('Payment verification failed. Contact support.');
66+
}
67+
},
68+
prefill: { name: address.name, contact: address.phone },
69+
theme: { color: '#FF3D00' },
70+
};
71+
72+
const rzp = new window.Razorpay(options);
73+
rzp.open();
74+
} catch (err) {
75+
console.error('Razorpay error:', err);
76+
alert(`Error: ${err.message || 'Something went wrong. Is backend running on port 5001?'}`);
77+
}
2078
};
2179

2280
if (orderPlaced) {

frontend/src/data/products.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export const ALL_PRODUCTS = [
2-
// ── WOMEN / UNISEX (existing, no gender = shown in ALL) ──
32
{
43
id: 105,
54
name: "Nike Journey Run",
@@ -201,7 +200,6 @@ export const ALL_PRODUCTS = [
201200
"Boasting Nike's tallest heel bag yet, these sneakers offer a super-soft ride that feels as good as it looks.",
202201
},
203202

204-
// ── MEN ──
205203
{
206204
id: 201,
207205
name: "Men's Street Runner Pro",

0 commit comments

Comments
 (0)