Drop-in React components and hooks for Razorpay subscription paywalls. Built for Indian SaaS apps.
Three exports. Zero runtime dependencies. Works with any React app.
Use react-premium-gate when:
- You're building a React app that monetizes via Razorpay subscriptions (monthly/yearly plans)
- You need to gate features or UI behind a Pro plan without writing the plumbing from scratch
- You're deploying on Vercel and want ready-made serverless API routes for payment flows
- You need server-side subscription verification, not just client-side localStorage checks
Not the right fit if you're using Stripe, Paddle, or Lemon Squeezy — this library is Razorpay-specific by design.
A complete Razorpay subscription flow in React requires five moving parts: create the subscription server-side, open the Razorpay SDK, verify the HMAC-SHA256 payment signature, persist subscription state, and re-verify against Razorpay's API on each session load.
react-premium-gate ships all five — as a hook, two components, and three copy-paste Vercel API templates. Add the package, paste the templates, configure env vars, and your paywall is live in under an hour.
| Export | Type | What it does |
|---|---|---|
<PremiumGate> |
Component | Blocks UI behind a paywall with a customizable CTA |
<PricingModal> |
Component | Bottom-sheet pricing modal with plan toggle |
usePro |
Hook | Manages Pro status via localStorage + server verification |
Plus Vercel API templates in /templates/api/ for Razorpay subscription creation and HMAC payment verification.
npm install react-premium-gateimport { usePro } from 'react-premium-gate'
function App() {
const { isPro, loading, activatePro } = usePro({
storageKey: 'my_app_sub', // localStorage key
verifyEndpoint: '/api/verify-subscription', // optional server check
})
if (loading) return null
return isPro ? <ProContent /> : <FreeContent />
}import { PremiumGate } from 'react-premium-gate'
<PremiumGate
onUpgrade={() => setShowModal(true)}
title="Pro Feature"
description="Unlock this with a Pro subscription."
buttonText="Upgrade to Pro — ₹399/mo"
icon="👑"
accentColor="#F0B429"
/>Props
| Prop | Type | Default |
|---|---|---|
onUpgrade |
function |
required |
title |
string |
'Pro Feature' |
description |
string |
'Upgrade to Pro to unlock this feature.' |
buttonText |
string |
'Upgrade to Pro' |
icon |
string |
'👑' |
accentColor |
string |
'#F0B429' |
style |
object |
{} |
import { PricingModal } from 'react-premium-gate'
const PLANS = [
{ id: 'monthly', label: 'Monthly', price: '₹399', period: '/mo' },
{ id: 'yearly', label: 'Yearly', price: '₹2,999', period: '/yr', badge: 'Save 37%' },
]
<PricingModal
isOpen={showModal}
onClose={() => setShowModal(false)}
plans={PLANS}
onSelectPlan={(planId) => handlePayment(planId)}
accentColor="#F0B429"
loading={paymentLoading}
/>Props
| Prop | Type | Default |
|---|---|---|
isOpen |
boolean |
required |
onClose |
function |
required |
plans |
array |
[] |
onSelectPlan |
function |
required |
title |
string |
'Upgrade to Pro' |
subtitle |
string |
'Less than a coffee. Way more value.' |
icon |
string |
'👑' |
accentColor |
string |
'#F0B429' |
fineText |
string |
'Secure payment · Cancel anytime' |
loading |
boolean |
false |
Copy the files from /templates/api/ into your Vercel project's api/ folder.
templates/api/
├── create-subscription.js # Creates Razorpay subscription server-side
├── verify-payment.js # Verifies HMAC-SHA256 payment signature
└── verify-subscription.js # Checks if subscription is still active
Required Vercel env vars:
RAZORPAY_KEY_ID=rzp_live_xxx
RAZORPAY_KEY_SECRET=your_secret
RAZORPAY_PLAN_MONTHLY=plan_xxx
RAZORPAY_PLAN_YEARLY=plan_xxximport { useState } from 'react'
import { usePro, PremiumGate, PricingModal } from 'react-premium-gate'
const PLANS = [
{ id: 'monthly', price: '₹399', period: '/mo' },
{ id: 'yearly', price: '₹2,999', period: '/yr', badge: 'Save 37%' },
]
export default function App() {
const { isPro, activatePro } = usePro({
storageKey: 'my_app_sub',
verifyEndpoint: '/api/verify-subscription',
})
const [showModal, setShowModal] = useState(false)
const [loading, setLoading] = useState(false)
const handlePayment = async (planId) => {
setLoading(true)
const res = await fetch(`/api/create-subscription?plan=${planId}`)
const { subscriptionId } = await res.json()
const rzp = new window.Razorpay({
key: process.env.VITE_RAZORPAY_KEY_ID,
subscription_id: subscriptionId,
handler: async (response) => {
const verify = await fetch('/api/verify-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(response),
})
const { valid, subscriptionId: subId } = await verify.json()
if (valid) { activatePro(subId); setShowModal(false) }
setLoading(false)
},
})
rzp.open()
}
return (
<>
{!isPro && (
<PremiumGate
onUpgrade={() => setShowModal(true)}
title="Analytics — Pro only"
description="Unlock detailed charts and AI insights."
buttonText="Upgrade to Pro"
/>
)}
<PricingModal
isOpen={showModal}
onClose={() => setShowModal(false)}
plans={PLANS}
onSelectPlan={handlePayment}
loading={loading}
/>
</>
)
}M. Adhitya — Builder, Rewrite Labs · Newsletter
MIT © 2025 M. Adhitya
Built at Rewrite Labs · Used in production across the Rewrite Labs app suite.