@moonpay/platform-sdk-react-native 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +370 -0
- package/dist/index.cjs +11 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# @moonpay/platform-sdk-react-native
|
|
2
|
+
|
|
3
|
+
React Native SDK for the [MoonPay Developer Platform](https://dev.moonpay.com). 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.
|
|
4
|
+
|
|
5
|
+
> React Native guides are not yet published on [dev.moonpay.com](https://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](https://www.npmjs.com/package/@moonpay/platform-sdk-web), so the existing guides still apply.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @moonpay/platform-sdk-react-native react-native-webview
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For Expo projects:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx expo install react-native-webview
|
|
17
|
+
npm install @moonpay/platform-sdk-react-native
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Peer dependencies:
|
|
21
|
+
|
|
22
|
+
- `react` >= 18
|
|
23
|
+
- `react-native` >= 0.73 (Hermes supported)
|
|
24
|
+
- `react-native-webview` >= 13
|
|
25
|
+
|
|
26
|
+
The SDK's crypto layer is pure JavaScript (`@noble` libraries) — no native modules or polyfills required.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
**1. Create a session server-side** with [`@moonpay/platform-sdk-node`](https://www.npmjs.com/package/@moonpay/platform-sdk-node) and pass the session token to your app:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// On your server — never ship your API key in the app
|
|
34
|
+
import { createServerClient } from '@moonpay/platform-sdk-node';
|
|
35
|
+
|
|
36
|
+
const server = createServerClient({ apiKey: process.env.MOONPAY_API_KEY });
|
|
37
|
+
|
|
38
|
+
const result = await server.createSession({
|
|
39
|
+
externalCustomerId: 'customer-123',
|
|
40
|
+
deviceIp: customerIp,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (result.ok) {
|
|
44
|
+
// Send result.value.sessionToken to your app
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**2. Wrap your app in `MoonPayProvider`** with the session token:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { MoonPayProvider } from '@moonpay/platform-sdk-react-native';
|
|
52
|
+
|
|
53
|
+
export function App() {
|
|
54
|
+
return (
|
|
55
|
+
<MoonPayProvider sessionToken={sessionToken}>
|
|
56
|
+
<YourApp />
|
|
57
|
+
</MoonPayProvider>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`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.
|
|
63
|
+
|
|
64
|
+
**3. Check the connection and connect the customer:**
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { Button } from 'react-native';
|
|
68
|
+
import { useMoonPay } from '@moonpay/platform-sdk-react-native';
|
|
69
|
+
|
|
70
|
+
function BuyScreen() {
|
|
71
|
+
const { client } = useMoonPay();
|
|
72
|
+
|
|
73
|
+
const startConnect = async () => {
|
|
74
|
+
const connection = await client.getConnection();
|
|
75
|
+
if (!connection.ok) {
|
|
76
|
+
console.error(connection.error);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (connection.value.status === 'connectionRequired') {
|
|
81
|
+
// Presents the connect flow full-screen so the customer can sign in to MoonPay
|
|
82
|
+
await client.connect({
|
|
83
|
+
onEvent: (event) => console.log(event.kind),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return <Button title="Get started" onPress={startConnect} />;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
All client methods return a `Result<T, E>` discriminated union instead of throwing — check `result.ok` before using `result.value`.
|
|
93
|
+
|
|
94
|
+
## Connection statuses
|
|
95
|
+
|
|
96
|
+
`getConnection()` resolves with one of six statuses. Handle `connectionRequired` and `active` at minimum:
|
|
97
|
+
|
|
98
|
+
| Status | Meaning | What to do |
|
|
99
|
+
|--------|---------|------------|
|
|
100
|
+
| `connectionRequired` | New or expired customer | Run the connect flow — `client.connect()` or `<MoonPayConnect>`. Headless integrations: `<MoonPayAuth>` |
|
|
101
|
+
| `active` | Customer is connected | Proceed — quotes, payment methods, and payment frames are ready to use |
|
|
102
|
+
| `pending` | KYC decision is delayed | Show a waiting state; the status can resolve to `active` on a later visit |
|
|
103
|
+
| `failed` | Terminal failure, such as a KYC rejection | Do not retry the connect flow; the `reason` field describes the failure |
|
|
104
|
+
| `unavailable` | Customer is in a restricted location | Hide MoonPay functionality |
|
|
105
|
+
| `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 |
|
|
106
|
+
|
|
107
|
+
## Two ways to render frames
|
|
108
|
+
|
|
109
|
+
The SDK supports both a declarative and an imperative style:
|
|
110
|
+
|
|
111
|
+
- **Components** (`<MoonPayWidget>`, `<MoonPayApplePayButton>`, …) render inline wherever you place them in your layout. Prefer these — you keep full control over placement, styling, and navigation.
|
|
112
|
+
- **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.
|
|
113
|
+
|
|
114
|
+
Headless frames (`<MoonPayConnectionCheck>`, `<MoonPayBuyFrame>`, `<MoonPayConnectionReset>`) render nothing visible — they run a flow in a hidden WebView and report results through `onEvent`.
|
|
115
|
+
|
|
116
|
+
## Examples
|
|
117
|
+
|
|
118
|
+
### Connect a customer inline
|
|
119
|
+
|
|
120
|
+
Render the connect flow as part of your own screen instead of a modal:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { MoonPayConnect } from '@moonpay/platform-sdk-react-native';
|
|
124
|
+
|
|
125
|
+
function ConnectScreen({ onConnected }: { onConnected: () => void }) {
|
|
126
|
+
return (
|
|
127
|
+
<MoonPayConnect
|
|
128
|
+
theme={{ appearance: 'dark' }}
|
|
129
|
+
onEvent={(event) => {
|
|
130
|
+
if (event.kind === 'complete') onConnected();
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
On completion, credentials are applied to the shared client automatically — you can call payment methods right away.
|
|
138
|
+
|
|
139
|
+
### Authenticate with email and OTP (headless)
|
|
140
|
+
|
|
141
|
+
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:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { useState } from 'react';
|
|
145
|
+
import { Button } from 'react-native';
|
|
146
|
+
import { MoonPayAuth, useMoonPay } from '@moonpay/platform-sdk-react-native';
|
|
147
|
+
|
|
148
|
+
function SignInScreen({ onAuthenticated }: { onAuthenticated: () => void }) {
|
|
149
|
+
const { client } = useMoonPay();
|
|
150
|
+
const [showAuth, setShowAuth] = useState(false);
|
|
151
|
+
|
|
152
|
+
const start = async () => {
|
|
153
|
+
const connection = await client.getConnection();
|
|
154
|
+
if (connection.ok && connection.value.status === 'connectionRequired') {
|
|
155
|
+
setShowAuth(true); // the clientToken is now stored on the client
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (!showAuth) {
|
|
160
|
+
return <Button title="Sign in" onPress={start} />;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<MoonPayAuth
|
|
165
|
+
onEvent={(event) => {
|
|
166
|
+
if (event.kind === 'complete') onAuthenticated();
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
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](#connection-statuses)).
|
|
174
|
+
|
|
175
|
+
### Get a quote and render the widget
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { useState } from 'react';
|
|
179
|
+
import { Button } from 'react-native';
|
|
180
|
+
import { MoonPayWidget, useMoonPay } from '@moonpay/platform-sdk-react-native';
|
|
181
|
+
|
|
182
|
+
function WidgetScreen() {
|
|
183
|
+
const { client } = useMoonPay();
|
|
184
|
+
const [quote, setQuote] = useState<string | null>(null);
|
|
185
|
+
|
|
186
|
+
const loadQuote = async () => {
|
|
187
|
+
const result = await client.getQuote({
|
|
188
|
+
source: { asset: { code: 'USD' }, amount: '100' },
|
|
189
|
+
destination: { asset: { code: 'BTC' } },
|
|
190
|
+
});
|
|
191
|
+
if (result.ok) {
|
|
192
|
+
setQuote(result.value.data.signature);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
if (!quote) {
|
|
197
|
+
return <Button title="Get quote" onPress={loadQuote} />;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<MoonPayWidget
|
|
202
|
+
quote={quote}
|
|
203
|
+
onEvent={(event) => {
|
|
204
|
+
if (event.kind === 'complete') {
|
|
205
|
+
console.log('Transaction complete', event.payload.transaction.id);
|
|
206
|
+
}
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Render an Apple Pay button
|
|
214
|
+
|
|
215
|
+
The button mounts inline at its natural size (height 48 by default). Quote updates are pushed into the frame without remounting:
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import { MoonPayApplePayButton } from '@moonpay/platform-sdk-react-native';
|
|
219
|
+
|
|
220
|
+
<MoonPayApplePayButton
|
|
221
|
+
quote={quoteSignature}
|
|
222
|
+
onEvent={(event) => {
|
|
223
|
+
switch (event.kind) {
|
|
224
|
+
case 'complete':
|
|
225
|
+
console.log('Paid', event.payload.transaction.id);
|
|
226
|
+
break;
|
|
227
|
+
case 'challenge':
|
|
228
|
+
// Card issuer requires 3DS — see "Handle a 3DS challenge" below
|
|
229
|
+
setChallengeUrl(event.payload.url);
|
|
230
|
+
break;
|
|
231
|
+
case 'quoteExpired':
|
|
232
|
+
// Fetch a fresh quote and push it without remounting
|
|
233
|
+
refreshQuote().then((signature) => event.payload.setQuote(signature));
|
|
234
|
+
break;
|
|
235
|
+
case 'unsupported':
|
|
236
|
+
// Device or region does not support Apple Pay — render a fallback
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}}
|
|
240
|
+
/>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`<MoonPayGooglePayButton>` and `<MoonPayBuyButton>` follow the same pattern.
|
|
244
|
+
|
|
245
|
+
### Execute a buy headlessly
|
|
246
|
+
|
|
247
|
+
`<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:
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import { MoonPayBuyFrame } from '@moonpay/platform-sdk-react-native';
|
|
251
|
+
|
|
252
|
+
<MoonPayBuyFrame
|
|
253
|
+
quote={quoteSignature}
|
|
254
|
+
externalTransactionId="order-123"
|
|
255
|
+
onEvent={(event) => {
|
|
256
|
+
switch (event.kind) {
|
|
257
|
+
case 'complete':
|
|
258
|
+
console.log('Transaction complete', event.payload.transaction.id);
|
|
259
|
+
break;
|
|
260
|
+
case 'challenge':
|
|
261
|
+
// Card issuer requires 3DS — see "Handle a 3DS challenge" below
|
|
262
|
+
setChallengeUrl(event.payload.url);
|
|
263
|
+
break;
|
|
264
|
+
case 'error':
|
|
265
|
+
console.error(event.payload.code, event.payload.message);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}}
|
|
269
|
+
/>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Like the payment buttons, `quote` is reactive — pushing a fresh signature does not remount the frame.
|
|
273
|
+
|
|
274
|
+
### Handle a 3DS challenge
|
|
275
|
+
|
|
276
|
+
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:
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
// In the payment frame's onEvent handler:
|
|
280
|
+
case 'challenge':
|
|
281
|
+
setChallengeUrl(event.payload.url);
|
|
282
|
+
break;
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
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:
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { MoonPayChallenge } from '@moonpay/platform-sdk-react-native';
|
|
289
|
+
|
|
290
|
+
{challengeUrl && (
|
|
291
|
+
<MoonPayChallenge
|
|
292
|
+
url={challengeUrl}
|
|
293
|
+
onEvent={(event) => {
|
|
294
|
+
if (event.kind === 'complete' && event.payload.flow === 'buy') {
|
|
295
|
+
console.log('Transaction complete', event.payload.transaction.id);
|
|
296
|
+
}
|
|
297
|
+
if (event.kind === 'complete' || event.kind === 'cancelled') {
|
|
298
|
+
setChallengeUrl(null);
|
|
299
|
+
}
|
|
300
|
+
}}
|
|
301
|
+
/>
|
|
302
|
+
)}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Error handling
|
|
306
|
+
|
|
307
|
+
Methods never throw — they return `Result<T, E>`:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
const result = await client.getPaymentMethods();
|
|
311
|
+
|
|
312
|
+
if (result.ok) {
|
|
313
|
+
console.log(result.value.data);
|
|
314
|
+
} else {
|
|
315
|
+
console.error(result.error.code, result.error.message);
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## What the SDK provides
|
|
320
|
+
|
|
321
|
+
**Hook**
|
|
322
|
+
|
|
323
|
+
- `useMoonPay()` — returns the `client` from the nearest `MoonPayProvider`
|
|
324
|
+
|
|
325
|
+
**Client methods**
|
|
326
|
+
|
|
327
|
+
- **Connection** — `getConnection()`, `connect()`, `setupAuth()` (requires a prior `getConnection()` call), `resetConnection()`
|
|
328
|
+
- **Payments data** — `getQuote()`, `getPaymentMethods()`, `deletePaymentMethod()`, `listTransactions()`, `getTransaction()`
|
|
329
|
+
- **Frames** — `setupWidget()`, `setupBuy()`, `setupChallenge()`, `setupAddCard()`
|
|
330
|
+
- **Identity** — `createIdentity()`, `getIdentity()`, `updateIdentity()`, `verifyIdentity()` and file upload helpers for headless integrations
|
|
331
|
+
|
|
332
|
+
**Components**
|
|
333
|
+
|
|
334
|
+
| Component | Renders |
|
|
335
|
+
|-----------|---------|
|
|
336
|
+
| `<MoonPayConnect>` | Connect flow, inline |
|
|
337
|
+
| `<MoonPayConnectionCheck>` | Headless connection check (nothing visible) |
|
|
338
|
+
| `<MoonPayConnectionReset>` | Headless connection reset (nothing visible) |
|
|
339
|
+
| `<MoonPayAuth>` | Email/OTP auth for headless / Identity API integrations — check the connection first |
|
|
340
|
+
| `<MoonPayWidget>` | Full buy widget, inline |
|
|
341
|
+
| `<MoonPayApplePayButton>` | Apple Pay button, inline |
|
|
342
|
+
| `<MoonPayGooglePayButton>` | Google Pay button, inline |
|
|
343
|
+
| `<MoonPayBuyButton>` | Card buy button, inline |
|
|
344
|
+
| `<MoonPayBuyFrame>` | Headless buy execution (nothing visible) |
|
|
345
|
+
| `<MoonPayAddCard>` | Card entry form, inline |
|
|
346
|
+
| `<MoonPayChallenge>` | 3DS challenge, inline |
|
|
347
|
+
|
|
348
|
+
All visible components accept a `style` prop and an `onEvent` callback typed to that frame's events.
|
|
349
|
+
|
|
350
|
+
## Related packages
|
|
351
|
+
|
|
352
|
+
| Package | Use it for |
|
|
353
|
+
|---------|------------|
|
|
354
|
+
| `@moonpay/platform-sdk-node` | Server-side session creation |
|
|
355
|
+
| `@moonpay/platform-sdk-web` | Web apps |
|
|
356
|
+
| `@moonpay/platform-protocol` | Shared protocol and API types |
|
|
357
|
+
|
|
358
|
+
## Documentation
|
|
359
|
+
|
|
360
|
+
Platform guides and API reference: [dev.moonpay.com](https://dev.moonpay.com)
|
|
361
|
+
|
|
362
|
+
- [Introduction](https://dev.moonpay.com/platform/overview/introduction)
|
|
363
|
+
- [API & SDK credentials](https://dev.moonpay.com/platform/guides/api-and-sdk-credentials)
|
|
364
|
+
- [Connect a customer](https://dev.moonpay.com/platform/guides/connect-a-customer)
|
|
365
|
+
- [Pay with Widget](https://dev.moonpay.com/platform/guides/pay-with-widget)
|
|
366
|
+
- [Pay with Apple Pay](https://dev.moonpay.com/platform/guides/pay-with-apple-pay)
|
|
367
|
+
|
|
368
|
+
## License
|
|
369
|
+
|
|
370
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -180,7 +180,10 @@ function applyCredentialsOnComplete(msg, ctx) {
|
|
|
180
180
|
if (msg.kind !== "complete" || !ctx.privateKey)
|
|
181
181
|
return;
|
|
182
182
|
const connection = msg.payload;
|
|
183
|
-
if (connection.status !== import_connection.ConnectionStatus.active)
|
|
183
|
+
if (connection.status !== import_connection.ConnectionStatus.active && connection.status !== import_connection.ConnectionStatus.connectionRequired) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (!connection.credentials)
|
|
184
187
|
return;
|
|
185
188
|
const creds = (0, import_platform_sdk_core.decryptCredentials)(connection.credentials, ctx.privateKey);
|
|
186
189
|
ctx.core.context.setAccessToken(creds.accessToken);
|
|
@@ -555,8 +558,13 @@ function createFrameSession(spec, ctx, props, overrides = {}) {
|
|
|
555
558
|
}
|
|
556
559
|
};
|
|
557
560
|
handle.onMessage((msg) => {
|
|
558
|
-
|
|
559
|
-
|
|
561
|
+
let event;
|
|
562
|
+
try {
|
|
563
|
+
spec.onMessageEffect?.(msg, { ...ctx, privateKey: keyPair?.privateKey });
|
|
564
|
+
event = spec.mapMessage(msg, commands);
|
|
565
|
+
} catch (e) {
|
|
566
|
+
event = spec.errorEvent(e instanceof Error ? e.message : "Failed to process frame message");
|
|
567
|
+
}
|
|
560
568
|
if (event !== null) {
|
|
561
569
|
for (const handler of [...eventHandlers]) {
|
|
562
570
|
handler(event);
|