@moonpay/platform-sdk-react-native
v1.22.0
Published
MoonPay Developer Platform SDK for React Native
Keywords
Readme
@moonpay/platform-sdk-react-native
React Native SDK for the MoonPay Developer Platform. Connect customers to MoonPay, fetch quotes and payment methods, and render MoonPay frames (widget, Apple Pay, Google Pay, buy, add-card, challenge) as WebViews in your React Native app.
React Native guides are not yet published on dev.moonpay.com — this README is the current starting point. The platform concepts (sessions, connections, quotes, frames) are the same as for the web SDK, so the existing guides still apply.
Installation
npm install @moonpay/platform-sdk-react-native react-native-webviewFor Expo projects:
npx expo install react-native-webview
npm install @moonpay/platform-sdk-react-nativePeer dependencies:
react>= 18react-native>= 0.73 (Hermes supported)react-native-webview>= 13
Crypto polyfill (required on React Native)
The SDK's crypto layer is pure JavaScript (@noble libraries), but key exchange needs a cryptographically-secure crypto.getRandomValues. React Native (Hermes) has no Web Crypto API, so you must install a polyfill and register it before the SDK runs — at your app entry, or a polyfills.ts you import first. Without it, the first getConnection() fails with a clear error telling you to add one.
Expo — expo-crypto provides a Web-Crypto-compatible getRandomValues but does not auto-install it on the global (works in Expo Go):
// polyfills.ts — imported first, before the MoonPay SDK
import * as Crypto from 'expo-crypto';
if (typeof globalThis.crypto?.getRandomValues !== 'function') {
globalThis.crypto = {
...globalThis.crypto,
getRandomValues: (array) => Crypto.getRandomValues(array),
};
}Bare React Native — react-native-get-random-values installs the global on import:
// index.js — first import, before the app
import 'react-native-get-random-values';Quick start
1. Create a session server-side with @moonpay/platform-sdk-node and pass the session token to your app:
// On your server — never ship your API key in the app
import { createServerClient } from '@moonpay/platform-sdk-node';
const server = createServerClient({ apiKey: process.env.MOONPAY_API_KEY });
const result = await server.createSession({
externalCustomerId: 'customer-123',
deviceIp: customerIp,
});
if (result.ok) {
// Send result.value.sessionToken to your app
}2. Wrap your app in MoonPayProvider with the session token:
import { MoonPayProvider } from '@moonpay/platform-sdk-react-native';
export function App() {
return (
<MoonPayProvider sessionToken={sessionToken}>
<YourApp />
</MoonPayProvider>
);
}MoonPayProvider also accepts optional apiBaseUrl and frameBaseUrl props that point the SDK at a different MoonPay environment — useful if MoonPay gives you test endpoints during onboarding. Leave them unset to use production.
Pass an optional theme={{ appearance: 'light' | 'dark' }} to render every MoonPay frame in light or dark mode. Omit it to follow the customer's system preference. The connect flow can override it per call (client.connect({ theme }) or <MoonPayConnect theme={...} />).
Providing the session token later
The sessionToken prop is optional. If you don't have it when the provider mounts (for example, you fetch it from your server after the customer signs in), mount MoonPayProvider without it and call initialize() from useMoonPay() once the token is available:
import { MoonPayProvider, useMoonPay } from '@moonpay/platform-sdk-react-native';
export function App() {
return (
<MoonPayProvider>
<YourApp />
</MoonPayProvider>
);
}
function SignInScreen() {
const { initialize, isInitialized } = useMoonPay();
const onSignedIn = async () => {
const sessionToken = await fetchSessionTokenFromYourServer();
initialize(sessionToken); // the SDK is now ready
};
// ...
}isInitialized is true once a token is available (via the prop or initialize()). Calling a connection method before then resolves to an err() Result. You can also call initialize() again later to replace the token (for example, after a refresh). Choose one style — the controlled sessionToken prop or initialize(); if both are used, the prop wins.
Because initialize() updates React state, the client that carries the new token is available on the next render — not synchronously within the same call. Gate your connection flow on isInitialized (or read client from useMoonPay() after the re-render) rather than reusing a client captured before initialize():
function BuyScreen() {
const { client, isInitialized } = useMoonPay();
useEffect(() => {
if (!isInitialized) return;
// `client` here reflects the initialized session token
client.getConnection().then((result) => {
// ...
});
}, [isInitialized, client]);
}Reusing a client reference captured before initialize() runs against the pre-initialization client and resolves to an err() (no session token).
3. Check the connection and connect the customer:
import { Button } from 'react-native';
import { useMoonPay } from '@moonpay/platform-sdk-react-native';
function BuyScreen() {
const { client } = useMoonPay();
const startConnect = async () => {
const connection = await client.getConnection();
if (!connection.ok) {
console.error(connection.error);
return;
}
if (connection.value.status === 'connectionRequired') {
// Presents the connect flow full-screen so the customer can sign in to MoonPay
await client.connect({
onEvent: (event) => console.log(event.kind),
});
}
};
return <Button title="Get started" onPress={startConnect} />;
}All client methods return a Result<T, E> discriminated union instead of throwing — check result.ok before using result.value.
Connection statuses
getConnection() resolves with one of six statuses. Handle connectionRequired and active at minimum:
| Status | Meaning | What to do |
|--------|---------|------------|
| connectionRequired | New or expired customer | Run the connect flow — client.connect() or <MoonPayConnect>. Headless integrations: <MoonPayAuth> |
| active | Customer is connected | Proceed — quotes, payment methods, and payment frames are ready to use |
| pending | KYC decision is delayed | Show a waiting state; the status can resolve to active on a later visit |
| failed | Terminal failure, such as a KYC rejection | Do not retry the connect flow; the reason field describes the failure |
| unavailable | Customer is in a restricted location | Hide MoonPay functionality |
| termsAcceptanceRequired | Headless integrations only — the customer has no valid Terms of Use attestation on file | Show the Terms of Use in your UI, capture acceptance via POST /platform/v1/terms/attestations, then check the connection again |
Two ways to render frames
The SDK supports both a declarative and an imperative style:
- Components (
<MoonPayWidget>,<MoonPayApplePayButton>, …) render inline wherever you place them in your layout. Prefer these — you keep full control over placement, styling, and navigation. - Client methods (
client.setupWidget(),client.connect(), …) present the frame in a full-screen modal managed by the SDK. Useful when you want a flow to take over the screen without layout work.
Headless frames (<MoonPayConnectionCheck>, <MoonPayBuyFrame>, <MoonPayConnectionReset>) render nothing visible — they run a flow in a hidden WebView and report results through onEvent.
Examples
Connect a customer inline
Render the connect flow as part of your own screen instead of a modal:
import { MoonPayConnect } from '@moonpay/platform-sdk-react-native';
function ConnectScreen({ onConnected }: { onConnected: () => void }) {
return (
<MoonPayConnect
theme={{ appearance: 'dark' }}
onEvent={(event) => {
if (event.kind === 'complete') onConnected();
}}
/>
);
}On completion, credentials are applied to the shared client automatically — you can call payment methods right away.
Authenticate with email and OTP (headless)
Identity API partners can use <MoonPayAuth> instead of the full connect flow — it drives the customer through email and OTP authentication only. The auth frame needs a clientToken, which the SDK stores automatically when getConnection() resolves with connectionRequired, so always check the connection first:
import { useState } from 'react';
import { Button } from 'react-native';
import { MoonPayAuth, useMoonPay } from '@moonpay/platform-sdk-react-native';
function SignInScreen({ onAuthenticated }: { onAuthenticated: () => void }) {
const { client } = useMoonPay();
const [showAuth, setShowAuth] = useState(false);
const start = async () => {
const connection = await client.getConnection();
if (connection.ok && connection.value.status === 'connectionRequired') {
setShowAuth(true); // the clientToken is now stored on the client
}
};
if (!showAuth) {
return <Button title="Sign in" onPress={start} />;
}
return (
<MoonPayAuth
onEvent={(event) => {
if (event.kind === 'complete') onAuthenticated();
}}
/>
);
}Mounting <MoonPayAuth> — or calling setupAuth() — without that prior getConnection() call emits an error event. On completion, credentials are applied to the shared client automatically, and event.payload.status is either active or termsAcceptanceRequired (see Connection statuses).
Get a quote and render the widget
import { useState } from 'react';
import { Button } from 'react-native';
import { MoonPayWidget, useMoonPay } from '@moonpay/platform-sdk-react-native';
function WidgetScreen() {
const { client } = useMoonPay();
const [quote, setQuote] = useState<string | null>(null);
const loadQuote = async () => {
const result = await client.getQuote({
source: { asset: { code: 'USD' }, amount: '100' },
destination: { asset: { code: 'BTC' } },
});
if (result.ok) {
setQuote(result.value.data.signature);
}
};
if (!quote) {
return <Button title="Get quote" onPress={loadQuote} />;
}
return (
<MoonPayWidget
quote={quote}
onEvent={(event) => {
if (event.kind === 'complete') {
console.log('Transaction complete', event.payload.transaction.id);
}
}}
/>
);
}Render an Apple Pay button
The button mounts inline at its natural size (height 48 by default). Quote updates are pushed into the frame without remounting:
import { MoonPayApplePayButton } from '@moonpay/platform-sdk-react-native';
<MoonPayApplePayButton
quote={quoteSignature}
onEvent={(event) => {
switch (event.kind) {
case 'complete':
console.log('Paid', event.payload.transaction.id);
break;
case 'challenge':
// Card issuer requires 3DS — see "Handle a 3DS challenge" below
setChallengeUrl(event.payload.url);
break;
case 'quoteExpired':
// Fetch a fresh quote and push it without remounting
refreshQuote().then((signature) => event.payload.setQuote(signature));
break;
case 'unsupported':
// Device or region does not support Apple Pay — render a fallback
break;
}
}}
/><MoonPayGooglePayButton> and <MoonPayBuyButton> follow the same pattern.
Execute a buy headlessly
<MoonPayBuyFrame> executes a buy in a hidden WebView — your own UI stays in control the whole time. It needs an executable quote: one created with a wallet and paymentMethod. Render the frame when the customer confirms the purchase:
import { MoonPayBuyFrame } from '@moonpay/platform-sdk-react-native';
<MoonPayBuyFrame
quote={quoteSignature}
externalTransactionId="order-123"
onEvent={(event) => {
switch (event.kind) {
case 'complete':
console.log('Transaction complete', event.payload.transaction.id);
break;
case 'challenge':
// Card issuer requires 3DS — see "Handle a 3DS challenge" below
setChallengeUrl(event.payload.url);
break;
case 'error':
console.error(event.payload.code, event.payload.message);
break;
}
}}
/>Like the payment buttons, quote is reactive — pushing a fresh signature does not remount the frame.
Handle a 3DS challenge
Payment frames (buy, buy button, Apple Pay, Google Pay) emit a challenge event when the card issuer requires verification. The event payload carries the full challenge URL:
// In the payment frame's onEvent handler:
case 'challenge':
setChallengeUrl(event.payload.url);
break;Mount <MoonPayChallenge> with that URL. The challenge frame reports the final outcome itself — complete carries the finished transaction, and cancelled means the customer dismissed the verification. Unmount it on either event:
import { MoonPayChallenge } from '@moonpay/platform-sdk-react-native';
{challengeUrl && (
<MoonPayChallenge
url={challengeUrl}
onEvent={(event) => {
if (event.kind === 'complete' && event.payload.flow === 'buy') {
console.log('Transaction complete', event.payload.transaction.id);
}
if (event.kind === 'complete' || event.kind === 'cancelled') {
setChallengeUrl(null);
}
}}
/>
)}Error handling
Methods never throw — they return Result<T, E>:
const result = await client.getPaymentMethods();
if (result.ok) {
console.log(result.value.data);
} else {
console.error(result.error.code, result.error.message);
}What the SDK provides
Hook
useMoonPay()— returns theclientfrom the nearestMoonPayProvider
Client methods
- Connection —
getConnection(),connect(),setupAuth()(requires a priorgetConnection()call),resetConnection() - Payments data —
getQuote(),getPaymentMethods(),deletePaymentMethod(),listTransactions(),getTransaction() - Frames —
setupWidget(),setupBuy(),setupChallenge(),setupAddCard(),setupCustomerExport() - Customer —
getCustomer(),submitCustomerKyc(),getCustomerUploadUrl(),submitCustomerFiles()for headless integrations - Identity (deprecated, use Customer above) —
createIdentity(),getIdentity(),updateIdentity(),verifyIdentity()and file upload helpers
Components
| Component | Renders |
|-----------|---------|
| <MoonPayConnect> | Connect flow, inline |
| <MoonPayConnectionCheck> | Headless connection check (nothing visible) |
| <MoonPayConnectionReset> | Headless connection reset (nothing visible) |
| <MoonPayAuth> | Email/OTP auth for headless / Customer API integrations — check the connection first |
| <MoonPayCustomerExport> | Customer-export consent capture, inline |
| <MoonPayWidget> | Full buy widget, inline |
| <MoonPayApplePayButton> | Apple Pay button, inline |
| <MoonPayGooglePayButton> | Google Pay button, inline |
| <MoonPayBuyButton> | Card buy button, inline |
| <MoonPayBuyFrame> | Headless buy execution (nothing visible) |
| <MoonPayAddCard> | Card entry form, inline |
| <MoonPayChallenge> | 3DS challenge, inline |
All visible components accept a style prop and an onEvent callback typed to that frame's events.
Related packages
| Package | Use it for |
|---------|------------|
| @moonpay/platform-sdk-node | Server-side session creation |
| @moonpay/platform-sdk-web | Web apps |
| @moonpay/platform-protocol | Shared protocol and API types |
Documentation
Platform guides and API reference: dev.moonpay.com
License
MIT
