npm package: @netappsng/react-native-pay
Description: netapps payment sdk
Author: lawrence
Latest version: 0.3.0

# @netappsng/react-native-pay

NetAppsPay React Native SDK — accept card, bank transfer, USSD, PayAttitude and Tap to Pay (NFC) payments in your React Native app. Supports **iOS** and **Android**.

## Installation

```sh
npm install @netappsng/react-native-pay
# or
yarn add @netappsng/react-native-pay
```

### iOS

```sh
cd ios && pod install
```

The SDK uses the native [NetAppsPaySDK](https://cocoapods.org/pods/NetAppsPaySDK) which is resolved automatically via CocoaPods.

### Android

The Android SDK is pulled from Maven Central automatically — no extra setup needed.

Make sure your project's `android/build.gradle` includes `mavenCentral()` in repositories (this is the default for new React Native projects).

## Usage

```tsx
import { presentPayment } from '@netappsng/react-native-pay';
import type { PaymentConfig } from '@netappsng/react-native-pay';

function handlePayment() {
  const amountNaira = 100;
  const amountKobo = Math.round(amountNaira * 100);

  const config: PaymentConfig = {
    publicKey: 'pk_live_xxxxxxxxxxxxxxxxxxxx',
    amount: amountKobo, // ₦100.00 = 10000 kobo
    currency: 'NGN',
    email: 'john@example.com',
    fullName: 'John Doe',
    phoneNumber: '08106720418',
    narration: 'Dev Test Payment',
    paymentChannels: ['card', 'transfer', 'ussd', 'payattitude', 'moniflow'],
    defaultChannel: 'card',
    address1: 'Customer Address',
    metadata: { inputAmount: amountNaira, env: 'development' },
    businessName: 'Demo Store',
    showTransactionSummary: true,
  };

  const cleanup = presentPayment(config, {
    onSuccess: (payload) => {
      console.log('Payment successful!', payload.transactionRef);
      // payload: { status, merchantRef, transactionRef, amount, currency, channel, message, timestamp }
    },
    onFailed: (payload) => {
      console.log('Payment failed:', payload.message);
      // payload: { status, amount, currency, message, timestamp, errorCode? }
    },
    onCancel: () => {
      console.log('Payment cancelled');
    },
    onReady: () => {
      console.log('Payment UI ready');
    },
  });

  // Call cleanup() to remove event listeners when unmounting
  // e.g. in useEffect: return cleanup;
}
```

### With React Hook

```tsx
import React, { useCallback, useRef } from 'react';
import { Button } from 'react-native';
import { presentPayment } from '@netappsng/react-native-pay';
import type { PaymentConfig } from '@netappsng/react-native-pay';

export default function CheckoutScreen() {
  const cleanupRef = useRef<(() => void) | null>(null);

  const pay = useCallback(() => {
    cleanupRef.current?.();

    const config: PaymentConfig = {
      publicKey: 'pk_live_xxxxxxxxxxxxxxxxxxxx',
      amount: 10000, // ₦100.00
      currency: 'NGN',
      email: 'john@example.com',
      fullName: 'John Doe',
      phoneNumber: '08106720418',
      narration: 'Dev Test Payment',
      paymentChannels: ['card', 'transfer', 'ussd', 'payattitude', 'moniflow'],
      defaultChannel: 'card',
      address1: 'Customer Address',
      metadata: { env: 'development' },
      businessName: 'Demo Store',
      showTransactionSummary: true,
    };

    cleanupRef.current = presentPayment(config, {
      onSuccess: (payload) => {
        console.log('Ref:', payload.transactionRef);
        cleanupRef.current?.();
      },
      onFailed: (payload) => {
        alert(payload.message);
        cleanupRef.current?.();
      },
      onCancel: () => {
        console.log('Cancelled');
        cleanupRef.current?.();
      },
    });
  }, []);

  return <Button title="Pay ₦100" onPress={pay} />;
}
```

## Configuration

### Required Fields

| Field | Type | Description |
|-------|------|-------------|
| `publicKey` | `string` | Your NetAppsPay public key |
| `amount` | `number` | Amount in minor units (kobo/cents). ₦15.00 = `1500` |
| `currency` | `'NGN' \| 'USD'` | Payment currency |
| `email` | `string` | Customer email |
| `fullName` | `string` | Customer full name |
| `narration` | `string` | Payment description |
| `paymentChannels` | `PaymentChannel[]` | Channels to display |
| `defaultChannel` | `PaymentChannel` | Initially selected channel |

### Optional Fields

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `phoneNumber` | `string` | — | Customer phone number |
| `transactionRef` | `string` | auto-generated | Custom transaction reference |
| `address1` | `string` | `'Customer Address'` | Customer address line 1 |
| `address2` | `string` | — | Customer address line 2 |
| `city` | `string` | `'Lagos'` | Customer city |
| `postalCode` | `string` | — | Customer postal code |
| `locality` | `string` | — | Country code (e.g. `'NG'`) |
| `administrativeArea` | `string` | — | State/province (e.g. `'Lagos'`) |
| `chargeAllocation` | `'CUSTOMER' \| 'MERCHANT'` | `'CUSTOMER'` | Who bears the transaction charge |
| `businessName` | `string` | — | Merchant display name |
| `logoURL` | `string` | — | Merchant logo URL |
| `productName` | `string` | — | Product name |
| `metadata` | `Record<string, any>` | — | Custom key-value metadata |
| `cardHolderName` | `string` | — | Pre-filled cardholder name |
| `customerId` | `string` | — | Your internal customer ID |
| `enableSaveCard` | `boolean` | `false` | Allow saving cards for future payments |
| `showTransactionSummary` | `boolean` | `true` | Show transaction summary |

### Payment Channels

| Channel | Description |
|---------|-------------|
| `'card'` | Card payment (Visa, Mastercard, Verve) |
| `'transfer'` | Bank transfer |
| `'ussd'` | USSD payment |
| `'payattitude'` | PayAttitude (USSD push) |
| `'moniflow'` | MoniFlow marketplace |
| `'tap_to_pay'` | NFC Tap to Pay (Android only — requires setup, see below) |

> **Note:** `tap_to_pay` is only available on Android. On iOS, this channel is silently ignored.

### Callbacks

| Callback | Payload | Description |
|----------|---------|-------------|
| `onSuccess` | `PaymentSuccessPayload` | Payment completed successfully |
| `onFailed` | `PaymentFailedPayload` | Payment failed |
| `onCancel` | — | User dismissed the payment sheet |
| `onReady` | — | Payment sheet is fully loaded |

## Tap to Pay (NFC) — Android Only

Tap to Pay lets customers pay by tapping their contactless card on the Android device. It requires an additional native `taptopay` module that is **not** included by default. Your app will compile and work normally without it — devices without NFC are unaffected.

> **Not supported on iOS.** If you include `'tap_to_pay'` in `paymentChannels` on iOS, it is silently ignored.

### Step 1 — Set minSdk to 28

The NFC contactless SDK requires Android API 28+. Open your React Native project's Android build file and set `minSdkVersion`:

```groovy
// android/app/build.gradle

android {
    defaultConfig {
        minSdkVersion 28  // Required for NFC contactless SDK
    }
}
```

### Step 2 — Add the taptopay module dependency

In your React Native project's **Android** build files, add the `taptopay` module alongside the payment SDK:

```groovy
// android/app/build.gradle

dependencies {
    // The core payment SDK (already added by the RN package)
    implementation "ng.netapps:netappspay:1.1.0"

    // Tap to Pay NFC plugin — add this line
    implementation "ng.netapps:taptopay:1.0.0"
}
```

Then sync your Gradle project (Android Studio will prompt you, or run `cd android && ./gradlew sync`).

### Step 3 — Fix the manifest merger conflict

The POS SDK includes its own theme which conflicts with your app's theme. Add `tools:replace="android:theme"` to your `<application>` tag:

```xml
<!-- android/app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:theme="@style/AppTheme"
        tools:replace="android:theme">
        <!-- your activities -->
    </application>
</manifest>
```

> If your manifest already has an `<application>` tag with a theme, just add `xmlns:tools="http://schemas.android.com/tools"` to the `<manifest>` tag and `tools:replace="android:theme"` to the `<application>` tag.

### Step 4 — Register the NFC provider in your Android app

Open your `MainApplication.kt` (or `MainApplication.java`) file — this is usually at:

```
android/app/src/main/java/com/yourapp/MainApplication.kt
```

Add the import and registration call inside `onCreate`:

```kotlin
// MainApplication.kt

import com.netappspay.sdk.taptopay.TapToPayRegistry
import com.netappspay.taptopay.NfcTapToPayProvider

class MainApplication : Application(), ReactApplication {

    override fun onCreate() {
        super.onCreate()

        // Register NFC Tap to Pay provider — do this once on app start
        TapToPayRegistry.register(NfcTapToPayProvider())

        // ... rest of your onCreate
    }
}
```

**Java version:**

```java
// MainApplication.java

import com.netappspay.sdk.taptopay.TapToPayRegistry;
import com.netappspay.taptopay.NfcTapToPayProvider;

public class MainApplication extends Application implements ReactApplication {

    @Override
    public void onCreate() {
        super.onCreate();

        // Register NFC Tap to Pay provider
        TapToPayRegistry.INSTANCE.register(new NfcTapToPayProvider());

        // ... rest of your onCreate
    }
}
```

That's all the native code you need. The `taptopay` module's `AndroidManifest.xml` automatically adds the NFC permission with `android:required="false"`, so your app still installs on devices without NFC.

### Step 5 — Include `'tap_to_pay'` in your payment channels

Now in your React Native / TypeScript code, add `'tap_to_pay'` to the `paymentChannels` array:

```tsx
import { presentPayment } from '@netappsng/react-native-pay';
import type { PaymentConfig } from '@netappsng/react-native-pay';

const config: PaymentConfig = {
  publicKey: 'pk_live_xxxxxxxxxxxxxxxxxxxx',
  amount: 10000,
  currency: 'NGN',
  email: 'john@example.com',
  fullName: 'John Doe',
  phoneNumber: '08106720418',
  narration: 'POS Payment',
  paymentChannels: ['card', 'transfer', 'tap_to_pay'],
  defaultChannel: 'card',
  businessName: 'Demo Store',
};

presentPayment(config, {
  onSuccess: (payload) => {
    console.log('Tap to Pay successful!', payload.transactionRef);
  },
  onFailed: (payload) => {
    console.log('Payment failed:', payload.message);
  },
  onCancel: () => {
    console.log('Cancelled');
  },
});
```

### How it works

1. When the payment sheet opens, the SDK checks if a Tap to Pay provider is registered via `TapToPayRegistry`
2. If registered **and** the device has NFC hardware enabled, the "Tap to Pay" tab appears
3. If no provider is registered or the device lacks NFC, the tab is automatically hidden — no errors, no crashes
4. The user selects their **Account Type** and **Device Type** using the chip controls on the Tap to Pay screen
5. The customer taps their contactless card on the device to complete payment

### Account Type

The Tap to Pay screen presents a segmented chip control for account type selection:

| Chip | Value |
|------|-------|
| **Default** | `DEFAULT_UNSPECIFIED` — the issuer's default account |
| **Savings** | `SAVINGS` |
| **Current** | `CURRENT` |
| **Credit** | `CREDIT` |

### Device Type

A second chip control lets the user choose how the NFC card is read:

| Chip | Description |
|------|-------------|
| **Internal NFC** | Uses the device's built-in NFC antenna (most common) |
| **External** | Uses an external NFC reader connected to the device |
| **NFC Kit** | Uses a third-party NFC kit module |

The selected device type is **persisted automatically** — the SDK remembers the user's last choice across sessions so they don't have to re-select each time.

### Requirements

| Requirement | Value |
|---|---|
| Min Android SDK | **28** (Android 9.0) |
| NFC hardware | Required on the device (for Internal NFC) |
| `taptopay` module | Must be added as a dependency |
| Manifest fix | `tools:replace="android:theme"` on `<application>` |
| Provider registration | Must call `TapToPayRegistry.register()` before payment |

### Unregister (optional)

If you need to disable Tap to Pay at runtime:

```kotlin
TapToPayRegistry.unregister()
```

## Native SDKs

This package bridges the native NetAppsPay SDKs:

- **iOS**: [NetAppsPaySDK](https://github.com/nicenapp/NetAppsPaySDK) (Swift Package)
- **Android**: `ng.netapps:netappspay:1.1.0` (Maven Central)

## License

MIT