Documentation Index
Fetch the complete documentation index at: https://docs.momentco.io/llms.txt
Use this file to discover all available pages before exploring further.
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.
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
- App starts —
Moment(clientKey: ...) creates an SDK instance; moment.checkout() is called to create a checkout instance with your fetchClientToken, fetchSessionStatus, and event callbacks (onSuccess, onFailure, etc.)
- Customer taps Pay —
checkout.launch() is called
- SDK calls
fetchClientToken — your callback creates a session on your backend (with a success_url) and returns the client_token
- Checkout opens in a native bottom sheet
- Customer submits payment — Moment takes over and redirects to the authentication page
- Customer completes authentication on the external page
- Customer is redirected back to your app via link with
?session_id=abc123
- SDK detects the link and calls your
fetchSessionStatus('abc123') callback automatically
- Your callback fetches the status from your backend, which queries Moment API
- SDK routes to the right handler —
onSuccess, onFailure, onExpired, or onCancel
- 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';
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:
Node.js
Python
Go
Firebase
Supabase
Lambda
// 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 });
});
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET_KEY = 'sk_test_your_secret_key'
# Called by fetchClientToken when checkout.launch() is triggered
@app.route('/api/create-payment-session', methods=['POST'])
def create_payment_session():
response = requests.post(
'https://api.momentpay.net/collect/payment_sessions',
headers={
'Authorization': f'Bearer {SECRET_KEY}',
'Content-Type': 'application/json',
},
json={
'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
},
},
},
)
data = response.json()
return jsonify({'client_token': data['client_token']})
# Called by fetchSessionStatus after customer returns via deep link
@app.route('/api/session-status', methods=['GET'])
def get_session_status():
session_id = request.args.get('session_id')
response = requests.get(
f'https://api.momentpay.net/collect/payment_sessions/{session_id}',
headers={'Authorization': f'Bearer {SECRET_KEY}'},
)
session = response.json()
return jsonify({'status': session['status'], **session})
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const secretKey = "sk_test_your_secret_key"
const momentAPI = "https://api.momentpay.net"
// Called by fetchClientToken when checkout.launch() is triggered
func createPaymentSession(w http.ResponseWriter, r *http.Request) {
payload, _ := json.Marshal(map[string]any{
"amount": 10000, // Amount in cents (e.g., R100.00)
"currency": "ZAR",
"type": "one_time",
"options": map[string]any{
"checkout_options": map[string]any{
"presentation_mode": map[string]string{"mode": "embedded"},
"success_url": "yourapp://checkout?session_id={SESSION_ID}", // deep link back to app
},
},
})
req, _ := http.NewRequest("POST", momentAPI+"/collect/payment_sessions", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+secretKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"client_token": result["client_token"]})
}
// Called by fetchSessionStatus after customer returns via deep link
func getSessionStatus(w http.ResponseWriter, r *http.Request) {
sessionID := r.URL.Query().Get("session_id")
req, _ := http.NewRequest("GET", fmt.Sprintf("%s/collect/payment_sessions/%s", momentAPI, sessionID), nil)
req.Header.Set("Authorization", "Bearer "+secretKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
w.Header().Set("Content-Type", "application/json")
w.Write(body)
}
func main() {
http.HandleFunc("/api/create-payment-session", createPaymentSession)
http.HandleFunc("/api/session-status", getSessionStatus)
http.ListenAndServe(":8080", nil)
}
const { onCall } = require('firebase-functions/v2/https');
const axios = require('axios');
// Called by fetchClientToken — creates a payment session
exports.createPaymentSession = onCall(async (request) => {
const { amount, currency } = request.data;
const { data } = await axios.post(
'https://api.momentpay.net/collect/payment_sessions',
{
amount,
currency,
type: 'one_time',
options: {
checkout_options: {
presentation_mode: { mode: 'embedded' },
success_url: 'yourapp://checkout?session_id={SESSION_ID}', // deep link back to app
},
},
},
{
headers: {
Authorization: `Bearer ${process.env.MOMENT_SECRET_KEY}`,
'Content-Type': 'application/json',
},
}
);
return { client_token: data.client_token };
});
// Called by fetchSessionStatus — retrieves session status after deep link redirect
exports.getSessionStatus = onCall(async (request) => {
const { sessionId } = request.data;
const { data } = await axios.get(
`https://api.momentpay.net/collect/payment_sessions/${sessionId}`,
{ headers: { Authorization: `Bearer ${process.env.MOMENT_SECRET_KEY}` } }
);
return { status: data.status, ...data };
});
supabase/functions/create-payment-session/index.ts
import { serve } from 'https://deno.land/std/http/server.ts';
// Invoked by fetchClientToken
serve(async () => {
const response = await fetch('https://api.momentpay.net/collect/payment_sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${Deno.env.get('MOMENT_SECRET_KEY')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 10000,
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();
return new Response(JSON.stringify({ client_token }), {
headers: { 'Content-Type': 'application/json' },
});
});
supabase/functions/get-session-status/index.ts
import { serve } from 'https://deno.land/std/http/server.ts';
// Invoked by fetchSessionStatus
serve(async (req) => {
const { session_id } = await req.json();
const response = await fetch(
`https://api.momentpay.net/collect/payment_sessions/${session_id}`,
{ headers: { Authorization: `Bearer ${Deno.env.get('MOMENT_SECRET_KEY')}` } }
);
const session = await response.json();
return new Response(JSON.stringify({ status: session.status, ...session }), {
headers: { 'Content-Type': 'application/json' },
});
});
// Called by fetchClientToken — POST /api/create-payment-session
exports.createPaymentSession = async (event) => {
const response = await fetch('https://api.momentpay.net/collect/payment_sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MOMENT_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 10000,
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();
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ client_token }),
};
};
// Called by fetchSessionStatus — GET /api/session-status?session_id=abc123
exports.getSessionStatus = async (event) => {
const sessionId = event.queryStringParameters?.session_id;
const response = await fetch(
`https://api.momentpay.net/collect/payment_sessions/${sessionId}`,
{ headers: { Authorization: `Bearer ${process.env.MOMENT_SECRET_KEY}` } }
);
const session = await response.json();
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: session.status, ...session }),
};
};
Flutter — initialize once on app start:
http (Dart)
Dio
Firebase
Supabase
Amplify (Lambda)
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());
}
import 'package:dio/dio.dart';
import 'package:momentpay/momentpay/moment_sdk.dart';
final _dio = Dio(BaseOptions(baseUrl: 'https://your-backend.com'));
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 _dio.post('/api/create-payment-session');
return response.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 _dio.get(
'/api/session-status',
queryParameters: {'session_id': sessionId},
);
return response.data;
},
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());
}
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_functions/cloud_functions.dart';
import 'package:momentpay/momentpay/moment_sdk.dart';
final moment = Moment(clientKey: 'pk_your_key_here');
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final createSession = FirebaseFunctions.instance.httpsCallable('createPaymentSession');
final getStatus = FirebaseFunctions.instance.httpsCallable('getSessionStatus');
final checkout = moment.checkout(
// SDK calls this when checkout.launch() is triggered to get a fresh session token
fetchClientToken: () async {
final result = await createSession.call({'amount': 10000, 'currency': 'ZAR'});
return result.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 result = await getStatus.call({'sessionId': sessionId});
return result.data;
},
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());
}
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:momentpay/momentpay/moment_sdk.dart';
final moment = Moment(clientKey: 'pk_your_key_here');
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'https://your-project.supabase.co',
anonKey: 'your-anon-key',
);
final supabase = Supabase.instance.client;
final checkout = moment.checkout(
// SDK calls this when checkout.launch() is triggered to get a fresh session token.
// Invokes a Supabase Edge Function named 'create-payment-session'.
fetchClientToken: () async {
final response = await supabase.functions.invoke('create-payment-session');
return response.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 supabase.functions.invoke(
'get-session-status',
body: {'session_id': sessionId},
);
return response.data;
},
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());
}
import 'dart:convert';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:amplify_api/amplify_api.dart';
import 'package:momentpay/momentpay/moment_sdk.dart';
final moment = Moment(clientKey: 'pk_your_key_here');
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Amplify.addPlugin(AmplifyAPI());
await Amplify.configure(amplifyconfig);
final checkout = moment.checkout(
// SDK calls this when checkout.launch() is triggered to get a fresh session token
fetchClientToken: () async {
final restOperation = Amplify.API.post(
'/api/create-payment-session',
apiName: 'PaymentApi',
);
final response = await restOperation.response;
final data = jsonDecode(response.decodeBody());
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 restOperation = Amplify.API.get(
'/api/session-status',
apiName: 'PaymentApi',
queryParameters: {'session_id': sessionId},
);
final response = await restOperation.response;
return jsonDecode(response.decodeBody());
},
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
// 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).
| Parameter | Type | Required | Description |
|---|
clientKey | String | Yes | Your 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.
| Parameter | Type | Required | Description |
|---|
fetchClientToken | Future<String> Function() | Yes | Async function returning a fresh client token. Called by the SDK when checkout.launch() is invoked. |
fetchSessionStatus | Future<Map> Function(String) | No | Async function receiving a sessionId, returning session status. Called when a deep link with session_id is detected after a redirect-based payment. |
onSuccess | Function(dynamic) | No | Callback fired when payment succeeds (both embedded and redirect flows) |
onFailure | Function(dynamic) | No | Callback fired when payment fails |
onCancel | Function(dynamic) | No | Callback fired when user cancels |
onAttemptFailed | Function(dynamic) | No | Callback fired when a payment attempt fails (user can retry) |
onExpired | Function(dynamic) | No | Callback 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.
| Parameter | Type | Required | Description |
|---|
context | BuildContext | Yes | Flutter build context |
checkout.launch(context: context);
checkout.close()
Manually closes the bottom sheet checkout.
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
- Never expose your secret key (
sk_...) in your Flutter app
- Always create sessions server-side via your backend
- Validate webhooks to confirm payment state before fulfilling orders
- Use HTTPS for all backend communication
- Handle token expiry — the SDK calls
fetchClientToken on every launch(), so tokens are always fresh
- Check
mounted before updating widget state after async operations
Next Steps