command: for f in app/Http/Requests/Api/Storefront/V1/*.php; do printf '%s\n' "$f"; sed -n '1,220p' "$f"; done

type: unknown

aggregated_output:
app/Http/Requests/Api/Storefront/V1/ApplyCheckoutDiscountRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class ApplyCheckoutDiscountRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'code' => ['nullable', 'string', 'max:255'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/SelectCheckoutPaymentRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class SelectCheckoutPaymentRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'payment_method' => ['required', 'in:credit_card,paypal,bank_transfer'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/SetCheckoutAddressRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class SetCheckoutAddressRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'email' => ['required', 'email'],
            'shipping_address' => ['required', 'array'],
            'shipping_address.first_name' => ['required', 'string'],
            'shipping_address.last_name' => ['required', 'string'],
            'shipping_address.address1' => ['required', 'string'],
            'shipping_address.address2' => ['nullable', 'string'],
            'shipping_address.city' => ['required', 'string'],
            'shipping_address.province_code' => ['nullable', 'string'],
            'shipping_address.country' => ['required', 'string', 'size:2'],
            'shipping_address.postal_code' => ['required', 'string'],
            'billing_address' => ['nullable', 'array'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/SetCheckoutShippingRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class SetCheckoutShippingRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'shipping_rate_id' => ['nullable', 'integer', 'exists:shipping_rates,id'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class StoreCartLineRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'variant_id' => ['required', 'integer', 'exists:product_variants,id'],
            'quantity' => ['required', 'integer', 'min:1'],
            'expected_version' => ['nullable', 'integer', 'min:1'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class StoreCheckoutRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'cart_id' => ['required', 'integer', 'exists:carts,id'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class UpdateCartLineRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'quantity' => ['required', 'integer', 'min:0'],
            'expected_version' => ['nullable', 'integer', 'min:1'],
        ];
    }
}


output:
Chunk ID: 8073a7
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1436
Output:
app/Http/Requests/Api/Storefront/V1/ApplyCheckoutDiscountRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class ApplyCheckoutDiscountRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'code' => ['nullable', 'string', 'max:255'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/SelectCheckoutPaymentRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class SelectCheckoutPaymentRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'payment_method' => ['required', 'in:credit_card,paypal,bank_transfer'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/SetCheckoutAddressRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class SetCheckoutAddressRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'email' => ['required', 'email'],
            'shipping_address' => ['required', 'array'],
            'shipping_address.first_name' => ['required', 'string'],
            'shipping_address.last_name' => ['required', 'string'],
            'shipping_address.address1' => ['required', 'string'],
            'shipping_address.address2' => ['nullable', 'string'],
            'shipping_address.city' => ['required', 'string'],
            'shipping_address.province_code' => ['nullable', 'string'],
            'shipping_address.country' => ['required', 'string', 'size:2'],
            'shipping_address.postal_code' => ['required', 'string'],
            'billing_address' => ['nullable', 'array'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/SetCheckoutShippingRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class SetCheckoutShippingRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'shipping_rate_id' => ['nullable', 'integer', 'exists:shipping_rates,id'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class StoreCartLineRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'variant_id' => ['required', 'integer', 'exists:product_variants,id'],
            'quantity' => ['required', 'integer', 'min:1'],
            'expected_version' => ['nullable', 'integer', 'min:1'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class StoreCheckoutRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'cart_id' => ['required', 'integer', 'exists:carts,id'],
        ];
    }
}
app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php
<?php

namespace App\Http\Requests\Api\Storefront\V1;

use Illuminate\Foundation\Http\FormRequest;

class UpdateCartLineRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'quantity' => ['required', 'integer', 'min:0'],
            'expected_version' => ['nullable', 'integer', 'min:1'],
        ];
    }
}


output:
Chunk ID: f75e16
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 413
Output:
<?php

namespace App\Http\Resources\Storefront\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CartLineResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'variant_id' => $this->variant_id,
            'quantity' => $this->quantity,
            'unit_price_amount' => $this->unit_price_amount,
            'line_subtotal_amount' => $this->line_subtotal_amount,
            'line_discount_amount' => $this->line_discount_amount,
            'line_total_amount' => $this->line_total_amount,
            'product' => $this->whenLoaded('variant', fn (): ?array => $this->variant?->relationLoaded('product') ? [
                'id' => $this->variant->product?->id,
                'title' => $this->variant->product?->title,
                'handle' => $this->variant->product?->handle,
            ] : null),
            'variant' => $this->whenLoaded('variant', fn (): array => [
                'id' => $this->variant->id,
                'sku' => $this->variant->sku,
                'requires_shipping' => $this->variant->requires_shipping,
                'options' => $this->variant->relationLoaded('optionValues')
                    ? $this->variant->optionValues->map(fn ($value): array => [
                        'name' => $value->option?->name,
                        'value' => $value->value,
                    ])->values()->all()
                    : [],
            ]),
        ];
    }
}


output:
Chunk ID: d47ea1
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 176
Output:
<?php

namespace App\Http\Resources\Storefront\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class ShippingRateResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'zone_id' => $this->zone_id,
            'name' => $this->name,
            'type' => $this->type?->value,
            'amount' => $this->when($this->getAttribute('calculated_amount') !== null, $this->getAttribute('calculated_amount')),
            'config' => $this->config_json,
        ];
    }
}


output:
Chunk ID: 482be8
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 668
Output:
<?php

use App\Http\Controllers\Api\Storefront\V1\CartController;
use App\Http\Controllers\Api\Storefront\V1\CartLineController;
use App\Http\Controllers\Api\Storefront\V1\CheckoutController;
use Illuminate\Support\Facades\Route;

Route::middleware(['store.resolve', 'throttle:api.storefront'])
    ->prefix('storefront/v1')
    ->name('api.storefront.v1.')
    ->group(function (): void {
        Route::post('carts', [CartController::class, 'store'])->name('carts.store');
        Route::get('carts/{cart}', [CartController::class, 'show'])->name('carts.show');
        Route::post('carts/{cart}/lines', [CartLineController::class, 'store'])->name('carts.lines.store');
        Route::put('carts/{cart}/lines/{cartLine}', [CartLineController::class, 'update'])->name('carts.lines.update');
        Route::delete('carts/{cart}/lines/{cartLine}', [CartLineController::class, 'destroy'])->name('carts.lines.destroy');

        Route::post('checkouts', [CheckoutController::class, 'store'])->name('checkouts.store');
        Route::get('checkouts/{checkout}', [CheckoutController::class, 'show'])->name('checkouts.show');
        Route::put('checkouts/{checkout}/address', [CheckoutController::class, 'address'])->name('checkouts.address');
        Route::put('checkouts/{checkout}/shipping-method', [CheckoutController::class, 'shippingMethod'])->name('checkouts.shipping-method');
        Route::post('checkouts/{checkout}/apply-discount', [CheckoutController::class, 'applyDiscount'])->name('checkouts.apply-discount');
        Route::put('checkouts/{checkout}/payment-method', [CheckoutController::class, 'paymentMethod'])->name('checkouts.payment-method');
    });
<?php

use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\ResolveStore;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->alias([
            'role.check' => CheckStoreRole::class,
            'store.resolve' => ResolveStore::class,
        ]);

        $middleware->appendToGroup('storefront', [
            ResolveStore::class,
        ]);

        $middleware->appendToGroup('admin', [
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();


cmd: rg -n "cart|checkout|API|storefront/v1|expected_version|shipping-method|apply-discount|payment-method" specs/02-API-ROUTES.md specs/05-BUSINESS-LOGIC.md specs/09-IMPLEMENTATION-ROADMAP.md specs/progress.md


command:
/bin/zsh
-lc
sed -n '1,260p' specs/progress.md

progress.md

type: read

cmd: sed -n '1,260p' specs/progress.md

aggregated_output:
# Shop Build Progress

## Objective

Build a complete, self-contained Laravel shop system from `specs/*`, with implemented acceptance criteria mapped to evidence, passing automated checks, verified browser flows, independent QA, and meaningful commits.

## Current Status

- Status: in progress
- Active slice: Phase 4 - Storefront cart/checkout API surface and remaining cart estimates, after UI connection
- Started from: Laravel Livewire/Fortify starter kit with no shop domain tables or models present
- Last updated: 2026-05-03

## Phased Plan

1. Foundation: SQLite config, tenancy tables/models/factories/seeders, store resolution middleware, store scoping, admin/customer auth foundations, policies, and Phase 1 tests.
2. Catalog: product, variant, inventory, collection, media schema/models/services, admin catalog CRUD, storefront browsing, and tests.
3. Storefront theme/layout: theme/page/navigation data, storefront layout, product/collection/search/page views, and smoke browser verification.
4. Cart/checkout/pricing: cart API/UI, discount, shipping, tax, checkout state machine, and tests.
5. Payments/orders/fulfillment: mock PSP, order creation, refunds, fulfillments, customer account order views, and tests.
6. Admin panel: dashboard and all admin list/form/detail pages from Spec 03, with Livewire/Flux interactions.
7. Search/analytics/apps/webhooks: SQLite search, event aggregation, developer token/webhook UI, delivery jobs, and tests.
8. Final verification: full Pest suite, Pint, frontend build, Playwright MCP customer/admin flows, independent QA, fixes, and completion audit.

## Acceptance Checklist

| Area | Criteria Source | Status | Evidence |
| --- | --- | --- | --- |
| Database schema | `specs/01-DATABASE-SCHEMA.md` | partial | Phase 1 tenancy/auth tables, Phase 2 catalog tables, Phase 3 theme/content/navigation tables, and Phase 4 cart/checkout/pricing tables are implemented: carts, cart_lines, checkouts, shipping_zones, shipping_rates, tax_settings, discounts. Order/payment/search/analytics/app tables are still missing. |
| Routes/API | `specs/02-API-ROUTES.md` | partial | Phase 1 auth routes plus catalog/cart UI routes exist: `/`, `/collections`, `/collections/{handle}`, `/products/{handle}`, `/cart`, `/checkout`, `/search`, `/pages/{handle}`, `/admin/products`, `/admin/products/create`, `/admin/products/{product}/edit`, `/admin/collections`, `/admin/collections/create`, `/admin/collections/{collection}/edit`, `/admin/inventory`. Storefront/admin API routes are still missing. |
| Admin UI | `specs/03-ADMIN-UI.md` | partial | Flux/Livewire admin catalog shell now includes product index/form, collection index/form, and inventory list with auth protection, filtering, pagination, status changes, SKU uniqueness checks, and collection assignment. Order/customer/content/settings admin pages are still missing. |
| Storefront UI | `specs/04-STOREFRONT-UI.md` | partial | Storefront layout, home, collections index/detail, product detail, search, breadcrumbs, price display, product cards, DB-backed pages, seeded navigation menus, cached theme settings, product add-to-cart persistence, cart drawer, cart page, and checkout address/shipping/discount/payment-selection UI render seeded data. Cart page shipping estimate and discount entry are still pending. |
| Business logic | `specs/05-BUSINESS-LOGIC.md` | partial | `ResolveStore`, `BelongsToStore`, `StoreScope`, role checks, customer guard provider, catalog product lifecycle transitions, SKU uniqueness, variant matrix rebuilding, inventory reserve/release/commit/restock, automatic variant inventory, variant option mapping, media resize/cleanup job, `CartService`, `DiscountService`, `ShippingCalculator`, `TaxCalculator`, `PricingEngine`, checkout transitions through payment selection, checkout expiration, abandoned cart cleanup, session cart lookup, customer login cart merge, and product add-to-cart mutations are implemented. Order/payment services are still missing. |
| Auth/security | `specs/06-AUTH-AND-SECURITY.md` | partial | Admin Livewire login/logout, customer guard/provider, customer login/register, login rate limiting, session hardening, and resource policies implemented. Customer password reset and Sanctum token auth still missing. |
| Seed/test data | `specs/07-SEEDERS-AND-TEST-DATA.md` | partial | Seeders create Acme Fashion and Acme Electronics stores/domains/settings/admin access, 6 collections, 25 products, 127 variants, 127 inventory rows, 206 variant option pivots, 2 themes, 6 theme files, 2 theme settings rows, 6 pages, 4 navigation menus, 17 navigation items, 2 tax settings rows, 2 shipping zones, 6 shipping rates, 6 discounts, and no product media/runtime carts. Order/payment seed data is still missing. |
| Browser E2E plan | `specs/08-PLAYWRIGHT-E2E-PLAN.md` | partial | Manual Playwright MCP smoke coverage has verified storefront catalog browsing, product detail, add-to-cart, cart drawer/page, checkout through payment selection, search, DB-b…8985 chars truncated…tions where they do not conflict with the shop specs.
- Use integer minor units for all money columns and JSON text columns cast through Eloquent as specified.
- Keep `password_hash` as the database column for admin users and customers while exposing `password`/`getAuthPassword()` at the model layer for Fortify/Laravel auth compatibility.
- Seed both `shop.test` and `acme-fashion.test` as storefront domains for the first store so the goal URL and E2E spec URL can both resolve.
- Customer Livewire auth components persist `storeId` from the initial storefront request because Livewire update requests do not run through the original storefront route middleware.
- The seed specification contains an arithmetic conflict: Acme Fashion's per-product variant table sums to 117 variants, while the prose says 107. The implementation follows the concrete per-product table, resulting in 117 Fashion variants and 10 Electronics variants, 127 total.
- Child catalog models without a `store_id` column are tenant-scoped through their parent product relationship. Inventory keeps its denormalized `store_id` and enforces that it matches the variant product store at save time.
- Storefront content pages now resolve from the `pages` table and only published pages are rendered.
- The catalog admin form intentionally blocks active products without a priced variant and duplicate in-store SKUs during UI edits, mirroring service-layer invariants where the current CRUD surface touches product variants directly.
- Navigation tree support is flat for now because the Phase 3 schema defines `navigation_items.position` but no `parent_id`; `NavigationService::buildTree()` returns a flat tree-compatible array with empty children.
- Cart, checkout, and pricing runtime records are not seeded; only deterministic tax, shipping, and discount configuration is seeded. Runtime carts/checkouts are created by services and tests.
- Checkout completion intentionally stops before order creation in this slice. `CheckoutService::completeCheckout()` is a Phase 5 boundary until order, payment, and fulfillment tables/services exist.
- The cart drawer reads the current session/customer cart without creating an empty cart on every storefront render; carts are created on first add-to-cart or checkout/cart service mutation.
- The checkout UI currently reserves inventory by selecting a payment method and leaves payment capture/order creation for Phase 5.

## Open Issues

- Composer does not currently include Sanctum, while Spec 06 requires Sanctum personal access tokens. This will need either an approved dependency addition or a documented compatible alternative before final completion.
- The Herd app URL in the goal is `http://shop.test/`, while the E2E spec also references `http://acme-fashion.test`; domain handling must support seeded store domains and Herd verification.
- Phase 1 still lacks admin/customer password reset at the spec paths and a custom customer password reset token repository that scopes by store_id.
- Policy classes for later resources use generic object parameters until the concrete catalog/order/content models exist.
- `php artisan route:list --except-vendor` hides Livewire full-page routes because their controller is vendor-provided; path-filtered route-list commands are used as evidence for those routes.
- Phase 2 media upload/admin UI is still missing, even though the media schema/job layer exists.
- Product options can be displayed and edited as text in the admin form, but full option matrix generation and option-value reassignment UI are not complete.
- Theme/page/navigation admin management UI is still missing even though the Phase 3 data layer, seeders, services, and storefront consumption exist.
- Storefront cart page shipping estimate and cart-level discount entry are still missing; discounts can be applied during checkout.
- Storefront cart and checkout API endpoints are still missing.
- Checkout order creation, payment capture, discount usage-count increment on successful order completion, and fulfillment are still pending Phase 5.
- Automated browser tests from Spec 08 are still missing; current browser coverage is manual Playwright MCP smoke verification.
- SQLite enum/check constraints from the schema spec are not yet explicitly enforced as database `CHECK` constraints; enum validation is currently enforced through casts/services/model invariants.
- Product media processing now validates and resizes image uploads with GD and cleans predictable generated paths, but browser/admin upload UI is still pending.

## Completion Summary

Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, and cart/checkout storefront UI through payment selection are implemented, with known auth/token, media UI, theme admin UI, cart/checkout API, cart estimates, order/payment, browser-test, and database CHECK constraint gaps tracked above.

status: completed


command:
/bin/zsh
-lc
sed -n '1,260p' specs/02-API-ROUTES.md

cmd: sed -n '1,260p' specs/02-API-ROUTES.md

aggregated_output:
# API and Routing Specification

This document defines every route, endpoint, middleware stack, request/response schema, and rate limit for the e-commerce platform. The admin panel is built with Livewire v4 + Flux UI (server-rendered), while the storefront uses Blade + Livewire. REST API endpoints serve cart/checkout operations and programmatic admin access.

All monetary amounts are stored and transmitted in **minor units** (cents). All IDs are auto-incrementing integers. All monetary amounts are in minor units (cents).

---

## 1. Web Routes (Blade/Livewire Pages)

### 1.1 Admin Auth Routes

No middleware beyond the default `web` group. These routes handle admin login and logout.

| Method | Path | Description | Component / Action |
|--------|------|-------------|--------------------|
| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
| POST | `/admin/login` | Process admin login | Handled by Livewire action |
| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |

Rate limit: `login` tier (5 per minute per IP).

---

### 1.2 Admin Routes

**Prefix:** `/admin`
**Middleware:** `web`, `auth`, `verified`, `store.resolve`, `role.check`

All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).

> **Note:** The Create and Edit routes render the same Livewire form component (`ProductForm`, `OrderForm`, etc.) in create vs edit mode, following the shared-form pattern defined in Spec 03.

#### Dashboard

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin` | Dashboard with KPI tiles, orders chart, conversion funnel | `Admin\Dashboard` |

#### Products

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/products` | Product list - searchable, filterable by status, bulk actions | `Admin\Products\Index` |
| GET | `/admin/products/create` | Create product form | `Admin\Products\Create` |
| GET | `/admin/products/{product}/edit` | Edit product form with variants, media, options | `Admin\Products\Edit` |

#### Inventory

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/inventory` | Inventory management page | `Admin\Inventory\Index` |

#### Collections

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/collections` | Collection list | `Admin\Collections\Index` |
| GET | `/admin/collections/create` | Create collection form | `Admin\Collections\Create` |
| GET | `/admin/collections/{collection}/edit` | Edit collection form | `Admin\Collections\Edit` |

#### Orders

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/orders` | Order list with status filters | `Admin\Orders\Index` |
| GET | `/admin/orders/{order}` | Order detail with timeline, payments, fulfillments | `Admin\Orders\Show` |

#### Customers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/customers` | Customer list | `Admin\Customers\Index` |
| GET | `/admin/customers/{customer}` | Customer detail with order history, addresses | `Admin\Customers\Show` |

#### Discounts

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/discounts` | Discount list | `Admin\Discounts\Index` |
| GET | `/admin/discounts/create` | Create discount form with rules builder | `Admin\Discounts\Create` |
| GET | `/admin/discounts/{discount}/edit` | Edit discount form | `Admin\Discounts\Edit` |

#### Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/settings` | Store settings (tabs: General, Domains, Shipping, Taxes, Checkout, Notifications) | `Admin\Settings\Index` |
| GET | `/admin/settings/shipping` | Shipping zones and rates configuration | `Admin\Settings\Shipping` |
| GET | `/admin/settings/taxes` | Tax settings - mode, provider config, rates | `Admin\Settings\Taxes` |

#### Themes and Online Store

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/themes` | Theme management - cards with actions (Customize, Preview, Publish, Duplicate, Delete) | `Admin\Themes\Index` |
| GET | `/admin/themes/{theme}/editor` | Theme editor - left panel sections, center preview iframe, right panel settings | `Admin\Themes\Editor` |

#### Pages

| Method | Path | Description | Component |
|--------|------|--…588 chars truncated…nstalled and available apps | `Admin\Apps\Index` |
| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |

#### Developers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |

> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.

#### Analytics

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/analytics` | Analytics dashboard - sales, traffic, funnel, exports | `Admin\Analytics\Index` |

#### Search Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/search/settings` | Search settings - synonyms, stopwords, reindex | `Admin\Search\Settings` |

---

### 1.3 Storefront Routes

**Middleware:** `web`, `store.resolve`

These routes serve the public-facing storefront using Blade templates rendered through the active theme engine.

| Method | Path | Description | Component / View |
|--------|------|-------------|------------------|
| GET | `/` | Home page | `Storefront\Home` |
| GET | `/collections` | All collections listing | `Storefront\Collections\Index` |
| GET | `/collections/{handle}` | Single collection page with product grid and filters | `Storefront\Collections\Show` |
| GET | `/products/{handle}` | Product page - gallery, variant selector, add-to-cart | `Storefront\Products\Show` |
| GET | `/cart` | Full cart page - line items, quantity controls, discount input, totals | `Storefront\Cart\Show` |
| GET | `/search` | Search results page with filter panel, sort dropdown, product grid | `Storefront\Search\Index` |
| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |

#### Customer Account Routes

**Additional middleware:** `auth:customer`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account` | Customer account overview | `Storefront\Account\Dashboard` |
| GET | `/account/orders` | Order history list | `Storefront\Account\Orders\Index` |
| GET | `/account/orders/{orderNumber}` | Order detail | `Storefront\Account\Orders\Show` |
| GET | `/account/addresses` | Address book management | `Storefront\Account\Addresses\Index` |

#### Customer Auth Routes (no auth required)

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account/login` | Customer login page | `Storefront\Account\Auth\Login` |
| GET | `/account/register` | Customer registration page | `Storefront\Account\Auth\Register` |
| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
| POST | `/reset-password` | Reset password | Handled by Livewire action |

---

### 1.4 Checkout Routes

**Middleware:** `web`, `store.resolve`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/checkout/{checkoutId}` | Checkout page - stepper UI (Contact -> Shipping -> Payment) | `Storefront\Checkout\Show` |
| GET | `/checkout/{checkoutId}/confirmation` | Order confirmation page - order number, items, totals | `Storefront\Checkout\Confirmation` |

---

## 2. Storefront REST API

**Prefix:** `/api/storefront/v1`
**Middleware:** `store.resolve`, `throttle:api.storefront`
**Content-Type:** `application/json`

These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).

---

### 2.1 Cart Endpoints

#### POST /api/storefront/v1/carts

Create a new cart.

- **Authentication:** None (guest) or session-based (logged-in customer)
- **Rate limit:** `api.storefront` (120/min per IP)

**Request body:**

```json
{
  "currency": "EUR"
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `currency` | string | No | Valid ISO 4217 code. Defaults to store's `default_currency`. |

**Success response:** `201 Created`

```json
{
  "id": 42,
  "store_id": 1,
  "customer_id": null,
  "currency": "EUR",
  "cart_version": 1,
  "status": "active",
  "lines": [],
  "totals": {
    "subtotal": 0,
    "discount": 0,
    "total": 0,
    "currency": "EUR"
  },
  "created_at": "2026-02-11T10:00:00Z",
  "updated_at": "2026-02-11T10:00:00Z"
}
```

**Error responses:**
- `422` - Validation error (invalid currency)

---

#### GET /api/storefront/v1/carts/{cartId}

Retrieve a cart with all lines and computed totals.

- **Authentication:** None (cart ID acts as token)
- **Rate limit:** `api.storefront` (120/min per IP)


command: rg -n "cart|checkout|API|storefront/v1|expected_version|shipping-method|apply-discount|payment-method" specs/02-API-ROUTES.md specs/05-BUSINESS-LOGIC.md specs/09-IMPLEMENTATION-ROADMAP.md specs/progress.md

type: search

cmd: rg -n 'cart|checkout|API|storefront/v1|expected_version|shipping-method|apply-discount|payment-method' specs/02-API-ROUTES.md specs/05-BUSINESS-LOGIC.md specs/09-IMPLEMENTATION-ROADMAP.md specs/progress.md

query: cart|checkout|API|storefront/v1|expected_version|shipping-method|apply-discount|payment-method

aggregated_output:
specs/progress.md:10:- Active slice: Phase 4 - Storefront cart/checkout API surface and remaining cart estimates, after UI connection
specs/progress.md:19:4. Cart/checkout/pricing: cart API/UI, discount, shipping, tax, checkout state machine, and tests.
specs/progress.md:29:| Database schema | `specs/01-DATABASE-SCHEMA.md` | partial | Phase 1 tenancy/auth tables, Phase 2 catalog tables, Phase 3 theme/content/navigation tables, and Phase 4 cart/checkout/pricing tables are implemented: carts, cart_lines, checkouts, shipping_zones, shipping_rates, tax_settings, discounts. Order/payment/search/analytics/app tables are still missing. |
specs/progress.md:30:| Routes/API | `specs/02-API-ROUTES.md` | partial | Phase 1 auth routes plus catalog/cart UI routes exist: `/`, `/collections`, `/collections/{handle}`, `/products/{handle}`, `/cart`, `/checkout`, `/search`, `/pages/{handle}`, `/admin/products`, `/admin/products/create`, `/admin/products/{product}/edit`, `/admin/collections`, `/admin/collections/create`, `/admin/collections/{collection}/edit`, `/admin/inventory`. Storefront/admin API routes are still missing. |
specs/progress.md:32:| Storefront UI | `specs/04-STOREFRONT-UI.md` | partial | Storefront layout, home, collections index/detail, product detail, search, breadcrumbs, price display, product cards, DB-backed pages, seeded navigation menus, cached theme settings, product add-to-cart persistence, cart drawer, cart page, and checkout address/shipping/discount/payment-selection UI render seeded data. Cart page shipping estimate and discount entry are still pending. |
specs/progress.md:33:| Business logic | `specs/05-BUSINESS-LOGIC.md` | partial | `ResolveStore`, `BelongsToStore`, `StoreScope`, role checks, customer guard provider, catalog product lifecycle transitions, SKU uniqueness, variant matrix rebuilding, inventory reserve/release/commit/restock, automatic variant inventory, variant option mapping, media resize/cleanup job, `CartService`, `DiscountService`, `ShippingCalculator`, `TaxCalculator`, `PricingEngine`, checkout transitions through payment selection, checkout expiration, abandoned cart cleanup, session cart lookup, customer login cart merge, and product add-to-cart mutations are implemented. Order/payment services are still missing. |
specs/progress.md:35:| Seed/test data | `specs/07-SEEDERS-AND-TEST-DATA.md` | partial | Seeders create Acme Fashion and Acme Electronics stores/domains/settings/admin access, 6 collections, 25 products, 127 variants, 127 inventory rows, 206 variant option pivots, 2 themes, 6 theme files, 2 theme settings rows, 6 pages, 4 navigation menus, 17 navigation items, 2 tax settings rows, 2 shipping zones, 6 shipping rates, 6 discounts, and no product media/runtime carts. Order/payment seed data is still missing. |
specs/progress.md:36:| Browser E2E plan | `specs/08-PLAYWRIGHT-E2E-PLAN.md` | partial | Manual Playwright MCP smoke coverage has verified storefront catalog browsing, product detail, add-to-cart, cart drawer/page, checkout through payment selection, search, DB-backed content pages, seeded navigation, admin product/collection/inventory pages, and auth flows without console warnings/errors on successful pages. Automated browser tests are still missing. |
specs/progress.md:37:| Roadmap phases | `specs/09-IMPLEMENTATION-ROADMAP.md` | in progress | Phase 1 foundation, Phase 2 catalog data/UI, Phase 3 theme/content/navigation data with storefront consumption, Phase 4 cart/checkout/pricing backend foundation, and Phase 4 storefront cart/checkout UI through `payment_selected` are implemented. Phase 4 API/remaining cart estimates and Phase 5 orders/payments are next. |
specs/progress.md:45:- 2026-05-03: Planning challenger sub-agent found the repo was still a starter kit and produced the acceptance checklist by vertical slice. Risks added here: missing Sanctum dependency, password_hash/Fortify coordination, route/API absence, and E2E count inconsistencies.
specs/progress.md:78:- 2026-05-03: Phase 4 explorer QA mapped the cart, checkout, discount, shipping, tax, pricing, and cleanup-job boundary; order creation/payment capture were kept for Phase 5 because order/payment tables are not implemented yet.
specs/progress.md:80:- 2026-05-03: Boost schema summary confirmed Phase 4 tables: carts, cart_lines, checkouts, shipping_zones, shipping_rates, tax_settings, discounts. Boost query counts after fresh seed: carts 0, cart_lines 0, checkouts 0, shipping_zones 2, shipping_rates 6, tax_settings 2, discounts 6.
specs/progress.md:82:- 2026-05-03: `vendor/bin/pint --dirty --format agent` fixed checkout/pricing style and import order, then passed after job test coverage was added.
specs/progress.md:84:- 2026-05-03: `mcp__laravel_boost__.search_docs` consulted Livewire 4 full-page components, forms, validation, session, events, redirects, Flux modal/form controls, and Pest docs before Phase 4 cart/checkout UI changes.
specs/progress.md:85:- 2026-05-03: `php artisan route:list --path=…46087 chars truncated…ecs/09-IMPLEMENTATION-ROADMAP.md:1653:| sets checkout address | PUT /api/storefront/v1/checkouts/{id}/address | 200 response, status=addressed | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1654:| selects a shipping method | PUT /api/storefront/v1/checkouts/{id}/shipping-method | 200 response, status=shipping_selected, available rates returned in response | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1655:| applies a discount code | POST /api/storefront/v1/checkouts/{id}/apply-discount with code | 200 response, discount applied in totals | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1656:| retrieves checkout with totals | GET /api/storefront/v1/checkouts/{id} | 200 response, includes totals_json fields | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1657:| selects a payment method | PUT /api/storefront/v1/checkouts/{id}/payment-method with method=credit_card | 200 response, status=payment_selected, payment_method stored | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1658:| completes checkout with credit card payment | POST /api/storefront/v1/checkouts/{id}/pay with card fields | 200 response, status=completed, order created | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1659:| rejects payment with declined card | POST /api/storefront/v1/checkouts/{id}/pay with magic decline number | 422 response, payment declined message | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1669:| creates a product via API | POST /api/admin/v1/stores/{storeId}/products with product data | 201 response, product in response | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1670:| updates a product via API | PUT /api/admin/v1/stores/{storeId}/products/{id} with updated data | 200 response, product updated | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1671:| deletes a draft product via API | DELETE /api/admin/v1/stores/{storeId}/products/{id} | 200 response | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1685:| creates a fulfillment via API | POST /api/admin/v1/stores/{storeId}/orders/{id}/fulfillments | 201 response, fulfillment created | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1686:| creates a refund via API | POST /api/admin/v1/stores/{storeId}/orders/{id}/refunds | 201 response, refund created | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1715:| `Cart` | Shopping cart entity |
specs/09-IMPLEMENTATION-ROADMAP.md:1716:| `CartLine` | Individual cart line item |
specs/09-IMPLEMENTATION-ROADMAP.md:1772:| `Admin/Developers/Index` | API token and webhook management |
specs/09-IMPLEMENTATION-ROADMAP.md:1782:| `Storefront/Cart/Show` | Full cart page |
specs/09-IMPLEMENTATION-ROADMAP.md:1783:| `Storefront/CartDrawer` | Slide-out cart drawer |
specs/09-IMPLEMENTATION-ROADMAP.md:1787:| `Storefront/Checkout/Show` | Multi-step checkout |
specs/09-IMPLEMENTATION-ROADMAP.md:1829:| `SetCheckoutAddressRequest` | Validate checkout address |
specs/09-IMPLEMENTATION-ROADMAP.md:1830:| `SetCheckoutShippingRequest` | Validate checkout shipping selection |
specs/09-IMPLEMENTATION-ROADMAP.md:1849:| `CleanupAbandonedCarts` | Mark old active carts as abandoned |
specs/09-IMPLEMENTATION-ROADMAP.md:1850:| `ExpireAbandonedCheckouts` | Expire timed-out checkouts, release inventory |
specs/09-IMPLEMENTATION-ROADMAP.md:1861:| `CheckoutCompleted` | Dispatched when checkout completes |
specs/09-IMPLEMENTATION-ROADMAP.md:1865:| `CartUpdated` | Dispatched when cart is mutated |
specs/09-IMPLEMENTATION-ROADMAP.md:1888:| `Auth/SanctumTokenTest` | API token feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1895:| `Cart/CartApiTest` | Cart API feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1921:| `Api/StorefrontCartApiTest` | Storefront cart API feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1922:| `Api/StorefrontCheckoutApiTest` | Storefront checkout API feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1923:| `Api/AdminProductApiTest` | Admin product API feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1924:| `Api/AdminOrderApiTest` | Admin order API feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1944:- Configure rate limiters: `login` (5/min per IP), `api.admin` (60/min per token), `api.storefront` (120/min per IP), `checkout` (10/min per session), `search` (30/min per IP), `analytics` (60/min per IP), `webhooks` (100/min per IP)
specs/09-IMPLEMENTATION-ROADMAP.md:1951:| `routes/web.php` | Admin auth routes, admin routes (prefix `/admin`), storefront routes, checkout routes, customer account routes |
specs/09-IMPLEMENTATION-ROADMAP.md:1952:| `routes/api.php` | Storefront API (prefix `/api/storefront/v1`), Admin API (prefix `/api/admin/v1`), Webhook receiver routes |
specs/09-IMPLEMENTATION-ROADMAP.md:1976:| `OrderService` | Order creation from checkout, numbering, inventory commit |
specs/09-IMPLEMENTATION-ROADMAP.md:1977:| `RefundService` | Refund creation, provider API, inventory restock |
specs/09-IMPLEMENTATION-ROADMAP.md:2035:11. Manual smoke: add a product to the cart, proceed through the full checkout flow


output:
Chunk ID: cee6a0
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 14022
Output:
Total output lines: 418

specs/progress.md:10:- Active slice: Phase 4 - Storefront cart/checkout API surface and remaining cart estimates, after UI connection
specs/progress.md:19:4. Cart/checkout/pricing: cart API/UI, discount, shipping, tax, checkout state machine, and tests.
specs/progress.md:29:| Database schema | `specs/01-DATABASE-SCHEMA.md` | partial | Phase 1 tenancy/auth tables, Phase 2 catalog tables, Phase 3 theme/content/navigation tables, and Phase 4 cart/checkout/pricing tables are implemented: carts, cart_lines, checkouts, shipping_zones, shipping_rates, tax_settings, discounts. Order/payment/search/analytics/app tables are still missing. |
specs/progress.md:30:| Routes/API | `specs/02-API-ROUTES.md` | partial | Phase 1 auth routes plus catalog/cart UI routes exist: `/`, `/collections`, `/collections/{handle}`, `/products/{handle}`, `/cart`, `/checkout`, `/search`, `/pages/{handle}`, `/admin/products`, `/admin/products/create`, `/admin/products/{product}/edit`, `/admin/collections`, `/admin/collections/create`, `/admin/collections/{collection}/edit`, `/admin/inventory`. Storefront/admin API routes are still missing. |
specs/progress.md:32:| Storefront UI | `specs/04-STOREFRONT-UI.md` | partial | Storefront layout, home, collections index/detail, product detail, search, breadcrumbs, price display, product cards, DB-backed pages, seeded navigation menus, cached theme settings, product add-to-cart persistence, cart drawer, cart page, and checkout address/shipping/discount/payment-selection UI render seeded data. Cart page shipping estimate and discount entry are still pending. |
specs/progress.md:33:| Business logic | `specs/05-BUSINESS-LOGIC.md` | partial | `ResolveStore`, `BelongsToStore`, `StoreScope`, role checks, customer guard provider, catalog product lifecycle transitions, SKU uniqueness, variant matrix rebuilding, inventory reserve/release/commit/restock, automatic variant inventory, variant option mapping, media resize/cleanup job, `CartService`, `DiscountService`, `ShippingCalculator`, `TaxCalculator`, `PricingEngine`, checkout transitions through payment selection, checkout expiration, abandoned cart cleanup, session cart lookup, customer login cart merge, and product add-to-cart mutations are implemented. Order/payment services are still missing. |
specs/progress.md:35:| Seed/test data | `specs/07-SEEDERS-AND-TEST-DATA.md` | partial | Seeders create Acme Fashion and Acme Electronics stores/domains/settings/admin access, 6 collections, 25 products, 127 variants, 127 inventory rows, 206 variant option pivots, 2 themes, 6 theme files, 2 theme settings rows, 6 pages, 4 navigation menus, 17 navigation items, 2 tax settings rows, 2 shipping zones, 6 shipping rates, 6 discounts, and no product media/runtime carts. Order/payment seed data is still missing. |
specs/progress.md:36:| Browser E2E plan | `specs/08-PLAYWRIGHT-E2E-PLAN.md` | partial | Manual Playwright MCP smoke coverage has verified storefront catalog browsing, product detail, add-to-cart, cart drawer/page, checkout through payment selection, search, DB-backed content pages, seeded navigation, admin product/collection/inventory pages, and auth flows without console warnings/errors on successful pages. Automated browser tests are still missing. |
specs/progress.md:37:| Roadmap phases | `specs/09-IMPLEMENTATION-ROADMAP.md` | in progress | Phase 1 foundation, Phase 2 catalog data/UI, Phase 3 theme/content/navigation data with storefront consumption, Phase 4 cart/checkout/pricing backend foundation, and Phase 4 storefront cart/checkout UI through `payment_selected` are implemented. Phase 4 API/remaining cart estimates and Phase 5 orders/payments are next. |
specs/progress.md:45:- 2026-05-03: Planning challenger sub-agent found the repo was still a starter kit and produced the acceptance checklist by vertical slice. Risks added here: missing Sanctum dependency, password_hash/Fortify coordination, route/API absence, and E2E count inconsistencies.
specs/progress.md:78:- 2026-05-03: Phase 4 explorer QA mapped the cart, checkout, discount, shipping, tax, pricing, and cleanup-job boundary; order creation/payment capture were kept for Phase 5 because order/payment tables are not implemented yet.
specs/progress.md:80:- 2026-05-03: Boost schema summary confirmed Phase 4 tables: carts, cart_lines, checkouts, shipping_zones, shipping_rates, tax_settings, discounts. Boost query counts after fresh seed: carts 0, cart_lines 0, checkouts 0, shipping_zones 2, shipping_rates 6, tax_settings 2, discounts 6.
specs/progress.md:82:- 2026-05-03: `vendor/bin/pint --dirty --format agent` fixed checkout/pricing style and import order, then passed after job test coverage was added.
specs/progress.md:84:- 2026-05-03: `mcp__laravel_boost__.search_docs` consulted Livewire 4 full-page components, forms, validation, session, events, redirects, Flux modal/form controls, and Pest docs before Phase 4 cart/checkout UI changes.
specs/progress.md:85:- 2026-05-03: `php artisan route:list --path=cart` and `php artisan route:list --path=checkout` confirmed `/cart` and `/checkout` Livewire routes.
specs/progress.md:91:- 2026-05-03: `npm run build` passed after the cart/checkout Blade changes.
specs/progress.md:92:- 2026-05-03: Playwright MCP verified `http://shop.test/products/classic-cotton-t-shirt` add-to-cart, `http://shop.test/cart`, and `http://shop.test/checkout` render and progress through shipping, discount, and reserve-items payment selection with Livewire update requests returning 200 and no new console warnings/errors. Missing favicon links were fixed with `public/favicon.svg`.
specs/progress.md:96:- Follow the roadmap order strictly. Phase 1 is required before catalog, checkout, admin, storefront, and QA flows can be meaningfully implemented.
specs/progress.md:107:- Cart, checkout, and pricing runtime records are not seeded; only deterministic tax, shipping, and discount configuration is seeded. Runtime carts/checkouts are created by services and tests.
specs/progress.md:109:- The cart drawer reads the current session/customer cart without creating an empty cart on every storefront render; carts are created on first add-to-cart or checkout/cart service mutation.
specs/progress.md:110:- The checkout UI currently reserves inventory by selecting a payment method and leaves payment capture/order creation for Phase 5.
specs/progress.md:122:- Storefront cart page shipping estimate and cart-level discount entry are still missing; discounts can be applied during checkout.
specs/progress.md:123:- Storefront cart and checkout API endpoints are still missing.
specs/progress.md:131:Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, and cart/checkout storefront UI through payment selection are implemented, with known auth/token, media UI, theme admin UI, cart/checkout API, cart estimates, order/payment, browser-test, and database CHECK constraint gaps tracked above.
specs/02-API-ROUTES.md:1:# API and Routing Specification
specs/02-API-ROUTES.md:3:This document defines every route, endpoint, middleware stack, request/response schema, and rate limit for the e-commerce platform. The admin panel is built with Livewire v4 + Flux UI (server-rendered), while the storefront uses Blade + Livewire. REST API endpoints serve cart/checkout operations and programmatic admin access.
specs/02-API-ROUTES.md:128:| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |
specs/02-API-ROUTES.md:130:> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.
specs/02-API-ROUTES.md:157:| GET | `/products/{handle}` | Product page - gallery, variant selector, add-to-cart | `Storefront\Products\Show` |
specs/02-API-ROUTES.md:158:| GET | `/cart` | Full cart page - line items, quantity controls, discount input, totals | `Storefront\Cart\Show` |
specs/02-API-ROUTES.md:192:| GET | `/checkout/{checkoutId}` | Checkout page - stepper UI (Contact -> Shipping -> Payment) | `Storefront\Checkout\Show` |
specs/02-API-ROUTES.md:193:| GET | `/checkout/{checkoutId}/confirmation` | Order confirmation page - order number, items, totals | `Storefront\Checkout\Confirmation` |
specs/02-API-ROUTES.md:197:## 2. Storefront REST API
specs/02-API-ROUTES.md:199:**Prefix:** `/api/storefront/v1`
specs/02-API-ROUTES.md:203:These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).
specs/02-API-ROUTES.md:209:#### POST /api/storefront/v1/carts
specs/02-API-ROUTES.md:211:Create a new cart.
specs/02-API-ROUTES.md:236:  "cart_version": 1,
specs/02-API-ROUTES.md:255:#### GET /api/storefront/v1/carts/{cartId}
specs/02-API-ROUTES.md:257:Retrieve a cart with all lines and computed totals.
specs/02-API-ROUTES.md:259:- **Authentication:** None (cart ID acts as token)
specs/02-API-ROUTES.md:266:| `cartId` | integer | Yes | The cart identifier |
specs/02-API-ROUTES.md:276:  "cart_version": 3,
specs/02-API-ROUTES.md:313:#### POST /api/storefront/v1/carts/{cartId}/lines
specs/02-API-ROUTES.md:315:Add a line item to the cart.
specs/02-API-ROUTES.md:336:Returns the full cart object (same schema as GET /carts/{cartId}).
specs/02-API-ROUTES.md:340:- `409` - Cart version conflict (concurrent modification, retry with fresh cart_version)
specs/02-API-ROUTES.md:354:#### PUT /api/storefront/v1/carts/{cartId}/lines/{lineId}
specs/02-API-ROUTES.md:356:Update the quantity of a cart line.
specs/02-API-ROUTES.md:366:  "cart_version": 3
specs/02-API-ROUTES.md:373:| `cart_version` | integer | Yes | Must match current cart version for optimistic concurrency. |
specs/02-API-ROUTES.md:377:Returns the full cart object with updated totals.
specs/02-API-ROUTES.md:386:#### DELETE /api/storefront/v1/carts/{cartId}/lines/{lineId}
specs/02-API-ROUTES.md:388:Remove a line item from the cart.
specs/02-API-ROUTES.md:397:  "cart_version": 3
specs/02-API-ROUTES.md:403:| `cart_version` | integer | Yes | Must match current cart version. |
specs/02-API-ROUTES.md:407:Returns the full cart object.
specs/02-API-ROUTES.md:422:    participant API as Storefront API
specs/02-API-ROUTES.md:425:    C->>API: POST /checkouts (cart_id, email)
specs/02-API-ROUTES.md:426:    API-->>C: 201 Checkout (status: started)
specs/02-API-ROUTES.md:428:    C->>API: PUT /checkouts/{id}/address
specs/02-API-ROUTES.md:429:    API-->>C: 200 Checkout (status: addressed, available_shipping_methods)
specs/02-API-ROUTES.md:431:    C->>API: PUT /checkouts/{id}/shipping-method
specs/02-API-ROUTES.md:432:    API-->>C: 200 Checkout (status: shipping_selected, totals with tax)
specs/02-API-ROUTES.md:435:        C->>API: POST /checkouts/{id}/apply-discount
specs/02-API-ROUTES.md:436:        API-->>C: 200 Checkout (totals recalculated)
specs/02-API-ROUTES.md:439:    C->>API: POST /checkouts/{id}/pay (payment_method + details)
specs/02-API-ROUTES.md:440:    API->>API: Reserve inventory
specs/02-API-ROUTES.md:441:    API->>MockPSP: charge(checkout, paymentMethodData)
specs/02-API-ROUTES.md:444:        MockPSP-->>API: PaymentResult (success)
specs/02-API-ROUTES.md:445:        API->>API: Create order (paid), commit inventory
specs/02-API-ROUTES.md:446:        API-->>C: 200 (order confirmation data)
specs/02-API-ROUTES.md:448:        MockPSP-->>API: PaymentResult (failed)
specs/02-API-ROUTES.md:449:        API->>API: Release reserved inventory
specs/02-API-ROUTES.md:450:        API-->>C: 422 (error: card_declined / insufficient_funds)
specs/02-API-ROUTES.md:452:        MockPSP-->>API: PaymentResult (pending)
specs/02-API-ROUTES.md:453:        API->>API: Create order (pending), inventory stays reserved
specs/02-API-ROUTES.md:454:        API-->>C: 200 (order confirmation + bank transfer instructions)
specs/02-API-ROUTES.md:458:#### POST /api/storefront/v1/checkouts
specs/02-API-ROUTES.md:460:Create a checkout from a cart.
specs/02-API-ROUTES.md:463:- **Rate limit:** `checkout` (10/min per session)
specs/02-API-ROUTES.md:469:  "cart_id": 42,
specs/02-API-ROUTES.md:476:| `cart_id` | integer | Yes | Must reference an active cart with at least one line. |
specs/02-API-ROUTES.md:485:  "cart_id": 42,
specs/02-API-ROUTES.md:520:- `422` - Validation errors (empty cart, invalid email)
specs/02-API-ROUTES.md:524:#### GET /api/storefront/v1/checkouts/{checkoutId}
specs/02-API-ROUTES.md:526:Retrieve the current checkout state and computed totals.
specs/02-API-ROUTES.md:528:- **Authentication:** None (checkout ID acts as token)
specs/02-API-ROUTES.md:529:- **Rate limit:** `checkout` (10/min per session)
specs/02-API-ROUTES.md:533:Returns the full checkout object (same schema as POST response, with updated fields reflecting the current state).
specs/02-API-ROUTES.md:541:#### PUT /api/storefront/v1/checkouts/{checkoutId}/address
specs/02-API-ROUTES.md:543:Set the shipping and/or billing address on a checkout.
specs/02-API-ROUTES.md:546:- **Rate limit:** `checkout` (10/min per session)
specs/02-API-ROUTES.md:591:Returns the full checkout object. Status transitions to `addressed`. The response includes `available_shipping_methods` populated based on the shipping address and store's shipping zones.
specs/02-API-ROUTES.md:661:#### PUT /api/storefront/v1/checkouts/{checkoutId}/shipping-method
specs/02-API-ROUTES.md:663:Select a shipping method for the checkout.
specs/02-API-ROUTES.md:666:- **Rate limit:** `checkout` (10/min per session)
specs/02-API-ROUTES.md:678:| `shipping_method_id` | integer | Yes | Must be one of the available shipping methods for this checkout's address. |
specs/02-API-ROUTES.md:682:Returns the full checkout object. Status transitions to `shipping_selected`. Totals are recalculated including the shipping cost and taxes (if applicable).
specs/02-API-ROUTES.md:717:- `422` - Invalid shipping method (not available for this address/checkout)
specs/02-API-ROUTES.md:721:#### PUT /api/storefront/v1/checkouts/{checkoutId}/payment-method
specs/02-API-ROUTES.md:723:Select a payment method for the checkout. This step records the chosen method without processing payment.
specs/02-API-ROUTES.md:726:- **Rate limit:** `checkout` (10/min per session)
specs/02-API-ROUTES.md:742:Returns the full checkout object. Status transitions to `payment_selected`.
specs/02-API-ROUTES.md:763:- `422` - Invalid payment method or checkout not in `shipping_selected` state
specs/02-API-ROUTES.md:767:#### POST /api/storefront/v1/checkouts/{checkoutId}/apply-discount
specs/02-API-ROUTES.md:769:Apply a discount code to the checkout.
specs/02-API-ROUTES.md:772:- **Rate limit:** `checkout` (10/min per session)
specs/02-API-ROUTES.md:829:#### DELETE /api/storefront/v1/checkouts/{checkoutId}/discount
specs/02-API-ROUTES.md:831:Remove the applied discount code from the checkout.
specs/02-API-ROUTES.md:834:- **Rate limit:** `checkout` (10/min per session)
specs/02-API-ROUTES.md:838:Returns the full checkout object with discount removed and totals recalculated.
specs/02-API-ROUTES.md:848:#### POST /api/storefront/v1/checkouts/{checkoutId}/pay
specs/02-API-ROUTES.md:850:Process payment for a checkout using the selected payment method. All payment processing happens in-process via the Mock PSP - no external API calls, no redirects, no webhooks.
specs/02-API-ROUTES.md:853:- **Rate limit:** `checkout` (10/min per session)
specs/02-API-ROUTES.md:895:  "checkout_id": 7,
specs/02-API-ROUTES.md:913:  "checkout_id": 7,
specs/02-API-ROUTES.md:958:#### GET /api/storefront/v1/orders/{orderNumber}
specs/02-API-ROUTES.md:1019:#### GET /api/storefront/v1/search
specs/02-API-ROUTES.md:1097:#### GET /api/storefront/v1/search/suggest
specs/02-API-ROUTES.md:1143:#### POST /api/storefront/v1/analytics/events
specs/02-API-ROUTES.md:1167:      "type": "add_to_cart",
specs/02-API-ROUTES.md:1186:| `events[].type` | string | Yes | One of: `page_view`, `add_to_cart`, `remove_from_cart`, `checkout_started`, `checkout_completed`, `search`. |
specs/02-API-ROUTES.md:1206:## 3. Admin REST API
specs/02-API-ROUTES.md:1214:All endpoints require a valid Sanctum API token with appropriate scopes. Responses follow Laravel's API Resource conventions.
specs/02-API-ROUTES.md:2800:      "add_to_cart_count": 890,
specs/02-API-ROUTES.md:2801:      "checkout_started_count": 420,
specs/02-API-ROUTES.md:2812:        "add_to_cart_count": 72,
specs/02-API-ROUTES.md:2813:        "checkout_started_count": 35
specs/02-API-ROUTES.md:2977:### App API Endpoints
specs/02-API-ROUTES.md:2983:| GET | `/api/apps/v1/stores/{storeId}/products` | `read-products` | List products (same schema as admin API) |
specs/02-API-ROUTES.md:3027:#### API Storefront
specs/02-API-ROUTES.md:3029:Applied to `/api/storefront/v1/*` routes:
specs/02-API-ROUTES.md:3036:#### API Admin
specs/02-API-ROUTES.md:3056:- **API (storefront):** Same as storefront web - hostname-based resolution.
specs/02-API-ROUTES.md:3057:- **API (admin):** Reads `storeId` from route parameter and verifies the authenticated user has membership.
specs/02-API-ROUTES.md:3081:| `api.admin` | 60 requests | 1 minute | Sanctum token ID | Admin REST API endpoints |
specs/02-API-ROUTES.md:3082:| `api.storefront` | 120 requests | 1 minute | IP address | Storefront REST API (carts, etc.) |
specs/02-API-ROUTES.md:3083:| `checkout` | 10 requests | 1 minute | Session ID | Checkout creation and mutation endpoints |
specs/02-API-ROUTES.md:3113:| `POST /api/storefront/v1/carts*` | `api.storefront` |
specs/02-API-ROUTES.md:3114:| `GET /api/storefront/v1/carts*` | `api.storefront` |
specs/02-API-ROUTES.md:3115:| `PUT /api/storefront/v1/carts*` | `api.storefront` |
specs/02-API-ROUTES.md:3116:| `DELETE /api/storefront/v1/carts*` | `api.storefront` |
specs/02-API-ROUTES.md:3117:| `POST /api/storefront/v1/checkouts*` | `checkout` |
specs/02-API-ROUTES.md:3118:| `GET /api/storefront/v1/checkouts*` | `checkout` |
specs/02-API-ROUTES.md:3119:| `PUT /api/storefront/v1/checkouts*` | `checkout` |
specs/02-API-ROUTES.md:3120:| `DELETE /api/storefront/v1/checkouts*` | `checkout` |
specs/02-API-ROUTES.md:3121:| `POST /api/storefront/v1/checkouts/*/pay` | `checkout` |
specs/02-API-ROUTES.md:3122:| `GET /api/storefront/v1/orders*` | `api.storefront` |
specs/02-API-ROUTES.md:3123:| `GET /api/storefront/v1/search` | `search` |
specs/02-API-ROUTES.md:3124:| `GET /api/storefront/v1/search/suggest` | `search` |
specs/02-API-ROUTES.md:3125:| `POST /api/storefront/v1/analytics/events` | `analytics` |
specs/02-API-ROUTES.md:3136:The checkout follows a strict state machine with explicit transitions:
specs/02-API-ROUTES.md:3140:    [*] --> started : POST /checkouts (create from cart)
specs/02-API-ROUTES.md:3141:    started --> addressed : PUT /checkouts/{id}/address
specs/02-API-ROUTES.md:3142:    addressed --> shipping_selected : PUT /checkouts/{id}/shipping-method
specs/02-API-ROUTES.md:3143:    shipping_selected --> payment_selected : PUT /checkouts/{id}/payment-method
specs/02-API-ROUTES.md:3144:    payment_selected --> completed : POST /checkouts/{id}/pay (instant capture: credit_card/paypal)
specs/02-API-ROUTES.md:3145:    payment_selected --> completed : POST /checkouts/{id}/pay (bank_transfer: deferred, order created as pending)
specs/02-API-ROUTES.md:3158:| `started` | `addressed` | PUT /checkouts/{id}/address with valid address |
specs/02-API-ROUTES.md:3159:| `addressed` | `shipping_selected` | PUT /checkouts/{id}/shipping-method with valid method |
specs/02-API-ROUTES.md:3160:| `shipping_selected` | `payment_selected` | PUT /checkouts/{id}/payment-method with valid method |
specs/02-API-ROUTES.md:3161:| `payment_selected` | `completed` | POST /checkouts/{id}/pay - Mock PSP processes payment in-process (instant for card/paypal, deferred for bank_transfer) |
specs/02-API-ROUTES.md:3165:Applying or removing a discount does not c…4022 tokens truncated….php`; finds checkouts with `expires_at < now()` and active status; releases reserved inventory, transitions to `expired` |
specs/09-IMPLEMENTATION-ROADMAP.md:584:| Job | `App\Jobs\CleanupAbandonedCarts` | Runs daily; marks carts with `status = active` and `updated_at < 14 days ago` as `abandoned` |
specs/09-IMPLEMENTATION-ROADMAP.md:592:| `Storefront\CartDrawer` | Slide-out panel: line items, quantity controls, discount code input, subtotal, checkout button |
specs/09-IMPLEMENTATION-ROADMAP.md:593:| `Storefront\Cart\Show` | Full cart page with same functionality plus shipping estimate |
specs/09-IMPLEMENTATION-ROADMAP.md:668:| `charge(Checkout $checkout, PaymentMethod $method, array $details): PaymentResult` | Processes payment in-process. For credit_card: validates magic card numbers, returns success/decline. For paypal: always succeeds. For bank_transfer: returns pending (deferred capture). |
specs/09-IMPLEMENTATION-ROADMAP.md:672:- No external API calls, no webhooks, no redirects
specs/09-IMPLEMENTATION-ROADMAP.md:702:| `createFromCheckout(Checkout $checkout): Order` | Atomic transaction: create order + order lines with snapshots, commit inventory, mark cart converted, dispatch `OrderCreated` event |
specs/09-IMPLEMENTATION-ROADMAP.md:714:| `create(Order $order, Payment $payment, int $amount, ?string $reason, bool $restock): Refund` | Validates amount does not exceed payment, calls provider refund API, updates financial status |
specs/09-IMPLEMENTATION-ROADMAP.md:738:| Event | `OrderCreated` | Dispatched when order is created from checkout |
specs/09-IMPLEMENTATION-ROADMAP.md:852:| `Admin\Developers\Index` | API token management, webhook subscriptions |
specs/09-IMPLEMENTATION-ROADMAP.md:908:Event types: `page_view`, `product_view`, `add_to_cart`, `remove_from_cart`, `checkout_started`, `checkout_completed`, `search`.
specs/09-IMPLEMENTATION-ROADMAP.md:915:- Calculates: orders_count, revenue_amount, aov_amount, visits_count, add_to_cart_count, checkout_started_count
specs/09-IMPLEMENTATION-ROADMAP.md:976:5. Manual smoke: visit storefront, navigate products, add to cart, checkout
specs/09-IMPLEMENTATION-ROADMAP.md:1019:| returns zero subtotal for empty cart | 0 lines | subtotal = 0 | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1028:| calculates full checkout totals end to end | 2 lines x 2499 = 4998, discount WELCOME10 (10%), shipping flat 499, tax 19% inclusive; pipeline: subtotal 4998 -> discount 499 -> discounted 4499 -> shipping 499 -> tax extracted from gross | total calculation matches expected value | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1036:**Setup:** Instantiate `DiscountService` with mock store and cart.
specs/09-IMPLEMENTATION-ROADMAP.md:1046:| enforces minimum purchase amount rule | discount with rules_json containing minimum_purchase: 5000; cart subtotal: 3000 | throws InvalidDiscountException with reason 'minimum_not_met' | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1047:| passes minimum purchase when cart meets threshold | discount with rules_json minimum_purchase: 5000; cart subtotal: 5000 | validation passes | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1082:| calculates a weight-based rate | rate type: weight, config: ranges 0-500g=499, 501-2000g=899; cart total weight: 750g | shipping = 899 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1083:| calculates a price-based rate | rate type: price, config: ranges 0-5000=799, 5001-999999=399; cart subtotal: 7500 | shipping = 399 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1084:| returns zero shipping when no items require shipping | all cart line variants have requires_shipping = false | shipping = 0 | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1096:| starts at version 1 | create new cart | cart_version = 1 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1097:| increments version on add line | add a line to cart | cart_version = 2 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1098:| increments version on update quantity | update line quantity | cart_version incremented by 1 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1099:| increments version on remove line | remove a line | cart_version incremented by 1 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1100:| detects version mismatch | cart at version 3, client sends expected_version 2 | CartVersionMismatchException thrown | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1185:| merges guest cart into customer cart on login | guest has cart with variant A qty 2; customer has cart with variant A qty 1 and variant B qty 3; login | customer cart has variant A qty 3, variant B qty 3; guest cart abandoned | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1194:| authenticates API request with valid token | GET /api/admin/v1/stores/{storeId}/products with Bearer token | 200 response | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1195:| rejects API request with invalid token | GET /api/admin/v1/stores/{storeId}/products with fake token | 401 response | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1197:| revokes a token | create token, then delete it; make API request with revoked token | 401 response | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1282:| creates a cart for the current store | create cart | cart in DB with store_id, currency, version=1, status=active | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1283:| adds a line item to the cart | active product with variant priced at 2500; add to cart qty 2 | cart_line created, unit_price=2500, subtotal=5000, total=5000 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1284:| increments quantity when adding an existing variant | cart already has variant A qty 1; add variant A qty 2 | single line with qty 3, subtotal=7500 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1285:| rejects add when product is not active | product status = draft; attempt to add variant to cart | exception thrown | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1288:| updates line quantity | cart with variant A qty 2; update to qty 5 | line quantity=5, amounts recalculated | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1289:| removes a line when quantity set to zero | update line quantity to 0 | line removed from cart | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1290:| removes a specific line item | cart with 2 lines, remove first | 1 line remains | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1291:| increments cart version on every mutation | create cart (v1), add line (v2), update qty (v3), remove line (v4) | cart_version = 4 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1292:| returns cart via session for guest users | create cart, store ID in session; get cart for session | same cart returned | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1293:| merges guest cart into customer cart on login | guest cart: variant A qty 2; customer cart: variant A qty 1, variant B qty 3; merge | customer cart has A qty 3, B qty 3; guest cart status=abandoned | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1301:| creates a cart via API | POST /api/storefront/v1/carts | 201 response, response contains cart data | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1302:| retrieves a cart via API | create cart, then GET /api/storefront/v1/carts/{id} | 200 response, cart data with lines and totals | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1303:| adds a line via API | POST /api/storefront/v1/carts/{id}/lines with variant_id and quantity | 200 response, line added to cart | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1304:| updates line quantity via API | PUT /api/storefront/v1/carts/{id}/lines/{lineId} with quantity | 200 response, quantity updated | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1305:| removes a line via API | DELETE /api/storefront/v1/carts/{id}/lines/{lineId} | 200 response, line removed | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1306:| returns 404 for nonexistent cart | GET /api/storefront/v1/carts/nonexistent | 404 response | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1307:| returns 409 on version mismatch | cart at version 3; PUT with expected_version=2 | 409 Conflict with current cart state | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1316:| creates a checkout from a cart | cart with 2 lines; create checkout | checkout in DB, status=started, linked to cart | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1317:| completes full checkout happy path | create cart -> add lines -> create checkout -> set address -> select shipping -> begin payment -> complete | order created, cart status=converted, inventory committed | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1318:| rejects checkout for empty cart | cart with 0 lines; create checkout | validation error | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1319:| expires checkout after timeout | checkout with payment_selected status, expires_at in the past; run ExpireAbandonedCheckouts job | status=expired, inventory released | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1320:| prevents duplicate orders from same checkout | complete checkout once (creates order); call completeCheckout again | no duplicate order created, same order returned | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1344:| calculates correct totals for a simple checkout | 1 item: 2500 x 2 = 5000, no discount, flat shipping 499, 19% tax exclusive | subtotal=5000, shipping=499, tax=1044 (19% of 5499), total=6543 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1346:| stores pricing snapshot in totals_json | complete pricing calculation | checkout.totals_json contains all fields | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1356:| applies a valid percent discount code at checkout | discount: 10%, code "SAVE10"; apply to checkout with subtotal 5000 | discount = 500 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1357:| applies a valid fixed discount code at checkout | discount: 500 fixed, code "5OFF" | discount = 500 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1359:| rejects expired discount at checkout | discount ended yesterday | error returned | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1360:| increments usage count on order completion | discount usage_count=5; complete checkout with discount | usage_count=6 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1361:| handles free shipping discount at checkout | apply free_shipping discount | shipping = 0 in totals | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1372:| calculates weight-based rate correctly | cart total weight 750g, rate range 501-2000g = 899 | shipping = 899 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1381:| calculates exclusive tax correctly at checkout | tax mode: manual, rate: 1900, prices_include_tax: false; discounted subtotal + shipping = 5499 | tax = 1044 (rounded) | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1382:| extracts inclusive tax correctly at checkout | tax mode: manual, rate: 1900, prices_include_tax: true | tax extracted from gross amounts | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1392:| creates an order from a completed checkout | complete checkout flow | order in DB with correct amounts, status=pending | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1394:| creates order lines with snapshots | order from checkout with 2 items | order_lines have title_snapshot, sku_snapshot | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1396:| marks cart as converted | create order from checkout | cart status = converted | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1399:| links order to customer when authenticated | checkout by logged-in customer | order.customer_id set | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1400:| sets email from checkout on the order | guest checkout with email "test@example.com" | order.email = "test@example.com" | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1441:| charges credit card with success card number | checkout with card 4242424242424242 | PaymentResult success=true, status=captured | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1442:| declines credit card with decline card number | checkout with card 4000000000000002 | PaymentResult success=false, errorCode=card_declined | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1443:| returns insufficient funds for that card number | checkout with card 4000000000009995 | PaymentResult success=false, errorCode=insufficient_funds | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1444:| charges PayPal successfully | checkout with payment_method=paypal | PaymentResult success=true, status=captured | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1445:| creates pending payment for bank transfer | checkout with payment_method=bank_transfer | PaymentResult success=true, status=pending | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1454:| processes credit card payment and creates order as paid | checkout with credit card | order created with financial_status=paid, inventory committed | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1455:| processes PayPal payment and creates order as paid | checkout with PayPal | order created with financial_status=paid, inventory committed | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1456:| processes bank transfer and creates order as pending | checkout with bank transfer | order created with financial_status=pending, inventory reserved but not committed | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1593:| tracks an add to cart event | POST event: type=add_to_cart, properties include product_id | event stored with properties | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1604:| aggregates daily metrics from raw events | create events: 5 page_views, 3 add_to_carts, 2 checkout_completed; run AggregateAnalytics job | analytics_daily row with correct counts | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1637:| creates a cart | POST /api/storefront/v1/carts | 201 response, body has cart id, version 1 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1638:| retrieves a cart with lines and totals | create cart with 2 lines; GET /api/storefront/v1/carts/{id} | 200 response, lines array has 2 items, totals calculated | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1639:| adds a line to the cart | POST /api/storefront/v1/carts/{id}/lines | 200 response, line added | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1640:| updates a line quantity | PUT /api/storefront/v1/carts/{id}/lines/{lineId} | 200 response, quantity updated | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1641:| removes a line | DELETE /api/storefront/v1/carts/{id}/lines/{lineId} | 200 response, line removed | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1644:| returns 404 for nonexistent cart | GET /api/storefront/v1/carts/999 | 404 response | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1652:| creates a checkout from a cart | POST /api/storefront/v1/checkouts with cart_id | 201 response, checkout status=started | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1653:| sets checkout address | PUT /api/storefront/v1/checkouts/{id}/address | 200 response, status=addressed | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1654:| selects a shipping method | PUT /api/storefront/v1/checkouts/{id}/shipping-method | 200 response, status=shipping_selected, available rates returned in response | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1655:| applies a discount code | POST /api/storefront/v1/checkouts/{id}/apply-discount with code | 200 response, discount applied in totals | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1656:| retrieves checkout with totals | GET /api/storefront/v1/checkouts/{id} | 200 response, includes totals_json fields | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1657:| selects a payment method | PUT /api/storefront/v1/checkouts/{id}/payment-method with method=credit_card | 200 response, status=payment_selected, payment_method stored | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1658:| completes checkout with credit card payment | POST /api/storefront/v1/checkouts/{id}/pay with card fields | 200 response, status=completed, order created | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1659:| rejects payment with declined card | POST /api/storefront/v1/checkouts/{id}/pay with magic decline number | 422 response, payment declined message | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1669:| creates a product via API | POST /api/admin/v1/stores/{storeId}/products with product data | 201 response, product in response | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1670:| updates a product via API | PUT /api/admin/v1/stores/{storeId}/products/{id} with updated data | 200 response, product updated | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1671:| deletes a draft product via API | DELETE /api/admin/v1/stores/{storeId}/products/{id} | 200 response | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1685:| creates a fulfillment via API | POST /api/admin/v1/stores/{storeId}/orders/{id}/fulfillments | 201 response, fulfillment created | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1686:| creates a refund via API | POST /api/admin/v1/stores/{storeId}/orders/{id}/refunds | 201 response, refund created | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1715:| `Cart` | Shopping cart entity |
specs/09-IMPLEMENTATION-ROADMAP.md:1716:| `CartLine` | Individual cart line item |
specs/09-IMPLEMENTATION-ROADMAP.md:1772:| `Admin/Developers/Index` | API token and webhook management |
specs/09-IMPLEMENTATION-ROADMAP.md:1782:| `Storefront/Cart/Show` | Full cart page |
specs/09-IMPLEMENTATION-ROADMAP.md:1783:| `Storefront/CartDrawer` | Slide-out cart drawer |
specs/09-IMPLEMENTATION-ROADMAP.md:1787:| `Storefront/Checkout/Show` | Multi-step checkout |
specs/09-IMPLEMENTATION-ROADMAP.md:1829:| `SetCheckoutAddressRequest` | Validate checkout address |
specs/09-IMPLEMENTATION-ROADMAP.md:1830:| `SetCheckoutShippingRequest` | Validate checkout shipping selection |
specs/09-IMPLEMENTATION-ROADMAP.md:1849:| `CleanupAbandonedCarts` | Mark old active carts as abandoned |
specs/09-IMPLEMENTATION-ROADMAP.md:1850:| `ExpireAbandonedCheckouts` | Expire timed-out checkouts, release inventory |
specs/09-IMPLEMENTATION-ROADMAP.md:1861:| `CheckoutCompleted` | Dispatched when checkout completes |
specs/09-IMPLEMENTATION-ROADMAP.md:1865:| `CartUpdated` | Dispatched when cart is mutated |
specs/09-IMPLEMENTATION-ROADMAP.md:1888:| `Auth/SanctumTokenTest` | API token feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1895:| `Cart/CartApiTest` | Cart API feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1921:| `Api/StorefrontCartApiTest` | Storefront cart API feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1922:| `Api/StorefrontCheckoutApiTest` | Storefront checkout API feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1923:| `Api/AdminProductApiTest` | Admin product API feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1924:| `Api/AdminOrderApiTest` | Admin order API feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1944:- Configure rate limiters: `login` (5/min per IP), `api.admin` (60/min per token), `api.storefront` (120/min per IP), `checkout` (10/min per session), `search` (30/min per IP), `analytics` (60/min per IP), `webhooks` (100/min per IP)
specs/09-IMPLEMENTATION-ROADMAP.md:1951:| `routes/web.php` | Admin auth routes, admin routes (prefix `/admin`), storefront routes, checkout routes, customer account routes |
specs/09-IMPLEMENTATION-ROADMAP.md:1952:| `routes/api.php` | Storefront API (prefix `/api/storefront/v1`), Admin API (prefix `/api/admin/v1`), Webhook receiver routes |
specs/09-IMPLEMENTATION-ROADMAP.md:1976:| `OrderService` | Order creation from checkout, numbering, inventory commit |
specs/09-IMPLEMENTATION-ROADMAP.md:1977:| `RefundService` | Refund creation, provider API, inventory restock |
specs/09-IMPLEMENTATION-ROADMAP.md:2035:11. Manual smoke: add a product to the cart, proceed through the full checkout flow


output:
Chunk ID: 197665
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2647
Output:
# API and Routing Specification

This document defines every route, endpoint, middleware stack, request/response schema, and rate limit for the e-commerce platform. The admin panel is built with Livewire v4 + Flux UI (server-rendered), while the storefront uses Blade + Livewire. REST API endpoints serve cart/checkout operations and programmatic admin access.

All monetary amounts are stored and transmitted in **minor units** (cents). All IDs are auto-incrementing integers. All monetary amounts are in minor units (cents).

---

## 1. Web Routes (Blade/Livewire Pages)

### 1.1 Admin Auth Routes

No middleware beyond the default `web` group. These routes handle admin login and logout.

| Method | Path | Description | Component / Action |
|--------|------|-------------|--------------------|
| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
| POST | `/admin/login` | Process admin login | Handled by Livewire action |
| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |

Rate limit: `login` tier (5 per minute per IP).

---

### 1.2 Admin Routes

**Prefix:** `/admin`
**Middleware:** `web`, `auth`, `verified`, `store.resolve`, `role.check`

All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).

> **Note:** The Create and Edit routes render the same Livewire form component (`ProductForm`, `OrderForm`, etc.) in create vs edit mode, following the shared-form pattern defined in Spec 03.

#### Dashboard

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin` | Dashboard with KPI tiles, orders chart, conversion funnel | `Admin\Dashboard` |

#### Products

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/products` | Product list - searchable, filterable by status, bulk actions | `Admin\Products\Index` |
| GET | `/admin/products/create` | Create product form | `Admin\Products\Create` |
| GET | `/admin/products/{product}/edit` | Edit product form with variants, media, options | `Admin\Products\Edit` |

#### Inventory

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/inventory` | Inventory management page | `Admin\Inventory\Index` |

#### Collections

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/collections` | Collection list | `Admin\Collections\Index` |
| GET | `/admin/collections/create` | Create collection form | `Admin\Collections\Create` |
| GET | `/admin/collections/{collection}/edit` | Edit collection form | `Admin\Collections\Edit` |

#### Orders

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/orders` | Order list with status filters | `Admin\Orders\Index` |
| GET | `/admin/orders/{order}` | Order detail with timeline, payments, fulfillments | `Admin\Orders\Show` |

#### Customers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/customers` | Customer list | `Admin\Customers\Index` |
| GET | `/admin/customers/{customer}` | Customer detail with order history, addresses | `Admin\Customers\Show` |

#### Discounts

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/discounts` | Discount list | `Admin\Discounts\Index` |
| GET | `/admin/discounts/create` | Create discount form with rules builder | `Admin\Discounts\Create` |
| GET | `/admin/discounts/{discount}/edit` | Edit discount form | `Admin\Discounts\Edit` |

#### Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/settings` | Store settings (tabs: General, Domains, Shipping, Taxes, Checkout, Notifications) | `Admin\Settings\Index` |
| GET | `/admin/settings/shipping` | Shipping zones and rates configuration | `Admin\Settings\Shipping` |
| GET | `/admin/settings/taxes` | Tax settings - mode, provider config, rates | `Admin\Settings\Taxes` |

#### Themes and Online Store

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/themes` | Theme management - cards with actions (Customize, Preview, Publish, Duplicate, Delete) | `Admin\Themes\Index` |
| GET | `/admin/themes/{theme}/editor` | Theme editor - left panel sections, center preview iframe, right panel settings | `Admin\Themes\Editor` |

#### Pages

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/pages` | Content pages list | `Admin\Pages\Index` |
| GET | `/admin/pages/create` | Create content page | `Admin\Pages\Create` |
| GET | `/admin/pages/{page}/edit` | Edit content page | `Admin\Pages\Edit` |

#### Navigation

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/navigation` | Navigation menus management | `Admin\Navigation\Index` |

#### Apps

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |

#### Developers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |

> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.

#### Analytics

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/analytics` | Analytics dashboard - sales, traffic, funnel, exports | `Admin\Analytics\Index` |

#### Search Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/search/settings` | Search settings - synonyms, stopwords, reindex | `Admin\Search\Settings` |

---

### 1.3 Storefront Routes

**Middleware:** `web`, `store.resolve`

These routes serve the public-facing storefront using Blade templates rendered through the active theme engine.

| Method | Path | Description | Component / View |
|--------|------|-------------|------------------|
| GET | `/` | Home page | `Storefront\Home` |
| GET | `/collections` | All collections listing | `Storefront\Collections\Index` |
| GET | `/collections/{handle}` | Single collection page with product grid and filters | `Storefront\Collections\Show` |
| GET | `/products/{handle}` | Product page - gallery, variant selector, add-to-cart | `Storefront\Products\Show` |
| GET | `/cart` | Full cart page - line items, quantity controls, discount input, totals | `Storefront\Cart\Show` |
| GET | `/search` | Search results page with filter panel, sort dropdown, product grid | `Storefront\Search\Index` |
| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |

#### Customer Account Routes

**Additional middleware:** `auth:customer`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account` | Customer account overview | `Storefront\Account\Dashboard` |
| GET | `/account/orders` | Order history list | `Storefront\Account\Orders\Index` |
| GET | `/account/orders/{orderNumber}` | Order detail | `Storefront\Account\Orders\Show` |
| GET | `/account/addresses` | Address book management | `Storefront\Account\Addresses\Index` |

#### Customer Auth Routes (no auth required)

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account/login` | Customer login page | `Storefront\Account\Auth\Login` |
| GET | `/account/register` | Customer registration page | `Storefront\Account\Auth\Register` |
| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
| POST | `/reset-password` | Reset password | Handled by Livewire action |

---

### 1.4 Checkout Routes

**Middleware:** `web`, `store.resolve`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/checkout/{checkoutId}` | Checkout page - stepper UI (Contact -> Shipping -> Payment) | `Storefront\Checkout\Show` |
| GET | `/checkout/{checkoutId}/confirmation` | Order confirmation page - order number, items, totals | `Storefront\Checkout\Confirmation` |

---

## 2. Storefront REST API

**Prefix:** `/api/storefront/v1`
**Middleware:** `store.resolve`, `throttle:api.storefront`
**Content-Type:** `application/json`

These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).

---

### 2.1 Cart Endpoints

#### POST /api/storefront/v1/carts

Create a new cart.

- **Authentication:** None (guest) or session-based (logged-in customer)
- **Rate limit:** `api.storefront` (120/min per IP)

**Request body:**

```json
{
  "currency": "EUR"
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `currency` | string | No | Valid ISO 4217 code. Defaults to store's `default_currency`. |

**Success response:** `201 Created`

```json
{
  "id": 42,
  "store_id": 1,
  "customer_id": null,
  "currency": "EUR",
  "cart_version": 1,
  "status": "active",
  "lines": [],
  "totals": {
    "subtotal": 0,
    "discount": 0,
    "total": 0,
    "currency": "EUR"
  },
  "created_at": "2026-02-11T10:00:00Z",
  "updated_at": "2026-02-11T10:00:00Z"
}
```

**Error responses:**
- `422` - Validation error (invalid currency)

---

#### GET /api/storefront/v1/carts/{cartId}

Retrieve a cart with all lines and computed totals.

- **Authentication:** None (cart ID acts as token)
- **Rate limit:** `api.storefront` (120/min per IP)