npm package: @sablepay/react-native-sablepay-js
Description: SablePay React Native SDK for crypto payment integration with QR codes, polling, hooks, and pre-built payment flow management
Author: SablePay
Latest version: 1.0.3

﻿# @sablepay/react-native-sablepay-js

The official SablePay React Native SDK for **React Native** applications.

Accept crypto payments (USDC, USDT, and more) with QR codes, automatic status polling, and pre-built React hooks.

---

## Features

- **Full TypeScript support** with complete type definitions
- **React Native Hooks** — `useSablePay`, `usePaymentFlow`
- **QR code generation** — Built-in QR code generation (PNG, SVG)
- **Automatic polling** — Poll payment status with configurable intervals
- **Retry logic** — Automatic retry with exponential backoff for network errors
- **Payment flow management** — End-to-end payment lifecycle handling
- **Compatible with React Native 0.70+** and Expo
- **Tree-shakeable** — Import only what you need
- **Zero config** — Auto-detects sandbox vs production from API key
- **NPM deployable** — Ready to publish to npm

---

## Installation

```bash
npm install @sablepay/react-native-sablepay-js
# or
yarn add @sablepay/react-native-sablepay-js
# or
pnpm add @sablepay/react-native-sablepay-js
```

---

## Quick Start

### 1. Initialize the SDK

```tsx
// In your App.tsx or root component
import { SablePay } from '@sablepay/react-native-sablepay-js';

// Initialize once on app startup
SablePay.initialize({
  apiKey: 'sable_sk_sand_...',
  merchantId: '00000000-0000-0000-0000-000000000000',
  enableLogging: __DEV__,
});
```

### 2. Create a Payment (Simplest)

```tsx
import React, { useState } from 'react';
import { View, Button, Image, Text } from 'react-native';
import { SablePay, QrCodeGenerator } from '@sablepay/react-native-sablepay-js';

function CheckoutScreen() {
  const [qrUrl, setQrUrl] = useState('');
  const [loading, setLoading] = useState(false);

  const pay = async () => {
    setLoading(true);
    const sdk = SablePay.getInstance();
    const response = await sdk.createPayment({ amount: 10.50 });

    const qrGen = new QrCodeGenerator();
    const dataUrl = await qrGen.generatePaymentQr(response);
    setQrUrl(dataUrl || '');
    setLoading(false);
  };

  return (
    <View>
      <Button title="Pay $10.50" onPress={pay} disabled={loading} />
      {qrUrl ? <Image source={{ uri: qrUrl }} style={{ width: 300, height: 300 }} /> : null}
    </View>
  );
}
```

### 3. Use the usePaymentFlow Hook (Recommended)

```tsx
import React, { useEffect } from 'react';
import { View, Button, Image, Text } from 'react-native';
import { usePaymentFlow } from '@sablepay/react-native-sablepay-js';

function PaymentScreen() {
  const {
    state,
    qrDataUrl,
    paymentStatus,
    error,
    startPayment,
    cancel,
    release,
  } = usePaymentFlow();

  useEffect(() => {
    return () => release(); // Clean up on unmount
  }, []);

  const handlePay = async () => {
    await startPayment(10.50, undefined, {
      onPaymentCompleted: (status) => {
        console.log('Done!', status.transactionId);
      },
      onPaymentFailed: (err) => {
        console.error('Failed:', err.message);
      },
    });
  };

  return (
    <View>
      {state.type === 'idle' && <Button title="Pay $10.50" onPress={handlePay} />}
      {state.type === 'creating' && <Text>Creating payment...</Text>}
      {state.type === 'awaiting' && qrDataUrl && (
        <>
          <Image source={{ uri: qrDataUrl }} style={{ width: 300, height: 300 }} />
          <Text>Scan the QR code with your wallet</Text>
          <Button title="Cancel" onPress={cancel} />
        </>
      )}
      {state.type === 'processing' && <Text>Processing payment...</Text>}
      {state.type === 'completed' && <Text>Payment complete!</Text>}
      {state.type === 'failed' && <Text>Error: {error}</Text>}
    </View>
  );
}
```

---

## Using useSablePay Hook

The `useSablePay` hook wraps the core `SablePay` singleton for convenient use in components:

```tsx
import React from 'react';
import { View, Button, Text } from 'react-native';
import { useSablePay } from '@sablepay/react-native-sablepay-js';

function PaymentComponent() {
  const { createPayment, getPaymentStatus, getEnvironment } = useSablePay();

  const handlePay = async () => {
    const response = await createPayment({ amount: 25.00 });
    console.log('Payment ID:', response.paymentId);
    console.log('Payment Link:', response.paymentLink);
  };

  return (
    <View>
      <Text>Environment: {getEnvironment()}</Text>
      <Button title="Pay $25.00" onPress={handlePay} />
    </View>
  );
}
```

---

## API Reference

### SablePay (Core)

```ts
// Initialize (call once)
SablePay.initialize({
  apiKey: 'sable_sk_sand_...',     // Required
  merchantId: 'uuid-here',         // Required
  baseUrl: '...',                   // Optional (auto-detected)
  enableLogging: true,              // Optional (default: false)
});

// Get instance
const sdk = SablePay.getInstance();

// Create a payment
const response = await sdk.createPayment({
  amount: 10.50,
  metadata: { orderId: 'ORD-123' },
});
// response.paymentId, response.paymentLink, response.status, etc.

// Get payment status
const status = await sdk.getPaymentStatus('payment-id');
// status.status, status.transactionId, status.paidToken, etc.

// List payments
const payments = await sdk.listPayments(20, 0);

// Check environment
sdk.getEnvironment(); // 'sandbox' | 'production'

// Check if configured
sdk.isConfigured(); // boolean

// Create a payment flow
const flow = SablePay.createPaymentFlow();

// Release resources
SablePay.release();
```

### useSablePay Hook

```ts
const {
  sdk,                  // SablePay instance
  createPayment,        // (request) => Promise<CreatePaymentResponse>
  getPaymentStatus,     // (paymentId) => Promise<PaymentStatusResponse>
  listPayments,         // (limit?, offset?) => Promise<PaymentStatusResponse[]>
  getEnvironment,       // () => string | null
  isConfigured,         // () => boolean
  createPaymentFlow,    // (options?) => PaymentFlow
} = useSablePay();
```

### usePaymentFlow Hook

Complete payment lifecycle management with automatic state tracking.

```ts
const {
  state,               // PaymentFlowState
  isActive,            // boolean
  qrDataUrl,           // string | null
  paymentResponse,     // CreatePaymentResponse | null
  paymentStatus,       // PaymentStatusResponse | null
  error,               // string | null
  startPayment,        // (amount, metadata?, callbacks?, options?) => Promise<void>
  cancel,              // () => void
  release,             // () => void — call in useEffect cleanup
} = usePaymentFlow();
```

**Callbacks:**

```ts
await startPayment(10.50, undefined, {
  onPaymentCreated: (response, qrUrl) => { },
  onStatusUpdate: (status) => { },
  onPaymentCompleted: (status) => { },
  onPaymentFailed: (error) => { },
  onStateChange: (state) => { },
});
```

### Models

```ts
interface CreatePaymentRequest {
  amount: number;                         // USD amount (0.01 - 1,000,000)
  items?: PaymentItem[];                  // Optional line items
  metadata?: Record<string, string>;      // Optional metadata
  expiresIn?: number;                     // Expiration in seconds (default: 600)
}

interface CreatePaymentResponse {
  paymentId: string;
  status: string;
  amount: number;
  acceptedTokens: string;
  paymentLink: string | null;
  businessName: string;
  expiresAt: string | null;
  createdAt: string;
}

interface PaymentStatusResponse {
  paymentId: string;
  amount: number;
  status: string;             // PENDING | COMPLETED | FAILED | EXPIRED
  transactionId?: string | null;
  paidToken?: string | null;
  paidNetwork?: string | null;
  paidAmount?: number | null;
  metadata?: Record<string, unknown> | null;
  createdAt: string;
  completedAt?: string | null;
  expiresAt?: string | null;
}
```

### Error Handling

```ts
import { ApiException } from '@sablepay/react-native-sablepay-js';

try {
  await sdk.createPayment({ amount: 10.50 });
} catch (error) {
  if (error instanceof ApiException) {
    console.log(error.statusCode);    // 400, 401, 429, 500, etc.
    console.log(error.message);       // Error message
    console.log(error.requestId);     // For debugging
    console.log(error.isRetryable);   // true for 5xx and 429
    console.log(error.isAuthError);   // true for 401
    console.log(error.retryAfter);    // Seconds to wait (for 429)
  }
}
```

### QR Code Generator (Standalone)

```ts
import { QrCodeGenerator } from '@sablepay/react-native-sablepay-js';

const generator = new QrCodeGenerator({ width: 400, height: 400 });

// From payment response
const dataUrl = await generator.generatePaymentQr(response);

// From any text
const qr = await generator.generate('https://example.com');

// As SVG
const svg = await generator.generateSvg('https://example.com');
```

### Payment Poller (Standalone)

```ts
import { PaymentPoller } from '@sablepay/react-native-sablepay-js';

const poller = new PaymentPoller((id) => sdk.getPaymentStatus(id));

// Callback-based
poller.startPolling('payment-id', (result) => {
  if (result.success) console.log(result.data.status);
});
poller.stopPolling();

// Promise-based (awaits terminal state)
const finalStatus = await poller.pollUntilTerminal('payment-id');
```

---

## Environment Setup

```ts
// config/environment.ts
export const environment = {
  production: false,
  sablepayApiKey: 'sable_sk_sand_...',
  sablepayMerchantId: '00000000-0000-0000-0000-000000000000',
  sablepayBaseUrl: 'https://sandbox-api.sablepay.io/api/v1/',
};
```

- **Sandbox keys**: `sable_sk_sand_...` → auto-routes to `https://sandbox-api.sablepay.io`
- **Production keys**: `sable_sk_live_...` → auto-routes to `https://api.sablepay.io`
- **Sandbox web URL**: `https://sandbox.sablepay.io`
- **Live web URL**: `https://www.sablepay.io/`

---

## Payment Flow States

| State | Description |
|-------|-------------|
| `idle` | No payment in progress |
| `creating` | Payment being created via API |
| `awaiting` | QR code displayed, waiting for customer |
| `processing` | Customer scanned, processing on blockchain |
| `completed` | Payment successful |
| `failed` | Payment failed, expired, or error |

---

## React Native Integration Examples

### Functional Component with Hooks

```tsx
import React, { useEffect } from 'react';
import { View, Button, Image, Text, ActivityIndicator } from 'react-native';
import { usePaymentFlow } from '@sablepay/react-native-sablepay-js';

export function PaymentScreen() {
  const { state, qrDataUrl, error, startPayment, cancel, release } = usePaymentFlow();

  useEffect(() => () => release(), []);

  return (
    <View style={{ flex: 1, padding: 20 }}>
      {state.type === 'idle' && (
        <Button title="Pay $10.50" onPress={() => startPayment(10.50)} />
      )}

      {state.type === 'creating' && (
        <ActivityIndicator size="large" />
      )}

      {state.type === 'awaiting' && qrDataUrl && (
        <View style={{ alignItems: 'center' }}>
          <Image source={{ uri: qrDataUrl }} style={{ width: 300, height: 300 }} />
          <Text>Scan to pay</Text>
          <Button title="Cancel" onPress={cancel} />
        </View>
      )}

      {state.type === 'completed' && <Text>Payment complete!</Text>}
      {state.type === 'failed' && <Text style={{ color: 'red' }}>Error: {error}</Text>}
    </View>
  );
}
```

### Class Component (Direct SDK)

```tsx
import React, { Component } from 'react';
import { View, Button, Image, Text } from 'react-native';
import {
  SablePay,
  QrCodeGenerator,
  isCompleted,
  isExpired,
} from '@sablepay/react-native-sablepay-js';

export class PaymentComponent extends Component {
  state = { qrUrl: '', status: '', paymentId: '' };

  pay = async () => {
    const sdk = SablePay.getInstance();
    const response = await sdk.createPayment({ amount: 10.50 });
    this.setState({ paymentId: response.paymentId });

    const qrGen = new QrCodeGenerator();
    const dataUrl = await qrGen.generatePaymentQr(response);
    this.setState({ qrUrl: dataUrl || '' });

    this.pollStatus(response.paymentId);
  };

  pollStatus = async (paymentId) => {
    const sdk = SablePay.getInstance();
    const statusResp = await sdk.getPaymentStatus(paymentId);
    this.setState({ status: statusResp.status });

    if (!isCompleted(statusResp.status) && !isExpired(statusResp.status)) {
      setTimeout(() => this.pollStatus(paymentId), 3000);
    }
  };

  render() {
    return (
      <View>
        <Button title="Pay" onPress={this.pay} />
        {this.state.qrUrl ? (
          <Image source={{ uri: this.state.qrUrl }} style={{ width: 300, height: 300 }} />
        ) : null}
        <Text>Status: {this.state.status}</Text>
      </View>
    );
  }
}
```

### Expo App Initialization

```tsx
import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native';
import { SablePay } from '@sablepay/react-native-sablepay-js';

export default function App() {
  const [ready, setReady] = useState(false);

  useEffect(() => {
    SablePay.initialize({
      apiKey: 'sable_sk_sand_...',
      merchantId: '00000000-0000-0000-0000-000000000000',
      enableLogging: __DEV__,
    });
    setReady(true);

    return () => SablePay.release();
  }, []);

  if (!ready) return <Text>Loading...</Text>;

  return (
    <View>
      {/* Your payment screens here */}
    </View>
  );
}
```

---

## Example App

A full working React Native (Expo) example app is available at [https://github.com/Sable-Payments-Inc/sablepay-react-native-sdk-example](https://github.com/Sable-Payments-Inc/sablepay-react-native-sdk-example). It features a Coffee Shop POS and Payment Status Lookup — matching the Angular SDK example.

```bash
# 1. Clone the example app repository
git clone https://github.com/Sable-Payments-Inc/sablepay-react-native-sdk-example
cd sablepay-react-native-sdk-example

# 2. Update credentials in src/config/environment.js

# 3. Install dependencies
npm install

# 4. Run the app
npx expo start          # Start the Expo dev server (scan QR code with Expo Go)

# Or run on a specific platform:
npm run android          # expo run:android
npm run ios              # expo run:ios
npm run web              # expo start --web
```

---

## Building the SDK

```bash
npm install
npm run build       # Build CJS + ESM + type declarations
npm run typecheck   # Type checking only
```

---

## Publishing to npm

```bash
# Build the SDK
npm run build

# Login to npm
npm login

# Publish (scoped package — needs public access)
npm publish --access public
```

---

## License

MIT — see [LICENSE](./LICENSE)