<div align="center">

<img src="logo.png" alt="PayLayer Logo" width="200" />

# 💳 @paylayer/react

**Build billing once. Switch providers anytime.**

[![npm version](https://img.shields.io/npm/v/@paylayer/react.svg?style=flat-square)](https://www.npmjs.com/package/@paylayer/react)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
[![React Version](https://img.shields.io/badge/react-%3E%3D18.0.0-blue.svg?style=flat-square)](https://reactjs.org/)

The official React SDK for PayLayer. Simple, type-safe hooks for integrating payments into your React application.

[Features](#-features) • [Quick Start](#-quick-start) • [Hooks](#-hooks) • [API Reference](#-api-reference) • [Configuration](#-configuration)

</div>

---

## What is PayLayer React?

PayLayer React provides React hooks and components that make it easy to integrate payments into your React application. Write your billing logic once, switch providers anytime.

**Key Benefits:**

- **Simple Hooks** - `useCharge`, `useSubscription`, `useBillingPortal` for all payment operations
- **Provider Flexibility** - Switch between Stripe, Paddle, PayPal, Lemon Squeezy, and Polar without code changes
- **Type Safety** - Full TypeScript support with autocomplete
- **SSR Ready** - Works with Next.js, Remix, and other React frameworks

> **💡 Looking for Node.js/Backend support?** Check out [@paylayer/core](https://www.npmjs.com/package/@paylayer/core) - the server-side SDK for PayLayer that powers this React SDK.

---

## 📋 Table of Contents

- [Features](#-features)
- [Installation](#-installation)
- [Quick Start](#-quick-start)
- [Configuration](#-configuration)
- [Hooks](#-hooks)
- [API Reference](#-api-reference)
- [TypeScript Support](#-typescript-support)
- [Provider Abstraction](#-provider-abstraction)
- [Security](#-security)
- [Troubleshooting](#-troubleshooting)
- [Examples](#-examples)
- [License](#-license)

---

## ✨ Features

| Feature                  | Description                                             |
| ------------------------ | ------------------------------------------------------- |
| 💰 **One-time payments** | `useCharge` hook for simple payment processing          |
| 🔄 **Subscriptions**     | `useSubscription` hook for recurring billing management |
| 🏢 **Billing portal**    | `useBillingPortal` hook for customer self-service       |
| 🔀 **Provider-agnostic** | Switch providers without changing your code             |
| 📘 **TypeScript**        | Full type safety with autocomplete                      |
| ⚡ **SSR Ready**         | Works with Next.js, Remix, and other React frameworks   |
| 🎣 **Simple Hooks**      | Clean, intuitive API for all payment operations         |

---

## 📦 Installation

```bash
npm install @paylayer/react
```

---

## 🚀 Quick Start

### Step 1: Configure PayLayer

Load your configuration using `setConfig()` at the top level of your app, in the same place where you use `PayLayerProvider`:

```tsx
import {
  PayLayerProvider,
  setConfig,
  type PayLayerConfig,
} from "@paylayer/react";

const config: PayLayerConfig = {
  provider: "stripe",
  environment: "sandbox",
  stripe: {
    secretKey: process.env.STRIPE_SECRET_KEY!,
  },
};

setConfig(config);

function App() {
  return (
    <PayLayerProvider>
      <YourApp />
    </PayLayerProvider>
  );
}
```

> **Important:** `setConfig()` must be called at the top level of your app, in the same file where you render `PayLayerProvider`. It cannot be called inside components or hooks.

### Step 2: Use the Hooks

```tsx
import { useCharge } from "@paylayer/react";

function CheckoutButton() {
  const { charge, loading } = useCharge();

  const handlePayment = async () => {
    const result = await charge({
      amount: 29.99,
      currency: "USD",
      email: "customer@example.com",
    });

    if (result.url) {
      window.location.href = result.url;
    }
  };

  return (
    <button onClick={handlePayment} disabled={loading}>
      {loading ? "Processing..." : "Pay $29.99"}
    </button>
  );
}
```

---

## 📝 Configuration

### Using `setConfig()`

Load your PayLayer configuration by calling `setConfig()` with a configuration object. **Important:** `setConfig()` must be called at the top level of your app, in the same place where you render `PayLayerProvider`. It cannot be called inside components, hooks, or conditional blocks.

You can structure and load your config however you prefer - from a file, inline, or dynamically.

**Basic Example (Inline):**

```tsx
import { setConfig, type PayLayerConfig } from "@paylayer/react";

const config: PayLayerConfig = {
  provider: "stripe",
  environment: "sandbox",
  stripe: {
    secretKey: process.env.STRIPE_SECRET_KEY!,
    webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
  },
};

setConfig(config);
```

**Loading from a File (Recommended):**

You can organize your config in a separate file. We recommend placing it in your project root, but you can put it anywhere you prefer:

```tsx
import type { PayLayerConfig } from "@paylayer/react";

const config: PayLayerConfig = {
  provider: "stripe",
  environment: "sandbox",
  stripe: {
    secretKey: process.env.STRIPE_SECRET_KEY!,
    webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
  },
};

export default config;
```

Then load it at the top level of your app (in the same file where you use `PayLayerProvider`):

```tsx
import { PayLayerProvider, setConfig } from "@paylayer/react";
import config from "./paylayer.config.ts";

setConfig(config);

function App() {
  return (
    <PayLayerProvider>
      <YourApp />
    </PayLayerProvider>
  );
}
```

**Complete Example (All Providers):**

```tsx
import { setConfig, type PayLayerConfig } from "@paylayer/react";

const config: PayLayerConfig = {
  provider: "stripe",
  environment: "sandbox",
  stripe: {
    secretKey: process.env.STRIPE_SECRET_KEY!,
    webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
    checkoutSuccessUrl: "https://yourapp.com/success",
    checkoutCancelUrl: "https://yourapp.com/cancel",
    portalReturnUrl: "https://yourapp.com",
  },
  paddle: {
    apiKey: process.env.PADDLE_API_KEY!,
    webhookSecret: process.env.PADDLE_WEBHOOK_SECRET,
    sandbox: true,
  },
  paypal: {
    clientId: process.env.PAYPAL_CLIENT_ID!,
    clientSecret: process.env.PAYPAL_CLIENT_SECRET!,
    webhookId: process.env.PAYPAL_WEBHOOK_ID,
    sandbox: true,
  },
  lemonsqueezy: {
    apiKey: process.env.LEMONSQUEEZY_API_KEY!,
    storeId: process.env.LEMONSQUEEZY_STORE_ID!,
    webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
    testMode: true,
  },
  polar: {
    apiKey: process.env.POLAR_API_KEY!,
    webhookSecret: process.env.POLAR_WEBHOOK_SECRET,
    sandbox: true,
  },
};

setConfig(config);
```

> **Important:** `setConfig()` must be called at the top level of your app, in the same file where you render `PayLayerProvider`. It must be called before `PayLayerProvider` is rendered, and cannot be called inside components, hooks, or conditional blocks.

---

## 🎣 Hooks

### `useCharge`

One-time payment hook.

**Returns:**

| Property                     | Type                   | Description                    |
| ---------------------------- | ---------------------- | ------------------------------ |
| `charge(input: ChargeInput)` | `function`             | Execute payment                |
| `loading`                    | `boolean`              | Loading state                  |
| `error`                      | `Error \| null`        | Error object (if any)          |
| `payment`                    | `ChargeResult \| null` | Last successful payment result |

**Parameters:**

| Parameter    | Type     | Required | Description                                                     |
| ------------ | -------- | -------- | --------------------------------------------------------------- |
| `amount`     | `number` | ✅\*     | Payment amount (e.g., `29.99`) - **See provider support below** |
| `priceId`    | `string` | ✅\*     | Provider-specific price ID - **Recommended for most providers** |
| `productId`  | `string` | ✅\*     | Provider-specific product ID - **Works with all providers**     |
| `currency`   | `string` | ✅       | ISO 4217 currency code (e.g., `USD`)                            |
| `email`      | `string` | ❌       | Customer email address                                          |
| `successUrl` | `string` | ❌       | URL to redirect after successful payment                        |
| `cancelUrl`  | `string` | ❌       | URL to redirect if payment is cancelled                         |
| `metadata`   | `object` | ❌       | Additional metadata to attach to the payment                    |

\*Either `amount`, `priceId`, or `productId` must be provided.

**Setting the Payment Amount:**

You can specify the payment amount in three ways:

1. **`amount`** - Direct amount value (e.g., `29.99`)
2. **`priceId`** - Provider-specific price ID (recommended for most providers)
3. **`productId`** - Provider-specific product ID (works with all providers)

**Provider Support:**

| Provider          | `amount` | `priceId` | `productId` | Recommendation               |
| ----------------- | -------- | --------- | ----------- | ---------------------------- |
| **Stripe**        | ✅       | ✅        | ✅          | Use `amount` for Stripe      |
| **Paddle**        | ❌       | ✅        | ✅          | Use `priceId` or `productId` |
| **PayPal**        | ✅       | ❌        | ✅          | Use `amount` or `productId`  |
| **Lemon Squeezy** | ✅\*     | ✅        | ✅          | Use `priceId` or `productId` |
| **Polar**         | ❌       | ❌        | ✅          | Use `productId` only         |

\*Lemon Squeezy supports `amount` with `custom_price`, but requires a variant ID from environment or `priceId`.

**Best Practices:**

- **For Stripe**: You can use `amount` directly - it's the simplest option
- **For other providers**: Use `priceId` or `productId` for better compatibility
- **For maximum compatibility**: Use `productId` - it works with all providers

**Examples:**

**Using `amount` (Stripe, PayPal, Lemon Squeezy):**

```tsx
import { useCharge } from "@paylayer/react";

function CheckoutButton() {
  const { charge, loading } = useCharge();

  const handlePayment = async () => {
    const result = await charge({
      amount: 29.99,
      currency: "USD",
      email: "customer@example.com",
    });

    if (result.url) {
      window.location.href = result.url;
    }
  };

  return (
    <button onClick={handlePayment} disabled={loading}>
      {loading ? "Processing..." : "Pay $29.99"}
    </button>
  );
}
```

**Using `priceId` (Recommended for most providers):**

```tsx
const result = await charge({
  priceId: "price_1234567890",
  currency: "USD",
  email: "customer@example.com",
});
```

**Using `productId` (Works with all providers):**

```tsx
const result = await charge({
  productId: "prod_1234567890",
  currency: "USD",
  email: "customer@example.com",
});
```

### `useSubscription`

Subscription management hook.

**Returns:**

| Property                           | Type                         | Description           |
| ---------------------------------- | ---------------------------- | --------------------- |
| `subscribe(input: SubscribeInput)` | `function`                   | Create subscription   |
| `cancel(subscriptionId: string)`   | `function`                   | Cancel subscription   |
| `pause(subscriptionId: string)`    | `function`                   | Pause subscription    |
| `resume(subscriptionId: string)`   | `function`                   | Resume subscription   |
| `loading`                          | `boolean`                    | Loading state         |
| `error`                            | `Error \| null`              | Error object (if any) |
| `subscription`                     | `SubscriptionResult \| null` | Current subscription  |

**Subscribe Parameters:**

| Parameter  | Type     | Required | Description                          |
| ---------- | -------- | -------- | ------------------------------------ |
| `plan`     | `string` | ✅       | Plan identifier                      |
| `currency` | `string` | ✅       | ISO 4217 currency code (e.g., `USD`) |
| `email`    | `string` | ❌       | Customer email address               |

**Example:**

```tsx
import { useSubscription } from "@paylayer/react";

function SubscriptionButton() {
  const { subscribe, cancel, pause, resume, loading, subscription } =
    useSubscription();

  const handleSubscribe = async () => {
    try {
      const result = await subscribe({
        plan: "pro-monthly",
        currency: "USD",
        email: "customer@example.com",
      });

      if (result.url) {
        window.location.href = result.url;
      }
    } catch (err) {
      console.error("Subscription failed:", err);
    }
  };

  return (
    <div>
      <button onClick={handleSubscribe} disabled={loading}>
        Subscribe
      </button>
      {subscription && (
        <div>
          <p>Status: {subscription.status}</p>
          <button onClick={() => cancel(subscription.id)}>Cancel</button>
        </div>
      )}
    </div>
  );
}
```

### `useBillingPortal`

Billing portal access hook.

**Returns:**

| Property                         | Type       | Description                          |
| -------------------------------- | ---------- | ------------------------------------ |
| `open(input: { email: string })` | `function` | Open billing portal (auto-redirects) |
| `loading`                        | `boolean`  | Loading state                        |

**Parameters:**

| Parameter | Type     | Required | Description            |
| --------- | -------- | -------- | ---------------------- |
| `email`   | `string` | ✅       | Customer email address |

**Example:**

```tsx
import { useBillingPortal } from "@paylayer/react";

function BillingPortalButton() {
  const { open, loading } = useBillingPortal();

  const handleOpenPortal = async () => {
    try {
      await open({ email: "user@example.com" });
    } catch (err) {
      console.error("Failed to open portal:", err);
    }
  };

  return (
    <button onClick={handleOpenPortal} disabled={loading}>
      {loading ? "Opening..." : "Manage Billing"}
    </button>
  );
}
```

**What customers can do:**

- Update payment methods
- View billing history
- Cancel subscriptions
- Update billing information
- Download invoices

---

## 📘 TypeScript Support

The SDK is written in TypeScript and provides full type definitions:

```typescript
import { useCharge } from "@paylayer/react";
import type { ChargeResult, ChargeInput } from "@paylayer/react";

const { charge } = useCharge();

const result: ChargeResult = await charge({
  amount: 29.99,
  currency: "USD",
  email: "customer@example.com",
});
```

### Available Types

All types are exported from `@paylayer/react`:

```typescript
import type {
  UseChargeReturn,
  UseSubscriptionReturn,
  UseBillingPortalReturn,
  ChargeInput,
  ChargeResult,
  SubscribeInput,
  SubscriptionResult,
  ProviderName,
  ProviderConfig,
  PayLayerConfig,
  CurrencyCode,
  CustomerInfo,
  Provider,
} from "@paylayer/react";

import { Currency } from "@paylayer/react";
```

### Currency Enum

The SDK includes a comprehensive `Currency` enum with 150+ currencies for type safety and autocomplete:

```typescript
import { useCharge, Currency } from "@paylayer/react";

const { charge } = useCharge();

const result = await charge({
  amount: 29.99,
  currency: Currency.USD, // TypeScript autocomplete available
  email: "customer@example.com",
});
```

**Common Currencies:**

- `Currency.USD`, `Currency.EUR`, `Currency.GBP`, `Currency.JPY`
- `Currency.AUD`, `Currency.CAD`, `Currency.CHF`, `Currency.CNY`
- `Currency.HKD`, `Currency.NZD`, `Currency.SGD`

For a complete list, use your IDE's autocomplete or refer to the TypeScript definitions.

---

## 🔧 API Reference

### `setConfig`

Loads the PayLayer configuration.

**Parameters:**

| Parameter | Type             | Required | Description                       |
| --------- | ---------------- | -------- | --------------------------------- |
| `config`  | `PayLayerConfig` | ✅       | The PayLayer configuration object |

**When to use:**

- Call `setConfig()` at the top level of your app, in the same file where you render `PayLayerProvider`
- Must be called before rendering `PayLayerProvider`
- Must be called outside of components, hooks, or conditional blocks
- Use when you want to configure PayLayer programmatically instead of using environment variables

**Example:**

```tsx
import { setConfig, type PayLayerConfig } from "@paylayer/react";

const config: PayLayerConfig = {
  provider: "stripe",
  environment: "sandbox",
  stripe: {
    secretKey: process.env.STRIPE_SECRET_KEY!,
  },
};

setConfig(config);
```

### `PayLayerProvider`

Root provider component that provides PayLayer context to your React tree.

**Props:**

| Property   | Type        | Required | Description    |
| ---------- | ----------- | -------- | -------------- |
| `children` | `ReactNode` | ✅       | React children |

**Configuration Priority:**

1. Config loaded via `setConfig()`
2. Environment variables (if `setConfig()` was not called)

**Example:**

```tsx
import { PayLayerProvider } from "@paylayer/react";

function App() {
  return (
    <PayLayerProvider>
      <YourApp />
    </PayLayerProvider>
  );
}
```

---

## 🔄 Provider Abstraction

PayLayer abstracts away provider-specific details. Switch between Stripe, Paddle, PayPal, Lemon Squeezy, or Polar by changing your configuration - no code changes needed.

| Provider          | Status | Features                                        |
| ----------------- | ------ | ----------------------------------------------- |
| **Stripe**        | ✅     | Payments, subscriptions, and billing portal     |
| **Paddle**        | ✅     | Merchant of record, subscriptions, and checkout |
| **PayPal**        | ✅     | Payments and subscriptions                      |
| **Lemon Squeezy** | ✅     | Checkout and subscriptions                      |
| **Polar.sh**      | ✅     | Billing infrastructure and subscriptions        |

All providers are fully implemented with proper error handling and API integration.

**Example:**

```typescript
const config: PayLayerConfig = {
  provider: "stripe", // or "paddle", "paypal", "lemonsqueezy", "polar"
};
```

---

## 🔒 Security

**Important:** All API keys and secrets should be stored on your backend. The React SDK makes HTTP requests to your API endpoints. Never expose payment provider credentials in your frontend code.

---

## ❓ Troubleshooting

### Config Not Loading

If you're getting errors about the provider not being configured:

1. **Make sure you're calling `setConfig()` at the top level** before rendering `PayLayerProvider`:

   ```tsx
   setConfig(config);
   function App() {
     return <PayLayerProvider>...</PayLayerProvider>;
   }
   ```

2. **Verify your config structure** - Ensure your config matches the `PayLayerConfig` type:

   ```tsx
   import type { PayLayerConfig } from "@paylayer/react";

   const config: PayLayerConfig = {
     provider: "stripe",
   };
   ```

### Using Environment Variables Instead

If you prefer to use environment variables instead of calling `setConfig()`, simply don't call `setConfig()`. PayLayer will automatically read from environment variables:

```bash
# .env
PAYLAYER_PROVIDER=stripe
STRIPE_SECRET_KEY=sk_test_...
```

No `setConfig()` call needed - just use `<PayLayerProvider>` directly.

---

## 📚 Examples

### Next.js Quickstarter

A beautiful, production-ready Next.js starter template with PayLayer integration:

- **[PayLayer Next.js Quickstarter](https://github.com/ajagatobby/paylayer-nextjs-quickstarter)** - Complete Next.js 16 application with:
  - Beautiful, animated UI with Tailwind CSS
  - Ready-to-use charge and subscription flows
  - Webhook handling
  - TypeScript support
  - Production-ready setup

### Other Examples

See the [examples directory](../../examples) for additional integration examples.

---

## 🔗 Related Packages

- **[@paylayer/core](https://www.npmjs.com/package/@paylayer/core)** - The server-side Node.js SDK for PayLayer. Use this for backend payment processing, webhooks, and server-side operations.

---

## 📄 License

MIT

---

<div align="center">

**Made with ❤️ by PayLayer**

</div>
