Apps

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

Terminal
npm install @payweave/next zod

Full example

TypeScript
// 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.

TypeScript
// 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

TypeScript
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:

TypeScript
handler: async (_req, body, ctx) => {
  // ctx.transactionHash, ctx.payer, ctx.receipt
  return { ok: true, txHash: ctx.transactionHash };
}

Manual refund

TypeScript
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:

TypeScript
export const GET = payweave.charge(
  { price: '0.001', description: 'Custom' },
  async (_req) => NextResponse.json({ ok: true })
);
Set the 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.