Integration · Payments
How to integrate Razorpay in a Flutter or Next.js app
Payments are a conversation between four parties. Step through it, and see exactly where each mistake lives.
The full explanation
What you are building when you add online payments
Adding payments to an app sounds like adding a button. What you are really building is a small, careful conversation between your app, your server and a payment provider, with money at stake at every step and several places where things can fail. Razorpay is one of the most widely used payment gateways in India, and its flow is representative of how most of them work.
The diagram above steps through that conversation. This page explains why each step exists, what goes wrong when it is skipped, and what you should expect your developer to do. It is written for both Flutter and Next.js, because the shape of the flow is the same and only the plumbing differs.
Why the order is created on your server
The first mistake to avoid is letting the app decide the amount. If the app tells the payment provider what to charge, then anyone who can modify the app or intercept the request can change the price. The correct design has the app tell your server what the customer wants, and has your server work out the amount from its own prices.
Your server then asks Razorpay to create an order for that amount. The Orders API takes an amount in paise, the smallest unit of the rupee, so ₹499 is sent as 49900. A very common bug is to send 499 and charge ₹4.99, or to multiply twice and charge a hundred times too much. Test with real numbers early.
Razorpay replies with an order identifier. That identifier is the thread that ties everything together: the payment, the confirmation and the webhook all refer to it. Store it against your own order record so you can match them later.
Opening checkout and what comes back
Your app receives the order identifier and your public key identifier, and opens Razorpay's checkout. In Flutter this is done through a plugin, and in a Next.js web app by loading Razorpay's checkout script and passing it the same values. The customer chooses UPI, card or another method and completes the payment inside the provider's interface.
Only the public key goes to the app. The secret key must never leave your server. If it is placed inside a mobile app, it can be extracted from the package by anyone who wants it, and that gives them the ability to act as you.
When the payment succeeds, the checkout returns three values to the app: a payment identifier, the order identifier and a signature. The app should treat these as evidence and not as proof, because they arrive through the customer's device.
Verifying the signature on the server
The signature is how you know the confirmation came from Razorpay and was not made up by someone with a modified app. Your server computes a cryptographic hash, specifically an HMAC with SHA-256, over the order identifier and the payment identifier joined by a separator, using your secret key. If the result matches the signature, the payment is genuine.
Only after this check should your server mark the order as paid. Do not rely on the success screen in the app. A person with a little technical skill can make an app show success without paying anything.
Two further checks are worth doing: confirm that the amount on the order matches what you expected, and that this order has not already been marked paid. Handling the same payment twice is a classic source of double shipments.
Why you also need the webhook
Sometimes the customer pays and then the app is closed, the phone loses signal or the battery dies before the confirmation reaches your server. From the customer's point of view, the money has left the account and nothing has happened. This is how people get charged for orders that were never created.
A webhook solves it. Razorpay calls an address on your server directly, independently of the customer's device, to report events such as a payment being captured. Your server verifies the call, finds the order and updates it. It is the safety net that makes the system reliable.
Webhooks can be delivered more than once, and out of order. Your handler must be safe to run twice: check whether the order is already paid and do nothing if so. This property is called idempotency, and it is the most important quality of a payment handler.
Test mode, live mode and going live
Razorpay provides a test mode with separate keys, in which no real money moves. Build and test the whole flow there, including failures: declined payments, abandoned checkouts, delayed confirmations. A flow tested only on success is not tested.
Going live involves activating your account with business details and documents, which can take time, so start that process early. Then switch the keys and re-test with small real payments before you open to customers. Keep the test and live keys in configuration, not in the code.
Refunds, settlement and the accounting side
Payments are not the end of the story. Customers cancel and orders fail, so you need a way to issue refunds, either full or partial, and to track their status. Your admin panel should show it plainly, because refunds are a leading cause of customer support.
The money you collect reaches your bank account on a settlement schedule, less the provider's fees. Your accountant will want reports that match payments to settlements. Ask your developer to make sure each payment is recorded with enough detail to reconcile later.
If you are not using Razorpay, does any of this change?
Very little. Other payment gateways in India, whether Cashfree, PayU, PhonePe's gateway or a bank's, follow the same shape: your server creates a payment or order, the customer completes it in the provider's interface, a signed result comes back, and a server-to-server notification confirms it. The names of the fields differ. The principles do not.
The same mistakes appear everywhere: trusting the client, keeping a secret in the app, skipping the server-side confirmation and forgetting that notifications can repeat. If your developer understands why each step exists, switching providers is mostly a matter of reading a different set of documentation.
It is also worth designing your own code so that the provider is behind a small interface of your own. That way, if you need to change gateway, or add a second as a fallback, the rest of your app does not have to change.
Reconciliation: matching what customers paid to what you received
A payment does not reach your bank account the moment a customer pays. Razorpay settles to you on a schedule, net of its fees, and a single settlement may cover many payments, some of which may have been refunded in the meantime. To close your books, your accountant needs to match each order to the payment that paid it and each payment to the settlement that carried it.
This works only if your system stores the identifiers at each step: your order identifier, Razorpay's order identifier, the payment identifier, and later the refund identifier. Ask your developer to keep all of them against each order, together with the amount and the time. It costs almost nothing to store at the time and is very hard to reconstruct afterwards.
A simple report listing orders with their payment status, amount and settlement date is one of the most valued pieces of an admin panel, and it is rarely in the first quote.
What to ask your developer
- Where is the order amount calculated, and by what?
- Where is the secret key stored, and who can see it?
- Where is the signature verified, and what is checked besides the signature?
- Is there a webhook handler, and is it safe to run twice?
- How are failed, abandoned and delayed payments handled?
- How do refunds work, and how are they shown in the admin panel?
Field and endpoint names change over time, so ask your developer to build against Razorpay's current documentation and not from memory.
Payments are a conversation between four parties. Step through it, and see exactly where each mistake lives.
Ask to pay
The app tells your server what is being bought. The server works out the amount from its own prices.
Never take the amount from the app. It can be changed.
What costs real money
Four ways this goes wrong
Paise confusion
Charging 100 times too little, or too much, because of a unit mix-up.
Trusting the app
Marking an order paid from the client's word alone.
No webhook
Customers who paid, but the app closed, and never got their order.
Duplicate events
The same webhook processed twice, shipping two orders.
Field and endpoint names change over time. Check Razorpay's current documentation before you build. Razorpay documentation
Keep going
