> ## 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.

# Flutter

> 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 across both **iOS** and **Android** platforms.

<img style={{ height: '500px' }} src="https://mintcdn.com/momentholdingslimited/ADxHxgRJd2L3EWcV/libraries-and-sdks/resources/mobile-sdk-gif.gif?s=4538ec8d5996c7eef66115e92c3ca63f" alt="Moment Mobile SDK" width="400" height="715" data-path="libraries-and-sdks/resources/mobile-sdk-gif.gif" />

***

## 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

```mermaid theme={"system"}
sequenceDiagram
    participant Customer
    participant Flutter App
    participant Your Backend
    participant Moment API
    participant Moment Checkout
    participant 3DS / Bank Page

    Note over Flutter App: App starts — Moment(key) + moment.checkout() called
    Flutter App->>Flutter App: Registers fetchClientToken, fetchSessionStatus & event callbacks

    Customer->>Flutter App: Taps "Pay Now"
    Flutter App->>Moment Checkout: checkout.launch()
    Moment Checkout->>Your Backend: fetchClientToken() fires
    Your Backend->>Moment API: POST /collect/payment_sessions (with success_url)
    Moment API-->>Your Backend: Returns client_token (JWT)
    Your Backend-->>Moment Checkout: Returns client_token
    Moment Checkout-->>Customer: Displays checkout UI (Bottom Sheet)
    Customer->>Moment Checkout: Submits payment
    Moment Checkout->>3DS / Bank Page: Redirects customer
    Customer->>3DS / Bank Page: Completes authentication
    3DS / Bank Page-->>Flutter App: redirect link redirect?session_id=abc123
    Note over Flutter App: SDK detects the redirect — fetchSessionStatus fires
    Flutter App->>Your Backend: fetchSessionStatus('abc123') fires
    Your Backend->>Moment API: GET /collect/payment_sessions/abc123
    Moment API-->>Your Backend: Returns session status
    Your Backend-->>Flutter App: Returns { status: 'completed', ... }
    Flutter App->>Customer: onSuccess(result) fires — shows confirmation
```

1. **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.)
2. **Customer taps Pay** — `checkout.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 handler** — `onSuccess`, `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`:

```yaml theme={"system"}
dependencies:
  momentpay: latest
  http: latest        # or dio, depending on your preferred HTTP client
```

### **2. Import the SDK**

```dart theme={"system"}
import 'package:momentpay/moment_sdk.dart';
```

***

## Platform Configuration

### iOS

Add to `ios/Runner/Info.plist`:

```xml theme={"system"}
<key>io.flutter.embedded_views_preview</key>
<true/>
```

### Android

Add to `android/app/src/main/AndroidManifest.xml`:

```xml theme={"system"}
<uses-permission android:name="android.permission.INTERNET"/>
```

Ensure minimum SDK version in `android/app/build.gradle`:

```gradle theme={"system"}
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:**

<Tabs>
  <Tab title="Node.js">
    ```javascript server.js theme={"system"}
    // 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 });
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python server.py theme={"system"}
    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})
    ```
  </Tab>

  <Tab title="Go">
    ```go server.go theme={"system"}
    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)
    }
    ```
  </Tab>

  <Tab title="Firebase">
    ```javascript functions/index.js theme={"system"}
    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 };
    });
    ```
  </Tab>

  <Tab title="Supabase">
    ```typescript supabase/functions/create-payment-session/index.ts theme={"system"}
    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' },
      });
    });
    ```

    ```typescript supabase/functions/get-session-status/index.ts theme={"system"}
    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' },
      });
    });
    ```
  </Tab>

  <Tab title="Lambda">
    ```javascript handler.js theme={"system"}
    // 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 }),
      };
    };
    ```
  </Tab>
</Tabs>

**Flutter — initialize once on app start:**

<Tabs>
  <Tab title="http (Dart)">
    ```dart main.dart theme={"system"}
    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());
    }
    ```
  </Tab>

  <Tab title="Dio">
    ```dart main.dart theme={"system"}
    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());
    }
    ```
  </Tab>

  <Tab title="Firebase">
    ```dart main.dart theme={"system"}
    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());
    }
    ```
  </Tab>

  <Tab title="Supabase">
    ```dart main.dart theme={"system"}
    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());
    }
    ```
  </Tab>

  <Tab title="Amplify (Lambda)">
    ```dart main.dart theme={"system"}
    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());
    }
    ```
  </Tab>
</Tabs>

### **2. Open Checkout**

```dart checkout.dart theme={"system"}
// 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.

```dart theme={"system"}
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 |

```dart theme={"system"}
checkout.launch(context: context);
```

***

### **checkout.close()**

Manually closes the bottom sheet checkout.

```dart theme={"system"}
checkout.close();
```

***

## Complete Example

```dart theme={"system"}
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

* View **[Sample Applications](/libraries-and-sdks/mobile/samples)** for end-to-end examples
* Create your first **[Payment Session](/api-reference/collect/payment_sessions/create)**
* Configure **[Webhooks](/api-reference/webhooks/overview)**
* Test your integration in **[Sandbox mode](/api-reference/getting-started/testing)**
