Paiements x402

Sur cette page

Le paquet @emdash-cms/x402 ajoute la prise en charge du protocole de paiement x402 à tout site Astro sur Cloudflare. Il fonctionne seul — sans dépendre du cœur d’EmDash — et s’accorde bien avec les champs CMS d’EmDash pour un prix par page.

x402 est un protocole de paiement natif HTTP. Lorsqu’un client demande une ressource payante sans paiement, le serveur répond par 402 Payment Required et des instructions de paiement lisibles par machine. Les agents et navigateurs qui comprennent x402 peuvent régler le paiement automatiquement et relancer la requête.

Quand l’utiliser

Le cas le plus courant est le mode réservé aux bots : faire payer les agents IA et scrapers pour l’accès au contenu, tout en laissant les visiteurs humains lire gratuitement. Cloudflare Bot Management sert à distinguer bots et humains.

Vous pouvez aussi exiger le paiement pour tous les visiteurs, ou seulement vérifier la présence d’en-têtes de paiement sans appliquer de contrainte (rendu conditionnel).

Installation

pnpm

pnpm add @emdash-cms/x402

npm

npm install @emdash-cms/x402

yarn

yarn add @emdash-cms/x402

Configuration

Ajoutez l’intégration à votre config 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,
		}),
	],
});

Ajoutez la référence de types pour que TypeScript connaisse Astro.locals.x402 :

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

Utilisation de base

L’intégration place un enforceur sur Astro.locals.x402. Appelez enforce() dans le frontmatter de la page pour protéger le contenu par paiement :

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

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

// Sans paiement valide, enforce() renvoie une Response 402.
// Retournez-la directement pour envoyer les instructions au client.
if (result instanceof Response) return result;

// Paiement vérifié (ou ignoré en mode botOnly). Appliquer les en-têtes
// pour la preuve de règlement côté client.
x402.applyHeaders(result, Astro.response);
---

<article>
  <h1>Contenu premium</h1>
</article>

La méthode enforce() renvoie soit :

  • une Response (402) — le client doit payer ; retournez-la directement.
  • un EnforceResult — la requête peut continuer. Le contenu a été payé, ou l’application du paiement a été ignorée (humain en mode botOnly).

Mode réservé aux bots

Lorsque botOnly vaut true, l’intégration lit request.cf.botManagement.score pour classer les requêtes :

  • Score sous le seuil (30 par défaut) → traité comme bot, paiement exigé
  • Score au seuil ou au-dessus → traité comme humain, contrôle ignoré
  • Pas de données Bot Management (dev local, hors Cloudflare) → traité comme humain

EnforceResult inclut un indicateur skipped pour distinguer « n’a pas eu besoin de payer » et « a payé » :

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

x402.applyHeaders(result, Astro.response);

// result.paid    — true si le paiement a été vérifié
// result.skipped — true si le contrôle a été ignoré (humain en botOnly)
// result.payer   — adresse du portefeuille payeur (si payé)
---

Prix par page avec EmDash

Avec EmDash, vous pouvez ajouter un champ number à votre collection pour le prix par page. Aucun schéma spécial ni UI admin dédiée — un simple champ 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>

Vérifier le paiement sans l’exiger

Utilisez hasPayment() pour savoir si la requête contient des en-têtes de paiement, sans vérification ni application. Utile pour un rendu conditionnel — contenu différent pour visiteurs payants et non payants :

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

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

{hasPaid ? (
  <p>Contenu premium complet ici.</p>
) : (
  <p>Abonnez-vous pour l’article complet.</p>
)}

Référence de configuration

OptionTypeDéfautDescription
payTostringobligatoireAdresse du portefeuille de destination
networkstringobligatoireIdentifiant réseau CAIP-2 (ex. eip155:8453)
defaultPricePricePrix par défaut, surchargeable par page
facilitatorUrlstringhttps://x402.org/facilitatorURL du facilitateur de paiement
schemestring"exact"Schéma de paiement
maxTimeoutSecondsnumber60Délai max pour les signatures de paiement
evmbooleantrueActiver le support des chaînes EVM
svmbooleanfalseActiver Solana (nécessite @x402/svm)
botOnlybooleanfalseN’exiger le paiement que pour les bots
botScoreThresholdnumber30Seuil du score bot (1–99, plus bas = plus bot)

Format des prix

Les prix peuvent être indiqués de plusieurs façons :

  • Chaîne en dollars"$0.10" (le préfixe $ est retiré, la valeur est transmise telle quelle)
  • Chaîne numérique"0.10"
  • Nombre0.10
  • Objet{ amount: "100000", asset: "0x...", extra: {} } pour actif / montant explicites

Identifiants réseau

Les réseaux utilisent le format CAIP-2 :

RéseauIdentifiant
Base mainneteip155:8453
Base Sepoliaeip155:84532
Ethereumeip155:1
Solanasolana:mainnet

Options de enforce

Surchargez les valeurs par défaut pour une page donnée :

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

Prise en charge de Solana

Solana est optionnel. Installez @x402/svm et activez-le dans la config :

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

Fonctionnement

  1. L’intégration x402() enregistre un middleware qui crée un enforceur et le place sur Astro.locals.x402
  2. La configuration est transmise au middleware via un module virtuel Vite (virtual:x402/config)
  3. Lors d’un appel à enforce(), la présence de l’en-tête payment-signature est vérifiée
  4. Sans en-tête de paiement, une réponse 402 Payment Required est renvoyée avec des instructions dans l’en-tête PAYMENT-REQUIRED
  5. Avec un en-tête de paiement, celui-ci est vérifié via le facilitateur puis réglé
  6. Après règlement, applyHeaders() définit les en-têtes PAYMENT-RESPONSE sur la réponse

Le serveur de ressources est initialisé à la demande lors du premier appel et mis en cache pour la durée de vie du worker.