Architecture
This document explains the architecture of the SaaS kit: boundaries, core domains, and the event-driven billing model.
This document explains the architecture of the SaaS kit: boundaries, core domains, and the event-driven billing model.
1) Principles
- SSR-first by default (Blade + Filament/Livewire)
- Clear domain boundaries with minimal coupling
- Billing is event-driven; webhooks are the source of truth
- Entitlements are centralized and drive access
- User is the billing entity
2) Core domain map
Identity
User- Session auth, password reset, optional Socialite providers
Billing (canonical)
Canonical tables (provider-agnostic):
billing_customersproducts,pricessubscriptionsorderswebhook_eventsdiscounts,discount_redemptions
Provider adapters:
- StripeAdapter
- PaddleAdapter
Catalog source:
BILLING_CATALOG=databaseis the current default.- In the current schema, a "plan" is represented by
products.keyplus one or more relatedprices.
Content
- Blog posts, categories/tags
- Sitemap + RSS
Settings
- Theme tokens via CSS variables
- Branding settings (logo, support links)
3) Request lifecycle (SSR)
- Authenticate session
- Authorize action (policies, gates, Filament)
- Execute domain service
- Render SSR response
4) Entitlements
4.1 What are entitlements?
Entitlements are a computed capability set based on:
- active subscription (or one-time purchase)
- plan configuration
- feature flags
Examples:
can_use_feature_xstorage_limit_mb
4.2 Single source of truth
Implement one service:
EntitlementService::forUser(User $user): EntitlementsDTO
Everything else reads from it:
- UI gating in Blade/Filament
- middleware protection for routes/actions
- API gating (if you expose any endpoints)
5) Billing webhook pipeline
5.1 Pipeline overview
- Provider sends webhook to
/webhooks/{provider} - Signature is verified
- Event payload is saved to
webhook_events(status =received) - A job is dispatched to process the event (idempotent)
- Canonical subscription/order state is updated
- Entitlements recompute on next request (or cached and invalidated)
5.2 Idempotency
webhook_eventshas a unique(provider, event_id)constraint- If the same event is delivered twice, processing is skipped or safely repeated
- Jobs must be safe to retry
5.3 Processing page
After checkout redirect, show a page that:
- confirms the user is authenticated
- polls an endpoint like
/billing/statusuntil canonical state reflects the new subscription/order - provides a fallback "contact support" link
6) Background jobs and scheduling
- Webhook processing runs on the queue
- Use scheduler for recurring jobs (e.g., cleanup, emails)
7) Observability and audit
webhook_eventsprovides an audit log and retry surface- Admin Panel should expose event status and error messages
- Domain services should log important transitions (minimal PII)
8) Folder structure (suggested)
app/
Domain/
Identity/
Access/
Billing/
Content/
Settings/
Filament/
Http/
Jobs/
Policies/
Providers/
config/
docs/
database/
routes/
tests/
9) Extension guidelines
- Add new domains under
app/Domainand keep dependencies explicit - Avoid plan-name checks; use entitlements instead
- Keep provider-specific logic inside adapters
- Prefer DTOs for cross-domain data contracts
- Add tests for critical flows (billing, access control)