๐ณ Moneris Payment for Flutter
A modern, secure Flutter package for seamless Moneris Checkout integration.
Getting Started โข Examples โข Documentation โข Support
โจ Features
๐ PCI-Compliant Processing
- Secure payment handling through Moneris hosted pages
- Bank-grade security standards
๐ Dual Environment Support
- Test/QA environment for development
- Production mode for live transactions
๐ก๏ธ Error Handling & Validation
- Comprehensive error messages
- Smart callback system
- Detailed transaction logging
๐ Advanced Features
- Tokenization for recurring payments
- AVS/CVD verification support
- 3D-Secure ready
๐จ Rich UI Components
- Customizable cancel buttons
- Loading state indicators
- Debug mode toggles
๐ฑ Cross-Platform
- Full Android support
- Complete iOS compatibility
- Responsive design
๐ Developer Experience
- Extensive documentation
- Code examples
- Integration guides
๐ฆ Installation
1๏ธโฃ Add to your pubspec.yaml:
dependencies:
moneris_payment: ^1.0.2
2๏ธโฃ Run in your terminal:
$ flutter pub get
3๏ธโฃ Import in your Dart code:
import 'package:moneris_payment/moneris_payment.dart';
๐ ๏ธ Platform Configuration
๐ฑ Android Setup
- Add permissions to
android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:label="Your App Name"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
</activity>
</application>
</manifest>
- Update
android/app/build.gradle:
android {
compileSdkVersion 33
defaultConfig {
minSdkVersion 19 // Required for WebView
targetSdkVersion 33
}
}
๐ iOS Setup
- Update
ios/Runner/Info.plist:
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>io.flutter.embedded_views_preview</key>
<true/>
</dict>
- Configure
ios/Podfile:
platform :ios, '11.0' # Required for WebView support
๐ Backend Integration
Two backend endpoints are required for Moneris integration:
- ๐๏ธ Preload Endpoint: Generates checkout tickets
- ๐ Receipt Endpoint: Fetches transaction details
๐ SECURITY ALERT: Never expose Moneris credentials (Store ID, API Token, Checkout ID) in client-side code.
๐๏ธ Preload Endpoint
This endpoint generates a Moneris checkout ticket. The Flutter client calls this endpoint to get a ticket used to open the hosted Moneris page.
Node.js example (Express + axios):
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
// Your Moneris credentials
const STORE_ID = 'your_store_id';
const API_TOKEN = 'your_api_token';
const CHECKOUT_ID = 'your_checkout_id';
const MONERIS_TEST_URL = 'https://gatewayt.moneris.com/chkt/request/request.php';
const MONERIS_PROD_URL = 'https://gateway.moneris.com/chkt/request/request.php';
// Endpoint: Flutter calls this to get ticket (Preload)
app.post('/preload', async (req, res) => {
const {
txn_total,
order_no,
cust_id,
contact_details
} = req.body;
const preloadData = {
store_id: STORE_ID,
api_token: API_TOKEN,
checkout_id: CHECKOUT_ID,
txn_total: Number(txn_total).toFixed(2),
environment: 'qa', // 'prod' for production
action: 'preload',
order_no: order_no || '',
cust_id: cust_id || '',
contact_details: contact_details || {},
};
try {
const response = await axios.post(MONERIS_TEST_URL, preloadData, {
headers: { 'Content-Type': 'application/json' },
});
if (response.data.response.success === 'true') {
res.json({ ticket: response.data.response.ticket });
} else {
res.status(400).json({ error: response.data.response.error });
}
} catch (error) {
console.error('Preload error:', error);
res.status(500).json({ error: 'Preload failed' });
}
});
PHP example:
<?php
// preload.php
$STORE_ID = 'your_store_id';
$API_TOKEN = 'your_api_token';
$CHECKOUT_ID = 'your_checkout_id';
$MONERIS_URL = 'https://gatewayt.moneris.com/chkt/request/request.php';
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
$preloadData = [
'store_id' => $STORE_ID,
'api_token' => $API_TOKEN,
'checkout_id' => $CHECKOUT_ID,
'txn_total' => number_format($input['txn_total'], 2, '.', ''),
'environment' => 'qa',
'action' => 'preload',
'order_no' => $input['order_no'] ?? '',
'cust_id' => $input['cust_id'] ?? '',
'contact_details' => $input['contact_details'] ?? []
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $MONERIS_URL);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($preloadData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response) {
$data = json_decode($response, true);
if ($data['response']['success'] === 'true') {
echo json_encode(['ticket' => $data['response']['ticket']]);
} else {
http_response_code(400);
echo json_encode(['error' => $data['response']['error']]);
}
} else {
http_response_code(500);
echo json_encode(['error' => 'Preload failed']);
}
?>
2) Receipt Endpoint
This endpoint fetches transaction details after payment completion. The Flutter client calls this endpoint with the ticket returned from the hosted Moneris page.
Node.js example:
// Endpoint: Flutter calls this after payment complete to get receipt details
app.post('/receipt', async (req, res) => {
const { ticket } = req.body;
const receiptData = {
store_id: STORE_ID,
api_token: API_TOKEN,
checkout_id: CHECKOUT_ID,
ticket,
environment: 'qa', // 'prod' for production
action: 'receipt',
};
try {
const response = await axios.post(MONERIS_TEST_URL, receiptData, {
headers: { 'Content-Type': 'application/json' },
});
if (response.data.response.success === 'true') {
const transactionDetails = response.data.response.receipt;
// Extract important transaction details
const result = {
transaction_no: transactionDetails.transaction_no,
reference_no: transactionDetails.reference_no,
card_type: transactionDetails.card_type,
first6last4: transactionDetails.first6last4,
expiry_date: transactionDetails.expiry_date,
amount: transactionDetails.amount,
response_code: transactionDetails.response_code,
result: transactionDetails.result,
data_key: transactionDetails.tokenize?.data_key, // For tokenization
};
res.json({ transactionDetails: result });
} else {
res.status(400).json({ error: 'Receipt fetch failed' });
}
} catch (error) {
console.error('Receipt error:', error);
res.status(500).json({ error: 'Receipt failed' });
}
});
PHP example:
<?php
// receipt.php
$STORE_ID = 'your_store_id';
$API_TOKEN = 'your_api_token';
$CHECKOUT_ID = 'your_checkout_id';
$MONERIS_URL = 'https://gatewayt.moneris.com/chkt/request/request.php';
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
$ticket = $input['ticket'];
$receiptData = [
'store_id' => $STORE_ID,
'api_token' => $API_TOKEN,
'checkout_id' => $CHECKOUT_ID,
'ticket' => $ticket,
'environment' => 'qa',
'action' => 'receipt'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $MONERIS_URL);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($receiptData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response) {
$data = json_decode($response, true);
if ($data['response']['success'] === 'true') {
$receipt = $data['response']['receipt'];
$transactionDetails = [
'transaction_no' => $receipt['transaction_no'],
'reference_no' => $receipt['reference_no'],
'card_type' => $receipt['card_type'],
'first6last4' => $receipt['first6last4'],
'expiry_date' => $receipt['expiry_date'],
'amount' => $receipt['amount'],
'response_code' => $receipt['response_code'],
'result' => $receipt['result'],
'data_key' => $receipt['tokenize']['data_key'] ?? null,
];
echo json_encode(['transactionDetails' => $transactionDetails]);
} else {
http_response_code(400);
echo json_encode(['error' => 'Receipt fetch failed']);
}
} else {
http_response_code(500);
echo json_encode(['error' => 'Receipt failed']);
}
?>
Flutter Usage
Below are basic and advanced usage examples for integrating the MonerisPaymentWidget in your Flutter app.
Basic Implementation
import 'package:flutter/material.dart';
import 'package:moneris_payment/moneris_payment.dart';
class PaymentScreen extends StatefulWidget {
const PaymentScreen({super.key});
@override
State<PaymentScreen> createState() => _PaymentScreenState();
}
class _PaymentScreenState extends State<PaymentScreen> {
String _generateOrderId() {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final random = _generateRandomString(6);
return 'ORD_${timestamp}_$random';
}
String _generateRandomString(int length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
final random = Random();
return String.fromCharCodes(
Iterable.generate(
length,
(_) => chars.codeUnitAt(random.nextInt(chars.length))
)
);
}
void _showPaymentDialog(BuildContext context) {
final orderId = _generateOrderId();
final customerId = 'CUST_${_generateRandomString(8)}';
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Complete Payment'),
content: SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
width: MediaQuery.of(context).size.width * 0.9,
child: MonerisPaymentWidget(
preloadUrl: 'https://your-backend.com/preload',
receiptUrl: 'https://your-backend.com/receipt',
txnTotal: 99.99,
orderNo: orderId, // MUST be unique for each transaction
custId: customerId, // Should identify the customer
email: 'customer@example.com',
isTestMode: true, // Set to false for production
onSuccess: (details) {
print('Payment Success: ${details['transaction_no']}');
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Payment Successful! Transaction: ${details['transaction_no']}'),
backgroundColor: Colors.green,
),
);
},
onError: (error) {
print('Payment Error: $error');
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Payment Failed: $error'),
backgroundColor: Colors.red,
),
);
},
onCancel: () {
print('Payment cancelled by user');
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Payment Cancelled')),
);
},
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Payment')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => _showPaymentDialog(context),
child: const Text('Make Payment'),
),
const SizedBox(height: 20),
const Text('Test Credit Cards:'),
const Text('Visa: 4242424242424242'),
const Text('Expiry: 12/25, CVD: 123'),
],
),
),
);
}
}
Advanced Implementation with Order Management
class CheckoutPage extends StatefulWidget {
final double totalAmount;
final String userEmail;
final String userId;
const CheckoutPage({
super.key,
required this.totalAmount,
required this.userEmail,
required this.userId,
});
@override
State<CheckoutPage> createState() => _CheckoutPageState();
}
class _CheckoutPageState extends State<CheckoutPage> {
late String _orderId;
late String _customerId;
@override
void initState() {
super.initState();
_orderId = _generateOrderId();
_customerId = 'USER_${widget.userId}';
// Pre-create order in your database
_createOrderRecord(_orderId);
}
String _generateOrderId() {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final randomSuffix = Random().nextInt(9999).toString().padLeft(4, '0');
return 'ECO_${widget.userId}_${timestamp}_$randomSuffix';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Checkout')),
body: MonerisPaymentWidget(
preloadUrl: 'https://your-backend.com/preload',
receiptUrl: 'https://your-backend.com/receipt',
txnTotal: widget.totalAmount,
orderNo: _orderId,
custId: _customerId,
email: widget.userEmail,
isTestMode: false, // Production mode
enableDebugLogs: true, // Enable for debugging
cancelButtonText: 'Cancel Order',
showCancelButtonOnLoading: true,
showCancelButtonOnPayment: true,
onSuccess: (details) {
// Update order status in your database
_updateOrderStatus(_orderId, 'SUCCESS', details);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => OrderConfirmationPage(
orderId: _orderId,
transactionDetails: details,
),
),
);
},
onError: (error) {
// Update order status to failed
_updateOrderStatus(_orderId, 'FAILED', null, error);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Payment Failed: $error'),
backgroundColor: Colors.red,
),
);
},
onCancel: () {
// Update order status to cancelled
_updateOrderStatus(_orderId, 'CANCELLED');
Navigator.pop(context);
},
),
);
}
void _createOrderRecord(String orderId) {
// Implement your order creation logic
print('Creating order: $orderId');
}
void _updateOrderStatus(String orderId, String status,
[Map<String, dynamic>? details, String? error]) {
// Implement your order update logic
print('Updating order $orderId to $status');
}
}
Important Notes
Order ID Requirements
- MUST be unique for each transaction
- Never reuse order IDs
- Store in your database before starting payment
- Maximum 50 characters
- Format: Alphanumeric and underscores only
Customer ID Requirements
- Should identify the customer
- Can be user ID, session ID, or guest ID
- Maximum 50 characters
Security Best Practices
- Keep your Moneris credentials secure on the backend
- Never expose API tokens in client-side code
- Use HTTPS in production
- Validate all input data on your backend
- Implement proper error logging
๐งช Testing
๐ณ Test Credit Cards
| Card Type | Number | Expiry | CVD |
|---|---|---|---|
| Visa | 4242 4242 4242 4242 |
12/25 |
123 |
| MasterCard | 5555 5555 5555 4444 |
12/25 |
123 |
| AMEX | 3782 822463 10005 |
12/25 |
123 |
๐ง Moneris Setup
-
๐ Get Your Credentials
- Contact Moneris for Store ID, API Token, and Checkout ID
- Choose between Test/Production environment
-
๐ฆ Account Setup
- Configure Moneris MRC for testing
- Set up Production account for live transactions
-
๐ก๏ธ Security Configuration
- Enable AVS (Address Verification)
- Configure CVD validation
- Set up 3-D Secure if needed
-
๐ Advanced Features
- Configure tokenization for recurring payments
- Set up webhook URLs for notifications
- Test transaction flows
โ ๏ธ Troubleshooting
๐ซ Common Issues & Solutions
CSP (Content Security Policy)
- โ Package handles CSP errors automatically
- โ No additional configuration needed
๐ Network Issues
- โ Verify backend endpoint accessibility
- โ Check JSON response format
- โ Validate SSL certificates
๐ Order ID Problems
- โ Ensure unique IDs for each transaction
- โ Follow format requirements
- โ Verify database storage
๐ณ Payment Declined
- โ Use test cards in test mode
- โ Check Moneris dashboard
- โ Verify amount format
๐ Debug Mode
Enable detailed logging:
MonerisPaymentWidget(
enableDebugLogs: true, // Enable debugging
isTestMode: true, // Use test environment
// ... other parameters
)
๐ฌ Support
|
๐ง Email Support earbajsaria3@gmail.com |
๐ป GitHub Issues Create an Issue |
๐ญ Discord Chat Join Community |
๐ When Reporting Issues
Please include:
- Flutter version:
flutter --version - Package version:
pubspec.yamlentry - Error logs (if any)
- Steps to reproduce
- Expected vs actual behavior
๐ License
๐ Changelog
Version 1.0.0 - October 2025
โจ Initial Release
- ๐ Complete Moneris Checkout integration
- ๐ Test & Production environment support
- ๐ก๏ธ Comprehensive error handling
- ๐ฑ Cross-platform compatibility
- ๐ Full documentation
๐จ Technical Updates
- Implemented secure payment processing
- Added callback system
- Integrated WebView handling
- Enhanced error reporting
Version 1.0.1 - January 2026
๐จ Technical Updates
- Update readme
- and fix some known issue
Made with โค๏ธ for the Flutter Community
โญ Found it helpful? Star us on GitHub! โญ