Why Razorpay for Flutter Apps?
Razorpay is the dominant payment gateway in India, supporting UPI, net banking, cards, EMI, and wallets. For Flutter apps targeting Indian users, Razorpay is almost always the right choice. The official Flutter plugin (razorpay_flutter) is well-maintained and supports Android and iOS. This guide covers a production-ready integration — not just a quick start.
Architecture: Server-Side Order Creation is Non-Negotiable
The single most common security mistake in payment integrations: creating orders and verifying payments client-side. Never do this. The correct flow is:
- Client sends a payment request to your server
- Your server creates a Razorpay Order via the Razorpay API using your secret key
- Server returns the Order ID to the client
- Client opens the Razorpay checkout with the Order ID
- After payment, Razorpay sends a webhook to your server
- Your server verifies the payment signature using your secret key
- Only after verification does your server fulfil the order
Your Razorpay secret key must never touch the client. Anyone with your secret key can create fraudulent orders.
Setup
1. Add the dependency
# pubspec.yaml
dependencies:
razorpay_flutter: ^1.3.6
2. Android configuration
# android/app/build.gradle
android {
defaultConfig {
minSdkVersion 19 // Razorpay requires API 19+
}
}
3. iOS configuration
Add to ios/Runner/Info.plist:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>upi</string>
<string>phonepe</string>
<string>gpay</string>
<string>paytm</string>
</array>
Server-Side: Creating a Razorpay Order (Node.js)
const Razorpay = require('razorpay');
const crypto = require('crypto');
const razorpay = new Razorpay({
key_id: process.env.RAZORPAY_KEY_ID,
key_secret: process.env.RAZORPAY_KEY_SECRET,
});
// Create order endpoint
app.post('/api/create-order', async (req, res) => {
const { amount, currency = 'INR', receipt } = req.body;
const order = await razorpay.orders.create({
amount: amount * 100, // Razorpay uses paise
currency,
receipt,
});
res.json({ orderId: order.id, amount: order.amount, currency: order.currency });
});
// Verify payment endpoint
app.post('/api/verify-payment', (req, res) => {
const { razorpay_order_id, razorpay_payment_id, razorpay_signature } = req.body;
const body = razorpay_order_id + '|' + razorpay_payment_id;
const expectedSignature = crypto
.createHmac('sha256', process.env.RAZORPAY_KEY_SECRET)
.update(body)
.digest('hex');
if (expectedSignature === razorpay_signature) {
// Payment verified — fulfil the order
res.json({ success: true });
} else {
res.status(400).json({ success: false, error: 'Invalid signature' });
}
});
Flutter Client: Complete Integration
import 'package:razorpay_flutter/razorpay_flutter.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class PaymentService {
late Razorpay _razorpay;
void init() {
_razorpay = Razorpay();
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
}
Future<void> startPayment({
required double amount,
required String userName,
required String userEmail,
required String userPhone,
required String description,
}) async {
// Step 1: Create order on your server
final response = await http.post(
Uri.parse('https://yourapi.com/api/create-order'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'amount': amount, 'receipt': 'order_${DateTime.now().millisecondsSinceEpoch}'}),
);
final orderData = jsonDecode(response.body);
// Step 2: Open Razorpay checkout
var options = {
'key': 'rzp_live_YOUR_KEY_ID', // Use rzp_test_ for testing
'amount': orderData['amount'],
'currency': 'INR',
'order_id': orderData['orderId'],
'name': 'Your Business Name',
'description': description,
'prefill': {
'contact': userPhone,
'email': userEmail,
'name': userName,
},
'external': {
'wallets': ['paytm', 'phonepe']
}
};
_razorpay.open(options);
}
void _handlePaymentSuccess(PaymentSuccessResponse response) async {
// Verify on server before fulfilling order
final verifyRes = await http.post(
Uri.parse('https://yourapi.com/api/verify-payment'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'razorpay_order_id': response.orderId,
'razorpay_payment_id': response.paymentId,
'razorpay_signature': response.signature,
}),
);
if (jsonDecode(verifyRes.body)['success'] == true) {
// Show success UI, update order status
}
}
void _handlePaymentError(PaymentFailureResponse response) {
// Handle cancellation or failure
// response.code: error code
// response.message: human-readable message
}
void _handleExternalWallet(ExternalWalletResponse response) {
// User chose an external wallet
}
void dispose() {
_razorpay.clear();
}
}
Handling Webhooks (Recommended for Production)
Don't rely solely on client-side payment callbacks for order fulfilment. Network issues can cause the client to never call your verify endpoint even after a successful payment. Always configure a Razorpay webhook that calls your server on payment.captured events. This ensures orders are fulfilled even if the user's app crashes after payment.
We've integrated Razorpay into 20+ Flutter apps at DevXAI Technologies. If you're building a payment feature and need help with the integration, get in touch.