Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules
/dist
24 changes: 24 additions & 0 deletions .release-it.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = {
git: {
changelog: 'echo "## Changelog\n\n$(npx @uphold/github-changelog-generator -f unreleased | tail -n +4 -f)"',
commitMessage: 'Release ${version}',
requireBranch: 'master',
requireCleanWorkingDir: false,
requireCommits: true,
tagName: 'v${version}'
},
github: {
release: true,
releaseName: 'v${version}'
},
hooks: {
'after:bump': [
'echo "$(npx @uphold/github-changelog-generator -f v${version})\n$(tail -n +2 CHANGELOG.md)" > CHANGELOG.md',
'git add CHANGELOG.md --all'
]
},
npm: {
publish: true,
skipChecks: true
}
};
202 changes: 200 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,200 @@
# topper-react-native-sdk
React Native SDK designed to help developers to integrate Topper into their mobile applications.
# @uphold/topper-react-native-sdk

Seamless integration of [Topper](https://topperpay.com) into React Native applications.

This SDK renders the Topper flow inside a [`react-native-webview`](https://github.com/react-native-webview/react-native-webview) and bridges events from Topper back to your app. It mirrors the API and behavior of [`@uphold/topper-web-sdk`](https://github.com/uphold/topper-web-sdk), adapted for React Native.

## Installation

```sh
npm install @uphold/topper-react-native-sdk
```

### Peer dependencies

This package relies on the following peer dependencies, which your app must install:

```sh
npm install react react-native react-native-webview
```

`react-native-webview` contains native code. For bare React Native run `npx pod-install` after installing; for Expo use `npx expo install react-native-webview`.

## Quick start

The simplest way to use the SDK is the `<TopperWebView>` component with inline event props:

```tsx
import { TopperWebView, TOPPER_ENVIRONMENTS } from '@uphold/topper-react-native-sdk';

export default function TopperScreen() {
return (
<TopperWebView
bootstrapToken="YOUR_BOOTSTRAP_TOKEN"
config={{ environment: TOPPER_ENVIRONMENTS.SANDBOX }}
onOrderPlaced={({ data }) => console.log('Order placed', data)}
/>
);
}
```

> The component needs a sized container. Use explicit styling otherwise the WebView renders with zero height and nothing appears.

The `bootstrapToken` is a signed JWT generated by your backend. See the [Topper widget documentation](https://docs.topperpay.com/widgets) for how to create one.

## Usage

### `<TopperWebView>` — flat props (recommended)

Pass configuration and event handlers directly as props. The component creates and manages a `TopperReactNativeSdk` instance internally and disposes it on unmount.

```tsx
import { TopperWebView, TOPPER_ENVIRONMENTS, TOPPER_THEMES, TOPPER_FLOWS } from '@uphold/topper-react-native-sdk';

<TopperWebView
bootstrapToken="YOUR_BOOTSTRAP_TOKEN"
config={{
environment: TOPPER_ENVIRONMENTS.SANDBOX,
theme: TOPPER_THEMES.LIGHT,
active_flow: TOPPER_FLOWS.CRYPTO_ONRAMP,
}}
onEvent={({ name, data }) => console.log('any event', name, data)}
onOrderContinueButtonClicked={({ data }) => console.log('continue', data)}
onOrderPlaced={({ data }) => console.log('placed', data)}
onOrderWalletSendButtonClicked={({ data }) => console.log('wallet send', data)}
onWidgetContinueButtonClicked={({ data }) => console.log('widget continue', data)}
/>
```

#### Props

| Prop | Type | Description |
| --- | --- | --- |
| `bootstrapToken` | `string` | A single bootstrap token (JWT) generated by your backend. |
| `bootstrapTokens` | `string[]` | Multiple bootstrap tokens (joined with `;`). Use instead of `bootstrapToken`. |
| `config` | `Config` | Configuration object (see [Config](#config)). |
| `onEvent` | `EventHandler` | Catch-all handler — receives every event with `{ name, data }`. |
| `onOrderContinueButtonClicked` | `EventHandler` | Fired when the user taps continue on the order screen. |
| `onOrderPlaced` | `EventHandler` | Fired when an order is placed. |
| `onOrderWalletSendButtonClicked` | `EventHandler` | Fired on the off-ramp wallet send step. |
| `onWidgetContinueButtonClicked` | `EventHandler` | Fired when the user taps continue on the widget. |
| `style` | `StyleProp<ViewStyle>` | Style applied to the underlying WebView. |
| `webViewProps` | `Partial<WebViewProps>` | Escape hatch for any [`react-native-webview` prop](https://github.com/react-native-webview/react-native-webview/blob/master/docs/Reference.md). |

### `<TopperWebView>` — SDK instance

For advanced cases where you want to manage event registration imperatively, create a `TopperReactNativeSdk` instance and pass it via the `sdk` prop. Register handlers with `on()` before rendering.

```tsx
import { useRef } from 'react';
import { TopperWebView, TopperReactNativeSdk, TOPPER_ENVIRONMENTS, TOPPER_EVENTS } from '@uphold/topper-react-native-sdk';

export default function TopperScreen() {
const sdkRef = useRef(
(() => {
const sdk = new TopperReactNativeSdk({ environment: TOPPER_ENVIRONMENTS.SANDBOX });

sdk.on(TOPPER_EVENTS.ORDER_PLACED, ({ data }) => console.log('placed', data));
sdk.on(TOPPER_EVENTS.ALL, ({ name, data }) => console.log('any event', name, data));

return sdk;
})()
);

return <TopperWebView sdk={sdkRef.current} bootstrapToken="YOUR_BOOTSTRAP_TOKEN" />;
}
```

## API

### `TopperReactNativeSdk`

```ts
const sdk = new TopperReactNativeSdk(config?: Config);
```

| Method | Description |
| --- | --- |
| `on(eventName, handler)` | Register an event handler. Can be called before or after render. |
| `buildUrl({ bootstrapToken?, bootstrapTokens? })` | Build the fully-qualified Topper URL with all query params. Used internally by `<TopperWebView>`. |
| `createMessageHandler()` | Returns the `onMessage` handler for the WebView. Used internally by `<TopperWebView>`. |
| `dispose()` | Clear all registered event handlers. Called automatically on component unmount. |

### Config

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `environment` | `TOPPER_ENVIRONMENTS` | `production` | `production` or `sandbox`. Selects the Topper base URL. |
| `theme` | `TOPPER_THEMES` | — | `light` or `dark`. |
| `locale` | `TOPPER_LOCALES` | — | `en`, `en-US`, `pt`, `pt-BR`, `es`, `es-ES`. |
| `initial_screen` | `TOPPER_INITIAL_SCREENS` | `null` | `authentication`. |
| `active_flow` | `TOPPER_FLOWS` | `null` | `crypto_onramp` or `crypto_offramp`. |
| `baseUrl` | `string` | — | Overrides the base URL (e.g. a local Topper instance for development). When set, takes precedence over `environment`. |

The platform (`is_ios_webview` / `is_android_webview`) is detected automatically from `Platform.OS` — you do not need to set it.

### Events

Accessible via `TOPPER_EVENTS`:

| Event | Value |
| --- | --- |
| `ALL` | `*` |
| `ORDER_CONTINUE_BUTTON_CLICKED` | `orderContinueButtonClicked` |
| `ORDER_PLACED` | `orderPlaced` |
| `ORDER_WALLET_SEND_BUTTON_CLICKED` | `orderWalletSendButtonClicked` |
| `WIDGET_CONTINUE_BUTTON_CLICKED` | `widgetContinueButtonClicked` |

Handlers registered on `ALL` receive `{ name, data }`; handlers for a specific event receive `{ data }`.

> **Note:** only the events you register are requested from Topper (they are serialized into the URL). Some events are only emitted by Topper when their specific name is registered — registering only `ALL` (`onEvent`) is not sufficient to receive those. Register the specific handler (e.g. `onOrderContinueButtonClicked`) to guarantee delivery.

### Exports

```ts
import {
TopperReactNativeSdk,
TopperWebView,
TOPPER_ENVIRONMENTS,
TOPPER_EVENTS,
TOPPER_FLOWS,
TOPPER_INITIAL_SCREENS,
TOPPER_LOCALES,
TOPPER_THEMES,
TOPPER_URLS,
} from '@uphold/topper-react-native-sdk';

import type {
Config,
EventPayload,
EventHandler,
TopperWebViewWithSdkProps,
TopperWebViewWithPropsProps,
} from '@uphold/topper-react-native-sdk';
```

## How it works

Topper runs as a web app inside the WebView. When an event occurs, Topper posts a message through the `window.ReactNativeWebView` bridge with the shape:

```json
{ "name": "orderPlaced", "payload": { "...": "..." }, "source": "@topper-web-sdk" }
```

The SDK's message handler parses this, validates the `@topper-web-sdk` source, and dispatches to your registered handlers. The `is_ios_webview` / `is_android_webview` query params (set automatically) tell Topper to use the React Native bridge instead of `window.postMessage`.

## Requirements & notes

- **Container size** — the WebView needs a sized parent.
- **Cloudflare Turnstile** — Topper uses Cloudflare Turnstile for bot prevention, which renders inside `about:blank` / `about:srcdoc` frames. The component **allows these origins by default** (`['https://*', 'http://*', 'about:blank', 'about:srcdoc']`), so no setup is required. If you need a different policy, override it via `webViewProps`:
```tsx
webViewProps={{
originWhitelist: ['https://*', 'http://*', 'about:blank', 'about:srcdoc'],
}}
```
- **Camera & microphone permissions** — required for identity verification (Veriff). Configure these in your app's native permissions.
- See the [Topper Mobile integration guide](https://docs.topperpay.com/mobile) for the full WebView requirements.

## License

MIT
20 changes: 20 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createTypeScriptConfig } from 'eslint-config-uphold/configs';

const tsConfig = await createTypeScriptConfig();

export default [
{
ignores: ['dist/**']
},
...tsConfig,
{
rules: {
'n/no-process-env': 'off',
'n/no-process-exit': 'off',
'n/no-restricted-import': 'off',
'n/no-restricted-require': 'off',
'n/no-sync': 'off',
'sql-template/no-unsafe-query': 'off'
}
}
];
Loading