Skip to main content

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. Moment Mobile SDK

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

  1. App launchesMoment(clientKey:) creates the SDK instance; moment.checkout(...) creates and returns the checkout instance, registering your fetchClientToken, fetchSessionStatus, and event closures
  2. Customer taps Paycheckout.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 handleronSuccess, onFailure, onExpired, or onCancel
  11. Webhooks are delivered to your backend regardless of outcome

Installation

In Xcode, go to File → Add Package Dependencies and enter:
https://github.com/momentpay/sdk/swift
Or add to your Package.swift:
dependencies: [
    .package(url: "https://github.com/momentpay/sdk/swift", from: "1.0.0")
]

CocoaPods

Add to your Podfile:
pod 'MomentSDK'
Then run:
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:
server.js
// Called by fetchClientToken when checkout.launch() is triggered
app.post('/api/create-payment-session', async (req, res) => {
  const response = await fetch('https://api.momentpay.net/collect/payment_sessions', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_test_your_secret_key',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: 10000, // Amount in cents (e.g., R100.00)
      currency: 'ZAR',
      type: 'one_time',
      options: {
        checkout_options: {
          presentation_mode: { mode: 'embedded' },
          success_url: 'yourapp://checkout?session_id={SESSION_ID}', // deep link back to app
        },
      },
    }),
  });
  const { client_token } = await response.json();
  res.json({ client_token });
});

// Called by fetchSessionStatus after customer returns via deep link.
app.get('/api/session-status', async (req, res) => {
  const sessionId = req.query.session_id;
  const response = await fetch(
    `https://api.momentpay.net/collect/payment_sessions/${sessionId}`,
    { headers: { 'Authorization': 'Bearer sk_test_your_secret_key' } }
  );
  const session = await response.json();
  res.json({ status: session.status, ...session });
});
iOS — initialize once on app launch:
AppDelegate.swift
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)
    }
}

2. Launch Checkout

CheckoutViewController.swift
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)
    }
}

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.
ParameterTypeRequiredDescription
clientKeyStringYesYour publishable key (pk_test_... or pk_live_...)
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!.
ParameterTypeRequiredDescription
fetchClientToken() async throws -> StringYesAsync throwing closure returning a fresh client token. Called by the SDK when checkout.launch() is invoked.
fetchSessionStatus((String) async throws -> MomentSessionStatus)?NoAsync 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)?NoClosure fired when payment succeeds (both embedded and redirect flows)
onFailure((MomentResult) -> Void)?NoClosure fired when payment fails
onCancel((MomentResult) -> Void)?NoClosure fired when user cancels
onAttemptFailed((MomentResult) -> Void)?NoClosure fired when a payment attempt fails (user can retry)
onExpired((MomentResult) -> Void)?NoClosure fired when the payment session expires
Example:
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.
ParameterTypeRequiredDescription
fromUIViewControllerUIKit onlyThe presenting view controller
// UIKit
checkout.launch(from: viewController)

// SwiftUI
checkout.launch()

checkout.close()

Manually dismisses the bottom sheet checkout.
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