command: sed -n '1,420p' app/Http/Controllers/Api/AdminController.php; sed -n '1,260p' app/Http/Controllers/Api/StorefrontCartController.php; sed -n '1,220p' app/Http/Controllers/Api/StorefrontCheckoutController.php

StorefrontCheckoutController.php

stdout:
<?php

namespace App\Http\Controllers\Api;

use App\Enums\ProductStatus;
use App\Http\Controllers\Controller;
use App\Models\Collection;
use App\Models\Customer;
use App\Models\Discount;
use App\Models\Order;
use App\Models\Page;
use App\Models\Product;
use App\Models\ShippingRate;
use App\Models\ShippingZone;
use App\Models\TaxSettings;
use App\Models\Theme;
use App\Services\FulfillmentService;
use App\Services\ProductService;
use App\Services\RefundService;
use App\Services\SearchService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class AdminController extends Controller
{
    public function products(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['status' => ['nullable', 'in:draft,active,archived'], 'query' => ['nullable', 'string'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100']]);
        $products = Product::withoutGlobalScopes()->where('store_id', $storeId)->with(['variants.inventory', 'collections'])->when($data['status'] ?? null, fn ($query, string $status) => $query->where('status', $status))->when($data['query'] ?? null, fn ($query, string $queryText) => $query->where(function ($nested) use ($queryText): void {
            $nested->where('title', 'like', '%'.$queryText.'%')->orWhere('vendor', 'like', '%'.$queryText.'%')->orWhereHas('variants', fn ($variants) => $variants->where('sku', 'like', '%'.$queryText.'%'));
        }))->latest('updated_at')->paginate($data['per_page'] ?? 25);

        return $this->paginated($products);
    }

    public function storeProduct(Request $request, int $storeId, ProductService $products): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['title' => ['required', 'string', 'max:255'], 'handle' => ['nullable', 'string', 'max:255'], 'description' => ['nullable', 'string'], 'vendor' => ['nullable', 'string', 'max:255'], 'product_type' => ['nullable', 'string', 'max:255'], 'status' => ['nullable', 'in:draft,active,archived'], 'variants' => ['nullable', 'array']]);
        $product = $products->create(app('current_store'), [...$data, 'status' => ProductStatus::from($data['status'] ?? ProductStatus::Draft->value)]);

        return response()->json(['data' => $product->load('variants')->toArray()], 201);
    }

    public function showProduct(int $storeId, int $productId): JsonResponse
    {
        $this->assertStore($storeId);
        $product = Product::withoutGlobalScopes()->where('store_id', $storeId)->with(['variants.inventory', 'options.values', 'media', 'collections'])->findOrFail($productId);

        return response()->json(['data' => $product->toArray()]);
    }

    public function updateProduct(Request $request, int $storeId, int $productId, ProductService $products): JsonResponse
    {
        $this->assertStore($storeId);
        $product = Product::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($productId);
        $data = $request->validate(['title' => ['sometimes', 'string', 'max:255'], 'description' => ['sometimes', 'nullable', 'string'], 'vendor' => ['sometimes', 'nullable', 'string', 'max:255'], 'product_type' => ['sometimes', 'nullable', 'string', 'max:255'], 'status' => ['sometimes', 'in:draft,active,archived']]);

        return response()->json(['data' => $products->update($product, $data)->toArray()]);
    }

    public function deleteProduct(int $storeId, int $productId, ProductService $products): JsonResponse
    {
        $this->assertStore($storeId);
        $product = Product::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($productId);
        $products->transitionStatus($product, ProductStatus::Archived);

        return response()->json(['message' => 'Product archived']);
    }

    public function collections(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['query' => ['nullable', 'string'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100']]);
        $collections = Collection::withoutGlobalScopes()->where('store_id', $storeId)->withCount('products')->when($data['query'] ?? null, fn ($query, string $queryText) => $query->where('title', 'like', '%'.$queryText.'%'))->latest()->paginate($data['per_page'] ?? 25);

        return $this->paginated($collections);
    }

    public function storeCollection(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['title' => ['required', 'string', 'max:255'], 'handle' => ['nullable', 'string', 'max:255'], 'description_html' => ['nullable', 'string'], 'status' => ['nullable', 'in:draft,active,archived'], 'product_ids' => ['nullable', 'array']]);
        $collection = Collection::withoutGlobalScopes()->create(['store_id' => $storeId, 'title' => $data['title'], 'handle' => $data['handle'] ?? Str::slug($data['title']), 'description' => $data['description_html'] ?? null, 'status' => $data['status'] ?? 'active']);
        $this->syncCollectionProducts($collection, $data['product_ids'] ?? [], $storeId);

        return response()->json(['data' => $collection->load('products')->toArray()], 201);
    }

    public function updateCollection(Request $request, int $storeId, int $collectionId): JsonResponse
    {
        $this->assertStore($storeId);
        $collection = Collection::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($collectionId);
        $data = $request->validate(['title' => ['sometimes', 'string', 'max:255'], 'description_html' => ['sometimes', 'nullable', 'string'], 'status' => ['sometimes', 'in:draft,active,archived'], 'product_ids' => ['sometimes', 'array']]);
        $collection->update(array_filter(['title' => $data['title'] ?? null, 'description' => $data['description_html'] ?? null, 'status' => $data['status'] ?? null], fn ($value): bool => $value !== null));

        if (array_key_exists('product_ids', $data)) {
            $this->syncCollectionProducts($collection, $data['product_ids'], $storeId);
        }

        return response()->json(['data' => $collection->load('products')->toArray()]);
    }

    public function deleteCollection(int $storeId, int $collectionId): JsonResponse
    {
        $this->assertStore($storeId);
        Collection::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($collectionId)->delete();

        return response()->json(['message' => 'Collection deleted']);
    }

    public function orders(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['status' => ['nullable', 'string'], 'financial_status' => ['nullable', 'string'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100']]);
        $orders = Order::withoutGlobalScopes()->where('store_id', $storeId)->with('customer')->when($data['status'] ?? null, fn ($query, string $status) => $query->where('status', $status))->when($data['financial_status'] ?? null, fn ($query, string $status) => $query->where('financial_status', $status))->latest('placed_at')->paginate($data['per_page'] ?? 25);

        return $this->paginated($orders);
    }

    public function showOrder(int $storeId, int $orderId): JsonResponse
    {
        $this->assertStore($storeId);
        $order = Order::withoutGlobalScopes()->where('store_id', $storeId)->with(['customer', 'lines', 'payments', 'refunds', 'fulfillments.lines'])->findOrFail($orderId);

        return response()->json(['data' => $order->toArray()]);
    }

    public function customers(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['query' => ['nullable', 'string'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100']]);
        $customers = Customer::withoutGlobalScopes()->where('store_id', $storeId)->when($data['query'] ?? null, fn ($query, string $queryText) => $query->where(function ($nested) use ($queryText): void {
            $nested->where('email', 'like', '%'.$queryText.'%')->orWhere('first_name', 'like', '%'.$queryText.'%')->orWhere('last_name', 'like', '%'.$queryText.'%');
        }))->latest()->paginate($data['per_page'] ?? 25);

        return $this->paginated($customers);
    }

    public function discounts(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);

        return $this->paginated(Discount::withoutGlobalScopes()->where('store_id', $storeId)->latest()->paginate($request->integer('per_page', 25)));
    }

    public function storeDiscount(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['code' => ['nullable', 'string', 'max:64'], 'type' => ['required', 'string'], 'value_type' => ['required', 'in:percent,fixed,free_shipping'], 'value_amount' => ['required', 'integer', 'min:0'], 'status' => ['nullable', 'in:active,inactive'], 'usage_limit' => ['nullable', 'integer', 'min:1'], 'starts_at' => ['nullable', 'date'], 'ends_at' => ['nullable', 'date'], 'rules_json' => ['nullable', 'array']]);
        $discount = Discount::withoutGlobalScopes()->create([...$data, 'store_id' => $storeId, 'status' => $data['status'] ?? 'active', 'usage_count' => 0]);

        return response()->json(['data' => $discount], 201);
    }

    public function updateDiscount(Request $request, int $storeId, int $discountId): JsonResponse
    {
        $this->assertStore($storeId);
        $discount = Discount::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($discountId);
        $discount->update($request->validate(['code' => ['sometimes', 'nullable', 'string', 'max:64'], 'type' => ['sometimes', 'string'], 'value_type' => ['sometimes', 'in:percent,fixed,free_shipping'], 'value_amount' => ['sometimes', 'integer', 'min:0'], 'status' => ['sometimes', 'in:active,inactive'], 'usage_limit' => ['sometimes', 'nullable', 'integer', 'min:1'], 'starts_at' => ['sometimes', 'nullable', 'date'], 'ends_at' => ['sometimes', 'nullable', 'date'], 'rules_json' => ['sometimes', 'array']]));

        return response()->json(['data' => $discount->refresh()]);
    }

    public function deleteDiscount(int $storeId, int $discountId): JsonResponse
    {
        $this->assertStore($storeId);
        Discount::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($discountId)->delete();

        return response()->json(['message' => 'Discount deleted.']);
    }

    public function fulfillOrder(Request $request, int $storeId, int $orderId, FulfillmentService $fulfillments): JsonResponse
    {
        $this->assertStore($storeId);
        $order = Order::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($orderId);
        $data = $request->validate(['lines' => ['required', 'array', 'min:1'], 'lines.*.order_line_id' => ['required', 'integer'], 'lines.*.quantity' => ['required', 'integer', 'min:1'], 'tracking_company' => ['nullable', 'string', 'max:100'], 'tracking_number' => ['nullable', 'string', 'max:255'], 'tracking_url' => ['nullable', 'url']]);
        $fulfillment = $fulfillments->create($order, $data['lines'], array_filter(['tracking_company' => $data['tracking_company'] ?? null, 'tracking_number' => $data['tracking_number'] ?? null, 'tracking_url' => $data['tracking_url'] ?? null]));

        return response()->json(['data' => $fulfillment->toArray()], 201);
    }

    public function refundOrder(Request $request, int $storeId, int $orderId, RefundService $refunds): JsonResponse
    {
        $this->assertStore($storeId);
        $order = Order::withoutGlobalScopes()->where('store_id', $storeId)->with('payments')->findOrFail($orderId);
        $data = $request->validate(['payment_id' => ['nullable', 'integer'], 'amount' => ['nullable', 'integer', 'min:1'], 'lines' => ['nullable', 'array'], 'reason' => ['nullable', 'string', 'max:500'], 'restock' => ['nullable', 'boolean']]);
        $payment = $order->payments->firstWhere('id', $data['payment_id'] ?? null) ?? $order->payments->firstWhere('status', 'captured');
        abort_unless($payment !== null, 422, 'No captured payment is available.');
        $refund = $refunds->create($order, $payment, $data['lines'] ?? $data['amount'] ?? null, $data['reason'] ?? null, (bool) ($data['restock'] ?? false), $data['lines'] ?? []);

        return response()->json(['data' => $refund->toArray()], 201);
    }

    public function shippingZones(int $storeId): JsonResponse
    {
        $this->assertStore($storeId);

        return response()->json(['data' => ShippingZone::withoutGlobalScopes()->where('store_id', $storeId)->with('rates')->get()]);
    }

    public function storeShippingZone(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['name' => ['required', 'string', 'max:255'], 'countries_json' => ['nullable', 'array'], 'regions_json' => ['nullable', 'array']]);

        return response()->json(['data' => ShippingZone::withoutGlobalScopes()->create([...$data, 'store_id' => $storeId])], 201);
    }

    public function updateShippingZone(Request $request, int $storeId, int $zoneId): JsonResponse
    {
        $this->assertStore($storeId);
        $zone = ShippingZone::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($zoneId);
        $zone->update($request->validate(['name' => ['sometimes', 'string', 'max:255'], 'countries_json' => ['sometimes', 'array'], 'regions_json' => ['sometimes', 'array']]));

        return response()->json(['data' => $zone->refresh()]);
    }

    public function storeShippingRate(Request $request, int $storeId, int $zoneId): JsonResponse
    {
        $this->assertStore($storeId);
        ShippingZone::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($zoneId);
        $data = $request->validate(['name' => ['required', 'string', 'max:255'], 'type' => ['required', 'in:flat,weight,price,carrier'], 'price_amount' => ['required', 'integer', 'min:0'], 'currency' => ['required', 'size:3'], 'config_json' => ['nullable', 'array'], 'is_active' => ['nullable', 'boolean'], 'estimated_days_min' => ['nullable', 'integer', 'min:0'], 'estimated_days_max' => ['nullable', 'integer', 'min:0']]);

        return response()->json(['data' => ShippingRate::create([...$data, 'shipping_zone_id' => $zoneId, 'is_active' => $data['is_active'] ?? true])], 201);
    }

    public function taxSettings(int $storeId): JsonResponse
    {
        $this->assertStore($storeId);

        return response()->json(['data' => TaxSettings::withoutGlobalScopes()->firstOrCreate(['store_id' => $storeId])]);
    }

    public function updateTaxSettings(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['mode' => ['sometimes', 'string'], 'provider' => ['sometimes', 'nullable', 'string'], 'prices_include_tax' => ['sometimes', 'boolean'], 'default_rate_basis_points' => ['sometimes', 'integer', 'min:0', 'max:10000'], 'rates_json' => ['sometimes', 'array'], 'provider_config_json' => ['sometimes', 'array']]);
        $settings = TaxSettings::withoutGlobalScopes()->updateOrCreate(['store_id' => $storeId], $data);

        return response()->json(['data' => $settings]);
    }

    public function pages(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);

        return $this->paginated(Page::withoutGlobalScopes()->where('store_id', $storeId)->latest()->paginate($request->integer('per_page', 25)));
    }

    public function storePage(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['title' => ['required', 'string', 'max:255'], 'handle' => ['nullable', 'string', 'max:255'], 'body_html' => ['nullable', 'string'], 'status' => ['required', 'in:draft,published']]);
        $page = Page::withoutGlobalScopes()->create([...$data, 'store_id' => $storeId, 'handle' => $data['handle'] ?? Str::slug($data['title']), 'published_at' => $data['status'] === 'published' ? now() : null]);

        return response()->json(['data' => $page], 201);
    }

    public function updatePage(Request $request, int $storeId, int $pageId): JsonResponse
    {
        $this->assertStore($storeId);
        $page = Page::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($pageId);
        $data = $request->validate(['title' => ['sometimes', 'string', 'max:255'], 'handle' => ['sometimes', 'string', 'max:255'], 'body_html' => ['sometimes', 'nullable', 'string'], 'status' => ['sometimes', 'in:draft,published']]);
        if (($data['status'] ?? null) === 'published') {
            $data['published_at'] = $page->published_at ?? now();
        }
        $page->update($data);

        return response()->json(['data' => $page->refresh()]);
    }

    public function deletePage(int $storeId, int $pageId): JsonResponse
    {
        $this->assertStore($storeId);
        Page::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($pageId)->delete();

        return response()->json(['message' => 'Page deleted.']);
    }

    public function storeTheme(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['name' => ['required', 'string', 'max:255'], 'version' => ['nullable', 'string', 'max:30'], 'settings' => ['nullable', 'array']]);

        return response()->json(['data' => Theme::withoutGlobalScopes()->create([...$data, 'store_id' => $storeId, 'status' => 'draft'])], 201);
    }

    public function publishTheme(int $storeId, int $themeId): JsonResponse
    {
        $this->assertStore($storeId);
        $theme = Theme::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($themeId);
        Theme::withoutGlobalScopes()->where('store_id', $storeId)->update(['status' => 'draft']);
        $theme->update(['status' => 'published']);

        return response()->json(['data' => $theme->refresh()]);
    }

    public function updateThemeSettings(Request $request, int $storeId, int $themeId): JsonResponse
    {
        $this->assertStore($storeId);
        $theme = Theme::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($themeId);
        $theme->update(['settings' => $request->validate(['settings' => ['required', 'array']])['settings']]);

        return response()->json(['data' => $theme->refresh()]);
    }

    public function reindex(SearchService $search, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        Product::withoutGlobalScopes()->where('store_id', $storeId)->each(fn (Product $product) => $search->syncProduct($product));

        return response()->json(['status' => 'completed']);
    }

    public function searchStatus(int $storeId): JsonResponse
    {
        $this->assertStore($storeId);

        return response()->json(['status' => 'ready', 'product_count' => Product::withoutGlobalScopes()->where('store_id', $storeId)->count()]);
    }

    public function analyticsSummary(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $days = \App\Models\AnalyticsDaily::withoutGlobalScopes()->where('store_id', $storeId)->whereBetween('date', [$request->input('from', now()->subDays(29)->toDateString()), $request->input('to', now()->toDateString())])->get();

        return response()->json(['data' => ['visits' => (int) $days->sum('visits_count'), 'orders' => (int) $days->sum('orders_count'), 'revenue_amount' => (int) $days->sum('revenue_amount'), 'checkout_completed' => (int) $days->sum('checkout_completed_count'), 'days' => $days]]);
    }

    private function assertStore(int $storeId): void
    {
        abort_unless((int) app('current_store')->getKey() === $storeId, 404);
    }

    private function syncCollectionProducts(Collection $collection, array $productIds, int $storeId): void
    {
        $validIds = Product::withoutGlobalScopes()->where('store_id', $storeId)->whereIn('id', $productIds)->pluck('id')->all();
        $collection->products()->sync(array_fill_keys($validIds, ['position' => 0]));
    }

    private function paginated(LengthAwarePaginator $paginator): JsonResponse
    {
        return response()->json(['data' => $paginator->items(), 'meta' => ['current_page' => $paginator->currentPage(), 'per_page' => $paginator->perPage(), 'total' => $paginator->total(), 'last_page' => $paginator->lastPage()]]);
    }
}
<?php

namespace App\Http\Controllers\Api;

use App\Exceptions\CartVersionConflictException;
use App\Exceptions\InsufficientInventoryException;
use App\Http\Controllers\Controller;
use App\Models\Cart;
use App\Services\CartService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class StorefrontCartController extends Controller
{
    public function __construct(private readonly CartService $carts) {}

    public function store(Request $request): JsonResponse
    {
        $data = $request->validate(['currency' => ['nullable', 'string', 'size:3']]);
        $cart = $this->carts->create(app('current_store'), $request->user('customer'));

        if (isset($data['currency'])) {
            $cart->update(['currency' => strtoupper($data['currency'])]);
        }

        $request->session()->put(['cart_id' => $cart->getKey(), 'cart_id_'.app('current_store')->getKey() => $cart->getKey()]);

        return response()->json($this->payload($cart->refresh()), 201);
    }

    public function show(int $cartId): JsonResponse
    {
        return response()->json($this->payload($this->cart($cartId)));
    }

    public function addLine(Request $request, int $cartId): JsonResponse
    {
        $data = $request->validate(['variant_id' => ['required', 'integer'], 'quantity' => ['required', 'integer', 'min:1', 'max:9999'], 'cart_version' => ['nullable', 'integer'], 'expected_version' => ['nullable', 'integer']]);
        $cart = $this->cart($cartId);

        try {
            $this->carts->assertVersion($cart, $data['cart_version'] ?? $data['expected_version'] ?? null);
            $this->carts->addLine($cart, $data['variant_id'], $data['quantity']);
        } catch (CartVersionConflictException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'cart' => $this->payload($cart->refresh())], 409);
        } catch (InsufficientInventoryException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'insufficient_inventory'], 422);
        }

        return response()->json($this->payload($this->cart($cartId)), 201);
    }

    public function updateLine(Request $request, int $cartId, int $lineId): JsonResponse
    {
        $data = $request->validate(['quantity' => ['required', 'integer', 'min:1', 'max:9999'], 'cart_version' => ['required', 'integer']]);
        $cart = $this->cart($cartId);

        try {
            $this->carts->assertVersion($cart, $data['cart_version']);
            $this->carts->updateLineQuantity($cart, $lineId, $data['quantity']);
        } catch (CartVersionConflictException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'cart' => $this->payload($cart->refresh())], 409);
        } catch (InsufficientInventoryException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'insufficient_inventory'], 422);
        }

        return response()->json($this->payload($this->cart($cartId)));
    }

    public function removeLine(Request $request, int $cartId, int $lineId): JsonResponse
    {
        $data = $request->validate(['cart_version' => ['required', 'integer']]);
        $cart = $this->cart($cartId);

        try {
            $this->carts->assertVersion($cart, $data['cart_version']);
            $this->carts->removeLine($cart, $lineId);
        } catch (CartVersionConflictException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'cart' => $this->payload($cart->refresh())], 409);
        }

        return response()->json($this->payload($this->cart($cartId)));
    }

    private function cart(int $cartId): Cart
    {
        $cart = Cart::query()->with(['lines.variant.product.media', 'lines.variant.inventory'])->findOrFail($cartId);
        $customerId = request()->user('customer')?->getKey();

        if ($customerId !== null) {
            abort_unless((int) $cart->customer_id === (int) $customerId, 404);
        } else {
            $sessionCartIds = array_filter([request()->session()->get('cart_id'), request()->session()->get('cart_id_'.app('current_store')->getKey())]);
            abort_unless(in_array($cart->getKey(), $sessionCartIds, true), 404);
        }

        return $cart;
    }

    /** @return array<string, mixed> */
    private function payload(Cart $cart): array
    {
        $lines = $cart->lines->map(fn ($line): array => ['id' => $line->id, 'variant_id' => $line->variant_id, 'product_title' => $line->variant->product->title, 'variant_title' => $line->variant->title, 'sku' => $line->variant->sku, 'quantity' => $line->quantity, 'unit_price_amount' => $line->unit_price_amount, 'line_subtotal_amount' => $line->line_subtotal_amount, 'line_discount_amount' => $line->line_discount_amount, 'line_total_amount' => $line->line_total_amount, 'image_url' => $line->variant->product->media->first()?->url, 'requires_shipping' => $line->variant->requires_shipping, 'available_quantity' => $line->variant->inventory?->availableQuantity() ?? 0])->values()->all();
        $subtotal = (int) collect($lines)->sum('line_total_amount');

        return ['id' => $cart->id, 'store_id' => $cart->store_id, 'customer_id' => $cart->customer_id, 'currency' => $cart->currency, 'cart_version' => $cart->cart_version, 'status' => $cart->status, 'lines' => $lines, 'totals' => ['subtotal' => $subtotal, 'discount' => 0, 'total' => $subtotal, 'currency' => $cart->currency, 'line_count' => count($lines), 'item_count' => (int) collect($lines)->sum('quantity')], 'created_at' => $cart->created_at, 'updated_at' => $cart->updated_at];
    }
}
<?php

namespace App\Http\Controllers\Api;

use App\Enums\PaymentMethod;
use App\Exceptions\InsufficientInventoryException;
use App\Exceptions\InvalidDiscountException;
use App\Http\Controllers\Controller;
use App\Models\Cart;
use App\Models\Checkout;
use App\Services\CheckoutService;
use App\Services\DiscountService;
use App\Services\PaymentService;
use App\Services\PricingEngine;
use App\Services\ShippingCalculator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class StorefrontCheckoutController extends Controller
{
    public function __construct(private readonly CheckoutService $checkouts, private readonly PricingEngine $pricing, private readonly ShippingCalculator $shipping, private readonly DiscountService $discounts, private readonly PaymentService $payments) {}

    public function store(Request $request): JsonResponse
    {
        $data = $request->validate(['cart_id' => ['required', 'integer'], 'email' => ['required', 'email']]);
        $cart = Cart::query()->with('lines')->findOrFail($data['cart_id']);
        $customerId = $request->user('customer')?->getKey();
        if ($customerId !== null) {
            abort_unless((int) $cart->customer_id === (int) $customerId, 404);
        } else {
            $sessionCartIds = array_filter([$request->session()->get('cart_id'), $request->session()->get('cart_id_'.app('current_store')->getKey())]);
            abort_unless(in_array($cart->getKey(), $sessionCartIds, true), 404);
        }
        $checkout = $this->checkouts->create($cart, $data['email'], $request->user('customer'));
        $this->pricing->calculate($checkout);

        return response()->json($this->payload($checkout->refresh()), 201);
    }

    public function show(int $checkoutId): JsonResponse
    {
        $checkout = $this->checkout($checkoutId);

        if ($checkout->isExpired()) {
            return response()->json(['message' => 'Checkout expired.'], 410);
        }

        return response()->json($this->payload($checkout));
    }

    public function address(Request $request, int $checkoutId): JsonResponse
    {
        $data = $request->validate(['shipping_address' => ['required', 'array'], 'billing_address' => ['nullable', 'array'], 'use_shipping_as_billing' => ['nullable', 'boolean']]);
        try {
            $checkout = $this->checkouts->setAddress($this->checkout($checkoutId), $data['shipping_address'], $data['billing_address'] ?? null, $data['use_shipping_as_billing'] ?? true);
        } catch (\LogicException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'checkout_state_invalid'], 422);
        }

        return response()->json($this->payload($checkout));
    }

    public function shippingMethod(Request $request, int $checkoutId): JsonResponse
    {
        $data = $request->validate(['shipping_method_id' => ['required', 'integer'], 'shipping_rate_id' => ['nullable', 'integer']]);
        try {
            $checkout = $this->checkouts->setShippingMethod($this->checkout($checkoutId), $data['shipping_rate_id'] ?? $data['shipping_method_id']);
        } catch (\LogicException|\InvalidArgumentException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'checkout_state_invalid'], 422);
        }

        return response()->json($this->payload($checkout));
    }

    public function applyDiscount(Request $request, int $checkoutId): JsonResponse
    {
        $data = $request->validate(['code' => ['required', 'string', 'max:64']]);
        $checkout = $this->checkout($checkoutId);
        try {
            $discount = $this->discounts->validate($data['code'], app('current_store'), $checkout->cart);
        } catch (InvalidDiscountException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => $exception->reason], 422);
        }
        $checkout->update(['discount_code' => $discount->code]);
        $this->pricing->calculate($checkout->refresh());

        return response()->json($this->payload($checkout->refresh()));
    }

    public function removeDiscount(int $checkoutId): JsonResponse
    {
        $checkout = $this->checkout($checkoutId);
        $checkout->update(['discount_code' => null]);
        $this->pricing->calculate($checkout->refresh());

        return response()->json($this->payload($checkout->refresh()));
    }

    public function pay(Request $request, int $checkoutId): JsonResponse
    {
        $data = $request->validate(['payment_method' => ['required', 'in:credit_card,paypal,bank_transfer'], 'card_number' => ['nullable', 'string'], 'card_expiry' => ['nullable', 'string'], 'card_cvc' => ['nullable', 'string']]);
        try {
            $checkout = $this->checkouts->selectPaymentMethod($this->checkout($checkoutId), $data['payment_method']);
            $order = $this->payments->pay($checkout, PaymentMethod::from($data['payment_method']), $data);
        } catch (InsufficientInventoryException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'insufficient_inventory'], 422);
        } catch (\LogicException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'checkout_state_invalid'], 422);
        }

        if ($order === null) {
            return response()->json(['message' => 'Payment failed.', 'code' => 'payment_failed'], 422);
        }

        return response()->json(['order' => ['id' => $order->id, 'order_number' => $order->order_number, 'status' => $order->status, 'financial_status' => $order->financial_status, 'total_amount' => $order->total_amount], 'message' => $order->financial_status->value === 'pending' ? 'Bank transfer instructions generated.' : 'Order confirmed.']);
    }

    public function paymentMethod(Request $request, int $checkoutId): JsonResponse
    {
        $data = $request->validate(['payment_method' => ['required', 'in:credit_card,paypal,bank_transfer']]);
        try {
            $checkout = $this->checkouts->selectPaymentMethod($this->checkout($checkoutId), $data['payment_method']);
        } catch (InsufficientInventoryException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'insufficient_inventory'], 422);
        } catch (\LogicException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'checkout_state_invalid'], 422);
        }

        return response()->json($this->payload($checkout));
    }

    private function checkout(int $checkoutId): Checkout
    {
        $checkout = Checkout::query()->with(['cart.lines.variant.product', 'shippingRate'])->findOrFail($checkoutId);
        $customerId = request()->user('customer')?->getKey();

        if ($customerId !== null) {
            abort_unless((int) $checkout->customer_id === (int) $customerId, 404);
        } else {
            $sessionCartIds = array_filter([request()->session()->get('cart_id'), request()->session()->get('cart_id_'.app('current_store')->getKey())]);
            abort_unless(in_array($checkout->cart_id, $sessionCartIds, true), 404);
        }

        return $checkout;
    }

    /** @return array<string, mixed> */
    private function payload(Checkout $checkout): array
    {
        $totals = $checkout->totals_json ?? $this->pricing->calculate($checkout)->toArray();
        $rates = $checkout->shipping_address_json === null ? collect() : $this->shipping->getAvailableRates(app('current_store'), $checkout->shipping_address_json);

        return ['id' => $checkout->id, 'store_id' => $checkout->store_id, 'cart_id' => $checkout->cart_id, 'customer_id' => $checkout->customer_id, 'status' => $checkout->status, 'email' => $checkout->email, 'payment_method' => $checkout->payment_method, 'shipping_address_json' => $checkout->shipping_address_json, 'billing_address_json' => $checkout->billing_address_json, 'shipping_method_id' => $checkout->shipping_rate_id, 'discount_code' => $checkout->discount_code, 'lines' => $checkout->cart->lines->map(fn ($line): array => ['variant_id' => $line->variant_id, 'product_title' => $line->variant->product->title, 'variant_title' => $line->variant->title, 'sku' => $line->variant->sku, 'quantity' => $line->quantity, 'unit_price_amount' => $line->unit_price_amount, 'line_total_amount' => $line->line_total_amount])->all(), 'totals' => $totals, 'available_shipping_methods' => $rates->map(fn ($rate): array => ['id' => $rate->id, 'name' => $rate->name, 'type' => $rate->type, 'price_amount' => $rate->price_amount, 'currency' => $rate->currency, 'estimated_days_min' => $rate->estimated_days_min, 'estimated_days_max' => $rate->estimated_days_max])->all(), 'expires_at' => $checkout->expires_at, 'created_at' => $checkout->created_at];
    }
}


text:
Script completed
Wall time 0.1 seconds
Output:

text:
<?php

namespace App\Http\Controllers\Api;

use App\Enums\ProductStatus;
use App\Http\Controllers\Controller;
use App\Models\Collection;
use App\Models\Customer;
use App\Models\Discount;
use App\Models\Order;
use App\Models\Page;
use App\Models\Product;
use App\Models\ShippingRate;
use App\Models\ShippingZone;
use App\Models\TaxSettings;
use App\Models\Theme;
use App\Services\FulfillmentService;
use App\Services\ProductService;
use App\Services\RefundService;
use App\Services\SearchService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class AdminController extends Controller
{
    public function products(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['status' => ['nullable', 'in:draft,active,archived'], 'query' => ['nullable', 'string'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100']]);
        $products = Product::withoutGlobalScopes()->where('store_id', $storeId)->with(['variants.inventory', 'collections'])->when($data['status'] ?? null, fn ($query, string $status) => $query->where('status', $status))->when($data['query'] ?? null, fn ($query, string $queryText) => $query->where(function ($nested) use ($queryText): void {
            $nested->where('title', 'like', '%'.$queryText.'%')->orWhere('vendor', 'like', '%'.$queryText.'%')->orWhereHas('variants', fn ($variants) => $variants->where('sku', 'like', '%'.$queryText.'%'));
        }))->latest('updated_at')->paginate($data['per_page'] ?? 25);

        return $this->paginated($products);
    }

    public function storeProduct(Request $request, int $storeId, ProductService $products): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['title' => ['required', 'string', 'max:255'], 'handle' => ['nullable', 'string', 'max:255'], 'description' => ['nullable', 'string'], 'vendor' => ['nullable', 'string', 'max:255'], 'product_type' => ['nullable', 'string', 'max:255'], 'status' => ['nullable', 'in:draft,active,archived'], 'variants' => ['nullable', 'array']]);
        $product = $products->create(app('current_store'), [...$data, 'status' => ProductStatus::from($data['status'] ?? ProductStatus::Draft->value)]);

        return response()->json(['data' => $product->load('variants')->toArray()], 201);
    }

    public function showProduct(int $storeId, int $productId): JsonResponse
    {
        $this->assertStore($storeId);
        $product = Product::withoutGlobalScopes()->where('store_id', $storeId)->with(['variants.inventory', 'options.values', 'media', 'collections'])->findOrFail($productId);

        return response()->json(['data' => $product->toArray()]);
    }

    public function updateProduct(Request $request, int $storeId, int $productId, ProductService $products): JsonResponse
    {
        $this->assertStore($storeId);
        $product = Product::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($productId);
        $data = $request->validate(['title' => ['sometimes', 'string', 'max:255'], 'description' => ['sometimes', 'nullable', 'string'], 'vendor' => ['sometimes', 'nullable', 'string', 'max:255'], 'product_type' => ['sometimes', 'nullable', 'string', 'max:255'], 'status' => ['sometimes', 'in:draft,active,archived']]);

        return response()->json(['data' => $products->update($product, $data)->toArray()]);
    }

    public function deleteProduct(int $storeId, int $productId, ProductService $products): JsonResponse
    {
        $this->assertStore($storeId);
        $product = Product::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($productId);
        $products->transitionStatus($product, ProductStatus::Archived);

        return response()->json(['message' => 'Product archived']);
    }

    public function collections(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['query' => ['nullable', 'string'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100']]);
        $collections = Collection::withoutGlobalScopes()->where('store_id', $storeId)->withCount('products')->when($data['query'] ?? null, fn ($query, string $queryText) => $query->where('title', 'like', '%'.$queryText.'%'))->latest()->paginate($data['per_page'] ?? 25);

        return $this->paginated($collections);
    }

    public function storeCollection(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['title' => ['required', 'string', 'max:255'], 'handle' => ['nullable', 'string', 'max:255'], 'description_html' => ['nullable', 'string'], 'status' => ['nullable', 'in:draft,active,archived'], 'product_ids' => ['nullable', 'array']]);
        $collection = Collection::withoutGlobalScopes()->create(['store_id' => $storeId, 'title' => $data['title'], 'handle' => $data['handle'] ?? Str::slug($data['title']), 'description' => $data['description_html'] ?? null, 'status' => $data['status'] ?? 'active']);
        $this->syncCollectionProducts($collection, $data['product_ids'] ?? [], $storeId);

        return response()->json(['data' => $collection->load('products')->toArray()], 201);
    }

    public function updateCollection(Request $request, int $storeId, int $collectionId): JsonResponse
    {
        $this->assertStore($storeId);
        $collection = Collection::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($collectionId);
        $data = $request->validate(['title' => ['sometimes', 'string', 'max:255'], 'description_html' => ['sometimes', 'nullable', 'string'], 'status' => ['sometimes', 'in:draft,active,archived'], 'product_ids' => ['sometimes', 'array']]);
        $collection->update(array_filter(['title' => $data['title'] ?? null, 'description' => $data['description_html'] ?? null, 'status' => $data['status'] ?? null], fn ($value): bool => $value !== null));

        if (array_key_exists('product_ids', $data)) {
            $this->syncCollectionProducts($collection, $data['product_ids'], $storeId);
        }

        return response()->json(['data' => $collection->load('products')->toArray()]);
    }

    public function deleteCollection(int $storeId, int $collectionId): JsonResponse
    {
        $this->assertStore($storeId);
        Collection::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($collectionId)->delete();

        return response()->json(['message' => 'Collection deleted']);
    }

    public function orders(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['status' => ['nullable', 'string'], 'financial_status' => ['nullable', 'string'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100']]);
        $orders = Order::withoutGlobalScopes()->where('store_id', $storeId)->with('customer')->when($data['status'] ?? null, fn ($query, string $status) => $query->where('status', $status))->when($data['financial_status'] ?? null, fn ($query, string $status) => $query->where('financial_status', $status))->latest('placed_at')->paginate($data['per_page'] ?? 25);

        return $this->paginated($orders);
    }

    public function showOrder(int $storeId, int $orderId): JsonResponse
    {
        $this->assertStore($storeId);
        $order = Order::withoutGlobalScopes()->where('store_id', $storeId)->with(['customer', 'lines', 'payments', 'refunds', 'fulfillments.lines'])->findOrFail($orderId);

        return response()->json(['data' => $order->toArray()]);
    }

    public function customers(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['query' => ['nullable', 'string'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100']]);
        $customers = Customer::withoutGlobalScopes()->where('store_id', $storeId)->when($data['query'] ?? null, fn ($query, string $queryText) => $query->where(function ($nested) use ($queryText): void {
            $nested->where('email', 'like', '%'.$queryText.'%')->orWhere('first_name', 'like', '%'.$queryText.'%')->orWhere('last_name', 'like', '%'.$queryText.'%');
        }))->latest()->paginate($data['per_page'] ?? 25);

        return $this->paginated($customers);
    }

    public function discounts(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);

        return $this->paginated(Discount::withoutGlobalScopes()->where('store_id', $storeId)->latest()->paginate($request->integer('per_page', 25)));
    }

    public function storeDiscount(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['code' => ['nullable', 'string', 'max:64'], 'type' => ['required', 'string'], 'value_type' => ['required', 'in:percent,fixed,free_shipping'], 'value_amount' => ['required', 'integer', 'min:0'], 'status' => ['nullable', 'in:active,inactive'], 'usage_limit' => ['nullable', 'integer', 'min:1'], 'starts_at' => ['nullable', 'date'], 'ends_at' => ['nullable', 'date'], 'rules_json' => ['nullable', 'array']]);
        $discount = Discount::withoutGlobalScopes()->create([...$data, 'store_id' => $storeId, 'status' => $data['status'] ?? 'active', 'usage_count' => 0]);

        return response()->json(['data' => $discount], 201);
    }

    public function updateDiscount(Request $request, int $storeId, int $discountId): JsonResponse
    {
        $this->assertStore($storeId);
        $discount = Discount::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($discountId);
        $discount->update($request->validate(['code' => ['sometimes', 'nullable', 'string', 'max:64'], 'type' => ['sometimes', 'string'], 'value_type' => ['sometimes', 'in:percent,fixed,free_shipping'], 'value_amount' => ['sometimes', 'integer', 'min:0'], 'status' => ['sometimes', 'in:active,inactive'], 'usage_limit' => ['sometimes', 'nullable', 'integer', 'min:1'], 'starts_at' => ['sometimes', 'nullable', 'date'], 'ends_at' => ['sometimes', 'nullable', 'date'], 'rules_json' => ['sometimes', 'array']]));

        return response()->json(['data' => $discount->refresh()]);
    }

    public function deleteDiscount(int $storeId, int $discountId): JsonResponse
    {
        $this->assertStore($storeId);
        Discount::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($discountId)->delete();

        return response()->json(['message' => 'Discount deleted.']);
    }

    public function fulfillOrder(Request $request, int $storeId, int $orderId, FulfillmentService $fulfillments): JsonResponse
    {
        $this->assertStore($storeId);
        $order = Order::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($orderId);
        $data = $request->validate(['lines' => ['required', 'array', 'min:1'], 'lines.*.order_line_id' => ['required', 'integer'], 'lines.*.quantity' => ['required', 'integer', 'min:1'], 'tracking_company' => ['nullable', 'string', 'max:100'], 'tracking_number' => ['nullable', 'string', 'max:255'], 'tracking_url' => ['nullable', 'url']]);
        $fulfillment = $fulfillments->create($order, $data['lines'], array_filter(['tracking_company' => $data['tracking_company'] ?? null, 'tracking_number' => $data['tracking_number'] ?? null, 'tracking_url' => $data['tracking_url'] ?? null]));

        return response()->json(['data' => $fulfillment->toArray()], 201);
    }

    public function refundOrder(Request $request, int $storeId, int $orderId, RefundService $refunds): JsonResponse
    {
        $this->assertStore($storeId);
        $order = Order::withoutGlobalScopes()->where('store_id', $storeId)->with('payments')->findOrFail($orderId);
        $data = $request->validate(['payment_id' => ['nullable', 'integer'], 'amount' => ['nullable', 'integer', 'min:1'], 'lines' => ['nullable', 'array'], 'reason' => ['nullable', 'string', 'max:500'], 'restock' => ['nullable', 'boolean']]);
        $payment = $order->payments->firstWhere('id', $data['payment_id'] ?? null) ?? $order->payments->firstWhere('status', 'captured');
        abort_unless($payment !== null, 422, 'No captured payment is available.');
        $refund = $refunds->create($order, $payment, $data['lines'] ?? $data['amount'] ?? null, $data['reason'] ?? null, (bool) ($data['restock'] ?? false), $data['lines'] ?? []);

        return response()->json(['data' => $refund->toArray()], 201);
    }

    public function shippingZones(int $storeId): JsonResponse
    {
        $this->assertStore($storeId);

        return response()->json(['data' => ShippingZone::withoutGlobalScopes()->where('store_id', $storeId)->with('rates')->get()]);
    }

    public function storeShippingZone(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['name' => ['required', 'string', 'max:255'], 'countries_json' => ['nullable', 'array'], 'regions_json' => ['nullable', 'array']]);

        return response()->json(['data' => ShippingZone::withoutGlobalScopes()->create([...$data, 'store_id' => $storeId])], 201);
    }

    public function updateShippingZone(Request $request, int $storeId, int $zoneId): JsonResponse
    {
        $this->assertStore($storeId);
        $zone = ShippingZone::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($zoneId);
        $zone->update($request->validate(['name' => ['sometimes', 'string', 'max:255'], 'countries_json' => ['sometimes', 'array'], 'regions_json' => ['sometimes', 'array']]));

        return response()->json(['data' => $zone->refresh()]);
    }

    public function storeShippingRate(Request $request, int $storeId, int $zoneId): JsonResponse
    {
        $this->assertStore($storeId);
        ShippingZone::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($zoneId);
        $data = $request->validate(['name' => ['required', 'string', 'max:255'], 'type' => ['required', 'in:flat,weight,price,carrier'], 'price_amount' => ['required', 'integer', 'min:0'], 'currency' => ['required', 'size:3'], 'config_json' => ['nullable', 'array'], 'is_active' => ['nullable', 'boolean'], 'estimated_days_min' => ['nullable', 'integer', 'min:0'], 'estimated_days_max' => ['nullable', 'integer', 'min:0']]);

        return response()->json(['data' => ShippingRate::create([...$data, 'shipping_zone_id' => $zoneId, 'is_active' => $data['is_active'] ?? true])], 201);
    }

    public function taxSettings(int $storeId): JsonResponse
    {
        $this->assertStore($storeId);

        return response()->json(['data' => TaxSettings::withoutGlobalScopes()->firstOrCreate(['store_id' => $storeId])]);
    }

    public function updateTaxSettings(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['mode' => ['sometimes', 'string'], 'provider' => ['sometimes', 'nullable', 'string'], 'prices_include_tax' => ['sometimes', 'boolean'], 'default_rate_basis_points' => ['sometimes', 'integer', 'min:0', 'max:10000'], 'rates_json' => ['sometimes', 'array'], 'provider_config_json' => ['sometimes', 'array']]);
        $settings = TaxSettings::withoutGlobalScopes()->updateOrCreate(['store_id' => $storeId], $data);

        return response()->json(['data' => $settings]);
    }

    public function pages(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);

        return $this->paginated(Page::withoutGlobalScopes()->where('store_id', $storeId)->latest()->paginate($request->integer('per_page', 25)));
    }

    public function storePage(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['title' => ['required', 'string', 'max:255'], 'handle' => ['nullable', 'string', 'max:255'], 'body_html' => ['nullable', 'string'], 'status' => ['required', 'in:draft,published']]);
        $page = Page::withoutGlobalScopes()->create([...$data, 'store_id' => $storeId, 'handle' => $data['handle'] ?? Str::slug($data['title']), 'published_at' => $data['status'] === 'published' ? now() : null]);

        return response()->json(['data' => $page], 201);
    }

    public function updatePage(Request $request, int $storeId, int $pageId): JsonResponse
    {
        $this->assertStore($storeId);
        $page = Page::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($pageId);
        $data = $request->validate(['title' => ['sometimes', 'string', 'max:255'], 'handle' => ['sometimes', 'string', 'max:255'], 'body_html' => ['sometimes', 'nullable', 'string'], 'status' => ['sometimes', 'in:draft,published']]);
        if (($data['status'] ?? null) === 'published') {
            $data['published_at'] = $page->published_at ?? now();
        }
        $page->update($data);

        return response()->json(['data' => $page->refresh()]);
    }

    public function deletePage(int $storeId, int $pageId): JsonResponse
    {
        $this->assertStore($storeId);
        Page::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($pageId)->delete();

        return response()->json(['message' => 'Page deleted.']);
    }

    public function storeTheme(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $data = $request->validate(['name' => ['required', 'string', 'max:255'], 'version' => ['nullable', 'string', 'max:30'], 'settings' => ['nullable', 'array']]);

        return response()->json(['data' => Theme::withoutGlobalScopes()->create([...$data, 'store_id' => $storeId, 'status' => 'draft'])], 201);
    }

    public function publishTheme(int $storeId, int $themeId): JsonResponse
    {
        $this->assertStore($storeId);
        $theme = Theme::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($themeId);
        Theme::withoutGlobalScopes()->where('store_id', $storeId)->update(['status' => 'draft']);
        $theme->update(['status' => 'published']);

        return response()->json(['data' => $theme->refresh()]);
    }

    public function updateThemeSettings(Request $request, int $storeId, int $themeId): JsonResponse
    {
        $this->assertStore($storeId);
        $theme = Theme::withoutGlobalScopes()->where('store_id', $storeId)->findOrFail($themeId);
        $theme->update(['settings' => $request->validate(['settings' => ['required', 'array']])['settings']]);

        return response()->json(['data' => $theme->refresh()]);
    }

    public function reindex(SearchService $search, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        Product::withoutGlobalScopes()->where('store_id', $storeId)->each(fn (Product $product) => $search->syncProduct($product));

        return response()->json(['status' => 'completed']);
    }

    public function searchStatus(int $storeId): JsonResponse
    {
        $this->assertStore($storeId);

        return response()->json(['status' => 'ready', 'product_count' => Product::withoutGlobalScopes()->where('store_id', $storeId)->count()]);
    }

    public function analyticsSummary(Request $request, int $storeId): JsonResponse
    {
        $this->assertStore($storeId);
        $days = \App\Models\AnalyticsDaily::withoutGlobalScopes()->where('store_id', $storeId)->whereBetween('date', [$request->input('from', now()->subDays(29)->toDateString()), $request->input('to', now()->toDateString())])->get();

        return response()->json(['data' => ['visits' => (int) $days->sum('visits_count'), 'orders' => (int) $days->sum('orders_count'), 'revenue_amount' => (int) $days->sum('revenue_amount'), 'checkout_completed' => (int) $days->sum('checkout_completed_count'), 'days' => $days]]);
    }

    private function assertStore(int $storeId): void
    {
        abort_unless((int) app('current_store')->getKey() === $storeId, 404);
    }

    private function syncCollectionProducts(Collection $collection, array $productIds, int $storeId): void
    {
        $validIds = Product::withoutGlobalScopes()->where('store_id', $storeId)->whereIn('id', $productIds)->pluck('id')->all();
        $collection->products()->sync(array_fill_keys($validIds, ['position' => 0]));
    }

    private function paginated(LengthAwarePaginator $paginator): JsonResponse
    {
        return response()->json(['data' => $paginator->items(), 'meta' => ['current_page' => $paginator->currentPage(), 'per_page' => $paginator->perPage(), 'total' => $paginator->total(), 'last_page' => $paginator->lastPage()]]);
    }
}
<?php

namespace App\Http\Controllers\Api;

use App\Exceptions\CartVersionConflictException;
use App\Exceptions\InsufficientInventoryException;
use App\Http\Controllers\Controller;
use App\Models\Cart;
use App\Services\CartService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class StorefrontCartController extends Controller
{
    public function __construct(private readonly CartService $carts) {}

    public function store(Request $request): JsonResponse
    {
        $data = $request->validate(['currency' => ['nullable', 'string', 'size:3']]);
        $cart = $this->carts->create(app('current_store'), $request->user('customer'));

        if (isset($data['currency'])) {
            $cart->update(['currency' => strtoupper($data['currency'])]);
        }

        $request->session()->put(['cart_id' => $cart->getKey(), 'cart_id_'.app('current_store')->getKey() => $cart->getKey()]);

        return response()->json($this->payload($cart->refresh()), 201);
    }

    public function show(int $cartId): JsonResponse
    {
        return response()->json($this->payload($this->cart($cartId)));
    }

    public function addLine(Request $request, int $cartId): JsonResponse
    {
        $data = $request->validate(['variant_id' => ['required', 'integer'], 'quantity' => ['required', 'integer', 'min:1', 'max:9999'], 'cart_version' => ['nullable', 'integer'], 'expected_version' => ['nullable', 'integer']]);
        $cart = $this->cart($cartId);

        try {
            $this->carts->assertVersion($cart, $data['cart_version'] ?? $data['expected_version'] ?? null);
            $this->carts->addLine($cart, $data['variant_id'], $data['quantity']);
        } catch (CartVersionConflictException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'cart' => $this->payload($cart->refresh())], 409);
        } catch (InsufficientInventoryException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'insufficient_inventory'], 422);
        }

        return response()->json($this->payload($this->cart($cartId)), 201);
    }

    public function updateLine(Request $request, int $cartId, int $lineId): JsonResponse
    {
        $data = $request->validate(['quantity' => ['required', 'integer', 'min:1', 'max:9999'], 'cart_version' => ['required', 'integer']]);
        $cart = $this->cart($cartId);

        try {
            $this->carts->assertVersion($cart, $data['cart_version']);
            $this->carts->updateLineQuantity($cart, $lineId, $data['quantity']);
        } catch (CartVersionConflictException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'cart' => $this->payload($cart->refresh())], 409);
        } catch (InsufficientInventoryException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'insufficient_inventory'], 422);
        }

        return response()->json($this->payload($this->cart($cartId)));
    }

    public function removeLine(Request $request, int $cartId, int $lineId): JsonResponse
    {
        $data = $request->validate(['cart_version' => ['required', 'integer']]);
        $cart = $this->cart($cartId);

        try {
            $this->carts->assertVersion($cart, $data['cart_version']);
            $this->carts->removeLine($cart, $lineId);
        } catch (CartVersionConflictException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'cart' => $this->payload($cart->refresh())], 409);
        }

        return response()->json($this->payload($this->cart($cartId)));
    }

    private function cart(int $cartId): Cart
    {
        $cart = Cart::query()->with(['lines.variant.product.media', 'lines.variant.inventory'])->findOrFail($cartId);
        $customerId = request()->user('customer')?->getKey();

        if ($customerId !== null) {
            abort_unless((int) $cart->customer_id === (int) $customerId, 404);
        } else {
            $sessionCartIds = array_filter([request()->session()->get('cart_id'), request()->session()->get('cart_id_'.app('current_store')->getKey())]);
            abort_unless(in_array($cart->getKey(), $sessionCartIds, true), 404);
        }

        return $cart;
    }

    /** @return array<string, mixed> */
    private function payload(Cart $cart): array
    {
        $lines = $cart->lines->map(fn ($line): array => ['id' => $line->id, 'variant_id' => $line->variant_id, 'product_title' => $line->variant->product->title, 'variant_title' => $line->variant->title, 'sku' => $line->variant->sku, 'quantity' => $line->quantity, 'unit_price_amount' => $line->unit_price_amount, 'line_subtotal_amount' => $line->line_subtotal_amount, 'line_discount_amount' => $line->line_discount_amount, 'line_total_amount' => $line->line_total_amount, 'image_url' => $line->variant->product->media->first()?->url, 'requires_shipping' => $line->variant->requires_shipping, 'available_quantity' => $line->variant->inventory?->availableQuantity() ?? 0])->values()->all();
        $subtotal = (int) collect($lines)->sum('line_total_amount');

        return ['id' => $cart->id, 'store_id' => $cart->store_id, 'customer_id' => $cart->customer_id, 'currency' => $cart->currency, 'cart_version' => $cart->cart_version, 'status' => $cart->status, 'lines' => $lines, 'totals' => ['subtotal' => $subtotal, 'discount' => 0, 'total' => $subtotal, 'currency' => $cart->currency, 'line_count' => count($lines), 'item_count' => (int) collect($lines)->sum('quantity')], 'created_at' => $cart->created_at, 'updated_at' => $cart->updated_at];
    }
}
<?php

namespace App\Http\Controllers\Api;

use App\Enums\PaymentMethod;
use App\Exceptions\InsufficientInventoryException;
use App\Exceptions\InvalidDiscountException;
use App\Http\Controllers\Controller;
use App\Models\Cart;
use App\Models\Checkout;
use App\Services\CheckoutService;
use App\Services\DiscountService;
use App\Services\PaymentService;
use App\Services\PricingEngine;
use App\Services\ShippingCalculator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class StorefrontCheckoutController extends Controller
{
    public function __construct(private readonly CheckoutService $checkouts, private readonly PricingEngine $pricing, private readonly ShippingCalculator $shipping, private readonly DiscountService $discounts, private readonly PaymentService $payments) {}

    public function store(Request $request): JsonResponse
    {
        $data = $request->validate(['cart_id' => ['required', 'integer'], 'email' => ['required', 'email']]);
        $cart = Cart::query()->with('lines')->findOrFail($data['cart_id']);
        $customerId = $request->user('customer')?->getKey();
        if ($customerId !== null) {
            abort_unless((int) $cart->customer_id === (int) $customerId, 404);
        } else {
            $sessionCartIds = array_filter([$request->session()->get('cart_id'), $request->session()->get('cart_id_'.app('current_store')->getKey())]);
            abort_unless(in_array($cart->getKey(), $sessionCartIds, true), 404);
        }
        $checkout = $this->checkouts->create($cart, $data['email'], $request->user('customer'));
        $this->pricing->calculate($checkout);

        return response()->json($this->payload($checkout->refresh()), 201);
    }

    public function show(int $checkoutId): JsonResponse
    {
        $checkout = $this->checkout($checkoutId);

        if ($checkout->isExpired()) {
            return response()->json(['message' => 'Checkout expired.'], 410);
        }

        return response()->json($this->payload($checkout));
    }

    public function address(Request $request, int $checkoutId): JsonResponse
    {
        $data = $request->validate(['shipping_address' => ['required', 'array'], 'billing_address' => ['nullable', 'array'], 'use_shipping_as_billing' => ['nullable', 'boolean']]);
        try {
            $checkout = $this->checkouts->setAddress($this->checkout($checkoutId), $data['shipping_address'], $data['billing_address'] ?? null, $data['use_shipping_as_billing'] ?? true);
        } catch (\LogicException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'checkout_state_invalid'], 422);
        }

        return response()->json($this->payload($checkout));
    }

    public function shippingMethod(Request $request, int $checkoutId): JsonResponse
    {
        $data = $request->validate(['shipping_method_id' => ['required', 'integer'], 'shipping_rate_id' => ['nullable', 'integer']]);
        try {
            $checkout = $this->checkouts->setShippingMethod($this->checkout($checkoutId), $data['shipping_rate_id'] ?? $data['shipping_method_id']);
        } catch (\LogicException|\InvalidArgumentException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'checkout_state_invalid'], 422);
        }

        return response()->json($this->payload($checkout));
    }

    public function applyDiscount(Request $request, int $checkoutId): JsonResponse
    {
        $data = $request->validate(['code' => ['required', 'string', 'max:64']]);
        $checkout = $this->checkout($checkoutId);
        try {
            $discount = $this->discounts->validate($data['code'], app('current_store'), $checkout->cart);
        } catch (InvalidDiscountException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => $exception->reason], 422);
        }
        $checkout->update(['discount_code' => $discount->code]);
        $this->pricing->calculate($checkout->refresh());

        return response()->json($this->payload($checkout->refresh()));
    }

    public function removeDiscount(int $checkoutId): JsonResponse
    {
        $checkout = $this->checkout($checkoutId);
        $checkout->update(['discount_code' => null]);
        $this->pricing->calculate($checkout->refresh());

        return response()->json($this->payload($checkout->refresh()));
    }

    public function pay(Request $request, int $checkoutId): JsonResponse
    {
        $data = $request->validate(['payment_method' => ['required', 'in:credit_card,paypal,bank_transfer'], 'card_number' => ['nullable', 'string'], 'card_expiry' => ['nullable', 'string'], 'card_cvc' => ['nullable', 'string']]);
        try {
            $checkout = $this->checkouts->selectPaymentMethod($this->checkout($checkoutId), $data['payment_method']);
            $order = $this->payments->pay($checkout, PaymentMethod::from($data['payment_method']), $data);
        } catch (InsufficientInventoryException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'insufficient_inventory'], 422);
        } catch (\LogicException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'checkout_state_invalid'], 422);
        }

        if ($order === null) {
            return response()->json(['message' => 'Payment failed.', 'code' => 'payment_failed'], 422);
        }

        return response()->json(['order' => ['id' => $order->id, 'order_number' => $order->order_number, 'status' => $order->status, 'financial_status' => $order->financial_status, 'total_amount' => $order->total_amount], 'message' => $order->financial_status->value === 'pending' ? 'Bank transfer instructions generated.' : 'Order confirmed.']);
    }

    public function paymentMethod(Request $request, int $checkoutId): JsonResponse
    {
        $data = $request->validate(['payment_method' => ['required', 'in:credit_card,paypal,bank_transfer']]);
        try {
            $checkout = $this->checkouts->selectPaymentMethod($this->checkout($checkoutId), $data['payment_method']);
        } catch (InsufficientInventoryException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'insufficient_inventory'], 422);
        } catch (\LogicException $exception) {
            return response()->json(['message' => $exception->getMessage(), 'code' => 'checkout_state_invalid'], 422);
        }

        return response()->json($this->payload($checkout));
    }

    private function checkout(int $checkoutId): Checkout
    {
        $checkout = Checkout::query()->with(['cart.lines.variant.product', 'shippingRate'])->findOrFail($checkoutId);
        $customerId = request()->user('customer')?->getKey();

        if ($customerId !== null) {
            abort_unless((int) $checkout->customer_id === (int) $customerId, 404);
        } else {
            $sessionCartIds = array_filter([request()->session()->get('cart_id'), request()->session()->get('cart_id_'.app('current_store')->getKey())]);
            abort_unless(in_array($checkout->cart_id, $sessionCartIds, true), 404);
        }

        return $checkout;
    }

    /** @return array<string, mixed> */
    private function payload(Checkout $checkout): array
    {
        $totals = $checkout->totals_json ?? $this->pricing->calculate($checkout)->toArray();
        $rates = $checkout->shipping_address_json === null ? collect() : $this->shipping->getAvailableRates(app('current_store'), $checkout->shipping_address_json);

        return ['id' => $checkout->id, 'store_id' => $checkout->store_id, 'cart_id' => $checkout->cart_id, 'customer_id' => $checkout->customer_id, 'status' => $checkout->status, 'email' => $checkout->email, 'payment_method' => $checkout->payment_method, 'shipping_address_json' => $checkout->shipping_address_json, 'billing_address_json' => $checkout->billing_address_json, 'shipping_method_id' => $checkout->shipping_rate_id, 'discount_code' => $checkout->discount_code, 'lines' => $checkout->cart->lines->map(fn ($line): array => ['variant_id' => $line->variant_id, 'product_title' => $line->variant->product->title, 'variant_title' => $line->variant->title, 'sku' => $line->variant->sku, 'quantity' => $line->quantity, 'unit_price_amount' => $line->unit_price_amount, 'line_total_amount' => $line->line_total_amount])->all(), 'totals' => $totals, 'available_shipping_methods' => $rates->map(fn ($rate): array => ['id' => $rate->id, 'name' => $rate->name, 'type' => $rate->type, 'price_amount' => $rate->price_amount, 'currency' => $rate->currency, 'estimated_days_min' => $rate->estimated_days_min, 'estimated_days_max' => $rate->estimated_days_max])->all(), 'expires_at' => $checkout->expires_at, 'created_at' => $checkout->created_at];
    }
}


exec

status: completed

input:
const p = await tools.update_plan({explanation:"Independent audit found concrete spec gaps; delegated disjoint fixes are in progress while I handle the remaining validation/test coverage and integration.",plan:[
  {step:"Inventory current implementation against specs 01–09",status:"completed"},
  {step:"Implement missing core API/auth, admin CRUD, storefront, business-rule behavior",status:"in_progress"},
  {step:"Add/expand Pest coverage",status:"pending"},
  {step:"Run full migration/tests/build/Playwright review",status:"pending"},
  {step:"Independent audit/fix/progress/commit",status:"in_progress"}
]});
text(p);


text:
Script completed
Wall time 0.0 seconds
Output:


status: completed

input:
const requestNames = [
  "StoreProductRequest","UpdateProductRequest","StoreCollectionRequest","UpdateCollectionRequest",
  "StoreDiscountRequest","UpdateDiscountRequest","SetCheckoutAddressRequest","ApplyDiscountRequest",
  "CreateFulfillmentRequest","CreateRefundRequest","UpdateStoreSettingsRequest","InviteStaffRequest",
  "RegisterCustomerRequest","LoginRequest"
];
for (const name of requestNames) {
  const r = await tools.exec_command({cmd:`php artisan make:request --no-interaction ${name}`,workdir:"/Users/wesner/Herd/shop",yield_time_ms:30000,max_output_tokens:3000});
  text(r.output);
}


type: CommandExecution

command: php artisan make:request --no-interaction StoreProductRequest

type: unknown

status: completed

stdout: INFO  Request [app/Http/Requests/StoreProductRequest.php] created successfully.


command: php artisan make:request --no-interaction UpdateProductRequest

stdout: INFO  Request [app/Http/Requests/UpdateProductRequest.php] created successfully.


command: php artisan make:request --no-interaction StoreCollectionRequest

stdout: INFO  Request [app/Http/Requests/StoreCollectionRequest.php] created successfully.


command: php artisan make:request --no-interaction UpdateCollectionRequest

stdout: INFO  Request [app/Http/Requests/UpdateCollectionRequest.php] created successfully.


command: php artisan make:request --no-interaction StoreDiscountRequest

stdout: INFO  Request [app/Http/Requests/StoreDiscountRequest.php] created successfully.


command: php artisan make:request --no-interaction UpdateDiscountRequest

stdout: INFO  Request [app/Http/Requests/UpdateDiscountRequest.php] created successfully.


command: php artisan make:request --no-interaction SetCheckoutAddressRequest

stdout: INFO  Request [app/Http/Requests/SetCheckoutAddressRequest.php] created successfully.


command: php artisan make:request --no-interaction ApplyDiscountRequest

stdout: INFO  Request [app/Http/Requests/ApplyDiscountRequest.php] created successfully.


command: php artisan make:request --no-interaction CreateFulfillmentRequest

stdout: INFO  Request [app/Http/Requests/CreateFulfillmentRequest.php] created successfully.


command: php artisan make:request --no-interaction CreateRefundRequest

stdout: INFO  Request [app/Http/Requests/CreateRefundRequest.php] created successfully.


command: php artisan make:request --no-interaction UpdateStoreSettingsRequest

stdout: INFO  Request [app/Http/Requests/UpdateStoreSettingsRequest.php] created successfully.


command: php artisan make:request --no-interaction InviteStaffRequest

stdout: INFO  Request [app/Http/Requests/InviteStaffRequest.php] created successfully.


command: php artisan make:request --no-interaction RegisterCustomerRequest

stdout: INFO  Request [app/Http/Requests/RegisterCustomerRequest.php] created successfully.


command: php artisan make:request --no-interaction LoginRequest

stdout: INFO  Request [app/Http/Requests/LoginRequest.php] created successfully.


text:
Script completed
Wall time 2.1 seconds
Output:

text: INFO  Request [app/Http/Requests/StoreProductRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/UpdateProductRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/StoreCollectionRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/UpdateCollectionRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/StoreDiscountRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/UpdateDiscountRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/SetCheckoutAddressRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/ApplyDiscountRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/CreateFulfillmentRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/CreateRefundRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/UpdateStoreSettingsRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/InviteStaffRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/RegisterCustomerRequest.php] created successfully.

text: INFO  Request [app/Http/Requests/LoginRequest.php] created successfully.


status: completed

input:
const r = await tools.exec_command({cmd:"for f in app/Http/Requests/*.php; do echo ===$f; sed -n '1,120p' \"$f\"; done",workdir:"/Users/wesner/Herd/shop",yield_time_ms:10000,max_output_tokens:20000});
text(r.output);


command: for f in app/Http/Requests/*.php; do echo ===$f; sed -n '1,120p' "$f"; done

status: failed

stdout: zsh:1: ==app/Http/Requests/ApplyDiscountRequest.php not found


text:
Script completed
Wall time 0.1 seconds
Output:

text: zsh:1: ==app/Http/Requests/ApplyDiscountRequest.php not found


status: completed

input:
const r = await tools.exec_command({cmd:"sed -n '1,100p' app/Http/Requests/StoreProductRequest.php; sed -n '1,100p' app/Http/Requests/SetCheckoutAddressRequest.php",workdir:"/Users/wesner/Herd/shop",yield_time_ms:10000,max_output_tokens:5000});
text(r.output);


command: sed -n '1,100p' app/Http/Requests/StoreProductRequest.php; sed -n '1,100p' app/Http/Requests/SetCheckoutAddressRequest.php

StoreProductRequest.php

type: read

SetCheckoutAddressRequest.php

stdout:
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            //
        ];
    }
}
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            //
        ];
    }
}


text:
Script completed
Wall time 0.1 seconds
Output:

text:
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            //
        ];
    }
}
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            //
        ];
    }
}


exec

status: completed

input:
const r = await tools.exec_command({cmd:"for f in app/Policies/{Product,Collection,Discount,Order,Customer,Store,Page,Theme,Fulfillment,Refund}Policy.php; do echo \"--- $f\"; sed -n '1,160p' \"$f\"; done",workdir:"/Users/wesner/Herd/shop",yield_time_ms:10000,max_output_tokens:20000});
text(r.output);


command: for f in app/Policies/{Product,Collection,Discount,Order,Customer,Store,Page,Theme,Fulfillment,Refund}Policy.php; do echo "--- $f"; sed -n '1,160p' "$f"; done

stdout:
--- app/Policies/ProductPolicy.php
<?php

namespace App\Policies;

use App\Enums\StoreUserRole;
use App\Models\Product;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class ProductPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, StoreUserRole::cases());
    }

    public function view(User $user, Product $product): bool
    {
        return $this->userHasModelStoreRole($user, $product, StoreUserRole::cases());
    }

    public function create(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function update(User $user, Product $product): bool
    {
        return $this->userHasModelStoreRole($user, $product, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function delete(User $user, Product $product): bool
    {
        return $this->userHasModelStoreRole($user, $product, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }

    public function archive(User $user, Product $product): bool
    {
        return $this->delete($user, $product);
    }

    public function restore(User $user, Product $product): bool
    {
        return $this->delete($user, $product);
    }
}
--- app/Policies/CollectionPolicy.php
<?php

namespace App\Policies;

use App\Enums\StoreUserRole;
use App\Models\Collection;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class CollectionPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, StoreUserRole::cases());
    }

    public function view(User $user, Collection $collection): bool
    {
        return $this->userHasModelStoreRole($user, $collection, StoreUserRole::cases());
    }

    public function create(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function update(User $user, Collection $collection): bool
    {
        return $this->userHasModelStoreRole($user, $collection, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function delete(User $user, Collection $collection): bool
    {
        return $this->userHasModelStoreRole($user, $collection, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }
}
--- app/Policies/DiscountPolicy.php
<?php

namespace App\Policies;

use App\Enums\StoreUserRole;
use App\Models\Discount;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class DiscountPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, StoreUserRole::cases());
    }

    public function view(User $user, Discount $discount): bool
    {
        return $this->userHasModelStoreRole($user, $discount, StoreUserRole::cases());
    }

    public function create(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function update(User $user, Discount $discount): bool
    {
        return $this->userHasModelStoreRole($user, $discount, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function delete(User $user, Discount $discount): bool
    {
        return $this->userHasModelStoreRole($user, $discount, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }
}
--- app/Policies/OrderPolicy.php
<?php

namespace App\Policies;

use App\Enums\StoreUserRole;
use App\Models\Order;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class OrderPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, StoreUserRole::cases());
    }

    public function view(User $user, Order $order): bool
    {
        return $this->userHasModelStoreRole($user, $order, StoreUserRole::cases());
    }

    public function update(User $user, Order $order): bool
    {
        return $this->userHasModelStoreRole($user, $order, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function cancel(User $user, Order $order): bool
    {
        return $this->userHasModelStoreRole($user, $order, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }

    public function createFulfillment(User $user, Order $order): bool
    {
        return $this->userHasModelStoreRole($user, $order, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function createRefund(User $user, Order $order): bool
    {
        return $this->userHasModelStoreRole($user, $order, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }
}
--- app/Policies/CustomerPolicy.php
<?php

namespace App\Policies;

use App\Enums\StoreUserRole;
use App\Models\Customer;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class CustomerPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, StoreUserRole::cases());
    }

    public function view(User $user, Customer $customer): bool
    {
        return $this->userHasModelStoreRole($user, $customer, StoreUserRole::cases());
    }

    public function update(User $user, Customer $customer): bool
    {
        return $this->userHasModelStoreRole($user, $customer, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }
}
--- app/Policies/StorePolicy.php
<?php

namespace App\Policies;

use App\Enums\StoreUserRole;
use App\Models\Store;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class StorePolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $user->stores()->exists();
    }

    public function view(User $user, Store $store): bool
    {
        return $this->userHasModelStoreRole($user, $store, StoreUserRole::cases());
    }

    public function create(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }

    public function update(User $user, Store $store): bool
    {
        return $this->userHasModelStoreRole($user, $store, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }

    public function delete(User $user, Store $store): bool
    {
        return $this->userHasModelStoreRole($user, $store, [StoreUserRole::Owner]);
    }

    public function restore(User $user, Store $store): bool
    {
        return $this->delete($user, $store);
    }

    public function forceDelete(User $user, Store $store): bool
    {
        return $this->delete($user, $store);
    }
}
--- app/Policies/PagePolicy.php
<?php

namespace App\Policies;

use App\Enums\StoreUserRole;
use App\Models\Page;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class PagePolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, StoreUserRole::cases());
    }

    public function view(User $user, Page $page): bool
    {
        return $this->userHasModelStoreRole($user, $page, StoreUserRole::cases());
    }

    public function create(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function update(User $user, Page $page): bool
    {
        return $this->userHasModelStoreRole($user, $page, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function delete(User $user, Page $page): bool
    {
        return $this->userHasModelStoreRole($user, $page, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }
}
--- app/Policies/ThemePolicy.php
<?php

namespace App\Policies;

use App\Enums\StoreUserRole;
use App\Models\Theme;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class ThemePolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, StoreUserRole::cases());
    }

    public function view(User $user, Theme $theme): bool
    {
        return $this->userHasModelStoreRole($user, $theme, StoreUserRole::cases());
    }

    public function create(User $user): bool
    {
        return $this->userHasCurrentStoreRole($user, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }

    public function update(User $user, Theme $theme): bool
    {
        return $this->userHasModelStoreRole($user, $theme, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }

    public function delete(User $user, Theme $theme): bool
    {
        return $this->userHasModelStoreRole($user, $theme, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }

    public function publish(User $user, Theme $theme): bool
    {
        return $this->update($user, $theme);
    }
}
--- app/Policies/FulfillmentPolicy.php
<?php

namespace App\Policies;

use App\Enums\StoreUserRole;
use App\Models\Fulfillment;
use App\Models\Order;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class FulfillmentPolicy
{
    use ChecksStoreRole;

    public function create(User $user, Order $order): bool
    {
        return $this->userHasModelStoreRole($user, $order, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function view(User $user, Fulfillment $fulfillment): bool
    {
        return $this->userHasModelStoreRole($user, $fulfillment->loadMissing('order')->order, StoreUserRole::cases());
    }
}
--- app/Policies/RefundPolicy.php
<?php

namespace App\Policies;

use App\Enums\StoreUserRole;
use App\Models\Order;
use App\Models\Refund;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class RefundPolicy
{
    use ChecksStoreRole;

    public function create(User $user, Order $order): bool
    {
        return $this->userHasModelStoreRole($user, $order, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }

    public function view(User $user, Refund $refund): bool
    {
        return $this->userHasModelStoreRole($user, $refund->loadMissing('order')->order, StoreUserRole::cases());
    }
}