PayU Integration In Flutter
The Checkout Pro SDK provides a complete Mobile Checkout solution for the Flutter App. The Checkout Pro experience allows you to integrate with the PayU PG with minimal integration effort and accept payments faster.
In this tutorial, i will be teaching you step by step guide about payU money Integration in Flutter App with all issues & problems you may face.
1. Flutter Project Setup Guide
Create a flutter project (I guess you know how to). Use latest null safety version(I’m using Flutter 3.22.0–23.0.pre &Dart 3.5.0 (build 3.5.0–131.0.dev)
a) build.gradle: (Location in project: \android\app)
subprojects {
afterEvaluate { project -> if (project.hasProperty('android')) {
project.android {
if (namespace == null) {
namespace project.group
}
}
}
}
}Add the code at build.gradle file
Full Code:(build.gradle)
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
android {
namespace = "com.example.payu_checkout_main"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.payu_checkout_main"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.debug
}
}
}
flutter {
source = "../.."
}
subprojects {
afterEvaluate { project -> if (project.hasProperty('android')) {
project.android {
if (namespace == null) {
namespace project.group
}
}
}
}
}b)AndroidMenifast.xml file at (\android\app\src\main):
Add this to xml file:
xmlns:tools="http://schemas.android.com/tools"It will look like this:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">Also add this inside of Application
tools:replace="android:label"It will look like this
<application
android:label="payu_checkout_main"
android:name="${applicationName}"
tools:replace="android:label"
android:icon="@mipmap/ic_launcher">Also Add this permissions:
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.INTERNET" />My Whole XML Code:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="payu_checkout_main"
android:name="${applicationName}"
tools:replace="android:label"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>c) Add These Dependences in pubspec.yaml file
payu_checkoutpro_flutter: ^1.2.3
crypto: ^3.0.6
lottie: ^3.2.0
get: ^4.6.62. PayU Money Account Setup:
just Create a account at https://payu.in/
It will look like this:
Select Test Mode at the top right part to get all the test data
Next go to the Developer option from the sidebar :
From The Developer Section you need to get key, salt32bit for the flutter integration:
Now your PayU Money Account Setup done. Next Section we will jump into flutter app development.
Please note that I am using GetX for state management and MVC Clean Architecture. You may use any state management solution, but please follow the Feature-First Clean Architecture approach for better project structure and clean code.
3. Flutter Code for Payment Setup
Now we will move to the lib part of the flutter project. My Lib Structure look like this
Let’s explain the structure , Under the view folder we have three main page home.dart, checkout_screen.dart, on_sucess.dart & 2 widget under widget checkoutbtn.dart & customtextfield.dart
And the main part of the payu checkout written in payu_payment_controller.dart under controller folder
hash_service.dart under Services Folder
a) Let’s start with main Function: Simple Main File
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:payu_checkout_main/views/home.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const GetMaterialApp(
home: Home(),
);
}
}b) home.dart:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:payu_checkout_main/views/checkout_screen.dart';
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home"),
),
body: Center(
child: TextButton(
onPressed: () {
Get.to(() => const CheckoutScreen());
},
child: const Text(
"PayU Checkout",
style: TextStyle(fontSize: 20, color: Colors.blue),
)),
),
);
}
}Mobile SS:
c) checkout_screen.dart:
Get Arpandev’s stories in your inbox
Join Medium for free to get updates from this writer.
Just a screen to add customer details:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:payu_checkout_main/controller/payu_payment_controller.dart';
import 'package:payu_checkout_main/views/widgets/checkoutbtn.dart';
import 'package:payu_checkout_main/views/widgets/customtextfield.dart';
// ignore: must_be_immutable
class CheckoutScreen extends StatefulWidget {
const CheckoutScreen({super.key});
@override
State<CheckoutScreen> createState() => _CheckoutScreenState();
}
class _CheckoutScreenState extends State<CheckoutScreen> {
PayuPaymentController payuPaymentController =
Get.put(PayuPaymentController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 60,
automaticallyImplyLeading: false,
title: const Text(
'PayU Checkout',
style: TextStyle(
color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
),
centerTitle: true,
elevation: 0,
backgroundColor: Colors.greenAccent,
actions: [
IconButton(
icon: const Icon(
Icons.close,
color: Colors.white,
),
onPressed: () {
Navigator.pop(context);
},
),
],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
child: Column(
children: [
Customtextfield(
title: "Customer Name",
errorMsg: "Please Enter Customer Name",
textController: payuPaymentController.nameController),
const SizedBox(height: 10),
Customtextfield(
title: "Customer Email",
errorMsg: "Please Enter Customer Email",
textController: payuPaymentController.emailController),
const SizedBox(height: 10),
Customtextfield(
title: "Customer Phone",
errorMsg: "Please Enter Customer Phone",
textController: payuPaymentController.phoneController),
const SizedBox(height: 10),
Customtextfield(
title: "Amount",
errorMsg: "Please Enter Amount",
textController: payuPaymentController.amountController),
const SizedBox(height: 50),
Checkoutbtn(
onPress: () {
payuPaymentController.updatePaymentParams();
payuPaymentController.generateTxnId(13);
payuPaymentController.initCheckoutPro();
},
)
],
),
),
);
}
}i) Widgets:
customtextfield.dart:
import 'package:flutter/material.dart';
class Customtextfield extends StatelessWidget {
final String title;
final String errorMsg;
final TextEditingController textController;
const Customtextfield(
{super.key,
required this.title,
required this.errorMsg,
required this.textController});
@override
Widget build(BuildContext context) {
return TextFormField(
controller: textController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: title,
),
validator: (value) {
if (value == null || value.isEmpty) {
return errorMsg;
}
return null;
},
);
}
}checkoutbtn.dart:
import 'package:flutter/material.dart';
class Checkoutbtn extends StatelessWidget {
void Function()? onPress;
Checkoutbtn({
super.key,
this.onPress,
});
@override
Widget build(BuildContext context) {
return ElevatedButton.icon(
style: ElevatedButton.styleFrom(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
backgroundColor: Colors.green,
minimumSize: const Size(200, 50),
),
icon: const Icon(Icons.payment),
label: const Text(
'Pay Now',
style: TextStyle(color: Colors.white),
),
onPressed: onPress,
);
}
}d) on_sucess.dart:
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class OnSucess extends StatelessWidget {
const OnSucess({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Lottie.asset("assets/sucess.json", height: 200, width: 200),
const SizedBox(
height: 20,
),
const Text(
"Payment Successfull",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.green),
),
],
),
),
),
);
}
}Mob SS:
e) Service Folder:
hash_service.dart: Add your own merchantSalt & merchantSecretKey
We need this hash for payu_checkout_pro flutter package , for validating payment hash. (It’s better to generate this hash from server side)
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:payu_checkoutpro_flutter/PayUConstantKeys.dart'
show PayUHashConstantsKeys;
class HashService {
static const merchantSalt =
""; // Add you Salt here.
static const merchantSecretKey =
""; // Add Merchant Secrete Key - Optional
static Map generateHash(Map response) {
var hashName = response[PayUHashConstantsKeys.hashName];
var hashStringWithoutSalt = response[PayUHashConstantsKeys.hashString];
var hashType = response[PayUHashConstantsKeys.hashType];
var postSalt = response[PayUHashConstantsKeys.postSalt];
var hash = "";
if (hashType == PayUHashConstantsKeys.hashVersionV2) {
hash = getHmacSHA256Hash(hashStringWithoutSalt, merchantSalt);
} else if (hashName == PayUHashConstantsKeys.mcpLookup) {
hash = getHmacSHA1Hash(hashStringWithoutSalt, merchantSecretKey);
} else {
var hashDataWithSalt = hashStringWithoutSalt + merchantSalt;
if (postSalt != null) {
hashDataWithSalt = hashDataWithSalt + postSalt;
}
hash = getSHA512Hash(hashDataWithSalt);
}
var finalHash = {hashName: hash};
return finalHash;
}
static String getSHA512Hash(String hashData) {
var bytes = utf8.encode(hashData);
var hash = sha512.convert(bytes);
return hash.toString();
}
static String getHmacSHA256Hash(String hashData, String salt) {
var key = utf8.encode(salt);
var bytes = utf8.encode(hashData);
final hmacSha256 = Hmac(sha256, key).convert(bytes).bytes;
final hmacBase64 = base64Encode(hmacSha256);
return hmacBase64;
}
static String getHmacSHA1Hash(String hashData, String salt) {
var key = utf8.encode(salt);
var bytes = utf8.encode(hashData);
var hmacSha1 = Hmac(sha1, key); // HMAC-SHA1
var hash = hmacSha1.convert(bytes);
return hash.toString();
}
}f) Controller
payu_payment_controller.dart:
The most important part of the project.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:payu_checkoutpro_flutter/PayUConstantKeys.dart';
import 'package:payu_checkoutpro_flutter/payu_checkoutpro_flutter.dart';
import '../services/hash_service.dart';
import '../views/on_sucess.dart';
class PayuPaymentController extends GetxController
implements PayUCheckoutProProtocol {
RxBool isLoading = false.obs;
//! TextEditingControllers for payment details
TextEditingController nameController = TextEditingController();
TextEditingController emailController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController amountController = TextEditingController();
//! Payment Details Variables required for PayU Gateway
late Map<dynamic, dynamic> payUPaymentParams;
late PayUCheckoutProFlutter _checkoutPro;
late var txnId = "".obs;
@override
void onInit() {
super.onInit();
txnId.value = generateTxnId(13);
_checkoutPro = PayUCheckoutProFlutter(this);
//! Initialize payUPaymentParams in onInit method
payUPaymentParams = {
PayUPaymentParamKey.key: "9let8O",
PayUPaymentParamKey.amount: amountController.text.toString(),
PayUPaymentParamKey.productInfo: "Test Product",
PayUPaymentParamKey.firstName: nameController.text.toString(),
PayUPaymentParamKey.email: emailController.text.toString(),
PayUPaymentParamKey.phone: phoneController.text.toString(),
PayUPaymentParamKey.environment: "1",
// String - "0" for Production and "1" for Test
PayUPaymentParamKey.transactionId: txnId.toString(),
// transactionId Cannot be null or empty and should be unique for each transaction.
// Maximum allowed length is 25 characters. It cannot contain special characters like: -_/
PayUPaymentParamKey.userCredential: "9let8O:1000",
// Format: <merchantKey>:<userId> ... UserId is any id/email/phone number to uniquely identify the user.
PayUPaymentParamKey.android_surl:
"https:///www.payumoney.com/mobileapp/payumoney/success.php",
PayUPaymentParamKey.android_furl:
"https:///www.payumoney.com/mobileapp/payumoney/failure.php",
PayUPaymentParamKey.ios_surl:
"https:///www.payumoney.com/mobileapp/payumoney/success.php",
PayUPaymentParamKey.ios_furl:
"https:///www.payumoney.com/mobileapp/payumoney/failure.php",
};
}
//!Config Data
final Map<dynamic, dynamic> payUCheckoutProConfig = {
PayUCheckoutProConfigKeys.merchantName: "PayU",
// PayUCheckoutProConfigKeys.waitingTime:"30"
};
//! generate txn id
String generateTxnId(int length) {
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
final random = Random();
return List.generate(
length, (index) => characters[random.nextInt(characters.length)])
.join();
}
//! Method to update payment parameters dynamically
void updatePaymentParams() {
payUPaymentParams[PayUPaymentParamKey.amount] = amountController.text;
payUPaymentParams[PayUPaymentParamKey.firstName] = nameController.text;
payUPaymentParams[PayUPaymentParamKey.email] = emailController.text;
payUPaymentParams[PayUPaymentParamKey.phone] = phoneController.text;
}
@override
generateHash(Map response) {
// Pass response param to your backend server
// Backend will generate the hash which you need to pass to SDK
// hashResponse: is the response which you get from your server
Map hashResponse = HashService.generateHash(response);
_checkoutPro.hashGenerated(hash: hashResponse);
}
//!On Sucess Methods
@override
onPaymentSuccess(dynamic response) {
response.forEach((key, value) {
Get.to(() => const OnSucess());
});
}
//!On Payment Failure
@override
onPaymentFailure(dynamic response) {
response.forEach((key, value) {
print("$key: $value");
});
}
//!On Payment Cancel
@override
onPaymentCancel(Map? response) {
//Handle on cancel response
response?.forEach((key, value) {
print("$key: $value");
});
}
//!On Error
@override
onError(Map? response) {
response?.forEach((key, value) {
print("$key: $value");
});
}
//!Init Payment
void initCheckoutPro() {
try {
isLoading.value = true;
_checkoutPro.openCheckoutScreen(
payUPaymentParams: payUPaymentParams,
payUCheckoutProConfig: payUCheckoutProConfig);
isLoading.value = false;
} catch (e) {
isLoading.value = false;
throw Exception(e.toString());
}
}
}Description of PayuPaymentController
The PayuPaymentController is a GetX controller in a Flutter application that manages the PayU payment integration using the payu_checkoutpro_flutter package. It encapsulates all the necessary logic for initiating and handling payment transactions. Here's a breakdown of its functionality:
State Management:
- Maintains a reactive loading state (
isLoading) to track the payment process.
User Input:
- Manages user input for payment details through
TextEditingControllerinstances for name, email, phone, and amount.
Transaction Management:
- Generates unique transaction IDs (
txnId) using a utility method. - Configures and dynamically updates payment parameters for PayU.
Payment Initialization:
- Prepares payment details and launches the PayU Checkout Pro screen using the
openCheckoutScreenmethod.
Callback Handling:
- Implements the
PayUCheckoutProProtocolinterface to handle different payment outcomes: - Success: Redirects to a success screen.
- Failure, Cancellation, and Errors: Logs the responses for debugging and user feedback.
Hash Generation:
- generate the required hash for secure transactions.
This controller simplifies the integration of PayU payment gateway, ensuring clean separation of concerns and easy management of payment-related operations in the app.
Mob SS:
Use The Test Credential :
Card Number: 5123456789012346
Expiry: 05/25 CVV: 123 OTP: 123456
g) on_sucess.dart:
On Success Payment it will show the success payment page
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class OnSucess extends StatelessWidget {
const OnSucess({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Lottie.asset("assets/sucess.json", height: 200, width: 200),
const SizedBox(
height: 20,
),
const Text(
"Payment Successfull",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.green),
),
],
),
),
),
);
}
Demo video: https://vimeo.com/1038497258?share=copy
Source Code- https://github.com/Arpandev-code/PayUCheckOutProExample
For any issue connect @: https://www.linkedin.com/in/itsarpandev/









