@t402/next
Next.js integration for protecting routes with t402 payment requirements. Supports both App Router middleware and API route wrappers.
Installation
npm install @t402/next @t402/coreQuick Start
// middleware.ts
import { paymentProxy } from '@t402/next';
import { t402ResourceServer, createFacilitatorClient } from '@t402/core/server';
import { registerExactEvmScheme } from '@t402/evm/exact/server';
const facilitator = createFacilitatorClient({ url: 'https://facilitator.t402.io' });
const server = new t402ResourceServer(facilitator);
registerExactEvmScheme(server, {});
const routes = {
'/api/premium/*': {
accepts: {
scheme: 'exact',
network: 'eip155:8453',
payTo: '0xYourAddress',
price: '$0.01',
},
description: 'Premium API',
},
};
export const middleware = paymentProxy(routes, server);
export const config = {
matcher: '/api/premium/:path*',
};API Reference
paymentProxy
Creates Next.js middleware with a pre-configured server instance.
function paymentProxy(
routes: RoutesConfig,
server: t402ResourceServer,
paywallConfig?: PaywallConfig,
paywall?: PaywallProvider,
syncFacilitatorOnStart?: boolean
): (req: NextRequest) => Promise<NextResponse>Parameters
| Parameter | Type | Description |
|---|---|---|
routes | RoutesConfig | Route configurations for protected endpoints |
server | t402ResourceServer | Pre-configured server instance |
paywallConfig | PaywallConfig | Optional paywall UI configuration |
paywall | PaywallProvider | Optional custom paywall provider |
syncFacilitatorOnStart | boolean | Sync with facilitator on startup (default: true) |
paymentProxyFromConfig
Creates Next.js middleware with simple configuration.
function paymentProxyFromConfig(
routes: RoutesConfig,
facilitatorClients?: FacilitatorClient | FacilitatorClient[],
schemes?: SchemeRegistration[],
paywallConfig?: PaywallConfig,
paywall?: PaywallProvider,
syncFacilitatorOnStart?: boolean
): (req: NextRequest) => Promise<NextResponse>withT402
Wraps an individual API route handler with payment protection.
function withT402<T>(
routeHandler: (request: NextRequest) => Promise<NextResponse<T>>,
routeConfig: RouteConfig,
server: t402ResourceServer,
paywallConfig?: PaywallConfig,
paywall?: PaywallProvider,
syncFacilitatorOnStart?: boolean
): (request: NextRequest) => Promise<NextResponse<T>>Parameters
| Parameter | Type | Description |
|---|---|---|
routeHandler | Function | The API route handler to wrap |
routeConfig | RouteConfig | Payment configuration for this route |
server | t402ResourceServer | Pre-configured server instance |
paywallConfig | PaywallConfig | Optional paywall UI configuration |
paywall | PaywallProvider | Optional custom paywall provider |
syncFacilitatorOnStart | boolean | Sync with facilitator on startup (default: true) |
Middleware vs Route Wrapper
Choose the appropriate approach:
| Feature | paymentProxy (Middleware) | withT402 (Route Wrapper) |
|---|---|---|
| Scope | Multiple routes | Single route |
| Configuration | Centralized in middleware.ts | Per-route in route files |
| Settlement timing | After middleware passes | After handler returns |
| Best for | Multiple protected routes | Individual API endpoints |
withT402 guarantees that settlement only occurs after the handler returns a successful response (status < 400), providing more precise control.
Middleware Configuration
Route Matching
// middleware.ts
import { paymentProxy } from '@t402/next';
const routes = {
'/api/premium/*': {
accepts: { scheme: 'exact', network: 'eip155:8453', payTo: '0x...', price: '$0.01' },
description: 'Premium API',
},
'/api/analytics': {
accepts: { scheme: 'exact', network: 'eip155:8453', payTo: '0x...', price: '$0.05' },
description: 'Analytics endpoint',
},
};
export const middleware = paymentProxy(routes, server);
// Configure which routes the middleware applies to
export const config = {
matcher: ['/api/premium/:path*', '/api/analytics'],
};Multi-Network Support
const routes = {
'/api/premium/*': {
accepts: [
{ scheme: 'exact', network: 'eip155:8453', payTo: '0x...', price: '$0.01' },
{ scheme: 'exact', network: 'ton:mainnet', payTo: 'EQ...', price: '$0.01' },
{ scheme: 'exact', network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', payTo: '8GG...', price: '$0.01' },
],
description: 'Multi-chain support',
},
};API Route Examples
Basic Route Protection
// app/api/premium/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withT402 } from '@t402/next';
async function handler(request: NextRequest) {
const data = await fetchPremiumData();
return NextResponse.json({ data });
}
export const GET = withT402(handler, {
accepts: {
scheme: 'exact',
network: 'eip155:8453',
payTo: '0xRecipient',
price: '$0.01',
},
description: 'Premium data endpoint',
}, server);Dynamic Pricing
// app/api/generate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withT402 } from '@t402/next';
async function handler(request: NextRequest) {
const { searchParams } = new URL(request.url);
const tokens = parseInt(searchParams.get('tokens') || '100');
const result = await generateContent(tokens);
return NextResponse.json({ result });
}
export const POST = withT402(handler, {
accepts: {
scheme: 'exact',
network: 'eip155:8453',
payTo: '0xRecipient',
price: '$0.001', // Base price
},
description: 'AI content generation',
extensions: {
bazaar: {
endpoint: '/api/pricing',
},
},
}, server);Multiple HTTP Methods
// app/api/resource/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withT402 } from '@t402/next';
const routeConfig = {
accepts: {
scheme: 'exact',
network: 'eip155:8453',
payTo: '0xRecipient',
price: '$0.01',
},
description: 'Resource API',
};
async function getHandler(request: NextRequest) {
return NextResponse.json({ data: 'read' });
}
async function postHandler(request: NextRequest) {
const body = await request.json();
return NextResponse.json({ created: body });
}
export const GET = withT402(getHandler, routeConfig, server);
export const POST = withT402(postHandler, routeConfig, server);Bazaar Extension
Enable dynamic pricing with the bazaar extension:
const routes = {
'/api/dynamic/*': {
accepts: {
scheme: 'exact',
network: 'eip155:8453',
payTo: '0xRecipient',
price: '$0.01',
},
description: 'Dynamic pricing endpoint',
extensions: {
bazaar: {
endpoint: 'https://api.example.com/pricing',
},
},
},
};
export const middleware = paymentProxy(routes, server);The bazaar extension is automatically loaded when routes declare it. No additional configuration needed.
Settlement Behavior
With withT402, settlement occurs after the handler returns:
async function handler(request: NextRequest) {
try {
const data = await riskyOperation();
return NextResponse.json({ data }); // 200 -> Settlement occurs
} catch (error) {
return NextResponse.json({ error: 'Failed' }, { status: 500 }); // 500 -> No settlement
}
}
export const GET = withT402(handler, routeConfig, server);Paywall Configuration
const paywallConfig = {
title: 'Premium API Access',
description: 'Pay to unlock this endpoint',
logoUrl: 'https://example.com/logo.png',
theme: 'dark',
};
export const middleware = paymentProxy(routes, server, paywallConfig);Type Exports
// Functions
export { paymentProxy, paymentProxyFromConfig, withT402 } from '@t402/next';
// Types
export type { SchemeRegistration } from '@t402/next';
export type {
PaymentRequired,
PaymentRequirements,
PaymentPayload,
Network,
SchemeNetworkServer,
} from '@t402/core/types';
export type { PaywallProvider, PaywallConfig, RouteConfig } from '@t402/core/server';
export { RouteConfigurationError } from '@t402/core/server';
// Adapter
export { NextAdapter } from '@t402/next';Related
- @t402/core - Core protocol types
- @t402/express - Express middleware
- @t402/hono - Hono middleware
- @t402/fastify - Fastify plugin