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.
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!,
})
// 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 route-wrapper helpers that handle auth, body parsing, error formatting, and cache invalidation. Every wrapper returns Promise<NextResponse> — return it directly from your route handler.
Breaking change (SDK 1.1): wrappers such as checkPurchase, createPaymentIntent, processPaymentIntent, activatePlan, cancelRenewal, reactivateRenewal, createCheckoutSession, createCustomerSession, syncCustomer, listPlans, getMerchant, getProduct, getPaymentMethod, getCustomerBalance, and trackUsage now always return Promise<NextResponse>. Earlier versions returned NextResponse | data, so if you still have result instanceof NextResponse ? result : NextResponse.json(result) in your handlers, delete that branch.Unchanged: getAuthenticatedUser, getCustomerReference, and syncCustomer helpers that return raw user/customer data stay as-is.
// app/api/check-purchase/route.ts
import { checkPurchase } from '@solvapay/next'
export async function GET(request: Request) {
return checkPurchase(request)
}
Available Helper Functions
Check Purchase
// app/api/check-purchase/route.ts
import { checkPurchase } from '@solvapay/next'
export async function GET(request: Request) {
return checkPurchase(request)
}
Create Payment Intent
// app/api/create-payment-intent/route.ts
import { createPaymentIntent } from '@solvapay/next'
export async function POST(request: Request) {
const { planRef, productRef } = await request.json()
return createPaymentIntent(request, { planRef, productRef })
}
Process Payment
// app/api/process-payment/route.ts
import { processPaymentIntent } from '@solvapay/next'
export async function POST(request: Request) {
const { paymentIntentId, productRef, planRef } = await request.json()
return processPaymentIntent(request, { paymentIntentId, productRef, planRef })
}
Create Checkout Session
// app/api/create-checkout-session/route.ts
import { createCheckoutSession } from '@solvapay/next'
export async function POST(request: Request) {
const { planRef, productRef } = await request.json()
return createCheckoutSession(request, { planRef, productRef })
}
Create Customer Portal Session
// app/api/create-customer-session/route.ts
import { createCustomerSession } from '@solvapay/next'
export async function POST(request: Request) {
return createCustomerSession(request)
}
Sync Customer
// app/api/sync-customer/route.ts
import { syncCustomer } from '@solvapay/next'
export async function POST(request: Request) {
return syncCustomer(request)
}
Cancel Renewal
// app/api/cancel-renewal/route.ts
import { cancelRenewal } from '@solvapay/next'
export async function POST(request: Request) {
const { purchaseRef, reason } = await request.json()
return cancelRenewal(request, { purchaseRef, reason })
}
Reactivate Renewal
// app/api/reactivate-renewal/route.ts
import { reactivateRenewal } from '@solvapay/next'
export async function POST(request: Request) {
const { purchaseRef } = await request.json()
return reactivateRenewal(request, { purchaseRef })
}
Activate Plan
// app/api/activate-plan/route.ts
import { activatePlan } from '@solvapay/next'
export async function POST(request: Request) {
const { productRef, planRef } = await request.json()
return activatePlan(request, { productRef, planRef })
}
Response body: { status, purchaseRef?, checkoutUrl?, creditBalance?, ... }. Usage-based plans now activate eagerly at the plan step (no balance gate). See Purchase lifecycle management for all status values.
List Plans
// app/api/list-plans/route.ts
import { listPlans } from '@solvapay/next'
export async function GET(request: Request) {
return listPlans(request)
}
Response body: { plans, productRef }. Pass ?productRef=prd_... as a query parameter.
Merchant, Product, and Payment Method
// app/api/merchant/route.ts
import { getMerchant } from '@solvapay/next'
export async function GET(request: Request) {
return getMerchant(request)
}
// app/api/product/route.ts
import { getProduct } from '@solvapay/next'
export async function GET(request: Request) {
return getProduct(request)
}
// app/api/payment-method/route.ts
import { getPaymentMethod } from '@solvapay/next'
export async function GET(request: Request) {
return getPaymentMethod(request)
}
getMerchant returns { name, iconUrl, logoUrl, termsUrl, privacyUrl, ... } — use it to brand checkout and mandate copy.
getProduct returns the product with public plans for the current authenticated customer.
getPaymentMethod returns { kind: 'card', brand, last4, expMonth, expYear } | { kind: 'none' }, mirrored from the last successful payment_intent.succeeded webhook. Safe to poll alongside check-purchase.
Customer Balance
// app/api/customer-balance/route.ts
import { getCustomerBalance } from '@solvapay/next'
export async function GET(request: Request) {
return getCustomerBalance(request)
}
Track Usage
// app/api/track-usage/route.ts
import { trackUsage } from '@solvapay/next'
export async function POST(request: Request) {
const body = await request.json()
return trackUsage(request, body)
}
Server Components
In Server Components, reach for the *Core primitives from @solvapay/server instead of the route-wrapper helpers — the wrappers always return NextResponse, which isn’t useful when you want to read data.
// app/dashboard/page.tsx
import { checkPurchaseCore, isErrorResult } from '@solvapay/server';
import { cookies } from 'next/headers';
import { solvaPay } from '@/lib/solvapay';
export default async function DashboardPage() {
const cookieStore = await cookies();
const headers = new Headers();
const authToken = cookieStore.get('auth-token');
if (authToken) {
headers.set('authorization', `Bearer ${authToken.value}`);
}
const request = new Request('http://localhost', { headers });
const result = await checkPurchaseCore(request, { solvaPay });
if (isErrorResult(result)) {
return <div>Error checking purchase</div>;
}
return (
<div>
<h1>Dashboard</h1>
{result.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';
import { supabase } from '@/lib/supabase';
export default function RootLayout({ children }: { children: React.ReactNode }) {
// Optional: Use Supabase auth adapter
const supabaseAdapter = createSupabaseAuthAdapter({ client: supabase });
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 — each is a one-liner because every wrapper returns Promise<NextResponse>:
// app/api/check-purchase/route.ts
import { checkPurchase } from '@solvapay/next'
export async function GET(request: Request) {
return checkPurchase(request)
}
// app/api/create-payment-intent/route.ts
import { createPaymentIntent } from '@solvapay/next'
export async function POST(request: Request) {
const { planRef, productRef } = await request.json()
return createPaymentIntent(request, { planRef, productRef })
}
// app/api/process-payment/route.ts
import { processPaymentIntent } from '@solvapay/next'
export async function POST(request: Request) {
const { paymentIntentId, productRef, planRef } = await request.json()
return processPaymentIntent(request, { paymentIntentId, productRef, planRef })
}
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
├── cancel-renewal/
│ └── route.ts
├── reactivate-renewal/
│ └── route.ts
├── activate-plan/
│ └── route.ts
└── list-plans/
└── route.ts
lib/
└── solvapay.ts
proxy.ts
Root Layout
// app/layout.tsx
import { SolvaPayProvider } from '@solvapay/react';
import { createSupabaseAuthAdapter } from '@solvapay/react-supabase';
import { supabase } from '@/lib/supabase';
export default function RootLayout({ children }: { children: React.ReactNode }) {
const supabaseAdapter = createSupabaseAuthAdapter({ client: supabase });
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!,
})
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
-
Use Environment Variables: Store API keys and configuration in
.env.local.
-
Separate API Routes: Keep API routes separate from page routes for better organization.
-
Error Handling: Helpers always return
NextResponse (including error responses). For Server Component / RSC code paths where you need raw data, use the *Core primitives from @solvapay/server and check with isErrorResult.
-
Type Safety: Use TypeScript for better type safety.
-
Cache Management: Use purchase caching to reduce API calls and improve performance.
-
Middleware: Set up authentication middleware to extract user information early.
Next Steps