Search pub.dev for CCAvenue and the top result is a package called cc_avenue. Read its README and you hit this line: "As of now this package supports only Android." Its last release, 1.0.3, is from 2022. If your app ships on both stores, that package can't be your CCAvenue Flutter integration, and finding out after the Android build is approved is an expensive way to learn it.
This post covers what we'd build instead: one flow that works on iOS and Android, keeps the secrets on your server, and decides whether an order is paid on the backend rather than inside the app. The same shape works for a React Native app or a Next.js checkout, because almost none of it lives in Flutter.
Why a CCAvenue Flutter integration starts on the server
CCAvenue's standard checkout is a hosted payment page. You send it an encrypted request, the customer pays on CCAvenue's page, and CCAvenue posts an encrypted response back to a URL you control. Three credentials come from your CCAvenue dashboard: a Merchant ID, an Access Code and a Working Key (the encryption key).
The Working Key is the one that matters. Anyone who has it can encrypt a fake request or decrypt a real response. So it never goes into the app binary, not in a Dart constant, not in an .env file bundled with the build, not in remote config. App binaries get decompiled. Treat the key the way you'd treat a database password.
That leaves the server doing four jobs:
- Create the order in your own database first (amount, currency, user, status
pending), and generate the order ID there. - Build the CCAvenue request parameters (merchant ID, order ID, amount, currency, redirect and cancel URLs, billing details) and encrypt them with the Working Key, using the encryption utility from CCAvenue's integration kit for your server language.
- Receive the encrypted response at your redirect URL, decrypt it, and record what it says.
- Confirm the result with CCAvenue's status API before marking anything as paid.
The app's job is small: ask your server for a payment session, show CCAvenue's page, and tell the user what the server decided.
The cross-platform flow: a WebView you control
CCAvenue advertises in-app kits for iOS and Android. We couldn't find an official Flutter plugin from CCAvenue at the time of writing, so check with your CCAvenue account manager before you pick an approach. If they hand you native SDKs, you can wrap them with platform channels into Swift and Kotlin. That's real work, two native integrations plus a bridge, and you inherit their upgrade schedule.
The lighter route, and the one we'd default to, is the hosted page inside webview_flutter, which runs on both platforms. The sequence:
- App calls
POST /payments/ccavenue/sessionon your backend with a cart or order reference. Never the amount: the server computes that from its own data. - Server returns the encrypted request and the access code (the access code is not secret on its own; the Working Key is).
- App opens a WebView that submits a form, or loads a page served by your backend that auto-submits one, to CCAvenue's
transaction.do?command=initiateTransactionendpoint withencRequestandaccess_code. - Customer pays. CCAvenue posts the encrypted response to your redirect URL on your server.
- Your server decrypts it, verifies with the status API, updates the order, and then redirects the WebView to a plain result page such as
https://api.yourapp.com/payments/done?order=123. - The app's navigation delegate watches for that result URL, closes the WebView, and asks your backend for the order status.
Notice what the app never does: it never decrypts anything, and it never reads a success flag out of a URL query string. The result URL is a signal to close the WebView, nothing more. If someone hand-crafts that URL, they get a screen that says "checking your payment" and then whatever your database says.
Two platform details to check on real devices. UPI intent links (upi:// and app-specific schemes) need to be handed off to the OS from the navigation delegate, or UPI apps won't open. On Android that also means declaring the right <queries> entries in the manifest; on iOS, the schemes you query go in LSApplicationQueriesSchemes. And test the back button: a user who backs out mid-payment should land on a pending state, not a failure.
Confirm the payment on the backend, every time
The decrypted response contains an order_status field. It's tempting to trust it and move on. Don't stop there. Responses get lost when a phone drops off the network after the bank page, users close the app mid-redirect, and the redirect can arrive late.
CCAvenue offers a server-side status API (a POST to api.ccavenue.com/apis/servlet/DoWebTrans with your order or reference number) that returns the order's current status. Call it:
- right after decrypting the redirect response, before marking the order paid;
- from a scheduled job that sweeps orders stuck in
pendingfor more than a few minutes; - whenever the app asks about an order that's still pending.
Also compare amounts. The status response includes the amount CCAvenue actually charged. If it doesn't match what your order says, flag it for a human, don't fulfil it.
Make the "mark as paid" step idempotent. The redirect handler, the sweeper job and an app poll can all land on the same order within seconds of each other. A conditional update (UPDATE orders SET status='paid' WHERE id=? AND status='pending') lets exactly one of them win and fulfil the order. The others see zero rows changed and do nothing.
What carries over to web and React Native
Everything above the WebView is platform-neutral. A Next.js checkout posts the same form in the browser tab instead of a WebView. A React Native app uses react-native-webview with the same URL-watching trick. The backend (session endpoint, redirect handler, status check, sweeper) is identical across all three, which is the point: one source of truth for whether money arrived.
A checklist before you call it done:
- Working Key exists only on the server, and only in a secret manager or environment variable.
- Amount is computed server-side from the order, never sent by the client.
- Every paid order has passed a status API check and an amount match.
- Pending orders get swept, so a lost redirect doesn't mean a lost sale or a charged customer with no order.
- UPI apps open from the WebView on a real Android phone and a real iPhone.
- Test mode works end to end before you switch to production endpoints and keys.
We've built payment portals with multiple Indian gateways, GST invoicing and reconciliation for an ed-tech platform, and the pattern above is the one we keep coming back to. If you're adding CCAvenue to a Flutter, React Native or web checkout and want a second pair of eyes on the flow, orithLabs does this as part of our e-commerce development work. The first call is free, and we're happy to just review what you have.