npm package: @sablepay/react-sablepay-js
Description: SablePay React SDK for React Crypto payment integration with QR codes, polling, and pre-built UI components
Author: SablePay
Latest version: 1.1.6

# @sablepay/sdk

The official SablePay JavaScript/TypeScript SDK for **React** and **Next.js** applications.

Accept crypto payments (USDC, USDT, and more) with QR codes, automatic status polling, and pre-built UI components.

---

## Features

- **Full TypeScript support** with complete type definitions
- **React hooks** — `usePaymentFlow()`, `usePayment()`, `useSablePay()`
- **Pre-built components** — `<PaymentButton>`, `<PaymentQrCode>`, `<PaymentStatus>`
- **Context Provider** — `<SablePayProvider>` for app-wide SDK access
- **QR code generation** — Built-in QR code generation (PNG, SVG, Canvas)
- **Automatic polling** — Poll payment status with exponential backoff
- **Retry logic** — Automatic retry with exponential backoff for network errors
- **Payment flow management** — End-to-end payment lifecycle handling
- **SSR compatible** — Works with Next.js (App Router & Pages Router)
- **Tree-shakeable** — Import only what you need
- **Zero config** — Auto-detects sandbox vs production from API key

---

## Installation

```bash
npm install @sablepay/sdk
# or
yarn add @sablepay/sdk
# or
pnpm add @sablepay/sdk
```

---

## Quick Start

### 1. Initialize the SDK

```tsx
// app/layout.tsx (Next.js App Router) or _app.tsx (Pages Router)
import { SablePay } from '@sablepay/sdk';

SablePay.initialize({
  apiKey: process.env. PUBLIC_SABLEPAY_API_KEY!,
  merchantId: process.env.PUBLIC_SABLEPAY_MERCHANT_ID!,
  enableLogging: true,
});
```

### 2. Use the PaymentButton (Easiest)

```tsx
import { PaymentButton } from '@sablepay/sdk/react';

export default function CheckoutPage() {
  return (
    <PaymentButton
      amount={10.50}
      onSuccess={(status) => {
        console.log('Payment complete!', status.transactionId);
      }}
      onError={(err) => {
        console.error('Payment failed:', err);
      }}
    />
  );
}
```

### 3. Or use the Hook (More Control)

```tsx
import { usePaymentFlow } from '@sablepay/sdk/react';

export default function PaymentPage() {
  const { state, startPayment, cancel, qrDataUrl, error } = usePaymentFlow({
    onPaymentCompleted(status) {
      console.log('Done!', status.transactionId);
    },
  });

  return (
    <div>
      {state.type === 'idle' && (
        <button onClick={() => startPayment(10.50)}>Pay $10.50</button>
      )}
      {state.type === 'creating' && <p>Creating payment...</p>}
      {state.type === 'awaiting' && qrDataUrl && (
        <div>
          <img src={qrDataUrl} alt="Scan to pay" width={300} height={300} />
          <p>Scan the QR code with your wallet</p>
        </div>
      )}
      {state.type === 'processing' && <p>Processing payment...</p>}
      {state.type === 'completed' && <p>✅ Payment complete!</p>}
      {state.type === 'failed' && <p>❌ {error}</p>}
      {state.type !== 'idle' && state.type !== 'completed' && (
        <button onClick={cancel}>Cancel</button>
      )}
    </div>
  );
}
```

---

## Usage with Context Provider

```tsx
// app/providers.tsx
'use client';
import { SablePayProvider } from '@sablepay/sdk/react';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <SablePayProvider
      apiKey={process.env. PUBLIC_SABLEPAY_API_KEY!}
      merchantId={process.env.PUBLIC_SABLEPAY_MERCHANT_ID!}
    >
      {children}
    </SablePayProvider>
  );
}

// In any child component
import { useSablePay } from '@sablepay/sdk/react';

function MyComponent() {
  const sdk = useSablePay();
  // sdk.createPayment(), sdk.getPaymentStatus(), etc.
}
```

---

## 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();
```

### React Hooks

#### `usePaymentFlow(options?)` — Recommended

Complete payment lifecycle management with reactive state.

```ts
const {
  state,           // PaymentFlowState
  startPayment,    // (amount, metadata?) => Promise<void>
  cancel,          // () => void
  isActive,        // boolean
  qrDataUrl,       // string | null
  paymentResponse, // CreatePaymentResponse | null
  paymentStatus,   // PaymentStatusResponse | null
  error,           // string | null
} = usePaymentFlow({
  onPaymentCreated: (response, qrUrl) => {},
  onStatusUpdate: (status) => {},
  onPaymentCompleted: (status) => {},
  onPaymentFailed: (error) => {},
  qrCodeConfig: { width: 400 },
  pollOptions: { intervalMs: 3000 },
});
```

#### `usePayment()` — Low-level

Direct API access with loading/error state.

```ts
const {
  createPayment,    // (request) => Promise<CreatePaymentResponse>
  getPaymentStatus, // (id) => Promise<PaymentStatusResponse>
  listPayments,     // (limit?, offset?) => Promise<PaymentStatusResponse[]>
  loading,          // boolean
  error,            // Error | null
  clearError,       // () => void
} = usePayment();
```

#### `useSablePay()` — SDK Instance

Access the raw SDK instance (requires `<SablePayProvider>`).

```ts
const sdk = useSablePay();
```

### React Components

#### `<PaymentButton>`

```tsx
<PaymentButton
  amount={10.50}
  label="Checkout"
  metadata={{ orderId: 'ORD-123' }}
  onSuccess={(status) => {}}
  onError={(message) => {}}
  onCancel={() => {}}
  className="my-button"
  disabled={false}
/>
```

#### `<PaymentQrCode>`

```tsx
<PaymentQrCode
  paymentResponse={response}
  // Or: paymentLink="https://pay.sablepay.io/..."
  config={{ width: 400, height: 400 }}
  onGenerated={(dataUrl) => {}}
  onError={(error) => {}}
/>
```

#### `<PaymentStatus>`

```tsx
<PaymentStatus
  paymentId="payment-id"
  autoPoll={true}
  pollInterval={3000}
  onStatusChange={(status) => {}}
  onTerminal={(status) => {}}
/>

{/* Or with custom rendering */}
<PaymentStatus paymentId="payment-id">
  {(status, loading, error) => (
    <div>{status?.status}</div>
  )}
</PaymentStatus>
```

### Models

```ts
interface CreatePaymentRequest {
  amount: number;                         // USD amount (0.01 - 1,000,000)
  items?: PaymentItem[];                  // Optional line items
  metadata?: Record<string, string>;      // Optional metadata
}

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
  txHash?: string | null;   // Blockchain TX hash
  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/sdk';

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/sdk';

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/sdk';

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 Variables

```env
 PUBLIC_SABLEPAY_API_KEY=sable_sk_sand_...
PUBLIC_SABLEPAY_MERCHANT_ID=00000000-0000-0000-0000-000000000000
```

- **Sandbox keys**: `sable_sk_sand_...` → auto-routes to sandbox API
- **Production keys**: `sable_sk_live_...` → auto-routes to production API

---

## 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 |

---

## Next.js Integration

### App Router

```tsx
// app/layout.tsx
import { SablePay } from '@sablepay/sdk';

// Initialize outside component (runs once)
if (!SablePay.isInitialized()) {
  SablePay.initialize({
    apiKey: process.env. PUBLIC_SABLEPAY_API_KEY!,
    merchantId: process.env.PUBLIC_SABLEPAY_MERCHANT_ID!,
  });
}

export default function RootLayout({ children }) {
  return <html><body>{children}</body></html>;
}
```

### Pages Router

```tsx
// pages/_app.tsx
import { SablePay } from '@sablepay/sdk';

if (!SablePay.isInitialized()) {
  SablePay.initialize({
    apiKey: process.env. PUBLIC_SABLEPAY_API_KEY!,
    merchantId: process.env.PUBLIC_SABLEPAY_MERCHANT_ID!,
  });
}

export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}
```

---

## Example App

A full working **Next.js** example app demonstrating all SDK features (PaymentButton, QR codes, hooks, provider pattern, and payment status pages) is available in a separate repository:

👉 **[sablepay-react-sdk-example](https://github.com/Sable-Payments-Inc/sablepay-react-sdk-example)**

```bash
git clone https://github.com/Sable-Payments-Inc/sablepay-react-sdk-example.git
cd sablepay-react-sdk-example
npm install
npm run dev
```

See the example app's [README](https://github.com/Sable-Payments-Inc/sablepay-react-sdk-example#readme) for setup instructions and environment variable configuration.

---

## Building

```bash
npm run build       # Build CJS + ESM + type declarations
npm test            # Run tests
npm run typecheck   # Type checking only
```

---

## License

MIT — see [LICENSE](./LICENSE)