create-fedi-app
Modules

payment-gated

Optional module — lock pages behind Lightning invoices with HMAC-signed access cookies after payment verification.

Module ID at scaffold time: payment-gated-content.

Paywalled content without user accounts. Server creates a BOLT11 invoice, client pays via WebLN, server verifies preimage and sets an HMAC-signed cookie granting access.

Files added

FilePurpose
lib/payment-gate.tsCookie signing, route protection, invoice helpers
lib/payment-store.tsIn-memory payment record store
components/payment-gated/PayGate.tsxBlurred preview + pay UI
app/api/payment-gate/invoice/route.tsPOST — create invoice for contentId
app/api/payment-gate/verify/route.tsPOST — verify preimage, set cookie
app/demo/payment-gated/Demo article behind paywall
proxy.tsMiddleware replacing base — checks cookies on protected routes

Flow

1. User visits /demo/payment-gated/article
2. proxy.ts checks fedi-payment-token cookie → redirect to paywall if missing
3. PayGate fetches POST /api/payment-gate/invoice { contentId }
4. Server returns BOLT11 + paymentId
5. User pays via WebLN → preimage returned
6. Client POST /api/payment-gate/verify { paymentId, preimage, contentId }
7. Server verifies preimage, marks payment paid, sets signed cookie
8. User refreshes → proxy allows access

Environment variables

KeyRequiredDescription
PAYMENT_GATE_SECRETno (dev default)HMAC secret for cookie signing. Set in production.

API routes

POST /api/payment-gate/invoice

// Request
{ contentId: string; amountSats?: number }

// Response
{ paymentRequest: string; paymentId: string; amountSats: number }

POST /api/payment-gate/verify

// Request
{ paymentId: string; preimage: string; contentId: string }

// Response
{ success: true; token: string }

Also sets fedi-payment-token httpOnly cookie (30-day max age).

Protecting routes

Add entries to PROTECTED_ROUTES in lib/payment-gate.ts:

export const PROTECTED_ROUTES = [
  { path: '/premium/report', contentId: 'report-q1' },
] as const;

PayGate component

<PayGate
  contentId="demo-article"
  preview={<ArticlePreview />}
  amountSats={100}
/>

Shows blurred preview, invoice QR, WebLN pay button, and manual refresh link after payment.

Dev bypass

In development, x-mock-preimage header on verify route simulates payment without WebLN.

See also

On this page