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

# Swift (iOS)

> Embed secure payment checkout experiences directly into your native iOS application

## Overview

The **Moment Swift SDK** enables seamless payment integration into any native iOS application. The SDK provides a clean, idiomatic Swift API with full `async/await` support to handle the entire payment flow through a native bottom sheet experience.

Built for **iOS** with Swift, the SDK supports both **UIKit** and **SwiftUI** workflows.

<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

* **async/await API** – Modern Swift concurrency with no callback pyramid
* **Native Experience** – Bottom sheet presentation using native iOS sheet APIs
* **UIKit & SwiftUI** – Works seamlessly with both UI frameworks
* **Type-Safe** – Fully typed closures and result types with Swift-first design
* **Flexible Callbacks** – Handle success, failure, cancellation, retry, and expiry events
* **Secure** – Sensitive payment data handled entirely in Moment's secure WebView
* **PCI Compliant** – No card data touches your application

***

## How It Works

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

    Note over iOS App: App launches — Moment(clientKey:) + moment.checkout(...) called
    iOS App->>iOS App: checkout instance ready with fetchClientToken, fetchSessionStatus & event closures

    Customer->>iOS App: Taps "Pay Now"
    iOS App->>Moment Checkout: checkout.launch()
    Moment Checkout->>iOS App: fetchClientToken() closure fires
    iOS App->>Your Backend: POST /api/create-payment-session
    Your Backend->>Moment API: POST /collect/payment_sessions (with success_url)
    Moment API-->>Your Backend: Returns client_token (JWT)
    Your Backend-->>iOS App: Returns client_token
    iOS App-->>Moment Checkout: Resolves with 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-->>iOS App: redirect link https://www.checkout.com?session_id=abc123
    Note over iOS App: SDK detects redirect link — fetchSessionStatus fires
    Moment Checkout->>iOS App: fetchSessionStatus("abc123") closure fires
    iOS App->>Your Backend: GET /api/session-status?session_id=abc123
    Your Backend->>Moment API: GET /collect/payment_sessions/abc123
    Moment API-->>Your Backend: Returns session status
    Your Backend-->>iOS App: Returns { status: 'completed', ... }
    iOS App-->>Moment Checkout: Resolves with status
    Moment Checkout->>iOS App: onSuccess(result) fires — shows confirmation
```

1. **App launches** — `Moment(clientKey:)` creates the SDK instance; `moment.checkout(...)` creates and returns the `checkout` instance, registering your `fetchClientToken`, `fetchSessionStatus`, and event closures
2. **Customer taps Pay** — `checkout.launch()` is called
3. **SDK calls `fetchClientToken`** — your closure creates a session on your backend (with a `success_url` deep link) 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 redirect link with `?session_id=abc123`
8. **SDK detects the redirect link** and calls your `fetchSessionStatus("abc123")` closure automatically
9. **Your closure 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

### **Swift Package Manager (Recommended)**

In Xcode, go to **File → Add Package Dependencies** and enter:

```
https://github.com/momentpay/sdk/swift
```

Or add to your `Package.swift`:

```swift theme={"system"}
dependencies: [
    .package(url: "https://github.com/momentpay/sdk/swift", from: "1.0.0")
]
```

### **CocoaPods**

Add to your `Podfile`:

```ruby theme={"system"}
pod 'MomentSDK'
```

Then run:

```bash theme={"system"}
pod install
```

***

## Platform Configuration

### Requirements

* iOS 15.0+
* Swift 5.7+
* Xcode 14+

***

## **Quick Start**

### **1. Initialize the SDK and configure checkout**

Create the SDK instance with `Moment(clientKey:)` and configure the `checkout` product by calling `moment.checkout(...)`. Both are set up once when your app launches — in `AppDelegate` or your SwiftUI `App` initializer. You provide two async throwing closures — the SDK calls them automatically at the right moments.

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

**iOS — initialize once on app launch:**

<Tabs>
  <Tab title="UIKit · URLSession">
    ```swift AppDelegate.swift theme={"system"}
    import MomentSDK

    let moment = Moment(clientKey: "pk_your_key_here")
    var checkout: MomentCheckout!

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {

        func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {

            checkout = moment.checkout(
                // SDK calls this when checkout.launch() is triggered to get a fresh session token
                fetchClientToken: {
                    var request = URLRequest(
                        url: URL(string: "https://your-backend.com/api/create-payment-session")!
                    )
                    request.httpMethod = "POST"
                    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
                    let (data, _) = try await URLSession.shared.data(for: request)
                    let json = try JSONDecoder().decode([String: String].self, from: data)
                    return json["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 resolve to a MomentSessionStatus with at least a status field.
                fetchSessionStatus: { sessionId in
                    let url = URL(
                        string: "https://your-backend.com/api/session-status?session_id=\(sessionId)"
                    )!
                    let (data, _) = try await URLSession.shared.data(from: url)
                    return try JSONDecoder().decode(MomentSessionStatus.self, from: data)
                },

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

            return true
        }

        // Forward deep links to the SDK for redirect payment handling
        func application(
            _ app: UIApplication,
            open url: URL,
            options: [UIApplication.OpenURLOptionsKey: Any] = [:]
        ) -> Bool {
            return moment.handleDeepLink(url)
        }
    }
    ```
  </Tab>

  <Tab title="UIKit · Alamofire">
    ```swift AppDelegate.swift theme={"system"}
    import MomentSDK
    import Alamofire

    let moment = Moment(clientKey: "pk_your_key_here")
    var checkout: MomentCheckout!

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {

        func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {

            checkout = moment.checkout(
                // SDK calls this when checkout.launch() is triggered to get a fresh session token
                fetchClientToken: {
                    let response = try await AF
                        .request(
                            "https://your-backend.com/api/create-payment-session",
                            method: .post
                        )
                        .serializingDecodable([String: String].self)
                        .value
                    return response["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).
                fetchSessionStatus: { sessionId in
                    return try await AF
                        .request(
                            "https://your-backend.com/api/session-status",
                            parameters: ["session_id": sessionId]
                        )
                        .serializingDecodable(MomentSessionStatus.self)
                        .value
                },

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

            return true
        }

        func application(
            _ app: UIApplication,
            open url: URL,
            options: [UIApplication.OpenURLOptionsKey: Any] = [:]
        ) -> Bool {
            return moment.handleDeepLink(url)
        }
    }
    ```
  </Tab>

  <Tab title="UIKit · Firebase">
    ```swift AppDelegate.swift theme={"system"}
    import MomentSDK
    import FirebaseFunctions

    let moment = Moment(clientKey: "pk_your_key_here")
    var checkout: MomentCheckout!

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {

        func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {

            let functions = Functions.functions()

            checkout = moment.checkout(
                // SDK calls this when checkout.launch() is triggered to get a fresh session token
                fetchClientToken: {
                    let result = try await functions
                        .httpsCallable("createPaymentSession")
                        .call(["amount": 10000, "currency": "ZAR"])
                    let data = result.data as? [String: Any]
                    return data?["client_token"] as? String ?? ""
                },

                // 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).
                fetchSessionStatus: { sessionId in
                    let result = try await functions
                        .httpsCallable("getSessionStatus")
                        .call(["sessionId": sessionId])
                    let data = result.data as? [String: Any] ?? [:]
                    return try MomentSessionStatus(from: data)
                },

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

            return true
        }

        func application(
            _ app: UIApplication,
            open url: URL,
            options: [UIApplication.OpenURLOptionsKey: Any] = [:]
        ) -> Bool {
            return moment.handleDeepLink(url)
        }
    }
    ```
  </Tab>

  <Tab title="UIKit · Supabase">
    ```swift AppDelegate.swift theme={"system"}
    import MomentSDK
    import Supabase

    let moment = Moment(clientKey: "pk_your_key_here")
    var checkout: MomentCheckout!

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {

        let supabase = SupabaseClient(
            supabaseURL: URL(string: "https://your-project.supabase.co")!,
            supabaseKey: "your-anon-key"
        )

        func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {

            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: { [weak self] in
                    guard let self else { throw MomentSDKError.notInitialized }
                    let response: [String: String] = try await self.supabase.functions
                        .invoke("create-payment-session")
                    return response["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).
                fetchSessionStatus: { [weak self] sessionId in
                    guard let self else { throw MomentSDKError.notInitialized }
                    return try await self.supabase.functions
                        .invoke(
                            "get-session-status",
                            options: FunctionInvokeOptions(body: ["session_id": sessionId])
                        )
                },

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

            return true
        }

        func application(
            _ app: UIApplication,
            open url: URL,
            options: [UIApplication.OpenURLOptionsKey: Any] = [:]
        ) -> Bool {
            return moment.handleDeepLink(url)
        }
    }
    ```
  </Tab>

  <Tab title="SwiftUI · URLSession">
    ```swift YourApp.swift theme={"system"}
    import SwiftUI
    import MomentSDK

    let moment = Moment(clientKey: "pk_your_key_here")
    var checkout: MomentCheckout!

    @main
    struct YourApp: App {

        init() {
            checkout = moment.checkout(
                // SDK calls this when checkout.launch() is triggered to get a fresh session token
                fetchClientToken: {
                    var request = URLRequest(
                        url: URL(string: "https://your-backend.com/api/create-payment-session")!
                    )
                    request.httpMethod = "POST"
                    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
                    let (data, _) = try await URLSession.shared.data(for: request)
                    let json = try JSONDecoder().decode([String: String].self, from: data)
                    return json["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 resolve to a MomentSessionStatus with at least a status field.
                fetchSessionStatus: { sessionId in
                    let url = URL(
                        string: "https://your-backend.com/api/session-status?session_id=\(sessionId)"
                    )!
                    let (data, _) = try await URLSession.shared.data(from: url)
                    return try JSONDecoder().decode(MomentSessionStatus.self, from: data)
                },

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

        var body: some Scene {
            WindowGroup {
                ContentView()
                    .onOpenURL { url in
                        moment.handleDeepLink(url)
                    }
            }
        }
    }
    ```
  </Tab>

  <Tab title="SwiftUI · Alamofire">
    ```swift YourApp.swift theme={"system"}
    import SwiftUI
    import MomentSDK
    import Alamofire

    let moment = Moment(clientKey: "pk_your_key_here")
    var checkout: MomentCheckout!

    @main
    struct YourApp: App {

        init() {
            checkout = moment.checkout(
                // SDK calls this when checkout.launch() is triggered to get a fresh session token
                fetchClientToken: {
                    let response = try await AF
                        .request(
                            "https://your-backend.com/api/create-payment-session",
                            method: .post
                        )
                        .serializingDecodable([String: String].self)
                        .value
                    return response["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).
                fetchSessionStatus: { sessionId in
                    return try await AF
                        .request(
                            "https://your-backend.com/api/session-status",
                            parameters: ["session_id": sessionId]
                        )
                        .serializingDecodable(MomentSessionStatus.self)
                        .value
                },

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

        var body: some Scene {
            WindowGroup {
                ContentView()
                    .onOpenURL { url in
                        moment.handleDeepLink(url)
                    }
            }
        }
    }
    ```
  </Tab>

  <Tab title="SwiftUI · Firebase">
    ```swift YourApp.swift theme={"system"}
    import SwiftUI
    import MomentSDK
    import FirebaseFunctions

    let moment = Moment(clientKey: "pk_your_key_here")
    var checkout: MomentCheckout!

    @main
    struct YourApp: App {

        init() {
            checkout = moment.checkout(
                // SDK calls this when checkout.launch() is triggered to get a fresh session token
                fetchClientToken: {
                    let result = try await Functions.functions()
                        .httpsCallable("createPaymentSession")
                        .call(["amount": 10000, "currency": "ZAR"])
                    let data = result.data as? [String: Any]
                    return data?["client_token"] as? String ?? ""
                },

                // 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).
                fetchSessionStatus: { sessionId in
                    let result = try await Functions.functions()
                        .httpsCallable("getSessionStatus")
                        .call(["sessionId": sessionId])
                    let data = result.data as? [String: Any] ?? [:]
                    return try MomentSessionStatus(from: data)
                },

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

        var body: some Scene {
            WindowGroup {
                ContentView()
                    .onOpenURL { url in
                        moment.handleDeepLink(url)
                    }
            }
        }
    }
    ```
  </Tab>

  <Tab title="SwiftUI · Supabase">
    ```swift YourApp.swift theme={"system"}
    import SwiftUI
    import MomentSDK
    import Supabase

    let moment = Moment(clientKey: "pk_your_key_here")
    var checkout: MomentCheckout!

    @main
    struct YourApp: App {

        init() {
            let supabase = SupabaseClient(
                supabaseURL: URL(string: "https://your-project.supabase.co")!,
                supabaseKey: "your-anon-key"
            )

            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: {
                    let response: [String: String] = try await supabase.functions
                        .invoke("create-payment-session")
                    return response["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).
                fetchSessionStatus: { sessionId in
                    return try await supabase.functions
                        .invoke(
                            "get-session-status",
                            options: FunctionInvokeOptions(body: ["session_id": sessionId])
                        )
                },

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

        var body: some Scene {
            WindowGroup {
                ContentView()
                    .onOpenURL { url in
                        moment.handleDeepLink(url)
                    }
            }
        }
    }
    ```
  </Tab>
</Tabs>

### **2. Launch Checkout**

<Tabs>
  <Tab title="UIKit">
    ```swift CheckoutViewController.swift theme={"system"}
    import UIKit
    import MomentSDK

    class CheckoutViewController: UIViewController {

        @IBAction func payButtonTapped(_ sender: UIButton) {
            // SDK calls fetchClientToken internally — no token management needed here
            checkout.launch(from: self)
        }
    }
    ```
  </Tab>

  <Tab title="SwiftUI">
    ```swift CheckoutView.swift theme={"system"}
    import SwiftUI
    import MomentSDK

    struct CheckoutView: View {
        var body: some View {
            Button("Pay Now") {
                // SDK calls fetchClientToken internally — no token management needed here
                checkout.launch()
            }
            .buttonStyle(.borderedProminent)
        }
    }
    ```
  </Tab>
</Tabs>

***

## **API Reference**

### **Moment(clientKey:)**

Creates the SDK instance. Call once when your app launches — typically stored as a global `let` before your `AppDelegate` class or `App` struct.

| Parameter   | Type     | Required | Description                                           |
| ----------- | -------- | -------- | ----------------------------------------------------- |
| `clientKey` | `String` | Yes      | Your publishable key (`pk_test_...` or `pk_live_...`) |

```swift theme={"system"}
let moment = Moment(clientKey: "pk_test_abc123")
```

***

### **moment.checkout(config)**

Creates and returns a `MomentCheckout` instance for the `checkout` product. Registers your fetch closures and event handlers — the SDK calls them automatically at the right moments. Typically called in `AppDelegate` or your SwiftUI `App` `init()` and assigned to a global `var checkout: MomentCheckout!`.

| Parameter            | Type                                              | Required | Description                                                                                                                                                                                             |
| -------------------- | ------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fetchClientToken`   | `() async throws -> String`                       | Yes      | Async throwing closure returning a fresh client token. Called by the SDK when `checkout.launch()` is invoked.                                                                                           |
| `fetchSessionStatus` | `((String) async throws -> MomentSessionStatus)?` | No       | Async throwing closure receiving a `sessionId`, returning session status. Called when a deep link with `session_id` is detected after a redirect-based payment. Required only if you use redirect mode. |
| `onSuccess`          | `((MomentResult) -> Void)?`                       | No       | Closure fired when payment succeeds (both embedded and redirect flows)                                                                                                                                  |
| `onFailure`          | `((MomentResult) -> Void)?`                       | No       | Closure fired when payment fails                                                                                                                                                                        |
| `onCancel`           | `((MomentResult) -> Void)?`                       | No       | Closure fired when user cancels                                                                                                                                                                         |
| `onAttemptFailed`    | `((MomentResult) -> Void)?`                       | No       | Closure fired when a payment attempt fails (user can retry)                                                                                                                                             |
| `onExpired`          | `((MomentResult) -> Void)?`                       | No       | Closure fired when the payment session expires                                                                                                                                                          |

**Example:**

```swift theme={"system"}
checkout = moment.checkout(
    fetchClientToken: {
        var request = URLRequest(url: URL(string: "https://your-backend.com/api/create-payment-session")!)
        request.httpMethod = "POST"
        let (data, _) = try await URLSession.shared.data(for: request)
        let json = try JSONDecoder().decode([String: String].self, from: data)
        return json["client_token"] ?? ""
    },
    // Only needed if using redirect mode
    fetchSessionStatus: { sessionId in
        let url = URL(string: "https://your-backend.com/api/session-status?session_id=\(sessionId)")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(MomentSessionStatus.self, from: data)
        // MomentSessionStatus must have at minimum: status: "completed" | "failed" | "expired" | "cancelled"
    },
    onSuccess: { result in print("Success:", result) },
    onFailure: { result in print("Failed:", result) },
    onCancel: { result in print("Cancelled") },
    onAttemptFailed: { result in print("Attempt failed:", result) },
    onExpired: { result in print("Expired") }
)
```

***

### **checkout.launch()**

Opens the payment checkout in a native bottom sheet. Internally calls your `fetchClientToken` closure to retrieve a fresh token.

| Parameter | Type               | Required   | Description                    |
| --------- | ------------------ | ---------- | ------------------------------ |
| `from`    | `UIViewController` | UIKit only | The presenting view controller |

```swift theme={"system"}
// UIKit
checkout.launch(from: viewController)

// SwiftUI
checkout.launch()
```

***

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

Manually dismisses the bottom sheet checkout.

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

***

## Security Best Practices

1. **Never embed your secret key** (`sk_...`) in your iOS app — it must only live on your backend
2. **Always create sessions server-side** via your backend API
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 `open()`, so tokens are always fresh
6. **Use the Keychain** to store any persistent credentials — never `UserDefaults`
7. **Avoid logging sensitive data** — do not log `client_token` or session IDs in production builds

***

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