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

# React Integration Guide

> This guide shows you how to integrate SolvaPay React components and hooks into your React application to build payment flows and purchase management UIs.

## Table of Contents

* [Installation](#installation)
* [Basic Setup](#basic-setup)
* [Provider Configuration](#provider-configuration)
* [Components](#components)
* [Hooks](#hooks)
* [Payment Flow](#payment-flow)
* [Purchase Management](#purchase-management)
* [Complete Example](#complete-example)

## Installation

Install the required packages:

```bash theme={null}
npm install @solvapay/react @solvapay/react-supabase
# or
pnpm add @solvapay/react @solvapay/react-supabase
# or
yarn add @solvapay/react @solvapay/react-supabase
```

### Peer Dependencies

SolvaPay React requires:

* `react` ^18.2.0 || ^19.0.0
* `react-dom` ^18.2.0 || ^19.0.0
* `@stripe/react-stripe-js` (for payment forms)
* `@stripe/stripe-js` (for Stripe integration)

## Basic Setup

### 1. Wrap Your App with Provider

The `SolvaPayProvider` is required to use SolvaPay hooks and components:

```tsx theme={null}
// App.tsx or main entry point
import { SolvaPayProvider } from '@solvapay/react'

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

### 2. Zero-Config Usage

By default, `SolvaPayProvider` uses these API endpoints:

* `/api/check-purchase` - Check purchase status
* `/api/create-payment-intent` - Create payment intents
* `/api/process-payment` - Process payments

If your backend uses these routes, no configuration is needed!

## Provider Configuration

### Custom API Routes

If your backend uses different API routes, configure them:

```tsx theme={null}
import { SolvaPayProvider } from '@solvapay/react'

function App() {
  return (
    <SolvaPayProvider
      config={{
        api: {
          checkPurchase: '/api/custom/purchase',
          createPayment: '/api/custom/payment',
          processPayment: '/api/custom/process',
        },
      }}
    >
      <YourApp />
    </SolvaPayProvider>
  )
}
```

### With Supabase Authentication

Use the Supabase auth adapter for automatic user ID extraction:

```tsx theme={null}
import { SolvaPayProvider } from '@solvapay/react'
import { createSupabaseAuthAdapter } from '@solvapay/react-supabase'
import { supabase } from './lib/supabase'

function App() {
  const supabaseAdapter = createSupabaseAuthAdapter({ client: supabase })

  return (
    <SolvaPayProvider
      config={{
        auth: { adapter: supabaseAdapter },
      }}
    >
      <YourApp />
    </SolvaPayProvider>
  )
}
```

### Custom Authentication Adapter

Create a custom auth adapter for other authentication systems:

```tsx theme={null}
import { SolvaPayProvider, AuthAdapter } from '@solvapay/react'

const customAuthAdapter: AuthAdapter = {
  getToken: async () => {
    // Return auth token from your auth system
    return localStorage.getItem('auth-token')
  },

  getUserId: async () => {
    // Extract user ID from your auth system
    const token = localStorage.getItem('auth-token')
    if (!token) return null

    const decoded = JSON.parse(atob(token.split('.')[1]))
    return decoded.userId
  },
}

function App() {
  return (
    <SolvaPayProvider
      config={{
        auth: { adapter: customAuthAdapter },
      }}
    >
      <YourApp />
    </SolvaPayProvider>
  )
}
```

## Components

### PaymentForm

A complete payment form component with Stripe integration:

```tsx theme={null}
import { PaymentForm } from '@solvapay/react'

function CheckoutPage() {
  return (
    <PaymentForm
      planRef="pln_premium"
      productRef="prd_myapi"
      onSuccess={() => {
        console.log('Payment successful!')
        // Redirect or show success message
      }}
      onError={error => {
        console.error('Payment failed:', error)
      }}
    />
  )
}
```

#### PaymentForm Props

* `planRef` (required) - Plan reference to subscribe to
* `productRef` (optional) - Product reference for usage tracking
* `onSuccess` - Callback when payment succeeds
* `onError` - Callback when payment fails
* `returnUrl` - Optional return URL after payment
* `submitButtonText` - Custom submit button text (default: "Pay Now")
* `className` - Custom CSS class for form container
* `buttonClassName` - Custom CSS class for submit button

### PricingSelector

Select a pricing option from available options using the render-prop API:

```tsx theme={null}
import { PricingSelector } from '@solvapay/react'

function PricingPage() {
  return (
    <PricingSelector
      fetcher={async (productRef) => {
        const res = await fetch(`/api/plans?productRef=${productRef}`)
        return res.json()
      }}
      productRef="prd_myapi"
    >
      {({ plans, loading, error, selectedPlan, selectPlan }) => {
        if (loading) return <div>Loading pricing...</div>
        if (error) return <div>Error: {error.message}</div>
        return (
          <div>
            {plans.map(plan => (
              <button
                key={plan.id}
                onClick={() => selectPlan(plan)}
                style={{ fontWeight: selectedPlan?.id === plan.id ? 'bold' : 'normal' }}
              >
                ${plan.price}/{plan.interval}
              </button>
            ))}
          </div>
        )
      }}
    </PricingSelector>
  )
}
```

### PurchaseGate

Conditionally render content based on purchase status using the compound
primitive. Match by `productRef` (any plan of that product) or `planRef`
(specific plan). Both together require they match on the same active
purchase.

```tsx theme={null}
import { PurchaseGate } from '@solvapay/react'

function PremiumContent() {
  return (
    <PurchaseGate.Root planRef="pln_premium">
      <PurchaseGate.Loading>Loading...</PurchaseGate.Loading>
      <PurchaseGate.Blocked>Please subscribe to access this content.</PurchaseGate.Blocked>
      <PurchaseGate.Allowed>Premium content here!</PurchaseGate.Allowed>
    </PurchaseGate.Root>
  )
}
```

### ProductBadge

Display product subscription information using the render-prop pattern:

```tsx theme={null}
import { ProductBadge } from '@solvapay/react'

function UserProfile() {
  return (
    <div>
      <h1>Your Profile</h1>
      <ProductBadge>
        {({ displayPlan, shouldShow }) =>
          shouldShow ? <span className="badge">{displayPlan}</span> : null
        }
      </ProductBadge>
    </div>
  )
}
```

## Hooks

### usePurchase

Check purchase status and access purchase data:

```tsx theme={null}
import { usePurchase } from '@solvapay/react'

function Dashboard() {
  const { hasPaidPurchase, loading, activePurchase, refetch } = usePurchase()

  if (loading) {
    return <div>Loading purchase status...</div>
  }

  return (
    <div>
      {hasPaidPurchase ? (
        <div>
          <h2>Active Purchase</h2>
          <p>Product: {activePurchase?.productName}</p>
          <p>Status: {activePurchase?.status}</p>
        </div>
      ) : (
        <div>
          <p>No active purchase</p>
          <button onClick={() => refetch()}>Refresh</button>
        </div>
      )}
    </div>
  )
}
```

#### usePurchase Return Values

* `loading` - Boolean indicating the first purchase check for this user is in progress. Stays `false` on background refetches — gate your initial skeleton on this.
* `isRefetching` - Boolean indicating a background refetch is in progress (first fetch already completed). Use for subtle "refreshing" indicators that shouldn't remount the UI.
* `purchases` - Array of purchase objects
* `activePurchase` - The active purchase (or null)
* `hasPaidPurchase` - Boolean indicating if user has paid purchase
* `activePaidPurchase` - The active paid purchase (or null)
* `hasPurchase(criteria?)` - Predicate with AND semantics. Pass `{ productRef }`, `{ planRef }`, or both (both must match the same active purchase). Call with no arguments to check for any active purchase. Mirrors `<PurchaseGate.Root>` prop shape.
* `refetch` - Function to manually refetch purchase status

### useCheckout

Programmatic checkout flow:

```tsx theme={null}
import { useCheckout } from '@solvapay/react'

function CustomCheckout() {
  const { startCheckout, loading, error, stripePromise, clientSecret, reset } = useCheckout(
    'pln_premium',
    'prd_myapi',
  )

  const handleCheckout = async () => {
    try {
      await startCheckout()
      console.log('Checkout started!')
    } catch (error) {
      console.error('Checkout failed:', error)
    }
  }

  return (
    <button onClick={handleCheckout} disabled={loading}>
      {loading ? 'Processing...' : 'Checkout'}
    </button>
  )
}
```

### useCustomer

Access customer information:

```tsx theme={null}
import { useCustomer } from '@solvapay/react'

function CustomerInfo() {
  const { customerRef, email, name, loading } = useCustomer()

  if (loading) {
    return <div>Loading customer info...</div>
  }

  return (
    <div>
      <h2>Customer Information</h2>
      <p>Customer ID: {customerRef}</p>
      <p>Email: {email}</p>
      <p>Name: {name}</p>
    </div>
  )
}
```

### usePlans

Fetch available plans:

```tsx theme={null}
import { usePlans } from '@solvapay/react'

function PlansPage() {
  const { plans, loading, error } = usePlans({
    fetcher: async (productRef) => {
      const res = await fetch(`/api/plans?productRef=${productRef}`)
      return res.json()
    },
    productRef: 'prd_myapi',
  })

  if (loading) {
    return <div>Loading plans...</div>
  }

  if (error) {
    return <div>Error loading plans: {error.message}</div>
  }

  return (
    <div>
      <h1>Available Plans</h1>
      {plans?.map(plan => (
        <div key={plan.id}>
          <h3>${plan.price}/{plan.interval}</h3>
        </div>
      ))}
    </div>
  )
}
```

### useSolvaPay

Access all SolvaPay functionality:

```tsx theme={null}
import { useSolvaPay } from '@solvapay/react'

function CustomComponent() {
  const { activePurchase, startCheckout, customerRef, refetchPurchase } =
    useSolvaPay()

  // Use any SolvaPay functionality
  return <div>...</div>
}
```

## Payment Flow

### Simple Payment Flow

Use `PaymentForm` for a complete payment flow:

```tsx theme={null}
import { PaymentForm } from '@solvapay/react'
import { useRouter } from 'next/navigation' // or your router

function CheckoutPage() {
  const router = useRouter()

  return (
    <div className="checkout-container">
      <h1>Subscribe to Premium</h1>
      <PaymentForm
        planRef="pln_premium"
        productRef="prd_myapi"
        onSuccess={() => {
          router.push('/dashboard')
        }}
        onError={error => {
          alert(`Payment failed: ${error.message}`)
        }}
      />
    </div>
  )
}
```

### Custom Payment Flow

Build a custom payment flow with hooks:

```tsx theme={null}
import { useCheckout, usePurchase } from '@solvapay/react'
import { loadStripe } from '@stripe/stripe-js'

function CustomCheckoutPage() {
  const { startCheckout, loading, stripePromise, clientSecret, reset } = useCheckout('pln_premium', 'prd_myapi')
  const { refetch } = usePurchase()

  const handleCheckout = async () => {
    try {
      await startCheckout()

      // After successful checkout, refresh purchase status
      await refetch()
      router.push('/dashboard')
    } catch (error) {
      console.error('Checkout failed:', error)
    }
  }

  return (
    <div>
      {/* Your custom UI */}
      <button onClick={handleCheckout} disabled={loading}>
        Pay Now
      </button>
    </div>
  )
}
```

## Purchase Management

### Check Purchase Status

```tsx theme={null}
import { usePurchase } from '@solvapay/react'

function ProtectedContent() {
  const { hasPaidPurchase, loading } = usePurchase()

  if (loading) {
    return <div>Loading...</div>
  }

  if (!hasPaidPurchase) {
    return <div>Please subscribe to access this content.</div>
  }

  return <div>Premium content here!</div>
}
```

### Display Purchase Details

```tsx theme={null}
import { usePurchase } from '@solvapay/react'

function PurchaseDetails() {
  const { activePurchase, loading } = usePurchase()

  if (loading) {
    return <div>Loading...</div>
  }

  if (!activePurchase) {
    return <div>No active purchase</div>
  }

  return (
    <div>
      <h2>Your Purchase</h2>
      <p>Product: {activePurchase.productName}</p>
      <p>Status: {activePurchase.status}</p>
      <p>Current Period End: {activePurchase.currentPeriodEnd}</p>
    </div>
  )
}
```

### Refresh Purchase Status

```tsx theme={null}
import { usePurchase } from '@solvapay/react'

function PurchaseStatus() {
  const { activePurchase, refetch, loading, isRefetching } = usePurchase()

  return (
    <div>
      <p>Status: {activePurchase?.status || 'None'}</p>
      <button onClick={() => refetch()} disabled={loading || isRefetching}>
        {isRefetching ? 'Refreshing...' : 'Refresh'}
      </button>
    </div>
  )
}
```

## Complete Example

Here's a complete React application with SolvaPay integration:

```tsx theme={null}
// App.tsx
import { SolvaPayProvider } from '@solvapay/react'
import { createSupabaseAuthAdapter } from '@solvapay/react-supabase'
import { supabase } from './lib/supabase'
import { Dashboard } from './Dashboard'
import { Checkout } from './Checkout'

function App() {
  const supabaseAdapter = createSupabaseAuthAdapter({ client: supabase })

  return (
    <SolvaPayProvider config={{ auth: { adapter: supabaseAdapter } }}>
      <Router>
        <Routes>
          <Route path="/" element={<Dashboard />} />
          <Route path="/checkout" element={<Checkout />} />
        </Routes>
      </Router>
    </SolvaPayProvider>
  )
}

// Dashboard.tsx
import { usePurchase } from '@solvapay/react'
import { Link } from 'react-router-dom'

export function Dashboard() {
  const { hasPaidPurchase, loading, activePurchase } = usePurchase()

  if (loading) {
    return <div>Loading...</div>
  }

  return (
    <div>
      <h1>Dashboard</h1>

      {hasPaidPurchase ? (
        <div>
          <h2>Welcome, Premium User!</h2>
          <p>Product: {activePurchase?.productName}</p>
          <p>Status: {activePurchase?.status}</p>
        </div>
      ) : (
        <div>
          <p>Please subscribe to access premium features.</p>
          <Link to="/checkout">Go to Checkout</Link>
        </div>
      )}
    </div>
  )
}

// Checkout.tsx
import { PaymentForm } from '@solvapay/react'
import { useNavigate } from 'react-router-dom'

export function Checkout() {
  const navigate = useNavigate()

  return (
    <div>
      <h1>Subscribe to Premium</h1>
      <PaymentForm
        planRef="pln_premium"
        productRef="prd_myapi"
        onSuccess={() => {
          navigate('/dashboard')
        }}
        onError={error => {
          console.error('Payment failed:', error)
        }}
      />
    </div>
  )
}
```

## Styling

SolvaPay components are headless and don't include default styles. Style them to match your design system:

```tsx theme={null}
<PaymentForm
  planRef="pln_premium"
  productRef="prd_myapi"
  className="my-custom-form"
  buttonClassName="my-custom-button"
/>
```

```css theme={null}
.my-custom-form {
  max-width: 500px;
  margin: 0 auto;
  padding: 2rem;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
}

.my-custom-button {
  background-color: #007bff;
  color: white;
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
```

## Best Practices

1. **Provider Placement**: Place `SolvaPayProvider` at the root of your app, above all routes.

2. **Error Handling**: Always handle errors from hooks and components.

3. **Loading States**: Show loading states while purchase checks are in progress.

4. **Refetch After Payment**: Call `refetch()` after successful payment to update purchase status.

5. **Type Safety**: Use TypeScript for better type safety and autocomplete.

6. **Custom Styling**: Style components to match your design system.

## Next Steps

* [Next.js Integration Guide](./nextjs) - Learn Next.js-specific integration
* [Custom Authentication Adapters](./custom-auth) - Build custom auth adapters
* [Usage Events](./usage-events) - Track purchase and usage activity
* [API Reference](/sdks/typescript/intro) - Full API documentation
