@moonpay/platform-sdk-react-native
v1.9.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
The SDK's crypto layer is pure JavaScript (@noble libraries) — no native modules or polyfills required.
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={...} />).
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
