npm package: @jimrising/easymerchantsdk-react-native
Description: This guide provides instructions for integrating the EasyMerchant SDK into a React Native application for Android and iOS platforms. It supports two payment flows: `makePayment` (direct payment with API key and secret) and `makePaymentV2` (client token-ba
Latest version: 2.8.8

# EasyMerchant SDK React Native Integration

This guide provides instructions for integrating the EasyMerchant SDK into a React Native application for Android and iOS platforms. It supports two payment flows: `makePayment` (direct payment with API key and secret) and `makePaymentV2` (client token-based payment). The SDK enables card payments, ACH transfers, recurring payments, 3D Secure authentication, and customizable billing/additional fields with a unified configuration object.

## Prerequisites

- **Node.js and npm**: Version 16 or higher.
- **React Native Environment**: Set up as per the [official React Native documentation](https://reactnative.dev/docs/environment-setup).
- **Ruby**: Version 3.2.8 or higher for iOS setup (required for CocoaPods).
- **Xcode**: Version 14 or higher for iOS development.
- **Android Studio**: For Android development, with Gradle configured.
- **EasyMerchant SDK Credentials**: Obtain API keys and secret keys for `sandbox` or `production` environments (for `makePayment`), and access to the payment intent API for client token generation (for `makePaymentV2`).

## Installation

### 1. Add the SDK to Your Project

Add the EasyMerchant SDK to your `package.json` under `dependencies`:

```json
"dependencies": {
  "@jimrising/easymerchantsdk-react-native": "^2.3.9"
}
```

Or install it via npm:

```bash
npm install @jimrising/easymerchantsdk-react-native
```

### 2. Android Configuration

1. Open `android/build.gradle` and add the following to the `allprojects.repositories` section:

```gradle
allprojects {
  repositories {
    google()
    mavenCentral()
    maven { url 'https://jitpack.io' }
    maven {
      url = uri("https://maven.pkg.github.com/jimrising/easymerchantsdk-react-native")
      credentials {
        username = properties.getProperty('GITHUB_USERNAME')
        password = properties.getProperty('GITHUB_PASSWORD')
      }
    }
  }
}
```

2. Define `GITHUB_USERNAME` and `GITHUB_PASSWORD` in `android/gradle.properties`:

```properties
GITHUB_USERNAME=your-github-username
GITHUB_PASSWORD=your-github-token
```

3. Sync your project with Gradle to apply changes.

### 3. iOS Configuration

1. **Update AppDelegate.swift**

Modify `ios/AppDelegate.swift` to initialize the React Native bridge and set up the EasyMerchant SDK:

```swift
import UIKit
import easymerchantsdk
import React

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    let jsCodeLocation: URL

    #if DEBUG
      jsCodeLocation = URL(string: "http://localhost:8081/index.bundle?platform=ios")!
    #else
      jsCodeLocation = Bundle.main.url(forResource: "main", withExtension: "jsbundle")!
    #endif

    let bridge = RCTBridge(
      bundleURL: jsCodeLocation,
      moduleProvider: nil,
      launchOptions: launchOptions
    )

    guard let validBridge = bridge else {
      fatalError("React Native bridge failed to initialize.")
    }

    let rootView = RCTRootView(
      bridge: validBridge,
      moduleName: "EasyMerchantTestApp", // Replace with your app's module name
      initialProperties: nil
    )

    self.window = UIWindow(frame: UIScreen.main.bounds)
    let rootViewController = UIViewController()
    rootViewController.view = rootView
    self.window?.rootViewController = rootViewController
    self.window?.makeKeyAndVisible()

    if let easyMerchantSdkPlugin = validBridge.module(for: EasyMerchantSdkPlugin.self) as? EasyMerchantSdkPlugin {
      easyMerchantSdkPlugin.setViewController(rootViewController)
    } else {
      print("Failed to retrieve EasyMerchantSdkPlugin instance from React Native bridge.")
    }
    return true
  }
}
```

2. **Update Podfile**

In `ios/Podfile`, add the EasyMerchant SDK pod:

```ruby
platform :ios, '16.0'

pod 'easymerchantsdk', :path => '../node_modules/@jimrising/easymerchantsdk-react-native/ios'

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
```

3. Install pod dependencies:

```bash
cd ios
pod install --repo-update
```

## Usage

This section explains how to configure and process payments using `makePayment` (direct payment) and `makePaymentV2` (client token-based payment). Each flow uses a specific configuration object, with `makePaymentV2` requiring a `clientToken` instead of `apiKey` and `secretKey`.

### 1. Import Required Modules

```javascript
import { NativeModules, Platform, Alert, NativeEventEmitter } from 'react-native';
const { RNEasymerchantsdk, EasyMerchantSdk } = NativeModules;
```

### 2. Define Configurations

#### Configuration for `makePayment`

The `makePayment` flow requires `apiKey` and `secretKey`. Below is a sample configuration:

```javascript
const configMakePayment = {
  amount: '5.0', // String or number, must be positive
  appearanceSettings: {
    bodyBackgroundColor: '#121212', // String: hex color
    borderRadius: '8', // String: border radius in pixels
    containerBackgroundColor: '#1E1E1E', // String: hex color
    fontFamily: '"Inter", sans-serif', // String: font family
    fontSize: '16', // String: font size in pixels
    fontWeight: 500, // Number: font weight
    primaryButtonBackgroundColor: '#2563EB', // String: hex color
    primaryButtonFontColor: '#FFFFFF', // String: hex color
    primaryButtonHoverColor: '#1D4ED8', // String: hex color
    primaryFontColor: '#FFFFFF', // String: hex color
    secondaryButtonBackgroundColor: '#374151', // String: hex color
    secondaryButtonFontColor: '#E5E7EB', // String: hex color
    secondaryButtonHoverColor: '#4B5563', // String: hex color
    secondaryFontColor: '#B0B0B0', // String: hex color
    theme: 'light', // String: 'light' or 'dark'
  },
  apiKey: 'replace-with-api-key', // String: environment-specific API key
  authenticatedACH: true, // Boolean: enable authenticated ACH
  currency: 'usd', // String: currency code, e.g., 'usd'
  email: 'john.doe@example.com', // String: customer email
  environment: 'sandbox', // String: 'sandbox' or 'production'
  fields: {
    additional: [
      { name: 'phone_number', required: false, value: '+1-555-123-4567' },
      { name: 'description', required: true, value: 'Test payment for development purposes' }
    ],
    billing: [
      { name: 'address', required: true, value: '123 Main Street, Suite 100' },
      { name: 'country', required: true, value: 'United States' },
      { name: 'state', required: true, value: 'California' },
      { name: 'city', required: false, value: 'San Francisco' },
      { name: 'postal_code', required: true, value: '94105' }
    ],
    visibility: { additional: false, billing: false }, // Object: field visibility
  },
  grailPayParams: {
    brandingName: 'Lyfecycle Payments', // String: branding name
    finderSubtitle: 'Search for your bank', // String: bank finder subtitle
    role: 'business', // String: GrailPay role
    searchPlaceholder: 'Enter bank name', // String: search placeholder
    timeout: 10, // Number: timeout in seconds
  },
  isEmail: true, // Boolean: allow email editing
  is_recurring: false, // Boolean: enable recurring payments
  metadata: {
    metadataOne: 'metadataOne', // String: custom metadata
    metadataTwo: 'metadataTwo' // String: custom metadata
  },
  name: 'John Doe', // String: customer name
  numOfCycle: 2, // if is_recurring == true
  paymentMethods: ['card', 'ach'], // Array: at least one method ('card', 'ach')
  recurringIntervals: ['daily', 'weekly', 'monthly'], // if is_recurring == true
  recurringStartDate: 'dd/MM/yyyy', // must be today  // required if is_recurring == true  
  recurringStartDateType: 'fixed', // if is_recurring == true
  saveAccount: true, // Boolean: save account details for ACH
  saveCard: true, // Boolean: save card details
  secretKey: 'replace-with-secret-key', // String: environment-specific secret key
  secureAuthentication: true, // Boolean: enable 3D Secure
  showDonate: false, // Boolean: show donation option
  showReceipt: true, // Boolean: show receipt after payment
  showSubmitButton: true, // Boolean: show submit button
  showTotal: true, // Boolean: display total amount
  submitButtonText: 'Submit', // String: payment button text
  tokenOnly: false, // Boolean: true for tokenization, false for full payment
};

```

#### Configuration for `makePaymentV2`

The `makePaymentV2` flow requires a `clientToken` and omits `apiKey` and `secretKey`. Below is a sample configuration:

```javascript
const configMakePaymentV2 = {
  appearanceSettings: {
    bodyBackgroundColor: '#121212', // String: hex color
    borderRadius: '8', // String: border radius in pixels
    containerBackgroundColor: '#1E1E1E', // String: hex color
    fontFamily: '"Inter", sans-serif', // String: font family
    fontSize: '16', // String: font size in pixels
    fontWeight: 500, // Number: font weight
    primaryButtonBackgroundColor: '#2563EB', // String: hex color
    primaryButtonFontColor: '#FFFFFF', // String: hex color
    primaryButtonHoverColor: '#1D4ED8', // String: hex color
    primaryFontColor: '#FFFFFF', // String: hex color
    secondaryButtonBackgroundColor: '#374151', // String: hex color
    secondaryButtonFontColor: '#E5E7EB', // String: hex color
    secondaryButtonHoverColor: '#4B5563', // String: hex color
    secondaryFontColor: '#B0B0B0', // String: hex color
    theme: 'light', // String: 'light' or 'dark'
  },
  authenticatedACH: true, // Boolean: enable authenticated ACH
  clientToken: 'replace-with-client-token', // String: client token from payment intent API
  currency: 'usd', // String: currency code, e.g., 'usd'
  email: 'john.doe@example.com', // String: customer email
  environment: 'sandbox', // String: 'sandbox' or 'production'
  fields: {
    additional: [
      { name: 'phone_number', required: false, value: '+1-555-123-4567' },
      { name: 'description', required: true, value: 'Test payment for development purposes' }
    ],
    billing: [
      { name: 'address', required: true, value: '123 Main Street, Suite 100' },
      { name: 'country', required: true, value: 'United States' },
      { name: 'state', required: true, value: 'California' },
      { name: 'city', required: false, value: 'San Francisco' },
      { name: 'postal_code', required: true, value: '94105' }
    ],
    visibility: { additional: false, billing: false }, // Object: field visibility
  },
  grailPayParams: {
    brandingName: 'Lyfecycle Payments', // String: branding name
    finderSubtitle: 'Search for your bank', // String: bank finder subtitle
    role: 'business', // String: GrailPay role
    searchPlaceholder: 'Enter bank name', // String: search placeholder
    timeout: 10, // Number: timeout in seconds
  },
  isEmail: true, // Boolean: allow email editing
  metadata: {
    metadataOne: 'metadataOne', // String: custom metadata
    metadataTwo: 'metadataTwo', // String: custom metadata
  },
  name: 'John Doe', // String: customer name
  paymentMethods: ['card', 'ach'], // Array: at least one method ('card', 'ach')
  saveAccount: true, // Boolean: save account details for ACH
  saveCard: true, // Boolean: save card details
  secureAuthentication: true, // Boolean: enable 3D Secure
  showDonate: false, // Boolean: show donation option
  showReceipt: true, // Boolean: show receipt after payment
  showSubmitButton: true, // Boolean: show submit button
  showTotal: true, // Boolean: display total amount
  submitButtonText: 'Submit', // String: payment button text
};
```

**Key Differences**:
- **makePayment**: Requires `apiKey` and `secretKey`, used for direct payment processing.
- **makePaymentV2**: Requires `clientToken` (obtained from the payment intent API) and omits `apiKey` and `secretKey`.
- **Environment Initialization**: Required for `makePayment` on iOS; not needed for `makePaymentV2`.


### 3. Initialize Environment (iOS Only, for `makePayment`)

For `makePayment` on iOS, configure the environment before processing payments:

```javascript
const initializeEnvironment = async () => {
  try {
    await EasyMerchantSdk.setViewController();
    await EasyMerchantSdk.configureEnvironment(configMakePayment.environment, configMakePayment.apiKey, configMakePayment.secretKey);
    console.log(`iOS Environment configured: ${configMakePayment.environment}`);
  } catch (error) {
    console.error('iOS Initialization Error:', error);
    Alert.alert('Error', `Failed to configure iOS environment: ${error.message}`);
  }
};
```

**Note**: This step is required only for `makePayment` on iOS. Android and `makePaymentV2` do not require environment initialization.

### 4. Process Payments

#### Using `makePayment` (Direct Payment)

```javascript
const handlePayment = async () => {
  if (!configMakePayment.amount || isNaN(parseFloat(configMakePayment.amount)) || parseFloat(configMakePayment.amount) <= 0) {
    Alert.alert('Error', 'Please set a valid amount');
    return;
  }
  if (!configMakePayment.paymentMethods || configMakePayment.paymentMethods.length === 0) {
    Alert.alert('Error', 'Please select at least one payment method');
    return;
  }
  if (configMakePayment.is_recurring && (!configMakePayment.recurringIntervals || configMakePayment.recurringIntervals.length === 0)) {
    Alert.alert('Error', 'Please select at least one interval for recurring payment');
    return;
  }

  try {
    if (Platform.OS === 'android') {
      const response = await RNEasymerchantsdk.makePayment(configMakePayment);
      const raw = response?.response ? JSON.parse(response.response) : {};
      const parsedResponse = {
        ...raw,
        billingInfo: raw.billingInfo ? JSON.parse(raw.billingInfo) : raw.billingInfo,
        additional_info: raw.additional_info ? JSON.parse(raw.additional_info) : raw.additional_info
      };
      console.log('Android Payment Response:', parsedResponse);
      Alert.alert('Success', 'Payment processed!');
    } else {
      await initializeEnvironment();
      const configString = JSON.stringify(configMakePayment);
      const result = await EasyMerchantSdk.makePayment(configString);
      const refToken = result?.additionalInfo?.threeDSecureStatus?.data?.ref_token;
      console.log('iOS Payment Response:', result);
      if (refToken) console.log('Reference Token:', refToken);
      Alert.alert('Success', 'Payment processed!');
    }
  } catch (error) {
    console.error(`${Platform.OS === 'android' ? 'Android' : 'iOS'} Payment Error:`, error);
    Alert.alert('Payment Error', error.message || 'Unknown error');
  }
};
```

#### Using `makePaymentV2` (Client Token-Based)

```javascript
const handlePaymentV2 = async () => {
  if (!configMakePaymentV2.clientToken) {
    Alert.alert('Error', 'Please provide a valid client token');
    return;
  }
  if (!configMakePaymentV2.paymentMethods || configMakePaymentV2.paymentMethods.length === 0) {
    Alert.alert('Error', 'Please select at least one payment method');
    return;
  }
  if (configMakePaymentV2.is_recurring && (!configMakePaymentV2.recurringIntervals || configMakePaymentV2.recurringIntervals.length === 0)) {
    Alert.alert('Error', 'Please select at least one interval for recurring payment');
    return;
  }

  try {
    if (Platform.OS === 'android') {
      const response = await RNEasymerchantsdk.makePaymentV2(configMakePaymentV2);
      const raw = response?.response ? JSON.parse(response.response) : {};
      const parsedResponse = {
        ...raw,
        billingInfo: raw.billingInfo ? JSON.parse(raw.billingInfo) : raw.billingInfo,
        additional_info: raw.additional_info ? JSON.parse(raw.additional_info) : raw.additional_info
      };
      console.log('Android Payment V2 Response:', parsedResponse);
      Alert.alert('Success', 'Payment V2 processed!');
    } else {
      const configString = JSON.stringify(configMakePaymentV2);
      const result = await EasyMerchantSdk.makePaymentV2(configString);
      const refToken = result?.additionalInfo?.threeDSecureStatus?.data?.ref_token;
      console.log('iOS Payment V2 Response:', result);
      if (refToken) console.log('Reference Token:', refToken);
      Alert.alert('Success', 'Payment V2 processed!');
    }
  } catch (error) {
    console.error(`${Platform.OS === 'android' ? 'Android' : 'iOS'} Payment V2 Error:`, error);
    Alert.alert('Payment V2 Error', error.message || 'Unknown error');
  }
};
```

**How It Works**:
- **makePayment**: Uses `configMakePayment` with `apiKey` and `secretKey`. Requires environment initialization on iOS.
- **makePaymentV2**: Uses `configMakePaymentV2` with `clientToken`. Does not require environment initialization.
- **Android**: Pass the config object directly. Parse `billingInfo` and `additional_info` if they are JSON strings.
- **iOS**: Stringify the config (`JSON.stringify(config)`) for both methods. Store `ref_token` for 3D Secure transactions.
- **Validation**: Ensures `amount` is positive, at least one payment method is selected, and a `clientToken` is provided for `makePaymentV2`.
- **Error Handling**: Displays user-friendly alerts for errors.

### 5. Android Event Listeners

Set up event listeners for Android to handle payment success, status, and errors:

```javascript
const setupAndroidEventListeners = () => {
  if (Platform.OS !== 'android' || !RNEasymerchantsdk) {
    console.warn('RNEasymerchantsdk not available or not Android.');
    return () => {};
  }

  const easyMerchantEvents = new NativeEventEmitter(RNEasymerchantsdk);
  const successSub = easyMerchantEvents.addListener('PaymentSuccess', (data) => {
    const parsed = JSON.parse(data.response || '{}');
    const result = {
      ...parsed,
      billingInfo: parsed.billingInfo ? JSON.parse(parsed.billingInfo) : parsed.billingInfo,
      additional_info: parsed.additional_info ? JSON.parse(parsed.additional_info) : parsed.additional_info
    };
    console.log('Payment Success:', result);
    Alert.alert('Success', 'Payment processed!');
  });

  const statusSub = easyMerchantEvents.addListener('PaymentStatus', (data) => {
    const parsed = JSON.parse(data.statusResponse || '{}');
    console.log('Payment Status:', parsed);
    Alert.alert('Status', JSON.stringify(parsed));
  });

  const statusErrorSub = easyMerchantEvents.addListener('PaymentStatusError', (data) => {
    let parsedError = data.error;
    try {
      if (typeof parsedError === 'string') parsedError = JSON.parse(parsedError);
    } catch (e) {}
    console.error('Payment Status Error:', parsedError);
    Alert.alert('Status Error', JSON.stringify(parsedError));
  });

  return () => {
    successSub.remove();
    statusSub.remove();
    statusErrorSub.remove();
  };
};
```

**Note**: Call `setupAndroidEventListeners()` during app initialization and clean up listeners when done (returned function).

### 6. Check Payment Status (Android Only)

Check the status of a payment on Android:

```javascript
const checkPaymentStatus = async () => {
  if (Platform.OS !== 'android') {
    Alert.alert('Error', 'Payment status check is Android-only');
    return;
  }
  try {
    const response = await RNEasymerchantsdk.checkPaymentStatus();
    console.log('Payment Status:', response);
    Alert.alert('Status', JSON.stringify(response));
  } catch (error) {
    console.error('Status Check Error:', error);
    Alert.alert('Status Check Error', error.message || 'Unknown error');
  }
};
```

### 7. Payment Reference (iOS Only)

Retrieve payment reference details on iOS using a `ref_token`:

```javascript
const checkPaymentReference = async (refToken) => {
  if (Platform.OS !== 'ios') {
    Alert.alert('Error', 'Payment reference is iOS-only');
    return;
  }
  try {
    const response = await EasyMerchantSdk.paymentReference(refToken);
    console.log('Payment Reference:', response);
    Alert.alert('Payment Reference', JSON.stringify(response));
  } catch (error) {
    console.error('Payment Reference Error:', error);
    Alert.alert('Payment Reference Error', error.message || 'Unknown error');
  }
};
```

## Notes

- **Payment Methods**: Use `['card', 'ach']` (lowercase).
- **Recurring Payments**: Set `is_recurring: true` and include at least one interval in `recurringIntervals`.
- **Security**: Store `apiKey`, `secretKey`, and `clientToken` securely (e.g., in environment variables) and ensure they match the `environment`.
- **Optional Fields**: Set `fields.billing` and `fields.additional` to `[]` if not needed. Use `fields.visibility` to control display.
- **Theming**: Customize `appearanceSettings` for UI consistency. Ensure hex colors are valid.
- **GrailPay**: Configure `grailPayParams` for bank search UI (used with ACH payments).
- **Platform Differences**:
  - Android uses a direct object for `makePayment` and `makePaymentV2`; iOS requires a JSON string.
  - `makePayment` on iOS requires environment initialization; `makePaymentV2` does not.
  - Android supports event listeners; iOS uses direct method responses.
- **Client Token**: Required for `makePaymentV2`. Obtain via the payment intent API (`/api/v1/paymentintent`).