Skip to main content

Overview

The Moment React Native SDK enables seamless payment integration into any React Native application. The SDK provides a simple, declarative API with React hooks to handle the entire payment flow through a native bottom sheet experience. Built with React Native and modern hooks, the SDK works with both iOS and Android platforms and integrates naturally with your React Native workflow. Moment Mobile SDK

Key Features

  • React Hooks API – Declarative interface with useCheckout hook
  • Native Experience – Bottom sheet presentation feels native on both platforms
  • TypeScript Support – Full type definitions included
  • Flexible Callbacks – Handle success, failure, cancellation, retry, and expiry events
  • Secure – Sensitive payment data handled entirely in Moment’s secure WebView
  • Cross-Platform – Single codebase for iOS and Android
  • PCI Compliant – No card data touches your application

How It Works

  1. App startsMomentProvider + MomentCheckoutProvider mount, registering your fetchClientToken, fetchSessionStatus, and event callbacks (onSuccess, onFailure, etc.)
  2. Customer taps Paycheckout.launch() is called from your component
  3. SDK calls fetchClientToken — your callback 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') callback automatically
  9. Your callback 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

NPM (Bare React Native)

npm install momentpay

Yarn (Bare React Native)

yarn add momentpay

Expo

npx expo install momentpay
Expo vs Bare React Native: If you’re using Expo, use the Expo CLI installation. For bare React Native projects, use npm/yarn and follow the iOS pod install instructions below.

Install iOS Dependencies (Bare React Native Only)

cd ios && pod install && cd ..

Platform Configuration

For Expo Projects: Most platform configurations are handled automatically by Expo. Ensure you’re using Expo SDK 49 or later. If you need custom native configurations, you may need to use a development build.

Bare React Native Setup

iOS Setup

Add to ios/YourApp/Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <false/>
</dict>
Minimum iOS version in ios/Podfile:
platform :ios, '13.0'

Android Setup

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 21
        targetSdkVersion 33
    }
}

Quick Start

1. Initialize the SDK

Wrap your app with MomentProvider (SDK level) at the root, then nest MomentCheckoutProvider (checkout product) inside it with your fetch callbacks and event handlers — the SDK calls them automatically at the right moments, so you never manage tokens or session IDs manually. 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.
// The SDK extracts session_id from the deep link and passes it here.
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 });
});
React Native — wrap your app with MomentProvider + MomentCheckoutProvider:
App.tsx
import { MomentProvider, MomentCheckoutProvider } from 'momentpay';
import { NavigationContainer } from '@react-navigation/native';

function App() {
  return (
    <NavigationContainer>
      <MomentProvider clientKey="pk_your_key_here">
        <MomentCheckoutProvider
          // SDK calls this when checkout.launch() is invoked to get a fresh session token
          fetchClientToken={async () => {
            const response = await fetch('https://your-backend.com/api/create-payment-session', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
            });
            const data = await response.json();
            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: { status: 'completed' | 'failed' | 'expired' | 'cancelled', ... }
          fetchSessionStatus={async (sessionId) => {
            const response = await fetch(
              `https://your-backend.com/api/session-status?session_id=${sessionId}`
            );
            return response.json();
          }}

          onSuccess={(result) => {
            console.log('Payment successful!', result);
            // Navigate to success screen
          }}
          onFailure={(result) => {
            console.log('Payment failed', result);
            // Show error alert
          }}
          onCancel={(result) => {
            console.log('Payment cancelled', result);
            // Allow user to retry
          }}
          onAttemptFailed={(result) => {
            console.log('Payment attempt failed', result);
            // User can retry within the checkout
          }}
          onExpired={(result) => {
            console.log('Payment session expired', result);
            // Request a new session and retry
          }}
        >
          <YourApp />
        </MomentCheckoutProvider>
      </MomentProvider>
    </NavigationContainer>
  );
}

export default App;

2. Open Checkout

Add useCheckout to any component and call checkout.launch() to trigger checkout:
CheckoutScreen.tsx
import React, { useState } from 'react';
import { View, TouchableOpacity, Text, ActivityIndicator } from 'react-native';
import { useCheckout } from 'momentpay';

function CheckoutScreen() {
  const [loading, setLoading] = useState(false);

  const checkout = useCheckout({
    onSuccess: (result) => {
      console.log('Payment successful!', result);
      // Navigate to success screen
    },
    onFailure: (result) => {
      console.log('Payment failed', result);
      // Show error alert
    },
    onCancel: (result) => {
      console.log('Payment cancelled', result);
      // Allow user to retry
    },
    onAttemptFailed: (result) => {
      console.log('Payment attempt failed', result);
      // User can retry within the checkout
    },
  });

  const handlePayment = async () => {
    setLoading(true);
    try {
      // SDK calls fetchClientToken internally — no token management needed here
      await checkout.launch();
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <TouchableOpacity
        onPress={handlePayment}
        disabled={loading}
        style={{
          backgroundColor: loading ? '#ccc' : '#007AFF',
          padding: 16,
          borderRadius: 8,
        }}
      >
        {loading ? (
          <ActivityIndicator color="white" />
        ) : (
          <Text style={{ color: 'white', fontSize: 16 }}>Pay Now</Text>
        )}
      </TouchableOpacity>
    </View>
  );
}

export default CheckoutScreen;

API Reference

MomentProvider (SDK level)

Wraps your React Native app and initializes the SDK. Must be a parent of MomentCheckoutProvider and any component using useCheckout.
PropTypeRequiredDescription
clientKeystringYesYour publishable key (pk_test_... or pk_live_...). Determines sandbox vs. production environment.
<MomentProvider clientKey="pk_test_abc123">
  <MomentCheckoutProvider ...>
    <App />
  </MomentCheckoutProvider>
</MomentProvider>

MomentCheckoutProvider (checkout product)

React Native’s equivalent of moment.checkout() — configures the checkout product and makes it available to any child component via useCheckout. Must be a child of MomentProvider.
PropTypeRequiredDescription
fetchClientTokenfunctionYesAsync function returning Promise<string> — a fresh client token. Called by the SDK when checkout.launch() is invoked.
fetchSessionStatusfunctionNoAsync function receiving a sessionId string, returning Promise<{ status: string, ... }>. Called when a deep link with session_id is detected after a redirect-based payment. Required only if you use redirect mode.
onSuccessfunctionNoGlobal callback fired when payment succeeds (both embedded and redirect flows)
onFailurefunctionNoGlobal callback fired when payment fails
onCancelfunctionNoGlobal callback fired when user cancels
onAttemptFailedfunctionNoGlobal callback fired when a payment attempt fails (user can retry)
onExpiredfunctionNoGlobal callback fired when the payment session expires
Example:
<MomentProvider clientKey="pk_test_abc123">
  <MomentCheckoutProvider
    fetchClientToken={async () => {
      const res = await fetch('https://your-backend.com/api/create-payment-session', { method: 'POST' });
      const data = await res.json();
      return data.client_token;
    }}
    // Only needed if using redirect mode
    fetchSessionStatus={async (sessionId) => {
      const res = await fetch(`https://your-backend.com/api/session-status?session_id=${sessionId}`);
      return res.json();
      // Must return: { status: 'completed' | 'failed' | 'expired' | 'cancelled', ... }
    }}
    onSuccess={(result) => console.log('Success:', result)}
    onFailure={(result) => console.log('Failed:', result)}
    onCancel={(result) => console.log('Cancelled:', result)}
    onAttemptFailed={(result) => console.log('Attempt failed:', result)}
    onExpired={(result) => console.log('Expired:', result)}
  >
    <App />
  </MomentCheckoutProvider>
</MomentProvider>

useCheckout(config)

React Native hook that returns a checkout instance for triggering checkout from any component. Callbacks defined here are component-level and fire alongside any global callbacks on MomentCheckoutProvider.
ParameterTypeRequiredDescription
onSuccessfunctionNoComponent-level callback fired when payment succeeds
onFailurefunctionNoComponent-level callback fired when payment fails
onCancelfunctionNoComponent-level callback fired when user cancels
onAttemptFailedfunctionNoComponent-level callback fired when a payment attempt fails (user can retry)
onExpiredfunctionNoComponent-level callback fired when the payment session expires
Returns a checkout object — checkout.launch(), checkout.close(), and checkout.submit().

checkout.launch()

Opens the payment checkout in a native bottom sheet. Internally calls fetchClientToken from MomentCheckoutProvider to retrieve a fresh token.
// SDK calls fetchClientToken automatically — no token management needed
checkout.launch();
Returns a Promise that resolves once the checkout is open.

checkout.close()

Manually closes the bottom sheet checkout.
checkout.close();

Callback Strategies

The Moment React Native SDK offers flexible callback handling at both the global (provider) and component (hook) levels. Understanding when to use each approach helps you write cleaner, more maintainable code.

Global Callbacks (MomentCheckoutProvider Level)

Define callbacks once at the MomentCheckoutProvider level to apply consistent behavior across your entire application. ✅ Best for:
  • App-wide analytics tracking
  • Global error logging
  • Consistent navigation patterns
  • Shared alert/toast notifications
App.tsx
import { MomentProvider, MomentCheckoutProvider } from 'momentpay';
import { useNavigation } from '@react-navigation/native';
import { Alert } from 'react-native';
import { trackEvent } from './analytics';

function App() {
  const navigation = useNavigation();

  return (
    <MomentProvider clientKey="pk_your_key_here">
      <MomentCheckoutProvider
        fetchClientToken={async () => {
          const res = await fetch('https://your-backend.com/api/create-payment-session', { method: 'POST' });
          const data = await res.json();
          return data.client_token;
        }}
        onSuccess={(result) => {
          // Global success behavior
          trackEvent('payment_success', { transaction_id: result.transaction_id });
          Alert.alert('Success', 'Payment completed successfully!');
          navigation.navigate('OrderConfirmation');
        }}
        onFailure={(result) => {
          // Global failure behavior
          trackEvent('payment_failure', { error: result.error });
          Alert.alert('Payment Failed', 'Please try again.');
        }}
        onCancel={() => {
          trackEvent('payment_cancelled');
        }}
      >
        <YourApp />
      </MomentCheckoutProvider>
    </MomentProvider>
  );
}

Component-Level Callbacks (useCheckout Hook)

Override or supplement global callbacks with component-specific behavior. ✅ Best for:
  • Component-specific state updates
  • Different flows for different payment contexts
  • Local UI feedback
  • Context-aware error handling
SubscriptionScreen.tsx
import { useCheckout } from 'momentpay';

function SubscriptionScreen() {
  const [subscriptionActive, setSubscriptionActive] = useState(false);

  const checkout = useCheckout({
    onSuccess: (result) => {
      // Component-specific behavior
      setSubscriptionActive(true);
      // This will ALSO trigger the global onSuccess from MomentCheckoutProvider
    },
    onFailure: (result) => {
      setSubscriptionActive(false);
      // This will ALSO trigger the global onFailure from MomentCheckoutProvider
    }
  });

  // ... rest of component
}

Callback Precedence

Important: Component-level callbacks do not override global callbacks. Both will fire:
  1. Component-level callback fires first (if defined)
  2. Global callback fires second (if defined)
This allows you to handle both component-specific and app-wide logic.

Advanced Usage

Custom Hook for Payment Logic

hooks/usePayment.ts
import { useState, useCallback } from 'react';
import { Alert } from 'react-native';
import { useCheckout } from 'momentpay';
import { useNavigation } from '@react-navigation/native';

export function usePayment() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const navigation = useNavigation();

  const checkout = useCheckout({
    onSuccess: (result) => {
      setLoading(false);
      setError(null);
      navigation.navigate('Success', { transactionId: result.transaction_id });
    },
    onFailure: (result) => {
      setError(result.error?.message || 'Payment failed');
      setLoading(false);
      Alert.alert('Payment Failed', result.error?.message);
    },
    onCancel: () => {
      setLoading(false);
      setError(null);
    }
  });

  const processPayment = useCallback(async () => {
    setLoading(true);
    setError(null);

    try {
      // SDK calls fetchClientToken from MomentCheckoutProvider automatically
      await checkout.launch();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
      setLoading(false);
      Alert.alert('Error', 'Failed to initialize payment');
    }
  }, [checkout]);

  return { processPayment, loading, error };
}

Using the Custom Hook

ProductScreen.tsx
import { usePayment } from './hooks/usePayment';
import { TouchableOpacity, Text, ActivityIndicator } from 'react-native';

function ProductScreen({ product }) {
  const { processPayment, loading, error } = usePayment();

  return (
    <TouchableOpacity onPress={processPayment} disabled={loading}>
      {loading ? (
        <ActivityIndicator />
      ) : (
        <Text>Buy {product.name} - R{product.price.toFixed(2)}</Text>
      )}
    </TouchableOpacity>
  );
}

Security Best Practices

  1. Never expose your secret key (sk_...) in your React Native app
  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. Secure your backend endpoints with proper authentication
  7. Use environment variables for sensitive keys (use react-native-config)

Troubleshooting

Expo Build Issues

If you encounter issues with Expo: 1. Rebuild your project:
npx expo prebuild --clean
2. Development builds required: If the SDK uses custom native modules, you’ll need to create a development build:
npx expo run:ios
# or
npx expo run:android
3. Clear Expo cache:
npx expo start -c

iOS Build Issues (Bare React Native)

If you encounter build issues on iOS:
cd ios
pod deintegrate
pod install
cd ..
npx react-native run-ios

Android Build Issues (Bare React Native)

If you encounter build issues on Android:
cd android
./gradlew clean
cd ..
npx react-native run-android

Next Steps