# 08 — Billing (Packages) ## Overview - Model: one-off purchases of event packages (Endkunden) or annual subscriptions (Reseller); see 15-packages-design.md for details. - Tables: `packages`, `event_packages`, `tenant_packages`, `package_purchases` (see 04-data-model-migrations.md and 15-packages-design.md). - Providers: Stripe (server-side checkout + webhooks for Einmalkäufe/Subscriptions), PayPal (Orders API + webhooks); store receipts and metadata. - Idempotency: purchase intents keyed by provider_id; purchase writes idempotent (check existing before create); retries safe via DB transactions. - Limits: Enforce package selection at event creation; check event-specific limits (e.g. max_photos) during usage; tenant limits for reseller event count. - SaaS Aspects: Trial periods (14 days for first reseller subscription), subscription status updates, GDPR compliance (no PII in logs/metadata, privacy consent required), cancellation links to provider dashboards. ## PurchaseWizard Flow (Frontend: resources/js/pages/marketing/PurchaseWizard.tsx) - Multi-step SPA: Package Selection → Auth (Login/Register, conditional for unauthenticated) → Payment (Stripe Elements or PayPal Buttons toggle) → Success. - Persistence: sessionStorage for state (wizardData, currentStep, timestamp with 30min TTL); restores on reload/refresh without data loss. - No reloads: Inertia.js router.post for auth/payment, onSuccess advances step client-side. - Package Details: Enhanced UI with features list (Check icons), SaaS info (annual billing, trial notice, reseller benefits, cancellation policy). - Error Handling: Backend validation errors (422) displayed via Inertia onError; frontend try-catch for API calls, toasts for user feedback. - Localization: react-i18next with /public/lang/de/en/marketing.json (e.g., payment options, errors, trial texts). ## Backend Implementation (app/Http/Controllers/Api/PackageController.php) - Endpoints: - POST /api/packages: purchase (validates package_id, type, payment_method; handles free/paid). - POST /api/packages/create-payment-intent: Stripe client_secret for card payments. - POST /api/packages/complete-purchase: Finalizes after payment (creates PackagePurchase/TenantPackage; supports stripe/paypal provider_id). - POST /api/packages/paypal-create: Creates PayPal Order (OrdersCreateRequest with custom_id metadata: tenant_id/package_id). - POST /api/packages/paypal-capture: Captures Order (OrdersCaptureRequest; idempotent check, trial logic if first reseller). - Free Packages: Direct DB assignment (no payment). - Paid: Stripe PaymentIntent or PayPal Order; completePurchase in transaction. - Trial Logic: For reseller_subscription, if no active packages, set expires_at = now()->addDays(14); else full year. - Multi-Tenancy: Tenant middleware isolates data; metadata includes tenant_id. ## PayPal Integration - SDK: paypal/paypal-server-sdk (composer require); Sandbox/LiveEnvironment based on config/services.php. - Flow: Frontend PayPalButtons createOrder (fetch /api/packages/paypal-create) → onApprove captureOrder (fetch /api/packages/paypal-capture) → completePurchase. - Webhooks: Dedicated PayPalWebhookController.php (route POST /api/paypal/webhook/verify, no auth). - Verification: VerifyWebhookSignature with headers/webhook_id. - Events: PAYMENT.CAPTURE.COMPLETED (process idempotent purchase, trial activation), BILLING.SUBSCRIPTION.CANCELLED (deactivate TenantPackage, update status to 'cancelled'). - Idempotency: Check provider_id before processing. - Config: services.php with client_id/secret/sandbox; webhook_id for verification. ## Stripe Integration - SDK: stripe/stripe-php; config/services.php secret key. - Flow: Elements for card input → confirmCardPayment with client_secret → completePurchase on success. - Subscriptions: For reseller, createSubscription (setup in handlePaidPurchase); webhooks for invoice.paid (renew), customer.subscription.deleted (cancel). - Metadata: tenant_id/package_id/type for all intents/subscriptions. ## Error Handling & Security - Validation: Laravel Requests (e.g., package_id exists, privacy_consent); 422 JSON errors for API. - Auth: Sanctum for API; middleware('auth:sanctum', 'tenant') on purchase endpoints. - GDPR: No PII in sessionStorage/metadata; privacy consent checkbox in RegisterForm; logs anonymized (tenant_id only). - Cancellations: Success step links to provider dashboard (e.g., Stripe Customer Portal, PayPal Manage Subscriptions) based on purchase.provider. ## Testing - Feature Tests: tests/Feature/PurchaseTest.php (unauth redirects, free/paid flows, Stripe/PayPal mocks, errors, trial/renewal logic, idempotency). - E2E: Playwright for wizard steps (auth without reload, persistence on refresh, payment toggles). - Coverage: Auth errors (duplicate email, wrong pass), payment failures (no assignment), limits exceeded (403). ## Deployment & Ops - Migrations: Add trial_days to packages if configurable; webhook routes in api.php (without auth middleware). - Monitoring: Log payment events (success/fail); alert on webhook verification fails. - Legal: Update Privacy/AGB for payment providers; receipts via email (views/emails/purchase.blade.php).