payu_ppi_flutter 1.2.0-alpha.4
payu_ppi_flutter: ^1.2.0-alpha.4 copied to clipboard
The PPI for Flutter provides a complete Mobile solution for the Flutter App and allows you to integrate Virtual CARD DISPLAY POWERED BY PayU for iOS and Android.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:payu_ppi_flutter_example/HashService.dart';
import 'package:payu_ppi_flutter/payu_ppi_flutter.dart';
import 'package:payu_ppi_flutter/PayUConstantKeys.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> implements PayUPPIProtocol {
late PayUPPIFlutter _ppi;
// Default values aligned with iOS OnePayUJSSampleApp ViewController.
static const _defaultMerchantKey = 'YourKey';
static const _defaultSalt = 'YourSalt';
static const _defaultPhone = 'YourNumber';
static const _defaultEnvironment = 'Test';
static const _defaultWalletUrn = '1003097';
static const _defaultWalletIdentifier = 'OLW';
static const _defaultCustomerName = 'Amrendra';
static const _defaultPrimaryTheme = '#3394B4';
static const _defaultSecondaryTheme = '#AD42f5';
static const _kycModeIdHint = 'digioKyc:10, adharKyc:11';
static const _defaultKycUrl = 'https://test.payu.in/kyc-demo-url';
static const _defaultRedirectionUrl = 'https://citruspay-api.pc.enstage-sas.com/login';
static const _defaultMerchantName = 'Flutter Merchant';
final _keyController = TextEditingController(text: _defaultMerchantKey);
final _saltController = TextEditingController(text: _defaultSalt);
final _phoneController = TextEditingController(text: _defaultPhone);
final _environmentController = TextEditingController(text: _defaultEnvironment);
final _requestIdController = TextEditingController();
final _walletUrnController = TextEditingController(text: _defaultWalletUrn);
final _walletIdentifierController =
TextEditingController(text: _defaultWalletIdentifier);
final _customerNameController =
TextEditingController(text: _defaultCustomerName);
final _videoKycModeIdController = TextEditingController();
final _primaryThemeController = TextEditingController();
final _secondaryThemeController = TextEditingController();
final _kycUrlController = TextEditingController(text: _defaultKycUrl);
final _redirectionUrlController =
TextEditingController(text: _defaultRedirectionUrl);
final _merchantNameController =
TextEditingController(text: _defaultMerchantName);
bool _kycConsent = true;
@override
void initState() {
super.initState();
_ppi = PayUPPIFlutter(this);
_refreshRequestId();
}
@override
void dispose() {
_keyController.dispose();
_saltController.dispose();
_phoneController.dispose();
_environmentController.dispose();
_requestIdController.dispose();
_walletUrnController.dispose();
_walletIdentifierController.dispose();
_customerNameController.dispose();
_videoKycModeIdController.dispose();
_primaryThemeController.dispose();
_secondaryThemeController.dispose();
_kycUrlController.dispose();
_redirectionUrlController.dispose();
_merchantNameController.dispose();
super.dispose();
}
/// Generates a fresh request/reference id for each SDK launch.
void _refreshRequestId() {
_requestIdController.text = 'payu${DateTime.now().millisecondsSinceEpoch}';
}
bool get _isProductionEnvironment {
return _environmentController.text.trim().toLowerCase() == 'prod';
}
/// Builds wallet params used by showCards and initiateCKYC.
Map<String, dynamic> _buildPayUPPIParams() {
return {
PayUPPIParamKey.merchantKey: _keyController.text.trim(),
PayUPPIParamKey.referenceId: _requestIdController.text.trim(),
// OnePayUJSEnvironment: production = 0, test = 1
PayUPPIParamKey.environment: _isProductionEnvironment ? '0' : '1',
PayUPPIParamKey.mobileNumber: _phoneController.text.trim(),
PayUPPIParamKey.walletUrn: _walletUrnController.text.trim(),
PayUPPIParamKey.walletIdentifier: _walletIdentifierController.text.trim(),
};
}
/// Builds standard KYC params; phone field drives customerMobile.
Map<String, dynamic> _buildKYCParams() {
return {
PayUKYCParamKey.clientTxnId: 'TXN${DateTime.now().millisecondsSinceEpoch}',
PayUKYCParamKey.customerMobile: _phoneController.text.trim(),
PayUKYCParamKey.consent: _kycConsent ? '1' : '0',
PayUKYCParamKey.clientId: '2070',
PayUKYCParamKey.entityId: '503',
PayUKYCParamKey.bankId: '6050',
PayUKYCParamKey.modeId: _videoKycModeIdController.text.trim(),
PayUKYCParamKey.redirectionUrl: _redirectionUrlController.text.trim(),
PayUKYCParamKey.secureCode: 'NcRfUjXn2r5u8x',
PayUKYCParamKey.environment: _isProductionEnvironment
? PayUEnvironment.production
: PayUEnvironment.test,
};
}
/// Resolves videoKycModeId — optional; allowed values: empty (nil), 10, or 11.
String? _resolveVideoKycModeId() {
final modeId = _videoKycModeIdController.text.trim();
if (modeId.isEmpty) {
return null;
}
return modeId;
}
/// Builds CKYC params from CKYC section fields.
Map<String, dynamic> _buildCKYCParams() {
final primaryTheme = _primaryThemeController.text.trim().isEmpty
? _defaultPrimaryTheme
: _primaryThemeController.text.trim();
final secondaryTheme = _secondaryThemeController.text.trim().isEmpty
? _defaultSecondaryTheme
: _secondaryThemeController.text.trim();
final videoKycModeId = _resolveVideoKycModeId();
final ckycParams = <String, dynamic>{
PayUPPIParamKey.merchantKey: _keyController.text.trim(),
PayUPPIParamKey.referenceId: _requestIdController.text.trim(),
PayUPPIParamKey.walletIdentifier: _walletIdentifierController.text.trim(),
PayUPPIParamKey.walletUrn: _walletUrnController.text.trim(),
PayUCKYCParamKey.customerMobile: _phoneController.text.trim(),
PayUCKYCParamKey.customerName: _customerNameController.text.trim(),
PayUCKYCParamKey.kycConsent: _kycConsent ? '1' : '0',
PayUCKYCParamKey.primaryTheme: primaryTheme,
PayUCKYCParamKey.secondaryTheme: secondaryTheme,
PayUCKYCParamKey.redirectionUrl: _redirectionUrlController.text.trim(),
PayUCKYCParamKey.merchantName: _merchantNameController.text.trim(),
PayUCKYCParamKey.environment: _isProductionEnvironment
? PayUEnvironment.production
: PayUEnvironment.test,
};
// videoKycModeId is optional — nil, 10 (digioKyc), or 11 (adharKyc).
if (videoKycModeId != null) {
ckycParams[PayUCKYCParamKey.videoKycModeId] = videoKycModeId;
}
return ckycParams;
}
/// Builds KYC-with-URL params.
Map<String, dynamic> _buildKYCUrlParams() {
return {
PayUKYCUrlParamKey.redirectionUrl: _redirectionUrlController.text.trim(),
PayUKYCUrlParamKey.kycUrl: _kycUrlController.text.trim(),
};
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
// Keep action buttons pinned at bottom when keyboard is visible.
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: const Text('PayU PPI SDK'),
),
body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
behavior: HitTestBehavior.translucent,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
keyboardDismissBehavior:
ScrollViewKeyboardDismissBehavior.onDrag,
child: Column(
children: [
_buildFieldRow('Key', _keyController),
_buildFieldRow('Salt', _saltController),
_buildFieldRow('Phone', _phoneController),
_buildFieldRow('Environment', _environmentController,
hint: 'Test or Prod'),
_buildFieldRow('Request ID', _requestIdController),
_buildFieldRow('WalletUrn', _walletUrnController),
_buildFieldRow(
'WalletIdentifier', _walletIdentifierController),
const Divider(height: 24),
_buildFieldRow(
'customer name', _customerNameController),
_buildFieldRow(
'Merchant Name', _merchantNameController),
_buildFieldRow(
'Kyc Mode Id',
_videoKycModeIdController,
hint: _kycModeIdHint,
keyboardType: TextInputType.number,
),
_buildSwitchRow('kyc Consent', _kycConsent, (value) {
setState(() => _kycConsent = value);
}),
_buildFieldRow(
'Color (for ckyc)',
_primaryThemeController,
hint: _defaultPrimaryTheme,
),
_buildFieldRow(
'secondaryTheme',
_secondaryThemeController,
hint: _defaultSecondaryTheme,
),
const Divider(height: 24),
_buildFieldRow('KYC URL', _kycUrlController),
_buildFieldRow(
'Redirection URL', _redirectionUrlController),
const SizedBox(height: 16),
],
),
),
),
SafeArea(
top: false,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildActionButton(
label: 'INITIATE KYC WITH URL',
onPressed: () {
debugPrint('Launching initiateKYCWithUrl');
_ppi.initiateKYCWithUrl(
kycUrlParams: _buildKYCUrlParams());
},
),
_buildActionButton(
label: 'initiate CKYC',
onPressed: () {
debugPrint('Launching initiateCKYC');
_ppi.initiateCKYC(ckycParams: _buildCKYCParams());
},
),
_buildActionButton(
label: 'INITIATE KYC',
onPressed: () {
debugPrint('Launching initiateKYC');
_ppi.initiateKYC(kycParams: _buildKYCParams());
},
),
_buildActionButton(
label: 'CARDS',
onPressed: () {
debugPrint('Launching showCards');
_ppi.showCards(payUPPIParams: _buildPayUPPIParams());
},
),
],
),
),
],
),
),
),
);
}
/// Label + bordered text field row for editable inputs.
Widget _buildFieldRow(
String label,
TextEditingController controller, {
String? hint,
TextInputType keyboardType = TextInputType.text,
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 130,
child: Text(
label,
style: const TextStyle(fontSize: 15),
),
),
Expanded(
child: TextField(
controller: controller,
keyboardType: keyboardType,
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(
fontSize: 14,
color: Colors.grey.shade500,
),
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: BorderSide(color: Colors.grey.shade400),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(color: Colors.blue, width: 1.5),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: BorderSide(color: Colors.grey.shade400),
),
isDense: true,
),
style: const TextStyle(fontSize: 15),
textInputAction: TextInputAction.done,
onSubmitted: (_) => FocusScope.of(context).unfocus(),
),
),
],
),
);
}
/// Toggle row for CKYC consent switch.
Widget _buildSwitchRow(
String label,
bool value,
ValueChanged<bool> onChanged,
) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Row(
children: [
SizedBox(
width: 130,
child: Text(
label,
style: const TextStyle(fontSize: 15),
),
),
Switch(
value: value,
onChanged: onChanged,
activeColor: Colors.green,
),
],
),
);
}
/// Full-width outlined action button using default Flutter style.
Widget _buildActionButton({
required String label,
required VoidCallback onPressed,
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: SizedBox(
width: double.infinity,
height: 48,
child: OutlinedButton(
onPressed: onPressed,
child: Text(
label,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
void _showAlertDialog(String title, String content) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(title),
content: SingleChildScrollView(
child: Text(content),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
);
},
);
}
@override
generateHash(Map response) {
// Pass response param to your backend server; sample uses salt from form.
final hashResponse = HashService.generateHash(
response,
merchantSalt: _saltController.text.trim(),
);
_ppi.hashGenerated(hash: hashResponse);
}
@override
onCancel() {
_refreshRequestId();
_showAlertDialog('onCancel', 'Cancel By user');
}
@override
onSuccess() {
_refreshRequestId();
_showAlertDialog('onSuccess', 'Flow completed successfully');
}
@override
onError(Map? response) {
_refreshRequestId();
_showAlertDialog('onError', response.toString());
}
}