Aller au contenu

console-merchant

console-merchant/ est le back-office self-service du marchand. Une fois intégré à Titreo E-Pay, un marchand doit pouvoir gérer son intégration sans ticket de support : brancher son PSP (Stripe, PayPlug…), générer ses clés API, configurer son webhook, suivre ses transactions, rembourser, et inviter son équipe. Cette console est l’interface qui rend tout ça possible.

C’est une brique cliente : elle ne contient aucune logique métier de paiement. Elle est une façade web par-dessus l’API backend. Chaque écran lit ou écrit via les endpoints /v1/merchants/me* et /v1/sessions* du Backend ; l’autorité reste côté API.

Concrètement le marchand y gère lui-même :

Domaine Ce qu’il peut faire Endpoint API sous-jacent
PSP Configurer / mettre à jour / supprimer ses clés Stripe, Adyen, PayPlug /v1/merchants/me/psp-configs[/:provider]
Clés API Lister, créer une clé (avec scopes) /v1/merchants/me/api-keys
Webhook URL + secret de notification /v1/merchants/me (champs webhookUrl, webhookSecret)
Livraisons webhook Lister, inspecter le payload, rejouer (replay) /v1/merchants/me/webhook-deliveries[/:id][/replay]
Sessions Lister, voir le détail, rembourser /v1/sessions[/:id][/refund]
Apparence Logo + tokens de thème (couleur d’accent…) /v1/merchants/me/branding
Équipe Inviter, changer de rôle, désactiver un membre /v1/merchants/me/users[/invites]
Compte Nom, TTL de session /v1/merchants/me

Next.js 15 (App Router, React 19, Server Components + Server Actions), TypeScript strict, Tailwind, validation Zod, lint via Biome. Aucune dépendance HTTP custom : tout passe par fetch côté serveur dans console-merchant/lib/api.ts.

Scripts réels (console-merchant/package.json) :

Script Commande Effet
dev next dev -p 3500 Serveur de dev sur le port 3500
build next build Build de production
start next start -p 3500 Serveur de production sur le port 3500
typecheck tsc --noEmit Vérification de types
lint biome check . Lint
lint:fix biome check --write . Lint + autofix

Configuration (console-merchant/.env.example) :

Fenêtre de terminal
TITREO_API_BASE_URL=http://localhost:3000
NODE_ENV=development

TITREO_API_BASE_URL est l’URL du backend. Lue à un seul endroit (console-merchant/lib/config.ts), valeur par défaut http://localhost:3000. Voir Environnement de dev local.

Un Dockerfile multi-stage (deps / builder / runner) est présent : il s’appuie sur pnpm + un workspace racine (pnpm-workspace.yaml) et expose le port 3500. Lancement via pnpm start.

Le marchand se connecte avec email + mot de passe. La console échange ces identifiants contre un cookie de session opaque qu’elle relaie ensuite à l’API à chaque appel.

sequenceDiagram
    participant U as "Navigateur"
    participant C as "Console (Server Action)"
    participant A as "API Titreo E-Pay"
    U->>C: "POST login (email, password)"
    C->>A: "POST /v1/auth/login"
    A-->>C: "Set-Cookie: tep_session=..."
    C->>C: "Pose cookie tep_session (httpOnly, Secure, SameSite=strict)"
    C-->>U: "Redirige /onboarding ou /dashboard"
    Note over U,A: "Appels suivants : la console relaie tep_session vers l'API"

Points clés du flux d’auth (console-merchant/lib/actions/auth.ts, lib/api.ts, middleware.ts) :

  • Le cookie tep_session est la credential porteuse vers l’API. Il est posé httpOnly, SameSite=strict et toujours Secure (même en dev sur localhost, qui est un contexte sécurisé) — jamais conditionné à NODE_ENV.
  • middleware.ts protège tout sauf les chemins publics (/login, /signup, /verify-email, /accept-invite) : en l’absence du cookie, redirection vers /login.
  • Le même middleware émet un en-tête CSP (Content-Security-Policy) avec nonce par requête, en mode Report-Only par défaut (CSP_REPORT_ONLY=false pour passer en enforce).
  • getCurrentUser() (lib/auth.ts) appelle /v1/auth/me ; requireMerchant() redirige vers /login si l’utilisateur n’est pas un marchand. Voir Sécurité.

Les appels API sont toujours faits côté serveur (Server Actions / RSC). Le navigateur ne parle qu’à la console ; il ne voit jamais l’URL de l’API ni le token en clair côté client.

Le routing App Router se découpe en trois zones, dont deux groupes de routes ((authed), (onboarding)) qui partagent chacun un layout sans apparaître dans l’URL.

app/
page.tsx -> redirige vers /dashboard
login/ public
signup/ public
verify-email/ public (vérification email post-signup)
accept-invite/ public (acceptation d'invitation équipe)
(authed)/ layout protégé + Sidebar
dashboard/
sessions/ [id]/ liste + détail + bouton remboursement
psp-configs/ [provider]/
api-keys/
webhooks/ deliveries/ [id]/ réglage + livraisons + replay
branding/
users/ équipe (invitations, rôles)
settings/
(onboarding)/
onboarding/ wizard + layout barre de progression
psp/ api-key/ webhook/ branding/ plugin/ done/

Le (authed)/layout.tsx impose requireMerchant() et affiche le Sidebar. La navigation est filtrée par rôle : chaque entrée du menu porte un scope requis, masqué si le rôle ne le possède pas (components/Sidebar.tsx + lib/permissions.ts).

Trois rôles marchands, avec des scopes synthétisés côté UI pour masquer/désactiver les contrôles. C’est un miroir de la logique d’autorisation de l’API (api/src/http/plugins/auth.ts:scopesForMerchantRole) ; l’API reste l’autorité, la console ne fait que cacher ce qui serait de toute façon refusé.

Rôle Libellé Périmètre
owner Propriétaire Tout : business + technique
developer Développeur Sessions, PSP, webhooks, clés API, branding (lecture)
ops Opérations Sessions (dont remboursement), webhooks, équipe (lecture), branding (lecture)

C’est la pièce centrale du parcours marchand. Au premier login, si l’onboarding n’est pas complet (/v1/merchants/me/onboarding renvoie complete: false), la console redirige vers /onboarding au lieu du dashboard.

flowchart LR
    PSP["1. PSP"] --> KEY["2. Clé API"]
    KEY --> WH["3. Webhook"]
    WH --> BR["4. Apparence"]
    BR --> PL["5. Plugin CMS"]
    PL --> DONE["Terminé"]

L’ordre des étapes est défini en dur dans console-merchant/lib/onboarding-steps.ts (WIZARD_STEPS = ['psp', 'api-key', 'webhook', 'branding', 'plugin']). La page index (onboarding/page.tsx) interroge l’état et redirige vers la première étape non terminée, ce qui permet de reprendre l’onboarding où il s’était arrêté. La barre de progression ((onboarding)/onboarding/layout.tsx) calcule l’avancement depuis les drapeaux done renvoyés par l’API.

Étape Slug Ce que fait l’action serveur État API
PSP psp PUT /v1/merchants/me/psp-configs/:provider (clé secrète + secret webhook) pspConfigured
Clé API api-key POST /v1/merchants/me/api-keys (scopes sessions:read/write, préfixe tep_live_) — le token n’est montré qu’une fois apiKeyCreated
Webhook webhook PUT /v1/merchants/me (webhookUrl + webhookSecret) webhookConfigured
Apparence branding PUT /v1/merchants/me/branding (logo, token --accent) brandingSet
Plugin CMS plugin Aucune écriture : instructions d’installation par CMS pluginInstalled

Pour une passation honnête, voici la distinction sans fard.

Réel et branché sur l’API :

  • Auth complète : login, signup (avec lien de vérification), vérification email, accept-invite, logout — toutes via /v1/auth/*.
  • CRUD PSP, clés API, webhook, branding, équipe (invitations / rôles / désactivation) — vrais endpoints /v1/merchants/me*.
  • Sessions : liste paginée par curseur, détail, remboursement (POST /v1/sessions/:id/refund) avec garde de statut remboursable (lib/sessions-helpers.ts).
  • Livraisons webhook : liste, détail du payload, replay (POST .../replay).
  • Wizard d’onboarding piloté par l’état réel renvoyé par l’API.
  • Garde de sécurité côté serveur sur le branding : ré-validation Zod (tokens CSS, logo) car les Server Actions sont des POST directement appelables (lib/actions/branding.ts).

Esquisse / non finalisé :

  • Aucun test (ni unitaire ni e2e).
  • Design system réduit à components/ui.tsx ; pas de bundle design intégré.
  • Confirmation « plugin installé » non vérifiée (lien manuel).
  • Branding produit ici mais pas encore consommé par une surface de checkout dans cette brique.
  • Pas de gestion d’erreur réseau riche (états de chargement / retry minimalistes).