Reference@t402/fastify

@t402/fastify

Fastify plugin for protecting routes with t402 payment requirements. Uses Fastify’s high-performance hook system for efficient payment processing.

Installation

npm install @t402/fastify @t402/core

Quick Start

import Fastify from 'fastify';
import { paymentPlugin } from '@t402/fastify';
import { t402ResourceServer, createFacilitatorClient } from '@t402/core/server';
import { registerExactEvmScheme } from '@t402/evm/exact/server';
 
const fastify = Fastify();
 
// Create and configure the t402 server
const facilitator = createFacilitatorClient({ url: 'https://facilitator.t402.io' });
const server = new t402ResourceServer(facilitator);
registerExactEvmScheme(server, {});
 
// Define protected routes
const routes = {
  '/api/premium/*': {
    accepts: {
      scheme: 'exact',
      network: 'eip155:8453',
      payTo: '0xYourAddress',
      price: '$0.01',
    },
    description: 'Premium API access',
  },
};
 
// Register plugin
await fastify.register(paymentPlugin, { routes, server });
 
// Protected route
fastify.get('/api/premium/data', async (request, reply) => {
  return { data: 'Premium content' };
});
 
await fastify.listen({ port: 3000 });

API Reference

paymentPlugin

Fastify plugin that adds payment protection using preHandler hooks.

const paymentPlugin: FastifyPluginAsync<PaymentPluginOptions>

Options

OptionTypeDescription
routesRoutesConfigRoute configurations for protected endpoints
servert402ResourceServerPre-configured server instance
paywallConfigPaywallConfigOptional paywall UI configuration
paywallPaywallProviderOptional custom paywall provider
syncFacilitatorOnStartbooleanSync with facilitator on startup (default: true)

Example

import { paymentPlugin } from '@t402/fastify';
 
await fastify.register(paymentPlugin, {
  routes,
  server,
  paywallConfig: {
    title: 'Premium API',
    theme: 'dark',
  },
});

paymentPluginFromConfig

Fastify plugin with simple configuration (server created internally).

const paymentPluginFromConfig: FastifyPluginAsync<PaymentPluginFromConfigOptions>

Options

OptionTypeDescription
routesRoutesConfigRoute configurations for protected endpoints
facilitatorClientsFacilitatorClient | FacilitatorClient[]Facilitator client(s)
schemesSchemeRegistration[]Array of scheme registrations
paywallConfigPaywallConfigOptional paywall UI configuration
paywallPaywallProviderOptional custom paywall provider
syncFacilitatorOnStartbooleanSync with facilitator on startup (default: true)

Example

import { paymentPluginFromConfig } from '@t402/fastify';
import { createExactEvmServer } from '@t402/evm/exact/server';
 
await fastify.register(paymentPluginFromConfig, {
  routes,
  facilitatorClients: facilitatorClient,
  schemes: [{ network: 'eip155:8453', server: createExactEvmServer({}) }],
});

Route Configuration

const routes: RoutesConfig = {
  // Single price
  '/api/premium/*': {
    accepts: {
      scheme: 'exact',
      network: 'eip155:8453',
      payTo: '0xRecipient',
      price: '$0.01',
    },
    description: 'Premium API',
  },
 
  // Multiple networks
  '/api/multi/*': {
    accepts: [
      { scheme: 'exact', network: 'eip155:8453', payTo: '0x...', price: '$0.01' },
      { scheme: 'exact', network: 'ton:mainnet', payTo: 'EQ...', price: '$0.01' },
    ],
    description: 'Multi-chain support',
  },
};

Plugin Scoping

Use Fastify’s plugin system for scoped protection:

import Fastify from 'fastify';
import { paymentPlugin } from '@t402/fastify';
 
const fastify = Fastify();
 
// Public routes (no payment)
fastify.get('/api/public', async () => ({ public: true }));
 
// Premium routes (with payment)
fastify.register(async (instance) => {
  await instance.register(paymentPlugin, {
    routes: {
      '/*': {
        accepts: {
          scheme: 'exact',
          network: 'eip155:8453',
          payTo: '0xRecipient',
          price: '$0.01',
        },
        description: 'Premium content',
      },
    },
    server,
  });
 
  instance.get('/data', async () => ({ premium: true }));
  instance.get('/analytics', async () => ({ analytics: [] }));
}, { prefix: '/api/premium' });

Hook-Based Architecture

The plugin uses Fastify’s preHandler hook for payment verification:

// Internally, the plugin works like this:
fastify.addHook('preHandler', async (request, reply) => {
  // 1. Check if route requires payment
  if (!httpServer.requiresPayment(context)) {
    return; // Continue to handler
  }
 
  // 2. Process payment
  const result = await httpServer.processHTTPRequest(context);
 
  // 3. Handle result
  switch (result.type) {
    case 'no-payment-required':
      return; // Continue
    case 'payment-error':
      reply.code(402).send(result.response);
      return;
    case 'payment-verified':
      // Continue, settle after response
      return;
  }
});

Settlement Flow

Payment settlement occurs after successful handler execution:

fastify.get('/api/premium/data', async (request, reply) => {
  const data = await fetchData();
  return { data }; // 200 -> Payment settles
});
 
fastify.get('/api/premium/error', async (request, reply) => {
  reply.code(500);
  return { error: 'Failed' }; // 500 -> No settlement
});

Settlement only occurs when your handler returns a successful response (status < 400). Error responses prevent payment settlement.

With TypeBox Schema

Combine with Fastify’s schema validation:

import { Type } from '@sinclair/typebox';
 
fastify.get('/api/premium/data', {
  schema: {
    response: {
      200: Type.Object({
        data: Type.String(),
        timestamp: Type.Number(),
      }),
    },
  },
}, async (request, reply) => {
  return {
    data: 'Premium content',
    timestamp: Date.now(),
  };
});

Paywall Configuration

await fastify.register(paymentPlugin, {
  routes,
  server,
  paywallConfig: {
    title: 'API Access Required',
    description: 'Pay to unlock this endpoint',
    logoUrl: 'https://example.com/logo.png',
    theme: 'auto',
  },
});

Custom Paywall Provider

import { PaywallProvider } from '@t402/core/server';
 
const customPaywall: PaywallProvider = {
  render: (paymentRequired, config) => {
    return `
      <!DOCTYPE html>
      <html>
        <head><title>${config?.title || 'Pay'}</title></head>
        <body>
          <h1>Payment Required</h1>
          <pre>${JSON.stringify(paymentRequired, null, 2)}</pre>
        </body>
      </html>
    `;
  },
};
 
await fastify.register(paymentPlugin, {
  routes,
  server,
  paywall: customPaywall,
});

Error Handling

fastify.setErrorHandler((error, request, reply) => {
  if (error.statusCode === 402) {
    // Payment-related error
    reply.code(402).send({
      error: 'Payment required',
      message: error.message,
    });
    return;
  }
 
  // Other errors
  reply.code(500).send({ error: 'Internal server error' });
});

Type Exports

// Server types
export { t402ResourceServer, t402HTTPResourceServer } from '@t402/core/server';
export type { PaywallProvider, PaywallConfig } from '@t402/core/server';
export { RouteConfigurationError } from '@t402/core/server';
 
// Protocol types
export type {
  PaymentRequired,
  PaymentRequirements,
  PaymentPayload,
  Network,
  SchemeNetworkServer,
} from '@t402/core/types';
 
// Plugin types
export type { PaymentPluginOptions, PaymentPluginFromConfigOptions } from '@t402/fastify';
 
// Adapter
export { FastifyAdapter } from '@t402/fastify';