diff --git a/client/sample.env b/client/sample.env
index 7c6199a..47674be 100644
--- a/client/sample.env
+++ b/client/sample.env
@@ -1,3 +1,5 @@
VITE_CLERK_PUBLISHABLE_KEY=Enter_the_key
VITE_BACKEND_URL=your_backend_url
-VITE_CURRENCY=your_preferred_currency
\ No newline at end of file
+VITE_CURRENCY=your_preferred_currency
+
+VITE_APP_RZP_KEY_ID=your_razorpay_api_key
diff --git a/client/src/pages/MyBookings.jsx b/client/src/pages/MyBookings.jsx
index 995f08a..af3fb6c 100644
--- a/client/src/pages/MyBookings.jsx
+++ b/client/src/pages/MyBookings.jsx
@@ -1,8 +1,134 @@
import React, { useState } from 'react'
import Title from '../components/Title'
-import { assets, userBookingsDummyData } from '../assets/assets'
+import { assets, userBookingsDummyData, userDummyData } from '../assets/assets'
+import { loadRazorpayScript } from '../utils/rzpUitl'
+
const MyBookings = () => {
const [bookings, setBookings] = useState(userBookingsDummyData);
+ const [error, setError] = useState(null);
+
+ const handlePayment = async () => {
+ const isScriptLoaded = await loadRazorpayScript();
+ if (!isScriptLoaded) {
+ console.error({ type: 'error', message: 'Failed to load Razorpay SDK. Please check your connection.' });
+ return;
+ }
+
+ try {
+ // create the Razorpay booking on the server
+ const response = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/payment/create-booking`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ amount: userBookingsDummyData[1].totalPrice, // booking amount from dummy data
+ currency: 'INR',
+ receipt: `booking_${new Date().getTime()}`,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to create Razorpay order.');
+ }
+
+ const { booking } = await response.json();
+
+ // Now that we've the booking, initiate Razorpay payment
+ const razorpayOptions = {
+ key: import.meta.env.VITE_APP_RZP_KEY_ID,
+ amount: userBookingsDummyData[1].totalPrice * 100,
+ currency: 'INR',
+ name: 'QuickStay',
+ description: 'Booking Payment',
+ order_id: booking.id,
+ handler: function (response) {
+ // Verify the payment signature in backend
+ fetch(`${import.meta.env.VITE_BACKEND_URL}/api/payment/verify-payment`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ razorpay_order_id: response.razorpay_order_id,
+ razorpay_payment_id: response.razorpay_payment_id,
+ razorpay_signature: response.razorpay_signature,
+ }),
+ })
+ .then((res) => res.json())
+ .then((data) => {
+ if (data.success) {
+ // Payment is successful, submit the booking to the backend
+ //submitBooking();
+ alert('Payment Successful! Booking has been placed.');
+ } else {
+ alert('Payment verification failed.');
+ }
+ })
+ .catch((err) => {
+ alert('Error verifying payment: ' + err.message);
+ });
+ },
+ prefill: {
+ // User details can be fetched from user profile, currently hardcoded for demo
+ name: userDummyData?.username || 'Guest',
+ email: userDummyData?.email || 'customer@example.com',
+ contact: userDummyData?.contact || '1234567890',
+ },
+ theme: {
+ color: '#663cc79a',
+ },
+ };
+ console.log(razorpayOptions);
+
+ const razorpayInstance = new window.Razorpay(razorpayOptions);
+ razorpayInstance.open();
+ } catch (err) {
+ console.error('Error initiating Razorpay payment:', err);
+ setError('Payment failed. Please try again.');
+ }
+ };
+
+ // const submitBooking = async () => {
+ // const bookingData = {
+ // hotelId: userBookingsDummyData.hotelId,
+ // roomId: userBookingsDummyData.roomId,
+ // userId: userBookingsDummyData.userId,
+ // guests: userBookingsDummyData.guests,
+ // checkInDate: userBookingsDummyData.checkInDate,
+ // checkOutDate: userBookingsDummyData.checkOutDate,
+ // roomType: userBookingsDummyData.roomType,
+ // totalPrice: userBookingsDummyData.totalPrice,
+ // isPaid: true,
+ // paymentMethod: 'Razorpay',
+ // boookingId: userBookingsDummyData.bookingId,
+ // date: new Date().toLocaleString(),
+
+ // };
+
+ // try {
+ // const response = await fetch('${import.meta.env.VITE_BACKEND_URL}/api/bookings', {
+ // method: 'POST',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // },
+ // body: JSON.stringify(bookingData),
+ // });
+
+ // if (!response.ok) {
+ // throw new Error(`Failed to submit booking: ${response.statusText}`);
+ // }
+ // const result = await response.json();
+ // console.log('Booking submitted successfully:', result);
+
+ // } catch (err) {
+ // console.error('Error placing booking:', err);
+ // setError('Failed to place the booking. Please try again later.');
+ // }
+ // console.log('Booking Data:', bookingData);
+
+
+
return (
@@ -54,7 +180,7 @@ const MyBookings = () => {
{!booking.isPaid && (
-
+
)}
diff --git a/client/src/utils/rzpUitl.js b/client/src/utils/rzpUitl.js
new file mode 100644
index 0000000..befe397
--- /dev/null
+++ b/client/src/utils/rzpUitl.js
@@ -0,0 +1,16 @@
+export const loadRazorpayScript = () => {
+ return new Promise((resolve) => {
+ if (document.getElementById('razorpay-script')) {
+ resolve(true); // Script already loaded
+ return;
+ }
+
+ const script = document.createElement('script');
+ script.id = 'razorpay-script';
+ script.src = 'https://checkout.razorpay.com/v1/checkout.js';
+ script.onload = () => resolve(true);
+ script.onerror = () => resolve(false);
+ document.body.appendChild(script);
+ });
+ };
+
\ No newline at end of file
diff --git a/server/controllers/razorpayGateway.js b/server/controllers/razorpayGateway.js
new file mode 100644
index 0000000..dc62485
--- /dev/null
+++ b/server/controllers/razorpayGateway.js
@@ -0,0 +1,45 @@
+import crypto from 'crypto';
+import Razorpay from 'razorpay';
+
+const razorpayInstance = new Razorpay({
+ key_id: process.env.RZP_KEY_ID,
+ key_secret: process.env.RZR_KEY_SECRET,
+});
+
+// Create a booking
+export const createBooking = async (req, res) => {
+ try {
+ const { amount, currency = 'INR', receipt } = req.body;
+
+ const options = {
+ amount: amount * 100, // Amount in cents
+ currency,
+ receipt,
+ };
+ console.log(options);
+ const booking = await razorpayInstance.orders.create(options);
+ res.status(200).json({ success: true, booking });
+ } catch (error) {
+ res.status(500).json({ success: false, error: error.message });
+ }
+};
+
+// Verify the payment
+export const verifyPayment = (req, res) => {
+ try {
+ const { razorpay_order_id, razorpay_payment_id, razorpay_signature } = req.body;
+ const body = razorpay_order_id + "|" + razorpay_payment_id;
+ const expectedSignature = crypto
+ .createHmac('sha256', process.env.RZR_KEY_SECRET)
+ .update(body)
+ .digest('hex');
+
+ if (expectedSignature === razorpay_signature) {
+ res.status(200).json({ success: true, message: 'Payment verified successfully!' });
+ } else {
+ res.status(400).json({ success: false, message: 'Invalid signature' });
+ }
+ } catch (error) {
+ res.status(500).json({ success: false, error: error.message });
+ }
+};
diff --git a/server/package-lock.json b/server/package-lock.json
index df5ecf3..dfee705 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -12,10 +12,12 @@
"@clerk/express": "^1.7.2",
"cloudinary": "^2.7.0",
"cors": "^2.8.5",
+ "crypto": "^1.0.1",
"dotenv": "^16.6.0",
"express": "^5.1.0",
"mongoose": "^8.16.1",
"multer": "^2.0.1",
+ "razorpay": "^2.9.6",
"svix": "^1.68.0"
},
"devDependencies": {
@@ -179,6 +181,23 @@
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
"license": "MIT"
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+ "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -345,6 +364,18 @@
"node": ">=9"
}
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -419,6 +450,13 @@
"node": ">= 0.10"
}
},
+ "node_modules/crypto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
+ "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
+ "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
+ "license": "ISC"
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -442,6 +480,15 @@
}
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -541,6 +588,21 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
@@ -640,6 +702,63 @@
"node": ">= 0.8"
}
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data/node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/form-data/node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -772,6 +891,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -1383,6 +1517,12 @@
"node": ">= 0.10"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
@@ -1455,6 +1595,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/razorpay": {
+ "version": "2.9.6",
+ "resolved": "https://registry.npmjs.org/razorpay/-/razorpay-2.9.6.tgz",
+ "integrity": "sha512-zsHAQzd6e1Cc6BNoCNZQaf65ElL6O6yw0wulxmoG5VQDr363fZC90Mp1V5EktVzG45yPyNomNXWlf4cQ3622gQ==",
+ "license": "MIT",
+ "dependencies": {
+ "axios": "^1.6.8"
+ }
+ },
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
diff --git a/server/package.json b/server/package.json
index 697af2b..b87bbd0 100644
--- a/server/package.json
+++ b/server/package.json
@@ -15,10 +15,12 @@
"@clerk/express": "^1.7.2",
"cloudinary": "^2.7.0",
"cors": "^2.8.5",
+ "crypto": "^1.0.1",
"dotenv": "^16.6.0",
"express": "^5.1.0",
"mongoose": "^8.16.1",
"multer": "^2.0.1",
+ "razorpay": "^2.9.6",
"svix": "^1.68.0"
},
"devDependencies": {
diff --git a/server/sample.env b/server/sample.env
index 2af365c..9059be8 100644
--- a/server/sample.env
+++ b/server/sample.env
@@ -4,4 +4,8 @@ MONGODB_URI = database_uri
#Clerk Keys
CLERK_PUBLISHABLE_KEY=your_publishable_key
CLERK_SECRET_KEY=your_secret_key
-CLERK_WEBHOOK_SECRET=your_webhook_key
\ No newline at end of file
+CLERK_WEBHOOK_SECRET=your_webhook_key
+
+#Razorpay Keys
+RZP_KEY_ID=your_razorpay_api_key
+RZR_KEY_SECRET=your_razorpay_secret
\ No newline at end of file
diff --git a/server/server.js b/server/server.js
index 6a10a2b..d638f37 100644
--- a/server/server.js
+++ b/server/server.js
@@ -4,6 +4,8 @@ import cors from "cors";
import connectDB from "./configs/db.js";
import { clerkMiddleware } from '@clerk/express'
import clerkWebhooks from "./controllers/clerkWebhooks.js";
+import { createBooking, verifyPayment } from './controllers/razorpayGateway.js';
+
connectDB();
const app = express();
@@ -18,6 +20,10 @@ app.use(clerkMiddleware());
app.use('/api/clerk', clerkWebhooks);
+//API to handle razorpay Payments
+app.use('/api/payment/create-booking', createBooking);
+app.use('/api/payment/verify-payment', verifyPayment);
+
app.get('/', (req, res) => {
res.send("API is Up and running");
});