Skip to main content

Table of Contents

Installation

Install the required packages:
npm install @solvapay/server @solvapay/next @solvapay/react @solvapay/react-supabase
# or
pnpm add @solvapay/server @solvapay/next @solvapay/react @solvapay/react-supabase

Basic Setup

1. Environment Variables

Create a .env.local file:
SOLVAPAY_SECRET_KEY=sk_...
NEXT_PUBLIC_SOLVAPAY_PRODUCT=prd_YOUR_PRODUCT_ID

2. Initialize SolvaPay Client

Create a shared SolvaPay instance:
// lib/solvapay.ts
import { createSolvaPay } from '@solvapay/server'

export const solvaPay = createSolvaPay({
  apiKey: process.env.SOLVAPAY_SECRET_KEY,
})

Protecting API Routes

Basic API Route Protection

Use the payable.next() adapter to protect Next.js App Router API routes:
// app/api/tasks/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { solvaPay } from '@/lib/solvapay'

const payable = solvaPay.payable({
  product: process.env.NEXT_PUBLIC_SOLVAPAY_PRODUCT!,
  plan: 'pln_premium',
})

// Your business logic
async function createTask(req: NextRequest) {
  const body = await req.json()
  const { title } = body

  return { success: true, task: { title } }
}

// Protect the route
export const POST = payable.next(createTask)

Using Next.js Helpers

The @solvapay/next package provides helper functions that simplify common operations:
// app/api/check-purchase/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { checkPurchase } from '@solvapay/next'

export async function GET(request: NextRequest) {
  const result = await checkPurchase(request)

  // checkPurchase returns NextResponse or data object
  if (result instanceof NextResponse) {
    return result
  }

  return NextResponse.json(result)
}

Available Helper Functions

Check Purchase

// app/api/check-purchase/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { checkPurchase } from '@solvapay/next'

export async function GET(request: NextRequest) {
  const result = await checkPurchase(request)
  return result instanceof NextResponse ? result : NextResponse.json(result)
}

Create Payment Intent

// app/api/create-payment-intent/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { createPaymentIntent } from '@solvapay/next'

export async function POST(request: NextRequest) {
  const { planRef, productRef } = await request.json()

  if (!planRef || !productRef) {
    return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 })
  }

  const result = await createPaymentIntent(request, { planRef, productRef })
  return result instanceof NextResponse ? result : NextResponse.json(result)
}

Process Payment

// app/api/process-payment/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { processPayment } from '@solvapay/next'

export async function POST(request: NextRequest) {
  const { paymentIntentId, productRef, planRef } = await request.json()

  if (!paymentIntentId || !productRef) {
    return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 })
  }

  const result = await processPayment(request, { paymentIntentId, productRef, planRef })
  return result instanceof NextResponse ? result : NextResponse.json(result)
}

Create Checkout Session

// app/api/create-checkout-session/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { createCheckoutSession } from '@solvapay/next'

export async function POST(request: NextRequest) {
  const { planRef, productRef } = await request.json()

  const result = await createCheckoutSession(request, { planRef, productRef })
  return result instanceof NextResponse ? result : NextResponse.json(result)
}

Sync Customer

// app/api/sync-customer/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { syncCustomer } from '@solvapay/next'

export async function POST(request: NextRequest) {
  const result = await syncCustomer(request)
  return result instanceof NextResponse ? result : NextResponse.json(result)
}

Server Components

Use SolvaPay in Server Components to check purchase status:
// app/dashboard/page.tsx
import { checkPurchase } from '@solvapay/next';
import { cookies } from 'next/headers';

export default async function DashboardPage() {
  // Create a request-like object for checkPurchase
  const cookieStore = await cookies();
  const headers = new Headers();

  // Add auth headers if needed
  const authToken = cookieStore.get('auth-token');
  if (authToken) {
    headers.set('authorization', `Bearer ${authToken.value}`);
  }

  const request = new Request('http://localhost', {
    headers,
  });

  const purchase = await checkPurchase(request);

  if (purchase instanceof Response) {
    // Handle error
    return <div>Error checking purchase</div>;
  }

  return (
    <div>
      <h1>Dashboard</h1>
      {purchase.hasPaidPurchase ? (
        <p>You have an active purchase!</p>
      ) : (
        <p>Please subscribe to access premium features.</p>
      )}
    </div>
  );
}

Client Components

Use React hooks and components for client-side payment flows:
// app/checkout/page.tsx
'use client';

import { PaymentForm } from '@solvapay/react';
import { useRouter } from 'next/navigation';

export default function CheckoutPage() {
  const router = useRouter();

  return (
    <PaymentForm
      planRef="pln_premium"
      productRef={process.env.NEXT_PUBLIC_SOLVAPAY_PRODUCT!}
      onSuccess={() => {
        router.push('/dashboard');
      }}
      onError={(error) => {
        console.error('Payment error:', error);
      }}
    />
  );
}

Middleware Setup

Authentication Middleware

Set up authentication middleware (implemented in proxy.ts) to extract user information and make it available to API routes:
// proxy.ts
import { NextRequest, NextResponse } from 'next/server'
import { getUserIdFromRequest } from '@solvapay/auth'

export async function proxy(request: NextRequest) {
  // Extract user ID from auth token, session, etc.
  const authToken = request.headers.get('authorization')?.replace('Bearer ', '')

  if (authToken) {
    // Verify token and extract user ID
    // This is a simplified example - use your actual auth logic
    const userId = await extractUserIdFromToken(authToken)

    if (userId) {
      // Add user ID to request headers for SolvaPay
      const requestHeaders = new Headers(request.headers)
      requestHeaders.set('x-user-id', userId)

      return NextResponse.next({
        request: {
          headers: requestHeaders,
        },
      })
    }
  }

  return NextResponse.next()
}

export const config = {
  matcher: '/api/:path*',
}

Supabase Auth Middleware

If using Supabase, use the provided middleware helper (exported from @solvapay/next/middleware and used from proxy.ts):
// proxy.ts
import { createSupabaseAuthMiddleware } from '@solvapay/next/middleware'
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
)

export const proxy = createSupabaseAuthMiddleware({
  supabase,
  // Optional: customize which routes to protect
  protectedPaths: ['/api/protected'],
})

Payment Flow Integration

1. Set Up Provider

Wrap your app with SolvaPayProvider:
// app/layout.tsx
import { SolvaPayProvider } from '@solvapay/react';
import { createSupabaseAuthAdapter } from '@solvapay/react-supabase';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  // Optional: Use Supabase auth adapter
  const supabaseAdapter = createSupabaseAuthAdapter({
    supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL!,
    supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
  });

  return (
    <html>
      <body>
        <SolvaPayProvider
          config={{
            // Optional: Custom API routes (defaults work out of the box)
            api: {
              checkPurchase: '/api/check-purchase',
              createPayment: '/api/create-payment-intent',
              processPayment: '/api/process-payment',
            },
            // Optional: Supabase auth adapter
            auth: { adapter: supabaseAdapter },
          }}
        >
          {children}
        </SolvaPayProvider>
      </body>
    </html>
  );
}

2. Create API Routes

Set up the required API routes using Next.js helpers:
// app/api/check-purchase/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { checkPurchase } from '@solvapay/next'

export async function GET(request: NextRequest) {
  const result = await checkPurchase(request)
  return result instanceof NextResponse ? result : NextResponse.json(result)
}
// app/api/create-payment-intent/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { createPaymentIntent } from '@solvapay/next'

export async function POST(request: NextRequest) {
  const { planRef, productRef } = await request.json()

  if (!planRef || !productRef) {
    return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 })
  }

  const result = await createPaymentIntent(request, { planRef, productRef })
  return result instanceof NextResponse ? result : NextResponse.json(result)
}
// app/api/process-payment/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { processPayment } from '@solvapay/next'

export async function POST(request: NextRequest) {
  const { paymentIntentId, productRef, planRef } = await request.json()

  if (!paymentIntentId || !productRef) {
    return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 })
  }

  const result = await processPayment(request, { paymentIntentId, productRef, planRef })
  return result instanceof NextResponse ? result : NextResponse.json(result)
}

3. Use Payment Components

Use React components and hooks in your pages:
// app/checkout/page.tsx
'use client';

import { PaymentForm, usePurchase } from '@solvapay/react';
import { useRouter } from 'next/navigation';

export default function CheckoutPage() {
  const router = useRouter();
  const { hasPaidPurchase, loading } = usePurchase();

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

  if (hasPaidPurchase) {
    return <div>You already have an active purchase!</div>;
  }

  return (
    <div>
      <h1>Subscribe to Premium</h1>
      <PaymentForm
        planRef="pln_premium"
      productRef={process.env.NEXT_PUBLIC_SOLVAPAY_PRODUCT!}
        onSuccess={() => {
          router.push('/dashboard');
        }}
      />
    </div>
  );
}

Complete Example

Here’s a complete Next.js application with SolvaPay integration:

Project Structure

app/
├── layout.tsx
├── page.tsx
├── dashboard/
│   └── page.tsx
├── checkout/
│   └── page.tsx
└── api/
    ├── tasks/
    │   └── route.ts
    ├── check-purchase/
    │   └── route.ts
    ├── create-payment-intent/
    │   └── route.ts
    └── process-payment/
        └── route.ts
lib/
└── solvapay.ts
proxy.ts

Root Layout

// app/layout.tsx
import { SolvaPayProvider } from '@solvapay/react';
import { createSupabaseAuthAdapter } from '@solvapay/react-supabase';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const supabaseAdapter = createSupabaseAuthAdapter({
    supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL!,
    supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
  });

  return (
    <html>
      <body>
        <SolvaPayProvider config={{ auth: { adapter: supabaseAdapter } }}>
          {children}
        </SolvaPayProvider>
      </body>
    </html>
  );
}

Protected API Route

// app/api/tasks/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { solvaPay } from '@/lib/solvapay'

const payable = solvaPay.payable({
  product: process.env.NEXT_PUBLIC_SOLVAPAY_PRODUCT!,
  plan: 'pln_premium',
})

async function createTask(req: NextRequest) {
  const body = await req.json()
  const { title } = body

  return { success: true, task: { title, id: Date.now().toString() } }
}

export const POST = payable.next(createTask)

Checkout Page

// app/checkout/page.tsx
'use client';

import { PaymentForm } from '@solvapay/react';
import { useRouter } from 'next/navigation';

export default function CheckoutPage() {
  const router = useRouter();

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">Subscribe to Premium</h1>
      <PaymentForm
        planRef="pln_premium"
        productRef={process.env.NEXT_PUBLIC_SOLVAPAY_PRODUCT!}
        onSuccess={() => router.push('/dashboard')}
      />
    </div>
  );
}

Dashboard Page

// app/dashboard/page.tsx
'use client';

import { usePurchase } from '@solvapay/react';
import Link from 'next/link';

export default function DashboardPage() {
  const { hasPaidPurchase, loading, activePurchase } = usePurchase();

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

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">Dashboard</h1>

      {hasPaidPurchase ? (
        <div>
          <p className="text-green-600">You have an active purchase!</p>
          {activePurchase && (
            <p>Product: {activePurchase.productName}</p>
          )}
        </div>
      ) : (
        <div>
          <p className="text-gray-600">Please subscribe to access premium features.</p>
          <Link href="/checkout" className="text-blue-600 underline">
            Go to Checkout
          </Link>
        </div>
      )}
    </div>
  );
}

Cache Management

The @solvapay/next package includes purchase caching to reduce API calls:
import { clearPurchaseCache, getPurchaseCacheStats } from '@solvapay/next'

// Clear cache for a specific user
await clearPurchaseCache(userId)

// Clear all caches
await clearAllPurchaseCache()

// Get cache statistics
const stats = await getPurchaseCacheStats()
console.log(stats)

Best Practices

  1. Use Environment Variables: Store API keys and configuration in .env.local.
  2. Separate API Routes: Keep API routes separate from page routes for better organization.
  3. Error Handling: Always handle errors from helper functions (they may return NextResponse).
  4. Type Safety: Use TypeScript for better type safety.
  5. Cache Management: Use purchase caching to reduce API calls and improve performance.
  6. Middleware: Set up authentication middleware to extract user information early.

Next Steps