Apps

Hono SDK

The @payweave/hono package works across every Hono runtime — Cloudflare Workers, Bun, Deno, Node.js. The recommended entry point is payweave.route(), which bundles validation, charging, auto-refund, and marketplace registration.

Installation

Terminal
npm install @payweave/hono zod

Full example

TypeScript
import { Hono } from 'hono';
import { Payweave } from '@payweave/hono';
import { z } from 'zod';

const app = new Hono();

// Cloudflare Workers: use fromEnv because env is only available at request time.
const payweave = Payweave.fromEnv(env => ({
  appId: env.PAYWEAVE_APP_ID,
  appSecret: env.PAYWEAVE_APP_SECRET,
  baseUrl: env.PAYWEAVE_API_URL,
}));

// Node / Bun / Deno: pass directly.
// const payweave = new Payweave(process.env.PAYWEAVE_APP_ID!, process.env.PAYWEAVE_APP_SECRET!);

const WeatherInput = z.object({ city: z.string() });

payweave.route(app, 'post', '/api/weather', {
  price: '0.001',
  description: 'Current weather for a city',
  inputSchema: WeatherInput,
  handler: async (_c, 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();
  },
});

payweave.mountSync(app);

export default app;

Dynamic pricing

The price option accepts a function that receives the Hono context. Useful for billing based on input size.

TypeScript
payweave.route(app, 'post', '/api/summarize', {
  price: async (c) => {
    const body = await c.req.json();
    return body.text.length > 5000 ? '0.01' : '0.002';
  },
  description: 'Text summarization',
  inputSchema: z.object({ text: z.string() }),
  handler: async (_c, body) => summarize(body.text),
});

Accessing payment metadata

After settlement, the charge middleware writes payment data onto the context. Handlers can read it via c.get(...):

TypeScript
handler: async (c, body) => {
  const txHash = c.get('payweaveTransactionHash'); // on-chain transaction hash
  const payer = c.get('payweavePayer');            // payer wallet address
  const receipt = c.get('payweaveReceipt');        // serialized Payment-Receipt
  return { txHash, payer };
}

Manual refund

For fine-grained control (e.g. different HTTP status per error kind), disable auto-refund and call payweave.refund() directly.

TypeScript
payweave.route(app, 'post', '/api/weather', {
  price: '0.001',
  refundOnError: false,           // opt out of auto-refund
  inputSchema: WeatherInput,
  handler: async (c, body) => {
    try {
      return await fetchWeather(body.city);
    } catch (err: any) {
      if (err.status === 429) {
        await payweave.refund(c.env, {
          transactionHash: c.get('payweaveTransactionHash')!,
          reason: 'upstream_rate_limited',
        });
        return c.json({ error: 'rate_limited', retry_after: 60 }, 429);
      }
      throw err; // propagates — framework handles
    }
  },
});

Low-level: payweave.charge()

For routes outside the route() helper (custom flows, streaming), use charge() as bare middleware. These routes aren't registered in the marketplace.

TypeScript
app.get(
  '/api/custom',
  payweave.charge({ price: '0.001', description: 'Custom' }),
  (c) => c.json({ ok: true })
);
Call payweave.mountSync(app) once after all routes are registered. It mounts GET /_payweave/sync, which the dashboard's Sync endpoints button pings to publish your manifest.