moment_sdk 0.1.0
moment_sdk: ^0.1.0 copied to clipboard
Moment payment checkout SDK for Flutter. Embed Secure Payment Checkout Experiences directly into your Flutter mobile application.
title: "Flutter" description: "Embed secure payment checkout experiences directly into your Flutter mobile application" #
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 with both iOS and Android platforms.
Key Features #
- Minimal Dependencies – Only requires
webview_flutter - Native Experience – Bottom sheet presentation feels native on both platforms
- Flexible Callbacks – Handle success, failure, and cancellation events
- Secure – Sensitive payment data handled entirely in Moment's secure WebView
- Easy Extraction – Single file can be published as a standalone Flutter package
- PCI Compliant – No card data touches your application
How It Works #
sequenceDiagram
participant Customer
participant Flutter App
participant Your Backend
participant Moment API
participant Moment Checkout
Customer->>Flutter App: Taps "Pay Now"
Flutter App->>Your Backend: Request payment session
Your Backend->>Moment API: POST /collect/payment_sessions
Moment API-->>Your Backend: Returns client_token (JWT)
Your Backend-->>Flutter App: Returns client_token
Flutter App->>Moment Checkout: MomentSDK.open(clientToken)
Moment Checkout-->>Customer: Displays checkout UI (Bottom Sheet)
Customer->>Moment Checkout: Completes payment
Moment Checkout-->>Flutter App: Returns MomentResult
Moment Checkout-->>Your Backend: Sends webhooks
Flutter App->>Customer: Shows confirmation
Flow Summary #
- Customer initiates payment in your app
- Backend creates a payment session and receives a
client_token - App opens checkout using
MomentSDK.open(clientToken) - Customer completes payment in Moment's secure checkout
- SDK returns a
MomentResultwith status and data - Webhooks are delivered to your backend
- Your app shows confirmation or handles errors
Installation #
1. Add Dependencies #
Add the following to your pubspec.yaml:
dependencies:
webview_flutter: ^4.4.2
webview_flutter_wkwebview: ^3.9.2 # iOS
webview_flutter_android: ^3.12.0 # Android
2. Add the SDK File #
Copy moment_sdk.dart into your project:
lib/
├── moment_sdk/
│ └── moment_sdk.dart
└── main.dart
3. Import the SDK #
import 'package:your_app/moment_sdk/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. Create a Session (Backend) #
Your backend should create a payment session and return the client_token:
// Node.js example
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',
},
},
},
}),
}
);
const { client_token } = await response.json();
// Return client_token to your Flutter app
2. Fetch Token from Backend (Flutter) #
class PaymentService {
static const String _baseUrl = 'https://your-backend.com';
static Future<String> createPaymentSession({
required int amount,
required String currency,
}) async {
final response = await http.post(
Uri.parse('$_baseUrl/create-session'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'amount': amount,
'currency': currency,
}),
);
if (response.statusCode != 200) {
throw Exception('Failed to create session');
}
final data = jsonDecode(response.body);
return data['client_token'];
}
}
3. Open Checkout #
Future<void> handlePayment() async {
try {
// Get client_token from your backend
final clientToken = await PaymentService.createPaymentSession(
amount: 10000, // R100.00 in cents
currency: 'ZAR',
);
// Open checkout
final result = await MomentSDK.open(
context: context,
clientToken: clientToken,
onComplete: (data) {
print('Payment successful: $data');
},
onError: (error) {
print('Payment error: $error');
},
onCancel: () {
print('Payment cancelled');
},
);
// Handle result
if (result.isSuccess) {
// Navigate to success screen
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => SuccessScreen()),
);
} else if (result.isError) {
// Show error message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Payment failed: ${result.errorMessage}')),
);
}
} catch (e) {
print('Error: $e');
}
}
API Reference #
MomentSDK.open() #
Opens the Moment payment checkout.
static Future<MomentResult> open({
required BuildContext context,
required String clientToken,
Function(Map<String, dynamic>)? onComplete,
Function(String)? onError,
Function()? onCancel,
})
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
context |
BuildContext |
Yes | Flutter build context for showing bottom sheet |
clientToken |
String |
Yes | JWT token from your backend's create-session endpoint |
onComplete |
Function(Map<String, dynamic>)? |
No | Callback when payment is successful |
onError |
Function(String)? |
No | Callback when an error occurs |
onCancel |
Function()? |
No | Callback when user cancels the payment |
Returns
Returns a Future<MomentResult> containing the payment outcome.
MomentResult #
Result object returned from MomentSDK.open().
class MomentResult {
final MomentStatus status;
final Map<String, dynamic>? data;
final String? errorMessage;
bool get isSuccess;
bool get isCancelled;
bool get isError;
}
Properties
| Property | Type | Description |
|---|---|---|
status |
MomentStatus |
The payment status (success, cancelled, error) |
data |
Map<String, dynamic>? |
Payment data returned on success |
errorMessage |
String? |
Error message if payment failed |
isSuccess |
bool |
Convenience getter for success status |
isCancelled |
bool |
Convenience getter for cancelled status |
isError |
bool |
Convenience getter for error status |
MomentStatus #
Enum representing the payment outcome.
enum MomentStatus {
success, // Payment completed successfully
cancelled, // User closed or cancelled the checkout
error // An error occurred during payment
}
MomentException #
Custom exception thrown for SDK errors.
class MomentException implements Exception {
final String message;
}
Complete Example #
import 'package:flutter/material.dart';
import 'moment_sdk/moment_sdk.dart';
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 {
final clientToken = await PaymentService.createPaymentSession(
amount: (widget.amount * 100).round(),
currency: 'ZAR',
);
if (!mounted) return;
final result = await MomentSDK.open(
context: context,
clientToken: clientToken,
onComplete: (data) => debugPrint('Complete: $data'),
onError: (error) => debugPrint('Error: $error'),
onCancel: () => debugPrint('Cancelled'),
);
if (!mounted) return;
switch (result.status) {
case MomentStatus.success:
_showSuccess();
break;
case MomentStatus.cancelled:
_showMessage('Payment cancelled');
break;
case MomentStatus.error:
_showError(result.errorMessage ?? 'Unknown error');
break;
}
} catch (e) {
_showError(e.toString());
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
void _showSuccess() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Payment successful!'),
backgroundColor: Colors.green,
),
);
}
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: $message'),
backgroundColor: Colors.red,
),
);
}
void _showMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
@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)}'),
),
),
);
}
}
Handling Edge Cases #
Network Errors #
try {
final result = await MomentSDK.open(
context: context,
clientToken: clientToken,
);
} on MomentException catch (e) {
// Handle SDK-specific errors
print('Moment error: ${e.message}');
} catch (e) {
// Handle other errors (network, etc.)
print('Error: $e');
}
Token Expiry #
Client tokens have a limited lifespan. Always request a fresh token before opening checkout:
// Good - fresh token each time
Future<void> pay() async {
final clientToken = await PaymentService.createPaymentSession(...);
await MomentSDK.open(context: context, clientToken: clientToken);
}
// Bad - stale token may be expired
String? _cachedToken;
Future<void> pay() async {
_cachedToken ??= await PaymentService.createPaymentSession(...);
await MomentSDK.open(context: context, clientToken: _cachedToken!);
}
Preventing Double Submissions #
bool _isProcessing = false;
Future<void> _handlePayment() async {
if (_isProcessing) return; // Prevent double tap
setState(() => _isProcessing = true);
try {
// ... payment logic
} finally {
if (mounted) {
setState(() => _isProcessing = false);
}
}
}
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 by requesting a new session when needed
- Check
mountedbefore updating state after async operations
Troubleshooting #
WebView Not Loading #
Ensure you've added the required platform configurations (see Platform Configuration).
"Invalid client token format" Error #
The client token must be a valid JWT with 3 parts separated by dots. Verify your backend is returning the correct token.
Bottom Sheet Closes Immediately #
Check that isDismissible: false and enableDrag: false are set in the bottom sheet configuration.
Payment Events Not Firing #
Ensure the JavaScript channel name matches what the checkout page expects (MomentSDK).
Next Steps #
- View Sample Applications for end-to-end examples
- Create your first Payment Session
- Configure Webhooks
- Test your integration in Sandbox mode