rg -n "Open Issues|Completion Summary|OAuth|Automated browser|CHECK|password reset|route:list" specs/progress.md specs/02-API-ROUTES.md specs/08-PLAYWRIGHT-E2E-PLAN.md specs/09-IMPLEMENTATION-ROADMAP.md


/bin/zsh
-lc
sed -n '360,400p' specs/progress.md

- Checkout and notification settings persist in `store_settings.settings_json`; `bank_transfer_cancel_days` remains a root-level key because the existing cancellation job already consumes it from that location.
- Admin navigation persists nested ordered menu items with Livewire `wire:sort`, up/down sibling controls, and a parent selector; children are saved under their selected top-level parent with per-parent positions.
- The theme editor uses the `ThemeSettingsService` default settings shape as the editable schema and writes back to `theme_settings.settings_json`.
- Theme files are seeded and edited through local disk storage at `theme_files.storage_key`; saving a file updates the stored contents plus `sha256` and `byte_size` metadata.
- Theme duplication copies each file's local disk contents to a new storage key for the copied theme instead of sharing the original file storage path.
- Search uses a local SQLite FTS5 virtual table (`products_fts`) with `SearchService` as the only query/sync boundary; product observer events keep the index synchronized for create/update/delete.
- Search settings apply at query time: per-store stop words are removed, synonym groups expand into sanitized SQLite FTS5 `OR` terms, and terms are joined with explicit `AND`; the admin page can rebuild the per-store index synchronously for this self-contained app.
- Search API rate limiting is registered as `search` (30/minute per IP), analytics ingestion uses the `analytics` limiter (60/minute per IP), and a `webhooks` limiter is registered for future inbound app endpoints.
- Analytics ingestion stores raw events with client-event deduplication and keeps aggregation separate in `analytics_daily`; admin analytics reads pre-aggregated daily metrics for KPIs and raw events for referrers.
- Seeded analytics metrics are deterministic demo data and are not tied to seeded orders because order/payment seed data remains intentionally absent.
- The admin analytics CSV export is generated as a data URL for the self-contained local app rather than creating persistent export files/jobs.
- OAuth/Passport app authorization remains deferred per the roadmap; the developer token UI uses the existing app installation and `oauth_tokens` schema as a self-contained local token store, but it is not Sanctum/Passport authentication yet.
- Admin order API routes use the `admin.api` middleware, which accepts either the existing session-authenticated admin user or a hashed `oauth_tokens` bearer token scoped to the route store; token requests require `read-orders` for GET routes and `write-orders` for refund/fulfillment mutations and update `last_used_at` on successful use.
- Outbound webhook delivery jobs are forced onto the database queue and `webhooks` queue name so domain events enqueue delivery work instead of making external HTTP requests inline when the default queue connection is `sync`.
- Webhook signatures follow the security spec message shape of `{timestamp}.{json_body}` with HMAC-SHA256, while the delivery payload is wrapped with `id`, `api_version`, `event_type`, `store_id`, `occurred_at`, and `data`.
- Webhook circuit breaker state is derived from the latest five delivery rows for a subscription because the schema does not include a dedicated consecutive-failure counter column.
- Customer password resets use a custom store-scoped service instead of Laravel's stock password broker token repository because the `customer_password_reset_tokens` table includes `store_id` in the primary key and the stock repository does not write tenant columns.
- Customer password reset pages are routed under `/account/forgot-password` and `/account/reset-password/{token}` so the existing Fortify starter/admin root routes (`/forgot-password`, `/reset-password/{token}`) remain intact.
- Product media uploads use Livewire temporary file uploads on the existing product form, create `ProductMedia` rows in `processing` status, and dispatch the existing `ProcessMediaUpload` job instead of adding a second media-processing path.
- Resource policies now type concrete model parameters for Product, Collection, Customer, Discount, Order, Fulfillment, Refund, Page, and Theme actions; `ChecksStoreRole::storeIdForModel()` stays generic because it is the shared store-id extraction helper.

## Open Issues

- Customer password reset is implemented under `/account/forgot-password` and `/account/reset-password/{token}`; the exact spec root paths remain occupied by the existing Fortify starter/admin reset routes.
- `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.
- OAuth/Passport app API endpoints and broader admin REST endpoints outside order management are still missing; outbound webhook subscription management is implemented through the admin UI only, as specified for the initial implementation.
- 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.

## Completion Summary

Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer order views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, order API surfaces, and outbound webhook delivery foundations are implemented, with known auth/token route compatibility, OAuth/app API, browser-test, and database CHECK constraint gaps tracked above.


Output:
Chunk ID: ec8297
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 0
Output:


rg -n "Open Issues|Completion Summary|OAuth|Automated browser|CHECK|password reset|route:list" specs/progress.md specs/02-API-ROUTES.md specs/08-PLAYWRIGHT-E2E-PLAN.md specs/09-IMPLEMENTATION-ROADMAP.md

rg -n 'Open Issues|Completion Summary|OAuth|Automated browser|CHECK|password reset|route:list' specs/progress.md specs/02-API-ROUTES.md specs/08-PLAYWRIGHT-E2E-PLAN.md specs/09-IMPLEMENTATION-ROADMAP.md

Question: Open Issues|Completion Summary|OAuth|Automated browser|CHECK|password reset|route:list

specs/progress.md:30:| Routes/API | `specs/02-API-ROUTES.md` | partial | Phase 1 auth routes plus catalog/cart/order/customer/discount/content/settings/search/analytics/apps/developers UI routes exist: `/`, `/collections`, `/collections/{handle}`, `/products/{handle}`, `/cart`, `/checkout`, `/checkout/confirmation/{order}`, `/search`, `/pages/{handle}`, `/account/login`, `/account/register`, `/account/forgot-password`, `/account/reset-password/{token}`, `/account`, `/account/orders/{order}`, `/admin/products`, `/admin/products/create`, `/admin/products/{product}/edit`, `/admin/collections`, `/admin/collections/create`, `/admin/collections/{collection}/edit`, `/admin/inventory`, `/admin/orders`, `/admin/orders/{order}`, `/admin/customers`, `/admin/customers/{customer}`, `/admin/discounts`, `/admin/discounts/create`, `/admin/discounts/{discount}/edit`, `/admin/pages`, `/admin/pages/create`, `/admin/pages/{page}/edit`, `/admin/navigation`, `/admin/themes`, `/admin/themes/{theme}/editor`, `/admin/settings`, `/admin/settings/shipping`, `/admin/settings/taxes`, `/admin/settings/checkout`, `/admin/settings/notifications`, `/admin/search/settings`, `/admin/analytics`, `/admin/apps`, `/admin/apps/{installation}`, and `/admin/developers`. Storefront REST API now includes cart line CRUD, checkout address/shipping/discount/payment-method selection, checkout payment completion, token-gated order lookup, product search, search suggestions, and analytics event ingestion under `/api/storefront/v1`. Admin REST API now includes store-scoped order list/detail, fulfillment creation, and refund creation under `/api/admin/v1/stores/{store}` with session or scoped bearer-token authentication. OAuth/app API endpoints and broader admin REST surfaces are still missing. |
specs/progress.md:32:| Storefront UI | `specs/04-STOREFRONT-UI.md` | partial | Storefront layout, home, collections index/detail, product detail, SQLite-backed search with filters/sort/pagination, breadcrumbs, price display, product cards, DB-backed pages, seeded navigation menus with one-level dropdown children, cached theme settings, product add-to-cart persistence, cart drawer, cart page with discount and shipping estimates, checkout address/shipping/discount/payment-selection/order-submission UI, order confirmation, customer password reset forms, and customer order list/detail views render seeded and runtime data. |
specs/progress.md:34:| Auth/security | `specs/06-AUTH-AND-SECURITY.md` | partial | Admin Livewire login/logout, customer guard/provider, customer login/register, customer password reset with store-scoped hashed tokens, reset-link throttling, generic reset responses, login rate limiting, session hardening, concrete resource policies, encrypted app client/webhook secrets, one-time display developer token generation, and a compatible hashed bearer-token admin API middleware with store/ability enforcement are implemented. |
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 with checkout and notification defaults, 2 apps, 4 app installations, 4 OAuth clients, 4 OAuth tokens, 8 webhook subscriptions, 2 search settings rows, 25 indexed products, 60 analytics_daily rows, 210 analytics_events rows, 6 collections, 127 variants, 127 inventory rows, 206 variant option pivots, 2 themes, 6 theme files with local disk contents and metadata, 2 theme settings rows, 6 pages, 4 navigation menus, 21 navigation items including 6 child 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 on `shop.test` and `acme-fashion.test`, product detail, add-to-cart, cart drawer/page, checkout through order completion, order confirmation, customer order list/detail, SQLite-backed search and search API JSON, analytics event ingestion API, DB-backed content pages, seeded nested navigation dropdowns, admin dashboard on desktop/mobile, admin analytics, admin app list/detail, admin developers token/webhook flows, admin product/collection/inventory/order/customer/discount/content/navigation/settings/theme/search pages, admin checkout/notification settings pages, admin product media section rendering, admin analytics CSV export, admin customer address creation, admin discount creation/editing/filtering, admin page creation, admin nested navigation rendering/item creation/save, admin theme editor preview, admin theme file list/editor, admin search reindexing, admin fulfillment creation/shipping/delivery, and auth flows without console warnings/errors on successful pages. Automated browser tests are still missing. |
specs/progress.md:49:- 2026-05-03: `php artisan route:list --path=admin` showed `/admin`, `…4219 chars truncated…under `/account/*` and existing Fortify reset routes at the root paths remain registered separately.
specs/progress.md:238:- 2026-05-04: `npm run build` passed after the customer password reset Blade changes.
specs/progress.md:270:- 2026-05-04: `php artisan route:list --name=admin.settings` confirmed 5 settings routes, including `/admin/settings/checkout` and `/admin/settings/notifications`.
specs/progress.md:305:- 2026-05-04: `php artisan route:list --path=api/admin/v1 --except-vendor` confirmed the four admin order API routes remain registered.
specs/progress.md:371:- OAuth/Passport app authorization remains deferred per the roadmap; the developer token UI uses the existing app installation and `oauth_tokens` schema as a self-contained local token store, but it is not Sanctum/Passport authentication yet.
specs/progress.md:376:- Customer password resets use a custom store-scoped service instead of Laravel's stock password broker token repository because the `customer_password_reset_tokens` table includes `store_id` in the primary key and the stock repository does not write tenant columns.
specs/progress.md:377:- Customer password reset pages are routed under `/account/forgot-password` and `/account/reset-password/{token}` so the existing Fortify starter/admin root routes (`/forgot-password`, `/reset-password/{token}`) remain intact.
specs/progress.md:381:## Open Issues
specs/progress.md:383:- Customer password reset is implemented under `/account/forgot-password` and `/account/reset-password/{token}`; the exact spec root paths remain occupied by the existing Fortify starter/admin reset routes.
specs/progress.md:384:- `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.
specs/progress.md:385:- OAuth/Passport app API endpoints and broader admin REST endpoints outside order management are still missing; outbound webhook subscription management is implemented through the admin UI only, as specified for the initial implementation.
specs/progress.md:386:- Automated browser tests from Spec 08 are still missing; current browser coverage is manual Playwright MCP smoke verification.
specs/progress.md:387:- 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.
specs/progress.md:389:## Completion Summary
specs/progress.md:391:Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer order views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, order API surfaces, and outbound webhook delivery foundations are implemented, with known auth/token route compatibility, OAuth/app API, browser-test, and database CHECK constraint gaps tracked above.
specs/02-API-ROUTES.md:21:| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
specs/02-API-ROUTES.md:2899:> **Note:** No inbound webhook endpoints are needed. All payment processing happens in-process via the Mock PSP -- there are no external payment provider callbacks. The webhook infrastructure defined in this section is reserved for **outbound** app webhooks (see Section 5, OAuth / App Ecosystem).
specs/02-API-ROUTES.md:2903:## 5. OAuth Endpoints (App Ecosystem)
specs/02-API-ROUTES.md:2905:> **Scope note:** OAuth and app ecosystem endpoints are deferred for initial implementation. The route structure below is defined for future use but implementations should return 501 Not Implemented.
specs/02-API-ROUTES.md:2907:These endpoints implement OAuth2 authorization for third-party apps, powered by Laravel Passport.
specs/02-API-ROUTES.md:2921:| `client_id` | string | Yes | The app's OAuth client ID |
specs/02-API-ROUTES.md:2979:Third-party apps use OAuth tokens to access store data via scoped endpoints.
specs/09-IMPLEMENTATION-ROADMAP.md:24:| OAuth (apps) | Passport | Deferred/Stubbed | Simplifies initial build, can add later |
specs/09-IMPLEMENTATION-ROADMAP.md:96:| `config/auth.php` | Passwords | Add `customers` password broker | Customer password reset |
specs/09-IMPLEMENTATION-ROADMAP.md:111:All monetary amounts stored as INTEGER in minor units (cents). All enums stored as TEXT with CHECK constraints. All timestamps as TEXT (ISO-8601) via Laravel.
specs/09-IMPLEMENTATION-ROADMAP.md:1732:| `OauthClient` | OAuth client for app |
specs/09-IMPLEMENTATION-ROADMAP.md:1733:| `OauthToken` | OAuth token for installation |


Output:
Chunk ID: 052a9f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3555
Output:
specs/progress.md:30:| Routes/API | `specs/02-API-ROUTES.md` | partial | Phase 1 auth routes plus catalog/cart/order/customer/discount/content/settings/search/analytics/apps/developers UI routes exist: `/`, `/collections`, `/collections/{handle}`, `/products/{handle}`, `/cart`, `/checkout`, `/checkout/confirmation/{order}`, `/search`, `/pages/{handle}`, `/account/login`, `/account/register`, `/account/forgot-password`, `/account/reset-password/{token}`, `/account`, `/account/orders/{order}`, `/admin/products`, `/admin/products/create`, `/admin/products/{product}/edit`, `/admin/collections`, `/admin/collections/create`, `/admin/collections/{collection}/edit`, `/admin/inventory`, `/admin/orders`, `/admin/orders/{order}`, `/admin/customers`, `/admin/customers/{customer}`, `/admin/discounts`, `/admin/discounts/create`, `/admin/discounts/{discount}/edit`, `/admin/pages`, `/admin/pages/create`, `/admin/pages/{page}/edit`, `/admin/navigation`, `/admin/themes`, `/admin/themes/{theme}/editor`, `/admin/settings`, `/admin/settings/shipping`, `/admin/settings/taxes`, `/admin/settings/checkout`, `/admin/settings/notifications`, `/admin/search/settings`, `/admin/analytics`, `/admin/apps`, `/admin/apps/{installation}`, and `/admin/developers`. Storefront REST API now includes cart line CRUD, checkout address/shipping/discount/payment-method selection, checkout payment completion, token-gated order lookup, product search, search suggestions, and analytics event ingestion under `/api/storefront/v1`. Admin REST API now includes store-scoped order list/detail, fulfillment creation, and refund creation under `/api/admin/v1/stores/{store}` with session or scoped bearer-token authentication. OAuth/app API endpoints and broader admin REST surfaces are still missing. |
specs/progress.md:32:| Storefront UI | `specs/04-STOREFRONT-UI.md` | partial | Storefront layout, home, collections index/detail, product detail, SQLite-backed search with filters/sort/pagination, breadcrumbs, price display, product cards, DB-backed pages, seeded navigation menus with one-level dropdown children, cached theme settings, product add-to-cart persistence, cart drawer, cart page with discount and shipping estimates, checkout address/shipping/discount/payment-selection/order-submission UI, order confirmation, customer password reset forms, and customer order list/detail views render seeded and runtime data. |
specs/progress.md:34:| Auth/security | `specs/06-AUTH-AND-SECURITY.md` | partial | Admin Livewire login/logout, customer guard/provider, customer login/register, customer password reset with store-scoped hashed tokens, reset-link throttling, generic reset responses, login rate limiting, session hardening, concrete resource policies, encrypted app client/webhook secrets, one-time display developer token generation, and a compatible hashed bearer-token admin API middleware with store/ability enforcement are implemented. |
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 with checkout and notification defaults, 2 apps, 4 app installations, 4 OAuth clients, 4 OAuth tokens, 8 webhook subscriptions, 2 search settings rows, 25 indexed products, 60 analytics_daily rows, 210 analytics_events rows, 6 collections, 127 variants, 127 inventory rows, 206 variant option pivots, 2 themes, 6 theme files with local disk contents and metadata, 2 theme settings rows, 6 pages, 4 navigation menus, 21 navigation items including 6 child 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 on `shop.test` and `acme-fashion.test`, product detail, add-to-cart, cart drawer/page, checkout through order completion, order confirmation, customer order list/detail, SQLite-backed search and search API JSON, analytics event ingestion API, DB-backed content pages, seeded nested navigation dropdowns, admin dashboard on desktop/mobile, admin analytics, admin app list/detail, admin developers token/webhook flows, admin product/collection/inventory/order/customer/discount/content/navigation/settings/theme/search pages, admin checkout/notification settings pages, admin product media section rendering, admin analytics CSV export, admin customer address creation, admin discount creation/editing/filtering, admin page creation, admin nested navigation rendering/item creation/save, admin theme editor preview, admin theme file list/editor, admin search reindexing, admin fulfillment creation/shipping/delivery, and auth flows without console warnings/errors on successful pages. Automated browser tests are still missing. |
specs/progress.md:49:- 2026-05-03: `php artisan route:list --path=admin` showed `/admin`, `/admin/login`, `/admin/logout`; `php artisan route:list --path=account` showed `/account`, `/account/login`, `/account/register`.
specs/progress.md:59:- 2026-05-03: `php artisan route:list --path=admin` showed 10 admin routes including product, collection, and inventory catalog pages. `php artisan route:list --path=collections`, `--path=products`, `--path=search`, and `--path=pages` showed storefront catalog/content routes.
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:94:- 2026-05-03: `php artisan route:list --path=api/storefront/v1` confirmed 12 storefront API routes for carts, cart lines, checkouts, checkout address, shipping method, discount apply/remove, and payment method selection.
specs/progress.md:134:- 2026-05-04: `php artisan route:list --path=admin/orders` confirmed `admin.orders.index` and `admin.orders.show` Livewire routes.
specs/progress.md:146:- 2026-05-04: `php artisan route:list --path=api/storefront/v1 --except-vendor` showed 14 storefront API routes including checkout payment completion and token-gated order lookup.
specs/progress.md:147:- 2026-05-04: `php artisan route:list --path=api/admin/v1 --except-vendor` showed 4 admin order API routes for order list/detail, fulfillment creation, and refund creation.
specs/progress.md:155:- 2026-05-04: `php artisan route:list --name=admin.dashboard` confirmed `/admin` is now the Livewire admin dashboard route.
specs/progress.md:166:- 2026-05-04: `php artisan route:list --path=admin/customers` confirmed `admin.customers.index` and `admin.customers.show` Livewire routes.
specs/progress.md:177:- 2026-05-04: `php artisan route:list --name=admin.discounts` confirmed `admin.discounts.index`, `admin.discounts.create`, and `admin.discounts.edit` Livewire routes.
specs/progress.md:189:- 2026-05-04: `php artisan route:list --name=admin.pages`, `--name=admin.navigation`, `--name=admin.themes`, and `--name=admin.settings` confirmed 9 new Livewire admin routes for pages, navigation, themes, and settings.
specs/progress.md:201:- 2026-05-04: `php artisan route:list --path=api/storefront/v1/search --except-vendor` confirmed `GET /api/storefront/v1/search` and `GET /api/storefront/v1/search/suggest`; `php artisan route:list --path=admin/search` confirmed `GET /admin/search/settings`.
specs/progress.md:211:- 2026-05-04: `php artisan route:list --path=api/storefront/v1/analytics --except-vendor` confirmed `POST /api/storefront/v1/analytics/events`; `php artisan route:list --path=admin/analytics` confirmed `GET /admin/analytics`.
specs/progress.md:224:- 2026-05-04: `php artisan route:list --path=admin/apps` confirmed `/admin/apps` and `/admin/apps/{installation}`; `php artisan route:list --path=admin/developers` confirmed `/admin/developers`; `php artisan event:list` confirmed `DispatchWebhooks` registered for order, refund, fulfillment, and product status events.
specs/progress.md:231:- 2026-05-04: `mcp__laravel_boost__.search_docs` consulted Laravel 12 password reset, notification, validation, and rate limiting docs plus Livewire 4, Flux UI, and Pest 4 docs before the customer password reset gap fix.
specs/progress.md:232:- 2026-05-04: `php artisan make:class Services/CustomerPasswordResetService --no-interaction`, `php artisan make:notification CustomerResetPassword --no-interaction`, `php artisan make:livewire Storefront/Account/Auth/ForgotPassword --class --no-interaction`, `php artisan make:livewire Storefront/Account/Auth/ResetPassword --class --no-interaction`, and `php artisan make:test Auth/CustomerPasswordResetTest --pest --no-interaction` created the customer password reset service, notification, Livewire pages, and tests.
specs/progress.md:233:- 2026-05-04: `vendor/bin/pint --dirty --format agent` fixed formatting after the customer password reset changes.
specs/progress.md:236:- 2026-05-04: `php artisan test --compact` passed after the customer password reset changes: 187 tests, 940 assertions.
specs/progress.md:237:- 2026-05-04: `php artisan route:list | rg "account/(forgot|reset)| forgot-password | reset-password"` confirmed customer reset routes under `/account/*` and existing Fortify reset routes at the root paths remain registered separately.
specs/progress.md:238:- 2026-05-04: `npm run build` passed after the customer password reset Blade changes.
specs/progress.md:270:- 2026-05-04: `php artisan route:list --name=admin.settings` confirmed 5 settings routes, including `/admin/settings/checkout` and `/admin/settings/notifications`.
specs/progress.md:305:- 2026-05-04: `php artisan route:list --path=api/admin/v1 --except-vendor` confirmed the four admin order API routes remain registered.
specs/progress.md:371:- OAuth/Passport app authorization remains deferred per the roadmap; the developer token UI uses the existing app installation and `oauth_tokens` schema as a self-contained local token store, but it is not Sanctum/Passport authentication yet.
specs/progress.md:376:- Customer password resets use a custom store-scoped service instead of Laravel's stock password broker token repository because the `customer_password_reset_tokens` table includes `store_id` in the primary key and the stock repository does not write tenant columns.
specs/progress.md:377:- Customer password reset pages are routed under `/account/forgot-password` and `/account/reset-password/{token}` so the existing Fortify starter/admin root routes (`/forgot-password`, `/reset-password/{token}`) remain intact.
specs/progress.md:381:## Open Issues
specs/progress.md:383:- Customer password reset is implemented under `/account/forgot-password` and `/account/reset-password/{token}`; the exact spec root paths remain occupied by the existing Fortify starter/admin reset routes.
specs/progress.md:384:- `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.
specs/progress.md:385:- OAuth/Passport app API endpoints and broader admin REST endpoints outside order management are still missing; outbound webhook subscription management is implemented through the admin UI only, as specified for the initial implementation.
specs/progress.md:386:- Automated browser tests from Spec 08 are still missing; current browser coverage is manual Playwright MCP smoke verification.
specs/progress.md:387:- 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.
specs/progress.md:389:## Completion Summary
specs/progress.md:391:Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer order views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, order API surfaces, and outbound webhook delivery foundations are implemented, with known auth/token route compatibility, OAuth/app API, browser-test, and database CHECK constraint gaps tracked above.
specs/02-API-ROUTES.md:21:| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
specs/02-API-ROUTES.md:2899:> **Note:** No inbound webhook endpoints are needed. All payment processing happens in-process via the Mock PSP -- there are no external payment provider callbacks. The webhook infrastructure defined in this section is reserved for **outbound** app webhooks (see Section 5, OAuth / App Ecosystem).
specs/02-API-ROUTES.md:2903:## 5. OAuth Endpoints (App Ecosystem)
specs/02-API-ROUTES.md:2905:> **Scope note:** OAuth and app ecosystem endpoints are deferred for initial implementation. The route structure below is defined for future use but implementations should return 501 Not Implemented.
specs/02-API-ROUTES.md:2907:These endpoints implement OAuth2 authorization for third-party apps, powered by Laravel Passport.
specs/02-API-ROUTES.md:2921:| `client_id` | string | Yes | The app's OAuth client ID |
specs/02-API-ROUTES.md:2979:Third-party apps use OAuth tokens to access store data via scoped endpoints.
specs/09-IMPLEMENTATION-ROADMAP.md:24:| OAuth (apps) | Passport | Deferred/Stubbed | Simplifies initial build, can add later |
specs/09-IMPLEMENTATION-ROADMAP.md:96:| `config/auth.php` | Passwords | Add `customers` password broker | Customer password reset |
specs/09-IMPLEMENTATION-ROADMAP.md:111:All monetary amounts stored as INTEGER in minor units (cents). All enums stored as TEXT with CHECK constraints. All timestamps as TEXT (ISO-8601) via Laravel.
specs/09-IMPLEMENTATION-ROADMAP.md:1732:| `OauthClient` | OAuth client for app |
specs/09-IMPLEMENTATION-ROADMAP.md:1733:| `OauthToken` | OAuth token for installation |


Output:
Chunk ID: 420195
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1522
Output:
- Checkout and notification settings persist in `store_settings.settings_json`; `bank_transfer_cancel_days` remains a root-level key because the existing cancellation job already consumes it from that location.
- Admin navigation persists nested ordered menu items with Livewire `wire:sort`, up/down sibling controls, and a parent selector; children are saved under their selected top-level parent with per-parent positions.
- The theme editor uses the `ThemeSettingsService` default settings shape as the editable schema and writes back to `theme_settings.settings_json`.
- Theme files are seeded and edited through local disk storage at `theme_files.storage_key`; saving a file updates the stored contents plus `sha256` and `byte_size` metadata.
- Theme duplication copies each file's local disk contents to a new storage key for the copied theme instead of sharing the original file storage path.
- Search uses a local SQLite FTS5 virtual table (`products_fts`) with `SearchService` as the only query/sync boundary; product observer events keep the index synchronized for create/update/delete.
- Search settings apply at query time: per-store stop words are removed, synonym groups expand into sanitized SQLite FTS5 `OR` terms, and terms are joined with explicit `AND`; the admin page can rebuild the per-store index synchronously for this self-contained app.
- Search API rate limiting is registered as `search` (30/minute per IP), analytics ingestion uses the `analytics` limiter (60/minute per IP), and a `webhooks` limiter is registered for future inbound app endpoints.
- Analytics ingestion stores raw events with client-event deduplication and keeps aggregation separate in `analytics_daily`; admin analytics reads pre-aggregated daily metrics for KPIs and raw events for referrers.
- Seeded analytics metrics are deterministic demo data and are not tied to seeded orders because order/payment seed data remains intentionally absent.
- The admin analytics CSV export is generated as a data URL for the self-contained local app rather than creating persistent export files/jobs.
- OAuth/Passport app authorization remains deferred per the roadmap; the developer token UI uses the existing app installation and `oauth_tokens` schema as a self-contained local token store, but it is not Sanctum/Passport authentication yet.
- Admin order API routes use the `admin.api` middleware, which accepts either the existing session-authenticated admin user or a hashed `oauth_tokens` bearer token scoped to the route store; token requests require `read-orders` for GET routes and `write-orders` for refund/fulfillment mutations and update `last_used_at` on successful use.
- Outbound webhook delivery jobs are forced onto the database queue and `webhooks` queue name so domain events enqueue delivery work instead of making external HTTP requests inline when the default queue connection is `sync`.
- Webhook signatures follow the security spec message shape of `{timestamp}.{json_body}` with HMAC-SHA256, while the delivery payload is wrapped with `id`, `api_version`, `event_type`, `store_id`, `occurred_at`, and `data`.
- Webhook circuit breaker state is derived from the latest five delivery rows for a subscription because the schema does not include a dedicated consecutive-failure counter column.
- Customer password resets use a custom store-scoped service instead of Laravel's stock password broker token repository because the `customer_password_reset_tokens` table includes `store_id` in the primary key and the stock repository does not write tenant columns.
- Customer password reset pages are routed under `/account/forgot-password` and `/account/reset-password/{token}` so the existing Fortify starter/admin root routes (`/forgot-password`, `/reset-password/{token}`) remain intact.
- Product media uploads use Livewire temporary file uploads on the existing product form, create `ProductMedia` rows in `processing` status, and dispatch the existing `ProcessMediaUpload` job instead of adding a second media-processing path.
- Resource policies now type concrete model parameters for Product, Collection, Customer, Discount, Order, Fulfillment, Refund, Page, and Theme actions; `ChecksStoreRole::storeIdForModel()` stays generic because it is the shared store-id extraction helper.

## Open Issues

- Customer password reset is implemented under `/account/forgot-password` and `/account/reset-password/{token}`; the exact spec root paths remain occupied by the existing Fortify starter/admin reset routes.
- `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.
- OAuth/Passport app API endpoints and broader admin REST endpoints outside order management are still missing; outbound webhook subscription management is implemented through the admin UI only, as specified for the initial implementation.
- 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.

## Completion Summary

Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer order views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, order API surfaces, and outbound webhook delivery foundations are implemented, with known auth/token route compatibility, OAuth/app API, browser-test, and database CHECK constraint gaps tracked above.


sed -n '2900,3005p' specs/02-API-ROUTES.md


rg -n "forgot-password|reset-password|Fortify|PasswordReset|Route::.*forgot|account/forgot" routes app config bootstrap resources tests -g '!vendor'


sed -n '2900,3005p' specs/02-API-ROUTES.md

---

## 5. OAuth Endpoints (App Ecosystem)

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

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

---

### GET /oauth/authorize

Display the authorization prompt to the merchant.

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

**Query parameters:**

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

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

---

### POST /oauth/token

Exchange an authorization code for access and refresh tokens.

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

**Authorization code exchange request:**

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

**Success response:** `200 OK`

```json
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ...",
  "refresh_token": "def..."
}
```

**Error responses:**
- `400` - Invalid grant (code already used, expired, etc.)
- `401` - Invalid client credentials

**Refresh token request:**

```json
{
  "grant_type": "refresh_token",
  "client_id": "app_client_abc123",
  "client_secret": "secret_xyz",
  "refresh_token": "def..."
}
```

---

### App API Endpoints

Third-party apps use OAuth tokens to access store data via scoped endpoints.

| Method | Path | Scope Required | Description |
|--------|------|----------------|-------------|
| GET | `/api/apps/v1/stores/{storeId}/products` | `read-products` | List products (same schema as admin API) |
| GET | `/api/apps/v1/stores/{storeId}/orders` | `read-orders` | List orders |
| GET | `/api/apps/v1/stores/{storeId}/customers` | `read-customers` | List customers |

Token scopes are enforced on every endpoint. Requests with insufficient scopes return `403 Forbidden`.

---

## 6. Middleware Stack

### 6.1 Route Group Middleware

#### Web (default)

Applied to all web routes via the `web` middleware group:

| Middleware | Purpose |
|-----------|---------|
| `EncryptCookies` | Encrypt/decrypt cookies |
| `AddQueuedCookiesToResponse` | Attach queued cookies |
| `StartSession` | Session handling |
| `ShareErrorsFromSession` | Flash validation errors |
| `VerifyCsrfToken` | CSRF protection on POST/PUT/PATCH/DELETE |


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

# 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 |
|--------|------|-------------|-----------|


Output:
Chunk ID: b3ea67
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1386
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 |
|--------|------|-------------|-----------|


Output:
Chunk ID: 4d733f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 736
Output:

---

## 5. OAuth Endpoints (App Ecosystem)

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

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

---

### GET /oauth/authorize

Display the authorization prompt to the merchant.

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

**Query parameters:**

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

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

---

### POST /oauth/token

Exchange an authorization code for access and refresh tokens.

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

**Authorization code exchange request:**

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

**Success response:** `200 OK`

```json
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ...",
  "refresh_token": "def..."
}
```

**Error responses:**
- `400` - Invalid grant (code already used, expired, etc.)
- `401` - Invalid client credentials

**Refresh token request:**

```json
{
  "grant_type": "refresh_token",
  "client_id": "app_client_abc123",
  "client_secret": "secret_xyz",
  "refresh_token": "def..."
}
```

---

### App API Endpoints

Third-party apps use OAuth tokens to access store data via scoped endpoints.

| Method | Path | Scope Required | Description |
|--------|------|----------------|-------------|
| GET | `/api/apps/v1/stores/{storeId}/products` | `read-products` | List products (same schema as admin API) |
| GET | `/api/apps/v1/stores/{storeId}/orders` | `read-orders` | List orders |
| GET | `/api/apps/v1/stores/{storeId}/customers` | `read-customers` | List customers |

Token scopes are enforced on every endpoint. Requests with insufficient scopes return `403 Forbidden`.

---

## 6. Middleware Stack

### 6.1 Route Group Middleware

#### Web (default)

Applied to all web routes via the `web` middleware group:

| Middleware | Purpose |
|-----------|---------|
| `EncryptCookies` | Encrypt/decrypt cookies |
| `AddQueuedCookiesToResponse` | Attach queued cookies |
| `StartSession` | Session handling |
| `ShareErrorsFromSession` | Flash validation errors |
| `VerifyCsrfToken` | CSRF protection on POST/PUT/PATCH/DELETE |


rg -n "forgot-password|reset-password|Fortify|PasswordReset|Route::.*forgot|account/forgot" routes app config bootstrap resources tests -g '!vendor'

rg -n 'forgot-password|reset-password|Fortify|PasswordReset|Route::.*forgot|account/forgot' routes app config bootstrap resources tests -g '!vendor'

Question: forgot-password|reset-password|Fortify|PasswordReset|Route::.*forgot|account/forgot

bootstrap/providers.php:5:    App\Providers\FortifyServiceProvider::class,
routes/web.php:118:    Route::livewire('account/forgot-password', CustomerForgotPassword::class)
routes/web.php:122:    Route::livewire('account/reset-password/{token}', CustomerResetPassword::class)
routes/settings.php:8:use Laravel\Fortify\Features;
config/fortify.php:3:use Laravel\Fortify\Features;
config/fortify.php:9:    | Fortify Guard
config/fortify.php:12:    | Here you may specify which authentication guard Fortify will use while
config/fortify.php:22:    | Fortify Password Broker
config/fortify.php:25:    | Here you may specify which password broker Fortify can use when a user
config/fortify.php:42:    | Out of the box, Fortify expects forgot password and reset password
config/fortify.php:80:    | Fortify Routes Prefix / Subdomain
config/fortify.php:83:    | Here you may specify which prefix Fortify will assign to all the routes
config/fortify.php:85:    | subdomain under which all of the Fortify routes will be available.
config/fortify.php:95:    | Fortify Routes Middleware
config/fortify.php:98:    | Here you may specify which middleware Fortify will assign to the routes
config/fortify.php:111:    | By default, Fortify will throttle logins to five requests per minute for
config/fortify.php:140:    | Some of the Fortify features are optional. You may disable the features
tests/Feature/Settings/TwoFactorAuthenticationTest.php:4:use Laravel\Fortify\Features;
tests/Feature/Auth/CustomerPasswordResetTest.php:9:use App\Services\CustomerPasswordResetService;
tests/Feature/Auth/CustomerPasswordResetTest.php:32:    $this->get('http://customer-reset.test/account/forgot-password')
tests/Feature/Auth/CustomerPasswordResetTest.php:37:    $this->get('http://customer-reset.test/account/reset-password/test-token?email=customer@example.test')
tests/Feature/Auth/CustomerPasswordResetTest.php:117:    app(CustomerPasswordResetService::class)->sendResetLink($store, $customer->email);
tests/Feature/Auth/AuthenticationTest.php:4:use Laravel\Fortify\Features;
tests/Feature/Auth/TwoFactorChallengeTest.php:4:use Laravel\Fortify\Features;
resources/views/components/settings/layout.blade.php:6:            @if (Laravel\Fortify\Features::canManageTwoFactorAuthentication())
app/Services/CustomerPasswordResetService.php:14:class CustomerPasswordResetService
app/Services/CustomerPasswordResetService.php:76:        event(new \Illuminate\Auth\Events\PasswordReset($customer));
app/Livewire/Settings/TwoFactor/RecoveryCodes.php:6:use Laravel\Fortify\Actions\GenerateNewRecoveryCodes;
app/Livewire/Settings/TwoFactor.php:6:use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication;
app/Livewire/Settings/TwoFactor.php:7:use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
app/Livewire/Settings/TwoFactor.php:8:use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
app/Livewire/Settings/TwoFactor.php:9:use Laravel\Fortify\Features;
app/Livewire/Settings/TwoFactor.php:10:use Laravel\Fortify\Fortify;
app/Livewire/Settings/TwoFactor.php:44:        if (Fortify::confirmsTwoFactorAuthentication() && is_null(auth()->user()->two_factor_confirmed_at)) {
app/Livewire/Storefront/Account/Auth/ForgotPassword.php:6:use App\Services\CustomerPasswordResetService;
app/Livewire/Storefront/Account/Auth/ForgotPassword.php:26:    public function send(CustomerPasswordResetService $passwords): void
app/Livewire/Storefront/Account/Auth/ForgotPassword.php:42:        return view('livewire.storefront.account.auth.forgot-password')
app/Livewire/Storefront/Account/Auth/ResetPassword.php:6:use App\Services\CustomerPasswordResetService;
app/Livewire/Storefront/Account/Auth/ResetPassword.php:35:    public function resetPassword(CustomerPasswordResetService $passwords): void
app/Livewire/Storefront/Account/Auth/ResetPassword.php:62:        return view('livewire.storefront.account.auth.reset-password')
app/Models/User.php:13:use Laravel\Fortify\TwoFactorAuthenticatable;
resources/views/livewire/auth/reset-password.blade.php:46:                <flux:button type="submit" variant="primary" class="w-full" data-test="reset-password-button">
app/Actions/Fortify/CreateNewUser.php:3:namespace App\Actions\Fortify;
app/Actions/Fortify/CreateNewUser.php:9:use Laravel\Fortify\Contracts\CreatesNewUsers;
app/Actions/Fortify/ResetUserPassword.php:3:namespace App\Actions\Fortify;
app/Actions/Fortify/ResetUserPassword.php:8:use Laravel\Fortify\Contracts\ResetsUserPasswords;
app/Providers/FortifyServiceProvider.php:5:use App\Actions\Fortify\CreateNewUser;
app/Providers/FortifyServiceProvider.php:6:use App\Actions\Fortify\ResetUserPassword;
app/Providers/FortifyServiceProvider.php:11:use Laravel\Fortify\Fortify;
app/Providers/FortifyServiceProvider.php:13:class FortifyServiceProvider extends ServiceProvider
app/Providers/FortifyServiceProvider.php:34:     * Configure Fortify actions.
app/Providers/FortifyServiceProvider.php:38:        Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
app/Providers/FortifyServiceProvider.php:39:        Fortify::createUsersUsing(CreateNewUser::class);
app/Providers/FortifyServiceProvider.php:43:     * Configure Fortify views.
app/Providers/FortifyServiceProvider.php:47:        Fortify::loginView(fn () => view('livewire.auth.login'));
app/Providers/FortifyServiceProvider.php:48:        Fortify::verifyEmailView(fn () => view('livewire.auth.verify-email'));
app/Providers/FortifyServiceProvider.php:49:        Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge'));
app/Providers/FortifyServiceProvider.php:50:        Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password'));
app/Providers/FortifyServiceProvider.php:51:        Fortify::registerView(fn () => view('livewire.auth.register'));
app/Providers/FortifyServiceProvider.php:52:        Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password'));
app/Providers/FortifyServiceProvider.php:53:        Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password'));
resources/views/livewire/storefront/account/auth/forgot-password.blade.php:21:        <flux:button variant="primary" type="submit" class="w-full" data-test="customer-forgot-password-button">
resources/views/livewire/storefront/account/auth/reset-password.blade.php:33:        <flux:button variant="primary" type="submit" class="w-full" data-test="customer-reset-password-button">


Output:
Chunk ID: 241198
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1608
Output:
bootstrap/providers.php:5:    App\Providers\FortifyServiceProvider::class,
routes/web.php:118:    Route::livewire('account/forgot-password', CustomerForgotPassword::class)
routes/web.php:122:    Route::livewire('account/reset-password/{token}', CustomerResetPassword::class)
routes/settings.php:8:use Laravel\Fortify\Features;
config/fortify.php:3:use Laravel\Fortify\Features;
config/fortify.php:9:    | Fortify Guard
config/fortify.php:12:    | Here you may specify which authentication guard Fortify will use while
config/fortify.php:22:    | Fortify Password Broker
config/fortify.php:25:    | Here you may specify which password broker Fortify can use when a user
config/fortify.php:42:    | Out of the box, Fortify expects forgot password and reset password
config/fortify.php:80:    | Fortify Routes Prefix / Subdomain
config/fortify.php:83:    | Here you may specify which prefix Fortify will assign to all the routes
config/fortify.php:85:    | subdomain under which all of the Fortify routes will be available.
config/fortify.php:95:    | Fortify Routes Middleware
config/fortify.php:98:    | Here you may specify which middleware Fortify will assign to the routes
config/fortify.php:111:    | By default, Fortify will throttle logins to five requests per minute for
config/fortify.php:140:    | Some of the Fortify features are optional. You may disable the features
tests/Feature/Settings/TwoFactorAuthenticationTest.php:4:use Laravel\Fortify\Features;
tests/Feature/Auth/CustomerPasswordResetTest.php:9:use App\Services\CustomerPasswordResetService;
tests/Feature/Auth/CustomerPasswordResetTest.php:32:    $this->get('http://customer-reset.test/account/forgot-password')
tests/Feature/Auth/CustomerPasswordResetTest.php:37:    $this->get('http://customer-reset.test/account/reset-password/test-token?email=customer@example.test')
tests/Feature/Auth/CustomerPasswordResetTest.php:117:    app(CustomerPasswordResetService::class)->sendResetLink($store, $customer->email);
tests/Feature/Auth/AuthenticationTest.php:4:use Laravel\Fortify\Features;
tests/Feature/Auth/TwoFactorChallengeTest.php:4:use Laravel\Fortify\Features;
resources/views/components/settings/layout.blade.php:6:            @if (Laravel\Fortify\Features::canManageTwoFactorAuthentication())
app/Services/CustomerPasswordResetService.php:14:class CustomerPasswordResetService
app/Services/CustomerPasswordResetService.php:76:        event(new \Illuminate\Auth\Events\PasswordReset($customer));
app/Livewire/Settings/TwoFactor/RecoveryCodes.php:6:use Laravel\Fortify\Actions\GenerateNewRecoveryCodes;
app/Livewire/Settings/TwoFactor.php:6:use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication;
app/Livewire/Settings/TwoFactor.php:7:use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
app/Livewire/Settings/TwoFactor.php:8:use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
app/Livewire/Settings/TwoFactor.php:9:use Laravel\Fortify\Features;
app/Livewire/Settings/TwoFactor.php:10:use Laravel\Fortify\Fortify;
app/Livewire/Settings/TwoFactor.php:44:        if (Fortify::confirmsTwoFactorAuthentication() && is_null(auth()->user()->two_factor_confirmed_at)) {
app/Livewire/Storefront/Account/Auth/ForgotPassword.php:6:use App\Services\CustomerPasswordResetService;
app/Livewire/Storefront/Account/Auth/ForgotPassword.php:26:    public function send(CustomerPasswordResetService $passwords): void
app/Livewire/Storefront/Account/Auth/ForgotPassword.php:42:        return view('livewire.storefront.account.auth.forgot-password')
app/Livewire/Storefront/Account/Auth/ResetPassword.php:6:use App\Services\CustomerPasswordResetService;
app/Livewire/Storefront/Account/Auth/ResetPassword.php:35:    public function resetPassword(CustomerPasswordResetService $passwords): void
app/Livewire/Storefront/Account/Auth/ResetPassword.php:62:        return view('livewire.storefront.account.auth.reset-password')
app/Models/User.php:13:use Laravel\Fortify\TwoFactorAuthenticatable;
resources/views/livewire/auth/reset-password.blade.php:46:                <flux:button type="submit" variant="primary" class="w-full" data-test="reset-password-button">
app/Actions/Fortify/CreateNewUser.php:3:namespace App\Actions\Fortify;
app/Actions/Fortify/CreateNewUser.php:9:use Laravel\Fortify\Contracts\CreatesNewUsers;
app/Actions/Fortify/ResetUserPassword.php:3:namespace App\Actions\Fortify;
app/Actions/Fortify/ResetUserPassword.php:8:use Laravel\Fortify\Contracts\ResetsUserPasswords;
app/Providers/FortifyServiceProvider.php:5:use App\Actions\Fortify\CreateNewUser;
app/Providers/FortifyServiceProvider.php:6:use App\Actions\Fortify\ResetUserPassword;
app/Providers/FortifyServiceProvider.php:11:use Laravel\Fortify\Fortify;
app/Providers/FortifyServiceProvider.php:13:class FortifyServiceProvider extends ServiceProvider
app/Providers/FortifyServiceProvider.php:34:     * Configure Fortify actions.
app/Providers/FortifyServiceProvider.php:38:        Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
app/Providers/FortifyServiceProvider.php:39:        Fortify::createUsersUsing(CreateNewUser::class);
app/Providers/FortifyServiceProvider.php:43:     * Configure Fortify views.
app/Providers/FortifyServiceProvider.php:47:        Fortify::loginView(fn () => view('livewire.auth.login'));
app/Providers/FortifyServiceProvider.php:48:        Fortify::verifyEmailView(fn () => view('livewire.auth.verify-email'));
app/Providers/FortifyServiceProvider.php:49:        Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge'));
app/Providers/FortifyServiceProvider.php:50:        Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password'));
app/Providers/FortifyServiceProvider.php:51:        Fortify::registerView(fn () => view('livewire.auth.register'));
app/Providers/FortifyServiceProvider.php:52:        Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password'));
app/Providers/FortifyServiceProvider.php:53:        Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password'));
resources/views/livewire/storefront/account/auth/forgot-password.blade.php:21:        <flux:button variant="primary" type="submit" class="w-full" data-test="customer-forgot-password-button">
resources/views/livewire/storefront/account/auth/reset-password.blade.php:33:        <flux:button variant="primary" type="submit" class="w-full" data-test="customer-reset-password-button">


rg -n "class .*Api|api/admin|api/apps|oauth|AuthenticateAdminApi|JsonResource|Resource" app/Http app/Models routes tests/Feature/Api -g '!vendor'


find app/Http -maxdepth 4 -type f | sort


/bin/zsh
-lc
sed -n '1,260p' routes/api.php

<?php

use App\Http\Controllers\Api\Admin\V1\OrderController as AdminOrderController;
use App\Http\Controllers\Api\Admin\V1\OrderFulfillmentController as AdminOrderFulfillmentController;
use App\Http\Controllers\Api\Admin\V1\OrderRefundController as AdminOrderRefundController;
use App\Http\Controllers\Api\Storefront\V1\AnalyticsEventController as StorefrontAnalyticsEventController;
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 App\Http\Controllers\Api\Storefront\V1\OrderController as StorefrontOrderController;
use App\Http\Controllers\Api\Storefront\V1\SearchController as StorefrontSearchController;
use Illuminate\Support\Facades\Route;

Route::middleware('store.resolve')
    ->prefix('storefront/v1')
    ->name('api.storefront.v1.')
    ->group(function (): void {
        Route::middleware('throttle:api.storefront')->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::middleware('throttle:search')->group(function (): void {
            Route::get('search', [StorefrontSearchController::class, 'index'])->name('search.index');
            Route::get('search/suggest', [StorefrontSearchController::class, 'suggest'])->name('search.suggest');
        });

        Route::middleware('throttle:analytics')->group(function (): void {
            Route::post('analytics/events', [StorefrontAnalyticsEventController::class, 'store'])->name('analytics.events.store');
        });

        Route::middleware('throttle:checkout')->group(function (): void {
            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::delete('checkouts/{checkout}/discount', [CheckoutController::class, 'destroyDiscount'])->name('checkouts.discount.destroy');
            Route::put('checkouts/{checkout}/payment-method', [CheckoutController::class, 'paymentMethod'])->name('checkouts.payment-method');
            Route::post('checkouts/{checkout}/pay', [CheckoutController::class, 'pay'])->name('checkouts.pay');
            Route::get('orders/{orderNumber}', [StorefrontOrderController::class, 'show'])->name('orders.show');
        });
    });

Route::middleware('throttle:60,1')
    ->prefix('admin/v1/stores/{store}')
    ->name('api.admin.v1.')
    ->group(function (): void {
        Route::middleware('admin.api:read-orders')->group(function (): void {
            Route::get('orders', [AdminOrderController::class, 'index'])->name('orders.index');
            Route::get('orders/{order}', [AdminOrderController::class, 'show'])->name('orders.show');
        });

        Route::middleware('admin.api:write-orders')->group(function (): void {
            Route::post('orders/{order}/fulfillments', [AdminOrderFulfillmentController::class, 'store'])->name('orders.fulfillments.store');
            Route::post('orders/{order}/refunds', [AdminOrderRefundController::class, 'store'])->name('orders.refunds.store');
        });
    });


Output:
Chunk ID: be71ae
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1026
Output:
<?php

use App\Http\Controllers\Api\Admin\V1\OrderController as AdminOrderController;
use App\Http\Controllers\Api\Admin\V1\OrderFulfillmentController as AdminOrderFulfillmentController;
use App\Http\Controllers\Api\Admin\V1\OrderRefundController as AdminOrderRefundController;
use App\Http\Controllers\Api\Storefront\V1\AnalyticsEventController as StorefrontAnalyticsEventController;
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 App\Http\Controllers\Api\Storefront\V1\OrderController as StorefrontOrderController;
use App\Http\Controllers\Api\Storefront\V1\SearchController as StorefrontSearchController;
use Illuminate\Support\Facades\Route;

Route::middleware('store.resolve')
    ->prefix('storefront/v1')
    ->name('api.storefront.v1.')
    ->group(function (): void {
        Route::middleware('throttle:api.storefront')->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::middleware('throttle:search')->group(function (): void {
            Route::get('search', [StorefrontSearchController::class, 'index'])->name('search.index');
            Route::get('search/suggest', [StorefrontSearchController::class, 'suggest'])->name('search.suggest');
        });

        Route::middleware('throttle:analytics')->group(function (): void {
            Route::post('analytics/events', [StorefrontAnalyticsEventController::class, 'store'])->name('analytics.events.store');
        });

        Route::middleware('throttle:checkout')->group(function (): void {
            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::delete('checkouts/{checkout}/discount', [CheckoutController::class, 'destroyDiscount'])->name('checkouts.discount.destroy');
            Route::put('checkouts/{checkout}/payment-method', [CheckoutController::class, 'paymentMethod'])->name('checkouts.payment-method');
            Route::post('checkouts/{checkout}/pay', [CheckoutController::class, 'pay'])->name('checkouts.pay');
            Route::get('orders/{orderNumber}', [StorefrontOrderController::class, 'show'])->name('orders.show');
        });
    });

Route::middleware('throttle:60,1')
    ->prefix('admin/v1/stores/{store}')
    ->name('api.admin.v1.')
    ->group(function (): void {
        Route::middleware('admin.api:read-orders')->group(function (): void {
            Route::get('orders', [AdminOrderController::class, 'index'])->name('orders.index');
            Route::get('orders/{order}', [AdminOrderController::class, 'show'])->name('orders.show');
        });

        Route::middleware('admin.api:write-orders')->group(function (): void {
            Route::post('orders/{order}/fulfillments', [AdminOrderFulfillmentController::class, 'store'])->name('orders.fulfillments.store');
            Route::post('orders/{order}/refunds', [AdminOrderRefundController::class, 'store'])->name('orders.refunds.store');
        });
    });


rg -n "class .*Api|api/admin|api/apps|oauth|AuthenticateAdminApi|JsonResource|Resource" app/Http app/Models routes tests/Feature/Api -g '!vendor'

rg -n 'class .*Api|api/admin|api/apps|oauth|AuthenticateAdminApi|JsonResource|Resource' app/Http app/Models routes tests/Feature/Api -g '!vendor'

Question: class .*Api|api/admin|api/apps|oauth|AuthenticateAdminApi|JsonResource|Resource

tests/Feature/Api/AdminOrderApiTest.php:106:    $this->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
tests/Feature/Api/AdminOrderApiTest.php:110:        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=alpha")
tests/Feature/Api/AdminOrderApiTest.php:116:        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}")
tests/Feature/Api/AdminOrderApiTest.php:128:        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
tests/Feature/Api/AdminOrderApiTest.php:139:        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/fulfillments", [
tests/Feature/Api/AdminOrderApiTest.php:164:        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$otherOrder->getKey()}")
tests/Feature/Api/AdminOrderApiTest.php:177:        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=token")
tests/Feature/Api/AdminOrderApiTest.php:193:        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
tests/Feature/Api/AdminOrderApiTest.php:200:        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
tests/Feature/Api/AdminOrderApiTest.php:204:        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
tests/Feature/Api/AdminOrderApiTest.php:219:        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:8:use App\Http\Resources\Admin\V1\RefundResource;
app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:17:    public function store(CreateOrderRefundRequest $request, Store $store, Order $order, RefundService $refunds): RefundResource|JsonResponse
app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:35:            return RefundResource::make($refund)
app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:45:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Models/App.php:44:    public function oauthClients(): HasMany
app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php:8:use App\Http\Resources\Admin\V1\FulfillmentResource;
app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php:17:    public function store(CreateOrderFulfillmentRequest $request, Store $store, Order $order, FulfillmentService $fulfillments): FulfillmentResource|JsonResponse
app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php:29:            return FulfillmentResource::make($fulfillment->load('lines'))
app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php:39:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Http/Controllers/Api/Admin/V1/OrderController.php:6:use App\Http\Resources\Admin\V1\OrderResource;
app/Http/Controllers/Api/Admin/V1/OrderController.php:11:use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
app/Http/Controllers/Api/Admin/V1/OrderController.php:16:    public function index(Request $request, Store $store): AnonymousResourceCollection
app/Http/Controllers/Api/Admin/V1/OrderController.php:49:        return OrderResource::collection($orders);
app/Http/Controllers/Api/Admin/V1/OrderController.php:52:    public function show(Request $request, Store $store, Order $order): OrderResource
app/Http/Controllers/Api/Admin/V1/OrderController.php:57:        return OrderResource::make($this->loadOrder($order));
app/Http/Controllers/Api/Admin/V1/OrderController.php:62:        if (! $request->attributes->has('admin_api_oauth_token')) {
app/Models/AppInstallation.php:57:    public function oauthTokens(): HasMany
app/Http/Controllers/Api/Storefront/V1/CartController.php:7:use App\Http\Resources\Storefront\V1\CartResource;
app/Http/Controllers/Api/Storefront/V1/CartController.php:19:        return CartResource::make($this->loadCart($carts->create($this->currentStore())))
app/Http/Controllers/Api/Storefront/V1/CartController.php:24:    public function show(Cart $cart): CartResource
app/Http/Controllers/Api/Storefront/V1/CartController.php:26:        return CartResource::make($this->loadCart($cart));
app/Http/Controllers/Api/Storefront/V1/CartLineController.php:12:use App\Http\Resources\Storefront\V1\CartResource;
app/Http/Controllers/Api/Storefront/V1/CartLineController.php:20:    public function store(StoreCartLineRequest $request, Cart $cart, CartService $carts): CartResource|JsonResponse
app/Http/Controllers/Api/Storefront/V1/CartLineController.php:30:            return CartResource::make($this->loadCart($cart->refresh()))
app/Http/Controllers/Api/Storefront/V1/CartLineController.php:40:    public function update(UpdateCartLineRequest $request, Cart $cart, CartLine $cartLine, CartService $carts): CartResource|JsonResponse
app/Http/Controllers/Api/Storefront/V1/CartLineController.php:52:            return CartResource::make($this->loadCart($cart->refresh()));
app/Http/Controllers/Api/Storefront/V1/CartLineController.php:60:    public function destroy(DestroyCartLineRequ…2783 chars truncated…ine $pricing): CheckoutResource|JsonResponse
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:88:            return CheckoutResource::make($this->loadCheckout($checkout->refresh()));
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:100:    public function destroyDiscount(Checkout $checkout, PricingEngine $pricing): CheckoutResource
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:107:        return CheckoutResource::make($this->loadCheckout($checkout->refresh()));
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:110:    public function paymentMethod(SelectCheckoutPaymentRequest $request, Checkout $checkout, CheckoutService $checkouts): CheckoutResource|JsonResponse
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:113:            return CheckoutResource::make($this->loadCheckout($checkouts->selectPaymentMethod(
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:122:    public function pay(CompleteCheckoutPaymentRequest $request, Checkout $checkout, CheckoutService $checkouts): OrderResource|JsonResponse
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:136:            return OrderResource::make($this->loadOrder($order))
app/Http/Resources/Admin/V1/RefundResource.php:3:namespace App\Http\Resources\Admin\V1;
app/Http/Resources/Admin/V1/RefundResource.php:6:use Illuminate\Http\Resources\Json\JsonResource;
app/Http/Resources/Admin/V1/RefundResource.php:8:class RefundResource extends JsonResource
app/Http/Resources/Admin/V1/FulfillmentResource.php:3:namespace App\Http\Resources\Admin\V1;
app/Http/Resources/Admin/V1/FulfillmentResource.php:6:use Illuminate\Http\Resources\Json\JsonResource;
app/Http/Resources/Admin/V1/FulfillmentResource.php:8:class FulfillmentResource extends JsonResource
app/Http/Resources/Admin/V1/OrderResource.php:3:namespace App\Http\Resources\Admin\V1;
app/Http/Resources/Admin/V1/OrderResource.php:6:use Illuminate\Http\Resources\Json\JsonResource;
app/Http/Resources/Admin/V1/OrderResource.php:8:class OrderResource extends JsonResource
app/Http/Resources/Admin/V1/OrderResource.php:57:            'refunds' => RefundResource::collection($this->whenLoaded('refunds')),
app/Http/Resources/Admin/V1/OrderResource.php:58:            'fulfillments' => FulfillmentResource::collection($this->whenLoaded('fulfillments')),
app/Http/Middleware/AuthenticateAdminApi.php:12:class AuthenticateAdminApi
app/Http/Middleware/AuthenticateAdminApi.php:23:        app()->forgetInstance('admin_api_oauth_token');
app/Http/Middleware/AuthenticateAdminApi.php:63:        $request->attributes->set('admin_api_oauth_token', $token);
app/Http/Middleware/AuthenticateAdminApi.php:64:        app()->instance('admin_api_oauth_token', $token);
app/Http/Resources/Storefront/V1/OrderResource.php:3:namespace App\Http\Resources\Storefront\V1;
app/Http/Resources/Storefront/V1/OrderResource.php:8:use Illuminate\Http\Resources\Json\JsonResource;
app/Http/Resources/Storefront/V1/OrderResource.php:10:class OrderResource extends JsonResource
app/Http/Resources/Storefront/V1/CheckoutResource.php:3:namespace App\Http\Resources\Storefront\V1;
app/Http/Resources/Storefront/V1/CheckoutResource.php:6:use Illuminate\Http\Resources\Json\JsonResource;
app/Http/Resources/Storefront/V1/CheckoutResource.php:8:class CheckoutResource extends JsonResource
app/Http/Resources/Storefront/V1/CheckoutResource.php:31:            'available_shipping_rates' => ShippingRateResource::collection($this->whenLoaded('availableRates')),
app/Http/Resources/Storefront/V1/CheckoutResource.php:32:            'cart' => new CartResource($this->whenLoaded('cart')),
app/Http/Resources/Storefront/V1/CartResource.php:3:namespace App\Http\Resources\Storefront\V1;
app/Http/Resources/Storefront/V1/CartResource.php:6:use Illuminate\Http\Resources\Json\JsonResource;
app/Http/Resources/Storefront/V1/CartResource.php:8:class CartResource extends JsonResource
app/Http/Resources/Storefront/V1/CartResource.php:33:            'lines' => CartLineResource::collection($lines),
app/Http/Resources/Storefront/V1/CartLineResource.php:3:namespace App\Http\Resources\Storefront\V1;
app/Http/Resources/Storefront/V1/CartLineResource.php:6:use Illuminate\Http\Resources\Json\JsonResource;
app/Http/Resources/Storefront/V1/CartLineResource.php:8:class CartLineResource extends JsonResource
app/Http/Resources/Storefront/V1/ShippingRateResource.php:3:namespace App\Http\Resources\Storefront\V1;
app/Http/Resources/Storefront/V1/ShippingRateResource.php:6:use Illuminate\Http\Resources\Json\JsonResource;
app/Http/Resources/Storefront/V1/ShippingRateResource.php:8:class ShippingRateResource extends JsonResource
app/Http/Resources/Storefront/V1/SearchProductResource.php:3:namespace App\Http\Resources\Storefront\V1;
app/Http/Resources/Storefront/V1/SearchProductResource.php:8:use Illuminate\Http\Resources\Json\JsonResource;
app/Http/Resources/Storefront/V1/SearchProductResource.php:10:class SearchProductResource extends JsonResource