Pagamenti x402

In questa pagina

Il pacchetto @emdash-cms/x402 aggiunge il supporto del protocollo di pagamento x402 a qualsiasi sito Astro su Cloudflare. Funziona in modo autonomo — senza dipendere dal core di EmDash — e si integra bene con i campi CMS di EmDash per prezzi per pagina.

x402 è un protocollo di pagamento nativo HTTP. Quando un client richiede una risorsa a pagamento senza aver pagato, il server risponde con 402 Payment Required e istruzioni di pagamento leggibili da macchina. Agent e browser che capiscono x402 possono completare il pagamento automaticamente e ripetere la richiesta.

Quando usarlo

Il caso d’uso più comune è la modalità solo bot: far pagare agent IA e scraper per l’accesso ai contenuti, lasciando i visitatori umani leggere gratis. Si usa Cloudflare Bot Management per distinguere bot e umani.

Puoi anche imporre il pagamento a tutti i visitatori, oppure controllare solo la presenza degli header di pagamento senza imporre nulla (rendering condizionale).

Installazione

pnpm

pnpm add @emdash-cms/x402

npm

npm install @emdash-cms/x402

yarn

yarn add @emdash-cms/x402

Configurazione

Aggiungi l’integrazione alla config di Astro:

import { defineConfig } from "astro/config";
import { x402 } from "@emdash-cms/x402";

export default defineConfig({
	integrations: [
		x402({
			payTo: "0xYourWalletAddress",
			network: "eip155:8453", // Base mainnet
			defaultPrice: "$0.01",
			botOnly: true,
			botScoreThreshold: 30,
		}),
	],
});

Aggiungi il riferimento ai tipi così TypeScript conosce Astro.locals.x402:

/// <reference types="@emdash-cms/x402/locals" />

Utilizzo base

L’integrazione espone un enforcer su Astro.locals.x402. Chiama enforce() nel frontmatter della pagina per proteggere i contenuti con il pagamento:

---
const { x402 } = Astro.locals;

const result = await x402.enforce(Astro.request, {
  price: "$0.05",
  description: "Articolo premium",
});

// Senza pagamento valido, enforce() restituisce una Response 402.
// Restituiscila direttamente per inviare le istruzioni al client.
if (result instanceof Response) return result;

// Pagamento verificato (o saltato in modalità botOnly). Applica gli header
// di risposta per la prova di regolamento.
x402.applyHeaders(result, Astro.response);
---

<article>
  <h1>Contenuto premium</h1>
</article>

Il metodo enforce() restituisce:

  • una Response (402) — il client deve pagare; restituiscila direttamente.
  • un EnforceResult — la richiesta può procedere. Il contenuto è stato pagato oppure l’applicazione è stata saltata (umano in botOnly).

Modalità solo bot

Con botOnly impostato a true, l’integrazione legge request.cf.botManagement.score:

  • Punteggio sotto la soglia (predefinita 30) → trattato come bot, pagamento richiesto
  • Punteggio alla soglia o superiore → trattato come umano, controllo saltato
  • Nessun dato Bot Management (dev locale, non Cloudflare) → trattato come umano

EnforceResult include il flag skipped per distinguere «non doveva pagare» da «ha pagato»:

---
const result = await x402.enforce(Astro.request, { price: "$0.01" });
if (result instanceof Response) return result;

x402.applyHeaders(result, Astro.response);

// result.paid    — true se il pagamento è stato verificato
// result.skipped — true se il controllo è stato saltato (umano in botOnly)
// result.payer   — indirizzo wallet del pagante (se pagato)
---

Prezzo per pagina con EmDash

Con EmDash puoi aggiungere un campo number alla collection per il prezzo per pagina. Nessuno schema speciale o UI admin dedicata — un normale campo CMS:

---
import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;
const { entry } = await getEmDashEntry("posts", slug);

if (!entry) return Astro.redirect("/404");

const { x402 } = Astro.locals;

const result = await x402.enforce(Astro.request, {
  price: entry.data.price || "$0.01",
  description: entry.data.title,
});
if (result instanceof Response) return result;

x402.applyHeaders(result, Astro.response);
---

<article>
  <h1>{entry.data.title}</h1>
</article>

Verificare il pagamento senza imporlo

Usa hasPayment() per controllare se la richiesta include header di pagamento, senza verificare o imporre. Utile per rendering condizionale — contenuti diversi per visitatori che hanno pagato e per chi no:

---
const { x402 } = Astro.locals;

const hasPaid = x402.hasPayment(Astro.request);
---

{hasPaid ? (
  <p>Qui il contenuto premium completo.</p>
) : (
  <p>Abbonati per l’articolo completo.</p>
)}

Riferimento configurazione

OpzioneTipoPredefinitoDescrizione
payTostringobbligatorioIndirizzo wallet di destinazione
networkstringobbligatorioIdentificatore rete CAIP-2 (es. eip155:8453)
defaultPricePricePrezzo predefinito, sovrascrivibile per pagina
facilitatorUrlstringhttps://x402.org/facilitatorURL del facilitatore di pagamento
schemestring"exact"Schema di pagamento
maxTimeoutSecondsnumber60Timeout massimo per le firme di pagamento
evmbooleantrueAbilita supporto catene EVM
svmbooleanfalseAbilita Solana (richiede @x402/svm)
botOnlybooleanfalseRichiedi pagamento solo per i bot
botScoreThresholdnumber30Soglia score bot (1–99, più basso = più probabile bot)

Formato prezzi

I prezzi possono essere specificati in diversi modi:

  • Stringa in dollari"$0.10" (il prefisso $ viene rimosso, il valore passa così com’è)
  • Stringa numerica"0.10"
  • Numero0.10
  • Oggetto{ amount: "100000", asset: "0x...", extra: {} } per asset/importo espliciti

Identificatori di rete

Le reti usano il formato CAIP-2:

ReteIdentificatore
Base mainneteip155:8453
Base Sepoliaeip155:84532
Ethereumeip155:1
Solanasolana:mainnet

Opzioni di enforce

Sovrascrivi i default della config per una pagina specifica:

await x402.enforce(Astro.request, {
	price: "$0.25",
	payTo: "0xDifferentWallet",
	network: "eip155:1",
	description: "Articolo: come funziona x402",
	mimeType: "text/html",
});

Supporto Solana

Solana è facoltativo. Installa @x402/svm e abilitalo nella config:

pnpm add @x402/svm
x402({
	payTo: "YourSolanaAddress",
	network: "solana:mainnet",
	svm: true,
	evm: false,
});

Come funziona

  1. L’integrazione x402() registra middleware che crea un enforcer e lo mette su Astro.locals.x402
  2. La configurazione arriva al middleware tramite un modulo virtuale Vite (virtual:x402/config)
  3. Quando chiami enforce(), viene controllato l’header payment-signature sulla richiesta
  4. Senza header di pagamento, viene restituita una risposta 402 Payment Required con istruzioni nell’header PAYMENT-REQUIRED
  5. Con header di pagamento, questo viene verificato tramite il facilitatore e regolato
  6. Dopo il regolamento, applyHeaders() imposta gli header PAYMENT-RESPONSE sulla risposta

Il server delle risorse si inizializza in modo lazy alla prima richiesta e viene messo in cache per la durata del worker.