# GlomoPay React Native SDK

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

> **⚠️ Important:** The React Native SDK currently only supports LRS (Liberalised Remittance Scheme) payments.


## Prerequisites

Before using this SDK, you need:

- API credentials (Public Key) from your GlomoPay dashboard


## Installation

### 1. Standard React Native CLI project

For standard installations, this SDK requires `react-native-webview` as a peer dependency. Install it first:


```bash
npm install react-native-webview
```

Then install the SDK


```bash
npm install @glomopay/react-native-sdk
```

**Recommended: Enhanced Security Compliance**

For enhanced device security compliance (rooted/jailbroken device detection), we HIGHLY recommend installing the peer dependency `jail-monkey`:


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

**Note:** The SDK works without `jail-monkey`, but installing it enables automatic device security compliance checks that help meet regulatory requirements. See [Device Security Compliance](#device-security-compliance) section for more details.

### 2. Expo Projects

This SDK is compatible with Expo projects. Install with:


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

**Note**: Requires custom dev client or EAS Build. Not compatible with Expo Go app.

For Expo Go limitations, see [Expo documentation](https://docs.expo.dev/workflow/customizing/).

## Quick Start

Before start the checkout flow, you'll need to create an LRS order using other Glomo SDKs and obtain the Order ID (Alphanumeric, starts with `order_`).

### 1. Import the SDK


```tsx
import { GlomoLrsCheckout, type GlomoLrsCheckoutRef, type SdkError } from '@glomopay/react-native-sdk';
```

### 2. Add the Component to Your App


```tsx
import React, { useState, useRef } from 'react';
import { View } from 'react-native';
import { GlomoLrsCheckout, type GlomoLrsCheckoutRef } from '@glomopay/react-native-sdk';

function CheckoutScreen() {
  const [orderId, setOrderId] = useState('YOUR_ORDER_ID');
  const checkoutRef = useRef<GlomoLrsCheckoutRef>(null);

  const startLrsCheckout = () => {
    const started = checkoutRef.current?.start();
    if (!started) {
      const status = checkoutRef.current?.getStatus();
      console.log('Checkout could not be started. Current status:', status);
    }
  };

  return (
    <View>
      <GlomoLrsCheckout
        ref={checkoutRef}
        publicKey={'YOUR_PUBLIC_KEY'}
        orderId={orderId}
        onPaymentSuccess={(payload) => {
          console.log('Payment successful!', payload);
          // Handle successful payment
        }}
        onPaymentFailure={(payload) => {
          console.log('Payment failed!', payload);
          // Handle failed payment
        }}
        onConnectionError={(error) => {
          console.log('Could not open LRS checkout!', error);
          // Handle connection/network errors
        }}
        onSdkError={(errors) => {
          errors.forEach((error) => {
            console.error('SDK Error:', error.message);
            // Handle validation errors
          });
        }}
      />
    </View>
  );
}
```

## API Reference

### GlomoLrsCheckout Component

#### Props

| Prop | Type | Required | Description |
|  --- | --- | --- | --- |
| `publicKey` | `string` | Yes | Your public key. If it starts with `test_`, mock mode will be enabled automatically. Otherwise, live mode is used. |
| `orderId` | `string` | Yes | The order ID for this transaction. Must start with `order_` |
| `onPaymentSuccess` | `(payload: GlomoLrsCheckoutPayload) => void` | Yes | Callback function called when the payment is successful |
| `onPaymentFailure` | `(payload: GlomoLrsCheckoutPayload) => void` | Yes | Callback function called when the payment fails |
| `onConnectionError` | `(error: unknown) => void` | No | Will be called on network connection problems. The checkout modal will automatically close before this is triggered. |
| `onPaymentTerminate` | `() => void` | No | Callback called when checkout is terminated. Triggered when the `checkout.closed` event is received from the WebView or when the user dismisses the modal (iOS swipe down) or presses the back button (Android) |
| `onSdkError` | `(errors: SdkError[]) => void` | No | Callback for SDK validation errors and configuration issues. Receives an array of `SdkError` objects when validation fails. |
| `devMode` | `boolean` | No | Enable debug logging (default: `false`) |
| `visible` | `boolean` | No | Control modal visibility manually (advanced usage only). It is HIGHLY recommended to not use this prop directly. |


### Ref Methods


```tsx
interface GlomoLrsCheckoutRef {
  start: () => boolean; // Starts the checkout flow. Returns true if started, false if it cannot be started.
  getStatus: () => LrsCheckoutStatus; // Returns the current checkout status
}
```

Usage:


```tsx
const checkoutRef = useRef<GlomoLrsCheckoutRef>(null);

// Start checkout - returns true if successful, false otherwise
const started = checkoutRef.current?.start();
if (started) {
  console.log('Checkout started successfully');
} else {
  console.log('Checkout could not be started. Check the status.');
}

// Check status
const currentStatus = checkoutRef.current?.getStatus();
```

**Note:**

- The `start()` method returns `true` if the checkout was started successfully, `false` otherwise
- The `start()` method performs device compliance checks first (before any other validation) **if `jail-monkey` is installed**.
  - If the device is rooted or jailbroken and `jail-monkey` is available, it will return `false` and trigger the `onSdkError` callback with a `device_forbidden` error.
  - If `jail-monkey` is not installed, compliance checks are skipped and checkout proceeds normally.
- The `start()` method may be used to restart an LRS checkout for an existing order,
  - If the user cancelled the payment midway (`"payment_cancelled"` status) by pressing the back button on Android, or dismissing the modal on iOS
  - Or if the payment failed or timed-out (`"payment_failed"` status)
- After an LRS checkout for an `orderId` is successful, further calls to the `start()` method will return false, and will not re-trigger an LRS checkout (`"payment_successful"` status)
- Changing the `orderId`/`publicKey` props resets the flow, and `start()` can be triggered again


#### GlomoLrsCheckoutPayload


```typescript
interface GlomoLrsCheckoutPayload {
  orderId: string; // Order identifier (e.g., "order_abc123")
  paymentId: string; // Payment id from GlomoPay
  signature: string; // Signature for verification
}
```

The payload is returned for both successful and failed LRS payments.

#### SdkError

The SDK provides validation and error reporting through the `onSdkError` callback:


```typescript
interface SdkError {
  type: 'validation_error' | 'device_forbidden';
  message: string;
  field?: 'publicKey' | 'orderId' | 'baseCheckoutUrl';
}
```

**Error Types:**

- `validation_error`: Input validation failed (invalid publicKey or orderId)
- `device_forbidden`: Device does not meet compliance requirements (LRS checkout is not permitted on compromised/rooted devices per regulatory policies).


**Example:**


```tsx
<GlomoLrsCheckout
  onSdkError={(errors) => {
    errors.forEach((error) => {
      console.error(`SDK Error [${error.type}]:`, error.message);
      if (error.field) {
        console.error(`Field: ${error.field}`);
      }
    });
    // Handle validation errors appropriately
  }}
  // ... other props
/>
```

**Note:** The SDK validates all inputs when `start()` is called. Validation errors are reported through `onSdkError` and `start()` will return `false` if validation fails.

#### Checkout Status System

The SDK provides a status system to track the checkout lifecycle. Access the current status using the `getStatus()` method on the component ref:


```tsx
const checkoutRef = useRef<GlomoLrsCheckoutRef>(null);
const currentStatus = checkoutRef.current?.getStatus();
```

#### Status Values

The `LrsCheckoutStatus` type includes the following values:

| Status | Description |
|  --- | --- |
| `"ready"` | Checkout is ready to be started |
| `"payment_in_progress"` | Checkout flow is active and ongoing |
| `"payment_successful"` | Payment completed successfully |
| `"payment_failed"` | Payment failed (`glomoLrsCheckoutRef.current?.start()` can be reused with the same `orderId`) |
| `"payment_cancelled"` | User cancelled the checkout midway (`glomoLrsCheckoutRef.current?.start()` can be reused with the same `orderId`) |


#### Status Lifecycle


```
ready → payment_in_progress → payment_successful (order complete)
         ↓                           ↑                    ↑
  payment_cancelled (can call start() again to restart)   |
         ↓                           ↑                    ↑
  payment_failed (can call start() again to retry) → _____⌟
```

## Platform-Specific Behavior

### iOS

- Checkout appears inside a modal within a `pageSheet`
- Users can dismiss `GlomoLrsCheckout` component by swiping down
- `onPaymentTerminate` is called when modal is dismissed


### Android

- Checkout appears in full-screen mode
- Users can press the hardware back button to close the full-screen `GlomoLrsCheckout` component
- Back button triggers `onPaymentTerminate` and closes checkout


## Troubleshooting

### Common Issues

#### 1. Checkout not appearing

**Solution**:

- Ensure `start()` is called via ref and check its return value
- If `start()` returns `false`, check the current status using `getStatus()`
- The checkout status must be `"ready"`, `"payment_cancelled"`, or `"payment_failed"` for `start()` to succeed
- The checkout status must NOT be `"payment_successful"` (successful payments cannot be restarted)
- **Device Security Compliance**: If peer dependency `jail-monkey` is installed and the device does not meet compliance requirements (the device is rooted/jailbroken), `start()` will return `false`. Check `onSdkError` callback for `device_forbidden` errors.
  - However, if `jail-monkey` has not been installed, security compliance checks will be skipped.
- Check that `orderId` is provided and valid (must start with `order_`)
- Ensure `publicKey` is valid (must start with `live_`)
- Implement `onSdkError` callback to receive validation and device compliance error details


#### 2. Expo Go "Invariant Violation" error

**Solution**:

- Expo Go doesn't support native modules
- Use custom dev client: `npx expo prebuild`
- Or use EAS Build for testing


### Debugging Mode

Enable detailed logging:


```tsx
<GlomoLrsCheckout
  devMode={true}
  // ... other props
/>
```

Remember to disable this in production.

### Mock Mode Testing

Mock mode is automatically enabled based on your `publicKey`:

- If your `publicKey` starts with `test_` (case-insensitive), mock mode is automatically enabled and the checkout URL will include `mode=mock`
- Otherwise, live mode is used and the checkout URL will include `mode=live`


**Example:**


```tsx
// Mock mode (publicKey starts with "test_")
<GlomoLrsCheckout
  publicKey="test_abc123..."
  // ... other props
/>

// Live mode (publicKey does not start with "test_")
<GlomoLrsCheckout
  publicKey="live_xyz789..."
  // ... other props
/>
```

Note that `devMode` is separate and only controls console logging, not the payment mode.

### Connection Handling

The SDK provides an (optional) `onConnectionError` callback for connection error events. The checkout modal/page will be dismissed/closed, before this callback is triggered.

**Scenarios:**

- Connection failures
- Internet unavailability
- DNS resolution failures


**Note:** HTTP `4xx`/`5xx` errors will not be captured as connection errors.

**Example:**


```tsx
<GlomoLrsCheckout
  onConnectionError={(error) => {
    // Handle connection/network errors
    // The modal will be automatically closed before this callback is triggered
    Alert.alert('Connection Error', 'Unable to connect to the GlomoPay servers. Please check your internet connection.');
  }}
  // ... other props
/>
```

**Note:** `onConnectionError` is for network/connection errors only. Payment-specific failures should be handled by `onPaymentFailure`.

### Device Security Compliance

**⚠️ Important:** The SDK provides automatic device security compliance checking via the `jail-monkey` library. For enhanced security compliance and to meet regulatory requirements, we **strongly recommend** installing the optional peer dependency `jail-monkey`.

#### Installation


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

#### How It Works

- **With `jail-monkey` installed:** The SDK automatically performs device security compliance checks before allowing an LRS checkout to start. Rooted (Android) or jailbroken (iOS) devices are prevented from starting checkouts.
- **Without `jail-monkey`:** The SDK skips automatic compliance checks, and allows checkout to proceed normally. Regulatory compliance would have to be enforced manually.


#### Features

An automatic device security compliance check:

- Runs synchronously as the **first validation step** in the `start()` method
- Blocks checkout on non-compliant devices to meet regulatory requirements
- Works on both iOS and Android platforms
- Triggers the `onSdkError` callback with a `device_forbidden` error if a non-compliant device is detected


**Example:**


```tsx
<GlomoLrsCheckout
  onSdkError={(errors) => {
    errors.forEach((error) => {
      if (error.type === 'device_forbidden') {
        Alert.alert('Device Unsafe', 'LRS checkout is not permitted on rooted or jailbroken devices per regulatory compliance requirements.');
      }
    });
  }}
  // ... other props
/>
```

### Input Validation

The SDK automatically validates all inputs:

- **`publicKey`**: Must start with `live_` and have a minimum length of 6 characters
- **`orderId`**: Must start with `order_` and have a minimum length of 7 characters


Validation errors are reported through the `onSdkError` callback. The SDK validates inputs when `start()` is called:

- Device security compliance check (first, before any other validation)
- Input validation (publicKey, orderId)
- Checkout URL validation (before building the final URL)


**Example:**


```tsx
<GlomoLrsCheckout
  onSdkError={(errors) => {
    if (errors.length > 0) {
      // Handle validation errors
      Alert.alert('Configuration Error', errors.map((e) => e.message).join('\n'));
    }
  }}
  // ... other props
/>
```