- No data uploaded
- Instant results
Bootstrap 5 Razorpay Checkout Form for React
A production-ready minimal razorpay checkout form: a two-column checkout with an order summary (subtotal, GST, total in INR), contact fields, a UPI / card / netbanking selector and a pay button that opens Razorpay Checkout. Pick your brand color, toggle dark mode and corner radius — the live preview and the React code update instantly in your browser.
Live component editor
import 'bootstrap/dist/css/bootstrap.min.css';
import './theme.css';
import { useState } from 'react';
// Razorpay Standard Checkout — https://razorpay.com/docs/payments/payment-gateway/web-integration/standard/
const RAZORPAY_KEY_ID = 'rzp_test_YOUR_KEY_ID'; // public key only — the secret stays on your server
function loadRazorpay() {
if (window.Razorpay) return Promise.resolve();
return new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = 'https://checkout.razorpay.com/v1/checkout.js';
s.onload = resolve;
s.onerror = () => reject(new Error('Razorpay SDK failed to load'));
document.head.appendChild(s);
});
}
async function payWithRazorpay() {
await loadRazorpay();
// 1) Server creates the order (amount in paise) — never trust a client-side amount.
const order = await fetch('/api/razorpay/order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ plan: 'pro-annual' })
}).then((r) => r.json());
// 2) Open Checkout for that order.
const rzp = new window.Razorpay({
key: RAZORPAY_KEY_ID,
order_id: order.id,
amount: order.amount,
currency: order.currency,
name: 'Acme Inc.',
description: 'Pro plan — annual',
prefill: {
email: document.getElementById('rp-email')?.value,
contact: document.getElementById('rp-phone')?.value
},
theme: { color: '#4f46e5' },
// 3) Server verifies razorpay_signature before fulfilling.
handler: (res) =>
fetch('/api/razorpay/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(res) })
});
rzp.on('payment.failed', (res) => console.error(res.error));
rzp.open();
}
export default function RazorpayCheckout() {
const [method, setMethod] = useState("upi");
return (
<div className="w-100 mw-4xl row row-cols-1 row-cols-md-2 g-4">
<div className="col">
<div className="h-100 p-4 bg-body border shadow-sm rounded-3">
<h2 className="fs-5 fw-semibold text-body-emphasis">Order summary</h2>
<div className="mt-3 d-flex align-items-start justify-content-between gap-3">
<div>
<p className="fw-medium text-body-emphasis">Pro plan · annual</p>
<p className="small text-body-secondary">Billed once, renews yearly</p>
</div>
<p className="fw-medium text-body-emphasis">₹8,474</p>
</div>
<div className="mt-4 pt-3 d-flex flex-column gap-2 small border-top">
<div className="d-flex justify-content-between text-body-secondary"><span>Subtotal</span><span>₹8,474</span></div>
<div className="d-flex justify-content-between text-body-secondary"><span>GST (18%)</span><span>₹1,525</span></div>
<div className="d-flex justify-content-between fw-semibold text-body-emphasis"><span>Total</span><span>₹9,999</span></div>
</div>
<p className="mt-4 small text-body-secondary">Secured by Razorpay · PCI DSS compliant</p>
</div>
</div>
<div className="col">
<form className="h-100 p-4 d-flex flex-column gap-3 bg-body border shadow-sm rounded-3">
<h2 className="fs-5 fw-semibold text-body-emphasis">Payment details</h2>
<div>
<label htmlFor="rp-email" className="form-label small fw-medium">Email</label>
<input id="rp-email" type="email" autoComplete="email" placeholder="you@company.com" className="form-control rounded-2" required />
</div>
<div>
<label htmlFor="rp-phone" className="form-label small fw-medium">Mobile number</label>
<input id="rp-phone" type="tel" inputmode="tel" autoComplete="tel" placeholder="+91 98765 43210" className="form-control rounded-2" required />
</div>
<div>
<p className="form-label small fw-medium">Payment method</p>
<div className="d-flex flex-wrap gap-2" role="group" aria-label="Payment method">
<button type="button" className="btn btn-outline-secondary choice fw-semibold rounded-2" onClick={() => setMethod('upi')} aria-pressed={method === 'upi'}>UPI</button>
<button type="button" className="btn btn-outline-secondary choice fw-semibold rounded-2" onClick={() => setMethod('card')} aria-pressed={method === 'card'}>Card</button>
<button type="button" className="btn btn-outline-secondary choice fw-semibold rounded-2" onClick={() => setMethod('netbanking')} aria-pressed={method === 'netbanking'}>Netbanking</button>
</div>
<p className="mt-2 small text-body-secondary" hidden={method !== 'upi'}>Pay with any UPI app — GPay, PhonePe, Paytm or BHIM.</p>
<p className="mt-2 small text-body-secondary" hidden={method !== 'card'}>Visa, Mastercard, RuPay and Amex credit or debit cards.</p>
<p className="mt-2 small text-body-secondary" hidden={method !== 'netbanking'}>All major Indian banks supported.</p>
</div>
<button type="button" className="btn btn-brand fw-semibold rounded-2 w-100" onClick={(e) => payWithRazorpay(e)}>Pay ₹9,999</button>
</form>
</div>
</div>
);
}
/* theme.css — brand color #4f46e5 for Bootstrap 5.3 */
:root { --brand: #4f46e5; --brand-hover: #433cc3; --brand-rgb: 79, 70, 229; --brand-contrast: #ffffff; }
.btn-brand {
--bs-btn-color: var(--brand-contrast); --bs-btn-bg: var(--brand); --bs-btn-border-color: var(--brand);
--bs-btn-hover-color: var(--brand-contrast); --bs-btn-hover-bg: var(--brand-hover); --bs-btn-hover-border-color: var(--brand-hover);
--bs-btn-active-color: var(--brand-contrast); --bs-btn-active-bg: var(--brand-hover); --bs-btn-active-border-color: var(--brand-hover);
--bs-btn-focus-shadow-rgb: var(--brand-rgb);
}
.text-brand { color: var(--brand) !important; }
.border-brand { border: 2px solid var(--brand) !important; }
.bg-brand-subtle { background-color: rgba(var(--brand-rgb), .12) !important; }
.form-control:focus, .form-check-input:focus { border-color: var(--brand); box-shadow: 0 0 0 .25rem rgba(var(--brand-rgb), .25); }
.form-check-input:checked { background-color: var(--brand); border-color: var(--brand); }
.overlay { background: rgba(15, 23, 42, .5); backdrop-filter: blur(4px); z-index: 1050; }
.choice[aria-pressed="true"] { border-color: var(--brand) !important; color: var(--brand) !important; box-shadow: 0 0 0 .2rem rgba(var(--brand-rgb), .2); }
.mw-sm { max-width: 24rem; } .mw-md { max-width: 28rem; } .mw-lg { max-width: 32rem; } .mw-xl { max-width: 36rem; }
.mw-2xl { max-width: 42rem; } .mw-4xl { max-width: 56rem; } .mw-6xl { max-width: 72rem; }
More Bootstrap components for React
All components →- Currency Pricing Table A dynamic pricing section that detects the visitor’s currency, shows purchasing-power-adjusted regional prices and lets users switch between USD, EUR, GBP, INR and JPY
- Pricing Table A three-tier pricing section with a monthly/yearly billing toggle, a highlighted “most popular” plan and feature checklists
- Signup Form A registration card with name, email and password fields, a password hint, a terms-of-service checkbox and a primary call to action
- Cookie Consent Banner A GDPR-style cookie banner with equally prominent “Accept all” and “Reject all”, granular analytics/marketing toggles (off by default), stored consent and Google Consent Mode v2 signals
- FAQ Accordion A frequently-asked-questions section with four expandable items, each toggled by a button that exposes its state via aria-expanded
- Login Modal A centered sign-in dialog with email and password fields, “remember me”, a forgot-password link and a social sign-in button
- Navbar A responsive top navigation bar with a logo, primary links, a sign-in/CTA pair on desktop and a collapsible hamburger menu on mobile
- Hero Section A landing-page hero with an announcement badge, a large headline, supporting copy, primary and secondary calls to action and a row of social-proof stats
Install Bootstrap 5 for React
Create a React project with Vite and install Bootstrap:
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm install bootstrapSave the generated theme.css next to the component; the component file already imports bootstrap/dist/css/bootstrap.min.css and ./theme.css. No Bootstrap JavaScript is required — interactivity uses useState.
Use the razorpay checkout form in React
// App.jsx
import RazorpayCheckout from './RazorpayCheckout';
export default function App() {
return <RazorpayCheckout />;
}Connecting the form to Razorpay
- Create an order on your server with the Orders API using your
key_secretand return{ id, amount, currency }from/api/razorpay/order. - Replace
rzp_test_YOUR_KEY_IDwith your public key_id (test keys first). - In
/api/razorpay/verify, computeHMAC_SHA256(order_id + "|" + payment_id, key_secret)and compare it withrazorpay_signaturebefore marking the order paid. Also handle thepayment.capturedwebhook as the source of truth.
The preview never contacts Razorpay — clicking “Pay” there only shows where checkout would open.
Accessibility & customization
Payment methods are toggle buttons with aria-pressed, totals are real text (not images), the phone field uses inputmode="tel" and autocomplete tokens, and the pay button states the exact amount.
Every color, radius and dark-mode choice is baked into plain Bootstrap classes plus a tiny CSS-variable theme file, so the copied code has no runtime dependency on this site.
Compatibility
| Item | Details |
|---|---|
| CSS framework | Bootstrap 5.3+ (CSS only) |
| Library | React 18+ (function component) |
| Interactive state | method |
| Dark mode | Yes — toggle in the editor |
| Style | Minimal |