Plugin PrestaShop
À quoi sert ce module
Section intitulée « À quoi sert ce module »Le module titreoepay est le pont entre une boutique PrestaShop 8 et l’orchestrateur Titreo E-Pay. Il ne contient aucune logique de paiement : il délègue tout au backend (création de la session, choix des instruments, débit cartes cadeaux + CB, remboursements). C’est un thin connector qui se contente de trois responsabilités :
- Créer une session de paiement côté API quand le client choisit Titreo E-Pay au checkout, puis le rediriger vers la page de paiement hébergée.
- Recevoir le webhook signé renvoyé par le backend et mettre à jour le statut de la commande PrestaShop en conséquence.
- Déclencher les remboursements (depuis le back-office ou depuis un changement d’état de commande).
Tout ce qui est apparence (logo, couleurs, polices), gestion d’équipe et suivi des transactions a migré dans la Console marchand : le plugin ne sert plus qu’à connecter la boutique à l’API. Pour situer cette brique dans l’ensemble, voir la vue d’ensemble de l’écosystème.
Le code vit dans plugins/prestashop/titreoepay/. Le module se déclare en version 0.2.0 (constructeur de titreoepay.php) ; le config.xml affiche encore 0.1.0 — un écart cosmétique à corriger.
plugins/prestashop/titreoepay/├── titreoepay.php classe module (install, hooks, callApi)├── config.xml manifest module├── controllers/front/redirect.php crée la session + redirige vers le checkout├── controllers/front/webhook.php reçoit les events signés, maj commande├── controllers/front/returnpayment.php page de retour client (+ polling)├── controllers/admin/AdminTitreoEpayRefundController.php refund depuis le BO├── cron/cleanup-pending.php purge des tables techniques├── views/js/return-poll.js polling JS de la page de retour├── views/templates/ panel BO, formulaire refund, page retour└── translations/error-codes.php libellés des codes d'erreurLa classe module
Section intitulée « La classe module »TitreoEpay (dans titreoepay.php) étend PaymentModule. Elle déclare trois contrôleurs front (redirect, returnpayment, webhook) et active le mode multi-devises par cases à cocher.
Ce que fait l’installation
Section intitulée « Ce que fait l’installation »La méthode install() crée deux tables techniques, enregistre les hooks, crée un état de commande personnalisé et initialise les clés de configuration.
| Table | Rôle |
|---|---|
*_titreoepay_pending |
Mapping id_cart → titreo_session_id → id_order. Pivot central : le webhook s’en sert pour retrouver le panier puis la commande. Clé primaire id_cart, contrainte unique sur titreo_session_id. |
*_titreoepay_event_seen |
Déduplication des événements webhook : un event_id déjà vu n’est pas rejoué. |
Hooks enregistrés :
| Hook | Usage |
|---|---|
paymentOptions |
Affiche le bouton « Payer avec Titreo E-Pay » au checkout. |
displayPaymentReturn |
Retour de paiement côté thème. |
actionOrderStatusPostUpdate |
Si la commande passe à l’état « Remboursé » dans le BO, déclenche un refund total via l’API. |
displayAdminOrderMainBottom / displayAdminOrder |
Affiche le panneau Titreo dans la page commande du BO (compatibilité 1.7.7+ et versions antérieures). |
install() crée aussi un état de commande dédié « litige ouvert » (installDisputedState(), clé TITREOEPAY_OS_DISPUTED) utilisé quand un événement de litige arrive, et initialise quatre clés de configuration (voir Configuration).
L’appel API mutualisé
Section intitulée « L’appel API mutualisé »La méthode publique callApi($method, $path, $body, $extraHeaders) centralise tous les appels HTTP sortants (cURL) vers le backend : elle ajoute l’en-tête Authorization: Bearer <API key> et renvoie ['ok', 'status', 'body']. Les contrôleurs redirect.php et returnpayment.php ont leur propre cURL inline (pour la création de session et le client-token) ; les autres passent par callApi.
Le flux de checkout
Section intitulée « Le flux de checkout »sequenceDiagram
participant C as "Client (navigateur)"
participant PS as "PrestaShop (module)"
participant API as "Backend Titreo E-Pay"
participant CO as "Checkout hébergé"
C->>PS: "Choisit Titreo E-Pay (hookPaymentOptions)"
C->>PS: "controller=redirect"
PS->>API: "POST /v1/sessions (Idempotency-Key)"
API-->>PS: "{ id: sessionId }"
PS->>PS: "INSERT titreoepay_pending (cart→session)"
PS->>API: "POST /v1/sessions/:id/client-token"
API-->>PS: "{ token }"
PS-->>C: "Redirect vers CHECKOUT_URL/:locale/c/:id?t=...&success=...&cancel=..."
C->>CO: "Paie (cartes cadeaux + CB)"
API->>PS: "POST controller=webhook (X-Titreo-Signature)"
PS->>PS: "validateOrder + UPDATE id_order"
CO-->>C: "Redirect vers returnpayment"
C->>PS: "controller=returnpayment"
PS-->>C: "order-confirmation (ou polling si webhook en retard)"
1. Sélection et redirection
Section intitulée « 1. Sélection et redirection »hookPaymentOptions() propose un bouton dont l’action pointe vers le contrôleur redirect. TitreoEpayRedirectModuleFrontController::postProcess() :
- vérifie que le panier est complet (client, adresses) ;
- calcule le montant total en centimes ;
- appelle
POST /v1/sessionsavecreference,totalAmount,currencyet un en-têteIdempotency-Keyaléatoire ; - enregistre le mapping
cart → sessiondanstitreoepay_pending(en supprimant d’abord toute entrée existante pour le même panier ou la même session, afin qu’une nouvelle tentative écrase la précédente) ; - demande un client-token court (
POST /v1/sessions/:id/client-token) destiné au navigateur ; - redirige vers la page de checkout hébergée :
{CHECKOUT_URL}/{locale}/c/{sessionId}?t={clientToken}&success={returnUrl}&cancel={returnUrl}La locale est forcée à fr ou en. Les URLs success/cancel repointent vers le contrôleur returnpayment de la boutique.
2. Réception du webhook
Section intitulée « 2. Réception du webhook »TitreoEpayWebhookModuleFrontController est le cœur de la synchronisation. C’est un endpoint serveur-à-serveur : il neutralise la redirection canonique de PrestaShop (canonicalRedirection() surchargée en no-op) pour éviter qu’un 302 ne casse la livraison.
Séquence de postProcess() :
- Vérification de signature (
verifySignature) : l’en-têteX-Titreo-Signaturecontientt=<timestamp>,v1=<hmac>. Le HMAC-SHA256 est calculé surtimestamp.payloadavec le webhook secret, comparé en temps constant (hash_equals). Les signatures de plus d’une heure sont rejetées (stale_signature). - Déduplication : si l’
event_idest déjà danstitreoepay_event_seen, réponse200 { duplicate: true }sans retraitement. - Résolution du panier :
findPending()retrouve la lignependingparsession_id. Si absente (course possible avecredirect.php), une attente de 1,5 s puis un retry. Toujours absente →202 unknown_session(le PSP re-livrera selon son outbox). - Dispatch par type d’événement (
dispatchEvent) :
| Événement / statut | Action sur la commande |
|---|---|
session.completed / COMPLETED |
validateOrder() (état PS_OS_PAYMENT), puis UPDATE id_order dans pending. Idempotent si la commande existe déjà. |
session.failed / session.expired / CANCELED |
Passe la commande en PS_OS_CANCELED + note. |
session.refunded / FULLY_REFUNDED |
Passe en PS_OS_REFUND + note. |
session.partially_refunded / PARTIALLY_REFUNDED |
Note de remboursement partiel ; bascule en PS_OS_REFUND si le cumulé atteint le total. |
session.disputed |
Passe à l’état custom « litige ouvert » + note. |
*.action_required |
Note demandant une intervention manuelle. |
- Marquage vu : insertion de l’
event_iddansevent_seen(INSERT IGNORE), puis réponse200 { ok: true }.
3. Page de retour client
Section intitulée « 3. Page de retour client »returnpayment.php gère le retour du navigateur après le checkout. Deux cas :
- Webhook déjà arrivé : la ligne
pendinga unid_order→ redirection directe versorder-confirmation. - Webhook en retard : la page affiche
return.tplet lancereturn-poll.js, qui interrogeGET /v1/sessions/:idtoutes les 2 s (max 60 s, backoff sur erreur) avec un client-token fraîchement émis. Dès qu’un état terminal est détecté (COMPLETED,FAILED, etc.), le JS recharge la page (qui redirige alors versorder-confirmation) ou affiche un message d’erreur traduit issu du catalogue (errorMessageCatalog()/translations/error-codes.php).
Remboursement depuis le back-office
Section intitulée « Remboursement depuis le back-office »Deux chemins déclenchent un refund :
- Panneau commande (
AdminTitreoEpayRefundController) : un formulaire propose montant et stratégie (all,gift_first,cb_first) et envoiePOST /v1/sessions/:id/refundavec uneIdempotency-Keydérivée de la commande. - Changement d’état : passer la commande à l’état « Remboursé » déclenche
hookActionOrderStatusPostUpdate, qui lance un refund total (strategy: all).
Configuration du module
Section intitulée « Configuration du module »Configurée dans la page module du BO (getContent() / renderForm()). Quatre clés stockées via Configuration :
| Clé | Rôle | Valeur par défaut à l’install |
|---|---|---|
TITREOEPAY_API_BASE_URL |
Base de l’API backend. | http://api:3000 |
TITREOEPAY_API_KEY |
Clé API marchand (tep_…), en-tête Authorization. |
vide (à renseigner) |
TITREOEPAY_WEBHOOK_SECRET |
Secret HMAC de vérification des webhooks. | vide |
TITREOEPAY_CHECKOUT_URL |
Base de la page de paiement hébergée. | http://localhost:3100 |
API base URL et API key sont requis. Le webhook ne traite aucun événement si le secret est absent (réponse 500 webhook_secret_missing).
Maturité et ce qui reste à faire
Section intitulée « Maturité et ce qui reste à faire »| Aspect | État |
|---|---|
| Flux checkout hébergé | Fonctionnel (redirect + retour + polling). |
| Webhook signé + idempotence | Fonctionnel (HMAC, double déduplication). |
| Refund BO + par état de commande | Fonctionnel (stratégies all / gift_first / cb_first). |
| États de commande couverts | completed, failed/expired/canceled, refund total/partiel, litige. |
Restes connus :
- Versions désynchronisées :
titreoepay.phpannonce0.2.0,config.xmlreste à0.1.0. - Aucun test automatisé ; validation manuelle uniquement.
- Traductions partielles : seul
error-codes.phpest traduit ; les chaînes du module et des templates ne sont pas internationalisées de façon exhaustive. - Valeurs par défaut orientées dev local (
api:3000,localhost:3100) : à durcir avant production. - Cron non sécurisé en HTTP : prévu pour exécution CLI ; aucune protection par token si exposé via web.
- Pas de packaging Marketplace (validation PrestaShop, archive zip distribuable).
Pour le détail des contrats (signature webhook, format de session, en-têtes d’idempotence) partagés avec le backend, voir Contrats inter-briques et Sécurité. Le module est l’analogue PrestaShop des autres connecteurs décrits dans la vue d’ensemble de l’écosystème.