Skip to main content

Overview

The Moment React SDK enables seamless payment integration into any React application. Whether you need a modal overlay or an inline embedded checkout, the SDK provides a simple, declarative API with React hooks to handle the entire payment flow. Built with React and modern hooks, the SDK integrates naturally with your React workflow and state management.

Embedded Modal :


Key Features

  • React Hooks API – Declarative interface with useCheckout hook
  • Flexible Display Options – Modal or inline embedded checkout
  • TypeScript Support – Full type definitions included
  • Flexible Callbacks – Handle success, failure, cancellation, and retry events
  • Cross-Origin Safe – Secure iframe communication via postMessage
  • Environment Detection – Automatic sandbox / production switching
  • PCI Compliant – Sensitive payment data handled entirely in Moment’s secure iframe

Display Variants

VariantAvailabilityDescription
modalAvailableOpens checkout in a centered modal overlay (default)
inlineComing SoonRenders checkout inside a specified container element

How It Works

  1. Page loadsMomentProvider mounts (SDK init), then MomentCheckoutProvider mounts, registering your fetchClientToken, fetchSessionStatus, and event callbacks (onSuccess, onFailure, etc.)
  2. Customer clicks Paycheckout.launch() is called from your component
  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 your chosen variant (modal or inline)
  5. Customer submits payment — Moment takes over the full page and redirects to the authentication page
  6. Customer completes authentication on the external page
  7. Customer is redirected back to your success_url with ?session_id=abc123 appended
  8. Page loads againMomentCheckoutProvider detects the session_id in the URL automatically and calls your fetchSessionStatus('abc123') callback
  9. Your callback fetches the status from your backend, which queries Moment API
  10. SDK routes to the right handleronSuccess, onFailure, onExpired, or onCancel — and removes session_id from the URL silently
  11. Webhooks are delivered to your backend regardless of outcome

Installation

NPM

npm install momentpay

Yarn

yarn add momentpay

pnpm

pnpm add momentpay

Quick Start

1. Initialize the SDK

Set up two providers at the root of your app: MomentProvider initializes the SDK instance (equivalent to Moment(clientKey) in vanilla JS), and MomentCheckoutProvider configures the checkout product (equivalent to moment.checkout(config)). You provide two fetch callbacks — 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: 'https://yoursite.com/checkout?session_id={SESSION_ID}', // required for redirect payments
        },
      },
    }),
  });
  const { client_token } = await response.json();
  res.json({ client_token });
});

// Called by fetchSessionStatus after customer returns from a redirect payment.
// The SDK extracts session_id from the URL (?session_id=abc123) 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 });
});
Frontend — wrap your app with MomentProvider + MomentCheckoutProvider:
App.jsx
import { MomentProvider, MomentCheckoutProvider } from 'momentpay';

function App() {
  return (
    <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('/api/create-payment-session', { method: 'POST' });
          const data = await response.json();
          return data.client_token;
        }}

        // SDK calls this automatically when ?session_id=... is detected in the URL.
        // 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('/api/session-status?session_id=' + sessionId);
          return response.json();
        }}

        onSuccess={(result) => {
          console.log('Payment successful!', result);
          // Redirect to success page or update UI
        }}
        onFailure={(result) => {
          console.log('Payment failed', result);
          // Show error message
        }}
        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>
  );
}

export default App;

2. Open Checkout

Add useCheckout to any component and call checkout.launch() to trigger checkout:
Checkout.jsx
import { useCheckout } from 'momentpay';

function CheckoutButton() {
  const checkout = useCheckout({
    onSuccess: (result) => {
      console.log('Payment successful!', result);
      // Redirect to success page or update UI
    },
    onFailure: (result) => {
      console.log('Payment failed', result);
      // Show error message
    },
    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
    },
  });

  return (
    <button onClick={() => checkout.launch()}>
      Pay Now
    </button>
  );
}

export default CheckoutButton;

API Reference

MomentProvider (SDK level)

Initializes the Moment SDK instance — equivalent to Moment(clientKey) in vanilla JS. Provides the SDK context to all product providers nested below it. Must be the outermost Moment wrapper in your app.
PropTypeRequiredDescription
clientKeystringYesYour publishable key (pk_test_... or pk_live_...). Determines sandbox vs. production environment.
Example:
<MomentProvider clientKey="pk_test_abc123">
  <MomentCheckoutProvider ...>
    <App />
  </MomentCheckoutProvider>
</MomentProvider>

MomentCheckoutProvider (checkout product)

Initializes the checkout product — equivalent to moment.checkout(config) in vanilla JS. Must be a child of MomentProvider and a parent of any component using useCheckout.
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 session_id is found in the URL 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('/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('/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 hook that returns a checkout object for triggering checkout from any component. Must be used inside MomentCheckoutProvider. 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(options)

Opens the payment checkout. Internally calls fetchClientToken from MomentCheckoutProvider to retrieve a fresh token, then opens checkout. The display variant (modal or inline) is determined from the JWT payload variant claim, which is set when creating the session on your backend.
ParameterTypeRequiredDescription
options.containerstring or ElementNoCSS selector or DOM element — for inline variant only
Returns a Promise that resolves with { checkoutUrl, variant } once the checkout is open. Examples:
// Open checkout — SDK fetches token via fetchClientToken from MomentCheckoutProvider
checkout.launch();

// Inline checkout with custom container
checkout.launch({ container: '#checkout-container' });

checkout.close()

Manually closes the modal or inline checkout.
checkout.close();

checkout.submit() Coming soon

Triggers payment submission from outside the checkout UI — useful when you want an external “Pay” button rather than relying on the checkout’s built-in submit. Only available for the inline variant.
// Wire up an external Pay button
<button onClick={() => checkout.submit()}>Pay</button>

Callback Strategies

The Moment React SDK offers flexible callback handling at both the global (MomentCheckoutProvider) and component (useCheckout 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 redirect patterns
  • Shared notification systems
App.jsx
import { MomentProvider, MomentCheckoutProvider } from 'momentpay';
import { useRouter } from 'next/router';
import { trackEvent } from './analytics';
import { showNotification } from './notifications';

function App() {
  const router = useRouter();

  return (
    <MomentProvider clientKey="pk_your_key_here">
      <MomentCheckoutProvider
        fetchClientToken={async () => {
          const res = await fetch('/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 });
          showNotification('Payment successful!', 'success');
          router.push('/success');
        }}
        onFailure={(result) => {
          // Global failure behavior
          trackEvent('payment_failure', { error: result.error });
          showNotification('Payment failed. Please try again.', 'error');
        }}
        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
ProductCheckout.jsx
function ProductCheckout({ product }) {
  const [status, setStatus] = useState('idle');

  const checkout = useCheckout({
    onSuccess: (result) => {
      // Component-specific behavior
      setStatus('success');
      // This will ALSO trigger the global onSuccess from MomentCheckoutProvider
    },
    onFailure: (result) => {
      setStatus('error');
      // 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.
// Global callback (in MomentCheckoutProvider)
onSuccess: (result) => {
  console.log('Global: Tracking analytics');
  trackEvent('payment_success', result);
}

// Component callback (in useCheckout)
onSuccess: (result) => {
  console.log('Component: Updating local state');
  setPaymentComplete(true);
}

// Both will fire when payment succeeds:
// Output:
// "Component: Updating local state"
// "Global: Tracking analytics"

Example: Multi-Level Callback Architecture

App.jsx
import { MomentProvider, MomentCheckoutProvider } from 'momentpay';
import { analytics, errorTracking, notifications } from './services';

function App() {
  return (
    <MomentProvider clientKey={process.env.NEXT_PUBLIC_MOMENT_KEY}>
      <MomentCheckoutProvider
        fetchClientToken={async () => {
          const res = await fetch('/api/create-payment-session', { method: 'POST' });
          const data = await res.json();
          return data.client_token;
        }}
        // Global: Cross-cutting concerns
        onSuccess={(result) => {
          analytics.track('payment_completed', {
            transaction_id: result.transaction_id,
            payment_method: result.payment_method
          });
        }}
        onFailure={(result) => {
          errorTracking.capture(result.error);
          notifications.error('Payment failed');
        }}
        onCancel={() => {
          analytics.track('payment_cancelled');
        }}
      >
        <AppContent />
      </MomentCheckoutProvider>
    </MomentProvider>
  );
}
SubscriptionPage.jsx
function SubscriptionPage() {
  const [subscriptionActive, setSubscriptionActive] = useState(false);
  const router = useRouter();

  // Component: Subscription-specific logic
  const checkout = useCheckout({
    onSuccess: (result) => {
      // Update subscription state
      setSubscriptionActive(true);
      // Navigate to dashboard
      router.push('/dashboard');
      // Show plan-specific message
      notifications.success(`Welcome to ${selectedPlan.name}!`);
    },
    onFailure: () => {
      // Show retry option
      setShowRetryModal(true);
    }
  });

  // ... rest of component
}

Advanced Usage

Custom Hook for Payment Logic

hooks/usePayment.ts
import { useState, useCallback } from 'react';
import { useCheckout } from 'momentpay';

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

  const checkout = useCheckout({
    onSuccess: () => {
      setSuccess(true);
      setLoading(false);
      setError(null);
    },
    onFailure: (result) => {
      setError(result.error?.message || 'Payment failed');
      setLoading(false);
    },
    onCancel: () => {
      setLoading(false);
      setError(null);
    }
  });

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

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

  return { processPayment, loading, error, success };
}

Using the Custom Hook

ProductCheckout.tsx
import { usePayment } from './hooks/usePayment';

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

  if (success) {
    return <div className="success">Purchase successful!</div>;
  }

  return (
    <div>
      <h2>{product.name}</h2>
      <p>R{product.price.toFixed(2)}</p>

      {error && <div className="error">{error}</div>}

      <button onClick={processPayment} disabled={loading}>
        {loading ? 'Processing...' : 'Buy Now'}
      </button>
    </div>
  );
}

Security Best Practices

  1. Never expose your secret key (sk_...) in frontend code
  2. Always create sessions server-side using your secret key
  3. Validate webhooks to confirm payment status server-side
  4. Use HTTPS for all pages that include the SDK
  5. Handle token expiry by requesting a new token if needed

Next Steps