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.

text
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:

php
$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)

text
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