En vous promenant sur Beamreactor, nous stockons votre IP 48h pour des raisons de sécurité.

Lecteur Markdown

payment Documentation › PAYMENT_DOCUMENTATION_EN

PAYMENT_DOCUMENTATION_EN

Plugin: Payment #

Payment return page (success, cancellation, pending). Receives the user after Stripe / PayPal / bank transfer / cheque checkout, verifies the status, displays the confirmation and sends the recap email.

---

Role #

The `payment` plugin does not initiate any payment — it receives the returns:

| Provider | Return URL | Action |

|----------|------------|--------|

| Stripe | `?obj=payment.php&order=NUM&session_id=cs_...` | Synchronous session verification, marks the order `paid` if Stripe confirms |

| PayPal | `?obj=payment.php&order=NUM&token=...` | Payment capture via `Payment::handleCallback('paypal', …)` |

| Transfer | `?obj=payment.php&order=NUM` | Displays IBAN/BIC + order reference |

| Cheque | `?obj=payment.php&order=NUM` | Displays the postal address + payee |

| Cancel | `?obj=payment.php&action=cancel&order=NUM` | Restores the cart, redirects to `products_checkout.php` |

Payment initiation is handled upstream by `products_checkout` (regular cart), `abo_checkout` (first subscription) and `reabo_checkout` (re-subscription).

---

HT / TTC Architecture #

| Layer | Format |

|-------|--------|

| `products.price`, `products.subscription_price` (DB) | HT (excl. VAT) |

| `order_items.unit_price_ht`, `unit_price_recurring_ht` (DB) | HT |

| User display (cart, product page, payment) | TTC (incl. VAT) |

| Stripe/PayPal API `unit_amount` (cents) | TTC (computed via `vat_rate`) |

| Editor admin input | TTC via toggle (but stored HT) |

| B2B VIES zero-rated | pass `vat_rate=0` at checkout time |

All amounts sent to providers are computed via `bcmul(number_format(HT × (1 + vat/100), 2, '.', ''), '100', 0)` to avoid float rounding.

---

Stripe flow — synchronous verification on return #

Stripe redirects to `success_url` before the webhook has always finished processing. Without synchronous verification, the user would see "payment in progress" while everything was actually OK.

Stripe Checkout → success_url (payment.php?session_id=...)
        ↓
payment.php : Payment::verifySession('stripe', $sessionId, $orderId)
        ↓
PaymentStripe::verifySession()
   GET /v1/checkout/sessions/{id}
   Checks : status='complete' AND payment_status IN ('paid','no_payment_required')
        ↓
If OK → Payment::updateStatus($orderId, 'paid', $paymentIntent, 'Stripe session verified on return')
        ↓
Confirmation displayed

The webhook remains the fallback source of truth (idempotent thanks to `Payment::updateStatus`). If the synchronous verification fails (network, slow Stripe), the webhook will take over and the user will see "in progress" then can refresh.

---

Total display with subscription #

For mixed orders (one-time products + subscription), `orders.total_ttc` only contains the one-time part. The first subscription instalment is billed by Stripe via a separate `line_item`.

The display therefore computes:

$paidTodayTtc = (float)$order['total_ttc'] + $recurringTtc;

with `$recurringTtc = Σ (unit_price_recurring_ht × (1 + vat_rate/100) × quantity)` over `order_items` where `is_subscription = 1`.

Three rows displayed:

  • Total paid today: `$paidTodayTtc`
  • Of which products: `$order['total_ttc']`
  • Of which first subscription payment: `$recurringTtc`
  • Then: `$recurringTtc / month` (recurring)

---

Confirmation email #

Sent only once per order (flag `orders.email_sent`). Contains:

  • Line recap (name, qty, line TTC)
  • Subtotal HT + VAT + total TTC
  • Billing/shipping addresses
  • Transfer/cheque instructions if applicable
  • Stripe/PayPal transaction reference if applicable

Sent via PHP native `mail()`, sender `$cfg[34]['headoffice_name'] <$cfg[11]['commercial']>`.

---

Cancellation #

If `?action=cancel` AND `payment_status='unpaid'`:

1. Fetch the order's `order_items`

2. Empty the current cart (`Panier::vider()`)

3. Re-inject the items (`Panier::ajouterProduit(productId, quantity)`)

4. Mark the order `failed` via `Payment::updateStatus(..., 'failed', ..., 'Payment cancelled by user')`

5. Redirect to `products_checkout.php`

Same logic if the user returns to `payment.php` with an order already `failed` (cart recovery).

---

Webhook — `handlers/payment.mod.php` #

Endpoint: `?obj=payment.mod.php&method=stripe` (or `paypal`)

POST raw payload
  ↓
$rawPayload = file_get_contents('php://input')
$method     = $_GET['method']
  ↓
Payment::handleCallback($method, ['_raw'=>…, '_headers'=>…, 'event'=>…])
  ↓
Provider verifies signature + handles the event
  ↓
HTTP 200 {status:ok} | 400 {status:error}

Stripe and PayPal must be configured in their respective dashboards with these 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`

---

`cog.php` configuration #

| Index | Role |

|-------|------|

| `$cfg[34]` | Company details (name, address, SIRET) — used in the email |

| `$cfg[35]` | IBAN / BIC for transfers |

| `$cfg[36]` | Stripe: `public_key`, `secret_key`, `webhook_secret`, `mode` |

| `$cfg[37]` | PayPal: `client_id`, `client_secret`, `webhook_id`, `mode` |

| `$cfg[38]` | Cheque: `order_to` (cheque payee) |

| `$cfg[11]['commercial']` | Sender email for confirmations |

---

Dependencies #

  • `Beamreactor\Database\SQL`
  • `Beamreactor\Payment\Payment` — public façade (`processSubscription`, `verifySession`, `handleCallback`, `updateStatus`, `getInstructions`)
  • `Beamreactor\Payment\PaymentStripe`, `PaymentPaypal`, `PaymentVirement`, `PaymentCheque` — concrete handlers, autoloaded by `Payment::loadHandler()` (explicit include, not by namespace)
  • `Beamreactor\Shop\Panier` — cart restoration on cancel/failure
  • `Beamreactor\Sanitizer\Parser` — sanitize GET parameters
  • PHP `mail()` function for sending the email

⚠️ Never instantiate `PaymentStripe` directly via `new \Beamreactor\Payment\PaymentStripe()` — the class is not autoloaded by namespace. Always go through the static methods of `Payment::`.

---

SQL tables #

  • `orders`: main order (`payment_status`, `total_ttc`, `payment_method`, `email_sent`, …)
  • `order_items`: order lines, including subscription columns (`is_subscription`, `unit_price_recurring_ht`, `subscription_billing_anchor`, `subscription_interval`)
  • `payment_subscriptions`: active Stripe/PayPal subscriptions (fed by webhooks)

---

Installation #

Drop the `payment/` directory into `/plugins/`. No specific migration. The `orders` / `order_items` tables are managed by the main shop module.

de en fr