   580→
   581→| Type | Name | Purpose |
   582→|------|------|---------|
   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` |
   584→| Job | `App\Jobs\CleanupAbandonedCarts` | Runs daily; marks carts with `status = active` and `updated_at < 14 days ago` as `abandoned` |
   585→
   586→#### Step 4.9: Storefront Cart/Checkout UI
   587→
   588→**Files to create:**
   589→
   590→| Component | Description |
   591→|-----------|-------------|
   592→| `Storefront\CartDrawer` | Slide-out panel: line items, quantity controls, discount code input, subtotal, checkout button |
   593→| `Storefront\Cart\Show` | Full cart page with same functionality plus shipping estimate |
   594→| `Storefront\Checkout\Show` | Multi-step: Contact/Address -> Shipping method -> Payment. Stepper UI. |
   595→| `Storefront\Checkout\Confirmation` | Order confirmation: order number, items, totals, next steps |
   596→
   597→---
   598→
   599→### Phase 5: Payments, Orders, Fulfillment
   600→
   601→**Priority:** HIGH - completes the purchase flow.
   602→
   603→#### Step 5.1: Migrations (Batch 5-7)
   604→
   605→1. `create_customers_table` - FK: `store_id`
   606→2. `create_customer_addresses_table` - FK: `customer_id`
   607→3. `create_orders_table` - FK: `store_id`, `customer_id`
   608→4. `create_order_lines_table` - FK: `order_id`, `product_id` (nullable), `variant_id` (nullable)
   609→5. `create_payments_table` - FK: `order_id`
   610→6. `create_refunds_table` - FK: `order_id`, `payment_id`
   611→7. `create_fulfillments_table` - FK: `order_id`
   612→8. `create_fulfillment_lines_table` - FK: `fulfillment_id`, `order_line_id`
   613→
   614→#### Step 5.2: Models
   615→
   616→**Files to create:**
   617→
   618→| Type | Name | Purpose |
   619→|------|------|---------|
   620→| Model (with migration, factory, seeder) | `Customer` | Store customer entity |
   621→| Model (with migration, factory, seeder) | `CustomerAddress` | Customer address |
   622→| Model (with migration, factory, seeder) | `Order` | Order entity |
   623→| Model (with migration, factory, seeder) | `OrderLine` | Order line item |
   624→| Model (with migration, factory, seeder) | `Payment` | Payment record |
   625→| Model (with migration, factory, seeder) | `Refund` | Refund record |
   626→| Model (with migration, factory, seeder) | `Fulfillment` | Fulfillment record |
   627→| Model (with migration, factory, seeder) | `FulfillmentLine` | Fulfillment line item |
   628→
   629→**Relationship definitions:**
   630→
   631→| Model | Key Relationships |
   632→|-------|-------------------|
   633→| `Customer` | `belongsTo(Store)`, `hasMany(CustomerAddress)`, `hasMany(Order)`, `hasMany(Cart)` |
   634→| `CustomerAddress` | `belongsTo(Customer)` |
   635→| `Order` | `belongsTo(Store)`, `belongsTo(Customer)`, `hasMany(OrderLine)`, `hasMany(Payment)`, `hasMany(Refund)`, `hasMany(Fulfillment)` |
   636→| `OrderLine` | `belongsTo(Order)`, `belongsTo(Product)` (nullable), `belongsTo(ProductVariant)` (nullable) |
   637→| `Payment` | `belongsTo(Order)` |
   638→| `Refund` | `belongsTo(Order)`, `belongsTo(Payment)` |
   639→| `Fulfillment` | `belongsTo(Order)`, `hasMany(FulfillmentLine)` |
   640→| `FulfillmentLine` | `belongsTo(Fulfillment)`, `belongsTo(OrderLine)` |
   641→
   642→**Enums to create:**
   643→
   644→| Enum | Values |
   645→|------|--------|
   646→| `OrderStatus` | `Pending`, `Paid`, `Fulfilled`, `Cancelled`, `Refunded` |
   647→| `FinancialStatus` | `Pending`, `Authorized`, `Paid`, `PartiallyRefunded`, `Refunded`, `Voided` |
   648→| `FulfillmentStatus` | `Unfulfilled`, `Partial`, `Fulfilled` |
   649→| `PaymentProvider` | `Stripe`, `Paypal` |
   650→| `PaymentStatus` | `RequiresAction`, `Authorized`, `Captured`, `Failed`, `Refunded` |
   651→| `RefundStatus` | `Pending`, `Processed`, `Failed` |
   652→| `FulfillmentShipmentStatus` | `Pending`, `Shipped`, `Delivered` |
   653→
   654→#### Step 5.3: Payment Service
   655→
   656→**Files to create:**
   657→
   658→| Type | Name | Purpose |
   659→|------|------|---------|
   660→| Interface | `App\Contracts\PaymentProvider` | Defines createSession, handleWebhook, refund methods |
   661→| Service | `App\Services\Payments\StripePaymentProvider` | Implements PaymentProvider with Stripe Checkout Sessions in test mode |
   662→| Service | `App\Services\Payments\PaypalPaymentProvider` | Stub implementation that throws NotImplementedException |
   663→
   664→**PaymentProvider interface methods:**
   665→
   666→| Method | Description |
   667→|--------|-------------|
   668→| `createSession(Checkout $checkout, int $amount, string $currency): PaymentSession` | Creates payment session with the provider |
   669→| `handleWebhook(Request $request): void` | Processes incoming webhook from provider |
   670→| `refund(Payment $payment, int $amount): RefundResult` | Issues a refund through the provider |
   671→
   672→**StripePaymentProvider requirements:**
   673→- Creates Stripe Checkout Sessions in test mode
   674→- Webhook handler with HMAC signature verification
   675→- Idempotent event processing (checks `provider_payment_id` uniqueness)
   676→
   677→**Registration:** Bind `PaymentProvider` interface to `StripePaymentProvider` in `AppServiceProvider`.
   678→
   679→#### Step 5.4: Order Service
   680→
   681→**File to create:** `App\Services\OrderService`
   682→
   683→| Method | Description |
   684→|--------|-------------|
   685→| `createFromCheckout(Checkout $checkout): Order` | Atomic transaction: create order + order lines with snapshots, commit inventory, mark cart converted, dispatch `OrderCreated` event |
   686→| `generateOrderNumber(Store $store): string` | Sequential per store: #1001, #1002, etc. |
   687→| `cancel(Order $order, string $reason): void` | Only if not yet fulfilled, releases inventory |
   688→
   689→Order lines store snapshots (`title_snapshot`, `sku_snapshot`) so order history survives product deletion.
   690→
   691→#### Step 5.5: Refund Service
   692→
   693→**File to create:** `App\Services\RefundService`
   694→
   695→| Method | Description |
   696→|--------|-------------|
   697→| `create(Order $order, Payment $payment, int $amount, ?string $reason, bool $restock): Refund` | Validates amount does not exceed payment, calls provider refund API, updates financial status |
   698→
   699→- Partial refunds: `financial_status` -> `partially_refunded`
   700→- Full refunds: `financial_status` -> `refunded`
   701→- Restock flag: calls `InventoryService::restock()` for each line
   702→
   703→#### Step 5.6: Fulfillment Service
   704→
   705→**File to create:** `App\Services\FulfillmentService`
   706→
   707→| Method | Description |
   708→|--------|-------------|
   709→| `create(Order $order, array $lines, ?array $tracking): Fulfillment` | Creates fulfillment with specified lines and quantities |
   710→| `ship(Fulfillment $fulfillment, array $tracking): void` | Sets tracking info, transitions to `shipped` |
   711→| `deliver(Fulfillment $fulfillment): void` | Transitions to `delivered` |
   712→
   713→Updates `order.fulfillment_status` based on whether all lines are fulfilled (`fulfilled`) or some (`partial`).
   714→
   715→**Events to create:**
   716→
   717→| Type | Name | Purpose |
   718→|------|------|---------|
   719→| Event | `OrderCreated` | Dispatched when order is created from checkout |
   720→| Event | `OrderPaid` | Dispatched when payment is captured |
   721→| Event | `OrderFulfilled` | Dispatched when all order lines are fulfilled |
   722→| Event | `OrderCancelled` | Dispatched when order is cancelled |
   723→| Event | `OrderRefunded` | Dispatched when refund is processed |
   724→
   725→---
   726→
   727→### Phase 6: Customer Accounts
   728→
   729→**Priority:** MEDIUM - enhances the shopping experience.
   730→
   731→#### Step 6.1: Customer Auth
   732→
   733→- `Customer` model with custom `customer` guard (already configured in Phase 1)
   734→- Custom `CustomerUserProvider` - scopes queries by `store_id`
   735→- Customer email unique per store, not globally
   736→
   737→#### Step 6.2: Customer Account Pages (Livewire)
   738→
   739→**Files to create:**
   740→
   741→| Component | Route | Description |
   742→|-----------|-------|-------------|
   743→| `Storefront\Account\Auth\Login` | `GET /account/login` | Login form |
   744→| `Storefront\Account\Auth\Register` | `GET /account/register` | Registration form |
   745→| `Storefront\Account\Dashboard` | `GET /account` | Overview with recent orders |
   746→| `Storefront\Account\Orders\Index` | `GET /account/orders` | Paginated order history |
   747→| `Storefront\Account\Orders\Show` | `GET /account/orders/{orderNumber}` | Order detail with timeline |
   748→| `Storefront\Account\Addresses\Index` | `GET /account/addresses` | Address book CRUD |
   749→
   750→Logout: `POST /account/logout` handled by Livewire action. Invalidates session, regenerates CSRF token, redirects to `/account/login`.
   751→
   752→---
   753→
   754→### Phase 7: Admin Panel
   755→
   756→**Priority:** MEDIUM - merchant management interface.
   757→
   758→#### Step 7.1: Admin Layout
   759→
   760→**File to create:** `resources/views/livewire/admin/layout/app.blade.php`
   761→
   762→**Components to create:**
   763→
   764→| Component | Description |
   765→|-----------|-------------|
   766→| `Admin\Layout\Sidebar` | Fixed left sidebar (256px desktop, overlay mobile), navigation groups, Flux `brand`, `icon`, `separator` |
   767→| `Admin\Layout\TopBar` | Store selector dropdown, user profile, hamburger toggle (mobile) |
   768→| `Admin\Layout\Breadcrumbs` | Dynamic breadcrumb trail |
   769→
   770→Toast notifications via Livewire events.
   771→
   772→#### Step 7.2: Dashboard
   773→
   774→**File to create:** `Admin\Dashboard` Livewire component
   775→
   776→Requirements:
   777→- KPI tiles: total sales (period), order count, average order value, conversion rate
   778→- Orders chart (last 30 days line chart)
   779→- Recent orders table (last 10)

| Type | Name | Purpose |
|------|------|---------|
| 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` |
| Job | `App\Jobs\CleanupAbandonedCarts` | Runs daily; marks carts with `status = active` and `updated_at < 14 days ago` as `abandoned` |

#### Step 4.9: Storefront Cart/Checkout UI

**Files to create:**

| Component | Description |
|-----------|-------------|
| `Storefront\CartDrawer` | Slide-out panel: line items, quantity controls, discount code input, subtotal, checkout button |
| `Storefront\Cart\Show` | Full cart page with same functionality plus shipping estimate |
| `Storefront\Checkout\Show` | Multi-step: Contact/Address -> Shipping method -> Payment. Stepper UI. |
| `Storefront\Checkout\Confirmation` | Order confirmation: order number, items, totals, next steps |

---

### Phase 5: Payments, Orders, Fulfillment

**Priority:** HIGH - completes the purchase flow.

#### Step 5.1: Migrations (Batch 5-7)

1. `create_customers_table` - FK: `store_id`
2. `create_customer_addresses_table` - FK: `customer_id`
3. `create_orders_table` - FK: `store_id`, `customer_id`
4. `create_order_lines_table` - FK: `order_id`, `product_id` (nullable), `variant_id` (nullable)
5. `create_payments_table` - FK: `order_id`
6. `create_refunds_table` - FK: `order_id`, `payment_id`
7. `create_fulfillments_table` - FK: `order_id`
8. `create_fulfillment_lines_table` - FK: `fulfillment_id`, `order_line_id`

#### Step 5.2: Models

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Model (with migration, factory, seeder) | `Customer` | Store customer entity |
| Model (with migration, factory, seeder) | `CustomerAddress` | Customer address |
| Model (with migration, factory, seeder) | `Order` | Order entity |
| Model (with migration, factory, seeder) | `OrderLine` | Order line item |
| Model (with migration, factory, seeder) | `Payment` | Payment record |
| Model (with migration, factory, seeder) | `Refund` | Refund record |
| Model (with migration, factory, seeder) | `Fulfillment` | Fulfillment record |
| Model (with migration, factory, seeder) | `FulfillmentLine` | Fulfillment line item |

**Relationship definitions:**

| Model | Key Relationships |
|-------|-------------------|
| `Customer` | `belongsTo(Store)`, `hasMany(CustomerAddress)`, `hasMany(Order)`, `hasMany(Cart)` |
| `CustomerAddress` | `belongsTo(Customer)` |
| `Order` | `belongsTo(Store)`, `belongsTo(Customer)`, `hasMany(OrderLine)`, `hasMany(Payment)`, `hasMany(Refund)`, `hasMany(Fulfillment)` |
| `OrderLine` | `belongsTo(Order)`, `belongsTo(Product)` (nullable), `belongsTo(ProductVariant)` (nullable) |
| `Payment` | `belongsTo(Order)` |
| `Refund` | `belongsTo(Order)`, `belongsTo(Payment)` |
| `Fulfillment` | `belongsTo(Order)`, `hasMany(FulfillmentLine)` |
| `FulfillmentLine` | `belongsTo(Fulfillment)`, `belongsTo(OrderLine)` |

**Enums to create:**

| Enum | Values |
|------|--------|
| `OrderStatus` | `Pending`, `Paid`, `Fulfilled`, `Cancelled`, `Refunded` |
| `FinancialStatus` | `Pending`, `Authorized`, `Paid`, `PartiallyRefunded`, `Refunded`, `Voided` |
| `FulfillmentStatus` | `Unfulfilled`, `Partial`, `Fulfilled` |
| `PaymentProvider` | `Stripe`, `Paypal` |
| `PaymentStatus` | `RequiresAction`, `Authorized`, `Captured`, `Failed`, `Refunded` |
| `RefundStatus` | `Pending`, `Processed`, `Failed` |
| `FulfillmentShipmentStatus` | `Pending`, `Shipped`, `Delivered` |

#### Step 5.3: Payment Service

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Interface | `App\Contracts\PaymentProvider` | Defines createSession, handleWebhook, refund methods |
| Service | `App\Services\Payments\StripePaymentProvider` | Implements PaymentProvider with Stripe Checkout Sessions in test mode |
| Service | `App\Services\Payments\PaypalPaymentProvider` | Stub implementation that throws NotImplementedException |

**PaymentProvider interface methods:**

| Method | Description |
|--------|-------------|
| `createSession(Checkout $checkout, int $amount, string $currency): PaymentSession` | Creates payment session with the provider |
| `handleWebhook(Request $request): void` | Processes incoming webhook from provider |
| `refund(Payment $payment, int $amount): RefundResult` | Issues a refund through the provider |

**StripePaymentProvider requirements:**
- Creates Stripe Checkout Sessions in test mode
- Webhook handler with HMAC signature verification
- Idempotent event processing (checks `provider_payment_id` uniqueness)

**Registration:** Bind `PaymentProvider` interface to `StripePaymentProvider` in `AppServiceProvider`.

#### Step 5.4: Order Service

**File to create:** `App\Services\OrderService`

| Method | Description |
|--------|-------------|
| `createFromCheckout(Checkout $checkout): Order` | Atomic transaction: create order + order lines with snapshots, commit inventory, mark cart converted, dispatch `OrderCreated` event |
| `generateOrderNumber(Store $store): string` | Sequential per store: #1001, #1002, etc. |
| `cancel(Order $order, string $reason): void` | Only if not yet fulfilled, releases inventory |

Order lines store snapshots (`title_snapshot`, `sku_snapshot`) so order history survives product deletion.

#### Step 5.5: Refund Service

**File to create:** `App\Services\RefundService`

| Method | Description |
|--------|-------------|
| `create(Order $order, Payment $payment, int $amount, ?string $reason, bool $restock): Refund` | Validates amount does not exceed payment, calls provider refund API, updates financial status |

- Partial refunds: `financial_status` -> `partially_refunded`
- Full refunds: `financial_status` -> `refunded`
- Restock flag: calls `InventoryService::restock()` for each line

#### Step 5.6: Fulfillment Service

**File to create:** `App\Services\FulfillmentService`

| Method | Description |
|--------|-------------|
| `create(Order $order, array $lines, ?array $tracking): Fulfillment` | Creates fulfillment with specified lines and quantities |
| `ship(Fulfillment $fulfillment, array $tracking): void` | Sets tracking info, transitions to `shipped` |
| `deliver(Fulfillment $fulfillment): void` | Transitions to `delivered` |

Updates `order.fulfillment_status` based on whether all lines are fulfilled (`fulfilled`) or some (`partial`).

**Events to create:**

| Type | Name | Purpose |
|------|------|---------|
| Event | `OrderCreated` | Dispatched when order is created from checkout |
| Event | `OrderPaid` | Dispatched when payment is captured |
| Event | `OrderFulfilled` | Dispatched when all order lines are fulfilled |
| Event | `OrderCancelled` | Dispatched when order is cancelled |
| Event | `OrderRefunded` | Dispatched when refund is processed |

---

### Phase 6: Customer Accounts

**Priority:** MEDIUM - enhances the shopping experience.

#### Step 6.1: Customer Auth

- `Customer` model with custom `customer` guard (already configured in Phase 1)
- Custom `CustomerUserProvider` - scopes queries by `store_id`
- Customer email unique per store, not globally

#### Step 6.2: Customer Account Pages (Livewire)

**Files to create:**

| Component | Route | Description |
|-----------|-------|-------------|
| `Storefront\Account\Auth\Login` | `GET /account/login` | Login form |
| `Storefront\Account\Auth\Register` | `GET /account/register` | Registration form |
| `Storefront\Account\Dashboard` | `GET /account` | Overview with recent orders |
| `Storefront\Account\Orders\Index` | `GET /account/orders` | Paginated order history |
| `Storefront\Account\Orders\Show` | `GET /account/orders/{orderNumber}` | Order detail with timeline |
| `Storefront\Account\Addresses\Index` | `GET /account/addresses` | Address book CRUD |

Logout: `POST /account/logout` handled by Livewire action. Invalidates session, regenerates CSRF token, redirects to `/account/login`.

---

### Phase 7: Admin Panel

**Priority:** MEDIUM - merchant management interface.

#### Step 7.1: Admin Layout

**File to create:** `resources/views/livewire/admin/layout/app.blade.php`

**Components to create:**

| Component | Description |
|-----------|-------------|
| `Admin\Layout\Sidebar` | Fixed left sidebar (256px desktop, overlay mobile), navigation groups, Flux `brand`, `icon`, `separator` |
| `Admin\Layout\TopBar` | Store selector dropdown, user profile, hamburger toggle (mobile) |
| `Admin\Layout\Breadcrumbs` | Dynamic breadcrumb trail |

Toast notifications via Livewire events.

#### Step 7.2: Dashboard

**File to create:** `Admin\Dashboard` Livewire component

Requirements:
- KPI tiles: total sales (period), order count, average order value, conversion rate
- Orders chart (last 30 days line chart)
- Recent orders table (last 10)


Now I have a good picture of all the sections that need updating. Let me also check the checkout flow tests (Suite 9) in the E2E plan.


Let me read the checkout test suite and the 09-IMPLEMENTATION-ROADMAP technology decisions section.


Grep

Suite 9|Test 9\.|checkout.*flow|Suite 4.*Order|Test 4\.[5-9]


118:    S2 --> S4[Suite 4: Admin Orders]
126:    S8 --> S9[Suite 9: Checkout Flow]
140:| Phase 5 | Suite 9 (Checkout) | Depends on cart flow working |
677:## Suite 4: Admin Order Management
773:### Test 4.5: Can create a fulfillment
794:### Test 4.6: Can process a refund
815:### Test 4.7: Shows customer information in order detail
1618:## Suite 9: Checkout Flow
1676:### Test 9.1: Completes full checkout happy path
1709:### Test 9.2: Shows shipping methods based on German address
1737:### Test 9.3: Shows international shipping methods for non-DE address
1764:### Test 9.4: Applies discount during checkout
1798:### Test 9.5: Validates required contact email
1817:### Test 9.6: Validates required shipping address fields
1838:### Test 9.7: Validates invalid postal code format
1865:### Test 9.8: Prevents checkout with empty cart
2956:| Run checkout flow tests | `php artisan test tests/Browser/Storefront/CheckoutTest.php` |


   829→**Total: 7 tests**
   830→
   831→---
   832→
   833→## Suite 5: Admin Discount Management
   834→
   835→**File:** `tests/Browser/Admin/DiscountManagementTest.php`
   836→
   837→**Purpose:** Verify discount code listing, creation (all three value types), editing, and status display.
   838→
   839→### Discount Types
   840→
   841→| Value Type | Description | Fields Required |
   842→|------------|-------------|-----------------|
   843→| `percent` | Percentage off the order subtotal | code, value (whole number, e.g., 25 for 25%), starts_at, optional ends_at |
   844→| `fixed` | Fixed currency amount off | code, value (e.g., 10.00), starts_at, optional ends_at |
   845→| `free_shipping` | Waive shipping costs entirely | code, starts_at, optional ends_at |
   846→
   847→---
   848→
   849→### Test 5.1: Shows seeded discount codes
   850→
   851→**Preconditions:** Logged in as admin, seeded discounts
   852→
   853→**Steps:**
   854→1. Navigate to `/admin/login`
   855→2. Log in as admin
   856→3. Click "Discounts" in the sidebar
   857→
   858→**Expected Results:**
   859→- Page displays "WELCOME10"
   860→- Page displays "FLAT5"
   861→- Page displays "FREESHIP"
   862→- No JavaScript errors
   863→
   864→---
   865→
   866→### Test 5.2: Can create a new percentage discount code
   867→
   868→**Preconditions:** Logged in as admin
   869→
   870→**Steps:**
   871→1. Navigate to `/admin/login`
   872→2. Log in as admin
   873→3. Click "Discounts" in the sidebar
   874→4. Click "Create discount"
   875→5. Enter code: `E2ETEST25`
   876→6. Click "Percentage" to select the value type
   877→7. Enter value: `25`
   878→8. Enter starts_at: `2026-01-01`
   879→9. Enter ends_at: `2026-12-31`
   880→10. Click "Save"
   881→11. Click "Discounts" to return to the list
   882→
   883→**Expected Results:**
   884→- After step 10: page displays "Discount saved", no JavaScript errors
   885→- After step 11: page displays "E2ETEST25" in the list
   886→
   887→---
   888→
   889→### Test 5.3: Can create a fixed amount discount code
   890→
   891→**Preconditions:** Logged in as admin
   892→
   893→**Steps:**
   894→1. Navigate to `/admin/login`
   895→2. Log in as admin
   896→3. Click "Discounts" then "Create discount"
   897→4. Enter code: `E2EFLAT10`
   898→5. Click "Fixed amount" to select the value type
   899→6. Enter value: `10.00`
   900→7. Enter starts_at: `2026-01-01`
   901→8. Click "Save"
   902→
   903→**Expected Results:**
   904→- Page displays "Discount saved"
   905→- No JavaScript errors
   906→
   907→---
   908→
   909→### Test 5.4: Can create a free shipping discount code
   910→
   911→**Preconditions:** Logged in as admin
   912→
   913→**Steps:**
   914→1. Navigate to `/admin/login`
   915→2. Log in as admin
   916→3. Click "Discounts" then "Create discount"
   917→4. Enter code: `E2EFREESHIP`
   918→5. Click "Free shipping" to select the value type
   919→6. Enter starts_at: `2026-01-01`
   920→7. Click "Save"
   921→
   922→**Expected Results:**
   923→- Page displays "Discount saved"
   924→- No JavaScript errors
   925→
   926→---
   927→
   928→### Test 5.5: Can edit a discount
   929→
   930→**Preconditions:** Logged in as admin, WELCOME10 discount exists
   931→
   932→**Steps:**
   933→1. Navigate to `/admin/login`
   934→2. Log in as admin
   935→3. Click "Discounts" in the sidebar
   936→4. Click "WELCOME10" to open the edit form
   937→5. Change the value field to: `15`
   938→6. Click "Save"
   939→
   940→**Expected Results:**
   941→- Page displays "Discount saved"
   942→- No JavaScript errors
   943→
   944→---
   945→
   946→### Test 5.6: Shows discount status indicators
   947→
   948→**Preconditions:** Logged in as admin, seeded discounts (active and expired)
   949→
   950→**Steps:**
   951→1. Navigate to `/admin/login`
   952→2. Log in as admin
   953→3. Click "Discounts" in the sidebar
   954→
   955→**Expected Results:**
   956→- Page displays "Active" status badge (for active discounts)
   957→- Page displays "Expired" status badge (for EXPIRED20)
   958→- No JavaScript errors
   959→
   960→**Total: 6 tests**
   961→
   962→---
   963→
   964→## Suite 6: Admin Settings
   965→
   966→**File:** `tests/Browser/Admin/SettingsTest.php`
   967→
   968→**Purpose:** Verify store settings management across all settings tabs: General, Domains, Shipping, Taxes, Checkout, and Notifications.
   969→
   970→### Settings Page Structure
   971→
   972→The admin settings page at `/admin/settings` uses a tabbed interface:
   973→
   974→| Tab | Route | Content |
   975→|-----|-------|---------|
   976→| General | `/admin/settings` | Store name, contact email, currency, locale |
   977→| Domains | (tab within settings) | Domain list including `acme-fashion.test` |
   978→| Shipping | `/admin/settings/shipping` | Shipping zones, rates per zone |
   979→| Taxes | `/admin/settings/taxes` | Tax mode (manual/provider), prices_include_tax toggle |
   980→| Checkout | `/admin/settings/checkout` | Checkout behavior settings |
   981→| Notifications | `/admin/settings/notifications` | Email notification preferences |
   982→
   983→> **Note:** Domains tab is covered by Test 6.7. Checkout and Notifications tabs follow the same tabbed navigation pattern; E2E coverage for these tabs should be added when their functionality is implemented.
   984→
   985→---
   986→
   987→### Test 6.1: Can view store settings
   988→
   989→**Preconditions:** Logged in as admin, Store 1 exists
   990→
   991→**Steps:**
   992→1. Navigate to `/admin/login`
   993→2. Log in as admin
   994→3. Click "Settings" in the sidebar
   995→
   996→**Expected Results:**
   997→- Page displays "Store Settings"
   998→- Page displays "Acme Fashion"
   999→- No JavaScript errors
  1000→
  1001→---
  1002→
  1003→### Test 6.2: Can update store name
  1004→
  1005→**Preconditions:** Logged in as admin
  1006→
  1007→**Steps:**
  1008→1. Navigate to `/admin/login`
  1009→2. Log in as admin
  1010→3. Click "Settings" in the sidebar
  1011→4. Update the name field to: `Acme Fashion Updated`
  1012→5. Click "Save"
  1013→6. Click "Settings" again to reload the page
  1014→
  1015→**Expected Results:**
  1016→- After step 5: page displays "Settings saved", no JavaScript errors
  1017→- After step 6: page displays "Acme Fashion Updated" (change persisted)
  1018→
  1019→---
  1020→
  1021→### Test 6.3: Can view shipping zones
  1022→
  1023→**Preconditions:** Logged in as admin, seeded shipping zones
  1024→
  1025→**Steps:**
  1026→1. Navigate to `/admin/login`
  1027→2. Log in as admin
  1028→3. Click "Settings" in the sidebar
  1029→4. Click "Shipping" tab
  1030→
  1031→**Expected Results:**
  1032→- Page displays "Domestic"
  1033→- Page displays "Standard Shipping"
  1034→- Page displays "4.99"
  1035→- No JavaScript errors
  1036→
  1037→---
  1038→
  1039→### Test 6.4: Can add a new shipping rate to existing zone
  1040→
  1041→**Preconditions:** Logged in as admin, Domestic shipping zone exists
  1042→
  1043→**Steps:**
  1044→1. Navigate to `/admin/login`
  1045→2. Log in as admin
  1046→3. Click "Settings" in the sidebar
  1047→4. Click "Shipping" tab
  1048→5. Click "Add rate" within the Domestic zone section
  1049→6. Enter name: `Overnight Shipping`
  1050→7. Enter price: `14.99`
  1051→8. Click "Save"
  1052→
  1053→**Expected Results:**
  1054→- Page displays "Shipping rate saved"
  1055→- Page displays "Overnight Shipping" and "14.99" in the zone
  1056→- No JavaScript errors
  1057→
  1058→---
  1059→
  1060→### Test 6.5: Can view tax settings
  1061→
  1062→**Preconditions:** Logged in as admin
  1063→
  1064→**Steps:**
  1065→1. Navigate to `/admin/login`
  1066→2. Log in as admin
  1067→3. Click "Settings" in the sidebar
  1068→4. Click "Taxes" tab
  1069→
  1070→**Expected Results:**
  1071→- Page displays "Tax Settings"
  1072→- No JavaScript errors
  1073→
  1074→---
  1075→
  1076→### Test 6.6: Can update tax inclusion setting
  1077→
  1078→**Preconditions:** Logged in as admin
  1079→
  1080→**Steps:**
  1081→1. Navigate to `/admin/login`
  1082→2. Log in as admin
  1083→3. Click "Settings" in the sidebar
  1084→4. Click "Taxes" tab
  1085→5. Click/toggle the "Prices include tax" switch
  1086→6. Click "Save"
  1087→
  1088→**Expected Results:**
  1089→- Page displays "Tax settings saved"
  1090→- No JavaScript errors
  1091→
  1092→---
  1093→
  1094→### Test 6.7: Can view domain settings
  1095→
  1096→**Preconditions:** Logged in as admin, Store 1 with domain configured
  1097→
  1098→**Steps:**
  1099→1. Navigate to `/admin/login`
  1100→2. Log in as admin
  1101→3. Click "Settings" in the sidebar
  1102→4. Click "Domains" tab
  1103→
  1104→**Expected Results:**
  1105→- Page displays "acme-fashion.test"
  1106→- No JavaScript errors
  1107→
  1108→**Total: 7 tests**
  1109→
  1110→---
  1111→
  1112→## Suite 7: Storefront Browsing
  1113→
  1114→**File:** `tests/Browser/Storefront/BrowsingTest.php`
  1115→
  1116→**Purpose:** Verify all storefront pages render correctly as a guest visitor - home page, collection listings, product detail with variant options, search results, static pages, and content visibility rules (draft/stock).
  1117→
  1118→### Storefront Page Map
  1119→
  1120→```mermaid
  1121→flowchart LR
  1122→    Home["/"] --> Collections["/collections"]
  1123→    Home --> Product["/products/{handle}"]
  1124→    Collections --> CollectionDetail["/collections/{handle}"]
  1125→    CollectionDetail --> Product
  1126→    Home --> Search["/search?q=..."]
  1127→    Search --> Product
  1128→    Home --> Pages["/pages/{handle}"]
  1129→    Product --> Cart["/cart"]
  1130→```
  1131→
  1132→---
  1133→
  1134→### Test 7.1: Shows featured products on home page
  1135→
  1136→**Preconditions:** Seeded products
  1137→
  1138→**Steps:**
  1139→1. Navigate to `/`
  1140→
  1141→**Expected Results:**
  1142→- Page displays "Acme Fashion"
  1143→- Page displays "Classic Cotton T-Shirt"
  1144→- Page displays "24.99"
  1145→- No JavaScript errors
  1146→
  1147→---
  1148→
  1149→### Test 7.2: Shows collection with product grid
  1150→
  1151→**Preconditions:** Seeded collections and products
  1152→
  1153→**Steps:**
  1154→1. Navigate to `/collections/t-shirts`
  1155→
  1156→**Expected Results:**
  1157→- Page displays "T-Shirts"
  1158→- Page displays "Classic Cotton T-Shirt"
  1159→- No JavaScript errors
  1160→
  1161→---
  1162→
  1163→### Test 7.3: Can navigate from collection to product
  1164→
  1165→**Preconditions:** Seeded collections and products
  1166→
  1167→**Steps:**
  1168→1. Navigate to `/collections/t-shirts`
  1169→2. Click "Classic Cotton T-Shirt"
  1170→
  1171→**Expected Results:**
  1172→- Page displays "Classic Cotton T-Shirt"
  1173→- Page displays "24.99"
  1174→- Page displays "Add to cart"
  1175→- No JavaScript errors
  1176→
  1177→---
  1178→
  1179→### Test 7.4: Shows product detail with variant options
  1180→
  1181→**Preconditions:** Product #1
  1182→
  1183→**Steps:**
  1184→1. Navigate to `/products/classic-cotton-t-shirt`
  1185→
  1186→**Expected Results:**
  1187→- Page displays "Classic Cotton T-Shirt"
  1188→- Page displays "24.99"
  1189→- Page displays "Size"
  1190→- Page displays "Color"
  1191→- No JavaScript errors
  1192→
  1193→---
  1194→
  1195→### Test 7.5: Shows size and color option values
  1196→
  1197→**Preconditions:** Product #1
  1198→
  1199→**Steps:**
  1200→1. Navigate to `/products/classic-cotton-t-shirt`
  1201→
  1202→**Expected Results:**
  1203→- Page displays size options: "S", "M", "L", "XL"
  1204→- Page displays color options: "Black", "White", "Navy"
  1205→- No JavaScript errors
  1206→
  1207→---
  1208→
  1209→### Test 7.6: Updates price when variant changes on product with compare-at pricing
  1210→
  1211→**Preconditions:** Product #2 with compare-at pricing
  1212→
  1213→**Steps:**
  1214→1. Navigate to `/products/premium-slim-fit-jeans`
  1215→2. Select a variant that has a compare-at price (sale variant)
  1216→
  1217→**Expected Results:**
  1218→- Page displays "Premium Slim Fit Jeans"
  1219→- Sale price is displayed
  1220→- Compare-at (original) price is shown with strikethrough styling
  1221→- No JavaScript errors
  1222→
  1223→---
  1224→
  1225→### Test 7.7: Shows search results for valid query
  1226→
  1227→**Preconditions:** Seeded products
  1228→
  1229→**Steps:**
  1230→1. Navigate to `/search?q=cotton`
  1231→
  1232→**Expected Results:**
  1233→- Page displays "Classic Cotton T-Shirt" in results
  1234→- No JavaScript errors
  1235→
  1236→---
  1237→
  1238→### Test 7.8: Shows no results message for invalid query
  1239→
  1240→**Preconditions:** None
  1241→
  1242→**Steps:**
  1243→1. Navigate to `/search?q=zznonexistentproductzz`
  1244→
  1245→**Expected Results:**
  1246→- Page displays "No results"
  1247→- No JavaScript errors
  1248→
  1249→---
  1250→
  1251→### Test 7.9: Does not show draft products on storefront collections
  1252→
  1253→**Preconditions:** Product #15 with draft status
  1254→
  1255→**Steps:**
  1256→1. Navigate to `/collections`
  1257→
  1258→**Expected Results:**
  1259→- Product #15 (draft) must NOT appear anywhere in the listing
  1260→- No JavaScript errors
  1261→
  1262→---
  1263→
  1264→### Test 7.10: Does not show draft products in search results
  1265→
  1266→**Preconditions:** Product #15 with draft status
  1267→
  1268→**Steps:**
  1269→1. Navigate to `/search?q=draft`
  1270→
  1271→**Expected Results:**
  1272→- No draft products appear in search results
  1273→- No JavaScript errors
  1274→
  1275→---
  1276→
  1277→### Test 7.11: Shows out of stock messaging for deny-policy product
  1278→
  1279→**Preconditions:** Product #17 (0 stock, inventory policy = deny)
  1280→
  1281→**Steps:**
  1282→1. Navigate to `/products/limited-edition-sneakers`
  1283→
  1284→**Expected Results:**
  1285→- Page displays "Sold out"
  1286→- "Add to cart" button is disabled or hidden
  1287→- No JavaScript errors
  1288→
  1289→---
  1290→
  1291→### Test 7.12: Shows backorder messaging for continue-policy product
  1292→
  1293→**Preconditions:** Product #18 (0 stock, inventory policy = continue)
  1294→
  1295→**Steps:**
  1296→1. Navigate to `/products/backorder-denim-jacket`
  1297→
  1298→**Expected Results:**
  1299→- Page displays "Available on backorder"
  1300→- "Add to cart" button is still enabled
  1301→- No JavaScript errors
  1302→
  1303→---
  1304→
  1305→### Test 7.13: Shows new arrivals collection
  1306→
  1307→**Preconditions:** Seeded collections
  1308→
  1309→**Steps:**
  1310→1. Navigate to `/collections/new-arrivals`
  1311→
  1312→**Expected Results:**
  1313→- Page displays "New Arrivals"
  1314→- No JavaScript errors
  1315→
  1316→---
  1317→
  1318→### Test 7.14: Shows static about page
  1319→
  1320→**Preconditions:** Seeded pages
  1321→
  1322→**Steps:**
  1323→1. Navigate to `/pages/about`
  1324→
  1325→**Expected Results:**
  1326→- Page displays "About"
  1327→- No JavaScript errors
  1328→

**Total: 7 tests**

---

## Suite 5: Admin Discount Management

**File:** `tests/Browser/Admin/DiscountManagementTest.php`

**Purpose:** Verify discount code listing, creation (all three value types), editing, and status display.

### Discount Types

| Value Type | Description | Fields Required |
|------------|-------------|-----------------|
| `percent` | Percentage off the order subtotal | code, value (whole number, e.g., 25 for 25%), starts_at, optional ends_at |
| `fixed` | Fixed currency amount off | code, value (e.g., 10.00), starts_at, optional ends_at |
| `free_shipping` | Waive shipping costs entirely | code, starts_at, optional ends_at |

---

### Test 5.1: Shows seeded discount codes

**Preconditions:** Logged in as admin, seeded discounts

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Discounts" in the sidebar

**Expected Results:**
- Page displays "WELCOME10"
- Page displays "FLAT5"
- Page displays "FREESHIP"
- No JavaScript errors

---

### Test 5.2: Can create a new percentage discount code

**Preconditions:** Logged in as admin

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Discounts" in the sidebar
4. Click "Create discount"
5. Enter code: `E2ETEST25`
6. Click "Percentage" to select the value type
7. Enter value: `25`
8. Enter starts_at: `2026-01-01`
9. Enter ends_at: `2026-12-31`
10. Click "Save"
11. Click "Discounts" to return to the list

**Expected Results:**
- After step 10: page displays "Discount saved", no JavaScript errors
- After step 11: page displays "E2ETEST25" in the list

---

### Test 5.3: Can create a fixed amount discount code

**Preconditions:** Logged in as admin

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Discounts" then "Create discount"
4. Enter code: `E2EFLAT10`
5. Click "Fixed amount" to select the value type
6. Enter value: `10.00`
7. Enter starts_at: `2026-01-01`
8. Click "Save"

**Expected Results:**
- Page displays "Discount saved"
- No JavaScript errors

---

### Test 5.4: Can create a free shipping discount code

**Preconditions:** Logged in as admin

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Discounts" then "Create discount"
4. Enter code: `E2EFREESHIP`
5. Click "Free shipping" to select the value type
6. Enter starts_at: `2026-01-01`
7. Click "Save"

**Expected Results:**
- Page displays "Discount saved"
- No JavaScript errors

---

### Test 5.5: Can edit a discount

**Preconditions:** Logged in as admin, WELCOME10 discount exists

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Discounts" in the sidebar
4. Click "WELCOME10" to open the edit form
5. Change the value field to: `15`
6. Click "Save"

**Expected Results:**
- Page displays "Discount saved"
- No JavaScript errors

---

### Test 5.6: Shows discount status indicators

**Preconditions:** Logged in as admin, seeded discounts (active and expired)

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Discounts" in the sidebar

**Expected Results:**
- Page displays "Active" status badge (for active discounts)
- Page displays "Expired" status badge (for EXPIRED20)
- No JavaScript errors

**Total: 6 tests**

---

## Suite 6: Admin Settings

**File:** `tests/Browser/Admin/SettingsTest.php`

**Purpose:** Verify store settings management across all settings tabs: General, Domains, Shipping, Taxes, Checkout, and Notifications.

### Settings Page Structure

The admin settings page at `/admin/settings` uses a tabbed interface:

| Tab | Route | Content |
|-----|-------|---------|
| General | `/admin/settings` | Store name, contact email, currency, locale |
| Domains | (tab within settings) | Domain list including `acme-fashion.test` |
| Shipping | `/admin/settings/shipping` | Shipping zones, rates per zone |
| Taxes | `/admin/settings/taxes` | Tax mode (manual/provider), prices_include_tax toggle |
| Checkout | `/admin/settings/checkout` | Checkout behavior settings |
| Notifications | `/admin/settings/notifications` | Email notification preferences |

> **Note:** Domains tab is covered by Test 6.7. Checkout and Notifications tabs follow the same tabbed navigation pattern; E2E coverage for these tabs should be added when their functionality is implemented.

---

### Test 6.1: Can view store settings

**Preconditions:** Logged in as admin, Store 1 exists

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Settings" in the sidebar

**Expected Results:**
- Page displays "Store Settings"
- Page displays "Acme Fashion"
- No JavaScript errors

---

### Test 6.2: Can update store name

**Preconditions:** Logged in as admin

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Settings" in the sidebar
4. Update the name field to: `Acme Fashion Updated`
5. Click "Save"
6. Click "Settings" again to reload the page

**Expected Results:**
- After step 5: page displays "Settings saved", no JavaScript errors
- After step 6: page displays "Acme Fashion Updated" (change persisted)

---

### Test 6.3: Can view shipping zones

**Preconditions:** Logged in as admin, seeded shipping zones

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Settings" in the sidebar
4. Click "Shipping" tab

**Expected Results:**
- Page displays "Domestic"
- Page displays "Standard Shipping"
- Page displays "4.99"
- No JavaScript errors

---

### Test 6.4: Can add a new shipping rate to existing zone

**Preconditions:** Logged in as admin, Domestic shipping zone exists

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Settings" in the sidebar
4. Click "Shipping" tab
5. Click "Add rate" within the Domestic zone section
6. Enter name: `Overnight Shipping`
7. Enter price: `14.99`
8. Click "Save"

**Expected Results:**
- Page displays "Shipping rate saved"
- Page displays "Overnight Shipping" and "14.99" in the zone
- No JavaScript errors

---

### Test 6.5: Can view tax settings

**Preconditions:** Logged in as admin

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Settings" in the sidebar
4. Click "Taxes" tab

**Expected Results:**
- Page displays "Tax Settings"
- No JavaScript errors

---

### Test 6.6: Can update tax inclusion setting

**Preconditions:** Logged in as admin

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Settings" in the sidebar
4. Click "Taxes" tab
5. Click/toggle the "Prices include tax" switch
6. Click "Save"

**Expected Results:**
- Page displays "Tax settings saved"
- No JavaScript errors

---

### Test 6.7: Can view domain settings

**Preconditions:** Logged in as admin, Store 1 with domain configured

**Steps:**
1. Navigate to `/admin/login`
2. Log in as admin
3. Click "Settings" in the sidebar
4. Click "Domains" tab

**Expected Results:**
- Page displays "acme-fashion.test"
- No JavaScript errors

**Total: 7 tests**

---

## Suite 7: Storefront Browsing

**File:** `tests/Browser/Storefront/BrowsingTest.php`

**Purpose:** Verify all storefront pages render correctly as a guest visitor - home page, collection listings, product detail with variant options, search results, static pages, and content visibility rules (draft/stock).

### Storefront Page Map

```mermaid
flowchart LR
    Home["/"] --> Collections["/collections"]
    Home --> Product["/products/{handle}"]
    Collections --> CollectionDetail["/collections/{handle}"]
    CollectionDetail --> Product
    Home --> Search["/search?q=..."]
    Search --> Product
    Home --> Pages["/pages/{handle}"]
    Product --> Cart["/cart"]
```

---

### Test 7.1: Shows featured products on home page

**Preconditions:** Seeded products

**Steps:**
1. Navigate to `/`

**Expected Results:**
- Page displays "Acme Fashion"
- Page displays "Classic Cotton T-Shirt"
- Page displays "24.99"
- No JavaScript errors

---

### Test 7.2: Shows collection with product grid

**Preconditions:** Seeded collections and products

**Steps:**
1. Navigate to `/collections/t-shirts`

**Expected Results:**
- Page displays "T-Shirts"
- Page displays "Classic Cotton T-Shirt"
- No JavaScript errors

---

### Test 7.3: Can navigate from collection to product

**Preconditions:** Seeded collections and products

**Steps:**
1. Navigate to `/collections/t-shirts`
2. Click "Classic Cotton T-Shirt"

**Expected Results:**
- Page displays "Classic Cotton T-Shirt"
- Page displays "24.99"
- Page displays "Add to cart"
- No JavaScript errors

---

### Test 7.4: Shows product detail with variant options

**Preconditions:** Product #1

**Steps:**
1. Navigate to `/products/classic-cotton-t-shirt`

**Expected Results:**
- Page displays "Classic Cotton T-Shirt"
- Page displays "24.99"
- Page displays "Size"
- Page displays "Color"
- No JavaScript errors

---

### Test 7.5: Shows size and color option values

**Preconditions:** Product #1

**Steps:**
1. Navigate to `/products/classic-cotton-t-shirt`

**Expected Results:**
- Page displays size options: "S", "M", "L", "XL"
- Page displays color options: "Black", "White", "Navy"
- No JavaScript errors

---

### Test 7.6: Updates price when variant changes on product with compare-at pricing

**Preconditions:** Product #2 with compare-at pricing

**Steps:**
1. Navigate to `/products/premium-slim-fit-jeans`
2. Select a variant that has a compare-at price (sale variant)

**Expected Results:**
- Page displays "Premium Slim Fit Jeans"
- Sale price is displayed
- Compare-at (original) price is shown with strikethrough styling
- No JavaScript errors

---

### Test 7.7: Shows search results for valid query

**Preconditions:** Seeded products

**Steps:**
1. Navigate to `/search?q=cotton`

**Expected Results:**
- Page displays "Classic Cotton T-Shirt" in results
- No JavaScript errors

---

### Test 7.8: Shows no results message for invalid query

**Preconditions:** None

**Steps:**
1. Navigate to `/search?q=zznonexistentproductzz`

**Expected Results:**
- Page displays "No results"
- No JavaScript errors

---

### Test 7.9: Does not show draft products on storefront collections

**Preconditions:** Product #15 with draft status

**Steps:**
1. Navigate to `/collections`

**Expected Results:**
- Product #15 (draft) must NOT appear anywhere in the listing
- No JavaScript errors

---

### Test 7.10: Does not show draft products in search results

**Preconditions:** Product #15 with draft status

**Steps:**
1. Navigate to `/search?q=draft`

**Expected Results:**
- No draft products appear in search results
- No JavaScript errors

---

### Test 7.11: Shows out of stock messaging for deny-policy product

**Preconditions:** Product #17 (0 stock, inventory policy = deny)

**Steps:**
1. Navigate to `/products/limited-edition-sneakers`

**Expected Results:**
- Page displays "Sold out"
- "Add to cart" button is disabled or hidden
- No JavaScript errors

---

### Test 7.12: Shows backorder messaging for continue-policy product

**Preconditions:** Product #18 (0 stock, inventory policy = continue)

**Steps:**
1. Navigate to `/products/backorder-denim-jacket`

**Expected Results:**
- Page displays "Available on backorder"
- "Add to cart" button is still enabled
- No JavaScript errors

---

### Test 7.13: Shows new arrivals collection

**Preconditions:** Seeded collections

**Steps:**
1. Navigate to `/collections/new-arrivals`

**Expected Results:**
- Page displays "New Arrivals"
- No JavaScript errors

---

### Test 7.14: Shows static about page

**Preconditions:** Seeded pages

**Steps:**
1. Navigate to `/pages/about`

**Expected Results:**
- Page displays "About"
- No JavaScript errors


    23→| Auth (customer) | Separate guard | Custom `customer` guard | Storefront customer auth |
    24→| OAuth (apps) | Passport | Deferred/Stubbed | Simplifies initial build, can add later |
    25→| Payments | Stripe + PayPal | Stripe test mode + PayPal stub | Stripe with test keys, PayPal interface only |
    26→| Email | SMTP/SES | Log driver (dev) / SMTP | Log for benchmark, SMTP configurable |
    27→| Monitoring | OpenTelemetry | Laravel Log (structured JSON) | Simplified for benchmark |
    28→| CI/CD | GitHub Actions | Not implemented | Out of scope for one-shot benchmark |
    29→| Docker | Multi-stage | Not implemented | App served by Laravel Herd locally |
    30→
    31→---
    32→

| Auth (customer) | Separate guard | Custom `customer` guard | Storefront customer auth |
| OAuth (apps) | Passport | Deferred/Stubbed | Simplifies initial build, can add later |
| Payments | Stripe + PayPal | Stripe test mode + PayPal stub | Stripe with test keys, PayPal interface only |
| Email | SMTP/SES | Log driver (dev) / SMTP | Log for benchmark, SMTP configurable |
| Monitoring | OpenTelemetry | Laravel Log (structured JSON) | Simplified for benchmark |
| CI/CD | GitHub Actions | Not implemented | Out of scope for one-shot benchmark |
| Docker | Multi-stage | Not implemented | App served by Laravel Herd locally |

---


Now let me also read the checkout test suite in 08-PLAYWRIGHT-E2E-PLAN.md.


Let me read the checkout test suite (Suite 9).


  1618→## Suite 9: Checkout Flow
  1619→
  1620→**File:** `tests/Browser/Storefront/CheckoutTest.php`
  1621→
  1622→**Purpose:** Verify the complete multi-step checkout process: cart to contact info, shipping address, shipping method selection, payment, and order confirmation.
  1623→
  1624→### Checkout Step Flow
  1625→
  1626→```mermaid
  1627→flowchart TD
  1628→    Cart[Cart Page] -->|click 'Checkout'| Step1[Step 1: Contact Info]
  1629→    Step1 -->|fill email, click 'Continue'| Step2[Step 2: Shipping Address]
  1630→    Step2 -->|fill address fields, click 'Continue'| Step3[Step 3: Shipping Method]
  1631→    Step3 -->|select method, click 'Continue'| Step4[Step 4: Payment]
  1632→    Step4 -->|click 'Pay now'| Confirmation[Order Confirmation]
  1633→
  1634→    Step1 -->|empty email| Step1Error[Validation error on email]
  1635→    Step2 -->|missing fields| Step2Error[Validation errors on address fields]
  1636→    Step2 -->|invalid postal code| Step2PError[Validation error on postal code]
  1637→
  1638→    Step2 -->|Country = DE| DomesticRates[Domestic: Standard 4.99 EUR]
  1639→    Step2 -->|Country = US| InternationalRates[International rates]
  1640→
  1641→    Cart -->|Empty cart| EmptyState[Checkout disabled]
  1642→```
  1643→
  1644→### Checkout Step Detail
  1645→
  1646→| Step | Section | Required Fields | Visible After Completion |
  1647→|------|---------|-----------------|--------------------------|
  1648→| 1. Contact | Contact section | email | Email summary shown |
  1649→| 2. Shipping Address | Address section | first_name, last_name, address1, city, postal_code, country (select) | Address summary shown |
  1650→| 3. Shipping Method | Shipping section | Select one of the available shipping rates | Selected method + price shown |
  1651→| 4. Payment | Payment section | Payment method (Stripe test mode) | Redirect to confirmation |
  1652→| 5. Confirmation | Confirmation page | n/a (read only) | "Thank you", order number, line items, totals |
  1653→
  1654→### Totals Calculation Reference
  1655→
  1656→For the happy path (Classic Cotton T-Shirt, DE address, Standard Shipping):
  1657→
  1658→| Line | Amount |
  1659→|------|--------|
  1660→| Subtotal | 24.99 EUR |
  1661→| Shipping (Standard, Domestic DE) | 4.99 EUR |
  1662→| Tax (included in price for DE, manual mode) | varies |
  1663→| **Total** | **29.98 EUR** |
  1664→
  1665→With FLAT5 discount applied:
  1666→
  1667→| Line | Amount |
  1668→|------|--------|
  1669→| Subtotal | 24.99 EUR |
  1670→| Discount (FLAT5) | -5.00 EUR |
  1671→| Shipping | 4.99 EUR |
  1672→| **Total** | **24.98 EUR** |
  1673→
  1674→---
  1675→
  1676→### Test 9.1: Completes full checkout happy path
  1677→
  1678→**Preconditions:** Product #1, Domestic shipping zone with Standard Shipping at 4.99 EUR
  1679→
  1680→**Steps:**
  1681→1. Navigate to `/products/classic-cotton-t-shirt`
  1682→2. Click "M" to select size M
  1683→3. Click "Black" to select color Black
  1684→4. Click "Add to cart"
  1685→5. Navigate to `/cart`
  1686→6. Click "Checkout"
  1687→7. Enter email: `test-buyer@example.com`
  1688→8. Click "Continue"
  1689→9. Enter first name: `Test`
  1690→10. Enter last name: `Buyer`
  1691→11. Enter address: `Teststrasse 1`
  1692→12. Enter city: `Berlin`
  1693→13. Enter postal code: `10115`
  1694→14. Enter country: `DE`
  1695→15. Click "Continue"
  1696→16. Verify "Standard Shipping" and "4.99" are displayed
  1697→17. Click "Standard Shipping" to select it
  1698→18. Click "Continue"
  1699→19. Verify total is 29.98 (24.99 + 4.99 shipping)
  1700→20. Click "Pay now" (test/mock payment flow)
  1701→
  1702→**Expected Results:**
  1703→- Confirmation page displays "Thank you"
  1704→- Confirmation page displays an order number (prefixed with "#")
  1705→- No JavaScript errors
  1706→
  1707→---
  1708→
  1709→### Test 9.2: Shows shipping methods based on German address
  1710→
  1711→**Preconditions:** Product #1 in cart, Domestic shipping zone
  1712→
  1713→**Steps:**
  1714→1. Navigate to `/products/classic-cotton-t-shirt`
  1715→2. Click "M" to select size M
  1716→3. Click "Black" to select color Black
  1717→4. Click "Add to cart"
  1718→5. Navigate to `/cart`
  1719→6. Click "Checkout"
  1720→7. Enter email: `test@example.com`
  1721→8. Click "Continue"
  1722→9. Enter first name: `Hans`
  1723→10. Enter last name: `Mueller`
  1724→11. Enter address: `Berliner Str. 10`
  1725→12. Enter city: `Munich`
  1726→13. Enter postal code: `80331`
  1727→14. Enter country: `DE`
  1728→15. Click "Continue"
  1729→
  1730→**Expected Results:**
  1731→- Page displays "Standard Shipping"
  1732→- Page displays "4.99" (Domestic zone rate)
  1733→- No JavaScript errors
  1734→
  1735→---
  1736→
  1737→### Test 9.3: Shows international shipping methods for non-DE address
  1738→
  1739→**Preconditions:** Product #1 in cart, International shipping zone
  1740→
  1741→**Steps:**
  1742→1. Navigate to `/products/classic-cotton-t-shirt`
  1743→2. Click "M" to select size M
  1744→3. Click "Black" to select color Black
  1745→4. Click "Add to cart"
  1746→5. Navigate to `/cart`
  1747→6. Click "Checkout"
  1748→7. Enter email: `test@example.com`
  1749→8. Click "Continue"
  1750→9. Enter first name: `John`
  1751→10. Enter last name: `Smith`
  1752→11. Enter address: `123 Main St`
  1753→12. Enter city: `New York`
  1754→13. Enter postal code: `10001`
  1755→14. Enter country: `US`
  1756→15. Click "Continue"
  1757→
  1758→**Expected Results:**
  1759→- Page displays "International" shipping rates (not Domestic rates)
  1760→- No JavaScript errors
  1761→
  1762→---
  1763→
  1764→### Test 9.4: Applies discount during checkout
  1765→
  1766→**Preconditions:** Product #1, FLAT5 discount, Domestic shipping zone
  1767→
  1768→**Steps:**
  1769→1. Navigate to `/products/classic-cotton-t-shirt`
  1770→2. Click "M" to select size M
  1771→3. Click "Black" to select color Black
  1772→4. Click "Add to cart"
  1773→5. Navigate to `/cart`
  1774→6. Enter discount code: `FLAT5` in the discount_code field
  1775→7. Click "Apply"
  1776→8. Verify "FLAT5" is displayed
  1777→9. Click "Checkout"
  1778→10. Enter email: `test@example.com`
  1779→11. Click "Continue"
  1780→12. Enter first name: `Test`
  1781→13. Enter last name: `User`
  1782→14. Enter address: `Teststr 1`
  1783→15. Enter city: `Berlin`
  1784→16. Enter postal code: `10115`
  1785→17. Enter country: `DE`
  1786→18. Click "Continue"
  1787→19. Click "Standard Shipping" to select it
  1788→20. Click "Continue"
  1789→
  1790→**Expected Results:**
  1791→- Page displays "FLAT5" label in checkout totals
  1792→- Page displays "5.00" discount amount
  1793→- Totals reflect: 24.99 - 5.00 + 4.99 = 24.98
  1794→- No JavaScript errors
  1795→
  1796→---
  1797→
  1798→### Test 9.5: Validates required contact email
  1799→
  1800→**Preconditions:** Product #1 in cart
  1801→
  1802→**Steps:**
  1803→1. Navigate to `/products/classic-cotton-t-shirt`
  1804→2. Click "M" to select size M
  1805→3. Click "Black" to select color Black
  1806→4. Click "Add to cart"
  1807→5. Navigate to `/cart`
  1808→6. Click "Checkout"
  1809→7. Click "Continue" without entering an email
  1810→
  1811→**Expected Results:**
  1812→- Validation error referencing "email" is displayed
  1813→- No JavaScript errors
  1814→
  1815→---
  1816→
  1817→### Test 9.6: Validates required shipping address fields
  1818→
  1819→**Preconditions:** Product #1 in cart
  1820→
  1821→**Steps:**
  1822→1. Navigate to `/products/classic-cotton-t-shirt`
  1823→2. Click "M" to select size M
  1824→3. Click "Black" to select color Black
  1825→4. Click "Add to cart"
  1826→5. Navigate to `/cart`
  1827→6. Click "Checkout"
  1828→7. Enter email: `test@example.com`
  1829→8. Click "Continue"
  1830→9. Click "Continue" without filling any address fields
  1831→
  1832→**Expected Results:**
  1833→- Validation errors for required fields (first_name, last_name, address1, city, postal_code, country) are displayed
  1834→- No JavaScript errors
  1835→
  1836→---
  1837→
  1838→### Test 9.7: Validates invalid postal code format
  1839→
  1840→**Preconditions:** Product #1 in cart
  1841→
  1842→**Steps:**
  1843→1. Navigate to `/products/classic-cotton-t-shirt`
  1844→2. Click "M" to select size M
  1845→3. Click "Black" to select color Black
  1846→4. Click "Add to cart"
  1847→5. Navigate to `/cart`
  1848→6. Click "Checkout"
  1849→7. Enter email: `test@example.com`
  1850→8. Click "Continue"
  1851→9. Enter first name: `Test`
  1852→10. Enter last name: `User`
  1853→11. Enter address: `Teststr 1`
  1854→12. Enter city: `Berlin`
  1855→13. Enter postal code: `INVALID`
  1856→14. Enter country: `DE`
  1857→15. Click "Continue"
  1858→
  1859→**Expected Results:**
  1860→- Validation error for postal code format is displayed
  1861→- No JavaScript errors
  1862→
  1863→---
  1864→
  1865→### Test 9.8: Prevents checkout with empty cart
  1866→
  1867→**Preconditions:** No products in cart
  1868→
  1869→**Steps:**
  1870→1. Navigate to `/cart` (without adding any items)
  1871→
  1872→**Expected Results:**
  1873→- Page displays "Your cart is empty"
  1874→- "Checkout" button is disabled or not shown
  1875→- No JavaScript errors
  1876→
  1877→**Total: 8 tests**
  1878→
  1879→---
  1880→
  1881→## Suite 10: Customer Account
  1882→
  1883→**File:** `tests/Browser/Storefront/CustomerAccountTest.php`
  1884→
  1885→**Purpose:** Verify customer registration, login, account dashboard, order history, order detail, address management, and logout on the storefront.
  1886→
  1887→### Customer Account Flow
  1888→
  1889→```mermaid
  1890→flowchart TD
  1891→    Guest[Guest visitor] -->|visit /account/register| Register[Registration Form]
  1892→    Guest -->|visit /account/login| Login[Login Form]
  1893→    Register -->|valid data + click 'Create account'| Dashboard[My Account Dashboard]
  1894→    Login -->|valid credentials + click 'Log in'| Dashboard
  1895→    Login -->|invalid credentials| Login
  1896→    Dashboard --> OrderHistory[/account/orders]
  1897→    OrderHistory --> OrderDetail[/account/orders/number]
  1898→    Dashboard --> Addresses[/account/addresses]
  1899→    Addresses -->|click 'Add address'| AddAddress[Address form]
  1900→    Addresses -->|click 'Edit'| EditAddress[Edit address form]
  1901→    Dashboard -->|click 'Logout'| Guest
  1902→```
  1903→
  1904→---
  1905→
  1906→### Test 10.1: Can register a new customer
  1907→
  1908→**Preconditions:** None
  1909→
  1910→**Steps:**
  1911→1. Navigate to `/account/register`
  1912→2. Enter name: `New Customer`
  1913→3. Enter email: `new-customer-e2e@example.com`
  1914→4. Enter password: `password123`
  1915→5. Enter password confirmation: `password123`
  1916→6. Click "Create account"
  1917→

## Suite 9: Checkout Flow

**File:** `tests/Browser/Storefront/CheckoutTest.php`

**Purpose:** Verify the complete multi-step checkout process: cart to contact info, shipping address, shipping method selection, payment, and order confirmation.

### Checkout Step Flow

```mermaid
flowchart TD
    Cart[Cart Page] -->|click 'Checkout'| Step1[Step 1: Contact Info]
    Step1 -->|fill email, click 'Continue'| Step2[Step 2: Shipping Address]
    Step2 -->|fill address fields, click 'Continue'| Step3[Step 3: Shipping Method]
    Step3 -->|select method, click 'Continue'| Step4[Step 4: Payment]
    Step4 -->|click 'Pay now'| Confirmation[Order Confirmation]

    Step1 -->|empty email| Step1Error[Validation error on email]
    Step2 -->|missing fields| Step2Error[Validation errors on address fields]
    Step2 -->|invalid postal code| Step2PError[Validation error on postal code]

    Step2 -->|Country = DE| DomesticRates[Domestic: Standard 4.99 EUR]
    Step2 -->|Country = US| InternationalRates[International rates]

    Cart -->|Empty cart| EmptyState[Checkout disabled]
```

### Checkout Step Detail

| Step | Section | Required Fields | Visible After Completion |
|------|---------|-----------------|--------------------------|
| 1. Contact | Contact section | email | Email summary shown |
| 2. Shipping Address | Address section | first_name, last_name, address1, city, postal_code, country (select) | Address summary shown |
| 3. Shipping Method | Shipping section | Select one of the available shipping rates | Selected method + price shown |
| 4. Payment | Payment section | Payment method (Stripe test mode) | Redirect to confirmation |
| 5. Confirmation | Confirmation page | n/a (read only) | "Thank you", order number, line items, totals |

### Totals Calculation Reference

For the happy path (Classic Cotton T-Shirt, DE address, Standard Shipping):

| Line | Amount |
|------|--------|
| Subtotal | 24.99 EUR |
| Shipping (Standard, Domestic DE) | 4.99 EUR |
| Tax (included in price for DE, manual mode) | varies |
| **Total** | **29.98 EUR** |

With FLAT5 discount applied:

| Line | Amount |
|------|--------|
| Subtotal | 24.99 EUR |
| Discount (FLAT5) | -5.00 EUR |
| Shipping | 4.99 EUR |
| **Total** | **24.98 EUR** |

---

### Test 9.1: Completes full checkout happy path

**Preconditions:** Product #1, Domestic shipping zone with Standard Shipping at 4.99 EUR

**Steps:**
1. Navigate to `/products/classic-cotton-t-shirt`
2. Click "M" to select size M
3. Click "Black" to select color Black
4. Click "Add to cart"
5. Navigate to `/cart`
6. Click "Checkout"
7. Enter email: `test-buyer@example.com`
8. Click "Continue"
9. Enter first name: `Test`
10. Enter last name: `Buyer`
11. Enter address: `Teststrasse 1`
12. Enter city: `Berlin`
13. Enter postal code: `10115`
14. Enter country: `DE`
15. Click "Continue"
16. Verify "Standard Shipping" and "4.99" are displayed
17. Click "Standard Shipping" to select it
18. Click "Continue"
19. Verify total is 29.98 (24.99 + 4.99 shipping)
20. Click "Pay now" (test/mock payment flow)

**Expected Results:**
- Confirmation page displays "Thank you"
- Confirmation page displays an order number (prefixed with "#")
- No JavaScript errors

---

### Test 9.2: Shows shipping methods based on German address

**Preconditions:** Product #1 in cart, Domestic shipping zone

**Steps:**
1. Navigate to `/products/classic-cotton-t-shirt`
2. Click "M" to select size M
3. Click "Black" to select color Black
4. Click "Add to cart"
5. Navigate to `/cart`
6. Click "Checkout"
7. Enter email: `test@example.com`
8. Click "Continue"
9. Enter first name: `Hans`
10. Enter last name: `Mueller`
11. Enter address: `Berliner Str. 10`
12. Enter city: `Munich`
13. Enter postal code: `80331`
14. Enter country: `DE`
15. Click "Continue"

**Expected Results:**
- Page displays "Standard Shipping"
- Page displays "4.99" (Domestic zone rate)
- No JavaScript errors

---

### Test 9.3: Shows international shipping methods for non-DE address

**Preconditions:** Product #1 in cart, International shipping zone

**Steps:**
1. Navigate to `/products/classic-cotton-t-shirt`
2. Click "M" to select size M
3. Click "Black" to select color Black
4. Click "Add to cart"
5. Navigate to `/cart`
6. Click "Checkout"
7. Enter email: `test@example.com`
8. Click "Continue"
9. Enter first name: `John`
10. Enter last name: `Smith`
11. Enter address: `123 Main St`
12. Enter city: `New York`
13. Enter postal code: `10001`
14. Enter country: `US`
15. Click "Continue"

**Expected Results:**
- Page displays "International" shipping rates (not Domestic rates)
- No JavaScript errors

---

### Test 9.4: Applies discount during checkout

**Preconditions:** Product #1, FLAT5 discount, Domestic shipping zone

**Steps:**
1. Navigate to `/products/classic-cotton-t-shirt`
2. Click "M" to select size M
3. Click "Black" to select color Black
4. Click "Add to cart"
5. Navigate to `/cart`
6. Enter discount code: `FLAT5` in the discount_code field
7. Click "Apply"
8. Verify "FLAT5" is displayed
9. Click "Checkout"
10. Enter email: `test@example.com`
11. Click "Continue"
12. Enter first name: `Test`
13. Enter last name: `User`
14. Enter address: `Teststr 1`
15. Enter city: `Berlin`
16. Enter postal code: `10115`
17. Enter country: `DE`
18. Click "Continue"
19. Click "Standard Shipping" to select it
20. Click "Continue"

**Expected Results:**
- Page displays "FLAT5" label in checkout totals
- Page displays "5.00" discount amount
- Totals reflect: 24.99 - 5.00 + 4.99 = 24.98
- No JavaScript errors

---

### Test 9.5: Validates required contact email

**Preconditions:** Product #1 in cart

**Steps:**
1. Navigate to `/products/classic-cotton-t-shirt`
2. Click "M" to select size M
3. Click "Black" to select color Black
4. Click "Add to cart"
5. Navigate to `/cart`
6. Click "Checkout"
7. Click "Continue" without entering an email

**Expected Results:**
- Validation error referencing "email" is displayed
- No JavaScript errors

---

### Test 9.6: Validates required shipping address fields

**Preconditions:** Product #1 in cart

**Steps:**
1. Navigate to `/products/classic-cotton-t-shirt`
2. Click "M" to select size M
3. Click "Black" to select color Black
4. Click "Add to cart"
5. Navigate to `/cart`
6. Click "Checkout"
7. Enter email: `test@example.com`
8. Click "Continue"
9. Click "Continue" without filling any address fields

**Expected Results:**
- Validation errors for required fields (first_name, last_name, address1, city, postal_code, country) are displayed
- No JavaScript errors

---

### Test 9.7: Validates invalid postal code format

**Preconditions:** Product #1 in cart

**Steps:**
1. Navigate to `/products/classic-cotton-t-shirt`
2. Click "M" to select size M
3. Click "Black" to select color Black
4. Click "Add to cart"
5. Navigate to `/cart`
6. Click "Checkout"
7. Enter email: `test@example.com`
8. Click "Continue"
9. Enter first name: `Test`
10. Enter last name: `User`
11. Enter address: `Teststr 1`
12. Enter city: `Berlin`
13. Enter postal code: `INVALID`
14. Enter country: `DE`
15. Click "Continue"

**Expected Results:**
- Validation error for postal code format is displayed
- No JavaScript errors

---

### Test 9.8: Prevents checkout with empty cart

**Preconditions:** No products in cart

**Steps:**
1. Navigate to `/cart` (without adding any items)

**Expected Results:**
- Page displays "Your cart is empty"
- "Checkout" button is disabled or not shown
- No JavaScript errors

**Total: 8 tests**

---

## Suite 10: Customer Account

**File:** `tests/Browser/Storefront/CustomerAccountTest.php`

**Purpose:** Verify customer registration, login, account dashboard, order history, order detail, address management, and logout on the storefront.

### Customer Account Flow

```mermaid
flowchart TD
    Guest[Guest visitor] -->|visit /account/register| Register[Registration Form]
    Guest -->|visit /account/login| Login[Login Form]
    Register -->|valid data + click 'Create account'| Dashboard[My Account Dashboard]
    Login -->|valid credentials + click 'Log in'| Dashboard
    Login -->|invalid credentials| Login
    Dashboard --> OrderHistory[/account/orders]
    OrderHistory --> OrderDetail[/account/orders/number]
    Dashboard --> Addresses[/account/addresses]
    Addresses -->|click 'Add address'| AddAddress[Address form]
    Addresses -->|click 'Edit'| EditAddress[Edit address form]
    Dashboard -->|click 'Logout'| Guest
```

---

### Test 10.1: Can register a new customer

**Preconditions:** None

**Steps:**
1. Navigate to `/account/register`
2. Enter name: `New Customer`
3. Enter email: `new-customer-e2e@example.com`
4. Enter password: `password123`
5. Enter password confirmation: `password123`
6. Click "Create account"


Now I have all the information I need. Let me start making the edits. I'll work through each file systematically.

Let me start with finishing Task #3 (02-API-ROUTES.md), then move to the other tasks.

For 02-API-ROUTES.md, the remaining changes are:
1. Fix the order response example that shows `"provider": "stripe"` (line 1946) - change to "mock" and add "method"
2. Remove the entire Section 4 (Webhook Receiver Endpoints) - lines 2850-2985
3. Remove webhooks middleware section (lines 3131-3139)


Now I have all the context I need. Let me start making edits. I'll work through the files systematically, starting with finishing Task #3 (02-API-ROUTES.md).


Edit

        "provider": "stripe",
        "provider_payment_id": "pi_abc123",
        "status": "captured",
        "amount": 5950,
        "currency": "EUR",
        "created_at": "2026-02-11T10:10:00Z"

        "provider": "mock",
        "method": "credit_card",
        "provider_payment_id": "mock_abc123",
        "status": "captured",
        "amount": 5950,
        "currency": "EUR",
        "created_at": "2026-02-11T10:10:00Z"