   900→|  ------|------------- |---------|----------|---------|-------|
   901→|  [img] | Title        | $24.99  | [-][2][+]| $49.98  | [X]   |
   902→|        | Size: M      |         |          |         |       |
   903→|  [img] | Title        | $34.99  | [-][1][+]| $34.99  | [X]   |
   904→|        | Color: Blue  |         |          |         |       |
   905→+--------------------------------------------------------------+
   906→|                                            Subtotal: $84.97   |
   907→|                              [Discount code] [Apply]          |
   908→|                                            Discount: -$12.75  |
   909→|                                            Total:    $72.22   |
   910→|                                                               |
   911→|                                            [Checkout button]  |
   912→|                                            [Continue shopping]|
   913→+--------------------------------------------------------------+
   914→```
   915→
   916→| Attribute | Description |
   917→|---|---|
   918→| Container | Constrained max-width, centered, with horizontal padding and vertical spacing |
   919→| Title | H1, large bold, generous bottom margin |
   920→| Desktop layout | Proper table with header row and body |
   921→| Table headers | Left-aligned, small, semi-bold, muted, uppercase, wide tracking |
   922→| Mobile layout | Card-based layout (same as cart drawer items, stacked) |
   923→
   924→### 7.2 Totals and Actions
   925→
   926→- Right-aligned on desktop, full-width on mobile
   927→- Same discount code input/apply functionality as drawer
   928→- Same totals breakdown
   929→- "Checkout" button: primary, aligned right
   930→- "Continue shopping" link: below checkout button
   931→
   932→### 7.3 Empty State
   933→
   934→Same as drawer empty state but centered in the page content area with more vertical padding.
   935→
   936→---
   937→
   938→## 8. Checkout Page (GET /checkout/{checkoutId})
   939→
   940→**Livewire Component:** CheckoutPage
   941→
   942→### 8.1 Layout
   943→
   944→**Desktop (large breakpoint and above):** Two-column layout. Left column (wider, approx 60%) contains the form sections. Right column (narrower, approx 40%) contains the sticky order summary.
   945→
   946→**Mobile:** Single column. Order summary is a collapsible section at the top (collapsed by default, showing a toggle with "Show order summary" / "Hide order summary" + chevron + total amount).
   947→
   948→```
   949→Desktop:
   950→+-------------------------------+-------------------+
   951→|  [Checkout Form Sections]     | [Order Summary]   |
   952→|  (wider, ~60%)                | (narrower, ~40%)  |
   953→|                               | (sticky)          |
   954→+-------------------------------+-------------------+
   955→
   956→Mobile:
   957→+--------------------------------------------------------------+
   958→|  [Order Summary - collapsible]                                |
   959→+--------------------------------------------------------------+
   960→|  [Checkout Form Sections]                                     |
   961→+--------------------------------------------------------------+
   962→```
   963→
   964→### 8.2 Checkout Steps
   965→
   966→The checkout uses a single-page design with collapsible sections. Steps: Contact, Shipping Address, Shipping Method, Payment.
   967→
   968→**Step State Behavior:**
   969→
   970→| State | Description |
   971→|---|---|
   972→| Completed | Shows summary of entered data with an "Edit" link |
   973→| Current | Expanded with the full form |
   974→| Future | Collapsed, muted, not interactive |
   975→
   976→Each step header shows the step number + title, e.g., "1. Contact information".
   977→
   978→#### Step 1 - Contact Information
   979→
   980→| Field | Description |
   981→|---|---|
   982→| Email | Full-width email input, required. Validated on blur |
   983→| Account link | "Already have an account? Log in" link below email, small blue text |
   984→| Continue button | Primary, below the form |
   985→
   986→#### Step 2 - Shipping Address
   987→
   988→Uses the address-form component.
   989→
   990→**Fields:**
   991→
   992→| Field | Type | Required | Layout Width |
   993→|---|---|---|---|
   994→| First name | Text | Yes | Half |
   995→| Last name | Text | Yes | Half |
   996→| Address line 1 | Text | Yes | Full |
   997→| Address line 2 | Text | No | Full |
   998→| City | Text | Yes | Half |
   999→| State/Province | Select or text | Yes (varies by country) | Half |
  1000→| Postal code | Text | Yes | Half |
  1001→| Country | Select | Yes | Half |
  1002→| Phone | Tel | No | Full |
  1003→
  1004→- Layout: 2-column grid, full-width fields span both columns
  1005→- Country select: on change, State/Province updates to show relevant regions
  1006→- All fields validate on blur
  1007→
  1008→**Billing address:**
  1009→
  1010→- Checkbox: "Billing address same as shipping" (checked by default)
  1011→- When unchecked, a second address form appears for billing
  1012→
  1013→**Saved addresses (logged-in customers):**
  1014→
  1015→- Dropdown above the form: "Select a saved address" with saved addresses listed
  1016→- "Use a new address" option to show the blank form
  1017→- On selection, form fields populate with the saved address data
  1018→
  1019→Continue button: primary.
  1020→
  1021→#### Step 3 - Shipping Method
  1022→
  1023→Radio button list of available shipping methods, filtered server-side by matching the shipping address to shipping zones.
  1024→
  1025→```
  1026→+--------------------------------------------------------------+
  1027→| (o) Standard Shipping          5-7 business days       $7.99 |
  1028→| ( ) Express Shipping           2-3 business days      $14.99 |
  1029→| ( ) Free Shipping              7-10 business days      Free  |
  1030→+--------------------------------------------------------------+
  1031→```
  1032→
  1033→| Attribute | Description |
  1034→|---|---|
  1035→| Each option | Bordered, rounded card with padding. Selected: primary border with subtle background tint. Unselected: gray border |
  1036→| Label layout | Method name (left), estimated delivery (center), price (right) |
  1037→| Live binding | On selection, the order summary sidebar updates totals |
  1038→| No methods | Warning message: "No shipping methods are available for your address. Please verify your address or contact us." in amber text with info icon |
  1039→
  1040→Continue button: primary.
  1041→
  1042→#### Step 4 - Payment
  1043→
  1044→| Element | Description |
  1045→|---|---|
  1046→| Order review | Brief summary of items, address, and shipping method |
  1047→| Pay button | Primary, full-width, large text: "Pay now". Loading state: "Processing..." with spinner. Redirects to Stripe Checkout (server-side creates session and returns URL) |
  1048→| PayPal | Optional PayPal button (if enabled) rendered below or alongside Stripe |
  1049→| Alternative | Embedded Stripe Elements form (card number, expiry, CVC) depending on configuration |
  1050→
  1051→### 8.3 Order Summary Sidebar (Right Column)
  1052→
  1053→Sticky on desktop.
  1054→
  1055→```
  1056→+-------------------------------+
  1057→|  Order Summary                |
  1058→+-------------------------------+
  1059→|  [Thumb] Title x2     $49.98 |
  1060→|          Size: M              |
  1061→|  [Thumb] Title x1     $34.99 |
  1062→+-------------------------------+
  1063→|  [Discount code] [Apply]     |
  1064→+-------------------------------+
  1065→|  Subtotal              $84.97|
  1066→|  Discount             -$12.75|
  1067→|  Shipping               $7.99|
  1068→|  Tax                    $5.74|
  1069→|  ----------------------------|
  1070→|  Total                 $85.95|
  1071→+-------------------------------+
  1072→```
  1073→
  1074→| Element | Description |
  1075→|---|---|
  1076→| Background | Light gray (dark: dark gray), large rounded corners, with padding |
  1077→| Cart items | Compact list with small thumbnail (48x48px), truncated title, quantity badge, and line total |
  1078→| Discount code input | Same as cart drawer |
  1079→| Totals | Line items for subtotal, discount (conditional), shipping ("Calculated at next step" until selected), tax (calculated after address), and total |
  1080→| Updates | Event-driven from step completion actions (no polling) |
  1081→
  1082→### 8.4 Accessibility
  1083→
  1084→| Requirement | Description |
  1085→|---|---|
  1086→| Labels | All form inputs have associated label elements |
  1087→| Validation errors | Linked via describedby pointing to error message element |
  1088→| Required fields | Marked with required attribute and visual asterisk |
  1089→| Step transitions | Focus moves to the first input of the newly expanded step |
  1090→| Shipping radios | Grouped in a fieldset with legend |
  1091→| Totals | Live region for screen reader updates |
  1092→
  1093→---
  1094→
  1095→## 9. Order Confirmation (GET /checkout/{checkoutId}/confirmation)
  1096→
  1097→### 9.1 Layout
  1098→
  1099→Centered content, single column.
  1100→
  1101→```
  1102→+--------------------------------------------------------------+
  1103→|  [Checkmark icon - green circle]                              |
  1104→|  "Thank you for your order!"                                  |
  1105→|  Order #1042                                                  |
  1106→|  "We've sent a confirmation to you@email.com"                 |
  1107→+--------------------------------------------------------------+
  1108→|  Order Summary                                                |
  1109→|  [Items list with thumbnails]                                 |
  1110→+--------------------------------------------------------------+
  1111→|  Shipping Address          |  Payment Method                  |
  1112→|  Jane Doe                  |  Visa ending in 4242             |
  1113→|  123 Main St               |                                  |
  1114→|  New York, NY 10001        |                                  |
  1115→+--------------------------------------------------------------+
  1116→|  Subtotal        $84.97                                       |
  1117→|  Shipping         $7.99                                       |
  1118→|  Tax              $5.74                                       |
  1119→|  Total           $98.70                                       |
  1120→+--------------------------------------------------------------+
  1121→|  [Continue shopping button]  [View order - if logged in]      |
  1122→+--------------------------------------------------------------+
  1123→```
  1124→
  1125→| Element | Description |
  1126→|---|---|
  1127→| Container | Constrained to a comfortable width, centered, generous vertical padding |
  1128→| Success icon | Large green checkmark in a circle, centered |
  1129→| Heading | H1, large bold, centered, generous top margin |
  1130→| Order number | Medium muted text, centered |
  1131→| Email notice | Small muted text, centered |
  1132→
  1133→### 9.2 Order Details
  1134→
  1135→| Element | Description |
  1136→|---|---|
  1137→| Items | List with thumbnail, title, variant info, quantity, price |
  1138→| Address and payment | Two-column grid on desktop, stacked on mobile |
  1139→| Totals | Right-aligned summary |
  1140→
  1141→### 9.3 Actions
  1142→
  1143→| Button | Description |
  1144→|---|---|
  1145→| Continue shopping | Primary, links to home page |
  1146→| View order | Link to account order detail page (logged-in customers only) |
  1147→| Layout | Centered row with gap |
  1148→
  1149→---
  1150→
  1151→## 10. Customer Account Pages
  1152→
  1153→All account pages require authentication. Unauthenticated access redirects to the login page.
  1154→
  1155→### 10.1 Login (GET /account/login)
  1156→
  1157→**Livewire Component:** Login
  1158→
  1159→```
  1160→+--------------------------------------------------------------+
  1161→|  Log in to your account                                       |
  1162→|                                                               |
  1163→|  [Email input]                                                |
  1164→|  [Password input]                                             |
  1165→|  [Forgot password? link]                                      |
  1166→|  [Log in button]                                              |
  1167→|                                                               |
  1168→|  Don't have an account? [Create one]                          |
  1169→+--------------------------------------------------------------+
  1170→```
  1171→
  1172→| Element | Description |
  1173→|---|---|
  1174→| Container | Narrow max-width, centered, generous vertical padding |
  1175→| Heading | H1, medium-large, bold, centered |
  1176→| Form | Vertical stack with gap |
  1177→| Email | Email type, required, autocomplete for email |
  1178→| Password | Password type, required, autocomplete for current password |
  1179→| Forgot password | Small blue link, right-aligned |
  1180→| Submit button | Primary, full-width, "Log in" |
  1181→| Error messages | Inline below fields in small red text |
  1182→| General error | Invalid credentials shown in a callout/alert above the form |
  1183→| Register link | "Create one" link to registration page |
  1184→
  1185→### 10.2 Register (GET /account/register)
  1186→
  1187→**Livewire Component:** Register
  1188→
  1189→```
  1190→+--------------------------------------------------------------+
  1191→|  Create an account                                            |
  1192→|                                                               |
  1193→|  [Name input]                                                 |
  1194→|  [Email input]                                                |
  1195→|  [Password input]                                             |
  1196→|  [Confirm password input]                                     |
  1197→|  [ ] Subscribe to marketing emails                            |
  1198→|  [Create account button]                                      |
  1199→|                                                               |

|  ------|------------- |---------|----------|---------|-------|
|  [img] | Title        | $24.99  | [-][2][+]| $49.98  | [X]   |
|        | Size: M      |         |          |         |       |
|  [img] | Title        | $34.99  | [-][1][+]| $34.99  | [X]   |
|        | Color: Blue  |         |          |         |       |
+--------------------------------------------------------------+
|                                            Subtotal: $84.97   |
|                              [Discount code] [Apply]          |
|                                            Discount: -$12.75  |
|                                            Total:    $72.22   |
|                                                               |
|                                            [Checkout button]  |
|                                            [Continue shopping]|
+--------------------------------------------------------------+
```

| Attribute | Description |
|---|---|
| Container | Constrained max-width, centered, with horizontal padding and vertical spacing |
| Title | H1, large bold, generous bottom margin |
| Desktop layout | Proper table with header row and body |
| Table headers | Left-aligned, small, semi-bold, muted, uppercase, wide tracking |
| Mobile layout | Card-based layout (same as cart drawer items, stacked) |

### 7.2 Totals and Actions

- Right-aligned on desktop, full-width on mobile
- Same discount code input/apply functionality as drawer
- Same totals breakdown
- "Checkout" button: primary, aligned right
- "Continue shopping" link: below checkout button

### 7.3 Empty State

Same as drawer empty state but centered in the page content area with more vertical padding.

---

## 8. Checkout Page (GET /checkout/{checkoutId})

**Livewire Component:** CheckoutPage

### 8.1 Layout

**Desktop (large breakpoint and above):** Two-column layout. Left column (wider, approx 60%) contains the form sections. Right column (narrower, approx 40%) contains the sticky order summary.

**Mobile:** Single column. Order summary is a collapsible section at the top (collapsed by default, showing a toggle with "Show order summary" / "Hide order summary" + chevron + total amount).

```
Desktop:
+-------------------------------+-------------------+
|  [Checkout Form Sections]     | [Order Summary]   |
|  (wider, ~60%)                | (narrower, ~40%)  |
|                               | (sticky)          |
+-------------------------------+-------------------+

Mobile:
+--------------------------------------------------------------+
|  [Order Summary - collapsible]                                |
+--------------------------------------------------------------+
|  [Checkout Form Sections]                                     |
+--------------------------------------------------------------+
```

### 8.2 Checkout Steps

The checkout uses a single-page design with collapsible sections. Steps: Contact, Shipping Address, Shipping Method, Payment.

**Step State Behavior:**

| State | Description |
|---|---|
| Completed | Shows summary of entered data with an "Edit" link |
| Current | Expanded with the full form |
| Future | Collapsed, muted, not interactive |

Each step header shows the step number + title, e.g., "1. Contact information".

#### Step 1 - Contact Information

| Field | Description |
|---|---|
| Email | Full-width email input, required. Validated on blur |
| Account link | "Already have an account? Log in" link below email, small blue text |
| Continue button | Primary, below the form |

#### Step 2 - Shipping Address

Uses the address-form component.

**Fields:**

| Field | Type | Required | Layout Width |
|---|---|---|---|
| First name | Text | Yes | Half |
| Last name | Text | Yes | Half |
| Address line 1 | Text | Yes | Full |
| Address line 2 | Text | No | Full |
| City | Text | Yes | Half |
| State/Province | Select or text | Yes (varies by country) | Half |
| Postal code | Text | Yes | Half |
| Country | Select | Yes | Half |
| Phone | Tel | No | Full |

- Layout: 2-column grid, full-width fields span both columns
- Country select: on change, State/Province updates to show relevant regions
- All fields validate on blur

**Billing address:**

- Checkbox: "Billing address same as shipping" (checked by default)
- When unchecked, a second address form appears for billing

**Saved addresses (logged-in customers):**

- Dropdown above the form: "Select a saved address" with saved addresses listed
- "Use a new address" option to show the blank form
- On selection, form fields populate with the saved address data

Continue button: primary.

#### Step 3 - Shipping Method

Radio button list of available shipping methods, filtered server-side by matching the shipping address to shipping zones.

```
+--------------------------------------------------------------+
| (o) Standard Shipping          5-7 business days       $7.99 |
| ( ) Express Shipping           2-3 business days      $14.99 |
| ( ) Free Shipping              7-10 business days      Free  |
+--------------------------------------------------------------+
```

| Attribute | Description |
|---|---|
| Each option | Bordered, rounded card with padding. Selected: primary border with subtle background tint. Unselected: gray border |
| Label layout | Method name (left), estimated delivery (center), price (right) |
| Live binding | On selection, the order summary sidebar updates totals |
| No methods | Warning message: "No shipping methods are available for your address. Please verify your address or contact us." in amber text with info icon |

Continue button: primary.

#### Step 4 - Payment

| Element | Description |
|---|---|
| Order review | Brief summary of items, address, and shipping method |
| Pay button | Primary, full-width, large text: "Pay now". Loading state: "Processing..." with spinner. Redirects to Stripe Checkout (server-side creates session and returns URL) |
| PayPal | Optional PayPal button (if enabled) rendered below or alongside Stripe |
| Alternative | Embedded Stripe Elements form (card number, expiry, CVC) depending on configuration |

### 8.3 Order Summary Sidebar (Right Column)

Sticky on desktop.

```
+-------------------------------+
|  Order Summary                |
+-------------------------------+
|  [Thumb] Title x2     $49.98 |
|          Size: M              |
|  [Thumb] Title x1     $34.99 |
+-------------------------------+
|  [Discount code] [Apply]     |
+-------------------------------+
|  Subtotal              $84.97|
|  Discount             -$12.75|
|  Shipping               $7.99|
|  Tax                    $5.74|
|  ----------------------------|
|  Total                 $85.95|
+-------------------------------+
```

| Element | Description |
|---|---|
| Background | Light gray (dark: dark gray), large rounded corners, with padding |
| Cart items | Compact list with small thumbnail (48x48px), truncated title, quantity badge, and line total |
| Discount code input | Same as cart drawer |
| Totals | Line items for subtotal, discount (conditional), shipping ("Calculated at next step" until selected), tax (calculated after address), and total |
| Updates | Event-driven from step completion actions (no polling) |

### 8.4 Accessibility

| Requirement | Description |
|---|---|
| Labels | All form inputs have associated label elements |
| Validation errors | Linked via describedby pointing to error message element |
| Required fields | Marked with required attribute and visual asterisk |
| Step transitions | Focus moves to the first input of the newly expanded step |
| Shipping radios | Grouped in a fieldset with legend |
| Totals | Live region for screen reader updates |

---

## 9. Order Confirmation (GET /checkout/{checkoutId}/confirmation)

### 9.1 Layout

Centered content, single column.

```
+--------------------------------------------------------------+
|  [Checkmark icon - green circle]                              |
|  "Thank you for your order!"                                  |
|  Order #1042                                                  |
|  "We've sent a confirmation to you@email.com"                 |
+--------------------------------------------------------------+
|  Order Summary                                                |
|  [Items list with thumbnails]                                 |
+--------------------------------------------------------------+
|  Shipping Address          |  Payment Method                  |
|  Jane Doe                  |  Visa ending in 4242             |
|  123 Main St               |                                  |
|  New York, NY 10001        |                                  |
+--------------------------------------------------------------+
|  Subtotal        $84.97                                       |
|  Shipping         $7.99                                       |
|  Tax              $5.74                                       |
|  Total           $98.70                                       |
+--------------------------------------------------------------+
|  [Continue shopping button]  [View order - if logged in]      |
+--------------------------------------------------------------+
```

| Element | Description |
|---|---|
| Container | Constrained to a comfortable width, centered, generous vertical padding |
| Success icon | Large green checkmark in a circle, centered |
| Heading | H1, large bold, centered, generous top margin |
| Order number | Medium muted text, centered |
| Email notice | Small muted text, centered |

### 9.2 Order Details

| Element | Description |
|---|---|
| Items | List with thumbnail, title, variant info, quantity, price |
| Address and payment | Two-column grid on desktop, stacked on mobile |
| Totals | Right-aligned summary |

### 9.3 Actions

| Button | Description |
|---|---|
| Continue shopping | Primary, links to home page |
| View order | Link to account order detail page (logged-in customers only) |
| Layout | Centered row with gap |

---

## 10. Customer Account Pages

All account pages require authentication. Unauthenticated access redirects to the login page.

### 10.1 Login (GET /account/login)

**Livewire Component:** Login

```
+--------------------------------------------------------------+
|  Log in to your account                                       |
|                                                               |
|  [Email input]                                                |
|  [Password input]                                             |
|  [Forgot password? link]                                      |
|  [Log in button]                                              |
|                                                               |
|  Don't have an account? [Create one]                          |
+--------------------------------------------------------------+
```

| Element | Description |
|---|---|
| Container | Narrow max-width, centered, generous vertical padding |
| Heading | H1, medium-large, bold, centered |
| Form | Vertical stack with gap |
| Email | Email type, required, autocomplete for email |
| Password | Password type, required, autocomplete for current password |
| Forgot password | Small blue link, right-aligned |
| Submit button | Primary, full-width, "Log in" |
| Error messages | Inline below fields in small red text |
| General error | Invalid credentials shown in a callout/alert above the form |
| Register link | "Create one" link to registration page |

### 10.2 Register (GET /account/register)

**Livewire Component:** Register

```
+--------------------------------------------------------------+
|  Create an account                                            |
|                                                               |
|  [Name input]                                                 |
|  [Email input]                                                |
|  [Password input]                                             |
|  [Confirm password input]                                     |
|  [ ] Subscribe to marketing emails                            |
|  [Create account button]                                      |
|                                                               |


Now I have a good understanding of what needs to be edited. Let me also read the remaining files I need to modify. Let me also check for Stripe/PayPal references in the other spec files and read the sections I need to update.

I have the 02-API-ROUTES.md file with remaining Stripe/PayPal references in:
- Section 4 (Webhook Receiver Endpoints) at lines 2850-2967 - needs to be removed/replaced
- Line 1946 - provider: "stripe" reference
- Lines 2422-2433 - stripe_tax references (these might be legitimate tax provider references)


Let me read the remaining sections I need to edit across all files.


  1920→      "address1": "123 Main St",
  1921→      "city": "Berlin",
  1922→      "country_code": "DE",
  1923→      "postal_code": "10115"
  1924→    },
  1925→    "lines": [
  1926→      {
  1927→        "id": 301,
  1928→        "product_id": 10,
  1929→        "variant_id": 101,
  1930→        "title_snapshot": "Classic T-Shirt",
  1931→        "sku_snapshot": "TSH-BLU-M",
  1932→        "quantity": 2,
  1933→        "unit_price_amount": 2500,
  1934→        "total_amount": 5000,
  1935→        "tax_lines_json": [
  1936→          { "rate": 1900, "amount": 950, "jurisdiction": "DE" }
  1937→        ],
  1938→        "discount_allocations_json": [
  1939→          { "code": "WELCOME10", "amount": 500 }
  1940→        ]
  1941→      }
  1942→    ],
  1943→    "payments": [
  1944→      {
  1945→        "id": 401,
  1946→        "provider": "stripe",
  1947→        "provider_payment_id": "pi_abc123",
  1948→        "status": "captured",
  1949→        "amount": 5950,
  1950→        "currency": "EUR",
  1951→        "created_at": "2026-02-11T10:10:00Z"
  1952→      }
  1953→    ],
  1954→    "fulfillments": [],
  1955→    "refunds": [],
  1956→    "placed_at": "2026-02-11T10:10:00Z",
  1957→    "created_at": "2026-02-11T10:10:00Z",
  1958→    "updated_at": "2026-02-11T10:10:00Z"
  1959→  }
  1960→}
  1961→```
  1962→
  1963→**Error responses:**
  1964→- `403` - Insufficient permissions
  1965→- `404` - Order not found
  1966→
  1967→---
  1968→
  1969→#### POST /api/admin/v1/stores/{storeId}/orders/{orderId}/fulfillments
  1970→
  1971→Create a fulfillment (mark items as shipped).
  1972→
  1973→**Request body:**
  1974→
  1975→```json
  1976→{
  1977→  "tracking_company": "DHL",
  1978→  "tracking_number": "1234567890",
  1979→  "tracking_url": "https://www.dhl.com/track/1234567890",
  1980→  "line_items": [
  1981→    {
  1982→      "order_line_id": 301,
  1983→      "quantity": 2
  1984→    }
  1985→  ],
  1986→  "notify_customer": true
  1987→}
  1988→```
  1989→
  1990→| Field | Type | Required | Validation |
  1991→|-------|------|----------|------------|
  1992→| `tracking_company` | string | No | Max: 255 |
  1993→| `tracking_number` | string | No | Max: 255 |
  1994→| `tracking_url` | string | No | Valid URL, max: 2048 |
  1995→| `line_items` | array | Yes | At least one line item. |
  1996→| `line_items[].order_line_id` | integer | Yes | Must belong to this order. |
  1997→| `line_items[].quantity` | integer | Yes | Min: 1. Must not exceed unfulfilled quantity. |
  1998→| `notify_customer` | boolean | No | Default: true. Sends shipping notification email. |
  1999→
  2000→**Success response:** `201 Created`
  2001→
  2002→```json
  2003→{
  2004→  "data": {
  2005→    "id": 501,
  2006→    "order_id": 50,
  2007→    "status": "shipped",
  2008→    "tracking_company": "DHL",
  2009→    "tracking_number": "1234567890",
  2010→    "tracking_url": "https://www.dhl.com/track/1234567890",
  2011→    "shipped_at": "2026-02-12T08:00:00Z",
  2012→    "line_items": [
  2013→      {
  2014→        "order_line_id": 301,
  2015→        "quantity": 2
  2016→      }
  2017→    ]
  2018→  }
  2019→}

      "address1": "123 Main St",
      "city": "Berlin",
      "country_code": "DE",
      "postal_code": "10115"
    },
    "lines": [
      {
        "id": 301,
        "product_id": 10,
        "variant_id": 101,
        "title_snapshot": "Classic T-Shirt",
        "sku_snapshot": "TSH-BLU-M",
        "quantity": 2,
        "unit_price_amount": 2500,
        "total_amount": 5000,
        "tax_lines_json": [
          { "rate": 1900, "amount": 950, "jurisdiction": "DE" }
        ],
        "discount_allocations_json": [
          { "code": "WELCOME10", "amount": 500 }
        ]
      }
    ],
    "payments": [
      {
        "id": 401,
        "provider": "stripe",
        "provider_payment_id": "pi_abc123",
        "status": "captured",
        "amount": 5950,
        "currency": "EUR",
        "created_at": "2026-02-11T10:10:00Z"
      }
    ],
    "fulfillments": [],
    "refunds": [],
    "placed_at": "2026-02-11T10:10:00Z",
    "created_at": "2026-02-11T10:10:00Z",
    "updated_at": "2026-02-11T10:10:00Z"
  }
}
```

**Error responses:**
- `403` - Insufficient permissions
- `404` - Order not found

---

#### POST /api/admin/v1/stores/{storeId}/orders/{orderId}/fulfillments

Create a fulfillment (mark items as shipped).

**Request body:**

```json
{
  "tracking_company": "DHL",
  "tracking_number": "1234567890",
  "tracking_url": "https://www.dhl.com/track/1234567890",
  "line_items": [
    {
      "order_line_id": 301,
      "quantity": 2
    }
  ],
  "notify_customer": true
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `tracking_company` | string | No | Max: 255 |
| `tracking_number` | string | No | Max: 255 |
| `tracking_url` | string | No | Valid URL, max: 2048 |
| `line_items` | array | Yes | At least one line item. |
| `line_items[].order_line_id` | integer | Yes | Must belong to this order. |
| `line_items[].quantity` | integer | Yes | Min: 1. Must not exceed unfulfilled quantity. |
| `notify_customer` | boolean | No | Default: true. Sends shipping notification email. |

**Success response:** `201 Created`

```json
{
  "data": {
    "id": 501,
    "order_id": 50,
    "status": "shipped",
    "tracking_company": "DHL",
    "tracking_number": "1234567890",
    "tracking_url": "https://www.dhl.com/track/1234567890",
    "shipped_at": "2026-02-12T08:00:00Z",
    "line_items": [
      {
        "order_line_id": 301,
        "quantity": 2
      }
    ]
  }
}


  2400→          "rate": 2000,
  2401→          "name": "USt",
  2402→          "shipping_taxed": true
  2403→        }
  2404→      ]
  2405→    },
  2406→    "updated_at": "2026-02-10T14:00:00Z"
  2407→  }
  2408→}
  2409→```
  2410→
  2411→---
  2412→
  2413→#### PUT /api/admin/v1/stores/{storeId}/tax/settings
  2414→
  2415→Update tax configuration.
  2416→
  2417→**Request body:**
  2418→
  2419→```json
  2420→{
  2421→  "mode": "provider",
  2422→  "provider": "stripe_tax",
  2423→  "prices_include_tax": false,
  2424→  "config_json": {
  2425→    "stripe_tax_settings_id": "txr_abc123"
  2426→  }
  2427→}
  2428→```
  2429→
  2430→| Field | Type | Required | Validation |
  2431→|-------|------|----------|------------|
  2432→| `mode` | string | Yes | One of: `manual`, `provider`. |
  2433→| `provider` | string | Conditional | Required when mode is `provider`. One of: `stripe_tax`, `none`. |
  2434→| `prices_include_tax` | boolean | Yes | Whether product prices are tax-inclusive. |
  2435→| `config_json` | object | Yes | Schema depends on mode/provider. |
  2436→
  2437→**Success response:** `200 OK`
  2438→
  2439→**Error responses:**
  2440→- `403` - Insufficient permissions
  2441→- `422` - Validation errors
  2442→
  2443→---
  2444→
  2445→### 3.8 Themes
  2446→
  2447→#### Admin Themes Endpoint Summary
  2448→
  2449→| Method | Path | Auth Scope | Description | Rate Limit |

          "rate": 2000,
          "name": "USt",
          "shipping_taxed": true
        }
      ]
    },
    "updated_at": "2026-02-10T14:00:00Z"
  }
}
```

---

#### PUT /api/admin/v1/stores/{storeId}/tax/settings

Update tax configuration.

**Request body:**

```json
{
  "mode": "provider",
  "provider": "stripe_tax",
  "prices_include_tax": false,
  "config_json": {
    "stripe_tax_settings_id": "txr_abc123"
  }
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `mode` | string | Yes | One of: `manual`, `provider`. |
| `provider` | string | Conditional | Required when mode is `provider`. One of: `stripe_tax`, `none`. |
| `prices_include_tax` | boolean | Yes | Whether product prices are tax-inclusive. |
| `config_json` | object | Yes | Schema depends on mode/provider. |

**Success response:** `200 OK`

**Error responses:**
- `403` - Insufficient permissions
- `422` - Validation errors

---

### 3.8 Themes

#### Admin Themes Endpoint Summary

| Method | Path | Auth Scope | Description | Rate Limit |


  2830→    "id": 1,
  2831→    "status": "completed",
  2832→    "format": "csv",
  2833→    "row_count": 156,
  2834→    "download_url": "https://s3.example.com/...presigned...",
  2835→    "download_expires_at": "2026-02-11T11:00:00Z",
  2836→    "created_at": "2026-02-11T10:00:00Z",
  2837→    "completed_at": "2026-02-11T10:01:30Z"
  2838→  }
  2839→}
  2840→```
  2841→
  2842→**Export status values:** `queued`, `processing`, `completed`, `failed`.
  2843→
  2844→**Error responses:**
  2845→- `403` - Insufficient permissions
  2846→- `404` - Export not found
  2847→
  2848→---
  2849→
  2850→## 4. Webhook Receiver Endpoints
  2851→
  2852→These endpoints receive incoming webhooks from payment providers. They are not authenticated via tokens but use signature verification to ensure authenticity.
  2853→
  2854→**Prefix:** `/api/webhooks`
  2855→**Middleware:** None (raw request body needed for signature verification)
  2856→
  2857→---
  2858→
  2859→### POST /api/webhooks/stripe
  2860→
  2861→Receive Stripe webhook events (payment_intent.succeeded, payment_intent.payment_failed, charge.refunded, etc.).
  2862→
  2863→- **Authentication:** `Stripe-Signature` header verification using `STRIPE_WEBHOOK_SECRET`
  2864→- **Rate limit:** `webhooks` (100/min per IP)
  2865→
  2866→**Required headers:**
  2867→
  2868→| Header | Required | Description |
  2869→|--------|----------|-------------|
  2870→| `Stripe-Signature` | Yes | HMAC signature for payload verification |
  2871→| `Content-Type` | Yes | `application/json` |
  2872→
  2873→**Example event payload:**
  2874→
  2875→```json
  2876→{
  2877→  "id": "evt_abc123",
  2878→  "type": "payment_intent.succeeded",
  2879→  "data": {
  2880→    "object": {
  2881→      "id": "pi_abc123",
  2882→      "amount": 6545,
  2883→      "currency": "eur",
  2884→      "status": "succeeded",
  2885→      "metadata": {
  2886→        "checkout_id": 7,
  2887→        "store_id": 1
  2888→      }
  2889→    }
  2890→  },
  2891→  "created": 1707645000
  2892→}
  2893→```
  2894→
  2895→**Webhook processing steps:**
  2896→
  2897→1. Verify `Stripe-Signature` header against `STRIPE_WEBHOOK_SECRET`
  2898→2. Track idempotency via `event.id` to prevent duplicate processing
  2899→3. Dispatch appropriate action based on event type
  2900→
  2901→**Supported Stripe event types and actions:**
  2902→
  2903→| Event Type | Action |
  2904→|------------|--------|
  2905→| `payment_intent.succeeded` | Transition order to `paid`, decrement inventory |
  2906→| `payment_intent.payment_failed` | Mark payment as failed, notify customer |
  2907→| `charge.refunded` | Process refund, update order financial_status |
  2908→| `checkout.session.completed` | Reconcile Checkout Session flow |
  2909→
  2910→**Success response:** `200 OK`
  2911→
  2912→```json
  2913→{
  2914→  "received": true
  2915→}
  2916→```
  2917→
  2918→**Error responses:**
  2919→- `400` - Invalid signature or malformed payload
  2920→
  2921→---
  2922→
  2923→### POST /api/webhooks/paypal
  2924→
  2925→Receive PayPal webhook notifications.
  2926→
  2927→- **Authentication:** PayPal signature verification via PayPal's verification endpoint. Checks `verification_status` response.
  2928→- **Rate limit:** `webhooks` (100/min per IP)
  2929→
  2930→**Required headers:**
  2931→
  2932→| Header | Required | Description |
  2933→|--------|----------|-------------|
  2934→| `PAYPAL-TRANSMISSION-SIG` | Yes | PayPal transmission signature |
  2935→| `PAYPAL-TRANSMISSION-ID` | Yes | Unique transmission ID |
  2936→| `PAYPAL-TRANSMISSION-TIME` | Yes | Transmission timestamp |
  2937→| `PAYPAL-CERT-URL` | Yes | URL to PayPal's certificate |
  2938→| `PAYPAL-AUTH-ALGO` | Yes | Algorithm used for signing |
  2939→| `Content-Type` | Yes | `application/json` |
  2940→
  2941→**Example event payload:**
  2942→
  2943→```json
  2944→{
  2945→  "id": "WH-abc123",
  2946→  "event_type": "PAYMENT.CAPTURE.COMPLETED",
  2947→  "resource": {
  2948→    "id": "5O190127TN364715T",
  2949→    "status": "COMPLETED",
  2950→    "amount": {
  2951→      "currency_code": "EUR",
  2952→      "value": "65.45"
  2953→    },
  2954→    "custom_id": 7
  2955→  },
  2956→  "create_time": "2026-02-11T10:10:00Z"
  2957→}
  2958→```
  2959→
  2960→**Webhook processing steps:**
  2961→
  2962→1. Verify signature by calling PayPal's verification endpoint
  2963→2. Check `verification_status` is `SUCCESS`; reject if `FAILURE`
  2964→3. Track idempotency via `event.id`
  2965→4. Dispatch action based on `event_type`
  2966→
  2967→**Supported PayPal event types and actions:**
  2968→
  2969→| Event Type | Action |
  2970→|------------|--------|
  2971→| `PAYMENT.CAPTURE.COMPLETED` | Transition order to `paid`, decrement inventory |
  2972→| `PAYMENT.CAPTURE.DENIED` | Mark payment as failed |
  2973→| `PAYMENT.CAPTURE.REFUNDED` | Process refund |
  2974→
  2975→**Success response:** `200 OK`
  2976→
  2977→```json
  2978→{
  2979→  "received": true
  2980→}
  2981→```
  2982→
  2983→**Error responses:**
  2984→- `400` - Verification failed or malformed payload
  2985→
  2986→---
  2987→
  2988→## 5. OAuth Endpoints (App Ecosystem)
  2989→
  2990→> **Scope note:** OAuth and app ecosystem endpoints are deferred for initial implementation. The route structure below is defined for future use but implementations should return 501 Not Implemented.
  2991→
  2992→These endpoints implement OAuth2 authorization for third-party apps, powered by Laravel Passport.
  2993→
  2994→---
  2995→
  2996→### GET /oauth/authorize
  2997→
  2998→Display the authorization prompt to the merchant.
  2999→
  3000→- **Middleware:** `web`, `auth`
  3001→
  3002→**Query parameters:**
  3003→
  3004→| Parameter | Type | Required | Description |
  3005→|-----------|------|----------|-------------|
  3006→| `client_id` | string | Yes | The app's OAuth client ID |
  3007→| `redirect_uri` | string | Yes | Must match one of the registered redirect URIs |
  3008→| `response_type` | string | Yes | Must be `code` |
  3009→| `scope` | string | Yes | Space-separated list of requested scopes |
  3010→| `state` | string | Yes | CSRF token for the client |
  3011→
  3012→**Flow:** Merchant reviews requested scopes and approves/denies. On approval, redirects to `redirect_uri` with `code` and `state` parameters. Authorization code is single-use.
  3013→
  3014→---
  3015→
  3016→### POST /oauth/token
  3017→
  3018→Exchange an authorization code for access and refresh tokens.
  3019→
  3020→- **Middleware:** None (public endpoint)
  3021→
  3022→**Authorization code exchange request:**
  3023→
  3024→```json
  3025→{
  3026→  "grant_type": "authorization_code",
  3027→  "client_id": "app_client_abc123",
  3028→  "client_secret": "secret_xyz",
  3029→  "redirect_uri": "https://myapp.example.com/callback",

    "id": 1,
    "status": "completed",
    "format": "csv",
    "row_count": 156,
    "download_url": "https://s3.example.com/...presigned...",
    "download_expires_at": "2026-02-11T11:00:00Z",
    "created_at": "2026-02-11T10:00:00Z",
    "completed_at": "2026-02-11T10:01:30Z"
  }
}
```

**Export status values:** `queued`, `processing`, `completed`, `failed`.

**Error responses:**
- `403` - Insufficient permissions
- `404` - Export not found

---

## 4. Webhook Receiver Endpoints

These endpoints receive incoming webhooks from payment providers. They are not authenticated via tokens but use signature verification to ensure authenticity.

**Prefix:** `/api/webhooks`
**Middleware:** None (raw request body needed for signature verification)

---

### POST /api/webhooks/stripe

Receive Stripe webhook events (payment_intent.succeeded, payment_intent.payment_failed, charge.refunded, etc.).

- **Authentication:** `Stripe-Signature` header verification using `STRIPE_WEBHOOK_SECRET`
- **Rate limit:** `webhooks` (100/min per IP)

**Required headers:**

| Header | Required | Description |
|--------|----------|-------------|
| `Stripe-Signature` | Yes | HMAC signature for payload verification |
| `Content-Type` | Yes | `application/json` |

**Example event payload:**

```json
{
  "id": "evt_abc123",
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_abc123",
      "amount": 6545,
      "currency": "eur",
      "status": "succeeded",
      "metadata": {
        "checkout_id": 7,
        "store_id": 1
      }
    }
  },
  "created": 1707645000
}
```

**Webhook processing steps:**

1. Verify `Stripe-Signature` header against `STRIPE_WEBHOOK_SECRET`
2. Track idempotency via `event.id` to prevent duplicate processing
3. Dispatch appropriate action based on event type

**Supported Stripe event types and actions:**

| Event Type | Action |
|------------|--------|
| `payment_intent.succeeded` | Transition order to `paid`, decrement inventory |
| `payment_intent.payment_failed` | Mark payment as failed, notify customer |
| `charge.refunded` | Process refund, update order financial_status |
| `checkout.session.completed` | Reconcile Checkout Session flow |

**Success response:** `200 OK`

```json
{
  "received": true
}
```

**Error responses:**
- `400` - Invalid signature or malformed payload

---

### POST /api/webhooks/paypal

Receive PayPal webhook notifications.

- **Authentication:** PayPal signature verification via PayPal's verification endpoint. Checks `verification_status` response.
- **Rate limit:** `webhooks` (100/min per IP)

**Required headers:**

| Header | Required | Description |
|--------|----------|-------------|
| `PAYPAL-TRANSMISSION-SIG` | Yes | PayPal transmission signature |
| `PAYPAL-TRANSMISSION-ID` | Yes | Unique transmission ID |
| `PAYPAL-TRANSMISSION-TIME` | Yes | Transmission timestamp |
| `PAYPAL-CERT-URL` | Yes | URL to PayPal's certificate |
| `PAYPAL-AUTH-ALGO` | Yes | Algorithm used for signing |
| `Content-Type` | Yes | `application/json` |

**Example event payload:**

```json
{
  "id": "WH-abc123",
  "event_type": "PAYMENT.CAPTURE.COMPLETED",
  "resource": {
    "id": "5O190127TN364715T",
    "status": "COMPLETED",
    "amount": {
      "currency_code": "EUR",
      "value": "65.45"
    },
    "custom_id": 7
  },
  "create_time": "2026-02-11T10:10:00Z"
}
```

**Webhook processing steps:**

1. Verify signature by calling PayPal's verification endpoint
2. Check `verification_status` is `SUCCESS`; reject if `FAILURE`
3. Track idempotency via `event.id`
4. Dispatch action based on `event_type`

**Supported PayPal event types and actions:**

| Event Type | Action |
|------------|--------|
| `PAYMENT.CAPTURE.COMPLETED` | Transition order to `paid`, decrement inventory |
| `PAYMENT.CAPTURE.DENIED` | Mark payment as failed |
| `PAYMENT.CAPTURE.REFUNDED` | Process refund |

**Success response:** `200 OK`

```json
{
  "received": true
}
```

**Error responses:**
- `400` - Verification failed or malformed payload

---

## 5. OAuth Endpoints (App Ecosystem)

> **Scope note:** OAuth and app ecosystem endpoints are deferred for initial implementation. The route structure below is defined for future use but implementations should return 501 Not Implemented.

These endpoints implement OAuth2 authorization for third-party apps, powered by Laravel Passport.

---

### GET /oauth/authorize

Display the authorization prompt to the merchant.

- **Middleware:** `web`, `auth`

**Query parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `client_id` | string | Yes | The app's OAuth client ID |
| `redirect_uri` | string | Yes | Must match one of the registered redirect URIs |
| `response_type` | string | Yes | Must be `code` |
| `scope` | string | Yes | Space-separated list of requested scopes |
| `state` | string | Yes | CSRF token for the client |

**Flow:** Merchant reviews requested scopes and approves/denies. On approval, redirects to `redirect_uri` with `code` and `state` parameters. Authorization code is single-use.

---

### POST /oauth/token

Exchange an authorization code for access and refresh tokens.

- **Middleware:** None (public endpoint)

**Authorization code exchange request:**

```json
{
  "grant_type": "authorization_code",
  "client_id": "app_client_abc123",
  "client_secret": "secret_xyz",
  "redirect_uri": "https://myapp.example.com/callback",


  3100→| `verified` | Require verified email address |
  3101→| `store.resolve` | Resolve current store from session/domain and set `app.store_id` |
  3102→| `role.check` | Verify user has required role for the current store |
  3103→
  3104→#### Storefront
  3105→
  3106→Applied to storefront routes in addition to `web`:
  3107→
  3108→| Middleware | Purpose |
  3109→|-----------|---------|
  3110→| `store.resolve` | Resolve store from request hostname via `store_domains` table |
  3111→
  3112→#### API Storefront
  3113→
  3114→Applied to `/api/storefront/v1/*` routes:
  3115→
  3116→| Middleware | Purpose |
  3117→|-----------|---------|
  3118→| `throttle:api.storefront` | Rate limiting (120/min per IP) |
  3119→| `store.resolve` | Resolve store from request hostname |
  3120→
  3121→#### API Admin
  3122→
  3123→Applied to `/api/admin/v1/*` routes:
  3124→
  3125→| Middleware | Purpose |
  3126→|-----------|---------|
  3127→| `auth:sanctum` | Sanctum token authentication |
  3128→| `store.resolve` | Resolve store from route parameter or token context |
  3129→| `throttle:api.admin` | Rate limiting (60/min per token) |
  3130→
  3131→#### Webhooks
  3132→
  3133→Applied to `/api/webhooks/*` routes:
  3134→
  3135→| Middleware | Purpose |
  3136→|-----------|---------|
  3137→| `throttle:webhooks` | Rate limiting (100/min per IP) |
  3138→
  3139→No session, CSRF, or auth middleware. Raw request body must be preserved for signature verification.
  3140→
  3141→---
  3142→
  3143→### 6.2 Custom Middleware Definitions
  3144→
  3145→#### `store.resolve`
  3146→
  3147→Resolves the current store context. The resolution strategy depends on the route group:
  3148→
  3149→- **Web (admin):** Reads `store_id` from session. If missing, redirects to store selector (for platform-level users with access to multiple stores).
  3150→- **Web (storefront):** Looks up hostname in `store_domains` table (cached via file cache). Hostname must match a domain with type `storefront`.
  3151→- **API (storefront):** Same as storefront web - hostname-based resolution.
  3152→- **API (admin):** Reads `storeId` from route parameter and verifies the authenticated user has membership.
  3153→
  3154→Sets `app.store_id` in the service container and binds the `Store` model to the request.
  3155→
  3156→**Failure responses:**
  3157→- `404` if store cannot be resolved from hostname.
  3158→- `403` if user does not have access to the requested store.
  3159→- `503` if store status is `suspended` on storefront (shows maintenance page); `403` on admin mutation endpoints for suspended stores.
  3160→
  3161→#### `role.check`
  3162→
  3163→Checks that the authenticated user has the required role for the current store. Roles hierarchy: `owner` > `admin` > `staff` > `support`. The `support` role grants read-only access for customer service purposes. Specific route permission requirements are configured in the route definition or controller.
  3164→
  3165→**Failure response:** `403 Forbidden`
  3166→
  3167→---
  3168→
  3169→## 7. Rate Limiting Configuration
  3170→
  3171→### Rate Limit Tiers
  3172→
  3173→| Tier | Limit | Window | Key | Description |
  3174→|------|-------|--------|-----|-------------|
  3175→| `login` | 5 requests | 1 minute | IP address | Admin and customer login attempts |
  3176→| `api.admin` | 60 requests | 1 minute | Sanctum token ID | Admin REST API endpoints |
  3177→| `api.storefront` | 120 requests | 1 minute | IP address | Storefront REST API (carts, etc.) |
  3178→| `checkout` | 10 requests | 1 minute | Session ID | Checkout creation and mutation endpoints |
  3179→| `search` | 30 requests | 1 minute | IP address | Search and autocomplete endpoints |
  3180→| `analytics` | 60 requests | 1 minute | IP address | Analytics event ingestion |
  3181→| `webhooks` | 100 requests | 1 minute | IP address | Inbound payment provider webhooks |
  3182→
  3183→### Rate Limit Response
  3184→
  3185→When a rate limit is exceeded, the server returns `429 Too Many Requests`:
  3186→
  3187→```json
  3188→{
  3189→  "message": "Too many requests. Please try again later.",
  3190→  "retry_after": 42
  3191→}
  3192→```
  3193→
  3194→**Headers included on all rate-limited responses:**
  3195→
  3196→| Header | Description |
  3197→|--------|-------------|
  3198→| `X-RateLimit-Limit` | Maximum requests allowed in the window |
  3199→| `X-RateLimit-Remaining` | Requests remaining in the current window |
  3200→| `Retry-After` | Seconds until the rate limit resets (only on 429) |
  3201→
  3202→### Per-Endpoint Rate Limit Mapping
  3203→
  3204→| Endpoint Group | Tier |
  3205→|---------------|------|
  3206→| `POST /admin/login` | `login` |
  3207→| `POST /account/login` | `login` |
  3208→| `POST /api/storefront/v1/carts*` | `api.storefront` |
  3209→| `GET /api/storefront/v1/carts*` | `api.storefront` |
  3210→| `PUT /api/storefront/v1/carts*` | `api.storefront` |
  3211→| `DELETE /api/storefront/v1/carts*` | `api.storefront` |
  3212→| `POST /api/storefront/v1/checkouts*` | `checkout` |
  3213→| `GET /api/storefront/v1/checkouts*` | `checkout` |
  3214→| `PUT /api/storefront/v1/checkouts*` | `checkout` |
  3215→| `DELETE /api/storefront/v1/checkouts*` | `checkout` |
  3216→| `POST /api/storefront/v1/payments*` | `checkout` |
  3217→| `GET /api/storefront/v1/orders*` | `api.storefront` |
  3218→| `GET /api/storefront/v1/search` | `search` |
  3219→| `GET /api/storefront/v1/search/suggest` | `search` |
  3220→| `POST /api/storefront/v1/analytics/events` | `analytics` |
  3221→| `GET /api/admin/v1/*` | `api.admin` |
  3222→| `POST /api/admin/v1/*` | `api.admin` |
  3223→| `PUT /api/admin/v1/*` | `api.admin` |
  3224→| `DELETE /api/admin/v1/*` | `api.admin` |
  3225→| `POST /api/webhooks/stripe` | `webhooks` |
  3226→| `POST /api/webhooks/paypal` | `webhooks` |
  3227→
  3228→---
  3229→
  3230→## 8. Checkout State Machine
  3231→
  3232→The checkout follows a strict state machine with explicit transitions:
  3233→
  3234→```mermaid
  3235→stateDiagram-v2
  3236→    [*] --> started : POST /checkouts (create from cart)
  3237→    started --> addressed : PUT /checkouts/{id}/address
  3238→    addressed --> shipping_selected : PUT /checkouts/{id}/shipping-method
  3239→    shipping_selected --> payment_pending : POST /payments/* (create intent/session)
  3240→    payment_pending --> completed : Webhook confirms successful payment
  3241→    payment_pending --> shipping_selected : Payment failed, customer retries
  3242→    started --> expired : 24-hour TTL reached
  3243→    addressed --> expired : 24-hour TTL reached
  3244→    shipping_selected --> expired : 24-hour TTL reached
  3245→    payment_pending --> expired : 24-hour TTL reached
  3246→    completed --> [*]
  3247→```
  3248→
  3249→### Checkout Status Transition Table
  3250→
  3251→| From | To | Trigger |
  3252→|------|----|---------|
  3253→| `started` | `addressed` | PUT /checkouts/{id}/address with valid address |
  3254→| `addressed` | `shipping_selected` | PUT /checkouts/{id}/shipping-method with valid method |
  3255→| `shipping_selected` | `payment_pending` | POST /payments/* creates a payment intent/session |
  3256→| `payment_pending` | `completed` | Successful payment confirmed via webhook |
  3257→| `payment_pending` | `shipping_selected` | Payment failed, customer retries |
  3258→| `*` (any active) | `expired` | Checkout expires_at reached (24-hour TTL, checked via scheduled job) |
  3259→
  3260→Applying or removing a discount does not change the checkout status but triggers totals recalculation at the current state.
  3261→
  3262→---
  3263→
  3264→## 9. API Versioning Strategy
  3265→
  3266→- **Storefront API:** Versioned via URL path (`/api/storefront/v1/`). Breaking changes require a new version.
  3267→- **Admin API:** Versioned via URL path (`/api/admin/v1/`). Same strategy.
  3268→- **App API:** Versioned via URL path (`/api/apps/v1/`).
  3269→- **Webhook payloads:** Include a `api_version` field in outbound webhook deliveries. Each app installation pins to a version.
  3270→
  3271→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.
  3272→
  3273→---
  3274→
  3275→## 10. Error Response Format
  3276→
  3277→All API error responses follow a consistent JSON structure:
  3278→
  3279→### Validation Errors (422)
  3280→
  3281→```json
  3282→{
  3283→  "message": "The given data was invalid.",
  3284→  "errors": {
  3285→    "title": ["The title field is required."],
  3286→    "variants.0.sku": ["The sku has already been taken."]
  3287→  }
  3288→}
  3289→```
  3290→
  3291→### Authorization Errors (403)
  3292→
  3293→```json
  3294→{
  3295→  "message": "You do not have permission to perform this action."
  3296→}
  3297→```
  3298→
  3299→### Not Found Errors (404)

| `verified` | Require verified email address |
| `store.resolve` | Resolve current store from session/domain and set `app.store_id` |
| `role.check` | Verify user has required role for the current store |

#### Storefront

Applied to storefront routes in addition to `web`:

| Middleware | Purpose |
|-----------|---------|
| `store.resolve` | Resolve store from request hostname via `store_domains` table |

#### API Storefront

Applied to `/api/storefront/v1/*` routes:

| Middleware | Purpose |
|-----------|---------|
| `throttle:api.storefront` | Rate limiting (120/min per IP) |
| `store.resolve` | Resolve store from request hostname |

#### API Admin

Applied to `/api/admin/v1/*` routes:

| Middleware | Purpose |
|-----------|---------|
| `auth:sanctum` | Sanctum token authentication |
| `store.resolve` | Resolve store from route parameter or token context |
| `throttle:api.admin` | Rate limiting (60/min per token) |

#### Webhooks

Applied to `/api/webhooks/*` routes:

| Middleware | Purpose |
|-----------|---------|
| `throttle:webhooks` | Rate limiting (100/min per IP) |

No session, CSRF, or auth middleware. Raw request body must be preserved for signature verification.

---

### 6.2 Custom Middleware Definitions

#### `store.resolve`

Resolves the current store context. The resolution strategy depends on the route group:

- **Web (admin):** Reads `store_id` from session. If missing, redirects to store selector (for platform-level users with access to multiple stores).
- **Web (storefront):** Looks up hostname in `store_domains` table (cached via file cache). Hostname must match a domain with type `storefront`.
- **API (storefront):** Same as storefront web - hostname-based resolution.
- **API (admin):** Reads `storeId` from route parameter and verifies the authenticated user has membership.

Sets `app.store_id` in the service container and binds the `Store` model to the request.

**Failure responses:**
- `404` if store cannot be resolved from hostname.
- `403` if user does not have access to the requested store.
- `503` if store status is `suspended` on storefront (shows maintenance page); `403` on admin mutation endpoints for suspended stores.

#### `role.check`

Checks that the authenticated user has the required role for the current store. Roles hierarchy: `owner` > `admin` > `staff` > `support`. The `support` role grants read-only access for customer service purposes. Specific route permission requirements are configured in the route definition or controller.

**Failure response:** `403 Forbidden`

---

## 7. Rate Limiting Configuration

### Rate Limit Tiers

| Tier | Limit | Window | Key | Description |
|------|-------|--------|-----|-------------|
| `login` | 5 requests | 1 minute | IP address | Admin and customer login attempts |
| `api.admin` | 60 requests | 1 minute | Sanctum token ID | Admin REST API endpoints |
| `api.storefront` | 120 requests | 1 minute | IP address | Storefront REST API (carts, etc.) |
| `checkout` | 10 requests | 1 minute | Session ID | Checkout creation and mutation endpoints |
| `search` | 30 requests | 1 minute | IP address | Search and autocomplete endpoints |
| `analytics` | 60 requests | 1 minute | IP address | Analytics event ingestion |
| `webhooks` | 100 requests | 1 minute | IP address | Inbound payment provider webhooks |

### Rate Limit Response

When a rate limit is exceeded, the server returns `429 Too Many Requests`:

```json
{
  "message": "Too many requests. Please try again later.",
  "retry_after": 42
}
```

**Headers included on all rate-limited responses:**

| Header | Description |
|--------|-------------|
| `X-RateLimit-Limit` | Maximum requests allowed in the window |
| `X-RateLimit-Remaining` | Requests remaining in the current window |
| `Retry-After` | Seconds until the rate limit resets (only on 429) |

### Per-Endpoint Rate Limit Mapping

| Endpoint Group | Tier |
|---------------|------|
| `POST /admin/login` | `login` |
| `POST /account/login` | `login` |
| `POST /api/storefront/v1/carts*` | `api.storefront` |
| `GET /api/storefront/v1/carts*` | `api.storefront` |
| `PUT /api/storefront/v1/carts*` | `api.storefront` |
| `DELETE /api/storefront/v1/carts*` | `api.storefront` |
| `POST /api/storefront/v1/checkouts*` | `checkout` |
| `GET /api/storefront/v1/checkouts*` | `checkout` |
| `PUT /api/storefront/v1/checkouts*` | `checkout` |
| `DELETE /api/storefront/v1/checkouts*` | `checkout` |
| `POST /api/storefront/v1/payments*` | `checkout` |
| `GET /api/storefront/v1/orders*` | `api.storefront` |
| `GET /api/storefront/v1/search` | `search` |
| `GET /api/storefront/v1/search/suggest` | `search` |
| `POST /api/storefront/v1/analytics/events` | `analytics` |
| `GET /api/admin/v1/*` | `api.admin` |
| `POST /api/admin/v1/*` | `api.admin` |
| `PUT /api/admin/v1/*` | `api.admin` |
| `DELETE /api/admin/v1/*` | `api.admin` |
| `POST /api/webhooks/stripe` | `webhooks` |
| `POST /api/webhooks/paypal` | `webhooks` |

---

## 8. Checkout State Machine

The checkout follows a strict state machine with explicit transitions:

```mermaid
stateDiagram-v2
    [*] --> started : POST /checkouts (create from cart)
    started --> addressed : PUT /checkouts/{id}/address
    addressed --> shipping_selected : PUT /checkouts/{id}/shipping-method
    shipping_selected --> payment_pending : POST /payments/* (create intent/session)
    payment_pending --> completed : Webhook confirms successful payment
    payment_pending --> shipping_selected : Payment failed, customer retries
    started --> expired : 24-hour TTL reached
    addressed --> expired : 24-hour TTL reached
    shipping_selected --> expired : 24-hour TTL reached
    payment_pending --> expired : 24-hour TTL reached
    completed --> [*]
```

### Checkout Status Transition Table

| From | To | Trigger |
|------|----|---------|
| `started` | `addressed` | PUT /checkouts/{id}/address with valid address |
| `addressed` | `shipping_selected` | PUT /checkouts/{id}/shipping-method with valid method |
| `shipping_selected` | `payment_pending` | POST /payments/* creates a payment intent/session |
| `payment_pending` | `completed` | Successful payment confirmed via webhook |
| `payment_pending` | `shipping_selected` | Payment failed, customer retries |
| `*` (any active) | `expired` | Checkout expires_at reached (24-hour TTL, checked via scheduled job) |

Applying or removing a discount does not change the checkout status but triggers totals recalculation at the current state.

---

## 9. API Versioning Strategy

- **Storefront API:** Versioned via URL path (`/api/storefront/v1/`). Breaking changes require a new version.
- **Admin API:** Versioned via URL path (`/api/admin/v1/`). Same strategy.
- **App API:** Versioned via URL path (`/api/apps/v1/`).
- **Webhook payloads:** Include a `api_version` field in outbound webhook deliveries. Each app installation pins to a version.

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.

---

## 10. Error Response Format

All API error responses follow a consistent JSON structure:

### Validation Errors (422)

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "title": ["The title field is required."],
    "variants.0.sku": ["The sku has already been taken."]
  }
}
```

### Authorization Errors (403)

```json
{
  "message": "You do not have permission to perform this action."
}
```

### Not Found Errors (404)


     1→# 07 - Seeders and Test Data Specification
     2→
     3→> Complete seeder and factory specification for populating the Shopify-clone with realistic demo data. Laravel 12, SQLite. All monetary amounts in cents (INTEGER). All seed data supports Playwright E2E tests.
     4→
     5→---
     6→
     7→## 1. DatabaseSeeder Orchestration
     8→
     9→The main `DatabaseSeeder` calls child seeders in a strict order that respects foreign key dependencies. Each seeder targets a specific entity type. The ordering ensures parent records exist before children are created.
    10→
    11→**Execution order:**
    12→
    13→| Step | Seeder Class | Depends On | Purpose |
    14→|------|-------------|------------|---------|
    15→| 1 | OrganizationSeeder | (none) | Creates billing organizations |
    16→| 2 | StoreSeeder | Organizations | Creates stores under organizations |
    17→| 3 | StoreDomainSeeder | Stores | Attaches hostnames to stores |
    18→| 4 | UserSeeder | (none) | Creates admin/staff user accounts |
    19→| 5 | StoreUserSeeder | Stores, Users | Links users to stores with roles (pivot) |
    20→| 6 | StoreSettingsSeeder | Stores | Inserts per-store JSON settings |
    21→| 7 | TaxSettingsSeeder | Stores | Configures tax mode and rates |
    22→| 8 | ShippingSeeder | Stores | Creates shipping zones and rates |
    23→| 9 | CollectionSeeder | Stores | Creates product collections |
    24→| 10 | ProductSeeder | Stores, Collections | Creates products, options, variants, inventory, collection assignments |
    25→| 11 | DiscountSeeder | Stores | Creates discount codes |
    26→| 12 | CustomerSeeder | Stores | Creates customer accounts with addresses |
    27→| 13 | OrderSeeder | Stores, Customers, Products, Variants, Discounts | Creates orders with lines, payments, fulfillments, refunds |
    28→| 14 | ThemeSeeder | Stores | Creates themes with settings |
    29→| 15 | PageSeeder | Stores | Creates content pages |
    30→| 16 | NavigationSeeder | Stores, Collections, Pages | Creates menus linking to collections and pages |
    31→| 17 | AnalyticsSeeder | Stores, Customers | Creates daily aggregates and event stream |
    32→| 18 | SearchSettingsSeeder | Stores | Configures search synonyms and stop words |
    33→
    34→**Dependency diagram (Mermaid):**
    35→
    36→```mermaid
    37→graph TD
    38→    A[OrganizationSeeder] --> B[StoreSeeder]
    39→    B --> C[StoreDomainSeeder]
    40→    B --> F[StoreSettingsSeeder]
    41→    B --> G[TaxSettingsSeeder]
    42→    B --> H[ShippingSeeder]
    43→    B --> I[CollectionSeeder]
    44→    D[UserSeeder] --> E[StoreUserSeeder]
    45→    B --> E
    46→    B --> J[ProductSeeder]
    47→    I --> J
    48→    B --> K[DiscountSeeder]
    49→    B --> L[CustomerSeeder]
    50→    J --> M[OrderSeeder]
    51→    L --> M
    52→    K --> M
    53→    B --> N[ThemeSeeder]
    54→    B --> O[PageSeeder]
    55→    I --> P[NavigationSeeder]
    56→    O --> P
    57→    B --> Q[AnalyticsSeeder]
    58→    L --> Q
    59→    B --> R[SearchSettingsSeeder]
    60→```
    61→
    62→**Running seeders:**
    63→- All seeders: `php artisan db:seed`
    64→- Single seeder: `php artisan db:seed --class=ProductSeeder`
    65→- Fresh database with seed data: `php artisan migrate:fresh --seed`
    66→
    67→---
    68→
    69→## 2. Factory Definitions
    70→
    71→Each factory below lists the model, default field generation rules, named states (overrides), and relationship hooks. The coding agent should create corresponding Laravel factory classes.
    72→
    73→---
    74→
    75→### 2.1 OrganizationFactory
    76→
    77→**Model:** Organization
    78→
    79→**Default definition:**
    80→
    81→| Field | Generation Rule |
    82→|-------|----------------|
    83→| name | Faker company name |
    84→| billing_email | Faker company email |
    85→
    86→**States:** None.
    87→
    88→---
    89→
    90→### 2.2 StoreFactory
    91→
    92→**Model:** Store
    93→
    94→**Default definition:**
    95→
    96→| Field | Generation Rule |
    97→|-------|----------------|
    98→| organization_id | Creates a new Organization via its factory |
    99→| name | Faker company name + " Store" suffix |
   100→| handle | Faker unique slug (2 words) |

# 07 - Seeders and Test Data Specification

> Complete seeder and factory specification for populating the Shopify-clone with realistic demo data. Laravel 12, SQLite. All monetary amounts in cents (INTEGER). All seed data supports Playwright E2E tests.

---

## 1. DatabaseSeeder Orchestration

The main `DatabaseSeeder` calls child seeders in a strict order that respects foreign key dependencies. Each seeder targets a specific entity type. The ordering ensures parent records exist before children are created.

**Execution order:**

| Step | Seeder Class | Depends On | Purpose |
|------|-------------|------------|---------|
| 1 | OrganizationSeeder | (none) | Creates billing organizations |
| 2 | StoreSeeder | Organizations | Creates stores under organizations |
| 3 | StoreDomainSeeder | Stores | Attaches hostnames to stores |
| 4 | UserSeeder | (none) | Creates admin/staff user accounts |
| 5 | StoreUserSeeder | Stores, Users | Links users to stores with roles (pivot) |
| 6 | StoreSettingsSeeder | Stores | Inserts per-store JSON settings |
| 7 | TaxSettingsSeeder | Stores | Configures tax mode and rates |
| 8 | ShippingSeeder | Stores | Creates shipping zones and rates |
| 9 | CollectionSeeder | Stores | Creates product collections |
| 10 | ProductSeeder | Stores, Collections | Creates products, options, variants, inventory, collection assignments |
| 11 | DiscountSeeder | Stores | Creates discount codes |
| 12 | CustomerSeeder | Stores | Creates customer accounts with addresses |
| 13 | OrderSeeder | Stores, Customers, Products, Variants, Discounts | Creates orders with lines, payments, fulfillments, refunds |
| 14 | ThemeSeeder | Stores | Creates themes with settings |
| 15 | PageSeeder | Stores | Creates content pages |
| 16 | NavigationSeeder | Stores, Collections, Pages | Creates menus linking to collections and pages |
| 17 | AnalyticsSeeder | Stores, Customers | Creates daily aggregates and event stream |
| 18 | SearchSettingsSeeder | Stores | Configures search synonyms and stop words |

**Dependency diagram (Mermaid):**

```mermaid
graph TD
    A[OrganizationSeeder] --> B[StoreSeeder]
    B --> C[StoreDomainSeeder]
    B --> F[StoreSettingsSeeder]
    B --> G[TaxSettingsSeeder]
    B --> H[ShippingSeeder]
    B --> I[CollectionSeeder]
    D[UserSeeder] --> E[StoreUserSeeder]
    B --> E
    B --> J[ProductSeeder]
    I --> J
    B --> K[DiscountSeeder]
    B --> L[CustomerSeeder]
    J --> M[OrderSeeder]
    L --> M
    K --> M
    B --> N[ThemeSeeder]
    B --> O[PageSeeder]
    I --> P[NavigationSeeder]
    O --> P
    B --> Q[AnalyticsSeeder]
    L --> Q
    B --> R[SearchSettingsSeeder]
```

**Running seeders:**
- All seeders: `php artisan db:seed`
- Single seeder: `php artisan db:seed --class=ProductSeeder`
- Fresh database with seed data: `php artisan migrate:fresh --seed`

---

## 2. Factory Definitions

Each factory below lists the model, default field generation rules, named states (overrides), and relationship hooks. The coding agent should create corresponding Laravel factory classes.

---

### 2.1 OrganizationFactory

**Model:** Organization

**Default definition:**

| Field | Generation Rule |
|-------|----------------|
| name | Faker company name |
| billing_email | Faker company email |

**States:** None.

---

### 2.2 StoreFactory

**Model:** Store

**Default definition:**

| Field | Generation Rule |
|-------|----------------|
| organization_id | Creates a new Organization via its factory |
| name | Faker company name + " Store" suffix |
| handle | Faker unique slug (2 words) |


     1→# 08 - Playwright E2E Test Plan
     2→
     3→> Complete end-to-end browser test specification for the Shopify-clone platform using Pest v4 browser testing (Playwright-backed). All tests verify the entire application works through a real browser. This document defines requirements and specifications for the coding agent to implement.
     4→
     5→---
     6→
     7→## Test Suite Summary
     8→
     9→| Suite | File | Tests | Purpose | Dependencies |
    10→|-------|------|:-----:|---------|--------------|
    11→| 1 - Smoke Tests | `tests/Browser/SmokeTest.php` | 10 | Hit every major page, assert HTTP 200 and no JS errors | Store 1, Products, Collections, Pages |
    12→| 2 - Admin Authentication | `tests/Browser/Admin/AuthenticationTest.php` | 10 | Admin login, logout, invalid credentials, session access control | Admin user |
    13→| 3 - Admin Product Management | `tests/Browser/Admin/ProductManagementTest.php` | 7 | Product CRUD: listing, creating, editing, archiving, filtering | Admin user, Products (incl. #15 draft) |
    14→| 4 - Admin Order Management | `tests/Browser/Admin/OrderManagementTest.php` | 7 | Order listing, filtering, detail view, fulfillment, refund | Admin user, Orders (#1001), Customer |
    15→| 5 - Admin Discount Management | `tests/Browser/Admin/DiscountManagementTest.php` | 6 | Discount code listing, creation, editing | Admin user, Discounts |
    16→| 6 - Admin Settings | `tests/Browser/Admin/SettingsTest.php` | 7 | Store settings, shipping zones, tax config, domains | Admin user, Shipping zones, Store 1 |
    17→| 7 - Storefront Browsing | `tests/Browser/Storefront/BrowsingTest.php` | 15 | Home page, collections, product detail, variants, search, pages | Products, Collections, Pages, Products #15/#17/#18 |
    18→| 8 - Cart Flow | `tests/Browser/Storefront/CartTest.php` | 12 | Cart lifecycle: add, update qty, remove, discounts, totals | Products, Discounts (all codes) |
    19→| 9 - Checkout Flow | `tests/Browser/Storefront/CheckoutTest.php` | 8 | Full checkout: contact, address, shipping, payment, confirmation | Products, Shipping zones, Discounts |
    20→| 10 - Customer Account | `tests/Browser/Storefront/CustomerAccountTest.php` | 12 | Registration, login, order history, addresses, logout | Customer user, Orders (#1001/#1002/#1004) |
    21→| 11 - Inventory Enforcement | `tests/Browser/Storefront/InventoryTest.php` | 4 | Inventory policy enforcement: deny vs. continue | Products #17 (deny), #18 (continue), #1 |
    22→| 12 - Tenant Isolation | `tests/Browser/Storefront/TenantIsolationTest.php` | 5 | Multi-store data isolation verification | Store 1, Admin user, Customer user |
    23→| 13 - Responsive / Mobile | `tests/Browser/Storefront/ResponsiveTest.php` | 8 | Mobile and tablet viewport rendering and interaction | Products, Admin user |
    24→| 14 - Accessibility | `tests/Browser/Storefront/AccessibilityTest.php` | 11 | Heading hierarchy, ARIA labels, form labels, keyboard nav | Products, Store 1 |
    25→| 15 - Admin Collections | `tests/Browser/Admin/CollectionManagementTest.php` | 3 | Collection CRUD in admin | Admin user, Collections |
    26→| 16 - Admin Customers | `tests/Browser/Admin/CustomerManagementTest.php` | 3 | Customer listing and detail views | Admin user, Customer user, Orders |
    27→| 17 - Admin Pages | `tests/Browser/Admin/PageManagementTest.php` | 3 | CMS page management | Admin user, Pages |
    28→| 18 - Admin Analytics | `tests/Browser/Admin/AnalyticsTest.php` | 3 | Analytics dashboard rendering | Admin user, Orders |
    29→| **Total** | **18 files** | **134** | | |
    30→
    31→---
    32→
    33→## Test Infrastructure Requirements
    34→
    35→### Framework
    36→
    37→- **Test runner:** Pest v4 with browser testing capabilities (Playwright under the hood)
    38→- **Test traits:** Each browser test file must use `TestCase` and `RefreshDatabase`
    39→- **Configuration:** Register the `Browser` test directory in `tests/Pest.php` with appropriate base classes
    40→- **Base URL:** Configured via `APP_URL` in `.env.testing`, set to `http://acme-fashion.test`
    41→
    42→### Pest v4 Browser Testing API Reference
    43→
    44→The coding agent should use these Pest v4 browser methods:
    45→
    46→| Method | Purpose |
    47→|--------|---------|
    48→| `visit(url)` | Open a real browser and navigate to the given URL |
    49→| `visit([url1, url2, ...])` | Visit multiple pages (for batch smoke testing) |
    50→| `fill(field, value)` | Type a value into a named form field |

# 08 - Playwright E2E Test Plan

> Complete end-to-end browser test specification for the Shopify-clone platform using Pest v4 browser testing (Playwright-backed). All tests verify the entire application works through a real browser. This document defines requirements and specifications for the coding agent to implement.

---

## Test Suite Summary

| Suite | File | Tests | Purpose | Dependencies |
|-------|------|:-----:|---------|--------------|
| 1 - Smoke Tests | `tests/Browser/SmokeTest.php` | 10 | Hit every major page, assert HTTP 200 and no JS errors | Store 1, Products, Collections, Pages |
| 2 - Admin Authentication | `tests/Browser/Admin/AuthenticationTest.php` | 10 | Admin login, logout, invalid credentials, session access control | Admin user |
| 3 - Admin Product Management | `tests/Browser/Admin/ProductManagementTest.php` | 7 | Product CRUD: listing, creating, editing, archiving, filtering | Admin user, Products (incl. #15 draft) |
| 4 - Admin Order Management | `tests/Browser/Admin/OrderManagementTest.php` | 7 | Order listing, filtering, detail view, fulfillment, refund | Admin user, Orders (#1001), Customer |
| 5 - Admin Discount Management | `tests/Browser/Admin/DiscountManagementTest.php` | 6 | Discount code listing, creation, editing | Admin user, Discounts |
| 6 - Admin Settings | `tests/Browser/Admin/SettingsTest.php` | 7 | Store settings, shipping zones, tax config, domains | Admin user, Shipping zones, Store 1 |
| 7 - Storefront Browsing | `tests/Browser/Storefront/BrowsingTest.php` | 15 | Home page, collections, product detail, variants, search, pages | Products, Collections, Pages, Products #15/#17/#18 |
| 8 - Cart Flow | `tests/Browser/Storefront/CartTest.php` | 12 | Cart lifecycle: add, update qty, remove, discounts, totals | Products, Discounts (all codes) |
| 9 - Checkout Flow | `tests/Browser/Storefront/CheckoutTest.php` | 8 | Full checkout: contact, address, shipping, payment, confirmation | Products, Shipping zones, Discounts |
| 10 - Customer Account | `tests/Browser/Storefront/CustomerAccountTest.php` | 12 | Registration, login, order history, addresses, logout | Customer user, Orders (#1001/#1002/#1004) |
| 11 - Inventory Enforcement | `tests/Browser/Storefront/InventoryTest.php` | 4 | Inventory policy enforcement: deny vs. continue | Products #17 (deny), #18 (continue), #1 |
| 12 - Tenant Isolation | `tests/Browser/Storefront/TenantIsolationTest.php` | 5 | Multi-store data isolation verification | Store 1, Admin user, Customer user |
| 13 - Responsive / Mobile | `tests/Browser/Storefront/ResponsiveTest.php` | 8 | Mobile and tablet viewport rendering and interaction | Products, Admin user |
| 14 - Accessibility | `tests/Browser/Storefront/AccessibilityTest.php` | 11 | Heading hierarchy, ARIA labels, form labels, keyboard nav | Products, Store 1 |
| 15 - Admin Collections | `tests/Browser/Admin/CollectionManagementTest.php` | 3 | Collection CRUD in admin | Admin user, Collections |
| 16 - Admin Customers | `tests/Browser/Admin/CustomerManagementTest.php` | 3 | Customer listing and detail views | Admin user, Customer user, Orders |
| 17 - Admin Pages | `tests/Browser/Admin/PageManagementTest.php` | 3 | CMS page management | Admin user, Pages |
| 18 - Admin Analytics | `tests/Browser/Admin/AnalyticsTest.php` | 3 | Analytics dashboard rendering | Admin user, Orders |
| **Total** | **18 files** | **134** | | |

---

## Test Infrastructure Requirements

### Framework

- **Test runner:** Pest v4 with browser testing capabilities (Playwright under the hood)
- **Test traits:** Each browser test file must use `TestCase` and `RefreshDatabase`
- **Configuration:** Register the `Browser` test directory in `tests/Pest.php` with appropriate base classes
- **Base URL:** Configured via `APP_URL` in `.env.testing`, set to `http://acme-fashion.test`

### Pest v4 Browser Testing API Reference

The coding agent should use these Pest v4 browser methods:

| Method | Purpose |
|--------|---------|
| `visit(url)` | Open a real browser and navigate to the given URL |
| `visit([url1, url2, ...])` | Visit multiple pages (for batch smoke testing) |
| `fill(field, value)` | Type a value into a named form field |


     1→# 09 - Implementation Roadmap and Pest Test Specification
     2→
     3→> Shopify-clone e-commerce platform - Step-by-step build order and exhaustive test plan for Laravel 12 + Livewire v4 + Flux UI Free + SQLite + Tailwind CSS v4
     4→
     5→---
     6→
     7→## 1. Technology Decisions (Final)
     8→
     9→These decisions adapt the original PDF spec for a self-contained, benchmarkable Laravel application. All decisions are final and must not be revisited.
    10→
    11→| Concern | PDF Default | Our Decision | Rationale |
    12→|---------|-------------|--------------|-----------|
    13→| Database | PostgreSQL | SQLite | Self-contained, no external DB, zero config |
    14→| Cache | Redis | File (Array for tests) | No Redis dependency |
    15→| Session | Redis | File | No Redis dependency |
    16→| Queue | Redis + Horizon | Sync (dev) / Database (prod) | No Redis dependency, sync for immediate feedback |
    17→| Search | Elasticsearch + Scout | SQLite FTS5 | Self-contained, no external service |
    18→| Storage | S3 | Local filesystem (storage/app/public) | No AWS dependency |
    19→| Admin UI | React/Vue SPA | Livewire v4 + Flux UI Free | Server-rendered, matches project stack |
    20→| Storefront | Blade SSR + Vite | Blade + Livewire + Tailwind v4 | Server-rendered, matches project stack |
    21→| Auth (admin) | Sanctum SPA | Laravel session auth | Standard Livewire auth pattern |
    22→| Auth (API) | Sanctum tokens | Sanctum personal access tokens | For programmatic API access |
    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→
    33→## 2. Implementation Phases (Build Order)
    34→
    35→Each phase builds on the previous. A coding agent should execute these in strict sequential order. Tests are written alongside each phase (see Section 3 for the full test specification).
    36→
    37→### Phase Dependency Graph
    38→
    39→```mermaid
    40→flowchart TD
    41→    P1[Phase 1: Foundation]
    42→    P2[Phase 2: Catalog]
    43→    P3[Phase 3: Themes and Storefront Layout]
    44→    P4[Phase 4: Cart, Checkout, Discounts, Shipping, Taxes]
    45→    P5[Phase 5: Payments, Orders, Fulfillment]
    46→    P6[Phase 6: Customer Accounts]
    47→    P7[Phase 7: Admin Panel]
    48→    P8[Phase 8: Search]
    49→    P9[Phase 9: Analytics]
    50→    P10[Phase 10: Apps and Webhooks]

# 09 - Implementation Roadmap and Pest Test Specification

> Shopify-clone e-commerce platform - Step-by-step build order and exhaustive test plan for Laravel 12 + Livewire v4 + Flux UI Free + SQLite + Tailwind CSS v4

---

## 1. Technology Decisions (Final)

These decisions adapt the original PDF spec for a self-contained, benchmarkable Laravel application. All decisions are final and must not be revisited.

| Concern | PDF Default | Our Decision | Rationale |
|---------|-------------|--------------|-----------|
| Database | PostgreSQL | SQLite | Self-contained, no external DB, zero config |
| Cache | Redis | File (Array for tests) | No Redis dependency |
| Session | Redis | File | No Redis dependency |
| Queue | Redis + Horizon | Sync (dev) / Database (prod) | No Redis dependency, sync for immediate feedback |
| Search | Elasticsearch + Scout | SQLite FTS5 | Self-contained, no external service |
| Storage | S3 | Local filesystem (storage/app/public) | No AWS dependency |
| Admin UI | React/Vue SPA | Livewire v4 + Flux UI Free | Server-rendered, matches project stack |
| Storefront | Blade SSR + Vite | Blade + Livewire + Tailwind v4 | Server-rendered, matches project stack |
| Auth (admin) | Sanctum SPA | Laravel session auth | Standard Livewire auth pattern |
| Auth (API) | Sanctum tokens | Sanctum personal access tokens | For programmatic API access |
| 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 |

---

## 2. Implementation Phases (Build Order)

Each phase builds on the previous. A coding agent should execute these in strict sequential order. Tests are written alongside each phase (see Section 3 for the full test specification).

### Phase Dependency Graph

```mermaid
flowchart TD
    P1[Phase 1: Foundation]
    P2[Phase 2: Catalog]
    P3[Phase 3: Themes and Storefront Layout]
    P4[Phase 4: Cart, Checkout, Discounts, Shipping, Taxes]
    P5[Phase 5: Payments, Orders, Fulfillment]
    P6[Phase 6: Customer Accounts]
    P7[Phase 7: Admin Panel]
    P8[Phase 8: Search]
    P9[Phase 9: Analytics]
    P10[Phase 10: Apps and Webhooks]


Now I have a good picture of what needs to be updated. Let me create the remaining tasks and start editing. I need to:

1. Finish Task #3 (02-API-ROUTES.md) - there are several remaining Stripe/PayPal/webhook references
2. Update 03-ADMIN-UI.md
3. Update 04-STOREFRONT-UI.md
4. Update 07-SEEDERS-AND-TEST-DATA.md
5. Update 08-PLAYWRIGHT-E2E-PLAN.md
6. Update 09-IMPLEMENTATION-ROADMAP.md

Let me first create the remaining tasks, then continue with the edits. Let me read more sections I need to understand before editing.


Let me create tasks for the remaining work and read more sections I need to edit.


TaskCreate

Update 03-ADMIN-UI.md - Payment method display, confirm payment, fulfillment guard

Add payment_method display in order detail, "Confirm Payment" button for bank_transfer, fulfillment guard callout, "Mark as shipped"/"Mark as delivered" buttons on fulfillment cards


Task #4 created successfully: Update 03-ADMIN-UI.md - Payment method display, confirm payment, fulfillment guard


Update 04-STOREFRONT-UI.md - Replace checkout Step 4 with payment method selection

Replace checkout Step 4 (Payment) with payment method selection (radio buttons) and onsite forms for credit card, PayPal, bank transfer. Update order confirmation page.


Task #5 created successfully: Update 04-STOREFRONT-UI.md - Replace checkout Step 4 with payment method selection


Update 07-SEEDERS-AND-TEST-DATA.md - Add payment_method to orders, update factories

Add payment_method to all seeded orders, add bank_transfer and paypal orders, update PaymentFactory provider to mock, update OrderFactory


Task #6 created successfully: Update 07-SEEDERS-AND-TEST-DATA.md - Add payment_method to orders, update factories


Update 08-PLAYWRIGHT-E2E-PLAN.md - Update checkout tests for mock PSP

Update checkout flow tests for payment method selection, add tests per payment method, magic card decline test, bank transfer admin confirmation test


Task #7 created successfully: Update 08-PLAYWRIGHT-E2E-PLAN.md - Update checkout tests for mock PSP


Update 09-IMPLEMENTATION-ROADMAP.md - Replace Stripe/PayPal with MockPaymentProvider

Update Phase 5 and technology decisions to use MockPaymentProvider, remove Stripe/PayPal references, add fulfillment guard and payment-method-specific OMS


Task #8 created successfully: Update 09-IMPLEMENTATION-ROADMAP.md - Replace Stripe/PayPal with MockPaymentProvider


Now let me finish Task #3 (02-API-ROUTES.md) by cleaning up remaining Stripe/PayPal/webhook references. Let me also read the sections I need for the other files.


   300→**Model:** CartLine
   301→
   302→**Default definition:**
   303→
   304→| Field | Generation Rule |
   305→|-------|----------------|
   306→| cart_id | Creates a new Cart via its factory |
   307→| variant_id | Creates a new ProductVariant via its factory |
   308→| quantity | Random 1-5 |
   309→| unit_price_amount | Random 999-19999 |
   310→| line_subtotal_amount | Computed: unit_price_amount * quantity |
   311→| line_discount_amount | 0 |
   312→| line_total_amount | Computed: line_subtotal_amount - line_discount_amount |
   313→
   314→**States:** None.
   315→
   316→---
   317→
   318→### 2.11 CheckoutFactory
   319→
   320→**Model:** Checkout
   321→
   322→**Default definition:**
   323→
   324→| Field | Generation Rule |
   325→|-------|----------------|
   326→| store_id | Creates a new Store via its factory |
   327→| cart_id | Creates a new Cart via its factory |
   328→| customer_id | null |
   329→| status | "started" |
   330→| email | Faker safe email |
   331→| shipping_address_json | JSON object with Faker first_name, last_name, address1, city, country_code "DE", zip |
   332→| billing_address_json | null |
   333→| shipping_method_id | null |
   334→| discount_code | null |
   335→| tax_provider_snapshot_json | null |
   336→| totals_json | null |
   337→| expires_at | 24 hours from now |
   338→
   339→**States:**
   340→
   341→| State Name | Overrides |
   342→|-----------|-----------|
   343→| `completed` | status = "completed" |
   344→| `expired` | status = "expired", expires_at = 1 hour ago |
   345→
   346→---
   347→
   348→### 2.12 OrderFactory
   349→
   350→**Model:** Order
   351→
   352→**Default definition:**
   353→
   354→| Field | Generation Rule |
   355→|-------|----------------|
   356→| store_id | Creates a new Store via its factory |
   357→| customer_id | Creates a new Customer via its factory |
   358→| order_number | "#" + Faker unique integer 1001-9999 |
   359→| status | "paid" |
   360→| financial_status | "paid" |
   361→| fulfillment_status | "unfulfilled" |
   362→| currency | "EUR" |
   363→| subtotal_amount | 4998 |
   364→| discount_amount | 0 |
   365→| shipping_amount | 499 |
   366→| tax_amount | 798 |
   367→| total_amount | 5497 |
   368→| email | Faker safe email |
   369→| billing_address_json | JSON address object with German Faker data |
   370→| shipping_address_json | JSON address object with German Faker data |
   371→| placed_at | Current timestamp |
   372→
   373→**States:**
   374→
   375→| State Name | Overrides |
   376→|-----------|-----------|
   377→| `pending` | status = "pending", financial_status = "pending" |
   378→| `fulfilled` | status = "fulfilled", fulfillment_status = "fulfilled" |
   379→| `cancelled` | status = "cancelled", financial_status = "refunded" |
   380→| `refunded` | status = "refunded", financial_status = "refunded" |
   381→| `partiallyFulfilled` | fulfillment_status = "partial" |
   382→
   383→---
   384→
   385→### 2.13 OrderLineFactory
   386→
   387→**Model:** OrderLine
   388→
   389→**Default definition:**
   390→
   391→| Field | Generation Rule |
   392→|-------|----------------|
   393→| order_id | Creates a new Order via its factory |
   394→| product_id | Creates a new Product via its factory |
   395→| variant_id | Creates a new ProductVariant via its factory |
   396→| title_snapshot | Faker 3 words |
   397→| sku_snapshot | Faker pattern "SKU-####-???" |
   398→| quantity | Random 1-3 |
   399→| unit_price_amount | Random 999-19999 |
   400→| total_amount | Computed: unit_price_amount * quantity |
   401→| tax_lines_json | Empty JSON array |
   402→| discount_allocations_json | Empty JSON array |
   403→
   404→**States:** None.
   405→
   406→---
   407→
   408→### 2.14 PaymentFactory
   409→
   410→**Model:** Payment
   411→
   412→**Default definition:**
   413→
   414→| Field | Generation Rule |
   415→|-------|----------------|
   416→| order_id | Creates a new Order via its factory |
   417→| provider | "stripe" |
   418→| provider_payment_id | "pi_" + Faker 20 random alphanumeric characters |
   419→| status | "captured" |
   420→| amount | Random 999-99999 |
   421→| currency | "EUR" |
   422→| raw_json_encrypted | null |
   423→
   424→**States:**
   425→
   426→| State Name | Overrides |
   427→|-----------|-----------|
   428→| `requiresAction` | status = "requires_action" |
   429→| `authorized` | status = "authorized" |
   430→| `failed` | status = "failed" |
   431→| `refunded` | status = "refunded" |
   432→
   433→---
   434→
   435→### 2.15 RefundFactory
   436→
   437→**Model:** Refund
   438→
   439→**Default definition:**
   440→
   441→| Field | Generation Rule |
   442→|-------|----------------|
   443→| order_id | Creates a new Order via its factory |
   444→| payment_id | Creates a new Payment via its factory |
   445→| amount | Random 999-19999 |
   446→| reason | Faker sentence |
   447→| status | "processed" |
   448→| provider_refund_id | "re_" + Faker 20 random alphanumeric characters |
   449→

**Model:** CartLine

**Default definition:**

| Field | Generation Rule |
|-------|----------------|
| cart_id | Creates a new Cart via its factory |
| variant_id | Creates a new ProductVariant via its factory |
| quantity | Random 1-5 |
| unit_price_amount | Random 999-19999 |
| line_subtotal_amount | Computed: unit_price_amount * quantity |
| line_discount_amount | 0 |
| line_total_amount | Computed: line_subtotal_amount - line_discount_amount |

**States:** None.

---

### 2.11 CheckoutFactory

**Model:** Checkout

**Default definition:**

| Field | Generation Rule |
|-------|----------------|
| store_id | Creates a new Store via its factory |
| cart_id | Creates a new Cart via its factory |
| customer_id | null |
| status | "started" |
| email | Faker safe email |
| shipping_address_json | JSON object with Faker first_name, last_name, address1, city, country_code "DE", zip |
| billing_address_json | null |
| shipping_method_id | null |
| discount_code | null |
| tax_provider_snapshot_json | null |
| totals_json | null |
| expires_at | 24 hours from now |

**States:**

| State Name | Overrides |
|-----------|-----------|
| `completed` | status = "completed" |
| `expired` | status = "expired", expires_at = 1 hour ago |

---

### 2.12 OrderFactory

**Model:** Order

**Default definition:**

| Field | Generation Rule |
|-------|----------------|
| store_id | Creates a new Store via its factory |
| customer_id | Creates a new Customer via its factory |
| order_number | "#" + Faker unique integer 1001-9999 |
| status | "paid" |
| financial_status | "paid" |
| fulfillment_status | "unfulfilled" |
| currency | "EUR" |
| subtotal_amount | 4998 |
| discount_amount | 0 |
| shipping_amount | 499 |
| tax_amount | 798 |
| total_amount | 5497 |
| email | Faker safe email |
| billing_address_json | JSON address object with German Faker data |
| shipping_address_json | JSON address object with German Faker data |
| placed_at | Current timestamp |

**States:**

| State Name | Overrides |
|-----------|-----------|
| `pending` | status = "pending", financial_status = "pending" |
| `fulfilled` | status = "fulfilled", fulfillment_status = "fulfilled" |
| `cancelled` | status = "cancelled", financial_status = "refunded" |
| `refunded` | status = "refunded", financial_status = "refunded" |
| `partiallyFulfilled` | fulfillment_status = "partial" |

---

### 2.13 OrderLineFactory

**Model:** OrderLine

**Default definition:**

| Field | Generation Rule |
|-------|----------------|
| order_id | Creates a new Order via its factory |
| product_id | Creates a new Product via its factory |
| variant_id | Creates a new ProductVariant via its factory |
| title_snapshot | Faker 3 words |
| sku_snapshot | Faker pattern "SKU-####-???" |
| quantity | Random 1-3 |
| unit_price_amount | Random 999-19999 |
| total_amount | Computed: unit_price_amount * quantity |
| tax_lines_json | Empty JSON array |
| discount_allocations_json | Empty JSON array |

**States:** None.

---

### 2.14 PaymentFactory

**Model:** Payment

**Default definition:**

| Field | Generation Rule |
|-------|----------------|
| order_id | Creates a new Order via its factory |
| provider | "stripe" |
| provider_payment_id | "pi_" + Faker 20 random alphanumeric characters |
| status | "captured" |
| amount | Random 999-99999 |
| currency | "EUR" |
| raw_json_encrypted | null |

**States:**

| State Name | Overrides |
|-----------|-----------|
| `requiresAction` | status = "requires_action" |
| `authorized` | status = "authorized" |
| `failed` | status = "failed" |
| `refunded` | status = "refunded" |

---

### 2.15 RefundFactory

**Model:** Refund

**Default definition:**

| Field | Generation Rule |
|-------|----------------|
| order_id | Creates a new Order via its factory |
| payment_id | Creates a new Payment via its factory |
| amount | Random 999-19999 |
| reason | Faker sentence |
| status | "processed" |
| provider_refund_id | "re_" + Faker 20 random alphanumeric characters |