Lecteur Markdown
PAYMENT_DOCUMENTATION_FR
Plugin : Payment #
Page de retour de paiement (succès, annulation, attente). Réceptionne l'utilisateur après checkout Stripe / PayPal / virement / chèque, vérifie le statut, affiche la confirmation et envoie l'email récapitulatif.
---
Rôle #
Le plugin `payment` n'initie aucun paiement — il reçoit les retours :
| Provider | URL de retour | Action |
|----------|---------------|--------|
| Stripe | `?obj=payment.php&order=NUM&session_id=cs_...` | Vérification synchrone de la session, marque l'order `paid` si Stripe confirme |
| PayPal | `?obj=payment.php&order=NUM&token=...` | Capture du paiement via `Payment::handleCallback('paypal', …)` |
| Virement | `?obj=payment.php&order=NUM` | Affiche IBAN/BIC + référence commande |
| Chèque | `?obj=payment.php&order=NUM` | Affiche l'adresse postale + libellé |
| Annulation | `?obj=payment.php&action=cancel&order=NUM` | Restaure le panier, redirige vers `products_checkout.php` |
L'initiation des paiements est gérée en amont par `products_checkout` (panier classique), `abo_checkout` (premier abonnement) et `reabo_checkout` (réabonnement).
---
Architecture HT / TTC #
| Couche | Format |
|--------|--------|
| `products.price`, `products.subscription_price` (DB) | HT |
| `order_items.unit_price_ht`, `unit_price_recurring_ht` (DB) | HT |
| Affichage utilisateur (panier, fiche, paiement) | TTC |
| API Stripe/PayPal `unit_amount` (cents) | TTC (calculé via `vat_rate`) |
| Saisie admin éditeur | TTC via toggle (mais stocké HT) |
| B2B VIES zero-rated | passer `vat_rate=0` au moment du checkout |
Tous les montants envoyés aux providers sont calculés via `bcmul(number_format(HT × (1 + vat/100), 2, '.', ''), '100', 0)` pour éviter les arrondis flottants.
---
Flux Stripe — vérification synchrone au retour #
Stripe redirige sur `success_url` avant que le webhook n'ait toujours fini de traiter. Sans vérification synchrone, l'utilisateur voyait "paiement en cours de traitement" alors que tout était OK.
Stripe Checkout → success_url (payment.php?session_id=...)
↓
payment.php : Payment::verifySession('stripe', $sessionId, $orderId)
↓
PaymentStripe::verifySession()
GET /v1/checkout/sessions/{id}
Vérifie : status='complete' ET payment_status IN ('paid','no_payment_required')
↓
Si OK → Payment::updateStatus($orderId, 'paid', $paymentIntent, 'Stripe session verified on return')
↓
Affichage de la confirmation
Le webhook reste la source de vérité de secours (idempotent grâce à `Payment::updateStatus`). Si la vérification synchrone échoue (réseau, Stripe lent), le webhook prendra le relais et l'utilisateur verra "en cours de traitement" puis pourra rafraîchir.
---
Affichage du total avec abonnement #
Pour les commandes mixtes (produits one-time + abonnement), `orders.total_ttc` ne contient que la part one-time. Le premier versement de l'abonnement est facturé par Stripe via une `line_item` séparée.
L'affichage calcule donc :
$paidTodayTtc = (float)$order['total_ttc'] + $recurringTtc;
avec `$recurringTtc = Σ (unit_price_recurring_ht × (1 + vat_rate/100) × quantity)` sur les `order_items` où `is_subscription = 1`.
Trois lignes affichées :
- Total payé aujourd'hui : `$paidTodayTtc`
- Dont produits : `$order['total_ttc']`
- Dont premier versement abonnement : `$recurringTtc`
- Puis : `$recurringTtc / mois` (récurrence)
---
Email de confirmation #
Envoyé une seule fois par commande (flag `orders.email_sent`). Contient :
- Récapitulatif lignes (nom, qty, ligne TTC)
- Sous-total HT + TVA + total TTC
- Adresses facturation/livraison
- Instructions virement/chèque si applicable
- Référence transaction Stripe/PayPal si applicable
Expédition via `mail()` natif PHP, expéditeur `$cfg[34]['headoffice_name'] <$cfg[11]['commercial']>`.
---
Annulation #
Si `?action=cancel` ET `payment_status='unpaid'` :
1. Récupère les `order_items` de l'order
2. Vide le panier courant (`Panier::vider()`)
3. Réinjecte les items (`Panier::ajouterProduit(productId, quantity)`)
4. Marque l'order `failed` via `Payment::updateStatus(..., 'failed', ..., 'Payment cancelled by user')`
5. Redirige vers `products_checkout.php`
Même logique si l'utilisateur revient sur `payment.php` avec un order déjà en `failed` (recovery panier).
---
Webhook — `handlers/payment.mod.php` #
Endpoint : `?obj=payment.mod.php&method=stripe` (ou `paypal`)
POST raw payload
↓
$rawPayload = file_get_contents('php://input')
$method = $_GET['method']
↓
Payment::handleCallback($method, ['_raw'=>…, '_headers'=>…, 'event'=>…])
↓
Provider vérifie signature + traite l'event
↓
HTTP 200 {status:ok} | 400 {status:error}
Stripe et PayPal doivent être configurés dans leurs dashboards respectifs avec ces URLs :
- `https://shop.example.com/index.php?obj=payment.mod.php&method=stripe`
- `https://shop.example.com/index.php?obj=payment.mod.php&method=paypal`
---
Configuration `cog.php` #
| Indice | Rôle |
|--------|------|
| `$cfg[34]` | Coordonnées société (nom, adresse, SIRET) — utilisées dans l'email |
| `$cfg[35]` | IBAN / BIC pour les virements |
| `$cfg[36]` | Stripe : `public_key`, `secret_key`, `webhook_secret`, `mode` |
| `$cfg[37]` | PayPal : `client_id`, `client_secret`, `webhook_id`, `mode` |
| `$cfg[38]` | Chèque : `order_to` (destinataire du chèque) |
| `$cfg[11]['commercial']` | Email expéditeur des confirmations |
---
Dépendances #
- `Beamreactor\Database\SQL`
- `Beamreactor\Payment\Payment` — façade publique (`processSubscription`, `verifySession`, `handleCallback`, `updateStatus`, `getInstructions`)
- `Beamreactor\Payment\PaymentStripe`, `PaymentPaypal`, `PaymentVirement`, `PaymentCheque` — handlers concrets, autoloadés par `Payment::loadHandler()` (include explicite, pas par namespace)
- `Beamreactor\Shop\Panier` — restauration panier sur annulation/échec
- `Beamreactor\Sanitizer\Parser` — sanitize des paramètres GET
- Fonction PHP `mail()` pour l'envoi de l'email
⚠️ Ne jamais instancier `PaymentStripe` directement par `new \Beamreactor\Payment\PaymentStripe()` — la classe n'est pas autoloadée par namespace. Toujours passer par les méthodes statiques de `Payment::`.
---
Tables SQL #
- `orders` : commande principale (`payment_status`, `total_ttc`, `payment_method`, `email_sent`, …)
- `order_items` : lignes commande, dont colonnes abonnement (`is_subscription`, `unit_price_recurring_ht`, `subscription_billing_anchor`, `subscription_interval`)
- `payment_subscriptions` : abonnements actifs Stripe/PayPal (alimentée par les webhooks)
---
Installation #
Déposer le répertoire `payment/` dans `/plugins/`. Aucune migration spécifique. Les tables `orders` / `order_items` sont gérées par le module shop principal.