npm package: @one-payments/react-native
Description: React Native wrapper for One Payments web component
Author: One Payments
Latest version: 1.9.3

# @one-payments/react-native

React Native wrapper for One Payments web component using WebView.

## Features

- ✅ **Cross-platform** - Works on iOS and Android
- ✅ **Type-safe** - Full TypeScript support
- ✅ **Easy integration** - Drop-in component with simple props
- ✅ **Event-driven** - Callbacks for payment success, errors, and state changes
- ✅ **Secure** - Props validation and XSS protection
- ✅ **Customizable** - Custom loading and error components

## Installation

```bash
npm install @one-payments/react-native react-native-webview
# or
yarn add @one-payments/react-native react-native-webview
# or
pnpm add @one-payments/react-native react-native-webview
```

### iOS Setup

After installing, run pod install:

```bash
cd ios && pod install
```

## Usage

### Basic Example

```tsx
import React from 'react';
import { SafeAreaView } from 'react-native';
import { OnePaymentView } from '@one-payments/react-native';
import { PaymentConfig } from '@one-payments/core';

export default function CheckoutScreen() {
  // Create config using PaymentConfig class (recommended for type safety and validation)
  const config = new PaymentConfig({
    apiKey: 'your-api-key',
    secretKey: 'your-secret-key',
    environment: 'demo'
  });

  const handleSuccess = (data) => {
    console.log('Payment succeeded!', data.paymentIntentId);
    // Navigate to success screen
  };

  const handleError = (error) => {
    console.error('Payment failed:', error.message);
    // Show error message
  };

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <OnePaymentView
        config={config}
        amount={5000}
        currency="SGD"
        orderId={`order-${Date.now()}`}
        firstName="John"
        lastName="Doe"
        email="john@example.com"
        onPaymentSuccess={handleSuccess}
        onPaymentError={handleError}
      />
    </SafeAreaView>
  );
}
```

> **Note**: You can also pass a plain config object instead of using `PaymentConfig`:
> ```tsx
> config={{
>   apiKey: 'your-api-key',
>   secretKey: 'your-secret-key',
>   environment: 'demo'
> }}
> ```
> However, `PaymentConfig` is recommended as it provides validation and better TypeScript support.

### With Navigation

```tsx
import React from 'react';
import { SafeAreaView, Alert } from 'react-native';
import { OnePaymentView } from '@one-payments/react-native';
import type { PaymentSuccessData, PaymentErrorData } from '@one-payments/react-native';

function CheckoutScreen({ navigation }) {
  const handleSuccess = (data: PaymentSuccessData) => {
    navigation.navigate('Success', {
      paymentId: data.paymentIntentId,
      amount: data.amount,
    });
  };

  const handleError = (error: PaymentErrorData) => {
    Alert.alert('Payment Failed', error.message);
  };

  const handleStateChange = (state) => {
    console.log('Payment state:', state.status);
  };

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <OnePaymentView
        config={{
          apiKey: process.env.ONE_PAYMENTS_API_KEY,
          secretKey: process.env.ONE_PAYMENTS_SECRET_KEY,
          environment: 'prod'
        }}
        amount={5000}
        currency="SGD"
        orderId={`order-${Date.now()}`}
        firstName="John"
        lastName="Doe"
        email="john@example.com"
        onPaymentSuccess={handleSuccess}
        onPaymentError={handleError}
        onStateChange={handleStateChange}
      />
    </SafeAreaView>
  );
}
```

### Custom Loading Component

```tsx
import React from 'react';
import { View, ActivityIndicator, Text, StyleSheet } from 'react-native';
import { OnePaymentView } from '@one-payments/react-native';

function CheckoutScreen() {
  const renderLoading = () => (
    <View style={styles.loadingContainer}>
      <ActivityIndicator size="large" color="#007AFF" />
      <Text style={styles.loadingText}>Preparing payment...</Text>
    </View>
  );

  return (
    <OnePaymentView
      config={/* ... */}
      amount={5000}
      currency="SGD"
      orderId="order-123"
      firstName="John"
      lastName="Doe"
      email="john@example.com"
      renderLoading={renderLoading}
      onPaymentSuccess={(data) => console.log('Success:', data)}
      onPaymentError={(error) => console.error('Error:', error)}
    />
  );
}

const styles = StyleSheet.create({
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f5f5f5',
  },
  loadingText: {
    marginTop: 16,
    fontSize: 16,
    color: '#333',
  },
});
```

## API Reference

### OnePaymentView Props

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `config` | `SDKConfig \| PaymentConfig` | Yes | SDK configuration - use `new PaymentConfig({...})` (recommended) or plain object |
| `amount` | `number` | Yes | Payment amount in smallest currency unit (e.g., cents for USD, SGD) |
| `currency` | `string` | Yes | ISO 4217 currency code (e.g., "USD", "EUR", "SGD") |
| `orderId` | `string` | Yes | Unique order identifier from your system |
| `firstName` | `string` | **Yes** | Customer's first name (required for all payments) |
| `lastName` | `string` | **Yes** | Customer's last name (required for all payments) |
| `email` | `string` | **Yes** | Customer's email address (required for all payments) |
| `onPaymentSuccess` | `(data: PaymentSuccessData) => void` | No | Callback fired when payment succeeds |
| `onPaymentError` | `(error: PaymentErrorData) => void` | No | Callback fired when payment fails |
| `onStateChange` | `(state: PaymentStateData) => void` | No | Callback fired when payment state changes |
| `style` | `ViewStyle` | No | Additional styles for container view |
| `showLoadingIndicator` | `boolean` | No | Whether to show loading indicator (default: true) |
| `renderLoading` | `() => ReactElement` | No | Custom loading component |
| `renderError` | `(error: Error) => ReactElement` | No | Custom error component |

### SDKConfig

```typescript
interface SDKConfig {
  apiKey: string;
  secretKey: string;
  environment: 'dev' | 'staging' | 'prod';
}
```

### PaymentSuccessData

```typescript
interface PaymentSuccessData {
  paymentIntentId: string;
  amount: number;
  currency: string;
  status: 'succeeded';
  metadata?: Record<string, unknown>;
}
```

### PaymentErrorData

```typescript
interface PaymentErrorData {
  code: string;
  message: string;
  details?: Record<string, unknown>;
  timestamp?: number;
}
```

### PaymentStateData

```typescript
interface PaymentStateData {
  status: 'idle' | 'initializing' | 'ready' | 'processing' | 'requires_action' | 'succeeded' | 'failed';
  [key: string]: unknown;
}
```

## Architecture

### WebView + Hybrid Adapters

This package uses a WebView to render the One Payments web component inside your React Native app. To ensure compatibility with React Native WebView environments (which don't support the Web Crypto API `crypto.subtle`), we use a **hybrid adapter approach**:

```
WebView Environment:
├── Crypto: @one-payments/adapters-crypto-js (crypto-js library)
├── Storage: @one-payments/adapters-web (localStorage)
├── HTTP: @one-payments/adapters-web (fetch API)
└── Timer: @one-payments/adapters-web (setTimeout/setInterval)
```

### Why Crypto-JS?

React Native WebView doesn't provide `crypto.subtle`, which is required for cryptographic operations like HMAC signing. To solve this, we use:

- **@one-payments/adapters-crypto-js** - A pure JavaScript implementation using the `crypto-js` library
- This adapter is loaded via unpkg CDN and bundled with crypto-js (no external dependencies)
- Provides identical cryptographic operations without requiring native crypto APIs

### Version History

- **v1.3.0** (Current) - Fixed crypto.subtle error with dedicated crypto-js adapter
- **v1.2.0** - Attempted fix using @one-payments/adapters-native (failed due to React Native dependencies)
- **v1.1.2** - Fixed JSON over-escaping in payment props serialization
- **v1.1.1** - Fixed ES module loading from unpkg CDN
- **v1.0.x** - Initial release

## Supported Currencies

- USD - US Dollar
- EUR - Euro
- GBP - British Pound
- SGD - Singapore Dollar

## Requirements

- React Native >= 0.70.0
- React >= 18.0.0
- react-native-webview >= 13.0.0

## Troubleshooting

### WebView not loading

- Ensure `react-native-webview` is installed
- On iOS, run `pod install` after installing
- Check console for errors

### Events not firing

- Verify callback props are provided (e.g., `onPaymentSuccess`)
- Check for JavaScript errors in the WebView
- Ensure WebView has JavaScript enabled (enabled by default)

### Build errors

- Check Node.js version (>= 18)
- Clear node_modules and reinstall
- Verify all peer dependencies are installed

### Type errors

- Ensure TypeScript version >= 5.0
- Restart TypeScript server in your IDE
- Check that `@one-payments/core` is installed

## License

MIT