moneris_payment 1.0.0 copy "moneris_payment: ^1.0.0" to clipboard
moneris_payment: ^1.0.0 copied to clipboard

A Flutter package for integrating Moneris Checkout payment gateway. Supports PCI-compliant payments, tokenization, and comprehensive error handling.

Moneris Payment for Flutter #

A Flutter package for integrating the Moneris Checkout payment gateway. This package provides a complete payment solution with PCI-compliant payment processing, comprehensive error handling, and easy integration.

Features #

  • ✅ PCI-Compliant Payment Processing - Secure payment handling through Moneris hosted pages
  • ✅ Test & Production Environments - Support for both QA and production modes
  • ✅ Comprehensive Error Handling - Detailed error messages and callback handling
  • ✅ Tokenization Support - Store card details for recurring payments
  • ✅ Customizable UI - Cancel buttons, loading states, and debug modes
  • ✅ Cross-Platform - Works on both Android and iOS
  • ✅ Well Documented - Complete integration guide with examples

Installation #

Add to your pubspec.yaml:

dependencies:
	moneris_payment: ^1.0.0

Then run:

flutter pub get

Platform Configuration #

Android #

Add these 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
		minSdkVersion 19  # webview_flutter minimum requirement
		targetSdkVersion 33
}

iOS #

Add these to ios/Runner/Info.plist:

<dict>
		<key>NSAppTransportSecurity</key>
		<dict>
				<key>NSAllowsArbitraryLoads</key>
				<true/>
		</dict>
		<key>io.flutter.embedded_views_preview</key>
		<true/>
</dict>

Update ios/Podfile:

platform :ios, '11.0'  # webview_flutter minimum requirement

Backend Integration #

You need two backend endpoints to communicate with Moneris: a preload endpoint (generates a checkout ticket) and a receipt endpoint (fetches transaction details after payment).

Important: Keep your Moneris credentials (Store ID, API Token, Checkout ID) on the backend only. Never expose them in client-side code.

1) 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):

// 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 (use in test mode):

  • Visa: 4242424242424242
  • MasterCard: 5555555555554444
  • AMEX: 378282246310005
  • Expiry: 12/25
  • CVD: 123

Moneris Setup #

  • Contact Moneris to get your Store ID, API Token, and Checkout ID
  • Set up your merchant account in Moneris MRC (test) or Moneris Production (live)
  • Configure AVS, CVD, and 3-D Secure as needed
  • Enable tokenization for recurring payments if you plan to store cards
  • Configure webhook URLs for server-to-server notifications (optional but recommended)

Troubleshooting #

Common issues and tips:

  • CSP Errors: If you see Content Security Policy errors, the package automatically handles them with fallback methods.
  • Network Errors: Ensure your backend endpoints are accessible and returning proper JSON responses.
  • Invalid Order ID: Make sure order IDs are unique and follow the format requirements.
  • Payment Declined: Use test credit cards in test mode. Check Moneris dashboard for decline reasons.

Debug Mode #

Enable debug logs to see detailed payment flow:

MonerisPaymentWidget(
	enableDebugLogs: true,
	// ... other parameters
)

Support #

💬 Need Help?

If you need assistance with integration, have questions, or want to report an issue, feel free to contact me!

Email: mezbahurict12@gmail.com

Please include:

  • Your Flutter version
  • Package version
  • Error messages or logs
  • Steps to reproduce the issue

License #

Copyright (c) 2025 EZ

Licensed under the BSD 3-Clause License


Changelog #

1.0.0 #

  • Initial release with complete Moneris Checkout integration
  • Support for test and production environments
  • Comprehensive error handling and callbacks
  • Cross-platform compatibility (Android & iOS)

Happy Coding! 🚀

3
likes
0
points
17
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for integrating Moneris Checkout payment gateway. Supports PCI-compliant payments, tokenization, and comprehensive error handling.

License

unknown (license)

Dependencies

flutter, http, webview_flutter

More

Packages that depend on moneris_payment