DEV Community

Cover image for How I Added Razorpay Payments to My React Fitness Website
Aman singh
Aman singh

Posted on

How I Added Razorpay Payments to My React Fitness Website

Building a fitness website is not only about workouts, animations, and a good-looking UI.

If you want to turn a fitness platform into a real product, you also need a way for users to purchase memberships.

While building my React-based fitness website, I decided to integrate Razorpay so users could join different membership plans directly from the website.

In this article, I'll show how I implemented the payment flow, connected React with a Node.js backend, created Razorpay orders securely, and verified payments on the server.

The project uses:

  • React + Vite
  • Node.js + Express
  • Razorpay
  • Supabase
  • Vercel

The goal was to create a simple flow:

Choose Plan → Create Order → Open Razorpay Checkout → Complete Payment → Verify Payment

1. Setting Up the Membership Plans

For the fitness website, I created three simple membership plans:

Plan Price
Basic ₹99
Premium ₹149
Elite ₹199

Each plan has its own Join Now button.

When a user clicks the button, the frontend sends the selected plan to the backend instead of directly trusting the price from the browser.

This is important because payment amounts should be controlled on the server side.

For example, my backend keeps the plan prices like this:

const MEMBERSHIP_PLANS = {
  basic: {
    name: "Basic",
    amount: 9900
  },
  premium: {
    name: "Premium",
    amount: 14900
  },
  elite: {
    name: "Elite",
    amount: 19900
  }
};

## 2. Creating a Razorpay Order on the Backend

I did not create the Razorpay order directly from the React frontend.

Instead, the frontend sends the selected plan to my Node.js + Express backend.

The backend then creates the Razorpay order using the server-side amount configured for that plan.

The basic flow looks like this:

Enter fullscreen mode Exit fullscreen mode


text
React Frontend

Select Membership

POST /api/create-order

Node.js + Express

Razorpay API

Order ID

Razorpay Checkout

app.post("/api/create-order", async (req, res) => {
try {
const { planId } = req.body;

const plan = MEMBERSHIP_PLANS[planId];

if (!plan) {
  return res.status(400).json({
    error: "Invalid membership plan"
  });
}

const razorpay = getRazorpayClient();

const order = await razorpay.orders.create({
  amount: plan.amount,
  currency: "INR",
  receipt: `receipt_${Date.now()}`
});

res.json({
  orderId: order.id,
  amount: order.amount,
  currency: order.currency,
  planName: plan.name
});
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.error(error);
res.status(500).json({
error: "Unable to create order"
});
}
});

3. Opening Razorpay Checkout from React

Once the backend creates the order, it returns the Razorpay orderId to the React frontend.

The frontend then uses the Razorpay Checkout SDK to open the payment window.

The flow is:

User clicks "Join Now"
        ↓
React calls /api/create-order
        ↓
Backend creates Razorpay Order
        ↓
Frontend receives Order ID
        ↓
Razorpay Checkout opens
        ↓
User selects payment method
        ↓
Payment is completed

const handlePayment = async (planId) => {
  const response = await fetch("/api/create-order", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ planId })
  });

  const order = await response.json();

  const options = {
    key: import.meta.env.VITE_RAZORPAY_KEY_ID,
    amount: order.amount,
    currency: "INR",
    name: "Official ASForge",
    description: `${order.planName} Membership`,
    order_id: order.orderId,

    handler: async (paymentResponse) => {
      // Payment verification happens here
    }
  };

  const razorpay = new window.Razorpay(options);
  razorpay.open();
};

## 4. Verifying the Payment Securely

Opening the Razorpay Checkout is only one part of the payment process.

After a successful payment, Razorpay returns payment details such as:

- `razorpay_payment_id`
- `razorpay_order_id`
- `razorpay_signature`

I send these values back to my backend for verification.

The backend generates an HMAC SHA256 signature using the Razorpay Key Secret and compares it with the signature received from Razorpay.

A simplified implementation looks like this:

Enter fullscreen mode Exit fullscreen mode


js
app.post("/api/verify-payment", (req, res) => {
try {
const {
razorpay_order_id,
razorpay_payment_id,
razorpay_signature
} = req.body;

const generatedSignature = crypto
  .createHmac("sha256", process.env.RAZORPAY_KEY_SECRET)
  .update(
    `${razorpay_order_id}|${razorpay_payment_id}`
  )
  .digest("hex");

const isValid =
  generatedSignature === razorpay_signature;

if (!isValid) {
  return res.status(400).json({
    success: false,
    error: "Invalid payment signature"
  });
}

res.json({
  success: true,
  message: "Payment verified successfully"
});
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.error(error);

res.status(500).json({
  success: false,
  error: "Payment verification failed"
});
Enter fullscreen mode Exit fullscreen mode

}
});


Step 8 — Keeping Secrets Safe

## 5. Keeping Razorpay Secrets Safe

One of the most important parts of the integration was keeping the Razorpay credentials secure.

I used environment variables for the backend credentials:

Enter fullscreen mode Exit fullscreen mode


env
RAZORPAY_KEY_ID=your_key_id
RAZORPAY_KEY_SECRET=your_secret
VITE_RAZORPAY_KEY_ID=your_key_id


Step 9 — Complete Payment Flow

## 6. The Complete Payment Flow

After putting everything together, the complete membership flow looks like this:

Enter fullscreen mode Exit fullscreen mode


text
User

Selects a Plan

Clicks "Join Now"

React Frontend

POST /api/create-order

Express Backend

Razorpay API

Order ID

Razorpay Checkout

User Pays

Payment Details

POST /api/verify-payment

Server-Side Verification

Payment Success


Step 10 — Supabase

## 7. Connecting Payments with Supabase

After getting the Razorpay payment flow working, I also connected Supabase to the project.

Supabase can be used to store application data such as:

- User information
- Memberships
- Payment records
- Workout plans
- Diet plans
- Progress
- AI usage
- Website events

For example, after a verified payment, a payment record can be stored with information such as:

Enter fullscreen mode Exit fullscreen mode


text
user_id
plan
amount
razorpay_order_id
razorpay_payment_id
payment_status
created_at


Step 11 — Vercel Deployment

## 8. Deploying the Payment System

My frontend is built with Vite and deployed using Vercel.

During local development, the React application communicates with my local Express server.

For production, the payment endpoints need to be available from the deployed application as well.

The production architecture can look like:

Enter fullscreen mode Exit fullscreen mode


text
React + Vite

Vercel

/api/create-order
/api/verify-payment

Razorpay


Step 12 — Problems I Faced

## 9. Problems I Faced During Integration

While integrating Razorpay, I ran into a few common issues.

### Duplicate function declarations

While modifying the React component, I accidentally declared the same `handlePayment` function more than once.

This resulted in an error similar to:

Enter fullscreen mode Exit fullscreen mode


text
Identifier 'handlePayment' has already been declared

11. What I Learned

This project taught me that adding payments to a website is more than simply opening a checkout window.

The important concepts I learned were:

  1. Never trust payment amounts coming directly from the frontend.
  2. Create orders on the backend.
  3. Keep secret keys on the server.
  4. Verify Razorpay signatures server-side.
  5. Use environment variables for sensitive credentials.
  6. Separate development and production API configurations.
  7. Store payment records only after successful verification.
  8. Think about security while designing the payment flow, not after finishing the UI.

What's Next?

In the next part, I'll explore how I built a multilingual AI fitness coach that can interact with users in multiple languages using Gemini.

Thanks for reading! 💪

If you enjoyed the article, feel free to follow me for more posts about:

  • React
  • AI
  • Three.js
  • Full-stack development
  • Fitness technology

Build. Train. Improve.

— Aman Singh

Top comments (0)