command: rg -n -C 4 'theme_settings|password reset|password_reset|session encrypt|CORS|cors|rate limit|webhook|cart ID|guest access|Form Request|card number|reservation|abandoned' specs/01-DATABASE-SCHEMA.md specs/02-API-ROUTES.md specs/05-BUSINESS-LOGIC.md specs/06-AUTH-AND-SECURITY.md specs/08-PLAYWRIGHT-E2E-PLAN.md specs/09-IMPLEMENTATION-ROADMAP.md

type: search

query: theme_settings|password reset|password_reset|session encrypt|CORS|cors|rate limit|webhook|cart ID|guest access|Form Request|card number|reservation|abandoned

stdout:
specs/08-PLAYWRIGHT-E2E-PLAN.md-1772-16. Verify "Standard Shipping" and "4.99" are displayed
specs/08-PLAYWRIGHT-E2E-PLAN.md-1773-17. Click "Standard Shipping" to select it
specs/08-PLAYWRIGHT-E2E-PLAN.md-1774-18. Click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-1775-19. Verify "Credit Card" is pre-selected as payment method
specs/08-PLAYWRIGHT-E2E-PLAN.md:1776:20. Enter card number: `4242 4242 4242 4242`
specs/08-PLAYWRIGHT-E2E-PLAN.md-1777-21. Enter cardholder name: `Test Buyer`
specs/08-PLAYWRIGHT-E2E-PLAN.md-1778-22. Enter expiry: `12/28`
specs/08-PLAYWRIGHT-E2E-PLAN.md-1779-23. Enter CVC: `123`
specs/08-PLAYWRIGHT-E2E-PLAN.md-1780-24. Verify total is 29.98 (24.99 + 4.99 shipping)
--
specs/08-PLAYWRIGHT-E2E-PLAN.md-2014-5. Enter email: `test@example.com`, click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2015-6. Fill shipping address (DE), click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2016-7. Select "Standard Shipping", click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2017-8. Verify "Credit Card" is selected
specs/08-PLAYWRIGHT-E2E-PLAN.md:2018:9. Enter card number: `4000 0000 0000 0002` (magic decline card)
specs/08-PLAYWRIGHT-E2E-PLAN.md-2019-10. Enter cardholder name: `Test Buyer`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2020-11. Enter expiry: `12/28`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2021-12. Enter CVC: `123`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2022-13. Click "Pay now"
--
specs/08-PLAYWRIGHT-E2E-PLAN.md-2039-4. Navigate to `/cart`, click "Checkout"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2040-5. Enter email: `test@example.com`, click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2041-6. Fill shipping address (DE), click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2042-7. Select "Standard Shipping", click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md:2043:8. Enter card number: `4000 0000 0000 9995` (magic insufficient funds card)
specs/08-PLAYWRIGHT-E2E-PLAN.md-2044-9. Enter cardholder name: `Test Buyer`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2045-10. Enter expiry: `12/28`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2046-11. Enter CVC: `123`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2047-12. Click "Pay now"
--
specs/08-PLAYWRIGHT-E2E-PLAN.md-3163-3. **Pest v4 syntax:** All tests use Pest's `it()` function, NOT class-based PHPUnit. No `$this->` within closures unless accessing Laravel test methods. Use `visit()`, `fill()`, `click()`, `assertSee()` as the primary browser interaction API.
specs/08-PLAYWRIGHT-E2E-PLAN.md-3164-
specs/08-PLAYWRIGHT-E2E-PLAN.md-3165-4. **Variant selection:** When adding products to cart, always select variant options first (e.g., click "M" for size, click "Black" for color) before clicking "Add to cart".
specs/08-PLAYWRIGHT-E2E-PLAN.md-3166-
specs/08-PLAYWRIGHT-E2E-PLAN.md:3167:5. **Payment testing:** All payments are processed in-process via the Mock PSP. For credit card tests, use the magic card number `4242 4242 4242 4242` for success, `4000 0000 0000 0002` for decline, and `4000 0000 0000 9995` for insufficient funds. No external API calls, webhooks, or redirects are involved.
specs/08-PLAYWRIGHT-E2E-PLAN.md-3168-
specs/08-PLAYWRIGHT-E2E-PLAN.md-3169-6. **Viewport testing (Suite 13):** The coding agent should use Pest v4 / Playwright's viewport configuration to set browser dimensions. The exact API for setting viewport in Pest v4 browser tests should be looked up via documentation search.
specs/08-PLAYWRIGHT-E2E-PLAN.md-3170-
specs/08-PLAYWRIGHT-E2E-PLAN.md-3171-7. **Form field names:** The field names used in `fill()` calls (e.g., "email", "password", "first_name", "variants.0.price") are logical names. The coding agent must use the actual HTML field names/labels as rendered by the Livewire components.
--
specs/06-AUTH-AND-SECURITY.md-30-    participant S as Session
specs/06-AUTH-AND-SECURITY.md-31-
specs/06-AUTH-AND-SECURITY.md-32-    B->>LW: Submit email + password
specs/06-AUTH-AND-SECURITY.md-33-    LW->>LW: Validate input (email required, password required)
specs/06-AUTH-AND-SECURITY.md:34:    LW->>LW: Check rate limiter (5/min per IP)
specs/06-AUTH-AND-SECURITY.md-35-    alt Rate limit exceeded
specs/06-AUTH-AND-SECURITY.md-36-        LW-->>B: Flash "Too many attempts. Try again in X seconds."
specs/06-AUTH-AND-SECURITY.md-37-    end
specs/06-AUTH-AND-SECURITY.md-38-    LW->>Auth: attempt(credentials, remember)
--
specs/06-AUTH-AND-SECURITY.md-52-
specs/06-AUTH-AND-SECURITY.md-53-- The login form collects `email` and `password`.
specs/06-AUTH-AND-SECURITY.md-54-- On successful login, the session MUST be regenerated to prevent session fixation attacks.
specs/06-AUTH-AND-SECURITY.md-55-- On failed login, only the generic message "Invalid credentials" is shown. The system must not indicate whether the email or password was wrong.
specs/06-AUTH-AND-SECURITY.md:56:- Rate limiting: maximum 5 attempts per minute, keyed by IP address. Uses the `login` rate limiter.
specs/06-AUTH-AND-SECURITY.md-57-- After login: redirect to `/admin` (the dashboard).
specs/06-AUTH-AND-SECURITY.md-58-
specs/06-AUTH-AND-SECURITY.md-59-#### Remember Me
specs/06-AUTH-AND-SECURITY.md-60-
--
specs/06-AUTH-AND-SECURITY.md-76-    App-->>B: Render forgot-password form
specs/06-AUTH-AND-SECURITY.md-77-    B->>App: POST /admin/forgot-password (email)
specs/06-AUTH-AND-SECURITY.md-78-    App->>DB: Look up user by email
specs/06-AUTH-AND-SECURITY.md-79-    alt User exists
specs/06-AUTH-AND-SECURITY.md:80:        App->>DB: Store reset token in password_reset_tokens table
specs/06-AUTH-AND-SECURITY.md-81-        App->>Mail: Send reset link email
specs/06-AUTH-AND-SECURITY.md-82-    end
specs/06-AUTH-AND-SECURITY.md-83-    App-->>B: "If that email exists, we sent a reset link." (generic)
specs/06-AUTH-AND-SECURITY.md-84-    B->>App: GET /admin/reset-password/{token}
--
specs/06-AUTH-AND-SECURITY.md-96-
specs/06-AUTH-AND-SECURITY.md-97-**Requirements:**
specs/06-AUTH-AND-SECURITY.md-98-
specs/06-AUTH-AND-SECURITY.md-99-- Uses Laravel's built-in password broker (`users` broker).
specs/06-AUTH-AND-SECURITY.md:100:- Reset tokens stored in the `password_reset_tokens` table.
specs/06-AUTH-AND-SECURITY.md-101-- Token expiration: 60 minutes (configurable in `config/auth.php` under `passwords.users.expire`).
specs/06-AUTH-AND-SECURITY.md-102-- Throttle: one reset email per 60 seconds per email address.
specs/06-AUTH-AND-SECURITY.md-103-- The "forgot password" response must be generic (do not reveal whether email exists).
specs/06-AUTH-AND-SECURITY.md-104-
--
specs/06-AUTH-AND-SECURITY.md-140-
specs/06-AUTH-AND-SECURITY.md-141-    Note over Store: Store already resolved from hostname
specs/06-AUTH-AND-SECURITY.md-142-    B->>LW: Submit email + password
specs/06-AUTH-AND-SECURITY.md-143-    LW->>LW: Validate input
specs/06-AUTH-AND-SECURITY.md:144:    LW->>LW: Check rate limiter (5/min per IP)
specs/06-AUTH-AND-SECURITY.md-145-    LW->>Auth: attempt(credentials + store_id, remember)
specs/06-AUTH-AND-SECURITY.md-146-    Auth->>DB: SELECT from customers WHERE email = ? AND store_id = ?
specs/06-AUTH-AND-SECURITY.md-147-    Auth->>Auth: Verify bcrypt hash
specs/06-AUTH-AND-SECURITY.md-148-    alt Valid
--
specs/06-AUTH-AND-SECURITY.md-158-
specs/06-AUTH-AND-SECURITY.md-159-- Customer login is always scoped to the current store. The store is resolved from the request hostname by the `ResolveStore` middleware before the login attempt.
specs/06-AUTH-AND-SECURITY.md-160-- The custom `CustomerUserProvider` must inject `store_id` into the credential check so the query filters by both `email` and `store_id`.
specs/06-AUTH-AND-SECURITY.md-161-- The same email address can exist in different stores (multi-tenant isolation).
specs/06-AUTH-AND-SECURITY.md:162:- Rate limiting: maximum 5 attempts per minute per IP, using the same `login` rate limiter.
specs/06-AUTH-AND-SECURITY.md-163-- After login: redirect to `/account` or the intended URL (if the customer was redirected from a protected page).
specs/06-AUTH-AND-SECURITY.md-164-
specs/06-AUTH-AND-SECURITY.md-165-#### Customer Registration Flow
specs/06-AUTH-AND-SECURITY.md-166-
--
specs/06-AUTH-AND-SECURITY.md-193-
specs/06-AUTH-AND-SECURITY.md-194-#### Customer Password Reset
specs/06-AUTH-AND-SECURITY.md-195-
specs/06-AUTH-AND-SECURITY.md-196-- Uses a separate password broker: `customers` broker.
specs/06-AUTH-AND-SECURITY.md:197:- Tokens stored in the `customer_password_reset_tokens` table (separate from admin tokens).
specs/06-AUTH-AND-SECURITY.md-198-- Token records include `store_id` to scope resets to the correct store.
specs/06-AUTH-AND-SECURITY.md-199-- Routes: `GET /forgot-password`, `POST /forgot-password`, `GET /reset-password/{token}`, `POST /reset-password`.
specs/06-AUTH-AND-SECURITY.md-200-- Token expiration: 60 minutes.
specs/06-AUTH-AND-SECURITY.md-201-- Throttle: one reset email per 60 seconds.
--
specs/06-AUTH-AND-SECURITY.md-288-#### Password Brokers Data Dictionary
specs/06-AUTH-AND-SECURITY.md-289-
specs/06-AUTH-AND-SECURITY.md-290-| Broker Name | Provider | Token Table | Expiry | Throttle |
specs/06-AUTH-AND-SECURITY.md-291-|-------------|----------|-------------|--------|----------|
specs/06-AUTH-AND-SECURITY.md:292:| `users` | `users` | `password_reset_tokens` | 60 min | 60 sec |
specs/06-AUTH-AND-SECURITY.md:293:| `customers` | `customers` | `customer_password_reset_tokens` | 60 min | 60 sec |
specs/06-AUTH-AND-SECURITY.md-294-
specs/06-AUTH-AND-SECURITY.md-295-**Defaults:** The default guard is `web` and the default password broker is `users`.
specs/06-AUTH-AND-SECURITY.md-296-
specs/06-AUTH-AND-SECURITY.md-297----
--
specs/06-AUTH-AND-SECURITY.md-548-| Gate Name | Required Roles | Purpose |
specs/06-AUTH-AND-SECURITY.md-549-|-----------|---------------|---------|
specs/06-AUTH-AND-SECURITY.md-550-| `manage-store-settings` | Owner, Admin | Access store settings pages |
specs/06-AUTH-AND-SECURITY.md-551-| `manage-staff` | Owner, Admin | Invite, update, remove staff members |
specs/06-AUTH-AND-SECURITY.md:552:| `manage-developers` | Owner, Admin | Create/revoke API tokens, manage webhooks |
specs/06-AUTH-AND-SECURITY.md-553-| `view-analytics` | Owner, Admin, Staff | View analytics dashboard and reports |
specs/06-AUTH-AND-SECURITY.md-554-| `manage-shipping` | Owner, Admin | Manage shipping zones, rates, profiles |
specs/06-AUTH-AND-SECURITY.md-555-| `manage-taxes` | Owner, Admin | Manage tax settings and rates |
specs/06-AUTH-AND-SECURITY.md-556-| `manage-search-settings` | Owner, Admin | Configure search weights and synonyms |
--
specs/06-AUTH-AND-SECURITY.md-578-#### In API Controllers
specs/06-AUTH-AND-SECURITY.md-579-
specs/06-AUTH-AND-SECURITY.md-580-- Use `$this->authorize('action', $model)` at the start of each controller method.
specs/06-AUTH-AND-SECURITY.md-581-- Alternatively, use `can` middleware on routes for simpler checks.
specs/06-AUTH-AND-SECURITY.md:582:- Form Requests also implement `authorize()` to check the relevant policy or gate before validation runs.
specs/06-AUTH-AND-SECURITY.md-583-
specs/06-AUTH-AND-SECURITY.md-584-#### In Blade Templates
specs/06-AUTH-AND-SECURITY.md-585-
specs/06-AUTH-AND-SECURITY.md-586-- Use `@can('gate-name')` / `@cannot` directives to conditionally show/hide navigation items and action buttons.
--
specs/06-AUTH-AND-SECURITY.md-613-| Admin API | `api`, `auth:sanctum`, `store.resolve`, `throttle:api.admin` | REST API endpoints |
specs/06-AUTH-AND-SECURITY.md-614-| Storefront web (public) | `web`, `store.resolve` | Public store pages (catalog, product, cart) |
specs/06-AUTH-AND-SECURITY.md-615-| Storefront web (customer) | `web`, `store.resolve`, `auth:customer` | Customer account pages |
specs/06-AUTH-AND-SECURITY.md-616-| Storefront API | `api`, `store.resolve`, `throttle:api.storefront` | Cart and checkout API |
specs/06-AUTH-AND-SECURITY.md:617:| Webhook routes | `api`, `throttle:webhooks` | Reserved for outbound app webhooks (no inbound payment webhooks - Mock PSP is in-process) |
specs/06-AUTH-AND-SECURITY.md-618-
specs/06-AUTH-AND-SECURITY.md-619----
specs/06-AUTH-AND-SECURITY.md-620-
specs/06-AUTH-AND-SECURITY.md-621-### 3.3 Custom Middleware Specifications
--
specs/06-AUTH-AND-SECURITY.md-730----
specs/06-AUTH-AND-SECURITY.md-731-
specs/06-AUTH-AND-SECURITY.md-732-### 4.2 Rate Limiting
specs/06-AUTH-AND-SECURITY.md-733-
specs/06-AUTH-AND-SECURITY.md:734:All rate limiters are defined in `bootstrap/app.php` (or `AppServiceProvider::boot()`).
specs/06-AUTH-AND-SECURITY.md-735-
specs/06-AUTH-AND-SECURITY.md-736-| Limiter Name | Limit | Keyed By | Applied To |
specs/06-AUTH-AND-SECURITY.md-737-|-------------|-------|----------|------------|
specs/06-AUTH-AND-SECURITY.md-738-| `login` | 5 per minute | IP address | Admin login, customer login |
--
specs/06-AUTH-AND-SECURITY.md-740-| `api.storefront` | 120 per minute | IP address | Public storefront API (cart, search) |
specs/06-AUTH-AND-SECURITY.md-741-| `checkout` | 10 per minute | Session ID | Checkout-related operations |
specs/06-AUTH-AND-SECURITY.md-742-| `search` | 30 per minute | IP address | Search queries |
specs/06-AUTH-AND-SECURITY.md-743-| `analytics` | 60 per minute | IP address | Analytics event ingestion |
specs/06-AUTH-AND-SECURITY.md:744:| `webhooks` | 100 per minute | IP address | Outbound app webhook endpoints (reserved; no inbound payment webhooks - Mock PSP is in-process) |
specs/06-AUTH-AND-SECURITY.md-745-
specs/06-AUTH-AND-SECURITY.md-746-**Rate limit response headers** (returned on all API responses by Laravel default):
specs/06-AUTH-AND-SECURITY.md-747-
specs/06-AUTH-AND-SECURITY.md-748-| Header | Description |
specs/06-AUTH-AND-SECURITY.md-749-|--------|-------------|
specs/06-AUTH-AND-SECURITY.md-750-| `X-RateLimit-Limit` | Maximum requests allowed in the window |
specs/06-AUTH-AND-SECURITY.md-751-| `X-RateLimit-Remaining` | Requests remaining in the current window |
specs/06-AUTH-AND-SECURITY.md:752:| `Retry-After` | Seconds until the rate limit resets (only on 429 responses) |
specs/06-AUTH-AND-SECURITY.md-753-
specs/06-AUTH-AND-SECURITY.md:754:When a rate limit is exceeded, the response is HTTP 429 Too Many Requests.
specs/06-AUTH-AND-SECURITY.md-755-
specs/06-AUTH-AND-SECURITY.md-756----
specs/06-AUTH-AND-SECURITY.md-757-
specs/06-AUTH-AND-SECURITY.md-758-### 4.3 Encryption at Rest
--
specs/06-AUTH-AND-SECURITY.md-763-
specs/06-AUTH-AND-SECURITY.md-764-| Model | Column | Cast Type | Purpose |
specs/06-AUTH-AND-SECURITY.md-765-|-------|--------|-----------|---------|
specs/06-AUTH-AND-SECURITY.md-766-| OauthClient | `client_secret_encrypted` | `encrypted` | Third-party OAuth client secrets |
specs/06-AUTH-AND-SECURITY.md:767:| WebhookSubscription | `signing_secret_encrypted` | `encrypted` | Per-subscription signing secrets for outbound webhooks |
specs/06-AUTH-AND-SECURITY.md-768-| Payment | `raw_json` | `encrypted:array` | Full payment gateway response (may contain sensitive card/account data) |
specs/06-AUTH-AND-SECURITY.md-769-
specs/06-AUTH-AND-SECURITY.md-770-**Requirements:**
specs/06-AUTH-AND-SECURITY.md-771-
--
specs/06-AUTH-AND-SECURITY.md-776----
specs/06-AUTH-AND-SECURITY.md-777-
specs/06-AUTH-AND-SECURITY.md-778-### 4.4 Input Validation
specs/06-AUTH-AND-SECURITY.md-779-
specs/06-AUTH-AND-SECURITY.md:780:All write endpoints must use dedicated Form Request classes. Validation rules use string syntax per project convention.
specs/06-AUTH-AND-SECURITY.md-781-
specs/06-AUTH-AND-SECURITY.md:782:#### Form Request Inventory
specs/06-AUTH-AND-SECURITY.md-783-
specs/06-AUTH-AND-SECURITY.md:784:| Form Request Class | Used By | Key Validation Rules |
specs/06-AUTH-AND-SECURITY.md-785-|-------------------|---------|---------------------|
specs/06-AUTH-AND-SECURITY.md-786-| `StoreProductRequest` | Create product | `title`: required, string, max:255. `description_html`: nullable, string. `status`: required, in:active,draft,archived. `vendor`: nullable, string. Price fields: required, integer, min:0. |
specs/06-AUTH-AND-SECURITY.md-787-| `UpdateProductRequest` | Update product | Same fields as StoreProductRequest but all optional (sometimes rule). |
specs/06-AUTH-AND-SECURITY.md-788-| `StoreCollectionRequest` | Create collection | `title`: required, string, max:255. `type`: required, in:manual,automated. `rules_json`: required_if:type,automated, valid JSON. |
--
specs/06-AUTH-AND-SECURITY.md-797-| `InviteStaffRequest` | Invite staff member | `email`: required, valid email. `role`: required, in:admin,staff,support (not owner). |
specs/06-AUTH-AND-SECURITY.md-798-| `RegisterCustomerRequest` | Customer registration | `name`: required. `email`: required, email, unique per store (composite). `password`: required, min:8, confirmed. |
specs/06-AUTH-AND-SECURITY.md-799-| `LoginRequest` | Admin/customer login | `email`: required, email. `password`: required, string. |
specs/06-AUTH-AND-SECURITY.md-800-
specs/06-AUTH-AND-SECURITY.md:801:**Authorization in Form Requests:**
specs/06-AUTH-AND-SECURITY.md-802-
specs/06-AUTH-AND-SECURITY.md:803:Each Form Request's `authorize()` method must check the relevant policy or gate. For example, `StoreProductRequest::authorize()` checks whether the user can create a Product. If authorization fails, the request is rejected with 403 before validation even runs.
specs/06-AUTH-AND-SECURITY.md-804-
specs/06-AUTH-AND-SECURITY.md-805----
specs/06-AUTH-AND-SECURITY.md-806-
specs/06-AUTH-AND-SECURITY.md-807-### 4.5 XSS Prevention
--
specs/06-AUTH-AND-SECURITY.md-912-| `partitioned` | `false` | Not needed for this use case. |
specs/06-AUTH-AND-SECURITY.md-913-
specs/06-AUTH-AND-SECURITY.md-914----
specs/06-AUTH-AND-SECURITY.md-915-
specs/06-AUTH-AND-SECURITY.md:916:## 6. CORS Configuration
specs/06-AUTH-AND-SECURITY.md-917-
specs/06-AUTH-AND-SECURITY.md:918:File: `config/cors.php`
specs/06-AUTH-AND-SECURITY.md-919-
specs/06-AUTH-AND-SECURITY.md-920-| Setting | Value | Rationale |
specs/06-AUTH-AND-SECURITY.md-921-|---------|-------|-----------|
specs/06-AUTH-AND-SECURITY.md:922:| `paths` | `['api/*', 'sanctum/csrf-cookie']` | Only API and Sanctum CSRF cookie endpoints are CORS-enabled. Web routes are not. |
specs/06-AUTH-AND-SECURITY.md-923-| `allowed_methods` | `['*']` | All HTTP methods allowed for API. |
specs/06-AUTH-AND-SECURITY.md-924-| `allowed_origins` | `[FRONTEND_URL env var, default '*']` | Restrict to store domain in production. Wildcard for development. |
specs/06-AUTH-AND-SECURITY.md-925-| `allowed_origins_patterns` | `[]` | No pattern-based origin matching needed. |
specs/06-AUTH-AND-SECURITY.md-926-| `allowed_headers` | `['*']` | All headers allowed (Authorization, Content-Type, etc.). |
specs/06-AUTH-AND-SECURITY.md:927:| `exposed_headers` | `['X-RateLimit-Limit', 'X-RateLimit-Remaining', 'Retry-After']` | Expose rate limit headers so API clients can implement backoff. |
specs/06-AUTH-AND-SECURITY.md-928-| `max_age` | `0` | No preflight caching (conservative default). |
specs/06-AUTH-AND-SECURITY.md-929-| `supports_credentials` | `true` | Required for Sanctum SPA auth (cookies). |
specs/06-AUTH-AND-SECURITY.md-930-
specs/06-AUTH-AND-SECURITY.md-931----
--
specs/06-AUTH-AND-SECURITY.md-933-## 7. Webhook Signature Verification
specs/06-AUTH-AND-SECURITY.md-934-
specs/06-AUTH-AND-SECURITY.md-935-### 7.1 Inbound Payment Webhooks
specs/06-AUTH-AND-SECURITY.md-936-
specs/06-AUTH-AND-SECURITY.md:937:> **Not applicable.** All payment processing happens in-process via the Mock PSP. There are no inbound payment webhooks from external providers (no Stripe, no PayPal). This section is preserved for reference only -- if a real PSP is integrated in the future, inbound webhook verification would follow the patterns described in the outbound webhook section below (HMAC signature verification).
specs/06-AUTH-AND-SECURITY.md-938-
specs/06-AUTH-AND-SECURITY.md-939----
specs/06-AUTH-AND-SECURITY.md-940-
specs/06-AUTH-AND-SECURITY.md-941-### 7.2 Platform Webhooks (Outbound)
specs/06-AUTH-AND-SECURITY.md-942-
specs/06-AUTH-AND-SECURITY.md-943-Service: `App\Services\WebhookDispatcher`
specs/06-AUTH-AND-SECURITY.md-944-
specs/06-AUTH-AND-SECURITY.md:945:When the platform sends webhooks to external subscribers (third-party apps, integrations).
specs/06-AUTH-AND-SECURITY.md-946-
specs/06-AUTH-AND-SECURITY.md-947-```mermaid
specs/06-AUTH-AND-SECURITY.md-948-sequenceDiagram
specs/06-AUTH-AND-SECURITY.md-949-    participant App as Platform
--
specs/06-AUTH-AND-SECURITY.md-1037-| Event ID | Trigger | Extra Data |
specs/06-AUTH-AND-SECURITY.md-1038-|----------|---------|------------|
specs/06-AUTH-AND-SECURITY.md-1039-| `api_token.created` | New API token issued | token_name, abilities list, user_id |
specs/06-AUTH-AND-SECURITY.md-1040-| `api_token.revoked` | API token revoked | token_name, user_id |
specs/06-AUTH-AND-SECURITY.md:1041:| `webhook.signature_invalid` | Outbound webhook delivery failed signature verification at subscriber | subscriber_url, ip |
specs/06-AUTH-AND-SECURITY.md-1042-| `store.settings_changed` | Store settings updated | changed fields with old/new values |
specs/06-AUTH-AND-SECURITY.md-1043-
specs/06-AUTH-AND-SECURITY.md-1044----
specs/06-AUTH-AND-SECURITY.md-1045-
--
specs/06-AUTH-AND-SECURITY.md-1096-| 2 | Session-based auth (customer) | Custom `customer` guard, store-scoped via custom user provider | Required |
specs/06-AUTH-AND-SECURITY.md-1097-| 3 | API token auth | Sanctum personal access tokens with per-token abilities | Required |
specs/06-AUTH-AND-SECURITY.md-1098-| 4 | Role-based access control | `StoreUserRole` enum, Laravel policies on all models, gates for non-model ops | Required |
specs/06-AUTH-AND-SECURITY.md-1099-| 5 | CSRF protection | `@csrf` on all session forms, Livewire auto-handles, API routes exempt | Required |
specs/06-AUTH-AND-SECURITY.md:1100:| 6 | Rate limiting | Seven named limiters (login, api.admin, api.storefront, checkout, search, analytics, webhooks) | Required |
specs/06-AUTH-AND-SECURITY.md:1101:| 7 | Input validation | Dedicated Form Request class for every write endpoint | Required |
specs/06-AUTH-AND-SECURITY.md-1102-| 8 | XSS prevention | Blade auto-escaping by default, HTMLPurifier for rich content fields | Required |
specs/06-AUTH-AND-SECURITY.md-1103-| 9 | SQL injection prevention | Eloquent ORM with parameterized queries, FTS5 input sanitization | Required |
specs/06-AUTH-AND-SECURITY.md-1104-| 10 | Mass assignment protection | `$fillable` on all models, always use `$request->validated()` | Required |
specs/06-AUTH-AND-SECURITY.md-1105-| 11 | Encryption at rest | Laravel `encrypted` cast for secrets and sensitive JSON columns | Required |
specs/06-AUTH-AND-SECURITY.md-1106-| 12 | Password security | Bcrypt, min 8 characters, `$hidden` on User and Customer models | Required |
specs/06-AUTH-AND-SECURITY.md-1107-| 13 | Session security | Encrypted sessions, HTTP-only cookies, secure flag, SameSite=Lax | Required |
specs/06-AUTH-AND-SECURITY.md:1108:| 14 | Webhook signature verification | HMAC-SHA256 for outbound platform webhooks (no inbound payment webhooks - Mock PSP is in-process) | Required |
specs/06-AUTH-AND-SECURITY.md-1109-| 15 | Audit logging | Structured JSON to dedicated daily-rotation log channel, 90-day retention | Required |
specs/06-AUTH-AND-SECURITY.md:1110:| 16 | CORS | Restricted to `api/*` paths, credentials supported, rate limit headers exposed | Required |
--
specs/09-IMPLEMENTATION-ROADMAP.md-21-| Auth (admin) | Sanctum SPA | Laravel session auth | Standard Livewire auth pattern |
specs/09-IMPLEMENTATION-ROADMAP.md-22-| Auth (API) | Sanctum tokens | Sanctum personal access tokens | For programmatic API access |
specs/09-IMPLEMENTATION-ROADMAP.md-23-| Auth (customer) | Separate guard | Custom `customer` guard | Storefront customer auth |
specs/09-IMPLEMENTATION-ROADMAP.md-24-| OAuth (apps) | Passport | Deferred/Stubbed | Simplifies initial build, can add later |
specs/09-IMPLEMENTATION-ROADMAP.md:25:| Payments | Stripe + PayPal | Mock PSP (in-process) | No external API calls. MockPaymentProvider handles credit card, PayPal, bank transfer with magic card numbers for testing. |
specs/09-IMPLEMENTATION-ROADMAP.md-26-| Email | SMTP/SES | Log driver (dev) / SMTP | Log for benchmark, SMTP configurable |
specs/09-IMPLEMENTATION-ROADMAP.md-27-| Monitoring | OpenTelemetry | Laravel Log (structured JSON) | Simplified for benchmark |
specs/09-IMPLEMENTATION-ROADMAP.md-28-| CI/CD | GitHub Actions | Not implemented | Out of scope for one-shot benchmark |
specs/09-IMPLEMENTATION-ROADMAP.md-29-| Docker | Multi-stage | Not implemented | App served by Laravel Herd locally |
--
specs/09-IMPLEMENTATION-ROADMAP.md-92-| `config/cache.php` | `default` | `file` | Cache driver |
specs/09-IMPLEMENTATION-ROADMAP.md-93-| `config/queue.php` | `default` | `sync` | Queue driver |
specs/09-IMPLEMENTATION-ROADMAP.md-94-| `config/auth.php` | Guards | Add `customer` guard with `customers` provider | Customer storefront authentication |
specs/09-IMPLEMENTATION-ROADMAP.md-95-| `config/auth.php` | Providers | Add `customers` provider pointing to Customer model | Customer user provider |
specs/09-IMPLEMENTATION-ROADMAP.md:96:| `config/auth.php` | Passwords | Add `customers` password broker | Customer password reset |
specs/09-IMPLEMENTATION-ROADMAP.md-97-| `config/filesystems.php` | `public` disk | Ensure configured for local storage | Media file storage |
specs/09-IMPLEMENTATION-ROADMAP.md-98-| `config/logging.php` | Channels | Add structured JSON channel | Structured logging |
specs/09-IMPLEMENTATION-ROADMAP.md-99-
specs/09-IMPLEMENTATION-ROADMAP.md-100-#### Step 1.2: Core Migrations (Batch 1-2)
--
specs/09-IMPLEMENTATION-ROADMAP.md-172-|------|------|---------|
specs/09-IMPLEMENTATION-ROADMAP.md-173-| Trait | `App\Models\Concerns\BelongsToStore` | Applies StoreScope and auto-sets store_id on creating event |
specs/09-IMPLEMENTATION-ROADMAP.md-174-| Scope | `App\Models\Scopes\StoreScope` | Applies `where('store_id', app('current_store')->id)` to all queries |
specs/09-IMPLEMENTATION-ROADMAP.md-175-
specs/09-IMPLEMENTATION-ROADMAP.md:176:Applied on: products, collections, customers, orders, carts, checkouts, discounts, shipping_zones, themes, pages, navigation_menus, analytics_events, analytics_daily, webhook_subscriptions, inventory_items, search_queries.
specs/09-IMPLEMENTATION-ROADMAP.md-177-
specs/09-IMPLEMENTATION-ROADMAP.md-178-#### Step 1.7: Authentication
specs/09-IMPLEMENTATION-ROADMAP.md-179-
specs/09-IMPLEMENTATION-ROADMAP.md-180-**Admin auth requirements:**
--
specs/09-IMPLEMENTATION-ROADMAP.md-198-| Uniqueness | Customer email unique per store, not globally |
specs/09-IMPLEMENTATION-ROADMAP.md-199-| Rate limiting | 5 attempts per minute per IP |
specs/09-IMPLEMENTATION-ROADMAP.md-200-
specs/09-IMPLEMENTATION-ROADMAP.md-201-**Rate limiter registration:**
specs/09-IMPLEMENTATION-ROADMAP.md:202:- Register rate limiters in `AppServiceProvider::boot()` for login (5 per minute per IP)
specs/09-IMPLEMENTATION-ROADMAP.md-203-
specs/09-IMPLEMENTATION-ROADMAP.md-204-#### Step 1.8: Authorization
specs/09-IMPLEMENTATION-ROADMAP.md-205-
specs/09-IMPLEMENTATION-ROADMAP.md-206-**Files to create:**
--
specs/09-IMPLEMENTATION-ROADMAP.md-353-#### Step 3.1: Migrations (Batch 3)
specs/09-IMPLEMENTATION-ROADMAP.md-354-
specs/09-IMPLEMENTATION-ROADMAP.md-355-1. `create_themes_table` - FK: `store_id`
specs/09-IMPLEMENTATION-ROADMAP.md-356-2. `create_theme_files_table` - FK: `theme_id`
specs/09-IMPLEMENTATION-ROADMAP.md:357:3. `create_theme_settings_table` - PK: `theme_id`
specs/09-IMPLEMENTATION-ROADMAP.md-358-4. `create_pages_table` - FK: `store_id`
specs/09-IMPLEMENTATION-ROADMAP.md-359-5. `create_navigation_menus_table` - FK: `store_id`
specs/09-IMPLEMENTATION-ROADMAP.md-360-6. `create_navigation_items_table` - FK: `menu_id`
specs/09-IMPLEMENTATION-ROADMAP.md-361-
--
specs/09-IMPLEMENTATION-ROADMAP.md-580-
specs/09-IMPLEMENTATION-ROADMAP.md-581-| Type | Name | Purpose |
specs/09-IMPLEMENTATION-ROADMAP.md-582-|------|------|---------|
specs/09-IMPLEMENTATION-ROADMAP.md-583-| Job | `App\Jobs\ExpireAbandonedCheckouts` | Runs every 15 minutes via `routes/console.php`; finds checkouts with `expires_at < now()` and active status; releases reserved inventory, transitions to `expired` |
specs/09-IMPLEMENTATION-ROADMAP.md:584:| Job | `App\Jobs\CleanupAbandonedCarts` | Runs daily; marks carts with `status = active` and `updated_at < 14 days ago` as `abandoned` |
specs/09-IMPLEMENTATION-ROADMAP.md-585-
specs/09-IMPLEMENTATION-ROADMAP.md-586-#### Step 4.9: Storefront Cart/Checkout UI
specs/09-IMPLEMENTATION-ROADMAP.md-587-
specs/09-IMPLEMENTATION-ROADMAP.md-588-**Files to create:**
--
specs/09-IMPLEMENTATION-ROADMAP.md-664-**PaymentProvider interface methods:**
specs/09-IMPLEMENTATION-ROADMAP.md-665-
specs/09-IMPLEMENTATION-ROADMAP.md-666-| Method | Description |
specs/09-IMPLEMENTATION-ROADMAP.md-667-|--------|-------------|
specs/09-IMPLEMENTATION-ROADMAP.md:668:| `charge(Checkout $checkout, PaymentMethod $method, array $details): PaymentResult` | Processes payment in-process. For credit_card: validates magic card numbers, returns success/decline. For paypal: always succeeds. For bank_transfer: returns pending (deferred capture). |
specs/09-IMPLEMENTATION-ROADMAP.md-669-| `refund(Payment $payment, int $amount): RefundResult` | Issues a mock refund (always succeeds). |
specs/09-IMPLEMENTATION-ROADMAP.md-670-
specs/09-IMPLEMENTATION-ROADMAP.md-671-**MockPaymentProvider requirements:**
specs/09-IMPLEMENTATION-ROADMAP.md:672:- No external API calls, no webhooks, no redirects
specs/09-IMPLEMENTATION-ROADMAP.md:673:- Magic card numbers: `4242424242424242` (success), `4000000000000002` (decline), `4000000000009995` (insufficient funds)
specs/09-IMPLEMENTATION-ROADMAP.md-674-- Credit card / PayPal: instant capture, returns PaymentResult with status=captured
specs/09-IMPLEMENTATION-ROADMAP.md-675-- Bank transfer: deferred capture, returns PaymentResult with status=pending
specs/09-IMPLEMENTATION-ROADMAP.md-676-- Generates mock reference IDs (`mock_` + random string)
specs/09-IMPLEMENTATION-ROADMAP.md-677-
--
specs/09-IMPLEMENTATION-ROADMAP.md-848-| `Admin\Analytics\Index` | Sales chart, traffic, funnel visualization, date range |
specs/09-IMPLEMENTATION-ROADMAP.md-849-| `Admin\Search\Settings` | Synonyms, stop words, reindex button |
specs/09-IMPLEMENTATION-ROADMAP.md-850-| `Admin\Apps\Index` | App directory |
specs/09-IMPLEMENTATION-ROADMAP.md-851-| `Admin\Apps\Show` | Installed app detail |
specs/09-IMPLEMENTATION-ROADMAP.md:852:| `Admin\Developers\Index` | API token management, webhook subscriptions |
specs/09-IMPLEMENTATION-ROADMAP.md-853-
specs/09-IMPLEMENTATION-ROADMAP.md-854----
specs/09-IMPLEMENTATION-ROADMAP.md-855-
specs/09-IMPLEMENTATION-ROADMAP.md-856-### Phase 8: Search
--
specs/09-IMPLEMENTATION-ROADMAP.md-925-1. `create_apps_table`
specs/09-IMPLEMENTATION-ROADMAP.md-926-2. `create_app_installations_table` - FK: `store_id`, `app_id`
specs/09-IMPLEMENTATION-ROADMAP.md-927-3. `create_oauth_clients_table` - FK: `app_id`
specs/09-IMPLEMENTATION-ROADMAP.md-928-4. `create_oauth_tokens_table` - FK: `installation_id`
specs/09-IMPLEMENTATION-ROADMAP.md:929:5. `create_webhook_subscriptions_table` - FK: `store_id`, `app_installation_id`
specs/09-IMPLEMENTATION-ROADMAP.md:930:6. `create_webhook_deliveries_table` - FK: `subscription_id`
specs/09-IMPLEMENTATION-ROADMAP.md-931-
specs/09-IMPLEMENTATION-ROADMAP.md-932-#### Step 10.2: Webhook Service
specs/09-IMPLEMENTATION-ROADMAP.md-933-
specs/09-IMPLEMENTATION-ROADMAP.md-934-**File to create:** `App\Services\WebhookService`
--
specs/09-IMPLEMENTATION-ROADMAP.md-936-| Method | Description |
specs/09-IMPLEMENTATION-ROADMAP.md-937-|--------|-------------|
specs/09-IMPLEMENTATION-ROADMAP.md-938-| `dispatch(Store $store, string $eventType, array $payload): void` | Finds matching subscriptions, queues delivery jobs |
specs/09-IMPLEMENTATION-ROADMAP.md-939-| `sign(string $payload, string $secret): string` | HMAC-SHA256 signature |
specs/09-IMPLEMENTATION-ROADMAP.md:940:| `verify(string $payload, string $signature, string $secret): bool` | Verify incoming webhook signatures |
specs/09-IMPLEMENTATION-ROADMAP.md-941-
specs/09-IMPLEMENTATION-ROADMAP.md-942-**File to create:** `App\Jobs\DeliverWebhook`
specs/09-IMPLEMENTATION-ROADMAP.md-943-
specs/09-IMPLEMENTATION-ROADMAP.md-944-Requirements:
specs/09-IMPLEMENTATION-ROADMAP.md-945-- HTTP POST to target URL with JSON payload
specs/09-IMPLEMENTATION-ROADMAP.md-946-- HMAC signature in `X-Platform-Signature` header, event type in `X-Platform-Event`, delivery ID in `X-Platform-Delivery-Id`, timestamp in `X-Platform-Timestamp`
specs/09-IMPLEMENTATION-ROADMAP.md-947-- Retry with exponential backoff: 1 min, 5 min, 30 min, 2 h, 12 h (6 total attempts: 1 initial + 5 retries; Laravel backoff config `[60, 300, 1800, 7200, 43200]`)
specs/09-IMPLEMENTATION-ROADMAP.md:948:- Records response in `webhook_deliveries`
specs/09-IMPLEMENTATION-ROADMAP.md-949-- Circuit breaker: after 5 consecutive failures, pause subscription
specs/09-IMPLEMENTATION-ROADMAP.md-950-
specs/09-IMPLEMENTATION-ROADMAP.md-951----
specs/09-IMPLEMENTATION-ROADMAP.md-952-
--
specs/09-IMPLEMENTATION-ROADMAP.md-1159-| renders the admin login page | GET /admin/login | 200 response, sees "Login" text | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1160-| authenticates an admin user with valid credentials | POST /admin/login with valid email/password | redirected to /admin, session has auth | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1161-| rejects invalid credentials | POST /admin/login with wrong password | redirected back, session has errors, sees "Invalid credentials" | validation |
specs/09-IMPLEMENTATION-ROADMAP.md-1162-| does not reveal whether email or password is incorrect | POST /admin/login with non-existent email | same generic "Invalid credentials" error | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1163:| rate limits login attempts | make 6 POST requests with wrong credentials within 1 minute | 6th request returns 429 Too Many Requests | validation |
specs/09-IMPLEMENTATION-ROADMAP.md-1164-| regenerates session on successful login | record session ID before login; login successfully | session ID changed | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1165-| logs out and invalidates session | login, then POST /admin/logout | redirected to /admin/login, session invalidated, no longer authenticated | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1166-| redirects unauthenticated users to login | GET /admin without auth | redirected to /admin/login | authorization |
specs/09-IMPLEMENTATION-ROADMAP.md-1167-| supports remember me functionality | login with remember=true | remember_web cookie is set | happy path |
--
specs/09-IMPLEMENTATION-ROADMAP.md-1176-| renders the customer login page | GET /account/login on storefront domain | 200 response, sees login form | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1177-| authenticates a customer with valid credentials | POST login with valid email/password | redirected to /account | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1178-| rejects invalid customer credentials | POST login with wrong password | redirected back with errors | validation |
specs/09-IMPLEMENTATION-ROADMAP.md-1179-| scopes customer login to the current store | customer exists in store A but not store B; login on store B domain with store A customer credentials | login fails | authorization |
specs/09-IMPLEMENTATION-ROADMAP.md:1180:| rate limits customer login attempts | 6 attempts in 1 minute | 429 on 6th attempt | validation |
specs/09-IMPLEMENTATION-ROADMAP.md-1181-| registers a new customer | POST /account/register with name, email, password, password_confirmation | customer created in DB, auto-logged in, redirected to /account | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1182-| rejects duplicate email registration in the same store | customer with email already exists in current store; POST /account/register with same email | validation error on email | validation |
specs/09-IMPLEMENTATION-ROADMAP.md-1183-| allows same email in different stores | customer with email in store A; register same email on store B | registration succeeds | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md-1184-| logs out customer and redirects to login | login, then POST /account/logout | redirected to /account/login | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1185:| merges guest cart into customer cart on login | guest has cart with variant A qty 2; customer has cart with variant A qty 1 and variant B qty 3; login | customer cart has variant A qty 3, variant B qty 3; guest cart abandoned | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1186-
specs/09-IMPLEMENTATION-ROADMAP.md-1187----
specs/09-IMPLEMENTATION-ROADMAP.md-1188-
specs/09-IMPLEMENTATION-ROADMAP.md-1189-#### tests/Feature/Auth/SanctumTokenTest.php
--
specs/09-IMPLEMENTATION-ROADMAP.md-1289-| removes a line when quantity set to zero | update line quantity to 0 | line removed from cart | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md-1290-| removes a specific line item | cart with 2 lines, remove first | 1 line remains | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1291-| increments cart version on every mutation | create cart (v1), add line (v2), update qty (v3), remove line (v4) | cart_version = 4 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1292-| returns cart via session for guest users | create cart, store ID in session; get cart for session | same cart returned | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1293:| merges guest cart into customer cart on login | guest cart: variant A qty 2; customer cart: variant A qty 1, variant B qty 3; merge | customer cart has A qty 3, B qty 3; guest cart status=abandoned | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1294-
specs/09-IMPLEMENTATION-ROADMAP.md-1295----
specs/09-IMPLEMENTATION-ROADMAP.md-1296-
specs/09-IMPLEMENTATION-ROADMAP.md-1297-#### tests/Feature/Cart/CartApiTest.php
--
specs/09-IMPLEMENTATION-ROADMAP.md-1304-| updates line quantity via API | PUT /api/storefront/v1/carts/{id}/lines/{lineId} with quantity | 200 response, quantity updated | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1305-| removes a line via API | DELETE /api/storefront/v1/carts/{id}/lines/{lineId} | 200 response, line removed | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1306-| returns 404 for nonexistent cart | GET /api/storefront/v1/carts/nonexistent | 404 response | validation |
specs/09-IMPLEMENTATION-ROADMAP.md-1307-| returns 409 on version mismatch | cart at version 3; PUT with expected_version=2 | 409 Conflict with current cart state | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1308:| respects storefront rate limiting | make 121 requests in 1 minute | 121st returns 429 | validation |
specs/09-IMPLEMENTATION-ROADMAP.md-1309-
specs/09-IMPLEMENTATION-ROADMAP.md-1310----
specs/09-IMPLEMENTATION-ROADMAP.md-1311-
specs/09-IMPLEMENTATION-ROADMAP.md-1312-#### tests/Feature/Checkout/CheckoutFlowTest.php
--
specs/09-IMPLEMENTATION-ROADMAP.md-1437-#### tests/Feature/Payments/MockPaymentProviderTest.php
specs/09-IMPLEMENTATION-ROADMAP.md-1438-
specs/09-IMPLEMENTATION-ROADMAP.md-1439-| Test Name | Setup / Preconditions | Key Assertions | Category |
specs/09-IMPLEMENTATION-ROADMAP.md-1440-|-----------|----------------------|----------------|----------|
specs/09-IMPLEMENTATION-ROADMAP.md:1441:| charges credit card with success card number | checkout with card 4242424242424242 | PaymentResult success=true, status=captured | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1442:| declines credit card with decline card number | checkout with card 4000000000000002 | PaymentResult success=false, errorCode=card_declined | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1443:| returns insufficient funds for that card number | checkout with card 4000000000009995 | PaymentResult success=false, errorCode=insufficient_funds | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md-1444-| charges PayPal successfully | checkout with payment_method=paypal | PaymentResult success=true, status=captured | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1445-| creates pending payment for bank transfer | checkout with payment_method=bank_transfer | PaymentResult success=true, status=pending | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1446-| generates mock reference ID | any charge | provider_payment_id starts with "mock_" | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1447-
--
specs/09-IMPLEMENTATION-ROADMAP.md-1610-#### tests/Feature/Webhooks/WebhookDeliveryTest.php
specs/09-IMPLEMENTATION-ROADMAP.md-1611-
specs/09-IMPLEMENTATION-ROADMAP.md-1612-| Test Name | Setup / Preconditions | Key Assertions | Category |
specs/09-IMPLEMENTATION-ROADMAP.md-1613-|-----------|----------------------|----------------|----------|
specs/09-IMPLEMENTATION-ROADMAP.md:1614:| delivers a webhook to a subscribed URL | mock HTTP client; subscription for event_type "order.created"; dispatch event | HTTP POST made to target_url | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1615:| signs the payload with HMAC | deliver webhook | `X-Platform-Signature` header present with valid signature | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md-1616-| retries failed deliveries with exponential backoff | first delivery returns 500 | job retried, attempt_count incremented | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md-1617-| marks delivery as failed after max retries | 6 total attempts exhausted (1 initial + 5 retries) | delivery status = failed | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md-1618-| pauses subscription after circuit breaker threshold | 5 consecutive failures | subscription status = paused | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md-1619-
--
specs/09-IMPLEMENTATION-ROADMAP.md-1768-| `Admin/Analytics/Index` | Analytics dashboard |
specs/09-IMPLEMENTATION-ROADMAP.md-1769-| `Admin/Search/Settings` | Search settings |
specs/09-IMPLEMENTATION-ROADMAP.md-1770-| `Admin/Apps/Index` | App directory |
specs/09-IMPLEMENTATION-ROADMAP.md-1771-| `Admin/Apps/Show` | Installed app detail |
specs/09-IMPLEMENTATION-ROADMAP.md:1772:| `Admin/Developers/Index` | API token and webhook management |
specs/09-IMPLEMENTATION-ROADMAP.md-1773-
specs/09-IMPLEMENTATION-ROADMAP.md-1774-### Livewire Components - Storefront
specs/09-IMPLEMENTATION-ROADMAP.md-1775-
specs/09-IMPLEMENTATION-ROADMAP.md-1776-| Name | Purpose |
--
specs/09-IMPLEMENTATION-ROADMAP.md-1815-| `ThemePolicy` | Theme |
specs/09-IMPLEMENTATION-ROADMAP.md-1816-| `FulfillmentPolicy` | Fulfillment |
specs/09-IMPLEMENTATION-ROADMAP.md-1817-| `RefundPolicy` | Refund |
specs/09-IMPLEMENTATION-ROADMAP.md-1818-
specs/09-IMPLEMENTATION-ROADMAP.md:1819:### Form Requests
specs/09-IMPLEMENTATION-ROADMAP.md-1820-
specs/09-IMPLEMENTATION-ROADMAP.md-1821-| Name | Purpose |
specs/09-IMPLEMENTATION-ROADMAP.md-1822-|------|---------|
specs/09-IMPLEMENTATION-ROADMAP.md-1823-| `StoreProductRequest` | Validate product creation |
--
specs/09-IMPLEMENTATION-ROADMAP.md-1843-
specs/09-IMPLEMENTATION-ROADMAP.md-1844-| Name | Purpose |
specs/09-IMPLEMENTATION-ROADMAP.md-1845-|------|---------|
specs/09-IMPLEMENTATION-ROADMAP.md-1846-| `ProcessMediaUpload` | Resize images, update media status |
specs/09-IMPLEMENTATION-ROADMAP.md:1847:| `DeliverWebhook` | HTTP POST webhook with retry logic |
specs/09-IMPLEMENTATION-ROADMAP.md-1848-| `AggregateAnalytics` | Daily analytics aggregation |
specs/09-IMPLEMENTATION-ROADMAP.md:1849:| `CleanupAbandonedCarts` | Mark old active carts as abandoned |
specs/09-IMPLEMENTATION-ROADMAP.md-1850-| `ExpireAbandonedCheckouts` | Expire timed-out checkouts, release inventory |
specs/09-IMPLEMENTATION-ROADMAP.md-1851-
specs/09-IMPLEMENTATION-ROADMAP.md-1852-### Events
specs/09-IMPLEMENTATION-ROADMAP.md-1853-
--
specs/09-IMPLEMENTATION-ROADMAP.md-1940-### bootstrap/app.php Changes
specs/09-IMPLEMENTATION-ROADMAP.md-1941-
specs/09-IMPLEMENTATION-ROADMAP.md-1942-- Register middleware aliases: `store.resolve`, `role.check`, `auth:customer`
specs/09-IMPLEMENTATION-ROADMAP.md-1943-- Register middleware groups for `storefront` and `admin` route groups
specs/09-IMPLEMENTATION-ROADMAP.md:1944:- Configure rate limiters: `login` (5/min per IP), `api.admin` (60/min per token), `api.storefront` (120/min per IP), `checkout` (10/min per session), `search` (30/min per IP), `analytics` (60/min per IP), `webhooks` (100/min per IP)
specs/09-IMPLEMENTATION-ROADMAP.md-1945-- Configure exception handling
specs/09-IMPLEMENTATION-ROADMAP.md-1946-
specs/09-IMPLEMENTATION-ROADMAP.md-1947-### Routes Files
specs/09-IMPLEMENTATION-ROADMAP.md-1948-
--
specs/09-IMPLEMENTATION-ROADMAP.md-1955-### Service Providers
specs/09-IMPLEMENTATION-ROADMAP.md-1956-
specs/09-IMPLEMENTATION-ROADMAP.md-1957-**AppServiceProvider::boot() responsibilities:**
specs/09-IMPLEMENTATION-ROADMAP.md-1958-- Bind services to container (PaymentProvider, CartService, PricingEngine, etc.)
specs/09-IMPLEMENTATION-ROADMAP.md:1959:- Register rate limiters
specs/09-IMPLEMENTATION-ROADMAP.md-1960-- Register global scopes
specs/09-IMPLEMENTATION-ROADMAP.md-1961-- Register model observers (ProductObserver for FTS5 sync)
specs/09-IMPLEMENTATION-ROADMAP.md-1962-
specs/09-IMPLEMENTATION-ROADMAP.md-1963-### Services Directory
--
specs/09-IMPLEMENTATION-ROADMAP.md-1996-| Blade Views (Storefront) | 18-22 |
specs/09-IMPLEMENTATION-ROADMAP.md-1997-| Blade Components (Storefront) | 8-10 |
specs/09-IMPLEMENTATION-ROADMAP.md-1998-| Services | 14-16 |
specs/09-IMPLEMENTATION-ROADMAP.md-1999-| Policies | 10 |
specs/09-IMPLEMENTATION-ROADMAP.md:2000:| Form Requests | 18-20 |
specs/09-IMPLEMENTATION-ROADMAP.md-2001-| Jobs | 5 |
specs/09-IMPLEMENTATION-ROADMAP.md-2002-| Events | 10 |
specs/09-IMPLEMENTATION-ROADMAP.md-2003-| Middleware | 3 |
specs/09-IMPLEMENTATION-ROADMAP.md-2004-| Enums | 18-20 |
--
specs/05-BUSINESS-LOGIC.md-76-- All SELECT, UPDATE, DELETE queries on tenant-scoped models include the store filter.
specs/05-BUSINESS-LOGIC.md-77-- In platform-admin contexts where cross-store access is needed, the scope can be temporarily removed.
specs/05-BUSINESS-LOGIC.md-78-
specs/05-BUSINESS-LOGIC.md-79-**Models that must use this trait:**
specs/05-BUSINESS-LOGIC.md:80:products, collections, customers, orders, carts, checkouts, discounts, shipping_zones, themes, pages, navigation_menus, analytics_events, analytics_daily, webhook_subscriptions, inventory_items, search_queries.
specs/05-BUSINESS-LOGIC.md-81-
specs/05-BUSINESS-LOGIC.md-82-### 1.3 Role-Based Access Control
specs/05-BUSINESS-LOGIC.md-83-
specs/05-BUSINESS-LOGIC.md-84-**Enum:** `App\Enums\StoreUserRole` - backed by string values: `owner`, `admin`, `staff`, `support`
--
specs/05-BUSINESS-LOGIC.md-237-
specs/05-BUSINESS-LOGIC.md-238-| Operation | Trigger | Effect on `quantity_on_hand` | Effect on `quantity_reserved` | Pre-check |
specs/05-BUSINESS-LOGIC.md-239-|---|---|---|---|---|
specs/05-BUSINESS-LOGIC.md-240-| **Reserve** | Checkout enters `payment_selected` | No change | +quantity | If policy=deny, verify available >= quantity. Throw `InsufficientInventoryException` if not. |
specs/05-BUSINESS-LOGIC.md:241:| **Release** | Checkout expires or is abandoned | No change | -quantity | None |
specs/05-BUSINESS-LOGIC.md:242:| **Commit** | Payment confirmed, order created | -quantity | -quantity | None (reservation already validated) |
specs/05-BUSINESS-LOGIC.md-243-| **Restock** | Refund processed with restock flag | +quantity | No change | None |
specs/05-BUSINESS-LOGIC.md-244-
specs/05-BUSINESS-LOGIC.md-245-### 3.4 Concurrency
specs/05-BUSINESS-LOGIC.md-246-
--
specs/05-BUSINESS-LOGIC.md-257-```mermaid
specs/05-BUSINESS-LOGIC.md-258-stateDiagram-v2
specs/05-BUSINESS-LOGIC.md-259-    [*] --> active : Cart created
specs/05-BUSINESS-LOGIC.md-260-    active --> converted : Checkout completed successfully
specs/05-BUSINESS-LOGIC.md:261:    active --> abandoned : Cleanup job (14 days inactive)
specs/05-BUSINESS-LOGIC.md-262-```
specs/05-BUSINESS-LOGIC.md-263-
specs/05-BUSINESS-LOGIC.md-264-**Cart identification:**
specs/05-BUSINESS-LOGIC.md:265:- Guest users: cart ID stored in session cookie.
specs/05-BUSINESS-LOGIC.md-266-- Authenticated customers: cart linked via `customer_id` foreign key.
specs/05-BUSINESS-LOGIC.md-267-
specs/05-BUSINESS-LOGIC.md-268-The guest cart is bound to the session using the key `cart_id`. When a guest visits the storefront, a cart is created on first add-to-cart and the cart's ID is stored in `session('cart_id')`. On customer login, if a session cart exists, it is merged with any existing customer cart (the session cart's items are added to the customer cart, preferring the higher quantity for duplicate variants). After merge, the session key is cleared.
specs/05-BUSINESS-LOGIC.md-269-
--
specs/05-BUSINESS-LOGIC.md-277-            existingLine.quantity = MAX(existingLine.quantity, line.quantity)
specs/05-BUSINESS-LOGIC.md-278-            RECALCULATE existingLine amounts
specs/05-BUSINESS-LOGIC.md-279-        ELSE:
specs/05-BUSINESS-LOGIC.md-280-            MOVE line to customerCart
specs/05-BUSINESS-LOGIC.md:281:    MARK guestCart as abandoned
specs/05-BUSINESS-LOGIC.md-282-    RECALCULATE all line amounts on customerCart
specs/05-BUSINESS-LOGIC.md-283-```
specs/05-BUSINESS-LOGIC.md-284-
specs/05-BUSINESS-LOGIC.md-285-### 4.2 Cart Operations
--
specs/05-BUSINESS-LOGIC.md-338-**Job:** `App\Jobs\CleanupAbandonedCarts`
specs/05-BUSINESS-LOGIC.md-339-
specs/05-BUSINESS-LOGIC.md-340-- Schedule: daily
specs/05-BUSINESS-LOGIC.md-341-- Finds carts where status = `active` AND updated_at < (now - 14 days). The 14-day threshold is configurable via `store_settings`.
specs/05-BUSINESS-LOGIC.md:342:- Marks them as `abandoned`.
specs/05-BUSINESS-LOGIC.md-343-- Releases any reserved inventory associated with active checkouts for these carts.
specs/05-BUSINESS-LOGIC.md-344-
specs/05-BUSINESS-LOGIC.md-345----
specs/05-BUSINESS-LOGIC.md-346-
--
specs/05-BUSINESS-LOGIC.md-460-| `postal_code` | Yes | string | |
specs/05-BUSINESS-LOGIC.md-461-| `phone` | No | string | |
specs/05-BUSINESS-LOGIC.md-462-
specs/05-BUSINESS-LOGIC.md-463-**Actions:**
specs/05-BUSINESS-LOGIC.md:464:1. Validate address fields using a dedicated Form Request (`SetCheckoutAddressRequest`).
specs/05-BUSINESS-LOGIC.md-465-2. Save email and shipping_address_json on the checkout.
specs/05-BUSINESS-LOGIC.md-466-3. Copy shipping address to billing_address_json by default (can be overridden later).
specs/05-BUSINESS-LOGIC.md-467-4. Recalculate pricing (shipping zones and tax regions may change based on address).
specs/05-BUSINESS-LOGIC.md-468-5. Transition status to `addressed`.
--
specs/05-BUSINESS-LOGIC.md-929-## 10. Payment Processing (Mock PSP)
specs/05-BUSINESS-LOGIC.md-930-
specs/05-BUSINESS-LOGIC.md-931-### 10.1 Mock Payment Provider Architecture
specs/05-BUSINESS-LOGIC.md-932-
specs/05-BUSINESS-LOGIC.md:933:The platform uses a **Mock Payment Service Provider (PSP)** instead of real payment gateways. All payment processing happens in-process with no external API calls, no webhooks, and no redirects. This enables a fully functional checkout flow for development, testing, and demonstration purposes.
specs/05-BUSINESS-LOGIC.md-934-
specs/05-BUSINESS-LOGIC.md-935-**Interface:** `App\Contracts\PaymentProvider`
specs/05-BUSINESS-LOGIC.md-936-
specs/05-BUSINESS-LOGIC.md-937-Must implement:
--
specs/05-BUSINESS-LOGIC.md-947-**Enum:** `App\Enums\PaymentMethod` - values: `CreditCard` (`credit_card`), `Paypal` (`paypal`), `BankTransfer` (`bank_transfer`)
specs/05-BUSINESS-LOGIC.md-948-
specs/05-BUSINESS-LOGIC.md-949-| Method | Behavior | Capture Timing |
specs/05-BUSINESS-LOGIC.md-950-|---|---|---|
specs/05-BUSINESS-LOGIC.md:951:| `credit_card` | Simulates card authorization and immediate capture. Uses magic card numbers for test scenarios. | Auto-capture on checkout (instant) |
specs/05-BUSINESS-LOGIC.md-952-| `paypal` | Simulates PayPal approval and immediate capture. Always succeeds (no magic numbers). | Auto-capture on checkout (instant) |
specs/05-BUSINESS-LOGIC.md-953-| `bank_transfer` | No charge at checkout. Order created with `financial_status = pending`. Admin manually confirms payment receipt. | Deferred - admin confirms payment |
specs/05-BUSINESS-LOGIC.md-954-
specs/05-BUSINESS-LOGIC.md-955-### 10.3 Magic Card Numbers (Credit Card Testing)
specs/05-BUSINESS-LOGIC.md-956-
specs/05-BUSINESS-LOGIC.md:957:When payment_method is `credit_card`, the mock provider inspects the card number submitted in the checkout form to determine the outcome:
specs/05-BUSINESS-LOGIC.md-958-
specs/05-BUSINESS-LOGIC.md-959-| Card Number | Outcome | PaymentResult |
specs/05-BUSINESS-LOGIC.md-960-|---|---|---|
specs/05-BUSINESS-LOGIC.md-961-| `4242 4242 4242 4242` | Success | `success = true`, status = `captured` |
--
specs/05-BUSINESS-LOGIC.md-1311-Requirements:
specs/05-BUSINESS-LOGIC.md-1312-- Configure a dedicated `customer` guard in auth config, using session driver.
specs/05-BUSINESS-LOGIC.md-1313-- Configure a dedicated `customers` provider using the Customer model.
specs/05-BUSINESS-LOGIC.md-1314-- Login: email + password, scoped to the current store.
specs/05-BUSINESS-LOGIC.md:1315:- Password reset: standard Laravel password reset flow adapted for the customer guard and broker.
specs/05-BUSINESS-LOGIC.md-1316-
specs/05-BUSINESS-LOGIC.md-1317-**Store isolation:** Customer accounts are per-store. The same email address can exist in different stores as separate accounts. Authentication always scopes the query to the current store.
specs/05-BUSINESS-LOGIC.md-1318-
specs/05-BUSINESS-LOGIC.md-1319-### 12.3 Data Access
--
specs/05-BUSINESS-LOGIC.md-1345-## 13. Webhook Delivery System
specs/05-BUSINESS-LOGIC.md-1346-
specs/05-BUSINESS-LOGIC.md-1347-### 13.1 Subscription Model
specs/05-BUSINESS-LOGIC.md-1348-
specs/05-BUSINESS-LOGIC.md:1349:Stores (or app installations) subscribe to event types via `webhook_subscriptions`.
specs/05-BUSINESS-LOGIC.md-1350-
specs/05-BUSINESS-LOGIC.md-1351-**Supported event types:**
specs/05-BUSINESS-LOGIC.md-1352-
specs/05-BUSINESS-LOGIC.md-1353-| Event Type | Triggered By |
--
specs/05-BUSINESS-LOGIC.md-1362-| `checkout.completed` | Checkout successfully completed |
specs/05-BUSINESS-LOGIC.md-1363-
specs/05-BUSINESS-LOGIC.md-1364-### 13.2 Delivery Process
specs/05-BUSINESS-LOGIC.md-1365-
specs/05-BUSINESS-LOGIC.md:1366:**Listener:** `App\Listeners\DispatchWebhooks` - registered to listen for all domain events that have webhook counterparts.
specs/05-BUSINESS-LOGIC.md-1367-
specs/05-BUSINESS-LOGIC.md-1368-**Job:** `App\Jobs\DeliverWebhook`
specs/05-BUSINESS-LOGIC.md-1369-
specs/05-BUSINESS-LOGIC.md-1370-```mermaid
--
specs/05-BUSINESS-LOGIC.md-1375-    participant Job
specs/05-BUSINESS-LOGIC.md-1376-    participant TargetURL
specs/05-BUSINESS-LOGIC.md-1377-
specs/05-BUSINESS-LOGIC.md-1378-    DomainEvent->>Listener: Event fired (e.g., OrderCreated)
specs/05-BUSINESS-LOGIC.md:1379:    Listener->>Listener: Query webhook_subscriptions for store_id + event_type where status = active
specs/05-BUSINESS-LOGIC.md-1380-    loop For each matching subscription
specs/05-BUSINESS-LOGIC.md-1381-        Listener->>Queue: Dispatch DeliverWebhook job
specs/05-BUSINESS-LOGIC.md-1382-    end
specs/05-BUSINESS-LOGIC.md-1383-    Queue->>Job: Process job
specs/05-BUSINESS-LOGIC.md-1384-    Job->>Job: Serialize event payload as JSON
specs/05-BUSINESS-LOGIC.md-1385-    Job->>Job: Generate HMAC-SHA256 signature using subscription's signing_secret (decrypted)
specs/05-BUSINESS-LOGIC.md-1386-    Job->>TargetURL: HTTP POST with headers and payload
specs/05-BUSINESS-LOGIC.md-1387-    TargetURL-->>Job: Response
specs/05-BUSINESS-LOGIC.md:1388:    Job->>Job: Record result in webhook_deliveries
specs/05-BUSINESS-LOGIC.md-1389-```
specs/05-BUSINESS-LOGIC.md-1390-
specs/05-BUSINESS-LOGIC.md-1391-**Required HTTP headers on delivery:**
specs/05-BUSINESS-LOGIC.md-1392-
--
specs/05-BUSINESS-LOGIC.md-1417-
specs/05-BUSINESS-LOGIC.md-1418-### 13.4 Circuit Breaker
specs/05-BUSINESS-LOGIC.md-1419-
specs/05-BUSINESS-LOGIC.md-1420-- Track consecutive failures per subscription (across different events).
specs/05-BUSINESS-LOGIC.md:1421:- If a subscription's endpoint fails 5 consecutive times: automatically set `webhook_subscriptions.status = 'paused'`.
specs/05-BUSINESS-LOGIC.md-1422-- Log a warning and (future) send an admin notification.
specs/05-BUSINESS-LOGIC.md-1423-- Manual re-enable required by the merchant through the admin UI.
specs/05-BUSINESS-LOGIC.md-1424-- On each successful delivery, reset the consecutive failure counter to 0.
specs/05-BUSINESS-LOGIC.md-1425-
--
specs/05-BUSINESS-LOGIC.md-1590-Summary of all domain events and their listeners/side effects:
specs/05-BUSINESS-LOGIC.md-1591-
specs/05-BUSINESS-LOGIC.md-1592-| Event | Listeners / Side Effects |
specs/05-BUSINESS-LOGIC.md-1593-|---|---|
specs/05-BUSINESS-LOGIC.md:1594:| `OrderCreated` | Send confirmation email, dispatch webhooks (`order.created`), log analytics event |
specs/05-BUSINESS-LOGIC.md:1595:| `OrderRefunded` | Send refund notification email, dispatch webhooks (`order.refunded`), optionally restock |
specs/05-BUSINESS-LOGIC.md-1596-| `CheckoutAddressed` | Recalculate pricing |
specs/05-BUSINESS-LOGIC.md-1597-| `CheckoutShippingSelected` | Recalculate pricing |
specs/05-BUSINESS-LOGIC.md:1598:| `CheckoutCompleted` | Dispatch webhooks (`checkout.completed`) |
specs/05-BUSINESS-LOGIC.md-1599-| `CheckoutExpired` | Release reserved inventory |
specs/05-BUSINESS-LOGIC.md-1600-| `ProductStatusChanged` | Sync FTS5 index if transitioning to/from active |
specs/05-BUSINESS-LOGIC.md:1601:| `ProductCreated` | Sync FTS5 index, dispatch webhooks (`product.created`) |
specs/05-BUSINESS-LOGIC.md:1602:| `ProductUpdated` | Sync FTS5 index, dispatch webhooks (`product.updated`) |
specs/05-BUSINESS-LOGIC.md:1603:| `ProductDeleted` | Remove from FTS5 index, dispatch webhooks (`product.deleted`) |
specs/05-BUSINESS-LOGIC.md-1604-| `OrderPaid` | Update order status, commit inventory (bank transfer confirmation), auto-fulfill digital products |
specs/05-BUSINESS-LOGIC.md-1605-| `FulfillmentCreated` | Update order fulfillment status |
specs/05-BUSINESS-LOGIC.md:1606:| `FulfillmentShipped` | Send shipping notification email with tracking info, dispatch webhooks (`order.fulfilled` if fully fulfilled) |
specs/05-BUSINESS-LOGIC.md:1607:| `FulfillmentDelivered` | Update fulfillment record, dispatch webhooks |
specs/05-BUSINESS-LOGIC.md-1608-| `OrderCancelled` | Release reserved inventory, send cancellation email |
specs/05-BUSINESS-LOGIC.md-1609-
specs/05-BUSINESS-LOGIC.md-1610-All events should be placed in `App\Events`. All listeners in `App\Listeners`.
specs/05-BUSINESS-LOGIC.md-1611-
--
specs/05-BUSINESS-LOGIC.md-1614-## 18. Scheduled Jobs Registry
specs/05-BUSINESS-LOGIC.md-1615-
specs/05-BUSINESS-LOGIC.md-1616-| Job Class | Schedule | Description |
specs/05-BUSINESS-LOGIC.md-1617-|---|---|---|
specs/05-BUSINESS-LOGIC.md:1618:| `App\Jobs\CleanupAbandonedCarts` | Daily | Mark stale active carts as abandoned (14-day threshold) |
specs/05-BUSINESS-LOGIC.md-1619-| `App\Jobs\ExpireAbandonedCheckouts` | Every 15 minutes | Expire checkouts idle for 24+ hours, release reserved inventory |
specs/05-BUSINESS-LOGIC.md-1620-| `App\Jobs\AggregateAnalytics` | Daily at 01:00 UTC | Roll up raw analytics events into daily aggregates |
specs/05-BUSINESS-LOGIC.md-1621-| `App\Jobs\CancelUnpaidBankTransferOrders` | Daily | Cancel bank transfer orders that remain unpaid after configurable days (default: 7) |
specs/05-BUSINESS-LOGIC.md-1622-
--
specs/05-BUSINESS-LOGIC.md-1624-
specs/05-BUSINESS-LOGIC.md-1625-| Job Class | Description |
specs/05-BUSINESS-LOGIC.md-1626-|---|---|
specs/05-BUSINESS-LOGIC.md-1627-| `App\Jobs\ProcessMediaUpload` | Resize uploaded images to standard sizes |
specs/05-BUSINESS-LOGIC.md:1628:| `App\Jobs\DeliverWebhook` | POST webhook payload to subscriber endpoint |
specs/05-BUSINESS-LOGIC.md-1629-
specs/05-BUSINESS-LOGIC.md-1630-Schedule registration belongs in `routes/console.php`.
specs/05-BUSINESS-LOGIC.md-1631-
specs/05-BUSINESS-LOGIC.md-1632----
--
specs/05-BUSINESS-LOGIC.md-1676-| ProductStatus | Draft (`draft`), Active (`active`), Archived (`archived`) |
specs/05-BUSINESS-LOGIC.md-1677-| VariantStatus | Active (`active`), Archived (`archived`) |
specs/05-BUSINESS-LOGIC.md-1678-| DiscountType | Code (`code`), Automatic (`automatic`) |
specs/05-BUSINESS-LOGIC.md-1679-| DiscountValueType | Percent (`percent`), Fixed (`fixed`), FreeShipping (`free_shipping`) |
specs/05-BUSINESS-LOGIC.md:1680:| CartStatus | Active (`active`), Converted (`converted`), Abandoned (`abandoned`) |
specs/05-BUSINESS-LOGIC.md-1681-| CheckoutStatus | Started (`started`), Addressed (`addressed`), ShippingSelected (`shipping_selected`), PaymentSelected (`payment_selected`), Completed (`completed`), Expired (`expired`) |
specs/05-BUSINESS-LOGIC.md-1682-| PaymentMethod | CreditCard (`credit_card`), Paypal (`paypal`), BankTransfer (`bank_transfer`) |
specs/05-BUSINESS-LOGIC.md-1683-| PaymentStatus | Pending (`pending`), Captured (`captured`), Failed (`failed`), Refunded (`refunded`) |
specs/05-BUSINESS-LOGIC.md-1684-| OrderStatus | Pending (`pending`), Paid (`paid`), Fulfilled (`fulfilled`), Cancelled (`cancelled`), Refunded (`refunded`) |
--
specs/01-DATABASE-SCHEMA.md-407-- `idx_theme_files_theme_id` on (`theme_id`) - FK lookup, listing files per theme
specs/01-DATABASE-SCHEMA.md-408-
specs/01-DATABASE-SCHEMA.md-409----
specs/01-DATABASE-SCHEMA.md-410-
specs/01-DATABASE-SCHEMA.md:411:### theme_settings
specs/01-DATABASE-SCHEMA.md-412-
specs/01-DATABASE-SCHEMA.md-413-Single-row JSON configuration per theme.
specs/01-DATABASE-SCHEMA.md-414-
specs/01-DATABASE-SCHEMA.md-415-| Column | Type | Nullable | Default | Constraints | Notes |
--
specs/01-DATABASE-SCHEMA.md-535-| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
specs/01-DATABASE-SCHEMA.md-536-| customer_id | INTEGER | Yes | NULL | FK -> customers(id) ON DELETE SET NULL | Nullable for guest carts |
specs/01-DATABASE-SCHEMA.md-537-| currency | TEXT | No | 'USD' | - | ISO 4217 currency code |
specs/01-DATABASE-SCHEMA.md-538-| cart_version | INTEGER | No | 1 | - | Optimistic concurrency version counter |
specs/01-DATABASE-SCHEMA.md:539:| status | TEXT | No | 'active' | CHECK IN ('active', 'converted', 'abandoned') | |
specs/01-DATABASE-SCHEMA.md-540-| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
specs/01-DATABASE-SCHEMA.md-541-| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
specs/01-DATABASE-SCHEMA.md-542-
specs/01-DATABASE-SCHEMA.md-543-**Indexes:**
specs/01-DATABASE-SCHEMA.md-544-
specs/01-DATABASE-SCHEMA.md-545-- `idx_carts_store_id` on (`store_id`) - FK lookup
specs/01-DATABASE-SCHEMA.md-546-- `idx_carts_customer_id` on (`customer_id`) - Finding carts by customer
specs/01-DATABASE-SCHEMA.md:547:- `idx_carts_store_status` on (`store_id`, `status`) - Filtering carts by status (e.g. abandoned cart recovery)
specs/01-DATABASE-SCHEMA.md-548-
specs/01-DATABASE-SCHEMA.md-549-**Notes:** `cart_version` is incremented on every mutation and used for optimistic concurrency control.
specs/01-DATABASE-SCHEMA.md-550-
specs/01-DATABASE-SCHEMA.md-551----
--
specs/01-DATABASE-SCHEMA.md-1044-- `idx_oauth_tokens_expires_at` on (`expires_at`) - Cleanup of expired tokens
specs/01-DATABASE-SCHEMA.md-1045-
specs/01-DATABASE-SCHEMA.md-1046----
specs/01-DATABASE-SCHEMA.md-1047-
specs/01-DATABASE-SCHEMA.md:1048:### webhook_subscriptions
specs/01-DATABASE-SCHEMA.md-1049-
specs/01-DATABASE-SCHEMA.md:1050:Registered webhook endpoints for event delivery.
specs/01-DATABASE-SCHEMA.md-1051-
specs/01-DATABASE-SCHEMA.md-1052-| Column | Type | Nullable | Default | Constraints | Notes |
specs/01-DATABASE-SCHEMA.md-1053-|---|---|---|---|---|---|
specs/01-DATABASE-SCHEMA.md-1054-| id | INTEGER | No | autoincrement | PRIMARY KEY | |
--
specs/01-DATABASE-SCHEMA.md-1060-| status | TEXT | No | 'active' | CHECK IN ('active', 'paused', 'disabled') | |
specs/01-DATABASE-SCHEMA.md-1061-
specs/01-DATABASE-SCHEMA.md-1062-**Indexes:**
specs/01-DATABASE-SCHEMA.md-1063-
specs/01-DATABASE-SCHEMA.md:1064:- `idx_webhook_subscriptions_store_id` on (`store_id`) - FK lookup
specs/01-DATABASE-SCHEMA.md:1065:- `idx_webhook_subscriptions_store_event` on (`store_id`, `event_type`) - Finding subscriptions for a given event
specs/01-DATABASE-SCHEMA.md:1066:- `idx_webhook_subscriptions_installation` on (`app_installation_id`) - Listing subscriptions per app installation
specs/01-DATABASE-SCHEMA.md-1067-
specs/01-DATABASE-SCHEMA.md-1068----
specs/01-DATABASE-SCHEMA.md-1069-
specs/01-DATABASE-SCHEMA.md:1070:### webhook_deliveries
specs/01-DATABASE-SCHEMA.md-1071-
specs/01-DATABASE-SCHEMA.md:1072:Delivery log for webhook attempts.
specs/01-DATABASE-SCHEMA.md-1073-
specs/01-DATABASE-SCHEMA.md-1074-| Column | Type | Nullable | Default | Constraints | Notes |
specs/01-DATABASE-SCHEMA.md-1075-|---|---|---|---|---|---|
specs/01-DATABASE-SCHEMA.md-1076-| id | INTEGER | No | autoincrement | PRIMARY KEY | |
specs/01-DATABASE-SCHEMA.md:1077:| subscription_id | INTEGER | No | - | FK -> webhook_subscriptions(id) ON DELETE CASCADE | Parent subscription |
specs/01-DATABASE-SCHEMA.md-1078-| event_id | TEXT | No | - | - | Unique event identifier |
specs/01-DATABASE-SCHEMA.md-1079-| attempt_count | INTEGER | No | 1 | - | Number of delivery attempts |
specs/01-DATABASE-SCHEMA.md-1080-| status | TEXT | No | 'pending' | CHECK IN ('pending', 'success', 'failed') | |
specs/01-DATABASE-SCHEMA.md-1081-| last_attempt_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
--
specs/01-DATABASE-SCHEMA.md-1083-| response_body_snippet | TEXT | Yes | NULL | - | Truncated response body for debugging |
specs/01-DATABASE-SCHEMA.md-1084-
specs/01-DATABASE-SCHEMA.md-1085-**Indexes:**
specs/01-DATABASE-SCHEMA.md-1086-
specs/01-DATABASE-SCHEMA.md:1087:- `idx_webhook_deliveries_subscription_id` on (`subscription_id`) - FK lookup, listing deliveries per subscription
specs/01-DATABASE-SCHEMA.md:1088:- `idx_webhook_deliveries_event_id` on (`event_id`) - Deduplication and lookup by event
specs/01-DATABASE-SCHEMA.md:1089:- `idx_webhook_deliveries_status` on (`status`) - Finding pending/failed deliveries for retry
specs/01-DATABASE-SCHEMA.md:1090:- `idx_webhook_deliveries_last_attempt` on (`last_attempt_at`) - Time-based retry scheduling
specs/01-DATABASE-SCHEMA.md-1091-
specs/01-DATABASE-SCHEMA.md-1092----
specs/01-DATABASE-SCHEMA.md-1093-
specs/01-DATABASE-SCHEMA.md-1094-## Laravel Framework Tables
--
specs/01-DATABASE-SCHEMA.md-1175-| expires_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
specs/01-DATABASE-SCHEMA.md-1176-| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
specs/01-DATABASE-SCHEMA.md-1177-| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
specs/01-DATABASE-SCHEMA.md-1178-
specs/01-DATABASE-SCHEMA.md:1179:### password_reset_tokens
specs/01-DATABASE-SCHEMA.md-1180-
specs/01-DATABASE-SCHEMA.md-1181-| Column | Type | Nullable | Default | Constraints | Notes |
specs/01-DATABASE-SCHEMA.md-1182-|---|---|---|---|---|---|
specs/01-DATABASE-SCHEMA.md-1183-| email | TEXT | No | - | PRIMARY KEY | Admin user email |
--
specs/01-DATABASE-SCHEMA.md-1185-| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
specs/01-DATABASE-SCHEMA.md-1186-
specs/01-DATABASE-SCHEMA.md-1187----
specs/01-DATABASE-SCHEMA.md-1188-
specs/01-DATABASE-SCHEMA.md:1189:### customer_password_reset_tokens
specs/01-DATABASE-SCHEMA.md-1190-
specs/01-DATABASE-SCHEMA.md-1191-| Column | Type | Nullable | Default | Constraints | Notes |
specs/01-DATABASE-SCHEMA.md-1192-|---|---|---|---|---|---|
specs/01-DATABASE-SCHEMA.md-1193-| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
--
specs/01-DATABASE-SCHEMA.md-1320-```mermaid
specs/01-DATABASE-SCHEMA.md-1321-erDiagram
specs/01-DATABASE-SCHEMA.md-1322-    stores ||--o{ themes : "has"
specs/01-DATABASE-SCHEMA.md-1323-    themes ||--o{ theme_files : "contains"
specs/01-DATABASE-SCHEMA.md:1324:    themes ||--|| theme_settings : "configured by"
specs/01-DATABASE-SCHEMA.md-1325-    stores ||--o{ pages : "has"
specs/01-DATABASE-SCHEMA.md-1326-    stores ||--o{ navigation_menus : "has"
specs/01-DATABASE-SCHEMA.md-1327-    navigation_menus ||--o{ navigation_items : "contains"
specs/01-DATABASE-SCHEMA.md-1328-
--
specs/01-DATABASE-SCHEMA.md-1337-        INTEGER theme_id FK
specs/01-DATABASE-SCHEMA.md-1338-        TEXT path
specs/01-DATABASE-SCHEMA.md-1339-        TEXT storage_key
specs/01-DATABASE-SCHEMA.md-1340-    }
specs/01-DATABASE-SCHEMA.md:1341:    theme_settings {
specs/01-DATABASE-SCHEMA.md-1342-        INTEGER theme_id PK
specs/01-DATABASE-SCHEMA.md-1343-        TEXT settings_json
specs/01-DATABASE-SCHEMA.md-1344-    }
specs/01-DATABASE-SCHEMA.md-1345-    pages {
--
specs/01-DATABASE-SCHEMA.md-1435-    apps ||--o{ app_installations : "installed via"
specs/01-DATABASE-SCHEMA.md-1436-    stores ||--o{ app_installations : "has"
specs/01-DATABASE-SCHEMA.md-1437-    apps ||--o{ oauth_clients : "authenticates via"
specs/01-DATABASE-SCHEMA.md-1438-    app_installations ||--o{ oauth_tokens : "issues"
specs/01-DATABASE-SCHEMA.md:1439:    stores ||--o{ webhook_subscriptions : "registers"
specs/01-DATABASE-SCHEMA.md:1440:    app_installations ||--o{ webhook_subscriptions : "owns"
specs/01-DATABASE-SCHEMA.md:1441:    webhook_subscriptions ||--o{ webhook_deliveries : "delivers"
specs/01-DATABASE-SCHEMA.md-1442-
specs/01-DATABASE-SCHEMA.md-1443-    analytics_events {
specs/01-DATABASE-SCHEMA.md-1444-        INTEGER id PK
specs/01-DATABASE-SCHEMA.md-1445-        INTEGER store_id FK
--
specs/01-DATABASE-SCHEMA.md-1462-        INTEGER store_id FK
specs/01-DATABASE-SCHEMA.md-1463-        INTEGER app_id FK
specs/01-DATABASE-SCHEMA.md-1464-        TEXT status
specs/01-DATABASE-SCHEMA.md-1465-    }
specs/01-DATABASE-SCHEMA.md:1466:    webhook_subscriptions {
specs/01-DATABASE-SCHEMA.md-1467-        INTEGER id PK
specs/01-DATABASE-SCHEMA.md-1468-        INTEGER store_id FK
specs/01-DATABASE-SCHEMA.md-1469-        TEXT event_type
specs/01-DATABASE-SCHEMA.md-1470-        TEXT target_url
specs/01-DATABASE-SCHEMA.md-1471-    }
specs/01-DATABASE-SCHEMA.md:1472:    webhook_deliveries {
specs/01-DATABASE-SCHEMA.md-1473-        INTEGER id PK
specs/01-DATABASE-SCHEMA.md-1474-        INTEGER subscription_id FK
specs/01-DATABASE-SCHEMA.md-1475-        TEXT event_id
specs/01-DATABASE-SCHEMA.md-1476-        TEXT status
--
specs/01-DATABASE-SCHEMA.md-1546-        customer_addresses
specs/01-DATABASE-SCHEMA.md-1547-        carts
specs/01-DATABASE-SCHEMA.md-1548-        navigation_items
specs/01-DATABASE-SCHEMA.md-1549-        theme_files
specs/01-DATABASE-SCHEMA.md:1550:        theme_settings
specs/01-DATABASE-SCHEMA.md-1551-        search_queries
specs/01-DATABASE-SCHEMA.md-1552-        shipping_rates
specs/01-DATABASE-SCHEMA.md-1553-        oauth_tokens
specs/01-DATABASE-SCHEMA.md:1554:        webhook_subscriptions
specs/01-DATABASE-SCHEMA.md-1555-    end
specs/01-DATABASE-SCHEMA.md-1556-
specs/01-DATABASE-SCHEMA.md-1557-    subgraph "Batch 5 - Depends on Batch 4"
specs/01-DATABASE-SCHEMA.md-1558-        product_option_values
--
specs/01-DATABASE-SCHEMA.md-1561-        checkouts
specs/01-DATABASE-SCHEMA.md-1562-        orders
specs/01-DATABASE-SCHEMA.md-1563-        analytics_events
specs/01-DATABASE-SCHEMA.md-1564-        analytics_daily
specs/01-DATABASE-SCHEMA.md:1565:        webhook_deliveries
specs/01-DATABASE-SCHEMA.md-1566-    end
specs/01-DATABASE-SCHEMA.md-1567-
specs/01-DATABASE-SCHEMA.md-1568-    subgraph "Batch 6 - Depends on Batch 5"
specs/01-DATABASE-SCHEMA.md-1569-        variant_option_values
--
specs/01-DATABASE-SCHEMA.md-1604-    stores --> carts
specs/01-DATABASE-SCHEMA.md-1605-    customers --> carts
specs/01-DATABASE-SCHEMA.md-1606-    navigation_menus --> navigation_items
specs/01-DATABASE-SCHEMA.md-1607-    themes --> theme_files
specs/01-DATABASE-SCHEMA.md:1608:    themes --> theme_settings
specs/01-DATABASE-SCHEMA.md-1609-    stores --> search_queries
specs/01-DATABASE-SCHEMA.md-1610-    shipping_zones --> shipping_rates
specs/01-DATABASE-SCHEMA.md-1611-    app_installations --> oauth_tokens
specs/01-DATABASE-SCHEMA.md:1612:    stores --> webhook_subscriptions
specs/01-DATABASE-SCHEMA.md:1613:    app_installations --> webhook_subscriptions
specs/01-DATABASE-SCHEMA.md-1614-    product_options --> product_option_values
specs/01-DATABASE-SCHEMA.md-1615-    stores --> inventory_items
specs/01-DATABASE-SCHEMA.md-1616-    product_variants --> inventory_items
specs/01-DATABASE-SCHEMA.md-1617-    carts --> cart_lines
--
specs/01-DATABASE-SCHEMA.md-1623-    customers --> orders
specs/01-DATABASE-SCHEMA.md-1624-    stores --> analytics_events
specs/01-DATABASE-SCHEMA.md-1625-    customers --> analytics_events
specs/01-DATABASE-SCHEMA.md-1626-    stores --> analytics_daily
specs/01-DATABASE-SCHEMA.md:1627:    webhook_subscriptions --> webhook_deliveries
specs/01-DATABASE-SCHEMA.md-1628-    product_variants --> variant_option_values
specs/01-DATABASE-SCHEMA.md-1629-    product_option_values --> variant_option_values
specs/01-DATABASE-SCHEMA.md-1630-    orders --> order_lines
specs/01-DATABASE-SCHEMA.md-1631-    products --> order_lines
--
specs/01-DATABASE-SCHEMA.md-1642-
specs/01-DATABASE-SCHEMA.md-1643-- **Batch 1** (no dependencies): `organizations`, `apps`
specs/01-DATABASE-SCHEMA.md-1644-- **Batch 2** (depends on Batch 1): `stores` (references organizations), `users` (standalone, but logically grouped here)
specs/01-DATABASE-SCHEMA.md-1645-- **Batch 3** (depends on Batch 2): `store_domains`, `store_users`, `store_settings`, `customers`, `themes`, `pages`, `navigation_menus`, `search_settings`, `shipping_zones`, `tax_settings`, `discounts`, `products`, `collections`, `app_installations`, `oauth_clients`
specs/01-DATABASE-SCHEMA.md:1646:- **Batch 4** (depends on Batch 3): `product_options`, `product_variants`, `product_media`, `collection_products`, `customer_addresses`, `carts`, `navigation_items`, `theme_files`, `theme_settings`, `search_queries`, `shipping_rates`, `oauth_tokens`, `webhook_subscriptions`
specs/01-DATABASE-SCHEMA.md:1647:- **Batch 5** (depends on Batch 4): `product_option_values`, `inventory_items`, `cart_lines`, `checkouts`, `orders`, `analytics_events`, `analytics_daily`, `webhook_deliveries`
specs/01-DATABASE-SCHEMA.md-1648-- **Batch 6** (depends on Batch 5): `variant_option_values`, `order_lines`, `payments`, `fulfillments`
specs/01-DATABASE-SCHEMA.md-1649-- **Batch 7** (depends on Batch 6): `refunds`, `fulfillment_lines`
specs/01-DATABASE-SCHEMA.md-1650-
specs/01-DATABASE-SCHEMA.md-1651----
--
specs/01-DATABASE-SCHEMA.md-1659-3. **Foreign key indexes** - Every foreign key column has an index to avoid full table scans during JOIN and CASCADE operations. SQLite does not auto-create indexes for foreign keys like some other databases.
specs/01-DATABASE-SCHEMA.md-1660-
specs/01-DATABASE-SCHEMA.md-1661-4. **Composite primary keys for pivot tables** - Tables like `store_users`, `collection_products`, `variant_option_values`, and `analytics_daily` use composite primary keys to naturally enforce uniqueness and serve as their own covering index for the most common query pattern.
specs/01-DATABASE-SCHEMA.md-1662-
specs/01-DATABASE-SCHEMA.md:1663:5. **Date-based indexes for time series** - Tables with temporal queries (orders, analytics, search queries, webhook deliveries) include indexes on date/timestamp columns scoped by store for efficient range scans.
specs/01-DATABASE-SCHEMA.md-1664-
specs/01-DATABASE-SCHEMA.md-1665-6. **Selective indexing** - Not every column is indexed. Columns queried only via their parent's primary key (e.g., `cart_lines` always accessed through `cart_id`) rely on the foreign key index alone. This keeps write overhead and database size manageable.
specs/01-DATABASE-SCHEMA.md-1666-
specs/01-DATABASE-SCHEMA.md-1667----
--
specs/01-DATABASE-SCHEMA.md-1671-| Epic | Tables | Count |
specs/01-DATABASE-SCHEMA.md-1672-|---|---|---|
specs/01-DATABASE-SCHEMA.md-1673-| 1 - Foundation and Multi-Store Tenancy | organizations, stores, store_domains, users, store_users, store_settings | 6 |
specs/01-DATABASE-SCHEMA.md-1674-| 2 - Catalog, Collections, Inventory, Media | products, product_options, product_option_values, product_variants, variant_option_values, inventory_items, collections, collection_products, product_media | 9 |
specs/01-DATABASE-SCHEMA.md:1675:| 3 - Themes, Storefront, Localization | themes, theme_files, theme_settings, pages, navigation_menus, navigation_items | 6 |
specs/01-DATABASE-SCHEMA.md-1676-| 4 - Search | search_settings, search_queries | 2 |
specs/01-DATABASE-SCHEMA.md-1677-| 5 - Cart, Checkout, Discounts, Shipping, Taxes | carts, cart_lines, checkouts, shipping_zones, shipping_rates, tax_settings, discounts | 7 |
specs/01-DATABASE-SCHEMA.md-1678-| 6 - Payments, Orders, Fulfillment, Customers | customers, customer_addresses, orders, order_lines, payments, refunds, fulfillments, fulfillment_lines | 8 |
specs/01-DATABASE-SCHEMA.md-1679-| 7 - Analytics | analytics_events, analytics_daily | 2 |
specs/01-DATABASE-SCHEMA.md:1680:| 8 - Apps and Webhooks | apps, app_installations, oauth_clients, oauth_tokens, webhook_subscriptions, webhook_deliveries | 6 |
specs/01-DATABASE-SCHEMA.md-1681-| **Total application tables** | | **46** |
specs/01-DATABASE-SCHEMA.md:1682:| Laravel framework tables | sessions, cache, cache_locks, jobs, job_batches, failed_jobs, personal_access_tokens, password_reset_tokens, customer_password_reset_tokens | 9 |
specs/01-DATABASE-SCHEMA.md-1683-| **Grand total** | | **55** |
--
specs/02-API-ROUTES.md-1-# API and Routing Specification
specs/02-API-ROUTES.md-2-
specs/02-API-ROUTES.md:3:This document defines every route, endpoint, middleware stack, request/response schema, and rate limit for the e-commerce platform. The admin panel is built with Livewire v4 + Flux UI (server-rendered), while the storefront uses Blade + Livewire. REST API endpoints serve cart/checkout operations and programmatic admin access.
specs/02-API-ROUTES.md-4-
specs/02-API-ROUTES.md-5-All monetary amounts are stored and transmitted in **minor units** (cents). All IDs are auto-incrementing integers. All monetary amounts are in minor units (cents).
specs/02-API-ROUTES.md-6-
specs/02-API-ROUTES.md-7----
--
specs/02-API-ROUTES.md-17-| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
specs/02-API-ROUTES.md-18-| POST | `/admin/login` | Process admin login | Handled by Livewire action |
specs/02-API-ROUTES.md-19-| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
specs/02-API-ROUTES.md-20-| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
specs/02-API-ROUTES.md:21:| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
specs/02-API-ROUTES.md-22-| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
specs/02-API-ROUTES.md-23-| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |
specs/02-API-ROUTES.md-24-
specs/02-API-ROUTES.md-25-Rate limit: `login` tier (5 per minute per IP).
--
specs/02-API-ROUTES.md-118-
specs/02-API-ROUTES.md-119-| Method | Path | Description | Component |
specs/02-API-ROUTES.md-120-|--------|------|-------------|-----------|
specs/02-API-ROUTES.md-121-| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
specs/02-API-ROUTES.md:122:| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |
specs/02-API-ROUTES.md-123-
specs/02-API-ROUTES.md-124-#### Developers
specs/02-API-ROUTES.md-125-
specs/02-API-ROUTES.md-126-| Method | Path | Description | Component |
specs/02-API-ROUTES.md-127-|--------|------|-------------|-----------|
specs/02-API-ROUTES.md:128:| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |
specs/02-API-ROUTES.md-129-
specs/02-API-ROUTES.md:130:> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.
specs/02-API-ROUTES.md-131-
specs/02-API-ROUTES.md-132-#### Analytics
specs/02-API-ROUTES.md-133-
specs/02-API-ROUTES.md-134-| Method | Path | Description | Component |
--
specs/02-API-ROUTES.md-255-#### GET /api/storefront/v1/carts/{cartId}
specs/02-API-ROUTES.md-256-
specs/02-API-ROUTES.md-257-Retrieve a cart with all lines and computed totals.
specs/02-API-ROUTES.md-258-
specs/02-API-ROUTES.md:259:- **Authentication:** None (cart ID acts as token)
specs/02-API-ROUTES.md-260-- **Rate limit:** `api.storefront` (120/min per IP)
specs/02-API-ROUTES.md-261-
specs/02-API-ROUTES.md-262-**URL parameters:**
specs/02-API-ROUTES.md-263-
--
specs/02-API-ROUTES.md-846-### 2.3 Payment Endpoint
specs/02-API-ROUTES.md-847-
specs/02-API-ROUTES.md-848-#### POST /api/storefront/v1/checkouts/{checkoutId}/pay
specs/02-API-ROUTES.md-849-
specs/02-API-ROUTES.md:850:Process payment for a checkout using the selected payment method. All payment processing happens in-process via the Mock PSP - no external API calls, no redirects, no webhooks.
specs/02-API-ROUTES.md-851-
specs/02-API-ROUTES.md-852-- **Authentication:** None
specs/02-API-ROUTES.md-853-- **Rate limit:** `checkout` (10/min per session)
specs/02-API-ROUTES.md-854-
--
specs/02-API-ROUTES.md-943-  "error_code": "card_declined"
specs/02-API-ROUTES.md-944-}
specs/02-API-ROUTES.md-945-```
specs/02-API-ROUTES.md-946-
specs/02-API-ROUTES.md:947:**Magic card number error codes:**
specs/02-API-ROUTES.md-948-
specs/02-API-ROUTES.md-949-| Card Number | Error Code | Message |
specs/02-API-ROUTES.md-950-|---|---|---|
specs/02-API-ROUTES.md-951-| `4000000000000002` | `card_declined` | "Your card was declined." |
--
specs/02-API-ROUTES.md-2895----
specs/02-API-ROUTES.md-2896-
specs/02-API-ROUTES.md-2897-## 4. Webhook Receiver Endpoints
specs/02-API-ROUTES.md-2898-
specs/02-API-ROUTES.md:2899:> **Note:** No inbound webhook endpoints are needed. All payment processing happens in-process via the Mock PSP -- there are no external payment provider callbacks. The webhook infrastructure defined in this section is reserved for **outbound** app webhooks (see Section 5, OAuth / App Ecosystem).
specs/02-API-ROUTES.md-2900-
specs/02-API-ROUTES.md-2901----
specs/02-API-ROUTES.md-2902-
specs/02-API-ROUTES.md-2903-## 5. OAuth Endpoints (App Ecosystem)
--
specs/02-API-ROUTES.md-3082-| `api.storefront` | 120 requests | 1 minute | IP address | Storefront REST API (carts, etc.) |
specs/02-API-ROUTES.md-3083-| `checkout` | 10 requests | 1 minute | Session ID | Checkout creation and mutation endpoints |
specs/02-API-ROUTES.md-3084-| `search` | 30 requests | 1 minute | IP address | Search and autocomplete endpoints |
specs/02-API-ROUTES.md-3085-| `analytics` | 60 requests | 1 minute | IP address | Analytics event ingestion |
specs/02-API-ROUTES.md:3086:| `webhooks` | 100 requests | 1 minute | IP address | Reserved for future outbound app webhook endpoints |
specs/02-API-ROUTES.md-3087-
specs/02-API-ROUTES.md-3088-### Rate Limit Response
specs/02-API-ROUTES.md-3089-
specs/02-API-ROUTES.md:3090:When a rate limit is exceeded, the server returns `429 Too Many Requests`:
specs/02-API-ROUTES.md-3091-
specs/02-API-ROUTES.md-3092-```json
specs/02-API-ROUTES.md-3093-{
specs/02-API-ROUTES.md-3094-  "message": "Too many requests. Please try again later.",
--
specs/02-API-ROUTES.md-3101-| Header | Description |
specs/02-API-ROUTES.md-3102-|--------|-------------|
specs/02-API-ROUTES.md-3103-| `X-RateLimit-Limit` | Maximum requests allowed in the window |
specs/02-API-ROUTES.md-3104-| `X-RateLimit-Remaining` | Requests remaining in the current window |
specs/02-API-ROUTES.md:3105:| `Retry-After` | Seconds until the rate limit resets (only on 429) |
specs/02-API-ROUTES.md-3106-
specs/02-API-ROUTES.md-3107-### Per-Endpoint Rate Limit Mapping
specs/02-API-ROUTES.md-3108-
specs/02-API-ROUTES.md-3109-| Endpoint Group | Tier |
--
specs/02-API-ROUTES.md-3126-| `GET /api/admin/v1/*` | `api.admin` |
specs/02-API-ROUTES.md-3127-| `POST /api/admin/v1/*` | `api.admin` |
specs/02-API-ROUTES.md-3128-| `PUT /api/admin/v1/*` | `api.admin` |
specs/02-API-ROUTES.md-3129-| `DELETE /api/admin/v1/*` | `api.admin` |
specs/02-API-ROUTES.md:3130:| *(no inbound webhook endpoints - mock PSP)* | - |
specs/02-API-ROUTES.md-3131-
specs/02-API-ROUTES.md-3132----
specs/02-API-ROUTES.md-3133-
specs/02-API-ROUTES.md-3134-## 8. Checkout State Machine
--
specs/02-API-ROUTES.md-3158-| `started` | `addressed` | PUT /checkouts/{id}/address with valid address |
specs/02-API-ROUTES.md-3159-| `addressed` | `shipping_selected` | PUT /checkouts/{id}/shipping-method with valid method |
specs/02-API-ROUTES.md-3160-| `shipping_selected` | `payment_selected` | PUT /checkouts/{id}/payment-method with valid method |
specs/02-API-ROUTES.md-3161-| `payment_selected` | `completed` | POST /checkouts/{id}/pay - Mock PSP processes payment in-process (instant for card/paypal, deferred for bank_transfer) |
specs/02-API-ROUTES.md:3162:| `payment_selected` | `shipping_selected` | Payment declined by Mock PSP (magic card number), customer retries |
specs/02-API-ROUTES.md-3163-| `*` (any active) | `expired` | Checkout expires_at reached (24-hour TTL, checked via scheduled job) |
specs/02-API-ROUTES.md-3164-
specs/02-API-ROUTES.md-3165-Applying or removing a discount does not change the checkout status but triggers totals recalculation at the current state.
specs/02-API-ROUTES.md-3166-
--
specs/02-API-ROUTES.md-3170-
specs/02-API-ROUTES.md-3171-- **Storefront API:** Versioned via URL path (`/api/storefront/v1/`). Breaking changes require a new version.
specs/02-API-ROUTES.md-3172-- **Admin API:** Versioned via URL path (`/api/admin/v1/`). Same strategy.
specs/02-API-ROUTES.md-3173-- **App API:** Versioned via URL path (`/api/apps/v1/`).
specs/02-API-ROUTES.md:3174:- **Outbound webhook payloads:** Include a `api_version` field in outbound webhook deliveries to app installations. Each app installation pins to a version.
specs/02-API-ROUTES.md-3175-
specs/02-API-ROUTES.md-3176-Non-breaking changes (new optional fields, new endpoints) can be added to existing versions. Breaking changes (removed fields, changed types, renamed endpoints) require version bumps.
specs/02-API-ROUTES.md-3177-
specs/02-API-ROUTES.md-3178----

formatted_output:
Warning: truncated output (original token count: 21646)
Total output lines: 1086

specs/08-PLAYWRIGHT-E2E-PLAN.md-1772-16. Verify "Standard Shipping" and "4.99" are displayed
specs/08-PLAYWRIGHT-E2E-PLAN.md-1773-17. Click "Standard Shipping" to select it
specs/08-PLAYWRIGHT-E2E-PLAN.md-1774-18. Click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-1775-19. Verify "Credit Card" is pre-selected as payment method
specs/08-PLAYWRIGHT-E2E-PLAN.md:1776:20. Enter card number: `4242 4242 4242 4242`
specs/08-PLAYWRIGHT-E2E-PLAN.md-1777-21. Enter cardholder name: `Test Buyer`
specs/08-PLAYWRIGHT-E2E-PLAN.md-1778-22. Enter expiry: `12/28`
specs/08-PLAYWRIGHT-E2E-PLAN.md-1779-23. Enter CVC: `123`
specs/08-PLAYWRIGHT-E2E-PLAN.md-1780-24. Verify total is 29.98 (24.99 + 4.99 shipping)
--
specs/08-PLAYWRIGHT-E2E-PLAN.md-2014-5. Enter email: `test@example.com`, click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2015-6. Fill shipping address (DE), click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2016-7. Select "Standard Shipping", click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2017-8. Verify "Credit Card" is selected
specs/08-PLAYWRIGHT-E2E-PLAN.md:2018:9. Enter card number: `4000 0000 0000 0002` (magic decline card)
specs/08-PLAYWRIGHT-E2E-PLAN.md-2019-10. Enter cardholder name: `Test Buyer`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2020-11. Enter expiry: `12/28`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2021-12. Enter CVC: `123`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2022-13. Click "Pay now"
--
specs/08-PLAYWRIGHT-E2E-PLAN.md-2039-4. Navigate to `/cart`, click "Checkout"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2040-5. Enter email: `test@example.com`, click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2041-6. Fill shipping address (DE), click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md-2042-7. Select "Standard Shipping", click "Continue"
specs/08-PLAYWRIGHT-E2E-PLAN.md:2043:8. Enter card number: `4000 0000 0000 9995` (magic insufficient funds card)
specs/08-PLAYWRIGHT-E2E-PLAN.md-2044-9. Enter cardholder name: `Test Buyer`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2045-10. Enter expiry: `12/28`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2046-11. Enter CVC: `123`
specs/08-PLAYWRIGHT-E2E-PLAN.md-2047-12. Click "Pay now"
--
specs/08-PLAYWRIGHT-E2E-PLAN.md-3163-3. **Pest v4 syntax:** All tests use Pest's `it()` function, NOT class-based PHPUnit. No `$this->` within closures unless accessing Laravel test methods. Use `visit()`, `fill()`, `click()`, `assertSee()` as the primary browser interaction API.
specs/08-PLAYWRIGHT-E2E-PLAN.md-3164-
specs/08-PLAYWRIGHT-E2E-PLAN.md-3165-4. **Variant selection:** When adding products to cart, always select variant options first (e.g., click "M" for size, click "Black" for color) before clicking "Add to cart".
specs/08-PLAYWRIGHT-E2E-PLAN.md-3166-
specs/08-PLAYWRIGHT-E2E-PLAN.md:3167:5. **Payment testing:** All payments are processed in-process via the Mock PSP. For credit card tests, use the magic card number `4242 4242 4242 4242` for success, `4000 0000 0000 0002` for decline, and `4000 0000 0000 9995` for insufficient funds. No external API calls, webhooks, or redirects are involved.
specs/08-PLAYWRIGHT-E2E-PLAN.md-3168-
specs/08-PLAYWRIGHT-E2E-PLAN.md-3169-6. **Viewport testing (Suite 13):** The coding agent should use Pest v4 / Playwright's viewport configuration to set browser dimensions. The exact API for setting viewport in Pest v4 browser tests should be looked up via documentation search.
specs/08-PLAYWRIGHT-E2E-PLAN.md-3170-
specs/08-PLAYWRIGHT-E2E-PLAN.md-3171-7. **Form field names:** The field names used in `fill()` calls (e.g., "email", "password", "first_name", "variants.0.price") are logical names. The coding agent must use the actual HTML field names/labels as rendered by the Livewire components.
--
specs/06-AUTH-AND-SECURITY.md-30-    participant S as Session
specs/06-AUTH-AND-SECURITY.md-31-
specs/06-AUTH-AND-SECURITY.md-32-    B->>LW: Submit email + password
specs/06-AUTH-AND-SECURITY.md-33-    LW->>LW: Validate input (email required, password required)
specs/06-AUTH-AND-SECURITY.md:34:    LW->>LW: Check rate limiter (5/min per IP)
specs/06-AUTH-AND-SECURITY.md-35-    alt Rate limit exceeded
specs/06-AUTH-AND-SECURITY.md-36-        LW-->>B: Flash "Too many attempts. Try again in X seconds."
specs/06-AUTH-AND-SECURITY.md-37-    end
specs/06-AUTH-AND-SECURITY.md-38-    LW->>Auth: attempt(credentials, remember)
--
specs/06-AUTH-AND-SECURITY.md-52-
specs/06-AUTH-AND-SECURITY.md-53-- The login form collects `email` and `password`.
specs/06-AUTH-AND-SECURITY.md-54-- On successful login, the session MUST be regenerated to prevent session fixation attacks.
specs/06-AUTH-AND-SECURITY.md-55-- On failed login, only the generic message "Invalid credentials" is shown. The system must not indicate whether the email or password was wrong.
specs/06-AUTH-AND-SECURITY.md:56:- Rate limiting: maximum 5 attempts per minute, keyed by IP address. Uses the `login` rate limiter.
specs/06-AUTH-AND-SECURITY.md-57-- After login: redirect to `/admin` (the dashboard).
specs/06-AUTH-AND-SECURITY.md-58-
specs/06-AUTH-AND-SECURITY.md-59-#### Remember Me
specs/06-AUTH-AND-SECURITY.md-60-
--
specs/06-AUTH-AND-SECURITY.md-76-    App-->>B: Render forgot-password form
specs/06-AUTH-AND-SECURITY.md-77-    B->>App: POST /admin/forgot-password (email)
specs/06-AUTH-AND-SECURITY.md-78-    App->>DB: Look up user by email
specs/06-AUTH-AND-SECURITY.md-79-    alt User exists
specs/06-AUTH-AND-SECURITY.md:80:        App->>DB: Store reset token in password_reset_tokens table
specs/06-AUTH-AND-SECURITY.md-81-        App->>Mail: Send reset link email
specs/06-AUTH-AND-SECURITY.md-82-    end
specs/06-AUTH-AND-SECURITY.md-83-    App-->>B: "If that email exists, we sent a reset link." (generic)
specs/06-AUTH-AND-SECURITY.md-84-    B->>App: GET /admin/reset-password/{token}
--
specs/06-AUTH-AND-SECURITY.md-96-
specs/06-AUTH-AND-SECURITY.md-97-**Requirements:**
specs/06-AUTH-AND-SECURITY.md-98-
specs/06-AUTH-AND-SECURITY.md-99-- Uses Laravel's built-in password broker (`users` broker).
specs/06-AUTH-AND-SECURITY.md:100:- Reset tokens stored in the `password_reset_tokens` table.
specs/06-AUTH-AND-SECURITY.md-101-- Token expiration: 60 minutes (configurable in `config/auth.php` under `passwords.users.expire`).
specs/06-AUTH-AND-SECURITY.md-102-- Throttle: one reset email per 60 seconds per email address.
specs/06-AUTH-AND-SECURITY.md-103-- The "forgot password" response must be generic (do not reveal whether email exists).
specs/06-AUTH-AND-SECURITY.md-104-
--
specs/06-AUTH-AND-SECURITY.md-140-
specs/06-AUTH-AND-SECURITY.md-141-    Note over Store: Store already resolved from hostname
specs/06-AUTH-AND-SECURITY.md-142-    B->>LW: Submit email + password
specs/06-AUTH-AND-SECURITY.md-143-    LW->>LW: Validate input
specs/06-AUTH-AND-SECURITY.md:144:    LW->>LW: Check rate limiter (5/min per IP)
specs/06-AUTH-AND-SECURITY.md-145-    LW->>Auth: attempt(credentials + store_id, remember)
specs/06-AUTH-AND-SECURITY.md-146-    Auth->>DB: SELECT from customers WHERE email = ? AND store_id = ?
specs/06-AUTH-AND-SECURITY.md-147-    Auth->>Auth: Verify bcrypt hash
specs/06-AUTH-AND-SECURITY.md-148-    alt Valid
--
specs/06-AUTH-AND-SECURITY.md-158-
specs/06-AUTH-AND-SECURITY.md-159-- Customer login is always scoped to the current store. The store is resolved from the request hostname by the `ResolveStore` middleware before the login attempt.
specs/06-AUTH-AND-SECURITY.md-160-- The custom `CustomerUserProvider` must inject `store_id` into the credential check so the query filters by both `email` and `store_id`.
specs/06-AUTH-AND-SECURITY.md-161-- The same email address can exist in different stores (multi-tenant isolation).
specs/06-AUTH-AND-SECURITY.md:162:- Rate limiting: maximum 5 attempts per minute per IP, using the same `login` rate limiter.
specs/06-AUTH-AND-SECURITY.md-163-- After login: redirect to `/account` or the intended URL (if the customer was redirected from a protected page).
specs/06-AUTH-AND-SECURITY.md-164-
specs/06-AUTH-AND-SECURITY.md-165-#### Customer Registration Flow
specs/06-AUTH-AND-SECURITY.md-166-
--
specs/06-AUTH-AND-SECURITY.md-193-
specs/06-AUTH-AND-SECURITY.md-194-#### Customer Password Reset
specs/06-AUTH-AND-SECURITY.md-195-
specs/06-AUTH-AND-SECURITY.md-196-- Uses a separate password broker: `customers` broker.
specs/06-AUTH-AND-SECURITY.md:197:- Tokens stored in the `customer_password_reset_tokens` table (separate from admin tokens).
specs/06-AUTH-AND-SECURITY.md-198-- Token records include `store_id` to scope resets to the correct store.
specs/06-AUTH-AND-SECURITY.md-199-- Routes: `GET /forgot-password`, `POST /forgot-password`, `GET /reset-password/{token}`, `POST /reset-password`.
specs/06-AUTH-AND-SECURITY.md-200-- Token expiration: 60 minutes.
specs/06-AUTH-AND-SECURITY.md-201-- Throttle: one reset email per 60 seconds.
--
specs/06-AUTH-AND-SECURITY.md-288-#### Password Brokers Data Dictionary
specs/06-AUTH-AND-SECURITY.md-289-
specs/06-AUTH-AND-SECURITY.md-290-| Broker Name | Provider | Token Table | Expiry | Throttle |
specs/06-AUTH-AND-SECURITY.md-291-|-------------|----------|-------------|--------|----------|
specs/06-AUTH-AND-SECURITY.md:292:| `users` | `users` | `password_reset_tokens` | 60 min | 60 sec |
specs/06-AUTH-AND-SECURITY.md:293:| `customers` | `customers` | `customer_password_reset_tokens` | 60 min | 60 sec |
specs/06-AUTH-AND-SECURITY.md-294-
specs/06-AUTH-AND-SECURITY.md-295-**Defaults:** The default guard is `web` and the default password broker is `users`.
specs/06-AUTH-AND-SECURITY.md-296-
specs/06-AUTH-AND-SECURITY.md-297----
--
specs/06-AUTH-AND-SECURITY.md-548-| Gate Name | Required Roles | Purpose |
specs/06-AUTH-AND-SECURITY.md-549-|-----------|---------------|---------|
specs/06-AUTH-AND-SECURITY.md-550-| `manage-store-settings` | Owner, Admin | Access store settings pages |
specs/06-AUTH-AND-SECURITY.md-551-| `manage-staff` | Owner, Admin | Invite, update, remove staff members |
specs/06-AUTH-AND-SECURITY.md:552:| `manage-developers` | Owner, Admin | Create/revoke API tokens, manage webhooks |
specs/06-AUTH-AND-SECURITY.md-553-| `view-analytics` | Owner, Admin, Staff | View analytics dashboard and reports |
specs/06-AUTH-AND-SECURITY.md-554-| `manage-shipping` | Owner, Admin | Manage shipping zones, rates, profiles |
specs/06-AUTH-AND-SECURITY.md-555-| `manage-taxes` | Owner, Admin | Manage tax settings and rates |
specs/06-AUTH-AND-SECURITY.md-556-| `manage-search-settings` | Owner, Admin | Configure search weights and synonyms |
--
specs/06-AUTH-AND-SECURITY.md-578-#### In API Controllers
specs/06-AUTH-AND-SECURITY.md-579-
specs/06-AUTH-AND-SECURITY.md-580-- Use `$this->authorize('action', $model)` at the start of each controller method.
specs/06-AUTH-AND-SECURITY.md-581-- Alternatively, use `can` middleware on routes for simpler checks.
specs/06-AUTH-AND-SECURITY.md:582:- Form Requests also implement `authorize()` to check the relevant policy or gate before validation runs.
specs/06-AUTH-AND-SECURITY.md-583-
specs/06-AUTH-AND-SECURITY.md-584-#### In Blade Templates
specs/06-AUTH-AND-SECURITY.md-585-
specs/06-AUTH-AND-SECURITY.md-586-- Use `@can('gate-name')` / `@cannot` directives to conditionally show/hide navigation items and action buttons.
--
specs/06-AUTH-AND-SECURITY.md-613-| Admin API | `api`, `auth:sanctum`, `store.resolve`, `throttle:api.admin` | REST API endpoints |
specs/06-AUTH-AND-SECURITY.md-614-| Storefront web (public) | `web`, `store.resolve` | Public store pages (catalog, product, cart) |
specs/06-AUTH-AND-SECURITY.md-615-| Storefront web (customer) | `web`, `store.resolve`, `auth:customer` | Customer account pages |
specs/06-AUTH-AND-SECURITY.md-616-| Storefront API | `api`, `store.resolve`, `throttle:api.storefront` | Cart and checkout API |
specs/06-AUTH-AND-SECURITY.md:617:| Webhook routes | `api`, `throttle:webhooks` | Reserved for outbound app webhooks (no inbound payment webhooks - Mock PSP is in-process) |
specs/06-AUTH-AND-SECURITY.md-618-
specs/06-AUTH-AND-SECURITY.md-619----
specs/06-AUTH-AND-SECURITY.md-620-
specs/06-AUTH-AND-SECURITY.md-621-### 3.3 Custom Middleware Specifications
--
specs/06-AUTH-AND-SECURITY.md-730----
specs/06-AUTH-AND-SECURITY.md-731-
specs/06-AUTH-AND-SECURITY.md-732-### 4.2 Rate Limiting
specs/06-AUTH-AND-SECURITY.md-733-
specs/06-AUTH-AND-SECURITY.md:734:All rate limiters are defined in `bootstrap/app.php` (or `AppServiceProvider::boot()`).
specs/06-AUTH-AND-SECURITY.md-735-
specs/06-AUTH-AND-SECURITY.md-736-| Limiter Name | Limit | Keyed By | Applied To |
specs/06-AUTH-AND-SECURITY.md-737-|-------------|-------|----------|------------|
specs/06-AUTH-AND-SECURITY.md-738-| `login` | 5 per minute | IP address | Admin login, customer login |
--
specs/06-AUTH-AND-SECURITY.md-740-| `api.storefront` | 120 per minute | IP address | Public storefront API (cart, search) |
specs/06-AUTH-AND-SECURITY.md-741-| `checkout` | 10 per minute | Session ID | Checkout-related operations |
specs/06-AUTH-AND-SECURITY.md-742-| `search` | 30 per minute | IP address | Search queries |
specs/06-AUTH-AND-SECURITY.md-743-| `analytics` | 60 per minute | IP address | Analytics event ingestion |
specs/06-AUTH-AND-SECURITY.md:744:| `webhooks` | 100 per minute | IP address | Outbound app webhook endpoints (reserved; no inbound payment webhooks - Mock PSP is in-process) |
specs/06-AUTH-AND-SECURITY.md-745-
specs/06-AUTH-AND-SECURITY.md-746-**Rate limit response headers** (returned on all API responses by Laravel default):
specs/06-AUTH-AND-SECURITY.md-747-
specs/06-AUTH-AND-SECURITY.md-748-| Header | Description |
specs/06-AUTH-AND-SECURITY.md-749-|--------|-------------|
specs/06-AUTH-AND-SECURITY.md-750-| `X-RateLimit-Limit` | Maximum requests allowed in the window |
specs/06-AUTH-AND-SECURITY.md-751-| `X-RateLimit-Remaining` | Requests remaining in the current window |
specs/06-AUTH-AND-SECURITY.md:752:| `Retry-After` | Seconds until the rate limit resets (only on 429 responses) |
specs/06-AUTH-AND-SECURITY.md-753-
specs/06-AUTH-AND-SECURITY.md:754:When a rate limit is exceeded, the response is HTTP 429 Too Many Requests.
specs/06-AUTH-AND-SECURITY.md-755-
specs/06-AUTH-AND-SECURITY.md-756----
specs/06-AUTH-AND-SECURITY.md-757-
specs/06-AUTH-AND-SECURITY.md-758-### 4.3 Encryption at Rest
--
specs/06-AUTH-AND-SECURITY.md-763-
specs/06-AUTH-AND-SECURITY.md-764-| Model | Column | Cast Type | Purpose |
specs/06-AUTH-AND-SECURITY.md-765-|-------|--------|-----------|---------|
specs/06-AUTH-AND-SECURITY.md-766-| OauthClient | `client_secret_encrypted` | `encrypted` | Third-party OAuth client secrets |
specs/06-AUTH-AND-SECURITY.md:767:| WebhookSubscription | `signing_secret_encrypted` | `encrypted` | Per-subscription signing secrets for outbound webhooks |
specs/06-AUTH-AND-SECURITY.md-768-| Payment | `raw_json` | `encrypted:array` | Full payment gateway response (may contain sensitive card/account data) |
specs/06-AUTH-AND-SECURITY.md-769-
specs/06-AUTH-AND-SECURITY.md-770-**Requirements:**
specs/06-AUTH-AND-SECURITY.md-771-
--
specs/06-AUTH-AND-SECURITY.md-776----
specs/06-AUTH-AND-SECURITY.md-777-
specs/06-AUTH-AND-SECURITY.md-778-### 4.4 Input Validation
specs/06-AUTH-AND-SECURITY.md-779-
specs/06-AUTH-AND-SECURITY.md:780:All write endpoints must use dedicated Form Request classes. Validation rules use string syntax per project convention.
specs/06-AUTH-AND-SECURITY.md-781-
specs/06-AUTH-AND-SECURITY.md:782:#### Form Request Inventory
specs/06-AUTH-AND-SECURITY.md-783-
specs/06-AUTH-AND-SECURITY.md:784:| Form Request Class | Used By | Key Validation Rules |
specs/06-AUTH-AND-SECURITY.md-785-|-------------------|---------|---------------------|
specs/06-AUTH-AND-SECURITY.md-786-| `StoreProductRequest` | Create product | `title`: required, string, max:255. `description_html`: nullable, string. `status`: required, in:active,draft,archived. `vendor`: nullable, string. Price fields: required, integer, min:0. |
specs/06-AUTH-AND-SECURITY.md-787-| `UpdateProductRequest` | Update product | Same fields as StoreProductRequest but all optional (sometimes rule). |
specs/06-AUTH-AND-SECURITY.md-788-| `StoreCollectionRequest` | Create collection | `title`: required, string, max:255. `type`: required, in:manual,automated. `rules_json`: required_if:type,automated, valid JSON. |
--
specs/06-AUTH-AND-SECURITY.md-797-| `InviteStaffRequest` | Invite staff member | `email`: required, valid email. `role`: required, in:admin,staff,support (not owner). |
specs/06-AUTH-AND-SECURITY.md-798-| `RegisterCustomerRequest` | Customer registration | `name`: required. `email`: required, email, unique per store (composite). `password`: required, min:8, confirmed. |
specs/06-AUTH-AND-SECURITY.md-799-| `LoginRequest` | Admin/customer login | `email`: required, email. `password`: required, string. |
specs/06-AUTH-AND-SECURITY.md-800-
specs/06-AUTH-AND-SECURITY.md:801:**Authorization in Form Requests:**
specs/06-AUTH-AND-SECURITY.md-802-
specs/06-AUTH-AND-SECURITY.md:803:Each Form Request's `authorize()` method must check the relevant policy or gate. For example, `StoreProductRequest::authorize()` checks whether the user can create a Product. If authorization fails, the request is rejected with 403 before validation even runs.
specs/06-AUTH-AND-SECURITY.md-804-
specs/06-AUTH-AND-SECURITY.md-805----
specs/06-AUTH-AND-SECURITY.md-806-
specs/06-AUTH-AND-SECURITY.md-807-### 4.5 XSS Prevention
--
specs/06-AUTH-AND-SECURITY.md-912-| `partitioned` | `false` | Not needed for this use case. |
specs/06-AUTH-AND-SECURITY.md-913-
specs/06-AUTH-AND-SECURITY.md-914----
specs/06-AUTH-AND-SECURITY.md-915-
specs/06-AUTH-AND-SECURITY.md:916:## 6. CORS Configuration
specs/06-AUTH-AND-SECURITY.md-917-
specs/06-AUTH-AND-SECURITY.md:918:File: `config/cors.php`
specs/06-AUTH-AND-SECURITY.md-919-
specs/06-AUTH-AND-SECURITY.md-920-| Setting | Value | Rationale |
specs/06-AUTH-AND-SECURITY.md-921-|---------|-------|-----------|
specs/06-AUTH-AND-SECURITY.md:922:| `paths` | `['api/*', 'sanctum/csrf-cookie']` | Only API and Sanctum CSRF cookie endpoints are CORS-enabled. Web routes are not. |
specs/06-AUTH-AND-SECURITY.md-923-| `allowed_methods` | `['*']` | All HTTP methods allowed for API. |
specs/06-AUTH-AND-SECURITY.md-924-| `allowed_origins` | `[FRONTEND_URL env var, default '*']` | Restrict to store domain in production. Wildcard for development. |
specs/06-AUTH-AND-SECURITY.md-925-| `allowed_origins_patterns` | `[]` | No pattern-based origin matching needed. |
specs/06-AUTH-AND-SECURITY.md-926-| `allowed_headers` | `['*']` | All headers allowed (Authorization, Content-Type, etc.). |
specs/06-AUTH-AND-SECURITY.md:927:| `exposed_headers` | `['X-RateLimit-Limit', 'X-RateLimit-Remaining', 'Retry-After']` | Expose rate limit headers so API clients can implement backoff. |
specs/06-AUTH-AND-SECURITY.md-928-| `max_age` | `0` | No preflight caching (conservative default). |
specs/06-AUTH-AND-SECURITY.md-929-| `supports_credentials` | `true` | Required for Sanctum SPA auth (cookies). |
specs/06-AUTH-AND-SECURITY.md-930-
specs/06-AUTH-AND-SECURITY.md-931----
--
specs/06-AUTH-AND-SECURITY.md-933-## 7. Webhook Signature Verification
specs/06-AUTH-AND-SECURITY.md-934-
specs/06-AUTH-AND-SECURITY.md-935-### 7.1 Inbound Payment Webhooks
specs/06-AUTH-AND-SECURITY.md-936-
specs/06-AUTH-AND-SECURITY.md:937:> **Not applicable.** All payment processing happens in-process via the Mock PSP. There are no inbound payment webhooks from external providers (no Stripe, no PayPal). This section is preserved for reference only -- if a real PSP is integrated in the future, inbound webhook verification would follow the patterns described in the outbound webhook section below (HMAC signature verification).
specs/06-AUTH-AND-SECURITY.md-938-
specs/06-AUTH-AND-SECURITY.md-939----
specs/06-AUTH-AND-SECURITY.md-940-
specs/06-AUTH-AND-SECURITY.md-941-### 7.2 Platform Webhooks (Outbound)
specs/06-AUTH-AND-SECURITY.md-942-
specs/06-AUTH-AND-SECURITY.md-943-Service: `App\Services\WebhookDispatcher`
specs/06-AUTH-AND-SECURITY.md-944-
specs/06-AUTH-AND-SECURITY.md:945:When the platform sends we…11646 tokens truncated…ABASE-SCHEMA.md-1176-| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
specs/01-DATABASE-SCHEMA.md-1177-| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
specs/01-DATABASE-SCHEMA.md-1178-
specs/01-DATABASE-SCHEMA.md:1179:### password_reset_tokens
specs/01-DATABASE-SCHEMA.md-1180-
specs/01-DATABASE-SCHEMA.md-1181-| Column | Type | Nullable | Default | Constraints | Notes |
specs/01-DATABASE-SCHEMA.md-1182-|---|---|---|---|---|---|
specs/01-DATABASE-SCHEMA.md-1183-| email | TEXT | No | - | PRIMARY KEY | Admin user email |
--
specs/01-DATABASE-SCHEMA.md-1185-| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
specs/01-DATABASE-SCHEMA.md-1186-
specs/01-DATABASE-SCHEMA.md-1187----
specs/01-DATABASE-SCHEMA.md-1188-
specs/01-DATABASE-SCHEMA.md:1189:### customer_password_reset_tokens
specs/01-DATABASE-SCHEMA.md-1190-
specs/01-DATABASE-SCHEMA.md-1191-| Column | Type | Nullable | Default | Constraints | Notes |
specs/01-DATABASE-SCHEMA.md-1192-|---|---|---|---|---|---|
specs/01-DATABASE-SCHEMA.md-1193-| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
--
specs/01-DATABASE-SCHEMA.md-1320-```mermaid
specs/01-DATABASE-SCHEMA.md-1321-erDiagram
specs/01-DATABASE-SCHEMA.md-1322-    stores ||--o{ themes : "has"
specs/01-DATABASE-SCHEMA.md-1323-    themes ||--o{ theme_files : "contains"
specs/01-DATABASE-SCHEMA.md:1324:    themes ||--|| theme_settings : "configured by"
specs/01-DATABASE-SCHEMA.md-1325-    stores ||--o{ pages : "has"
specs/01-DATABASE-SCHEMA.md-1326-    stores ||--o{ navigation_menus : "has"
specs/01-DATABASE-SCHEMA.md-1327-    navigation_menus ||--o{ navigation_items : "contains"
specs/01-DATABASE-SCHEMA.md-1328-
--
specs/01-DATABASE-SCHEMA.md-1337-        INTEGER theme_id FK
specs/01-DATABASE-SCHEMA.md-1338-        TEXT path
specs/01-DATABASE-SCHEMA.md-1339-        TEXT storage_key
specs/01-DATABASE-SCHEMA.md-1340-    }
specs/01-DATABASE-SCHEMA.md:1341:    theme_settings {
specs/01-DATABASE-SCHEMA.md-1342-        INTEGER theme_id PK
specs/01-DATABASE-SCHEMA.md-1343-        TEXT settings_json
specs/01-DATABASE-SCHEMA.md-1344-    }
specs/01-DATABASE-SCHEMA.md-1345-    pages {
--
specs/01-DATABASE-SCHEMA.md-1435-    apps ||--o{ app_installations : "installed via"
specs/01-DATABASE-SCHEMA.md-1436-    stores ||--o{ app_installations : "has"
specs/01-DATABASE-SCHEMA.md-1437-    apps ||--o{ oauth_clients : "authenticates via"
specs/01-DATABASE-SCHEMA.md-1438-    app_installations ||--o{ oauth_tokens : "issues"
specs/01-DATABASE-SCHEMA.md:1439:    stores ||--o{ webhook_subscriptions : "registers"
specs/01-DATABASE-SCHEMA.md:1440:    app_installations ||--o{ webhook_subscriptions : "owns"
specs/01-DATABASE-SCHEMA.md:1441:    webhook_subscriptions ||--o{ webhook_deliveries : "delivers"
specs/01-DATABASE-SCHEMA.md-1442-
specs/01-DATABASE-SCHEMA.md-1443-    analytics_events {
specs/01-DATABASE-SCHEMA.md-1444-        INTEGER id PK
specs/01-DATABASE-SCHEMA.md-1445-        INTEGER store_id FK
--
specs/01-DATABASE-SCHEMA.md-1462-        INTEGER store_id FK
specs/01-DATABASE-SCHEMA.md-1463-        INTEGER app_id FK
specs/01-DATABASE-SCHEMA.md-1464-        TEXT status
specs/01-DATABASE-SCHEMA.md-1465-    }
specs/01-DATABASE-SCHEMA.md:1466:    webhook_subscriptions {
specs/01-DATABASE-SCHEMA.md-1467-        INTEGER id PK
specs/01-DATABASE-SCHEMA.md-1468-        INTEGER store_id FK
specs/01-DATABASE-SCHEMA.md-1469-        TEXT event_type
specs/01-DATABASE-SCHEMA.md-1470-        TEXT target_url
specs/01-DATABASE-SCHEMA.md-1471-    }
specs/01-DATABASE-SCHEMA.md:1472:    webhook_deliveries {
specs/01-DATABASE-SCHEMA.md-1473-        INTEGER id PK
specs/01-DATABASE-SCHEMA.md-1474-        INTEGER subscription_id FK
specs/01-DATABASE-SCHEMA.md-1475-        TEXT event_id
specs/01-DATABASE-SCHEMA.md-1476-        TEXT status
--
specs/01-DATABASE-SCHEMA.md-1546-        customer_addresses
specs/01-DATABASE-SCHEMA.md-1547-        carts
specs/01-DATABASE-SCHEMA.md-1548-        navigation_items
specs/01-DATABASE-SCHEMA.md-1549-        theme_files
specs/01-DATABASE-SCHEMA.md:1550:        theme_settings
specs/01-DATABASE-SCHEMA.md-1551-        search_queries
specs/01-DATABASE-SCHEMA.md-1552-        shipping_rates
specs/01-DATABASE-SCHEMA.md-1553-        oauth_tokens
specs/01-DATABASE-SCHEMA.md:1554:        webhook_subscriptions
specs/01-DATABASE-SCHEMA.md-1555-    end
specs/01-DATABASE-SCHEMA.md-1556-
specs/01-DATABASE-SCHEMA.md-1557-    subgraph "Batch 5 - Depends on Batch 4"
specs/01-DATABASE-SCHEMA.md-1558-        product_option_values
--
specs/01-DATABASE-SCHEMA.md-1561-        checkouts
specs/01-DATABASE-SCHEMA.md-1562-        orders
specs/01-DATABASE-SCHEMA.md-1563-        analytics_events
specs/01-DATABASE-SCHEMA.md-1564-        analytics_daily
specs/01-DATABASE-SCHEMA.md:1565:        webhook_deliveries
specs/01-DATABASE-SCHEMA.md-1566-    end
specs/01-DATABASE-SCHEMA.md-1567-
specs/01-DATABASE-SCHEMA.md-1568-    subgraph "Batch 6 - Depends on Batch 5"
specs/01-DATABASE-SCHEMA.md-1569-        variant_option_values
--
specs/01-DATABASE-SCHEMA.md-1604-    stores --> carts
specs/01-DATABASE-SCHEMA.md-1605-    customers --> carts
specs/01-DATABASE-SCHEMA.md-1606-    navigation_menus --> navigation_items
specs/01-DATABASE-SCHEMA.md-1607-    themes --> theme_files
specs/01-DATABASE-SCHEMA.md:1608:    themes --> theme_settings
specs/01-DATABASE-SCHEMA.md-1609-    stores --> search_queries
specs/01-DATABASE-SCHEMA.md-1610-    shipping_zones --> shipping_rates
specs/01-DATABASE-SCHEMA.md-1611-    app_installations --> oauth_tokens
specs/01-DATABASE-SCHEMA.md:1612:    stores --> webhook_subscriptions
specs/01-DATABASE-SCHEMA.md:1613:    app_installations --> webhook_subscriptions
specs/01-DATABASE-SCHEMA.md-1614-    product_options --> product_option_values
specs/01-DATABASE-SCHEMA.md-1615-    stores --> inventory_items
specs/01-DATABASE-SCHEMA.md-1616-    product_variants --> inventory_items
specs/01-DATABASE-SCHEMA.md-1617-    carts --> cart_lines
--
specs/01-DATABASE-SCHEMA.md-1623-    customers --> orders
specs/01-DATABASE-SCHEMA.md-1624-    stores --> analytics_events
specs/01-DATABASE-SCHEMA.md-1625-    customers --> analytics_events
specs/01-DATABASE-SCHEMA.md-1626-    stores --> analytics_daily
specs/01-DATABASE-SCHEMA.md:1627:    webhook_subscriptions --> webhook_deliveries
specs/01-DATABASE-SCHEMA.md-1628-    product_variants --> variant_option_values
specs/01-DATABASE-SCHEMA.md-1629-    product_option_values --> variant_option_values
specs/01-DATABASE-SCHEMA.md-1630-    orders --> order_lines
specs/01-DATABASE-SCHEMA.md-1631-    products --> order_lines
--
specs/01-DATABASE-SCHEMA.md-1642-
specs/01-DATABASE-SCHEMA.md-1643-- **Batch 1** (no dependencies): `organizations`, `apps`
specs/01-DATABASE-SCHEMA.md-1644-- **Batch 2** (depends on Batch 1): `stores` (references organizations), `users` (standalone, but logically grouped here)
specs/01-DATABASE-SCHEMA.md-1645-- **Batch 3** (depends on Batch 2): `store_domains`, `store_users`, `store_settings`, `customers`, `themes`, `pages`, `navigation_menus`, `search_settings`, `shipping_zones`, `tax_settings`, `discounts`, `products`, `collections`, `app_installations`, `oauth_clients`
specs/01-DATABASE-SCHEMA.md:1646:- **Batch 4** (depends on Batch 3): `product_options`, `product_variants`, `product_media`, `collection_products`, `customer_addresses`, `carts`, `navigation_items`, `theme_files`, `theme_settings`, `search_queries`, `shipping_rates`, `oauth_tokens`, `webhook_subscriptions`
specs/01-DATABASE-SCHEMA.md:1647:- **Batch 5** (depends on Batch 4): `product_option_values`, `inventory_items`, `cart_lines`, `checkouts`, `orders`, `analytics_events`, `analytics_daily`, `webhook_deliveries`
specs/01-DATABASE-SCHEMA.md-1648-- **Batch 6** (depends on Batch 5): `variant_option_values`, `order_lines`, `payments`, `fulfillments`
specs/01-DATABASE-SCHEMA.md-1649-- **Batch 7** (depends on Batch 6): `refunds`, `fulfillment_lines`
specs/01-DATABASE-SCHEMA.md-1650-
specs/01-DATABASE-SCHEMA.md-1651----
--
specs/01-DATABASE-SCHEMA.md-1659-3. **Foreign key indexes** - Every foreign key column has an index to avoid full table scans during JOIN and CASCADE operations. SQLite does not auto-create indexes for foreign keys like some other databases.
specs/01-DATABASE-SCHEMA.md-1660-
specs/01-DATABASE-SCHEMA.md-1661-4. **Composite primary keys for pivot tables** - Tables like `store_users`, `collection_products`, `variant_option_values`, and `analytics_daily` use composite primary keys to naturally enforce uniqueness and serve as their own covering index for the most common query pattern.
specs/01-DATABASE-SCHEMA.md-1662-
specs/01-DATABASE-SCHEMA.md:1663:5. **Date-based indexes for time series** - Tables with temporal queries (orders, analytics, search queries, webhook deliveries) include indexes on date/timestamp columns scoped by store for efficient range scans.
specs/01-DATABASE-SCHEMA.md-1664-
specs/01-DATABASE-SCHEMA.md-1665-6. **Selective indexing** - Not every column is indexed. Columns queried only via their parent's primary key (e.g., `cart_lines` always accessed through `cart_id`) rely on the foreign key index alone. This keeps write overhead and database size manageable.
specs/01-DATABASE-SCHEMA.md-1666-
specs/01-DATABASE-SCHEMA.md-1667----
--
specs/01-DATABASE-SCHEMA.md-1671-| Epic | Tables | Count |
specs/01-DATABASE-SCHEMA.md-1672-|---|---|---|
specs/01-DATABASE-SCHEMA.md-1673-| 1 - Foundation and Multi-Store Tenancy | organizations, stores, store_domains, users, store_users, store_settings | 6 |
specs/01-DATABASE-SCHEMA.md-1674-| 2 - Catalog, Collections, Inventory, Media | products, product_options, product_option_values, product_variants, variant_option_values, inventory_items, collections, collection_products, product_media | 9 |
specs/01-DATABASE-SCHEMA.md:1675:| 3 - Themes, Storefront, Localization | themes, theme_files, theme_settings, pages, navigation_menus, navigation_items | 6 |
specs/01-DATABASE-SCHEMA.md-1676-| 4 - Search | search_settings, search_queries | 2 |
specs/01-DATABASE-SCHEMA.md-1677-| 5 - Cart, Checkout, Discounts, Shipping, Taxes | carts, cart_lines, checkouts, shipping_zones, shipping_rates, tax_settings, discounts | 7 |
specs/01-DATABASE-SCHEMA.md-1678-| 6 - Payments, Orders, Fulfillment, Customers | customers, customer_addresses, orders, order_lines, payments, refunds, fulfillments, fulfillment_lines | 8 |
specs/01-DATABASE-SCHEMA.md-1679-| 7 - Analytics | analytics_events, analytics_daily | 2 |
specs/01-DATABASE-SCHEMA.md:1680:| 8 - Apps and Webhooks | apps, app_installations, oauth_clients, oauth_tokens, webhook_subscriptions, webhook_deliveries | 6 |
specs/01-DATABASE-SCHEMA.md-1681-| **Total application tables** | | **46** |
specs/01-DATABASE-SCHEMA.md:1682:| Laravel framework tables | sessions, cache, cache_locks, jobs, job_batches, failed_jobs, personal_access_tokens, password_reset_tokens, customer_password_reset_tokens | 9 |
specs/01-DATABASE-SCHEMA.md-1683-| **Grand total** | | **55** |
--
specs/02-API-ROUTES.md-1-# API and Routing Specification
specs/02-API-ROUTES.md-2-
specs/02-API-ROUTES.md:3:This document defines every route, endpoint, middleware stack, request/response schema, and rate limit for the e-commerce platform. The admin panel is built with Livewire v4 + Flux UI (server-rendered), while the storefront uses Blade + Livewire. REST API endpoints serve cart/checkout operations and programmatic admin access.
specs/02-API-ROUTES.md-4-
specs/02-API-ROUTES.md-5-All monetary amounts are stored and transmitted in **minor units** (cents). All IDs are auto-incrementing integers. All monetary amounts are in minor units (cents).
specs/02-API-ROUTES.md-6-
specs/02-API-ROUTES.md-7----
--
specs/02-API-ROUTES.md-17-| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
specs/02-API-ROUTES.md-18-| POST | `/admin/login` | Process admin login | Handled by Livewire action |
specs/02-API-ROUTES.md-19-| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
specs/02-API-ROUTES.md-20-| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
specs/02-API-ROUTES.md:21:| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
specs/02-API-ROUTES.md-22-| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
specs/02-API-ROUTES.md-23-| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |
specs/02-API-ROUTES.md-24-
specs/02-API-ROUTES.md-25-Rate limit: `login` tier (5 per minute per IP).
--
specs/02-API-ROUTES.md-118-
specs/02-API-ROUTES.md-119-| Method | Path | Description | Component |
specs/02-API-ROUTES.md-120-|--------|------|-------------|-----------|
specs/02-API-ROUTES.md-121-| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
specs/02-API-ROUTES.md:122:| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |
specs/02-API-ROUTES.md-123-
specs/02-API-ROUTES.md-124-#### Developers
specs/02-API-ROUTES.md-125-
specs/02-API-ROUTES.md-126-| Method | Path | Description | Component |
specs/02-API-ROUTES.md-127-|--------|------|-------------|-----------|
specs/02-API-ROUTES.md:128:| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |
specs/02-API-ROUTES.md-129-
specs/02-API-ROUTES.md:130:> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.
specs/02-API-ROUTES.md-131-
specs/02-API-ROUTES.md-132-#### Analytics
specs/02-API-ROUTES.md-133-
specs/02-API-ROUTES.md-134-| Method | Path | Description | Component |
--
specs/02-API-ROUTES.md-255-#### GET /api/storefront/v1/carts/{cartId}
specs/02-API-ROUTES.md-256-
specs/02-API-ROUTES.md-257-Retrieve a cart with all lines and computed totals.
specs/02-API-ROUTES.md-258-
specs/02-API-ROUTES.md:259:- **Authentication:** None (cart ID acts as token)
specs/02-API-ROUTES.md-260-- **Rate limit:** `api.storefront` (120/min per IP)
specs/02-API-ROUTES.md-261-
specs/02-API-ROUTES.md-262-**URL parameters:**
specs/02-API-ROUTES.md-263-
--
specs/02-API-ROUTES.md-846-### 2.3 Payment Endpoint
specs/02-API-ROUTES.md-847-
specs/02-API-ROUTES.md-848-#### POST /api/storefront/v1/checkouts/{checkoutId}/pay
specs/02-API-ROUTES.md-849-
specs/02-API-ROUTES.md:850:Process payment for a checkout using the selected payment method. All payment processing happens in-process via the Mock PSP - no external API calls, no redirects, no webhooks.
specs/02-API-ROUTES.md-851-
specs/02-API-ROUTES.md-852-- **Authentication:** None
specs/02-API-ROUTES.md-853-- **Rate limit:** `checkout` (10/min per session)
specs/02-API-ROUTES.md-854-
--
specs/02-API-ROUTES.md-943-  "error_code": "card_declined"
specs/02-API-ROUTES.md-944-}
specs/02-API-ROUTES.md-945-```
specs/02-API-ROUTES.md-946-
specs/02-API-ROUTES.md:947:**Magic card number error codes:**
specs/02-API-ROUTES.md-948-
specs/02-API-ROUTES.md-949-| Card Number | Error Code | Message |
specs/02-API-ROUTES.md-950-|---|---|---|
specs/02-API-ROUTES.md-951-| `4000000000000002` | `card_declined` | "Your card was declined." |
--
specs/02-API-ROUTES.md-2895----
specs/02-API-ROUTES.md-2896-
specs/02-API-ROUTES.md-2897-## 4. Webhook Receiver Endpoints
specs/02-API-ROUTES.md-2898-
specs/02-API-ROUTES.md:2899:> **Note:** No inbound webhook endpoints are needed. All payment processing happens in-process via the Mock PSP -- there are no external payment provider callbacks. The webhook infrastructure defined in this section is reserved for **outbound** app webhooks (see Section 5, OAuth / App Ecosystem).
specs/02-API-ROUTES.md-2900-
specs/02-API-ROUTES.md-2901----
specs/02-API-ROUTES.md-2902-
specs/02-API-ROUTES.md-2903-## 5. OAuth Endpoints (App Ecosystem)
--
specs/02-API-ROUTES.md-3082-| `api.storefront` | 120 requests | 1 minute | IP address | Storefront REST API (carts, etc.) |
specs/02-API-ROUTES.md-3083-| `checkout` | 10 requests | 1 minute | Session ID | Checkout creation and mutation endpoints |
specs/02-API-ROUTES.md-3084-| `search` | 30 requests | 1 minute | IP address | Search and autocomplete endpoints |
specs/02-API-ROUTES.md-3085-| `analytics` | 60 requests | 1 minute | IP address | Analytics event ingestion |
specs/02-API-ROUTES.md:3086:| `webhooks` | 100 requests | 1 minute | IP address | Reserved for future outbound app webhook endpoints |
specs/02-API-ROUTES.md-3087-
specs/02-API-ROUTES.md-3088-### Rate Limit Response
specs/02-API-ROUTES.md-3089-
specs/02-API-ROUTES.md:3090:When a rate limit is exceeded, the server returns `429 Too Many Requests`:
specs/02-API-ROUTES.md-3091-
specs/02-API-ROUTES.md-3092-```json
specs/02-API-ROUTES.md-3093-{
specs/02-API-ROUTES.md-3094-  "message": "Too many requests. Please try again later.",
--
specs/02-API-ROUTES.md-3101-| Header | Description |
specs/02-API-ROUTES.md-3102-|--------|-------------|
specs/02-API-ROUTES.md-3103-| `X-RateLimit-Limit` | Maximum requests allowed in the window |
specs/02-API-ROUTES.md-3104-| `X-RateLimit-Remaining` | Requests remaining in the current window |
specs/02-API-ROUTES.md:3105:| `Retry-After` | Seconds until the rate limit resets (only on 429) |
specs/02-API-ROUTES.md-3106-
specs/02-API-ROUTES.md-3107-### Per-Endpoint Rate Limit Mapping
specs/02-API-ROUTES.md-3108-
specs/02-API-ROUTES.md-3109-| Endpoint Group | Tier |
--
specs/02-API-ROUTES.md-3126-| `GET /api/admin/v1/*` | `api.admin` |
specs/02-API-ROUTES.md-3127-| `POST /api/admin/v1/*` | `api.admin` |
specs/02-API-ROUTES.md-3128-| `PUT /api/admin/v1/*` | `api.admin` |
specs/02-API-ROUTES.md-3129-| `DELETE /api/admin/v1/*` | `api.admin` |
specs/02-API-ROUTES.md:3130:| *(no inbound webhook endpoints - mock PSP)* | - |
specs/02-API-ROUTES.md-3131-
specs/02-API-ROUTES.md-3132----
specs/02-API-ROUTES.md-3133-
specs/02-API-ROUTES.md-3134-## 8. Checkout State Machine
--
specs/02-API-ROUTES.md-3158-| `started` | `addressed` | PUT /checkouts/{id}/address with valid address |
specs/02-API-ROUTES.md-3159-| `addressed` | `shipping_selected` | PUT /checkouts/{id}/shipping-method with valid method |
specs/02-API-ROUTES.md-3160-| `shipping_selected` | `payment_selected` | PUT /checkouts/{id}/payment-method with valid method |
specs/02-API-ROUTES.md-3161-| `payment_selected` | `completed` | POST /checkouts/{id}/pay - Mock PSP processes payment in-process (instant for card/paypal, deferred for bank_transfer) |
specs/02-API-ROUTES.md:3162:| `payment_selected` | `shipping_selected` | Payment declined by Mock PSP (magic card number), customer retries |
specs/02-API-ROUTES.md-3163-| `*` (any active) | `expired` | Checkout expires_at reached (24-hour TTL, checked via scheduled job) |
specs/02-API-ROUTES.md-3164-
specs/02-API-ROUTES.md-3165-Applying or removing a discount does not change the checkout status but triggers totals recalculation at the current state.
specs/02-API-ROUTES.md-3166-
--
specs/02-API-ROUTES.md-3170-
specs/02-API-ROUTES.md-3171-- **Storefront API:** Versioned via URL path (`/api/storefront/v1/`). Breaking changes require a new version.
specs/02-API-ROUTES.md-3172-- **Admin API:** Versioned via URL path (`/api/admin/v1/`). Same strategy.
specs/02-API-ROUTES.md-3173-- **App API:** Versioned via URL path (`/api/apps/v1/`).
specs/02-API-ROUTES.md:3174:- **Outbound webhook payloads:** Include a `api_version` field in outbound webhook deliveries to app installations. Each app installation pins to a version.
specs/02-API-ROUTES.md-3175-
specs/02-API-ROUTES.md-3176-Non-breaking changes (new optional fields, new endpoints) can be added to existing versions. Breaking changes (removed fields, changed types, renamed endpoints) require version bumps.
specs/02-API-ROUTES.md-3177-
specs/02-API-ROUTES.md-3178----