# GlomoPay React Native SDK v4

Official React Native SDK for integrating GlomoPay payment checkout flows into your mobile applications.

> Upgrading from v1 or v3? See the [Migration Guide](/product-guide/sdk/react-native-sdk/migration). Full [Changelog](/product-guide/sdk/react-native-sdk/changelog) is also available.


## Prerequisites

Before using this SDK, you need:

- API credentials (Public Key) from your GlomoPay dashboard
- An order ID created via the GlomoPay API (or a subscription ID for subscription payments)


## System Requirements

| Requirement | Version |
|  --- | --- |
| Node.js | >= 16.0.0 |
| npm | >= 8.0.0 |
| React | >= 17.0.0 |
| React Native | >= 0.68.0 |
| react-native-webview | ^13.0.0 |
| jail-monkey (optional, but strongly recommended) | ^2.6.0 |


## Installation

### React Native CLI

```bash
npm install @glomopay/react-native-sdk react-native-webview
cd ios && pod install
```

### Expo

```bash
npx expo install @glomopay/react-native-sdk react-native-webview
```

### Recommended: Device Security Compliance

For rooted/jailbroken device detection (strongly recommended for regulated flows):

```bash
npm install jail-monkey
```

The SDK works without `jail-monkey`, but installing it enables automatic device security checks. Without it, the SDK logs a warning on every `start()` call.

## Quick Start

```tsx
import React, { useRef } from "react";
import { View, Button } from "react-native";
import {
  GlomoCheckout,
  ASYNC_PAYMENT_EVENTS,
  type GlomoCheckoutRef,
  type GlomoCheckoutPayload,
  type SdkError,
} from "@glomopay/react-native-sdk";

export default function PaymentScreen() {
  const checkoutRef = useRef<GlomoCheckoutRef>(null);

  const handlePay = async () => {
    const started = await checkoutRef.current?.start();
    if (!started) {
      console.log("Checkout could not start - check onSdkError for details");
    }
  };

  return (
    <View style={{ flex: 1 }}>
      <Button title="Pay Now" onPress={handlePay} />

      <GlomoCheckout
        ref={checkoutRef}
        publicKey="live_pk_abc123"
        orderId="order_xyz789"
        onPaymentSuccess={(payload: GlomoCheckoutPayload) => {
          console.log("Payment success:", payload.paymentId);
        }}
        onPaymentFailure={(payload: GlomoCheckoutPayload) => {
          console.log("Payment failed:", payload.paymentId);
        }}
        onPaymentTerminate={() => {
          console.log("User dismissed checkout");
        }}
        onConnectionError={(error) => {
          console.log("Connection error:", error);
        }}
        onUserJourneyCompleted={(payload) => {
          switch (payload.journeyType) {
            case ASYNC_PAYMENT_EVENTS.BANK_TRANSFER_SUBMITTED:
              console.log("Bank transfer submitted:", payload.transactionReference);
              break;
            case ASYNC_PAYMENT_EVENTS.PAY_VIA_BANK_COMPLETED:
              console.log("Pay via bank completed:", payload.status);
              break;
          }
        }}
        onUserRefusedCameraPermissions={() => {
          console.log("User refused camera - cannot proceed with bank authentication");
        }}
        onSdkError={(errors: SdkError[]) => {
          errors.forEach((e) => console.error(e.type, e.message, e.field));
        }}
      />
    </View>
  );
}
```

## Subscriptions Checkout

To process subscription payments, pass a `subscriptionId` instead of an `orderId`:

```tsx
<GlomoCheckout
  ref={checkoutRef}
  publicKey="live_pk_abc123"
  subscriptionId="sub_xyz789"
  onPaymentSuccess={(payload) => {
    console.log("Subscription payment success:", payload.paymentId);
  }}
  onPaymentFailure={(payload) => {
    console.log("Subscription payment failed:", payload.paymentId);
  }}
  onSdkError={(errors) => {
    errors.forEach((e) => console.error(e.type, e.message));
  }}
/>
```

When `subscriptionId` is provided:

- The SDK skips order type detection (no API call)
- The `subscriptionId` must start with `sub_`
- `onUserJourneyCompleted` is not applicable - subscriptions are card-only (`onPaymentSuccess`/`onPaymentFailure`)
- Do not pass both `orderId` and `subscriptionId` - the SDK will fire `onSdkError`
- The `detecting_order_type` status is not emitted for subscription flows


## Features

- **Cross-platform** - works on both iOS and Android
- **TypeScript** - full type definitions for all exports
- **Unified checkout** - renders the correct checkout flow automatically based on your order
- **Asynchronous payment flows** - single `onUserJourneyCompleted` callback for bank transfer and pay via bank events
- **Camera permissions** - built-in handling for bank authentication
- **Device security** - optional **(but strongly recommended)** `jail-monkey` integration for rooted/jailbroken device detection
- **Input validation** - publicKey and orderId format enforcement before checkout starts
- **Mock mode** - test with `test_` / `mock_` prefixed keys without hitting production


## API Reference

### GlomoCheckout Component

The unified checkout component. Place it in your render tree and control it via a ref.

```tsx
<GlomoCheckout ref={checkoutRef} {...props} />
```

#### Props (`GlomoCheckoutProps`)

| Prop | Type | Required | Description |
|  --- | --- | --- | --- |
| `publicKey` | `string` | Yes | Your GlomoPay public key (must start with `live_`, `mock_`, or `test_`) |
| `orderId` | `string` | No* | Order ID from the GlomoPay API (must start with `order_`) |
| `subscriptionId` | `string` | No* | Subscription ID (must start with `sub_`). Bypasses order type detection. |
| `onPaymentSuccess` | `(payload: GlomoCheckoutPayload) => void` | Yes | Called when payment succeeds |
| `onPaymentFailure` | `(payload: GlomoCheckoutPayload) => void` | Yes | Called when payment fails |
| `onConnectionError` | `(error: unknown) => void` | No | Called on network/connection errors |
| `onPaymentTerminate` | `() => void` | No | Called when user dismisses checkout (back button or swipe) |
| `onSdkError` | `(errors: SdkError[]) => void` | Yes | Called on validation errors or device compliance failures |
| `onUserJourneyCompleted` | `(payload: GlomoUserJourneyCompletedPayload) => void` | No | Called when an asynchronous payment flow completes (bank transfer submission, pay via bank completion). Check `payload.journeyType` to determine the flow. |
| `onUserRefusedCameraPermissions` | `() => void` | No | Called when user denies camera access needed for bank authentication |


*Exactly one of `orderId` or `subscriptionId` must be provided. If both or neither are set, `onSdkError` fires and `start()` returns `false`.

#### Ref Methods (`GlomoCheckoutRef`)

| Method | Signature | Description |
|  --- | --- | --- |
| `start()` | `() => Promise<boolean>` | Validates inputs, and opens the checkout modal. Resolves `true` if successful, `false` otherwise. |
| `getStatus()` | `() => CheckoutStatus` | Returns the current checkout status. |


#### CheckoutStatus

| Status | Description |
|  --- | --- |
| `ready` | Initial state - checkout can be started |
| `detecting_order_type` | Order type detection in progress (after `start()`, before checkout opens) |
| `payment_in_progress` | Checkout modal is open, user is interacting |
| `payment_successful` | Payment completed successfully. Cannot restart with same order |
| `payment_failed` | Payment failed. Can retry with same order |
| `payment_cancelled` | User dismissed checkout mid-flow. Can retry |
| `bank_transfer_submitted` | User submitted bank transfer details |
| `pay_via_bank_completed` | Pay via bank flow completed |


### Type Definitions

#### GlomoCheckoutPayload

```ts
interface GlomoCheckoutPayload {
  orderId: string;
  paymentId: string;
  signature: string;
}
```

#### ASYNC_PAYMENT_EVENTS

```ts
enum ASYNC_PAYMENT_EVENTS {
  BANK_TRANSFER_SUBMITTED = "bank_transfer_submitted",
  PAY_VIA_BANK_COMPLETED = "pay_via_bank_completed",
}
```

#### GlomoUserJourneyCompletedPayload

```ts
interface GlomoUserJourneyCompletedPayload {
  journeyType: ASYNC_PAYMENT_EVENTS;
  orderId?: string;
  status?: string;
  senderAccountNumber?: string;
  transactionReference?: string;
}
```

#### SdkError

```ts
interface SdkError {
  type: "validation_error" | "device_forbidden";
  message: string;
  field?: "publicKey" | "orderId" | "subscriptionId" | "baseCheckoutUrl" | "generatedCheckoutUrl";
}
```

## Asynchronous Payment Flows

The SDK fires `onUserJourneyCompleted` when an asynchronous payment flow completes. Use `payload.journeyType` to determine which flow triggered the callback:

```tsx
onUserJourneyCompleted={(payload) => {
  switch (payload.journeyType) {
    case ASYNC_PAYMENT_EVENTS.BANK_TRANSFER_SUBMITTED:
      /**
       * User submitted bank transfer details.
       * Relevant fields: orderId, senderAccountNumber, transactionReference
       * Note: fields may be absent if the checkout page omits them.
       */
      console.log("Transfer ref:", payload.transactionReference);
      break;
    case ASYNC_PAYMENT_EVENTS.PAY_VIA_BANK_COMPLETED:
      /**
       * Pay via bank journey completed.
       * Relevant fields: orderId, status
       * The SDK deduplicates this event - fires only once per session.
       */
      console.log("Pay via bank status:", payload.status);
      break;
  }
}}
```

The checkout status transitions to `bank_transfer_submitted` or `pay_via_bank_completed` respectively. Use `getStatus()` for granular programmatic checks.

## Camera Permissions

Some checkout flows may require camera access for bank authentication. The SDK handles permission prompts automatically, but you must declare the permissions in your native project config.

### Android

Add to `android/app/src/main/AndroidManifest.xml`:

```xml
<uses-permission android:name="android.permission.CAMERA" />
```

### iOS

Add to `ios/<YourApp>/Info.plist`:

```xml
<key>NSCameraUsageDescription</key>
<string>Camera access is required for identity verification during checkout</string>
```

### Behavior

- **Android**: SDK prompts the user via a native permission dialog. If denied, checkout closes and `onUserRefusedCameraPermissions` fires.
- **iOS**: SDK grants the WebView permission request; iOS shows its own system prompt. If the user denies at the system level, the WebView handles the denial.


## Platform Behavior

- **iOS**: Checkout opens as a `pageSheet` modal (swipe-down to dismiss). Dismissing fires `onPaymentTerminate`.
- **Android**: Checkout opens fullscreen. The hardware back button dismisses it and fires `onPaymentTerminate`.


## Device Security Compliance

The SDK optionally integrates with `jail-monkey` to detect rooted (Android) or jailbroken (iOS) devices.

- If `jail-monkey` is installed and the device is compromised: `start()` returns `false` and `onSdkError` fires with `type: "device_forbidden"`.
- If `jail-monkey` is installed and the device is clean: checkout proceeds normally.
- If `jail-monkey` is not installed: SDK logs a warning and proceeds without checking. This is not recommended for production deployments handling regulated transactions.


## Mock Mode

The SDK infers mock mode from the `publicKey` prefix:

| Prefix | Mode | Environment |
|  --- | --- | --- |
| `live_` | Live | Production |
| `test_` | Mock | Test/sandbox |
| `mock_` | Mock | Test/sandbox |


Mock mode keys route to the sandbox backend. No real transactions are created.

## Connection Handling

The SDK detects connection errors from the WebView and fires `onConnectionError`:

- **iOS**: NSURLErrorDomain codes (-1001 timeout, -1003 host not found, -1004 connection refused, -1009 offline)
- **Android**: ERR_EMPTY_RESPONSE, ERR_CONNECTION_REFUSED, ERR_NAME_NOT_RESOLVED, ERR_INTERNET_DISCONNECTED, ERR_CONNECTION_TIMED_OUT, ERR_NETWORK_CHANGED


The checkout modal is automatically dismissed on connection errors.

## Input Validation

The SDK validates inputs before starting the checkout:

| Field | Rule |
|  --- | --- |
| `publicKey` | Must start with `live_`, `mock_`, or `test_`. Min 6 characters. |
| `orderId` | Must start with `order_`. Min 7 characters. Required when `subscriptionId` is absent. |
| `subscriptionId` | Must start with `sub_`. Trimmed and checked for emptiness. Required when `orderId` is absent. |


Validation failures fire `onSdkError` with `type: "validation_error"` and the relevant `field` name. `start()` returns `false`.

Providing both `orderId` and `subscriptionId` (or neither) also fires `onSdkError`.

## useGlomoCheckout Hook (Deprecated)

> **Deprecated** - Legacy hook, will be removed in a future major version.


`useGlomoCheckout` is a legacy placeholder from the v1.x hook-based API. In v1.x, the equivalent
hook (`useLrsCheckout`) provided reactive WebView state and rendering primitives that merchants used
to build custom checkout UIs. In v3+, the inner checkout components are no longer exported, making
this hook ineffective for custom rendering.

The hook's public return type (`UseGlomoCheckoutReturn`) exposes:

- `start()` - identical to `GlomoCheckoutRef.start()`. Returns `Promise<boolean>`.
- `getStatus()` - returns a point-in-time `CheckoutStatus` snapshot. **Not reactive** - does not
trigger re-renders when status changes.


**Use the `GlomoCheckout` component with a ref instead:**

```tsx
import React, { useRef } from "react";
import { GlomoCheckout, type GlomoCheckoutRef } from "@glomopay/react-native-sdk";

const checkoutRef = useRef<GlomoCheckoutRef>(null);

// Start checkout
const started = await checkoutRef.current?.start();

// Check status (point-in-time, same as hook)
const status = checkoutRef.current?.getStatus();
```

The component-based API provides the same functionality with proper lifecycle management.

## Troubleshooting

### `start()` returns `false`

Check `onSdkError` for details. Common causes:

- Invalid `publicKey` format (must start with `live_`, `mock_`, or `test_`)
- Invalid `orderId` format (must start with `order_`)
- Invalid `subscriptionId` format (must start with `sub_`, non-empty after trimming)
- Both `orderId` and `subscriptionId` provided (or neither)
- Device is rooted/jailbroken (when `jail-monkey` is installed)


### Checkout opens but shows a blank screen

- Verify network connectivity
- Check that the `publicKey` and `orderId` are valid


### Camera permission denied

- Ensure `AndroidManifest.xml` and `Info.plist` include camera permission declarations
- The `onUserRefusedCameraPermissions` callback will fire when the user denies


### `onUserJourneyCompleted` not firing

- Ensure you are passing `onUserJourneyCompleted` as a prop to `GlomoCheckout`
- Verify the checkout flow involves an asynchronous payment (bank transfer or pay via bank)


## Exports

```ts
// Components
export { GlomoCheckout } from "@glomopay/react-native-sdk";

// Hooks (deprecated - use GlomoCheckout component with ref instead)
export { useGlomoCheckout } from "@glomopay/react-native-sdk";

// Enums
export { ASYNC_PAYMENT_EVENTS } from "@glomopay/react-native-sdk";

// Types
export type {
  GlomoCheckoutRef,
  GlomoCheckoutProps,
  GlomoCheckoutPayload,
  CheckoutStatus,
  GlomoUserJourneyCompletedPayload,
  UseGlomoCheckoutReturn,  // deprecated
  SdkError,
} from "@glomopay/react-native-sdk";
```

## Security

The SDK loads checkout pages in an isolated WebView. No PAN, CVV, or sensitive payment data passes through the React Native bridge - all sensitive input is handled within the WebView's sandboxed context.

Device integrity checking is available via optional `jail-monkey` integration (see [Device Security Compliance](#device-security-compliance)).

To report a security vulnerability, email security@glomopay.com. Do not open a public GitHub issue for security reports.

## Related

- [Migration Guide (v1 to v4)](/product-guide/sdk/react-native-sdk/migration) - step-by-step upgrade instructions with before/after code
- [Changelog](/product-guide/sdk/react-native-sdk/changelog) - all release notes
- [React Native SDK v3 (Archived)](/product-guide/sdk/react-native-sdk/v3) - previous version documentation
- [React Native SDK v1 (Archived)](/product-guide/sdk/react-native-sdk/v1) - legacy version documentation
- [Flutter SDK](/product-guide/sdk/flutter-sdk) - GlomoPay SDK for Flutter apps
- [Unified SDK (Web)](/product-guide/sdk/unified-sdk) - GlomoPay SDK for web apps
- [Checkout overview](/product-guide/payin/checkout) - server-side checkout integration and signature verification