Lyfecycle Payments SDK for Flutter
The Lyfecycle Payments SDK provides a Flutter interface for integrating secure payment processing into mobile applications on iOS and Android. It supports one-time and recurring payments, card and ACH transactions, 3D Secure authentication, and customizable UI elements, billing fields, and metadata. The SDK offers two payment flows: makePayment (direct payment with API keys) and makePaymentV2 (client token-based payment). This README provides a comprehensive guide to installing, configuring, and using the SDK, with example code and troubleshooting tips.
Installation
Prerequisites
- Flutter: Version 3.0.0 or higher.
- Dart: Version 2.17.0 or higher.
- Android:
- Android Studio with the Flutter plugin.
- Minimum SDK version 21.
- iOS:
- Xcode 14.0 or higher.
- Ruby 3.2.8 or higher (for CocoaPods).
- Credentials:
apiKeyandsecretKeyfor themakePaymentflow.clientTokenfor themakePaymentV2flow (obtained externally, e.g., from your backend).- Contact Lyfecycle Payments support for credentials.
Android Setup
-
Add the SDK to
pubspec.yaml:dependencies: easymerchantsdk: ^1.3.6Run
flutter pub getto install the package. -
Update
build.gradle: Inandroid/build.gradle, add the following to theallprojects.repositoriessection:allprojects { repositories { google() mavenCentral() maven { url 'https://jitpack.io' } maven { url = uri('https://maven.pkg.github.com/your-org/easymerchantsdk') credentials { username = 'your-github-username' password = 'your-github-token' } } } }Replace
your-org,your-github-username, andyour-github-tokenwith values provided by Lyfecycle Payments. -
Add Permissions: In
android/app/src/main/AndroidManifest.xml, add:<uses-permission android:name="android.permission.INTERNET" /> -
Sync Project: Run
flutter pub getand ensure the Android project builds successfully.
iOS Setup
-
Add Permissions: Update
ios/Runner/Info.plistto allow network access:<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict> -
Podfile Configuration: Update
ios/Podfileto include theLyfecycle Paymentsdependency. Runpod installin theiosdirectory: -
Install Pods: Navigate to the
iosdirectory and run:cd ios pod install -
Initialize ViewController: In
ios/Runner/AppDelegate.swift, ensure the SDK’s ViewController is initialized. Example:import UIKit import Flutter import EasyMerchantSDK @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } -
Verify Setup: Build and run the iOS app to ensure no integration errors occur.
Note: Refer to the official Lyfecycle Payments documentation for additional iOS-specific setup details or contact support for assistance.
Usage
The Lyfecycle Payments SDK supports two payment flows:
makePayment: Direct payment usingapiKeyandsecretKey. Requires environment initialization.makePaymentV2: Client token-based payment using aclientTokenprovided externally (e.g., from your backend). No environment initialization required.
Initializing the SDK
Import the SDK and set up event listeners for payment events. Initialize the ViewController for iOS.
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:easymerchantsdk/easymerchantsdk.dart';
class PaymentView extends StatefulWidget {
const PaymentView({super.key});
@override
_PaymentViewState createState() => _PaymentViewState();
}
class _PaymentViewState extends State<PaymentView> {
final Easymerchantsdk easymerchant = Easymerchantsdk();
final TextEditingController amountController = TextEditingController(text: '10.00');
final TextEditingController emailController = TextEditingController(text: 'test@example.com');
final TextEditingController nameController = TextEditingController(text: 'Test User');
final TextEditingController clientTokenController = TextEditingController();
String result = 'No response yet';
String referenceToken = '';
bool isLoading = false;
bool isRecurring = false;
@override
void initState() {
super.initState();
_setupEventListeners();
if (Platform.isIOS) {
_setViewController();
}
}
void _setupEventListeners() {
easymerchant.setupEventListeners(
onPaymentSuccess: (data) {
debugPrint('🎉 Payment Success: $data');
setState(() => isLoading = false);
try {
final decoded = jsonDecode(data);
setState(() {
result = const JsonEncoder.withIndent(' ').convert(decoded);
referenceToken = decoded['secure_token'] ?? decoded['ref_token'] ?? '';
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Payment Successful!')),
);
} catch (e) {
debugPrint('❌ Error parsing success: $e');
setState(() {
result = 'Payment success but error parsing response: $e';
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Payment success, but parsing error.')),
);
}
},
onPaymentStatusError: (error) {
debugPrint('❌ Payment Error: $error');
setState(() {
isLoading = false;
result = 'Error: $error';
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Payment failed: $error')),
);
},
onPaymentPending: (data) {
debugPrint('⏳ Payment Pending: $data');
setState(() {
result = 'Payment Pending: $data';
});
},
);
}
Future<void> _setViewController() async {
try {
await easymerchant.setViewController();
debugPrint('ViewController initialized');
} catch (e) {
debugPrint('Failed to initialize ViewController: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to initialize ViewController: $e')),
);
}
}
}
Configuring the Environment
For makePayment, configure the environment with apiKey and secretKey. This step is required for iOS and optional for Android.
Future<void> _configureEnvironment() async {
setState(() => isLoading = true);
try {
final keys = {
'apiKey': 'your-api-key',
'secretKey': 'your-secret-key',
};
await easymerchant.configureEnvironment(
'sandbox', // or 'production'
keys['apiKey']!,
keys['secretKey']!,
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Environment configured')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Configuration error: $e')),
);
} finally {
setState(() => isLoading = false);
}
}
Starting a Payment
makePayment (Direct Payment)
The makePayment flow requires apiKey and secretKey in the configuration and environment initialization. The config is JSON-encoded for both platforms.
Future<void> handlePayment() async {
if (amountController.text.isEmpty || double.tryParse(amountController.text) == null || double.parse(amountController.text) <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please enter a valid amount')),
);
return;
}
if (paymentConfig['paymentMethods'].isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Select at least one payment method')),
);
return;
}
if (isRecurring && recurringData['intervals'].isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Select at least one recurring interval')),
);
return;
}
setState(() => isLoading = true);
try {
final config1 = {
'environment': 'sandbox',
'apiKey': 'your-api-key',
'secretKey': 'your-secret-key',
'amount': double.parse(amountController.text),
'currency': 'usd',
'tokenOnly':false,
'email': emailController.text,
'name': nameController.text.isNotEmpty ? nameController.text : 'Test User',
'paymentMethods': ['card', 'ach'],
'saveCard': true,
'saveAccount': true,
'authenticatedACH': true,
'secureAuthentication': true,
'showReceipt': true,
'showDonate': false,
'showTotal': true,
'showSubmitButton': true,
'isEmail': true,
'is_recurring': isRecurring,
'numOfCycle': isRecurring ? 2 : null, // if is_recurring == true
'recurringIntervals': isRecurring ? ['weekly', 'monthly'] : null, // if is_recurring == true
'recurringStartDateType': isRecurring ? 'custom' : null, // if is_recurring == true
'recurringStartDate': isRecurring ? '09/04/2025' : null, // if is_recurring == true
'fields': {
'visibility': {'billing': false, 'additional': false},
'billing': [
{'name': 'address', 'required': true, 'value': '123 Main Street'},
{'name': 'country', 'required': true, 'value': 'United States'},
{'name': 'state', 'required': true, 'value': 'California'},
{'name': 'city', 'required': false, 'value': 'San Francisco'},
{'name': 'postal_code', 'required': true, 'value': '94105'},
],
'additional': [
{'name': 'phone_number', 'required': false, 'value': '+1-555-123-4567'},
{'name': 'description', 'required': true, 'value': 'Test payment'},
],
},
'appearanceSettings': {
'theme': 'light',
'bodyBackgroundColor': '#121212',
'containerBackgroundColor': '#1E1E1E',
'primaryFontColor': '#FFFFFF',
'secondaryFontColor': '#B0B0B0',
'primaryButtonBackgroundColor': '#2563EB',
'primaryButtonHoverColor': '#1D4ED8',
'primaryButtonFontColor': '#FFFFFF',
'secondaryButtonBackgroundColor': '#374151',
'secondaryButtonHoverColor': '#4B5563',
'secondaryButtonFontColor': '#E5E7EB',
'borderRadius': '8',
'fontSize': '16',
'fontWeight': '500',
'fontFamily': '"Inter", sans-serif',
},
'grailPayParams': {
'role': 'business',
'timeout': 10,
'brandingName': 'Lyfecycle Payments',
'finderSubtitle': 'Search for your bank Edg',
'searchPlaceholder': 'Enter bank name',
},
'metadata': {
'metadataOne': 'valueOne',
'metadataTwo': 'valueTwo',
},
};
final configJson = jsonEncode(config1);
await easymerchant.makePayment(configJson);
} catch (e) {
setState(() {
result = 'Payment Error: $e';
isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Payment failed: $e')),
);
}
}
makePaymentV2 (Client Token-Based Payment)
The makePaymentV2 flow requires a clientToken provided externally (e.g., from your backend). Environment initialization is not required. The config is JSON-encoded for both platforms.
Future<void> handlePaymentV2() async {
if (clientTokenController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please provide a client token')),
);
return;
}
if (paymentConfig['paymentMethods'].isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Select at least one payment method')),
);
return;
}
if (isRecurring && recurringData['intervals'].isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Select at least one recurring interval')),
);
return;
}
setState(() => isLoading = true);
try {
final config2 = {
'environment': 'sandbox',
'clientToken': clientTokenController.text,
'currency': 'usd',
'email': emailController.text,
'name': nameController.text.isNotEmpty ? nameController.text : 'Test User',
'paymentMethods': ['card', 'ach'],
'saveCard': true,
'saveAccount': true,
'authenticatedACH': true,
'secureAuthentication': true,
'showReceipt': true,
'showDonate': false,
'showTotal': true,
'showSubmitButton': true,
'isEmail': true,
'fields': {
'visibility': {'billing': false, 'additional': false},
'billing': [
{'name': 'address', 'required': true, 'value': '123 Main Street'},
{'name': 'country', 'required': true, 'value': 'United States'},
{'name': 'state', 'required': true, 'value': 'California'},
{'name': 'city', 'required': false, 'value': 'San Francisco'},
{'name': 'postal_code', 'required': true, 'value': '94105'},
],
'additional': [
{'name': 'phone_number', 'required': false, 'value': '+1-555-123-4567'},
{'name': 'description', 'required': true, 'value': 'Test payment'},
],
},
'appearanceSettings': {
'theme': 'light',
'bodyBackgroundColor': '#121212',
'containerBackgroundColor': '#1E1E1E',
'primaryFontColor': '#FFFFFF',
'secondaryFontColor': '#B0B0B0',
'primaryButtonBackgroundColor': '#2563EB',
'primaryButtonHoverColor': '#1D4ED8',
'primaryButtonFontColor': '#FFFFFF',
'secondaryButtonBackgroundColor': '#374151',
'secondaryButtonHoverColor': '#4B5563',
'secondaryButtonFontColor': '#E5E7EB',
'borderRadius': '8',
'fontSize': '16',
'fontWeight': '500',
'fontFamily': '"Inter", sans-serif',
},
'grailPayParams': {
'role': 'business',
'timeout': 10,
'brandingName': 'Lyfecycle Payments',
'finderSubtitle': 'Search for your bank',
'searchPlaceholder': 'Enter bank name',
},
'metadata': {
'metadataOne': 'valueOne',
'metadataTwo': 'valueTwo',
},
};
final configJson = jsonEncode(config2);
await easymerchant.makePaymentV2(configJson);
} catch (e) {
setState(() {
result = 'Payment Error: $e';
isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Payment failed: $e')),
);
}
}
✅ Success Response From the SDK :-
{
"status": true,
"message": "Payment processed successfully.",
"charge_id": "cha_517268bad50fc1b9b",
"data": "NA",
"last_4": "4242",
"card_last_4": "4242",
"card_brand_name": "Visa",
"exp_month": "02",
"exp_year": "2029",
"payment_method": "card",
// NOTE: card_id exists ONLY when the card is saved
// or when making a payment with a previously saved card
"card_id": "card_abcdef9876543210",
// NOTE: billingInfo will only be included
// if billing details were passed in the payment config
"billingInfo": {
"address": "123 Main Street, Suite 100",
"country": "United States",
"state": "California",
"city": "San Francisco",
"postal_code": "94105"
},
// NOTE: additionalInfo will only be included
// if additional details (like phone_number, description) were passed in the payment config
"additionalInfo": {
"phone_number": "+19999999999",
"description": "Test payment for development purposes"
}
}
✅ Error Response from the SDK :-
{
"status": false,
"message": "Error message here"
}
Checking Payment Reference (iOS Only)
Retrieve payment details using a reference token on iOS.
Future<void> checkPaymentReference() async {
if (referenceToken.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No reference token available')),
);
return;
}
try {
final response = await easymerchant.paymentReference(referenceToken);
setState(() {
result = const JsonEncoder.withIndent(' ').convert(jsonDecode(response!));
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Payment reference retrieved')),
);
} catch (e) {
setState(() {
result = 'Payment reference failed: $e';
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Payment reference failed: $e')),
);
}
}
Configuration Parameters
Environment Configuration Parameters
Used in configureEnvironment for makePayment.
| Parameter | Type | Description | Example Value |
|---|---|---|---|
environment |
String | Environment: sandbox or production. |
sandbox |
apiKey |
String | API key for authentication. | your-api-key |
secretKey |
String | Secret key for authentication. | your-secret-key |
makePayment Parameters
Used in the makePayment method for both iOS and Android.
| Parameter | Type | Description | Example Value |
|---|---|---|---|
environment |
String | Environment: sandbox or production. |
sandbox |
apiKey |
String | API key for authentication. | your-api-key |
secretKey |
String | Secret key for authentication. | your-secret-key |
amount |
double | Payment amount (must be > 0). | 10.00 |
currency |
String | Currency code (e.g., usd). |
usd |
email |
String | Email address for the transaction. | test@example.com |
name |
String | Name associated with the transaction. | Test User |
paymentMethods |
List | Payment methods: card, ach. |
['card', 'ach'] |
saveCard |
bool | Save card details. | true |
saveAccount |
bool | Save account details. | true |
authenticatedACH |
bool | Enable authenticated ACH payments. | true |
secureAuthentication |
bool | Enable 3D Secure authentication. | true |
showReceipt |
bool | Show payment receipt. | true |
showDonate |
bool | Show donate option. | false |
showTotal |
bool | Show total amount in UI. | true |
showSubmitButton |
bool | Show submit button in UI. | true |
isEmail |
bool | Allow email field to be editable. | true |
is_recurring |
bool | Enable recurring payments. | false |
numOfCycle |
int? | Number of recurring cycles (if is_recurring). |
2 |
recurringIntervals |
List | Recurring intervals: daily, weekly, monthly (if is_recurring). |
['weekly', 'monthly'] |
recurringStartDateType |
String? | Recurring start date type: custom, fixed (if is_recurring). |
custom |
recurringStartDate |
String? | Start date for recurring payments: MM/DD/YYYY (if is_recurring). |
09/04/2025 |
fields |
Map<String, dynamic> | Billing and additional field configurations. | See Fields Configuration |
appearanceSettings |
Map<String, dynamic> | UI theme settings. | See Appearance Settings |
grailPayParams |
Map<String, dynamic> | GrailPay-specific parameters. | See GrailPay Parameters |
metadata |
Map<String, dynamic> | Additional metadata for the transaction. | See Metadata Parameters |
makePaymentV2 Parameters
Used in the makePaymentV2 method for both iOS and Android.
| Parameter | Type | Description | Example Value |
|---|---|---|---|
environment |
String | Environment: sandbox or production. |
sandbox |
clientToken |
String | Client token obtained externally (e.g., from your backend). | your-client-token |
currency |
String | Currency code (e.g., usd). |
usd |
email |
String | Email address for the transaction. | test@example.com |
name |
String | Name associated with the transaction. | Test User |
paymentMethods |
List | Payment methods: card, ach. |
['card', 'ach'] |
saveCard |
bool | Save card details. | true |
saveAccount |
bool | Save account details. | true |
authenticatedACH |
bool | Enable authenticated ACH payments. | true |
secureAuthentication |
bool | Enable 3D Secure authentication. | true |
showReceipt |
bool | Show payment receipt. | true |
showDonate |
bool | Show donate option. | false |
showTotal |
bool | Show total amount in UI. | true |
showSubmitButton |
bool | Show submit button in UI. | true |
isEmail |
bool | Allow email field to be editable. | true |
fields |
Map<String, dynamic> | Billing and additional field configurations. | See Fields Configuration |
appearanceSettings |
Map<String, dynamic> | UI theme settings. | See Appearance Settings |
grailPayParams |
Map<String, dynamic> | GrailPay-specific parameters. | See GrailPay Parameters |
metadata |
Map<String, dynamic> | Additional metadata for the transaction. | See Metadata Parameters |
Fields Configuration
Used in both makePayment and makePaymentV2.
| Parameter | Type | Description | Example Value |
|---|---|---|---|
visibility |
Map<String, bool> | Visibility of billing/additional fields. | {'billing': false, 'additional': false} |
billing |
List<Map<String, dynamic>> | Billing fields with name, required status, and value. | See example below |
additional |
List<Map<String, dynamic>> | Additional fields with name, required status, and value. | See example below |
Example:
{
'visibility': {'billing': false, 'additional': false},
'billing': [
{'name': 'address', 'required': true, 'value': '123 Main Street'},
{'name': 'country', 'required': true, 'value': 'United States'},
{'name': 'state', 'required': true, 'value': 'California'},
{'name': 'city', 'required': false, 'value': 'San Francisco'},
{'name': 'postal_code', 'required': true, 'value': '94105'},
],
'additional': [
{'name': 'phone_number', 'required': false, 'value': '+1-555-123-4567'},
{'name': 'description', 'required': true, 'value': 'Test payment'},
],
}
Appearance Settings
Customize the UI for both iOS and Android.
| Parameter | Type | Description | Example Value |
|---|---|---|---|
theme |
String | Theme setting (e.g., light, dark). |
light |
bodyBackgroundColor |
String | Background color of the body (hex). | #121212 |
containerBackgroundColor |
String | Background color of containers (hex). | #1E1E1E |
primaryFontColor |
String | Primary font color (hex). | #FFFFFF |
secondaryFontColor |
String | Secondary font color (hex). | #B0B0B0 |
primaryButtonBackgroundColor |
String | Background color of primary buttons (hex). | #2563EB |
primaryButtonHoverColor |
String | Hover color for primary buttons (hex). | #1D4ED8 |
primaryButtonFontColor |
String | Font color for primary buttons (hex). | #FFFFFF |
secondaryButtonBackgroundColor |
String | Background color of secondary buttons (hex). | #374151 |
secondaryButtonHoverColor |
String | Hover color for secondary buttons (hex). | #4B5563 |
secondaryButtonFontColor |
String | Font color for secondary buttons (hex). | #E5E7EB |
borderRadius |
String | Border radius for UI elements (pixels). | 8 |
fontSize |
String | Font size for text (pixels). | 16 |
fontWeight |
String | Font weight for text. | 500 |
fontFamily |
String | Font family for text. | "Inter", sans-serif |
GrailPay Parameters
Configure GrailPay-specific settings.
| Parameter | Type | Description | Example Value |
|---|---|---|---|
role |
String | Role of the user (e.g., business). |
business |
timeout |
int | Timeout duration in seconds. | 10 |
brandingName |
String | Name for branding in the UI. | Lyfecycle Payments |
finderSubtitle |
String | Subtitle for bank search UI. | Search for your bank |
searchPlaceholder |
String | Placeholder text for bank search input. | Enter bank name |
Recurring Payment Parameters
Configure recurring payments (if is_recurring is true).
| Parameter | Type | Description | Example Value |
|---|---|---|---|
is_recurring |
bool | Enable recurring payments. | false |
numOfCycle |
int? | Number of recurring cycles. | 2 |
recurringIntervals |
List | Recurring intervals: daily, weekly, monthly. |
['weekly', 'monthly'] |
recurringStartDateType |
String? | Start date type: custom, fixed. |
custom |
recurringStartDate |
String? | Start date: MM/DD/YYYY. | 09/04/2025 |
Metadata Parameters
Add custom metadata to transactions.
| Parameter | Type | Description | Example Value |
|---|---|---|---|
metadata |
Map<String, dynamic> | Custom key-value pairs for additional data. | {'metadataOne': 'valueOne', 'metadataTwo': 'valueTwo'} |
Troubleshooting
- MissingPluginException: Ensure
easymerchantsdkis correctly added topubspec.yaml, and Android/iOS native setups are complete. - Invalid Amount: Verify
amountis a valid number greater than 0. - Empty Payment Methods: At least one payment method (
cardorach) must be included inpaymentMethods. - Empty Recurring Intervals: If
is_recurringis true,recurringIntervalsmust include at least one interval. - JSON Serialization Errors: Ensure all configuration maps are properly formatted before calling
jsonEncode. - Authentication Errors: Verify
apiKeyandsecretKeymatch the selected environment formakePayment. - Missing Client Token: For
makePaymentV2, ensure a validclientTokenis provided. - iOS ViewController Issues: Call
setViewControllerbeforemakePaymentormakePaymentV2on iOS. - No Response Timeout: If no response is received within 10 seconds, check the payment screen or network connectivity.
- Contact Support: For further assistance, reach out to Lyfecycle Payments support.