Payments· 6 min read

Razorpay in React Native: Integration Without Surprises

Integrate Razorpay in a React Native or Expo app: orders on the server, checkout in the app, signature verification, webhooks, UPI, fees and testing.

By

Full-Stack, Mobile & AI Engineer

Razorpay is the default payment gateway for many Indian startups: one integration gives you cards, UPI, netbanking and wallets. Its React Native SDK makes the checkout itself straightforward. The surprises come from everything around it: creating orders on the server, verifying signatures, UPI apps that hand control back late, and webhooks that arrive twice.

This guide shows a production-ready Razorpay integration in a React Native app: what happens on the server and in the app, how to verify payments safely, how to handle UPI and failures, what it costs, and how to test.

Key takeaways

  • Create an Order on your server first, with the amount in the smallest currency unit (paise for INR), and send only the order ID to the app.
  • Open Razorpay Checkout in the app with react-native-razorpay, which handles cards, UPI, netbanking and wallets.
  • Verify the payment signature on your server with your key secret before marking anything paid.
  • Use webhooks (payment.captured, order.paid) as the final source of truth, because users can close the app mid-payment.
  • Standard pricing is 2% on most domestic payments and up to 3% on international cards, plus 18% GST on the fee; check Razorpay's pricing page for current rates.

What Razorpay is, and how it makes money

Razorpay is an Indian payments company whose gateway lets businesses accept UPI, cards, netbanking and wallets through one integration, with settlements to the business's bank account. It earns a fee on each transaction: about 2% for most domestic payments and up to 3% for international cards, with 18% GST charged on the fee. Higher-volume businesses can negotiate custom pricing.

How the flow works

  1. The app asks your server to pay for an order.
  2. Your server creates a Razorpay Order with the amount and currency, and stores the order ID against your own order.
  3. The app opens Razorpay Checkout with the order ID and your public key ID.
  4. The customer pays. Checkout returns razorpay_payment_id, razorpay_order_id and razorpay_signature.
  5. The app sends those to your server, which verifies the signature and marks the order paid.
  6. A webhook confirms the same payment server-to-server, covering cases where the app never reported back.

The server: create an order

import Razorpay from "razorpay";

const razorpay = new Razorpay({
  key_id: process.env.RAZORPAY_KEY_ID!,
  key_secret: process.env.RAZORPAY_KEY_SECRET!,
});

// POST /payments/razorpay/order
const rzpOrder = await razorpay.orders.create({
  amount: order.totalInPaise, // e.g. 49900 for ₹499.00
  currency: "INR",
  receipt: order.id,
});
res.json({ orderId: rzpOrder.id, amount: rzpOrder.amount, keyId: process.env.RAZORPAY_KEY_ID });

The amount always comes from your own order record on the server, never from the app.

The app: open checkout

import RazorpayCheckout from "react-native-razorpay";

export async function payWithRazorpay(order: { orderId: string; amount: number; keyId: string }) {
  const result = await RazorpayCheckout.open({
    key: order.keyId,
    order_id: order.orderId,
    amount: order.amount,
    currency: "INR",
    name: "Your Store",
    description: "Order payment",
    prefill: { email: user.email, contact: user.phone },
    theme: { color: "#1f2bd6" },
  });
  // result: { razorpay_payment_id, razorpay_order_id, razorpay_signature }
  return api.post("/payments/razorpay/verify", result);
}

The SDK contains native code, so in an Expo project it runs in a development build rather than Expo Go.

The server: verify the signature

Razorpay signs order_id|payment_id with your key secret using HMAC SHA256. Recompute it and compare:

import crypto from "node:crypto";

export function isValidRazorpaySignature(orderId: string, paymentId: string, signature: string) {
  const expected = crypto
    .createHmac("sha256", process.env.RAZORPAY_KEY_SECRET!)
    .update(`${orderId}|${paymentId}`)
    .digest("hex");
  return expected.length === signature.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Only after a valid signature (and a matching order and amount in your database) should the order become paid.

Webhooks: the safety net

Configure a webhook in the Razorpay dashboard for events such as payment.captured and order.paid. Verify the webhook signature with your webhook secret, record each event ID so duplicates are ignored, and update the order if the app's verification call never arrived. This covers users who pay in a UPI app and never come back to yours.

From the field: Razorpay is one of the payment integrations I've shipped across 12+ apps, alongside Stripe, RevenueCat, in-app purchases and KNET, always with idempotent, webhook-driven billing. The habit that matters most with Razorpay is treating the app's success callback as a hint and the verified webhook as the truth.

UPI specifics

UPI is often the most popular method in India, and it behaves differently from cards:

  • Intent flows switch the user to a UPI app (Google Pay, PhonePe, Paytm and others) and back. Users sometimes don't return, so the webhook matters more.
  • Payments can be pending for a short time. Show a "confirming payment" state rather than a failure, and poll your server.
  • On Android, UPI app detection needs package visibility declarations; follow Razorpay's current SDK setup notes for your React Native version.

Standard checkout or a custom UI?

Razorpay's standard checkout handles method selection, UPI app switching, saved cards, OTP screens and bank redirects. For almost every app, it's the right choice: it's maintained by Razorpay, it keeps card details off your servers, and it's what Indian users recognise. A fully custom payment UI only makes sense when you have strong reasons and the time to keep up with every method's edge cases.

Common Razorpay mistakes

  • Sending rupees instead of paise. amount: 499 charges ₹4.99. Store and send amounts in paise.
  • Putting the key secret in the app. Only the key ID belongs in the client; the secret stays on the server.
  • Marking orders paid from the checkout callback without verifying the signature.
  • Ignoring payment.failed, so users can't see why a payment didn't go through or retry cleanly.
  • Not storing the Razorpay payment ID on your order, which makes support and refunds slow.
  • Creating a new Razorpay order for every retry without linking it to the same internal order, which makes reconciliation messy.

Refunds and reconciliation

  • Refunds are created through the API (full or partial) and confirmed by webhook; update your order and notify the user.
  • Settlements reach your bank on Razorpay's settlement schedule. Reconcile them against your orders, especially if you run discounts or partial refunds.
  • Keep the Razorpay payment ID on every order, so support can trace any payment in seconds.

Testing

  • Use test-mode keys and Razorpay's documented test cards and UPI IDs for success and failure cases.
  • Test closing the checkout, network loss after paying, and duplicate webhooks.
  • Test UPI on a real Android device with UPI apps installed, since emulators don't reflect real behaviour.

FAQ

How do I integrate Razorpay in React Native?

Create an Order on your server with the Razorpay Orders API, open Razorpay Checkout in the app with react-native-razorpay using the order ID, then send the returned payment ID, order ID and signature to your server and verify the signature with your key secret. Add webhooks as the final confirmation.

Does Razorpay work with Expo?

Yes, in a development build. The Razorpay SDK includes native code, so it doesn't run in Expo Go. Install it in your Expo project and build with EAS to test.

How much does Razorpay charge per transaction?

Razorpay's standard plan lists a 2% fee on most domestic payments, including cards, netbanking and wallets, and up to 3% on international cards, with 18% GST charged on the fee. Enterprise and negotiated rates differ, so check the current pricing page.

Can I use Razorpay for in-app subscriptions on iOS?

Only for what Apple allows outside in-app purchase, such as physical goods and services consumed outside the app. Digital content and features used inside an iOS app generally require Apple's in-app purchase, so use Razorpay for real-world goods and services.

Next steps

If you're building for the Indian market and want Razorpay, UPI and webhooks working reliably from launch, I can build and test the whole payment flow. Get in touch, or see how I work as a React Native developer.

Sources

Bhuwanesh Sisodia

Bhuwanesh Sisodia

Full-Stack, Mobile & AI Engineer

Bhuwanesh Sisodia is a Full-Stack, Mobile & AI Engineer based in Mumbai, with 7+ years of experience and 50+ apps shipped to the App Store and Google Play. He works independently with startups and product teams worldwide.

About meGitHubLinkedInX

Related