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

Lecteur Markdown

payment Documentation › PAYMENT_DOCUMENTATION_DE

PAYMENT_DOCUMENTATION_DE

Plugin: Payment #

Zahlungs-Rückkehrseite (Erfolg, Abbruch, Wartend). Empfängt den Benutzer nach dem Checkout über Stripe / PayPal / Überweisung / Scheck, prüft den Status, zeigt die Bestätigung an und versendet die Zusammenfassungs-E-Mail.

---

Rolle #

Das `payment`-Plugin initiiert keine Zahlung — es empfängt die Rückläufer:

| Provider | Rückkehr-URL | Aktion |

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

| Stripe | `?obj=payment.php&order=NUM&session_id=cs_...` | Synchrone Sitzungsprüfung, markiert die Bestellung als `paid`, wenn Stripe bestätigt |

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

| Überweisung | `?obj=payment.php&order=NUM` | Zeigt IBAN/BIC + Bestellreferenz |

| Scheck | `?obj=payment.php&order=NUM` | Zeigt Postanschrift + Empfänger |

| Abbruch | `?obj=payment.php&action=cancel&order=NUM` | Stellt den Warenkorb wieder her, leitet zu `products_checkout.php` weiter |

Die Zahlungsinitiierung wird vorgelagert von `products_checkout` (klassischer Warenkorb), `abo_checkout` (erstes Abonnement) und `reabo_checkout` (Wieder-Abonnement) übernommen.

---

Netto / Brutto Architektur #

| Schicht | Format |

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

| `products.price`, `products.subscription_price` (DB) | Netto |

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

| Benutzeranzeige (Warenkorb, Produktseite, Zahlung) | Brutto |

| Stripe/PayPal API `unit_amount` (Cent) | Brutto (berechnet über `vat_rate`) |

| Admin-Editor-Eingabe | Brutto über Toggle (aber als Netto gespeichert) |

| B2B VIES nullbesteuert | beim Checkout `vat_rate=0` übergeben |

Alle an die Provider gesendeten Beträge werden über `bcmul(number_format(Netto × (1 + vat/100), 2, '.', ''), '100', 0)` berechnet, um Float-Rundungen zu vermeiden.

---

Stripe-Flow — synchrone Prüfung bei der Rückkehr #

Stripe leitet zur `success_url` weiter, bevor der Webhook die Verarbeitung immer abgeschlossen hat. Ohne synchrone Prüfung sah der Benutzer „Zahlung wird bearbeitet", obwohl alles in Ordnung war.

Stripe Checkout → success_url (payment.php?session_id=...)
        ↓
payment.php : Payment::verifySession('stripe', $sessionId, $orderId)
        ↓
PaymentStripe::verifySession()
   GET /v1/checkout/sessions/{id}
   Prüft: status='complete' UND payment_status IN ('paid','no_payment_required')
        ↓
Wenn OK → Payment::updateStatus($orderId, 'paid', $paymentIntent, 'Stripe session verified on return')
        ↓
Bestätigung wird angezeigt

Der Webhook bleibt die Backup-Quelle der Wahrheit (idempotent dank `Payment::updateStatus`). Wenn die synchrone Prüfung fehlschlägt (Netzwerk, langsames Stripe), übernimmt der Webhook und der Benutzer sieht „in Bearbeitung" und kann dann aktualisieren.

---

Anzeige des Gesamtbetrags mit Abonnement #

Bei gemischten Bestellungen (Einmal-Produkte + Abonnement) enthält `orders.total_ttc` nur den Einmal-Teil. Die erste Abo-Rate wird von Stripe über einen separaten `line_item` abgerechnet.

Die Anzeige berechnet daher:

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

mit `$recurringTtc = Σ (unit_price_recurring_ht × (1 + vat_rate/100) × quantity)` über `order_items`, bei denen `is_subscription = 1`.

Drei angezeigte Zeilen:

  • Heute gezahlt: `$paidTodayTtc`
  • Davon Produkte: `$order['total_ttc']`
  • Davon erste Abo-Zahlung: `$recurringTtc`
  • Dann: `$recurringTtc / Monat` (wiederkehrend)

---

Bestätigungs-E-Mail #

Wird nur einmal pro Bestellung versendet (Flag `orders.email_sent`). Enthält:

  • Zeilen-Zusammenfassung (Name, Menge, Brutto-Zeile)
  • Netto-Zwischensumme + MwSt. + Brutto-Gesamtbetrag
  • Rechnungs-/Lieferadressen
  • Überweisungs-/Scheckhinweise falls zutreffend
  • Stripe/PayPal-Transaktionsreferenz falls zutreffend

Versand über natives PHP `mail()`, Absender `$cfg[34]['headoffice_name'] <$cfg[11]['commercial']>`.

---

Abbruch #

Wenn `?action=cancel` UND `payment_status='unpaid'`:

1. `order_items` der Bestellung abrufen

2. Aktuellen Warenkorb leeren (`Panier::vider()`)

3. Artikel wieder einfügen (`Panier::ajouterProduit(productId, quantity)`)

4. Bestellung als `failed` markieren über `Payment::updateStatus(..., 'failed', ..., 'Payment cancelled by user')`

5. Weiterleitung zu `products_checkout.php`

Gleiche Logik, wenn der Benutzer mit einer bereits `failed`-Bestellung zu `payment.php` zurückkehrt (Warenkorb-Wiederherstellung).

---

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

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

POST raw payload
  ↓
$rawPayload = file_get_contents('php://input')
$method     = $_GET['method']
  ↓
Payment::handleCallback($method, ['_raw'=>…, '_headers'=>…, 'event'=>…])
  ↓
Provider prüft Signatur + verarbeitet das Event
  ↓
HTTP 200 {status:ok} | 400 {status:error}

Stripe und PayPal müssen in ihren jeweiligen Dashboards mit diesen URLs konfiguriert werden:

  • `https://shop.example.com/index.php?obj=payment.mod.php&method=stripe`
  • `https://shop.example.com/index.php?obj=payment.mod.php&method=paypal`

---

Konfiguration `cog.php` #

| Index | Rolle |

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

| `$cfg[34]` | Firmendaten (Name, Adresse, SIRET) — in der E-Mail verwendet |

| `$cfg[35]` | IBAN / BIC für Überweisungen |

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

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

| `$cfg[38]` | Scheck: `order_to` (Scheck-Empfänger) |

| `$cfg[11]['commercial']` | Absender-E-Mail für Bestätigungen |

---

Abhängigkeiten #

  • `Beamreactor\Database\SQL`
  • `Beamreactor\Payment\Payment` — öffentliche Fassade (`processSubscription`, `verifySession`, `handleCallback`, `updateStatus`, `getInstructions`)
  • `Beamreactor\Payment\PaymentStripe`, `PaymentPaypal`, `PaymentVirement`, `PaymentCheque` — konkrete Handler, autoloaded durch `Payment::loadHandler()` (expliziter include, nicht über Namespace)
  • `Beamreactor\Shop\Panier` — Warenkorb-Wiederherstellung bei Abbruch/Fehler
  • `Beamreactor\Sanitizer\Parser` — Bereinigung der GET-Parameter
  • PHP-Funktion `mail()` zum Versenden der E-Mail

⚠️ Niemals `PaymentStripe` direkt mit `new \Beamreactor\Payment\PaymentStripe()` instanziieren — die Klasse wird nicht über den Namespace autoloaded. Immer über die statischen Methoden von `Payment::` gehen.

---

SQL-Tabellen #

  • `orders`: Hauptbestellung (`payment_status`, `total_ttc`, `payment_method`, `email_sent`, …)
  • `order_items`: Bestellzeilen, einschließlich Abo-Spalten (`is_subscription`, `unit_price_recurring_ht`, `subscription_billing_anchor`, `subscription_interval`)
  • `payment_subscriptions`: aktive Stripe/PayPal-Abonnements (durch Webhooks gespeist)

---

Installation #

Das Verzeichnis `payment/` in `/plugins/` ablegen. Keine spezifische Migration. Die Tabellen `orders` / `order_items` werden vom Haupt-Shop-Modul verwaltet.

de en fr