Lyfecycle Payments SDK for Flutter

The Lyfecycle Payments SDK (Flutter) provides a Flutter interface for Lyfecycle native mobile checkout on Android and iOS.

  • Card and ACH payments
  • 3D Secure, recurring payments, saved cards/accounts
  • makePayment — API key + secret key
  • makePaymentV2 — client token from your backend

Pub package name: easymerchantsdk — use this in pubspec.yaml and import 'package:easymerchantsdk/easymerchantsdk.dart';. The product name is Lyfecycle Payments SDK.

Pub.dev: pub.flutter-io.cn/packages/easymerchantsdk
Sample app: em-MobileCheckoutSDK-Flutter / local flutter-sdk-demo-app


Requirements

Platform Minimum
Flutter 3.3+
Dart 3.5+ (sdk: ">=3.5.0 <4.0.0")
Android minSdk 24+
iOS 16.0+, Xcode 14+, CocoaPods

You need Lyfecycle credentials (sandbox / staging / production) from your account team.


1. Add the Lyfecycle Payments SDK

In your app pubspec.yaml (package id easymerchantsdk):

dependencies:
  easymerchantsdk: ^1.3.9
flutter pub get

For local development against this repo:

dependencies:
  easymerchantsdk:
    path: ../em-MobileCheckoutSDK-Flutter

2. Android setup

The plugin depends on the native Android SDK com.app:paysdk hosted on GitHub Packages. Your app must expose Maven credentials at build time.

android/build.gradle or android/build.gradle.kts

Add the GitHub Maven repository (example Kotlin DSL):

// android/build.gradle.kts
val githubMavenUrl = System.getenv("GITHUB_MAVEN_URL")
    ?: "https://maven.pkg.github.com/EasyMerchant/em-MobileCheckoutSDK-Android"
val githubReadActor = System.getenv("GITHUB_READ_ACTOR") ?: ""
val githubReadToken = System.getenv("GITHUB_READ_TOKEN") ?: ""

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
        maven {
            url = uri(githubMavenUrl)
            credentials {
                username = githubReadActor
                password = githubReadToken
            }
        }
    }
}

Groovy build.gradle equivalent:

def githubMavenUrl = System.getenv("GITHUB_MAVEN_URL") ?: "https://maven.pkg.github.com/EasyMerchant/em-MobileCheckoutSDK-Android"
def githubReadActor = System.getenv("GITHUB_READ_ACTOR") ?: ""
def githubReadToken = System.getenv("GITHUB_READ_TOKEN") ?: ""

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
        maven {
            url = uri(githubMavenUrl)
            credentials {
                username = githubReadActor
                password = githubReadToken
            }
        }
    }
}

Environment variables (required for CI and local builds)

export GITHUB_READ_ACTOR="your-github-username-or-bot"
export GITHUB_READ_TOKEN="ghp_xxxxxxxx"   # PAT with read:packages

Optional override:

export GITHUB_MAVEN_URL="https://maven.pkg.github.com/EasyMerchant/em-MobileCheckoutSDK-Android"

AndroidManifest

<uses-permission android:name="android.permission.INTERNET" />

3. iOS setup

Native UI sources are bundled in the plugin (no separate XCFramework download for Flutter).

cd ios
pod install
cd ..

Open ios/Runner.xcworkspace in Xcode (not .xcodeproj).

Info.plist (network)

If you need cleartext HTTP in dev:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

Forward custom URL schemes to the plugin from ios/Runner/AppDelegate.swift:

import Flutter
import UIKit

@main
@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)
  }

  override func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
  ) -> Bool {
    if EasymerchantSdkPlugin.handleDeepLink(url: url) {
      return true
    }
    return super.application(app, open: url, options: options)
  }
}

Register your URL schemes in Info.plist (CFBundleURLTypes) as required by GrailPay / your bank flow.


4. Basic usage

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:easymerchantsdk/easymerchantsdk.dart';

// Lyfecycle Payments SDK entry point (class name matches pub package)
final lyfecyclePaymentsSdk = Easymerchantsdk();

The native SDK also pushes events on a broadcast channel. Set this up once (e.g. in initState):

lyfecyclePaymentsSdk.setupEventListeners(
  onPaymentSuccess: (jsonString) {
    final data = jsonDecode(jsonString);
    debugPrint('Success: $data');
  },
  onPaymentStatusError: (jsonString) {
    final data = jsonDecode(jsonString);
    debugPrint('Error/cancel: $data');
  },
  onPaymentPending: (jsonString) {
    // iOS only: initial hosted form load (status pending in chargeData)
    debugPrint('Pending: $jsonString');
  },
);

Flow A — makePayment (API key + secret)

Step 1 — Configure environment (once per session):

await lyfecyclePaymentsSdk.configureEnvironment(
  'sandbox',           // 'sandbox' | 'staging' | 'production'
  'your-api-key',
  'your-secret-key',
);

Step 2 — Build config JSON and pay:

final config = {
  'environment': 'sandbox',
  'amount': '10.00',
  'currency': 'usd',
  'paymentMethods': ['card', 'ach'],
  'saveCard': true,
  'saveAccount': true,
  'secureAuthentication': true,
  'authenticatedACH': false,
  'showReceipt': true,
  'showTotal': true,
  'showSubmitButton': true,
  'submitButtonText': 'Pay now',
  'email': 'user@example.com',
  'name': 'Jane Doe',
  'fields': {
    'visibility': {'billing': true, 'additional': true},
    'billing': [
      {'name': 'address', 'required': true, 'value': '123 Main St'},
      {'name': 'country', 'required': true, 'value': 'United States'},
      {'name': 'state', 'required': true, 'value': 'California'},
      {'name': 'city', 'required': false, 'value': 'San Francisco'},
      {'name': 'postal_code', 'required': false, 'value': '94105'},
    ],
    'additional': [
      {'name': 'phone_number', 'required': false, 'value': '+1-555-123-4567'},
      {'name': 'description', 'required': false, 'value': 'Order #123'},
    ],
  },
  'appearanceSettings': {
    'theme': 'light',
    'bodyBackgroundColor': '#FFFFFF',
    'primaryButtonBackgroundColor': '#2563EB',
  },
};

final result = await lyfecyclePaymentsSdk.makePayment(jsonEncode(config));
if (result != null) {
  final response = jsonDecode(result);
  // Handle response (see format below)
}

apiKey / secretKey may be included in the JSON for makePayment on some setups; configuring via configureEnvironment is required for the native layer.

Flow B — makePaymentV2 (client token)

Obtain a client_token from your backend (Payment Intent / hosted checkout API), then:

final config = {
  'environment': 'sandbox',
  'amount': '25.00',
  'currency': 'usd',
  'clientToken': clientTokenFromYourBackend,
  'paymentMethods': ['card', 'ach'],
  'saveCard': false,
  'saveAccount': false,
  'secureAuthentication': true,
  'fields': { /* same shape as makePayment */ },
};

final result = await lyfecyclePaymentsSdk.makePaymentV2(jsonEncode(config));

You do not need configureEnvironment for V2 if the token already encodes the session (demo app still sets keys for token generation on the server side).

Payment reference (3DS / follow-up)

After 3DS or certain flows you may receive a referenceToken in the response. Resume payment with:

final result = await lyfecyclePaymentsSdk.paymentReference(referenceToken);

5. Payment result format

Aligned with the React Native SDK (iOS native bridge). Parse the JSON string returned by makePayment / makePaymentV2 / events.

Success

{
  "status": "success",
  "chargeData": { },
  "billingInfo": { },
  "additionalInfo": { },
  "referenceToken": "optional-from-3ds"
}

Error

{
  "status": "error",
  "message": "Human-readable error"
}

Cancelled

{
  "status": "cancelled",
  "message": "Payment cancelled"
}

Android may still return legacy shapes in some paths ("status": true boolean). Prefer checking both during migration:

bool isSuccess(dynamic status) =>
  status == 'success' || status == true;

6. Configuration reference (common keys)

Key Type Description
environment String sandbox, staging, production
amount String Decimal amount as string, e.g. "10.00"
currency String e.g. usd
paymentMethods List<String> card, ach
clientToken String Required for makePaymentV2
tokenOnly bool Tokenize without charge
saveCard / saveAccount bool Save payment methods
secureAuthentication bool 3DS when supported
authenticatedACH bool GrailPay / verified ACH
showReceipt / showTotal / showSubmitButton bool UI toggles
fields Object Billing & additional field visibility/values
appearanceSettings Object Theme colors, fonts, radius
grailPayParams Object role, timeout (min 11), brandingName, etc.
is_recurring bool Recurring payment
recurringIntervals List<String> daily, weekly, monthly, …
metadata Object Custom metadata

See the demo app buildPaymentConfiguration() in MainPaymentView.dart / ClientPaymentView.dart for a full working example.


7. API summary

Method Description
configureEnvironment(env, apiKey, secretKey) Set native API environment (for makePayment)
makePayment(configJson) Hosted checkout with API credentials
makePaymentV2(configJson) Hosted checkout with clientToken
paymentReference(referenceToken) Continue after 3DS / reference flow
setViewController() iOS: bind root VC (usually optional)
getPlatformVersion() Debug helper
setupEventListeners(...) Subscribe to PaymentSuccess / PaymentStatusError / PaymentPending

8. Troubleshooting

Issue What to check
Android: failed to resolve com.app:paysdk GITHUB_READ_ACTOR / GITHUB_READ_TOKEN on app android/build.gradle
iOS: pod errors cd ios && pod install, use .xcworkspace
iOS: presentation errors Call payment from a visible screen; avoid presenting from a modal host
Empty / parse errors Pass JSON string via jsonEncode(map), not a raw Map
Gradle Kotlin mismatch Plugin uses Kotlin 2.0.21 and paysdk 2.0.9 (aligned with RN SDK)

9. Version & support

For credential and backend Payment Intent setup, contact Lyfecycle Payments support.