import type { IDialect } from '@mostajs/data-plug'; import type { ProviderName } from '../lib/webhook-helpers.js'; import type { SettleWebhookOutput } from './checkout-service.js'; /** * CRÉER LA ROUTE QUI REÇOIT LA BANQUE. * * ── POURQUOI CETTE FABRIQUE EXISTE ────────────────────────────────────────── * Relevé dans le parc : **chaque application réécrivait sa route webhook**, et aucune ne la * réécrivait pareil. `octonet-cloud` en a deux (`webhook/` et `webhook-chargily/`) qui appellent * `verifyWebhook` à la main ; `booking-baloon` n'en a **aucune** — sa vente n'est close que si le * client revient sur la page de confirmation, donc un client qui ferme son navigateur après avoir * payé n'est jamais encaissé. * * Ce n'est pas de l'interface : rien n'est affiché, l'interlocuteur est une banque. La glu de * route vit donc **ici**, dans le moteur, et non dans `pay-panel-ui`. * * ── AGNOSTIQUE DU CADRE ───────────────────────────────────────────────────── * La fabrique rend une fonction `Request → Response` — les types du Web, présents en Node ≥ 18, * dans Next, dans Bun et dans Deno. Aucun `next/server`, aucun `fastify` : un module qui importe * un cadre force ce cadre à tous ses consommateurs. Les adaptateurs tiennent en une ligne : * * Next.js App Router : export const POST = createWebhookHandler({ dialect }) * fichier `app/api/paiement/webhook/[banque]/route.ts` — le segment suffit * Node/Fastify : app.post('/paiement/webhook/:banque', …) cf. `depuisCorpsBrut` * * ⚠️ **Le corps doit rester BRUT.** La signature est calculée sur les octets exacts ; un * `JSON.parse` suivi d'un `JSON.stringify` réordonne les clés et invalide une signature pourtant * authentique. C'est le défaut le plus fréquent des routes webhook, et il ne se voit qu'en * production. */ export interface WebhookHandlerOptions { /** Dialecte **d'ORM** (via `@mostajs/data-plug`) — celui qui persiste la ligne `Payment`. * PAS la banque : la banque, c'est `providerName`. */ dialect: IDialect; /** * Impose le fournisseur. Omis, il est déduit de l'en-tête de signature. * * À préciser dès que la route est dédiée à une banque : la déduction ne peut pas trancher entre * deux fournisseurs qui emploient le même en-tête (cf. `detecterFournisseur`). */ providerName?: ProviderName; /** * Le CHEMIN de la requête, quand l'appelant n'a pas d'objet `Request` (Fastify, Express, http * natif). `createWebhookHandler` le lit tout seul depuis `req.url`. * * Il sert au routage par segment — cf. `resoudreBanque`. */ chemin?: string; /** * Le métier, exécuté **après** que la `Payment` est réglée : activer un abonnement, libérer une * commande en cuisine, envoyer un reçu. * * ⚠️ **Une banque livre le même webhook plusieurs fois** — c'est son mécanisme de réessai, pas * une anomalie. Le règlement de la `Payment`, lui, est idempotent (mise à jour par `orderId`) ; * ce rappel ne l'est pas forcément. Une action non idempotente ici enverra deux reçus et * déduira deux fois le stock. À l'appelant de vérifier l'état avant d'agir. * * S'il **lève**, la réponse est un 500 : la banque réessaiera. Voir `Response` plus bas. */ onSettled?: (resultat: SettleWebhookOutput) => void | Promise; } /** * Le dernier segment d'un chemin, s'il désigne une banque connue — sinon `null`. * * `/paiement/webhook/chargily` → `'chargily'` · `/paiement/webhook` → `null` (« webhook » n'est * pas une banque) · `/paiement/webhook/strpie` → `null`, et l'appelant saura le dire. */ export declare function banqueDuChemin(chemin: string | undefined): ProviderName | null; /** * Déduit le fournisseur des en-têtes, ou rend `null`. * * Rend `null` plutôt que de choisir un défaut : un défaut ferait vérifier la signature avec le * secret d'une autre banque et rejeter un paiement réel. */ export declare function detecterFournisseur(headers: Headers | Record): ProviderName | null; export declare function depuisCorpsBrut(opts: WebhookHandlerOptions, body: string, headers: Headers | Record): Promise<{ statut: number; corps: Record; }>; /** * La route webhook, prête à monter. * * @example Next.js App Router — `app/api/paiement/webhook/route.ts` * export const POST = createWebhookHandler({ * dialect, * onSettled: async (r) => { if (r.status === 'paid') await activerAbonnement(r.orderId!) }, * }) * * @example Node nu / Fastify * app.post('/paiement/webhook', async (req, reply) => { * const { statut, corps } = await depuisCorpsBrut({ dialect }, req.rawBody, req.headers) * return reply.code(statut).send(corps) * }) */ export declare function createWebhookHandler(opts: WebhookHandlerOptions): (req: Request) => Promise; //# sourceMappingURL=webhook-handler.d.ts.map