Skip to main content

Overview

The Moment Flutter SDK enables seamless payment integration into any Flutter application. The SDK provides a simple, unified API to handle the entire payment flow through a native bottom sheet experience. Built with pure Dart (minimal dependencies), the SDK works across both iOS and Android platforms. Moment Mobile SDK

Key Features

  • Native Experience – Bottom sheet presentation feels native on both platforms
  • Flexible Callbacks – Handle success, failure, cancellation, retry, and expiry events
  • Secure – Sensitive payment data handled entirely in Moment’s secure WebView
  • Minimal Dependencies – Single package, easy to integrate
  • PCI Compliant – No card data ever touches your application

How It Works

  1. App startsMoment(clientKey: ...) creates an SDK instance; moment.checkout() is called to create a checkout instance with your fetchClientToken, fetchSessionStatus, and event callbacks (onSuccess, onFailure, etc.)
  2. Customer taps Paycheckout.launch() is called
  3. SDK calls fetchClientToken — your callback creates a session on your backend (with a success_url) and returns the client_token
  4. Checkout opens in a native bottom sheet
  5. Customer submits payment — Moment takes over and redirects to the authentication page
  6. Customer completes authentication on the external page
  7. Customer is redirected back to your app via link with ?session_id=abc123
  8. SDK detects the link and calls your fetchSessionStatus('abc123') callback automatically
  9. Your callback fetches the status from your backend, which queries Moment API
  10. SDK routes to the right handleronSuccess, onFailure, onExpired, or onCancel
  11. Webhooks are delivered to your backend regardless of outcome

Installation

1. Add Dependencies

Add the following to your pubspec.yaml:
dependencies:
  momentpay: latest
  http: latest        # or dio, depending on your preferred HTTP client

2. Import the SDK

import 'package:momentpay/moment_sdk.dart';

Platform Configuration

iOS

Add to ios/Runner/Info.plist:
<key>io.flutter.embedded_views_preview</key>
<true/>

Android

Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
Ensure minimum SDK version in android/app/build.gradle:
android {
    defaultConfig {
        minSdkVersion 20
    }
}

Quick Start

1. Initialize the SDK

Call Moment(clientKey: ...) once when your app starts to create an SDK instance. Then call moment.checkout() with your fetch callbacks and event handlers — it returns a checkout instance, and the SDK calls your callbacks automatically at the right moments, so you never manage tokens or session IDs manually. Backend endpoints:
server.js
// Called by fetchClientToken when checkout.launch() is triggered
app.post('/api/create-payment-session', async (req, res) => {
  const response = await fetch('https://api.momentpay.net/collect/payment_sessions', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_test_your_secret_key',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: 10000, // Amount in cents (e.g., R100.00)
      currency: 'ZAR',
      type: 'one_time',
      options: {
        checkout_options: {
          presentation_mode: { mode: 'embedded' },
          success_url: 'yourapp://checkout?session_id={SESSION_ID}', // deep link back to app
        },
      },
    }),
  });
  const { client_token } = await response.json();
  res.json({ client_token });
});

// Called by fetchSessionStatus after customer returns via deep link
app.get('/api/session-status', async (req, res) => {
  const sessionId = req.query.session_id;
  const response = await fetch(
    `https://api.momentpay.net/collect/payment_sessions/${sessionId}`,
    { headers: { 'Authorization': 'Bearer sk_test_your_secret_key' } }
  );
  const session = await response.json();
  res.json({ status: session.status, ...session });
});
Flutter — initialize once on app start:
main.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:momentpay/momentpay/moment_sdk.dart';

final moment = Moment(clientKey: 'pk_your_key_here');

void main() {
  final checkout = moment.checkout(
    // SDK calls this when checkout.launch() is triggered to get a fresh session token
    fetchClientToken: () async {
      final response = await http.post(
        Uri.parse('https://your-backend.com/api/create-payment-session'),
        headers: {'Content-Type': 'application/json'},
      );
      final data = jsonDecode(response.body);
      return data['client_token'];
    },

    // SDK calls this automatically when a deep link with session_id is detected.
    // Happens after a customer returns from a redirect-based payment (3DS, bank redirect, EFT).
    // Must return a map with at least: { 'status': 'completed' | 'failed' | 'expired' | 'cancelled' }
    fetchSessionStatus: (sessionId) async {
      final response = await http.get(
        Uri.parse('https://your-backend.com/api/session-status?session_id=$sessionId'),
      );
      return jsonDecode(response.body);
    },

    onSuccess: (result) {
      debugPrint('Payment successful: $result');
      // Navigate to success screen or update UI
    },
    onFailure: (result) {
      debugPrint('Payment failed: $result');
      // Show error message
    },
    onCancel: (result) {
      debugPrint('Payment cancelled');
      // Allow user to retry
    },
    onAttemptFailed: (result) {
      debugPrint('Payment attempt failed: $result');
      // User can retry within the checkout
    },
    onExpired: (result) {
      debugPrint('Payment session expired');
      // Request a new session and retry
    },
  );

  runApp(const MyApp());
}

2. Open Checkout

checkout.dart
// SDK automatically calls fetchClientToken to get a fresh token, then opens checkout
checkout.launch(context: context);

API Reference

Moment(clientKey)

Factory constructor — creates an isolated SDK instance scoped to your publishable key. Returns an object with product namespaces (checkout, and more in the future).
ParameterTypeRequiredDescription
clientKeyStringYesYour publishable key (pk_test_... or pk_live_...). Determines sandbox vs. production environment.
Throws immediately if clientKey is invalid.
final moment = Moment(clientKey: 'pk_test_abc123');

moment.checkout(config)

Creates and returns a checkout instance for this SDK. Typically called in main() or your root widget’s initState(). Returns a checkout object with launch and close methods.
ParameterTypeRequiredDescription
fetchClientTokenFuture<String> Function()YesAsync function returning a fresh client token. Called by the SDK when checkout.launch() is invoked.
fetchSessionStatusFuture<Map> Function(String)NoAsync function receiving a sessionId, returning session status. Called when a deep link with session_id is detected after a redirect-based payment.
onSuccessFunction(dynamic)NoCallback fired when payment succeeds (both embedded and redirect flows)
onFailureFunction(dynamic)NoCallback fired when payment fails
onCancelFunction(dynamic)NoCallback fired when user cancels
onAttemptFailedFunction(dynamic)NoCallback fired when a payment attempt fails (user can retry)
onExpiredFunction(dynamic)NoCallback fired when the payment session expires

checkout.launch()

Opens the payment checkout in a native bottom sheet. Internally calls your fetchClientToken to retrieve a fresh token.
ParameterTypeRequiredDescription
contextBuildContextYesFlutter build context
checkout.launch(context: context);

checkout.close()

Manually closes the bottom sheet checkout.
checkout.close();

Complete Example

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:momentpay/momentpay/moment_sdk.dart';

final moment = Moment(clientKey: 'pk_your_key_here');

final checkout = moment.checkout(
  fetchClientToken: () async {
    final response = await http.post(
      Uri.parse('https://your-backend.com/api/create-payment-session'),
      headers: {'Content-Type': 'application/json'},
    );
    final data = jsonDecode(response.body);
    return data['client_token'];
  },

  fetchSessionStatus: (sessionId) async {
    final response = await http.get(
      Uri.parse('https://your-backend.com/api/session-status?session_id=$sessionId'),
    );
    return jsonDecode(response.body);
  },

  onSuccess: (result) => debugPrint('Success: $result'),
  onFailure: (result) => debugPrint('Failed: $result'),
  onCancel: (result) => debugPrint('Cancelled'),
  onAttemptFailed: (result) => debugPrint('Attempt failed: $result'),
  onExpired: (result) => debugPrint('Expired'),
);

void main() {
  runApp(const MyApp());
}

class CheckoutScreen extends StatefulWidget {
  final double amount;
  const CheckoutScreen({required this.amount});

  @override
  State<CheckoutScreen> createState() => _CheckoutScreenState();
}

class _CheckoutScreenState extends State<CheckoutScreen> {
  bool _isLoading = false;

  Future<void> _handlePayment() async {
    setState(() => _isLoading = true);
    try {
      // SDK calls fetchClientToken internally — no token management needed here
      await checkout.launch(context: context);
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Error: $e'), backgroundColor: Colors.red),
        );
      }
    } finally {
      if (mounted) setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Checkout')),
      body: Center(
        child: ElevatedButton(
          onPressed: _isLoading ? null : _handlePayment,
          child: _isLoading
              ? const CircularProgressIndicator()
              : Text('Pay R${widget.amount.toStringAsFixed(2)}'),
        ),
      ),
    );
  }
}

Security Best Practices

  1. Never expose your secret key (sk_...) in your Flutter app
  2. Always create sessions server-side via your backend
  3. Validate webhooks to confirm payment state before fulfilling orders
  4. Use HTTPS for all backend communication
  5. Handle token expiry — the SDK calls fetchClientToken on every launch(), so tokens are always fresh
  6. Check mounted before updating widget state after async operations

Next Steps