Next.js SDK
The @payweave/next package wraps Next.js App Router route handlers. Because Next routes can't be registered programmatically, the shape differs slightly from other adapters — you call payweave.route() and export the returned handler.
Installation
npm install @payweave/next zodFull example
// app/api/weather/route.ts
import { Payweave } from '@payweave/next';
import { z } from 'zod';
const payweave = new Payweave(
process.env.PAYWEAVE_APP_ID!,
process.env.PAYWEAVE_APP_SECRET!
);
const WeatherInput = z.object({ city: z.string() });
const r = payweave.route({
method: 'POST',
path: '/api/weather', // required for marketplace sync
price: '0.001',
description: 'Current weather for a city',
inputSchema: WeatherInput,
handler: async (_req, body) => {
// body: { city: string } — typed from Zod
const res = await fetch(`https://api.weather/?q=${body.city}`);
if (!res.ok) throw new Error('weather_upstream_error');
return await res.json();
},
});
export const POST = r.handler;Sync endpoint
Create a dedicated route file for the sync trigger. The dashboard's Sync endpoints button calls it to publish your routes to the marketplace.
// app/_payweave/sync/route.ts
import { payweave } from '@/lib/payweave';
export const GET = payweave.mountSync();The path option is required for sync
Next routes are file-based, so the SDK can't infer the path. Pass path: '/api/weather' explicitly. Without it, the route works normally but isn't registered in the marketplace manifest.
Dynamic pricing
const r = payweave.route({
method: 'POST',
path: '/api/summarize',
price: async (req) => {
const body = await req.clone().json();
return body.text.length > 5000 ? '0.01' : '0.002';
},
description: 'Text summarization',
inputSchema: z.object({ text: z.string() }),
handler: async (_req, body) => summarize(body.text),
});
export const POST = r.handler;Accessing payment metadata
The handler's third argument is a ctx with payment fields set after settlement:
handler: async (_req, body, ctx) => {
// ctx.transactionHash, ctx.payer, ctx.receipt
return { ok: true, txHash: ctx.transactionHash };
}Manual refund
const r = payweave.route({
method: 'POST',
path: '/api/weather',
price: '0.001',
refundOnError: false,
inputSchema: WeatherInput,
handler: async (_req, body, ctx) => {
try {
return await fetchWeather(body.city);
} catch (err: any) {
if (err.status === 429) {
await payweave.refund({
transactionHash: ctx.transactionHash!,
reason: 'upstream_rate_limited',
});
return NextResponse.json(
{ error: 'rate_limited', retry_after: 60 },
{ status: 429 }
);
}
throw err;
}
},
});
export const POST = r.handler;Low-level: payweave.charge()
The Next adapter's charge() takes both options and the handler function — it wraps the handler in the charge middleware:
export const GET = payweave.charge(
{ price: '0.001', description: 'Custom' },
async (_req) => NextResponse.json({ ok: true })
);path field on every route() call. It's the only way the SDK can tell the platform where your route lives — without it, the endpoint works but won't show up when you click Sync endpoints.