> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paymint.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js SDK

> Documentation for @paymint/nextjs

# @paymint/nextjs

Next.js SDK for Paymint - subscription billing made simple.

## Installation

```bash theme={null}
npm install @paymint/nextjs
```

## Quick Start

### 1. Create API Route

```typescript theme={null}
// app/api/paddle/route.ts
import { nextRouteHandler } from '@paymint/nextjs/server';
import { auth, clerkClient } from '@clerk/nextjs/server';

export const { GET, POST } = nextRouteHandler({
  apiKey: process.env.PAYMINT_API_KEY!,
  getCustomerEmail: async (req) => {
    const { userId } = await auth();
    if (!userId) return null;
    const client = await clerkClient();
    const user = await client.users.getUser(userId);
    return user.emailAddresses[0]?.emailAddress || null;
  },
});
```

### 2. Add Provider

```tsx theme={null}
// app/providers.tsx
'use client';

import { PaymintProvider } from '@paymint/nextjs';

export function Providers({ 
  children, 
  email 
}: { 
  children: React.ReactNode;
  email?: string;
}) {
  return (
    <PaymintProvider 
      apiRoute="/api/billing" 
      customerEmail={email}
      onCheckoutComplete={(transactionId) => {
        // Redirect to success page or refresh data
        window.location.href = `/checkout/success?txn=${transactionId}`;
      }}
    >
      {children}
    </PaymintProvider>
  );
}
```

### 3. Use Hooks

```tsx theme={null}
'use client';

import { useBilling, useCheckout } from '@paymint/nextjs';

export default function PricingPage() {
  const { products, subscription, loading, hasActiveSubscription } = useBilling();
  const { openCheckout, ready } = useCheckout();

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      {products.map(product => (
        <div key={product.id}>
          <h2>{product.name}</h2>
          {product.prices.map(price => (
            <button
              key={price.id}
              onClick={() => openCheckout(price.id)}
              disabled={!ready}
            >
              Subscribe - ${price.unitPrice.amount / 100}/{price.billingCycle.interval}
            </button>
          ))}
        </div>
      ))}
      
      {hasActiveSubscription && (
        <p>Current plan: {subscription?.productName}</p>
      )}
    </div>
  );
}
```

### 4. Create Success Page

```tsx theme={null}
// app/checkout/success/page.tsx
import { PaymintServer } from '@paymint/nextjs/server';
import { auth, clerkClient } from '@clerk/nextjs/server';
import { redirect } from 'next/navigation';

export default async function CheckoutSuccessPage() {
  const { userId } = await auth();
  if (!userId) redirect('/sign-in');

  const client = await clerkClient();
  const user = await client.users.getUser(userId);
  const email = user.emailAddresses[0]?.emailAddress;

  if (!email) redirect('/');

  // Fetch latest subscription
  const paymint = new PaymintServer({
    apiKey: process.env.PAYMINT_API_KEY!,
  });

  const billing = await paymint.getBilling(email);

  return (
    <div className="p-8 text-center">
      <h1 className="text-2xl font-bold text-green-600">Payment Successful!</h1>
      
      {billing.hasActiveSubscription ? (
        <div className="mt-4">
          <p>You're now subscribed to: <strong>{billing.currentSubscription?.items[0]?.priceId}</strong></p>
          <p>Status: {billing.currentSubscription?.status}</p>
          <a href="/dashboard" className="mt-4 inline-block bg-blue-600 text-white px-4 py-2 rounded">
            Go to Dashboard
          </a>
        </div>
      ) : (
        <div className="mt-4">
          <p>Processing your subscription...</p>
          <p className="text-sm text-gray-500">This may take a few moments. Please refresh.</p>
        </div>
      )}
    </div>
  );
}
```

## Imports

```typescript theme={null}
// Client-side (hooks, provider)
import { 
  PaymintProvider, 
  useBilling, 
  useProducts, 
  useSubscription, 
  useCheckout 
} from '@paymint/nextjs';

// Server-side (API routes, server components)
import { PaymintServer, nextRouteHandler } from '@paymint/nextjs/server';

// Types
import type { Product, Subscription, Price } from '@paymint/nextjs';
```

## API Routes Created

The `nextRouteHandler` creates these endpoints automatically:

| Route                        | Method | Description                |
| ---------------------------- | ------ | -------------------------- |
| `/api/billing/products`      | GET    | List all products          |
| `/api/billing/subscriptions` | GET    | Get customer subscriptions |
| `/api/billing/billing`       | GET    | Get full billing state     |
| `/api/billing/initialize`    | POST   | Initialize Paddle checkout |
| `/api/billing/cancel/{id}`   | POST   | Cancel subscription        |
| `/api/billing/pause/{id}`    | POST   | Pause subscription         |
| `/api/billing/resume/{id}`   | POST   | Resume subscription        |
| `/api/billing/activate/{id}` | POST   | Activate trial             |

## Hooks

### `useBilling()`

```tsx theme={null}
const {
  products,              // Product[]
  subscription,          // Subscription | null
  loading,               // boolean
  hasActiveSubscription, // boolean
  isOnTrial,             // boolean
  refresh,               // () => Promise<void>
} = useBilling();
```

### `useCheckout()`

```tsx theme={null}
const { openCheckout, ready, loading } = useCheckout();

// Simple
openCheckout('pri_xxx');

// With options
openCheckout({
  items: [{ priceId: 'pri_xxx', quantity: 1 }],
  discountCode: 'SAVE20',
  settings: { theme: 'dark', successUrl: '/checkout/success' },
});
```

### `useSubscription()`

```tsx theme={null}
const { 
  subscription,
  cancel,    // (options?) => Promise<void>
  pause,     // (options?) => Promise<void>
  resume,    // () => Promise<void>
  isMutating,
} = useSubscription();

await cancel({ effectiveFrom: 'next_billing_period' });
await pause({ effectiveFrom: 'next_billing_period' });
await resume();
```

## Post-Checkout Success Handling

After a successful checkout, you have two options:

### Option 1: Use `onCheckoutComplete` Callback

```tsx theme={null}
<PaymintProvider 
  apiRoute="/api/billing" 
  customerEmail={email}
  onCheckoutComplete={(transactionId) => {
    // Redirect to success page
    window.location.href = `/checkout/success?txn=${transactionId}`;
  }}
>
```

### Option 2: Use Paddle's `successUrl`

```tsx theme={null}
openCheckout({
  items: [{ priceId: 'pri_xxx' }],
  settings: { 
    successUrl: '/checkout/success',  // Paddle redirects here after payment
  },
});
```

### Fetching Latest Subscription on Success Page

```tsx theme={null}
// app/checkout/success/page.tsx (Server Component)
import { PaymintServer } from '@paymint/nextjs/server';

export default async function SuccessPage() {
  const email = /* get from auth session */;
  
  const paymint = new PaymintServer({
    apiKey: process.env.PAYMINT_API_KEY!,
  });

  // Fetch latest subscription data
  const billing = await paymint.getBilling(email);

  if (billing.hasActiveSubscription) {
    return <div>Welcome! Your subscription is active.</div>;
  }

  // Webhook may not have processed yet - show loading
  return <div>Processing your payment...</div>;
}
```

> **Note:** Subscriptions are created via Paddle webhooks. There may be a brief delay (1-5 seconds) between payment completion and subscription appearing in the database.

## Server-Side Usage

```typescript theme={null}
import { PaymintServer } from '@paymint/nextjs/server';

const paymint = new PaymintServer({
  apiKey: process.env.PAYMINT_API_KEY!,
});

// In server components or API routes
const products = await paymint.getProducts();
const billing = await paymint.getBilling('user@example.com');
```

## Auth Integration

### Clerk

```typescript theme={null}
getCustomerEmail: async (req) => {
  const { userId } = await auth();
  if (!userId) return null;
  const client = await clerkClient();
  const user = await client.users.getUser(userId);
  return user.emailAddresses[0]?.emailAddress || null;
}
```

### NextAuth

```typescript theme={null}
import { getServerSession } from 'next-auth';

getCustomerEmail: async (req) => {
  const session = await getServerSession(authOptions);
  return session?.user?.email || null;
}
```

### Supabase

```typescript theme={null}
import { createClient } from '@supabase/supabase-js';

getCustomerEmail: async (req) => {
  const supabase = createClient(/* ... */);
  const { data: { user } } = await supabase.auth.getUser();
  return user?.email || null;
}
```

## Security

### Developer Responsibilities

The SDK handles most security concerns, but developers must ensure:

#### 1. `getCustomerEmail` - Get Email from Auth Session (CRITICAL)

```typescript theme={null}
// ✅ CORRECT - Email from authenticated session
getCustomerEmail: async (req) => {
  const { userId } = await auth();  // Clerk verifies session
  if (!userId) return null;
  const user = await clerkClient.users.getUser(userId);
  return user.emailAddresses[0]?.emailAddress || null;
}

// ❌ WRONG - Email from request body (VULNERABLE!)
getCustomerEmail: async (req) => {
  const body = await req.json();
  return body.email;  // Attacker can impersonate any user!
}

// ❌ WRONG - Email from query params (VULNERABLE!)
getCustomerEmail: async (req) => {
  return req.nextUrl.searchParams.get('email');  // Attacker can impersonate!
}
```

#### 2. Keep API Key Server-Side Only

```typescript theme={null}
// ✅ CORRECT - Server-side only
apiKey: process.env.PAYMINT_API_KEY!,

// ❌ WRONG - Exposed to client
apiKey: process.env.NEXT_PUBLIC_PAYMINT_API_KEY!,
```

#### 3. Use HTTPS in Production

Always deploy with HTTPS to protect session cookies.

### What Paymint Handles

| Security Concern                    | Status    |
| ----------------------------------- | --------- |
| API key encryption at rest          | ✓ Handled |
| Subscription ownership verification | ✓ Handled |
| Customer email validation           | ✓ Handled |
| Paddle API authentication           | ✓ Handled |
| Webhook signature verification      | ✓ Handled |

### What Developers Must Handle

| Security Concern                  | Requirement                          |
| --------------------------------- | ------------------------------------ |
| `getCustomerEmail` implementation | Must return email from auth session  |
| API key storage                   | Keep server-side only                |
| Auth session security             | Use Clerk/NextAuth/Supabase properly |
| HTTPS                             | Enable in production                 |

## Environment Variables

```env theme={null}
PAYMINT_API_KEY=paymint_test_xxx  # or paymint_live_xxx
```

The SDK auto-detects sandbox vs production from your API key:

* `paymint_test_xxx` → Paddle Sandbox
* `paymint_live_xxx` → Paddle Production

No need for `NEXT_PUBLIC_PADDLE_ENVIRONMENT`!

## License

MIT
