output:
Chunk ID: 2ae16e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 4677
Output:
bootstrap/app.php:4:use App\Http\Middleware\ResolveStore;
bootstrap/app.php:19:            'store.resolve' => ResolveStore::class,
bootstrap/app.php:23:            ResolveStore::class,
bootstrap/app.php:27:            ResolveStore::class,
app/Jobs/CleanupAbandonedCarts.php:20:    public function handle(CheckoutService $checkouts): void
app/Jobs/CleanupAbandonedCarts.php:27:            ->each(function (Cart $cart) use ($checkouts): void {
app/Jobs/CleanupAbandonedCarts.php:32:                    ->each(fn (Checkout $checkout): Checkout => $checkouts->expireCheckout($checkout));
app/Providers/FortifyServiceProvider.php:9:use Illuminate\Support\Facades\RateLimiter;
app/Providers/FortifyServiceProvider.php:61:        RateLimiter::for('two-factor', function (Request $request) {
app/Providers/FortifyServiceProvider.php:65:        RateLimiter::for('login', function (Request $request) {
routes/api.php:8:Route::middleware(['store.resolve', 'throttle:api.storefront'])
routes/api.php:10:    ->name('api.storefront.v1.')
routes/api.php:18:        Route::post('checkouts', [CheckoutController::class, 'store'])->name('checkouts.store');
routes/api.php:19:        Route::get('checkouts/{checkout}', [CheckoutController::class, 'show'])->name('checkouts.show');
routes/api.php:20:        Route::put('checkouts/{checkout}/address', [CheckoutController::class, 'address'])->name('checkouts.address');
routes/api.php:21:        Route::put('checkouts/{checkout}/shipping-method', [CheckoutController::class, 'shippingMethod'])->name('checkouts.shipping-method');
routes/api.php:22:        Route::post('checkouts/{checkout}/apply-discount', [CheckoutController::class, 'applyDiscount'])->name('checkouts.apply-discount');
routes/api.php:23:        Route::put('checkouts/{checkout}/payment-method', [CheckoutController::class, 'paymentMethod'])->name('checkouts.payment-method');
routes/web.php:28:    Route::livewire('checkout', StorefrontCheckoutShow::class)->name('checkout.show');
app/Providers/AppServiceProvider.php:16:use Illuminate\Support\Facades\RateLimiter;
app/Providers/AppServiceProvider.php:67:        RateLimiter::for('api.storefront', function (Request $request): Limit {
app/Providers/AppServiceProvider.php:71:        RateLimiter::for('checkout', function (Request $request): Limit {
app/Jobs/ExpireAbandonedCheckouts.php:18:    public function handle(CheckoutService $checkouts): void
app/Jobs/ExpireAbandonedCheckouts.php:33:            ->each(fn (Checkout $checkout): Checkout => $checkouts->expireCheckout($checkout));
app/Http/Middleware/ResolveStore.php:13:class ResolveStore
bootstrap/cache/services.php:85:    'Illuminate\\Cache\\RateLimiter' => 'Illuminate\\Cache\\CacheServiceProvider',
app/Models/Customer.php:62:    public function checkouts(): HasMany
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:27:    public function store(StoreCheckoutRequest $request, CheckoutService $checkouts): CheckoutResource|JsonResponse
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:33:                $checkouts->createFromCart($cart),
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:40:    public function show(Checkout $checkout): CheckoutResource
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:42:        return CheckoutResource::make($this->loadCheckout($checkout));
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:45:    public function address(SetCheckoutAddressRequest $request, Checkout $checkout, CheckoutService $checkouts): CheckoutResource|JsonResponse
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:48:            return CheckoutResource::make($this->loadCheckout($checkouts->setAddress($checkout, $request->validated())));
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:54:    public function shippingMethod(SetCheckoutShippingRequest $request, Checkout $checkout, CheckoutService $checkouts): CheckoutResource|JsonResponse
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:57:            return CheckoutResource::make($this->loadCheckout($checkouts->setShippingMethod(
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:58:                $checkout,
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:66:    public function applyDiscount(ApplyCheckoutDiscountRequest $request, Checkout $checkout, PricingEngine $pricing): CheckoutResource|JsonResponse
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:68:        $checkout->forceFill([
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:73:            $pricing->calculate($checkout);
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:75:            return CheckoutResource::make($this->loadCheckout($checkout->refresh()));
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:77:            $checkout->forceFill(['discount_code' => null])->save();
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:78:            $pricing->calculate($checkout);
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:87:    public function paymentMethod(SelectCheckoutPaymentRequest $request, Checkout $checkout, CheckoutService $checkouts): CheckoutResource|JsonResponse
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:90:            return CheckoutResource::make($this->loadCheckout($checkouts->selectPaymentMethod(
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:91:                $checkout,
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:99:    private function loadCheckout(Checkout $checkout): Checkout
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:101:        $checkout = $checkout->load([
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:107:        $checkout->setRelation('availableRates', $this->availableRates($checkout));
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:109:        return $checkout;
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:115:    private function availableRates(Checkout $checkout): Collection
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:117:        if (! is_array($checkout->shipping_address_json) || $checkout->shipping_address_json === []) {
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:122:            ->getAvailableRates($checkout->store, $checkout->shipping_address_json)
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:123:            ->map(function (ShippingRate $rate) use ($checkout): ShippingRate {
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:124:                $rate->setAttribute('calculated_amount', app(ShippingCalculator::class)->calculate($rate, $checkout->cart));
app/Services/PricingEngine.php:22:    public function calculate(Checkout $checkout): PricingResult
app/Services/PricingEngine.php:24:        return DB::transaction(function () use ($checkout): PricingResult {
app/Services/PricingEngine.php:25:            $checkout = Checkout::withoutGlobalScopes()
app/Services/PricingEngine.php:27:                ->whereKey($checkout->getKey())
app/Services/PricingEngine.php:29:            $cart = $checkout->cart;
app/Services/PricingEngine.php:31:            $this->resetLineAmounts($checkout);
app/Services/PricingEngine.php:40:            if ($checkout->discount_code) {
app/Services/PricingEngine.php:41:                $discount = $this->discounts->validate($checkout->discount_code, $checkout->store, $cart);
app/Services/PricingEngine.php:49:            $shippingAmount = $this->shippingAmount($checkout, $discountResult);
app/Services/PricingEngine.php:50:            $taxSettings = $this->taxSettings($checkout);
app/Services/PricingEngine.php:55:                $checkout->shipping_address_json ?? [],
app/Services/PricingEngine.php:72:            $checkout->forceFill([
app/Services/PricingEngine.php:81:    private function resetLineAmounts(Checkout $checkout): void
app/Services/PricingEngine.php:84:            ->where('cart_id', $checkout->cart_id)
app/Services/PricingEngine.php:97:    private function shippingAmount(Checkout $checkout, DiscountResult $discountResult): int
app/Services/PricingEngine.php:99:        if (! $this->shipping->requiresShipping($checkout->cart)) {
app/Services/PricingEngine.php:103:        if ($checkout->shipping_method_id === null) {
app/Services/PricingEngine.php:107:        $rate = ShippingRate::withoutGlobalScopes()->find($checkout->shipping_method_id);
app/Services/PricingEngine.php:113:        $amount = $this->shipping->calculate($rate, $checkout->cart);
app/Services/PricingEngine.php:122:    private function taxSettings(Checkout $checkout): TaxSettings
app/Services/PricingEngine.php:125:            ->where('store_id', $checkout->store_id)
app/Services/PricingEngine.php:127:                'store_id' => $checkout->store_id,
app/Models/Store.php:100:    public function checkouts(): HasMany
app/Services/CheckoutService.php:50:    public function setAddress(Checkout $checkout, array $addressData): Checkout
app/Services/CheckoutService.php:52:        return DB::transaction(function () use ($checkout, $addressData): Checkout {
app/Services/CheckoutService.php:53:            $checkout = $this->freshCheckout($checkout);
app/Services/CheckoutService.php:54:            $this->assertStatus($checkout, [CheckoutStatus::Started, CheckoutStatus::Addressed]);
app/Services/CheckoutService.php:72:            $checkout->forceFill([
app/Services/CheckoutService.php:79:            $this->pricing->calculate($checkout);
app/Services/CheckoutService.php:81:            return $checkout->refresh();
app/Services/CheckoutService.php:85:    public function setShippingMethod(Checkout $checkout, ?int $shippingRateId): Checkout
app/Services/CheckoutService.php:87:        return DB::transaction(function () use ($checkout, $shippingRateId): Checkout {
app/Services/CheckoutService.php:88:            $checkout = $this->freshCheckout($checkout);
app/Services/CheckoutService.php:89:            $this->assertStatus($checkout, [CheckoutStatus::Addressed, CheckoutStatus::ShippingSelected]);
app/Services/CheckoutService.php:91:            if (! $this->shipping->requiresShipping($checkout->cart)) {
app/Services/CheckoutService.php:92:                $checkout->forceFill([
app/Services/CheckoutService.php:97:                $this->pricing->calculate($checkout);
app/Services/CheckoutService.php:99:                return $checkout->refresh();
app/Services/CheckoutService.php:102:            $availableRates = $this->shipping->getAvailableRates($checkout->store, $checkout->shipping_address_json ?? []);
app/Services/CheckoutService.php:114:            $checkout->forceFill([
app/Services/CheckoutService.php:119:            $this->pricing->calculate($checkout);
app/Services/CheckoutService.php:121:            return $checkout->refresh();
app/Services/CheckoutService.php:125:    public function selectPaymentMethod(Checkout $checkout, string $paymentMethod): Checkout
app/Services/CheckoutService.php:127:        return DB::transaction(function () use ($checkout, $paymentMethod): Checkout {
app/Services/CheckoutService.php:128:            $checkout = $this->freshCheckout($checkout);
app/Services/CheckoutService.php:130:            if ($checkout->status === CheckoutStatus::PaymentSelected) {
app/Services/CheckoutService.php:131:                return $checkout;
app/Services/CheckoutService.php:134:            $this->assertStatus($checkout, [CheckoutStatus::ShippingSelected]);
app/Services/CheckoutService.php:140:            if ($checkout->totals_json === null) {
app/Services/CheckoutService.php:141:                $this->pricing->calculate($checkout);
app/Services/CheckoutService.php:145:                ->where('cart_id', $checkout->cart_id)
app/Services/CheckoutService.php:155:            $checkout->forceFill([
app/Services/CheckoutService.php:161:            return $checkout->refresh();
app/Services/CheckoutService.php:165:    public function expireCheckout(Checkout $checkout): Checkout
app/Services/CheckoutService.php:167:        return DB::transaction(function () use ($checkout): Checkout {
app/Services/CheckoutService.php:168:            $checkout = $this->freshCheckout($checkout);
app/Services/CheckoutService.php:170:            if (in_array($checkout->status, [CheckoutStatus::Completed, CheckoutStatus::Expired], true)) {
app/Services/CheckoutService.php:171:                return $checkout;
app/Services/CheckoutService.php:174:            if ($checkout->status === CheckoutStatus::PaymentSelected) {
app/Services/CheckoutService.php:176:                    ->where('cart_id', $checkout->cart_id)
app/Services/CheckoutService.php:187:            $checkout->forceFill([
app/Services/CheckoutService.php:191:            return $checkout->refresh();
app/Services/CheckoutService.php:195:    public function completeCheckout(Checkout $checkout, array $paymentMethodData = []): never
app/Services/CheckoutService.php:200:    private function freshCheckout(Checkout $checkout): Checkout
app/Services/CheckoutService.php:204:            ->whereKey($checkout->getKey())
app/Services/CheckoutService.php:212:    private function assertStatus(Checkout $checkout, array $allowed): void
app/Services/CheckoutService.php:214:        if (! in_array($checkout->status, $allowed, true)) {
app/Services/CheckoutService.php:215:            throw InvalidCheckoutTransitionException::because("Checkout cannot transition from {$checkout->status->value}.");
app/Livewire/Admin/Auth/Login.php:6:use Illuminate\Support\Facades\RateLimiter;
app/Livewire/Admin/Auth/Login.php:28:        if (RateLimiter::tooManyAttempts($key, 5)) {
app/Livewire/Admin/Auth/Login.php:31:                    'seconds' => RateLimiter::availableIn($key),
app/Livewire/Admin/Auth/Login.php:41:            RateLimiter::hit($key, 60);
app/Livewire/Admin/Auth/Login.php:48:        RateLimiter::clear($key);
app/Services/ThemeSettingsService.php:63:                'tagline' => 'A self-contained demo storefront with scoped catalog data and checkout-ready products.',
app/Models/Cart.php:64:    public function checkouts(): HasMany
app/Livewire/Storefront/CartDrawer.php:63:    public function checkout(): void
app/Livewire/Storefront/CartDrawer.php:69:        $this->redirectRoute('checkout.show', navigate: true);
app/Livewire/Storefront/Cart/Show.php:63:    public function checkout(): void
app/Livewire/Storefront/Cart/Show.php:69:        $this->redirectRoute('checkout.show', navigate: true);
app/Livewire/Storefront/Checkout/Show.php:73:        $this->fillFromCheckout($this->checkout());
app/Livewire/Storefront/Checkout/Show.php:89:        $checkout = $this->checkout();
app/Livewire/Storefront/Checkout/Show.php:91:        if (! $checkout instanceof Checkout) {
app/Livewire/Storefront/Checkout/Show.php:96:            $checkout = app(CheckoutService::class)->setAddress($checkout, [
app/Livewire/Storefront/Checkout/Show.php:105:                app(CheckoutService::class)->setShippingMethod($checkout, null);
app/Livewire/Storefront/Checkout/Show.php:121:        $checkout = $this->checkout();
app/Livewire/Storefront/Checkout/Show.php:123:        if (! $checkout instanceof Checkout) {
app/Livewire/Storefront/Checkout/Show.php:134:            app(CheckoutService::class)->setShippingMethod($checkout, $this->selectedShippingRateId);
app/Livewire/Storefront/Checkout/Show.php:145:        $checkout = $this->checkout();
app/Livewire/Storefront/Checkout/Show.php:147:        if (! $checkout instanceof Checkout) {
app/Livewire/Storefront/Checkout/Show.php:151:        $checkout->forceFill([
app/Livewire/Storefront/Checkout/Show.php:156:            app(PricingEngine::class)->calculate($checkout);
app/Livewire/Storefront/Checkout/Show.php:159:            $checkout->forceFill(['discount_code' => null])->save();
app/Livewire/Storefront/Checkout/Show.php:161:            app(PricingEngine::class)->calculate($checkout);
app/Livewire/Storefront/Checkout/Show.php:175:        $checkout = $this->checkout();
app/Livewire/Storefront/Checkout/Show.php:177:        if (! $checkout instanceof Checkout) {
app/Livewire/Storefront/Checkout/Show.php:182:            app(CheckoutService::class)->selectPaymentMethod($checkout, $this->paymentMethod);
app/Livewire/Storefront/Checkout/Show.php:217:    public function checkout(): ?Checkout
app/Livewire/Storefront/Checkout/Show.php:225:        $checkout = Checkout::withoutGlobalScopes()
app/Livewire/Storefront/Checkout/Show.php:231:        if (! $checkout instanceof Checkout) {
app/Livewire/Storefront/Checkout/Show.php:232:            $checkout = app(CheckoutService::class)->createFromCart($cart, $this->customer());
app/Livewire/Storefront/Checkout/Show.php:235:        return $checkout->load(['cart.lines.variant.product', 'cart.lines.variant.optionValues.option']);
app/Livewire/Storefront/Checkout/Show.php:300:        return $this->checkout()?->totals_json ?? [
app/Livewire/Storefront/Checkout/Show.php:312:        return view('livewire.storefront.checkout.show', [
app/Livewire/Storefront/Checkout/Show.php:314:            'checkout' => $this->checkout(),
app/Livewire/Storefront/Checkout/Show.php:333:    private function fillFromCheckout(?Checkout $checkout): void
app/Livewire/Storefront/Checkout/Show.php:335:        if (! $checkout instanceof Checkout) {
app/Livewire/Storefront/Checkout/Show.php:339:        $this->email = $checkout->email ?: $this->email;
app/Livewire/Storefront/Checkout/Show.php:340:        $this->shippingAddress = array_replace($this->shippingAddress, $checkout->shipping_address_json ?? []);
app/Livewire/Storefront/Checkout/Show.php:341:        $this->billingAddress = array_replace($this->billingAddress, $checkout->billing_address_json ?? []);
app/Livewire/Storefront/Checkout/Show.php:342:        $this->selectedShippingRateId = $checkout->shipping_method_id;
app/Livewire/Storefront/Checkout/Show.php:343:        $this->discountCode = (string) ($checkout->discount_code ?? '');
app/Livewire/Storefront/Checkout/Show.php:344:        $this->paymentMethod = (string) ($checkout->payment_method ?? $this->paymentMethod);
app/Livewire/Storefront/Checkout/Show.php:345:        $this->step = match ($checkout->status) {
app/Livewire/Storefront/Account/Auth/Login.php:9:use Illuminate\Support\Facades\RateLimiter;
app/Livewire/Storefront/Account/Auth/Login.php:42:        if (RateLimiter::tooManyAttempts($key, 5)) {
app/Livewire/Storefront/Account/Auth/Login.php:45:                    'seconds' => RateLimiter::availableIn($key),
app/Livewire/Storefront/Account/Auth/Login.php:56:            RateLimiter::hit($key, 60);
app/Livewire/Storefront/Account/Auth/Login.php:63:        RateLimiter::clear($key);


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

namespace App\Providers;

use App\Auth\CustomerUserProvider;
use App\Models\Store;
use App\Services\NavigationService;
use App\Services\ThemeSettingsService;
use Carbon\CarbonImmutable;
use Illuminate\Auth\Middleware\Authenticate;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
use Illuminate\View\View as ViewInstance;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        Auth::provider('store_scoped_eloquent', function ($app, array $config): CustomerUserProvider {
            return new CustomerUserProvider($app['hash'], $config['model']);
        });

        $this->app->singleton(ThemeSettingsService::class);
        $this->app->singleton(NavigationService::class);
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureDefaults();
        $this->configureStorefrontViewData();
    }

    /**
     * Configure default behaviors for production-ready applications.
     */
    protected function configureDefaults(): void
    {
        Date::use(CarbonImmutable::class);

        DB::prohibitDestructiveCommands(
            app()->isProduction(),
        );

        Password::defaults(fn (): ?Password => app()->isProduction()
            ? Password::min(12)
                ->mixedCase()
                ->letters()
                ->numbers()
                ->symbols()
                ->uncompromised()
            : null
        );

        RateLimiter::for('api.storefront', function (Request $request): Limit {
            return Limit::perMinute(120)->by($request->ip());
        });

        RateLimiter::for('checkout', function (Request $request): Limit {
            return Limit::perMinute(10)->by($request->session()->getId() ?: $request->ip());
        });

        Authenticate::redirectUsing(function (Request $request): string {
            if ($request->is('admin*')) {
                return route('admin.login');
            }

            if ($request->is('account*')) {
                return route('account.login');
            }

            return route('login');
        });
    }

    protected function configureStorefrontViewData(): void
    {
        View::composer('layouts.storefront', function (ViewInstance $view): void {
            $store = app()->bound('current_store') ? app('current_store') : null;

            if (! $store instanceof Store) {
                $view->with([
                    'themeSettings' => [],
                    'mainNavigation' => [],
                    'footerNavigation' => [],
                ]);

                return;
            }

            $themeSettings = app(ThemeSettingsService::class)->forStore($store);
            $navigation = app(NavigationService::class);

            $view->with([
                'themeSettings' => $themeSettings,
                'mainNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'header.main_menu', 'main-menu')),
                'footerNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'footer.menu', 'footer-menu')),
            ]);
        });
    }
}


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

namespace App\Http\Middleware;

use App\Enums\StoreStatus;
use App\Models\Store;
use App\Models\StoreDomain;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;

class ResolveStore
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $store = $request->is('admin*')
            ? $this->resolveAdminStore($request)
            : $this->resolveStorefrontStore($request);

        abort_unless($store, 404);

        app()->instance('current_store', $store);

        if (! $request->is('admin*') && $store->status === StoreStatus::Suspended) {
            abort(503);
        }

        if ($request->is('admin*') && $store->status === StoreStatus::Suspended && ! $request->isMethodSafe()) {
            abort(403);
        }

        return $next($request);
    }

    private function resolveStorefrontStore(Request $request): ?Store
    {
        $hostname = $request->getHost();
        $cacheKey = "store_domain:{$hostname}";

        $storeId = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($hostname): ?int {
            return StoreDomain::query()
                ->where('hostname', $hostname)
                ->value('store_id');
        });

        return $storeId ? Store::query()->find($storeId) : null;
    }

    private function resolveAdminStore(Request $request): ?Store
    {
        $user = $request->user();

        if (! $user) {
            return null;
        }

        $storeId = $request->session()->get('current_store_id');

        if (! $storeId) {
            $storeId = $user->stores()->oldest('stores.id')->value('stores.id');

            if ($storeId) {
                $request->session()->put('current_store_id', $storeId);
            }
        }

        if (! $storeId || ! $user->stores()->whereKey($storeId)->exists()) {
            return null;
        }

        return Store::query()->find($storeId);
    }
}


cmd: sed -n '1,220p' app/Models/Concerns/BelongsToStore.php


cmd: sed -n '1,220p' app/Models/Concerns/StoreScope.php


cmd: sed -n '1,220p' database/factories/ProductFactory.php && sed -n '1,180p' database/factories/ProductVariantFactory.php && sed -n '1,160p' database/factories/InventoryItemFactory.php


command: sed -n '1,220p' app/Models/Concerns/StoreScope.php

StoreScope.php

type: read

aggregated_output: sed: app/Models/Concerns/StoreScope.php: No such file or directory

status: failed


command: sed -n '1,220p' app/Models/Concerns/BelongsToStore.php

aggregated_output:
<?php

namespace App\Models\Concerns;

use App\Models\Scopes\StoreScope;
use App\Models\Store;

trait BelongsToStore
{
    public static function bootBelongsToStore(): void
    {
        static::addGlobalScope(new StoreScope);

        static::creating(function (object $model): void {
            if ($model->store_id || ! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if ($store instanceof Store) {
                $model->store_id = $store->getKey();
            }
        });
    }
}

status: completed


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

namespace App\Models\Concerns;

use App\Models\Scopes\StoreScope;
use App\Models\Store;

trait BelongsToStore
{
    public static function bootBelongsToStore(): void
    {
        static::addGlobalScope(new StoreScope);

        static::creating(function (object $model): void {
            if ($model->store_id || ! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if ($store instanceof Store) {
                $model->store_id = $store->getKey();
            }
        });
    }
}


output:
Chunk ID: 8ed92e
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 17
Output:
sed: app/Models/Concerns/StoreScope.php: No such file or directory


command: sed -n '1,220p' database/factories/ProductFactory.php && sed -n '1,180p' database/factories/ProductVariantFactory.php && sed -n '1,160p' database/factories/InventoryItemFactory.php

InventoryItemFactory.php

aggregated_output:
<?php

namespace Database\Factories;

use App\Enums\ProductStatus;
use App\Models\InventoryItem;
use App\Models\ProductVariant;
use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Product>
 */
class ProductFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $title = Str::title(fake()->words(3, true));

        return [
            'store_id' => Store::factory(),
            'title' => $title,
            'handle' => Str::slug($title),
            'status' => ProductStatus::Active,
            'description_html' => '<p>'.fake()->paragraph().'</p>',
            'vendor' => fake()->company(),
            'product_type' => fake()->randomElement(['Shirts', 'Pants', 'Shoes', 'Accessories', 'Electronics', 'Books']),
            'tags' => fake()->randomElements(['new', 'sale', 'trending', 'popular', 'limited'], fake()->numberBetween(1, 3)),
            'published_at' => now(),
        ];
    }

    public function draft(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => ProductStatus::Draft,
            'published_at' => null,
        ]);
    }

    public function archived(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => ProductStatus::Archived,
        ]);
    }

    public function withVariants(int $count): static
    {
        return $this->afterCreating(function ($product) use ($count): void {
            ProductVariant::factory()
                ->count($count)
                ->for($product)
                ->create();
        });
    }

    public function withDefaultVariant(int $price = 2499): static
    {
        return $this->afterCreating(function ($product) use ($price): void {
            $variant = ProductVariant::factory()
                ->default()
                ->for($product)
                ->create([
                    'price_amount' => $price,
                    'currency' => $product->store->default_currency,
                ]);

            InventoryItem::withoutGlobalScopes()->where('variant_id', $variant->getKey())->update([
                'quantity_on_hand' => 50,
            ]);
        });
    }
}
<?php

namespace Database\Factories;

use App\Enums\VariantStatus;
use App\Models\Product;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ProductVariant>
 */
class ProductVariantFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'product_id' => Product::factory(),
            'sku' => 'SKU-'.fake()->unique()->numerify('####').'-'.fake()->lexify('???'),
            'barcode' => fake()->ean13(),
            'price_amount' => fake()->numberBetween(999, 19999),
            'compare_at_amount' => null,
            'currency' => 'EUR',
            'weight_g' => fake()->numberBetween(100, 5000),
            'requires_shipping' => true,
            'is_default' => false,
            'position' => 0,
            'status' => VariantStatus::Active,
        ];
    }

    public function onSale(): static
    {
        return $this->state(fn (array $attributes): array => [
            'compare_at_amount' => fake()->numberBetween(20000, 39999),
            'price_amount' => fake()->numberBetween(9999, 19999),
        ]);
    }

    public function digital(): static
    {
        return $this->state(fn (array $attributes): array => [
            'requires_shipping' => false,
            'weight_g' => 0,
        ]);
    }

    public function default(): static
    {
        return $this->state(fn (array $attributes): array => [
            'is_default' => true,
        ]);
    }

    public function archived(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => VariantStatus::Archived,
        ]);
    }
}
<?php

namespace Database\Factories;

use App\Enums\InventoryPolicy;
use App\Models\ProductVariant;
use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\InventoryItem>
 */
class InventoryItemFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'variant_id' => fn (): int => ProductVariant::withoutEvents(
                fn (): int => ProductVariant::factory()->create()->getKey(),
            ),
            'store_id' => fn (array $attributes): mixed => ProductVariant::query()
                ->with('product')
                ->find($attributes['variant_id'])
                ?->product
                ?->store_id ?? Store::factory(),
            'quantity_on_hand' => fake()->numberBetween(0, 100),
            'quantity_reserved' => 0,
            'policy' => InventoryPolicy::Deny,
        ];
    }

    public function outOfStock(): static
    {
        return $this->state(fn (array $attributes): array => [
            'quantity_on_hand' => 0,
            'quantity_reserved' => 0,
        ]);
    }

    public function continuePolicy(): static
    {
        return $this->state(fn (array $attributes): array => [
            'policy' => InventoryPolicy::Continue,
        ]);
    }

    public function lowStock(): static
    {
        return $this->state(fn (array $attributes): array => [
            'quantity_on_hand' => fake()->numberBetween(1, 3),
            'quantity_reserved' => 0,
        ]);
    }
}


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

namespace Database\Factories;

use App\Enums\ProductStatus;
use App\Models\InventoryItem;
use App\Models\ProductVariant;
use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Product>
 */
class ProductFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $title = Str::title(fake()->words(3, true));

        return [
            'store_id' => Store::factory(),
            'title' => $title,
            'handle' => Str::slug($title),
            'status' => ProductStatus::Active,
            'description_html' => '<p>'.fake()->paragraph().'</p>',
            'vendor' => fake()->company(),
            'product_type' => fake()->randomElement(['Shirts', 'Pants', 'Shoes', 'Accessories', 'Electronics', 'Books']),
            'tags' => fake()->randomElements(['new', 'sale', 'trending', 'popular', 'limited'], fake()->numberBetween(1, 3)),
            'published_at' => now(),
        ];
    }

    public function draft(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => ProductStatus::Draft,
            'published_at' => null,
        ]);
    }

    public function archived(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => ProductStatus::Archived,
        ]);
    }

    public function withVariants(int $count): static
    {
        return $this->afterCreating(function ($product) use ($count): void {
            ProductVariant::factory()
                ->count($count)
                ->for($product)
                ->create();
        });
    }

    public function withDefaultVariant(int $price = 2499): static
    {
        return $this->afterCreating(function ($product) use ($price): void {
            $variant = ProductVariant::factory()
                ->default()
                ->for($product)
                ->create([
                    'price_amount' => $price,
                    'currency' => $product->store->default_currency,
                ]);

            InventoryItem::withoutGlobalScopes()->where('variant_id', $variant->getKey())->update([
                'quantity_on_hand' => 50,
            ]);
        });
    }
}
<?php

namespace Database\Factories;

use App\Enums\VariantStatus;
use App\Models\Product;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ProductVariant>
 */
class ProductVariantFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'product_id' => Product::factory(),
            'sku' => 'SKU-'.fake()->unique()->numerify('####').'-'.fake()->lexify('???'),
            'barcode' => fake()->ean13(),
            'price_amount' => fake()->numberBetween(999, 19999),
            'compare_at_amount' => null,
            'currency' => 'EUR',
            'weight_g' => fake()->numberBetween(100, 5000),
            'requires_shipping' => true,
            'is_default' => false,
            'position' => 0,
            'status' => VariantStatus::Active,
        ];
    }

    public function onSale(): static
    {
        return $this->state(fn (array $attributes): array => [
            'compare_at_amount' => fake()->numberBetween(20000, 39999),
            'price_amount' => fake()->numberBetween(9999, 19999),
        ]);
    }

    public function digital(): static
    {
        return $this->state(fn (array $attributes): array => [
            'requires_shipping' => false,
            'weight_g' => 0,
        ]);
    }

    public function default(): static
    {
        return $this->state(fn (array $attributes): array => [
            'is_default' => true,
        ]);
    }

    public function archived(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => VariantStatus::Archived,
        ]);
    }
}
<?php

namespace Database\Factories;

use App\Enums\InventoryPolicy;
use App\Models\ProductVariant;
use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\InventoryItem>
 */
class InventoryItemFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'variant_id' => fn (): int => ProductVariant::withoutEvents(
                fn (): int => ProductVariant::factory()->create()->getKey(),
            ),
            'store_id' => fn (array $attributes): mixed => ProductVariant::query()
                ->with('product')
                ->find($attributes['variant_id'])
                ?->product
                ?->store_id ?? Store::factory(),
            'quantity_on_hand' => fake()->numberBetween(0, 100),
            'quantity_reserved' => 0,
            'policy' => InventoryPolicy::Deny,
        ];
    }

    public function outOfStock(): static
    {
        return $this->state(fn (array $attributes): array => [
            'quantity_on_hand' => 0,
            'quantity_reserved' => 0,
        ]);
    }

    public function continuePolicy(): static
    {
        return $this->state(fn (array $attributes): array => [
            'policy' => InventoryPolicy::Continue,
        ]);
    }

    public function lowStock(): static
    {
        return $this->state(fn (array $attributes): array => [
            'quantity_on_hand' => fake()->numberBetween(1, 3),
            'quantity_reserved' => 0,
        ]);
    }
}


cmd: sed -n '1,220p' app/Models/Scopes/StoreScope.php


cmd: sed -n '1,220p' app/Models/ProductVariant.php


command: sed -n '1,220p' app/Models/Scopes/StoreScope.php

aggregated_output:
<?php

namespace App\Models\Scopes;

use App\Models\Store;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class StoreScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        if (! app()->bound('current_store')) {
            return;
        }

        $store = app('current_store');

        if (! $store instanceof Store) {
            return;
        }

        $builder->where($model->qualifyColumn('store_id'), $store->getKey());
    }
}


command: sed -n '1,220p' app/Models/ProductVariant.php

aggregated_output:
<?php

namespace App\Models;

use App\Enums\InventoryPolicy;
use App\Enums\VariantStatus;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use RuntimeException;

class ProductVariant extends Model
{
    /** @use HasFactory<\Database\Factories\ProductVariantFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'product_id',
        'sku',
        'barcode',
        'price_amount',
        'compare_at_amount',
        'currency',
        'weight_g',
        'requires_shipping',
        'is_default',
        'position',
        'status',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'price_amount' => 0,
        'currency' => 'USD',
        'requires_shipping' => true,
        'is_default' => false,
        'position' => 0,
        'status' => 'active',
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('product', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });

        static::saving(function (ProductVariant $variant): void {
            $variant->assertSkuIsUniqueForStore();
        });

        static::created(function (ProductVariant $variant): void {
            if ($variant->inventoryItem()->exists()) {
                return;
            }

            $storeId = Product::withoutGlobalScopes()
                ->whereKey($variant->product_id)
                ->value('store_id');

            if (! $storeId) {
                return;
            }

            InventoryItem::withoutGlobalScopes()->create([
                'store_id' => $storeId,
                'variant_id' => $variant->getKey(),
                'quantity_on_hand' => 0,
                'quantity_reserved' => 0,
                'policy' => InventoryPolicy::Deny,
            ]);
        });
    }

    private function assertSkuIsUniqueForStore(): void
    {
        $sku = trim((string) $this->sku);

        if ($sku === '') {
            return;
        }

        $storeId = Product::withoutGlobalScopes()
            ->whereKey($this->product_id)
            ->value('store_id');

        if ($storeId === null) {
            return;
        }

        $query = self::withoutGlobalScopes()
            ->where('sku', $sku)
            ->whereHas('product', function (Builder $query) use ($storeId): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $storeId);
            });

        if ($this->exists) {
            $query->whereKeyNot($this->getKey());
        }

        if ($query->exists()) {
            throw new RuntimeException("The SKU [{$sku}] is already used in this store.");
        }
    }

    /**
     * @return BelongsTo<Product, $this>
     */
    public function product(): BelongsTo
    {
        return $this->belongsTo(Product::class);
    }

    /**
     * @return HasOne<InventoryItem, $this>
     */
    public function inventoryItem(): HasOne
    {
        return $this->hasOne(InventoryItem::class, 'variant_id');
    }

    /**
     * @return BelongsToMany<ProductOptionValue, $this>
     */
    public function optionValues(): BelongsToMany
    {
        return $this->belongsToMany(ProductOptionValue::class, 'variant_option_values', 'variant_id', 'product_option_value_id');
    }

    public function isPurchasable(): bool
    {
        return $this->status === VariantStatus::Active;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'price_amount' => 'integer',
            'compare_at_amount' => 'integer',
            'weight_g' => 'integer',
            'requires_shipping' => 'bool',
            'is_default' => 'bool',
            'position' => 'integer',
            'status' => VariantStatus::class,
        ];
    }
}


command:
/bin/zsh
-lc
sed -n '1,220p' app/Models/CartLine.php

CartLine.php

cmd: sed -n '1,220p' app/Models/CartLine.php

aggregated_output:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class CartLine extends Model
{
    /** @use HasFactory<\Database\Factories\CartLineFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'cart_id',
        'variant_id',
        'quantity',
        'unit_price_amount',
        'line_subtotal_amount',
        'line_discount_amount',
        'line_total_amount',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'quantity' => 1,
        'unit_price_amount' => 0,
        'line_subtotal_amount' => 0,
        'line_discount_amount' => 0,
        'line_total_amount' => 0,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('cart', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<Cart, $this>
     */
    public function cart(): BelongsTo
    {
        return $this->belongsTo(Cart::class);
    }

    /**
     * @return BelongsTo<ProductVariant, $this>
     */
    public function variant(): BelongsTo
    {
        return $this->belongsTo(ProductVariant::class, 'variant_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'quantity' => 'integer',
            'unit_price_amount' => 'integer',
            'line_subtotal_amount' => 'integer',
            'line_discount_amount' => 'integer',
            'line_total_amount' => 'integer',
        ];
    }
}


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

namespace App\Models\Scopes;

use App\Models\Store;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class StoreScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        if (! app()->bound('current_store')) {
            return;
        }

        $store = app('current_store');

        if (! $store instanceof Store) {
            return;
        }

        $builder->where($model->qualifyColumn('store_id'), $store->getKey());
    }
}


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

namespace App\Models;

use App\Enums\InventoryPolicy;
use App\Enums\VariantStatus;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use RuntimeException;

class ProductVariant extends Model
{
    /** @use HasFactory<\Database\Factories\ProductVariantFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'product_id',
        'sku',
        'barcode',
        'price_amount',
        'compare_at_amount',
        'currency',
        'weight_g',
        'requires_shipping',
        'is_default',
        'position',
        'status',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'price_amount' => 0,
        'currency' => 'USD',
        'requires_shipping' => true,
        'is_default' => false,
        'position' => 0,
        'status' => 'active',
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('product', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });

        static::saving(function (ProductVariant $variant): void {
            $variant->assertSkuIsUniqueForStore();
        });

        static::created(function (ProductVariant $variant): void {
            if ($variant->inventoryItem()->exists()) {
                return;
            }

            $storeId = Product::withoutGlobalScopes()
                ->whereKey($variant->product_id)
                ->value('store_id');

            if (! $storeId) {
                return;
            }

            InventoryItem::withoutGlobalScopes()->create([
                'store_id' => $storeId,
                'variant_id' => $variant->getKey(),
                'quantity_on_hand' => 0,
                'quantity_reserved' => 0,
                'policy' => InventoryPolicy::Deny,
            ]);
        });
    }

    private function assertSkuIsUniqueForStore(): void
    {
        $sku = trim((string) $this->sku);

        if ($sku === '') {
            return;
        }

        $storeId = Product::withoutGlobalScopes()
            ->whereKey($this->product_id)
            ->value('store_id');

        if ($storeId === null) {
            return;
        }

        $query = self::withoutGlobalScopes()
            ->where('sku', $sku)
            ->whereHas('product', function (Builder $query) use ($storeId): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $storeId);
            });

        if ($this->exists) {
            $query->whereKeyNot($this->getKey());
        }

        if ($query->exists()) {
            throw new RuntimeException("The SKU [{$sku}] is already used in this store.");
        }
    }

    /**
     * @return BelongsTo<Product, $this>
     */
    public function product(): BelongsTo
    {
        return $this->belongsTo(Product::class);
    }

    /**
     * @return HasOne<InventoryItem, $this>
     */
    public function inventoryItem(): HasOne
    {
        return $this->hasOne(InventoryItem::class, 'variant_id');
    }

    /**
     * @return BelongsToMany<ProductOptionValue, $this>
     */
    public function optionValues(): BelongsToMany
    {
        return $this->belongsToMany(ProductOptionValue::class, 'variant_option_values', 'variant_id', 'product_option_value_id');
    }

    public function isPurchasable(): bool
    {
        return $this->status === VariantStatus::Active;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'price_amount' => 'integer',
            'compare_at_amount' => 'integer',
            'weight_g' => 'integer',
            'requires_shipping' => 'bool',
            'is_default' => 'bool',
            'position' => 'integer',
            'status' => VariantStatus::class,
        ];
    }
}


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

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class CartLine extends Model
{
    /** @use HasFactory<\Database\Factories\CartLineFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'cart_id',
        'variant_id',
        'quantity',
        'unit_price_amount',
        'line_subtotal_amount',
        'line_discount_amount',
        'line_total_amount',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'quantity' => 1,
        'unit_price_amount' => 0,
        'line_subtotal_amount' => 0,
        'line_discount_amount' => 0,
        'line_total_amount' => 0,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('cart', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<Cart, $this>
     */
    public function cart(): BelongsTo
    {
        return $this->belongsTo(Cart::class);
    }

    /**
     * @return BelongsTo<ProductVariant, $this>
     */
    public function variant(): BelongsTo
    {
        return $this->belongsTo(ProductVariant::class, 'variant_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'quantity' => 'integer',
            'unit_price_amount' => 'integer',
            'line_subtotal_amount' => 'integer',
            'line_discount_amount' => 'integer',
            'line_total_amount' => 'integer',
        ];
    }
}


cmd: sed -n '1,160p' app/Exceptions/CartVersionMismatchException.php


cmd: sed -n '1,160p' app/Exceptions/InvalidDiscountException.php


cmd: sed -n '1,200p' app/Exceptions/InvalidCheckoutTransitionException.php


command: sed -n '1,160p' app/Exceptions/CartVersionMismatchException.php

aggregated_output:
<?php

namespace App\Exceptions;

use RuntimeException;

class CartVersionMismatchException extends RuntimeException
{
    public function __construct(
        public readonly int $expectedVersion,
        public readonly int $currentVersion,
    ) {
        parent::__construct("Cart version mismatch. Expected {$expectedVersion}, current {$currentVersion}.");
    }
}


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

namespace App\Exceptions;

use RuntimeException;

class CartVersionMismatchException extends RuntimeException
{
    public function __construct(
        public readonly int $expectedVersion,
        public readonly int $currentVersion,
    ) {
        parent::__construct("Cart version mismatch. Expected {$expectedVersion}, current {$currentVersion}.");
    }
}


command: sed -n '1,200p' app/Exceptions/InvalidCheckoutTransitionException.php

aggregated_output:
<?php

namespace App\Exceptions;

use RuntimeException;

class InvalidCheckoutTransitionException extends RuntimeException
{
    public static function because(string $reason): self
    {
        return new self($reason);
    }
}


command: sed -n '1,160p' app/Exceptions/InvalidDiscountException.php

aggregated_output:
<?php

namespace App\Exceptions;

use RuntimeException;

class InvalidDiscountException extends RuntimeException
{
    public function __construct(
        public readonly string $reasonCode,
        string $message,
    ) {
        parent::__construct($message);
    }

    public static function because(string $reasonCode, string $message): self
    {
        return new self($reasonCode, $message);
    }
}


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

namespace App\Exceptions;

use RuntimeException;

class InvalidDiscountException extends RuntimeException
{
    public function __construct(
        public readonly string $reasonCode,
        string $message,
    ) {
        parent::__construct($message);
    }

    public static function because(string $reasonCode, string $message): self
    {
        return new self($reasonCode, $message);
    }
}


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

namespace App\Exceptions;

use RuntimeException;

class InvalidCheckoutTransitionException extends RuntimeException
{
    public static function because(string $reason): self
    {
        return new self($reason);
    }
}


cmd: sed -n '1,320p' app/Services/TaxCalculator.php


command: sed -n '1,320p' app/Services/TaxCalculator.php

aggregated_output:
<?php

namespace App\Services;

use App\Enums\TaxMode;
use App\Models\TaxSettings;
use App\Services\Tax\ManualTaxProvider;
use App\Services\Tax\StripeTaxProvider;
use App\ValueObjects\TaxResult;

class TaxCalculator
{
    public function __construct(
        private readonly ManualTaxProvider $manual,
        private readonly StripeTaxProvider $stripe,
    ) {}

    /**
     * @param  array<string, mixed>  $address
     */
    public function calculate(int $amount, TaxSettings $settings, array $address): TaxResult
    {
        return $this->calculateForAmounts([$amount], 0, $settings, $address);
    }

    /**
     * @param  array<int, int>  $amounts
     * @param  array<string, mixed>  $address
     */
    public function calculateForAmounts(array $amounts, int $shippingAmount, TaxSettings $settings, array $address): TaxResult
    {
        if ($settings->mode === TaxMode::Provider && $settings->provider === 'stripe_tax') {
            return $this->stripe->calculate($amounts, $shippingAmount, $settings, $address);
        }

        return $this->manual->calculate($amounts, $shippingAmount, $settings, $address);
    }

    public function extractInclusive(int $grossAmount, int $rateBasisPoints): int
    {
        return $this->manual->extractInclusive($grossAmount, $rateBasisPoints);
    }

    public function addExclusive(int $netAmount, int $rateBasisPoints): int
    {
        return $this->manual->addExclusive($netAmount, $rateBasisPoints);
    }
}


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

namespace App\Services;

use App\Enums\TaxMode;
use App\Models\TaxSettings;
use App\Services\Tax\ManualTaxProvider;
use App\Services\Tax\StripeTaxProvider;
use App\ValueObjects\TaxResult;

class TaxCalculator
{
    public function __construct(
        private readonly ManualTaxProvider $manual,
        private readonly StripeTaxProvider $stripe,
    ) {}

    /**
     * @param  array<string, mixed>  $address
     */
    public function calculate(int $amount, TaxSettings $settings, array $address): TaxResult
    {
        return $this->calculateForAmounts([$amount], 0, $settings, $address);
    }

    /**
     * @param  array<int, int>  $amounts
     * @param  array<string, mixed>  $address
     */
    public function calculateForAmounts(array $amounts, int $shippingAmount, TaxSettings $settings, array $address): TaxResult
    {
        if ($settings->mode === TaxMode::Provider && $settings->provider === 'stripe_tax') {
            return $this->stripe->calculate($amounts, $shippingAmount, $settings, $address);
        }

        return $this->manual->calculate($amounts, $shippingAmount, $settings, $address);
    }

    public function extractInclusive(int $grossAmount, int $rateBasisPoints): int
    {
        return $this->manual->extractInclusive($grossAmount, $rateBasisPoints);
    }

    public function addExclusive(int $netAmount, int $rateBasisPoints): int
    {
        return $this->manual->addExclusive($netAmount, $rateBasisPoints);
    }
}


cmd: sed -n '1,300p' app/Services/Tax/ManualTaxProvider.php


command: sed -n '1,300p' app/Services/Tax/ManualTaxProvider.php

aggregated_output:
<?php

namespace App\Services\Tax;

use App\Contracts\TaxProvider;
use App\Models\TaxSettings;
use App\ValueObjects\TaxLine;
use App\ValueObjects\TaxResult;

class ManualTaxProvider implements TaxProvider
{
    /**
     * @param  array<int, int>  $amounts
     * @param  array<string, mixed>  $address
     */
    public function calculate(array $amounts, int $shippingAmount, TaxSettings $settings, array $address): TaxResult
    {
        $rate = $this->rateFor($settings, $address);

        if ($rate <= 0) {
            return new TaxResult([], 0, 0);
        }

        if ((bool) data_get($settings->config_json, 'shipping_taxable', true) && $shippingAmount > 0) {
            $amounts[] = $shippingAmount;
        }

        $taxAmount = collect($amounts)->sum(fn (int $amount): int => $settings->prices_include_tax
            ? $this->extractInclusive($amount, $rate)
            : $this->addExclusive($amount, $rate)
        );

        return new TaxResult([
            new TaxLine($this->nameFor($settings, $address), $rate, $taxAmount),
        ], $taxAmount, $rate);
    }

    /**
     * @param  array<string, mixed>  $address
     */
    public function rateFor(TaxSettings $settings, array $address): int
    {
        $country = strtoupper((string) (data_get($address, 'country_code') ?: data_get($address, 'country')));
        $province = strtoupper((string) data_get($address, 'province_code'));

        foreach (data_get($settings->config_json, 'rates', []) as $rate) {
            $rateCountry = strtoupper((string) data_get($rate, 'country'));
            $rateProvince = strtoupper((string) data_get($rate, 'province_code'));

            if ($rateCountry !== '' && $rateCountry !== $country) {
                continue;
            }

            if ($rateProvince !== '' && $rateProvince !== $province) {
                continue;
            }

            return (int) data_get($rate, 'rate_bps', 0);
        }

        return (int) data_get($settings->config_json, 'default_rate_bps', 0);
    }

    /**
     * @param  array<string, mixed>  $address
     */
    public function nameFor(TaxSettings $settings, array $address): string
    {
        $country = strtoupper((string) (data_get($address, 'country_code') ?: data_get($address, 'country')));

        foreach (data_get($settings->config_json, 'rates', []) as $rate) {
            if (strtoupper((string) data_get($rate, 'country')) === $country && data_get($rate, 'name')) {
                return (string) data_get($rate, 'name');
            }
        }

        return (string) data_get($settings->config_json, 'name', 'Tax');
    }

    public function extractInclusive(int $grossAmount, int $rateBasisPoints): int
    {
        if ($grossAmount <= 0 || $rateBasisPoints <= 0) {
            return 0;
        }

        return $grossAmount - intdiv($grossAmount * 10000, 10000 + $rateBasisPoints);
    }

    public function addExclusive(int $netAmount, int $rateBasisPoints): int
    {
        if ($netAmount <= 0 || $rateBasisPoints <= 0) {
            return 0;
        }

        return (int) round($netAmount * $rateBasisPoints / 10000);
    }
}


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

namespace App\Services\Tax;

use App\Contracts\TaxProvider;
use App\Models\TaxSettings;
use App\ValueObjects\TaxLine;
use App\ValueObjects\TaxResult;

class ManualTaxProvider implements TaxProvider
{
    /**
     * @param  array<int, int>  $amounts
     * @param  array<string, mixed>  $address
     */
    public function calculate(array $amounts, int $shippingAmount, TaxSettings $settings, array $address): TaxResult
    {
        $rate = $this->rateFor($settings, $address);

        if ($rate <= 0) {
            return new TaxResult([], 0, 0);
        }

        if ((bool) data_get($settings->config_json, 'shipping_taxable', true) && $shippingAmount > 0) {
            $amounts[] = $shippingAmount;
        }

        $taxAmount = collect($amounts)->sum(fn (int $amount): int => $settings->prices_include_tax
            ? $this->extractInclusive($amount, $rate)
            : $this->addExclusive($amount, $rate)
        );

        return new TaxResult([
            new TaxLine($this->nameFor($settings, $address), $rate, $taxAmount),
        ], $taxAmount, $rate);
    }

    /**
     * @param  array<string, mixed>  $address
     */
    public function rateFor(TaxSettings $settings, array $address): int
    {
        $country = strtoupper((string) (data_get($address, 'country_code') ?: data_get($address, 'country')));
        $province = strtoupper((string) data_get($address, 'province_code'));

        foreach (data_get($settings->config_json, 'rates', []) as $rate) {
            $rateCountry = strtoupper((string) data_get($rate, 'country'));
            $rateProvince = strtoupper((string) data_get($rate, 'province_code'));

            if ($rateCountry !== '' && $rateCountry !== $country) {
                continue;
            }

            if ($rateProvince !== '' && $rateProvince !== $province) {
                continue;
            }

            return (int) data_get($rate, 'rate_bps', 0);
        }

        return (int) data_get($settings->config_json, 'default_rate_bps', 0);
    }

    /**
     * @param  array<string, mixed>  $address
     */
    public function nameFor(TaxSettings $settings, array $address): string
    {
        $country = strtoupper((string) (data_get($address, 'country_code') ?: data_get($address, 'country')));

        foreach (data_get($settings->config_json, 'rates', []) as $rate) {
            if (strtoupper((string) data_get($rate, 'country')) === $country && data_get($rate, 'name')) {
                return (string) data_get($rate, 'name');
            }
        }

        return (string) data_get($settings->config_json, 'name', 'Tax');
    }

    public function extractInclusive(int $grossAmount, int $rateBasisPoints): int
    {
        if ($grossAmount <= 0 || $rateBasisPoints <= 0) {
            return 0;
        }

        return $grossAmount - intdiv($grossAmount * 10000, 10000 + $rateBasisPoints);
    }

    public function addExclusive(int $netAmount, int $rateBasisPoints): int
    {
        if ($netAmount <= 0 || $rateBasisPoints <= 0) {
            return 0;
        }

        return (int) round($netAmount * $rateBasisPoints / 10000);
    }
}


message: I’m going to tighten the API to the spec while adding tests: `cart_version` will be the public optimistic-lock field, cart line creation will return `201`, deletes will validate version conflicts, checkout routes will use the checkout limiter, and checkout creation will accept the email required by the API spec.

phase: commentary


type: message

role: assistant

text: I’m going to tighten the API to the spec while adding tests: `cart_version` will be the public optimistic-lock field, cart line creation will return `201`, deletes will validate version conflicts, checkout routes will use the checkout limiter, and checkout creation will accept the email required by the API spec.


cmd: php artisan make:request Api/Storefront/V1/DestroyCartLineRequest --no-interaction


cmd: php artisan make:request Api/Storefront/V1/StoreCartRequest --no-interaction


output:
Chunk ID: 083043
Wall time: 0.2144 seconds
Process exited with code 0
Original token count: 27
Output:

   INFO  Request [app/Http/Requests/Api/Storefront/V1/DestroyCartLineRequest.php] created successfully.


output:
Chunk ID: 27a805
Wall time: 0.2087 seconds
Process exited with code 0
Original token count: 26
Output:

   INFO  Request [app/Http/Requests/Api/Storefront/V1/StoreCartRequest.php] created successfully.


command: php artisan make:request Api/Storefront/V1/StoreCartRequest --no-interaction

type: unknown

aggregated_output: INFO  Request [app/Http/Requests/Api/Storefront/V1/StoreCartRequest.php] created successfully.


command: php artisan make:request Api/Storefront/V1/DestroyCartLineRequest --no-interaction

aggregated_output: INFO  Request [app/Http/Requests/Api/Storefront/V1/DestroyCartLineRequest.php] created successfully.


cmd: sed -n '1,300p' app/Services/ShippingCalculator.php


command: sed -n '1,300p' app/Services/ShippingCalculator.php

aggregated_output:
<?php

namespace App\Services;

use App\Enums\ShippingRateType;
use App\Models\Cart;
use App\Models\CartLine;
use App\Models\ProductVariant;
use App\Models\ShippingRate;
use App\Models\ShippingZone;
use App\Models\Store;
use Illuminate\Support\Collection;

class ShippingCalculator
{
    /**
     * @param  array<string, mixed>  $address
     * @return Collection<int, ShippingRate>
     */
    public function getAvailableRates(Store $store, array $address): Collection
    {
        $zone = $this->matchingZone($store, $address);

        if (! $zone instanceof ShippingZone) {
            return collect();
        }

        return ShippingRate::withoutGlobalScopes()
            ->where('zone_id', $zone->getKey())
            ->where('is_active', true)
            ->orderBy('id')
            ->get();
    }

    /**
     * @param  array<string, mixed>  $address
     */
    public function matchingZone(Store $store, array $address): ?ShippingZone
    {
        $country = strtoupper((string) (data_get($address, 'country_code') ?: data_get($address, 'country')));
        $region = strtoupper((string) data_get($address, 'province_code'));
        $bestZone = null;
        $bestSpecificity = -1;

        ShippingZone::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->orderBy('id')
            ->get()
            ->each(function (ShippingZone $zone) use ($country, $region, &$bestZone, &$bestSpecificity): void {
                $countries = collect($zone->countries_json)->map(fn (string $code): string => strtoupper($code));
                $regions = collect($zone->regions_json)->map(fn (string $code): string => strtoupper($code));

                if (! $countries->contains($country)) {
                    return;
                }

                $specificity = $regions->contains($region) ? 2 : 1;

                if ($specificity > $bestSpecificity) {
                    $bestZone = $zone;
                    $bestSpecificity = $specificity;
                }
            });

        return $bestZone;
    }

    public function calculate(ShippingRate $rate, Cart $cart): ?int
    {
        if (! $this->requiresShipping($cart)) {
            return 0;
        }

        return match ($rate->type) {
            ShippingRateType::Flat => (int) data_get($rate->config_json, 'amount', 0),
            ShippingRateType::Weight => $this->weightRate($rate, $cart),
            ShippingRateType::Price => $this->priceRate($rate, $cart),
            ShippingRateType::Carrier => (int) data_get($rate->config_json, 'amount', 1299),
        };
    }

    public function requiresShipping(Cart $cart): bool
    {
        return $this->physicalLines($cart)->isNotEmpty();
    }

    private function weightRate(ShippingRate $rate, Cart $cart): ?int
    {
        $totalWeight = $this->physicalLines($cart)
            ->sum(function (CartLine $line): int {
                $variant = $this->variant($line);

                return ($variant?->weight_g ?? 0) * $line->quantity;
            });

        foreach (data_get($rate->config_json, 'ranges', []) as $range) {
            $minimum = (int) data_get($range, 'min_g', 0);
            $maximum = data_get($range, 'max_g');

            if ($totalWeight >= $minimum && ($maximum === null || $totalWeight <= (int) $maximum)) {
                return (int) data_get($range, 'amount', 0);
            }
        }

        return null;
    }

    private function priceRate(ShippingRate $rate, Cart $cart): ?int
    {
        $subtotal = CartLine::withoutGlobalScopes()
            ->where('cart_id', $cart->getKey())
            ->sum('line_subtotal_amount');

        foreach (data_get($rate->config_json, 'ranges', []) as $range) {
            $minimum = (int) data_get($range, 'min_amount', 0);
            $maximum = data_get($range, 'max_amount');

            if ($subtotal >= $minimum && ($maximum === null || $subtotal <= (int) $maximum)) {
                return (int) data_get($range, 'amount', 0);
            }
        }

        return null;
    }

    /**
     * @return Collection<int, CartLine>
     */
    private function physicalLines(Cart $cart): Collection
    {
        return CartLine::withoutGlobalScopes()
            ->where('cart_id', $cart->getKey())
            ->get()
            ->filter(function (CartLine $line): bool {
                return (bool) $this->variant($line)?->requires_shipping;
            });
    }

    private function variant(CartLine $line): ?ProductVariant
    {
        return ProductVariant::withoutGlobalScopes()
            ->whereKey($line->variant_id)
            ->first();
    }
}


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

namespace App\Services;

use App\Enums\ShippingRateType;
use App\Models\Cart;
use App\Models\CartLine;
use App\Models\ProductVariant;
use App\Models\ShippingRate;
use App\Models\ShippingZone;
use App\Models\Store;
use Illuminate\Support\Collection;

class ShippingCalculator
{
    /**
     * @param  array<string, mixed>  $address
     * @return Collection<int, ShippingRate>
     */
    public function getAvailableRates(Store $store, array $address): Collection
    {
        $zone = $this->matchingZone($store, $address);

        if (! $zone instanceof ShippingZone) {
            return collect();
        }

        return ShippingRate::withoutGlobalScopes()
            ->where('zone_id', $zone->getKey())
            ->where('is_active', true)
            ->orderBy('id')
            ->get();
    }

    /**
     * @param  array<string, mixed>  $address
     */
    public function matchingZone(Store $store, array $address): ?ShippingZone
    {
        $country = strtoupper((string) (data_get($address, 'country_code') ?: data_get($address, 'country')));
        $region = strtoupper((string) data_get($address, 'province_code'));
        $bestZone = null;
        $bestSpecificity = -1;

        ShippingZone::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->orderBy('id')
            ->get()
            ->each(function (ShippingZone $zone) use ($country, $region, &$bestZone, &$bestSpecificity): void {
                $countries = collect($zone->countries_json)->map(fn (string $code): string => strtoupper($code));
                $regions = collect($zone->regions_json)->map(fn (string $code): string => strtoupper($code));

                if (! $countries->contains($country)) {
                    return;
                }

                $specificity = $regions->contains($region) ? 2 : 1;

                if ($specificity > $bestSpecificity) {
                    $bestZone = $zone;
                    $bestSpecificity = $specificity;
                }
            });

        return $bestZone;
    }

    public function calculate(ShippingRate $rate, Cart $cart): ?int
    {
        if (! $this->requiresShipping($cart)) {
            return 0;
        }

        return match ($rate->type) {
            ShippingRateType::Flat => (int) data_get($rate->config_json, 'amount', 0),
            ShippingRateType::Weight => $this->weightRate($rate, $cart),
            ShippingRateType::Price => $this->priceRate($rate, $cart),
            ShippingRateType::Carrier => (int) data_get($rate->config_json, 'amount', 1299),
        };
    }

    public function requiresShipping(Cart $cart): bool
    {
        return $this->physicalLines($cart)->isNotEmpty();
    }

    private function weightRate(ShippingRate $rate, Cart $cart): ?int
    {
        $totalWeight = $this->physicalLines($cart)
            ->sum(function (CartLine $line): int {
                $variant = $this->variant($line);

                return ($variant?->weight_g ?? 0) * $line->quantity;
            });

        foreach (data_get($rate->config_json, 'ranges', []) as $range) {
            $minimum = (int) data_get($range, 'min_g', 0);
            $maximum = data_get($range, 'max_g');

            if ($totalWeight >= $minimum && ($maximum === null || $totalWeight <= (int) $maximum)) {
                return (int) data_get($range, 'amount', 0);
            }
        }

        return null;
    }

    private function priceRate(ShippingRate $rate, Cart $cart): ?int
    {
        $subtotal = CartLine::withoutGlobalScopes()
            ->where('cart_id', $cart->getKey())
            ->sum('line_subtotal_amount');

        foreach (data_get($rate->config_json, 'ranges', []) as $range) {
            $minimum = (int) data_get($range, 'min_amount', 0);
            $maximum = data_get($range, 'max_amount');

            if ($subtotal >= $minimum && ($maximum === null || $subtotal <= (int) $maximum)) {
                return (int) data_get($range, 'amount', 0);
            }
        }

        return null;
    }

    /**
     * @return Collection<int, CartLine>
     */
    private function physicalLines(Cart $cart): Collection
    {
        return CartLine::withoutGlobalScopes()
            ->where('cart_id', $cart->getKey())
            ->get()
            ->filter(function (CartLine $line): bool {
                return (bool) $this->variant($line)?->requires_shipping;
            });
    }

    private function variant(CartLine $line): ?ProductVariant
    {
        return ProductVariant::withoutGlobalScopes()
            ->whereKey($line->variant_id)
            ->first();
    }
}


status: completed

input:
*** Begin Patch
*** Update File: app/Http/Requests/Api/Storefront/V1/StoreCartRequest.php
@@
 namespace App\Http\Requests\Api\Storefront\V1;

+use App\Models\Store;
 use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;

 class StoreCartRequest extends FormRequest
 {
@@
      */
     public function rules(): array
     {
+        $currencyRules = ['nullable', 'string', 'size:3'];
+        $store = app()->bound('current_store') ? app('current_store') : null;
+
+        if ($store instanceof Store) {
+            $currencyRules[] = Rule::in([$store->default_currency]);
+        }
+
         return [
-            //
+            'currency' => $currencyRules,
         ];
     }
 }
*** Update File: app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php
@@
     public function rules(): array
     {
         return [
             'variant_id' => ['required', 'integer', 'exists:product_variants,id'],
             'quantity' => ['required', 'integer', 'min:1'],
+            'cart_version' => ['nullable', 'integer', 'min:1'],
             'expected_version' => ['nullable', 'integer', 'min:1'],
         ];
     }
+
+    public function expectedVersion(): ?int
+    {
+        $version = $this->validated('cart_version') ?? $this->validated('expected_version');
+
+        return $version === null ? null : (int) $version;
+    }
 }
*** Update File: app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php
@@
     public function rules(): array
     {
         return [
             'quantity' => ['required', 'integer', 'min:0'],
-            'expected_version' => ['nullable', 'integer', 'min:1'],
+            'cart_version' => ['required_without:expected_version', 'integer', 'min:1'],
+            'expected_version' => ['required_without:cart_version', 'integer', 'min:1'],
         ];
     }
+
+    public function expectedVersion(): int
+    {
+        return (int) ($this->validated('cart_version') ?? $this->validated('expected_version'));
+    }
 }
*** Update File: app/Http/Requests/Api/Storefront/V1/DestroyCartLineRequest.php
@@
 namespace App\Http\Requests\Api\Storefront\V1;

 use Illuminate\Foundation\Http\FormRequest;
@@
     public function rules(): array
     {
         return [
-            //
+            'cart_version' => ['required_without:expected_version', 'integer', 'min:1'],
+            'expected_version' => ['required_without:cart_version', 'integer', 'min:1'],
         ];
     }
+
+    public function expectedVersion(): int
+    {
+        return (int) ($this->validated('cart_version') ?? $this->validated('expected_version'));
+    }
 }
*** Update File: app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php
@@
     public function rules(): array
     {
         return [
             'cart_id' => ['required', 'integer', 'exists:carts,id'],
+            'email' => ['required', 'email'],
         ];
     }
 }
*** Update File: app/Http/Requests/Api/Storefront/V1/SetCheckoutAddressRequest.php
@@
     public function rules(): array
     {
         return [
-            'email' => ['required', 'email'],
+            'email' => ['nullable', 'email'],
             'shipping_address' => ['required', 'array'],
             'shipping_address.first_name' => ['required', 'string'],
             'shipping_address.last_name' => ['required', 'string'],
             'shipping_address.address1' => ['required', 'string'],
             'shipping_address.address2' => ['nullable', 'string'],
             'shipping_address.city' => ['required', 'string'],
             'shipping_address.province_code' => ['nullable', 'string'],
             'shipping_address.country' => ['required', 'string', 'size:2'],
+            'shipping_address.country_code' => ['nullable', 'string', 'size:2'],
             'shipping_address.postal_code' => ['required', 'string'],
             'billing_address' => ['nullable', 'array'],
         ];
     }
 }
*** Update File: app/Http/Requests/Api/Storefront/V1/ApplyCheckoutDiscountRequest.php
@@
     public function rules(): array
     {
         return [
-            'code' => ['nullable', 'string', 'max:255'],
+            'code' => ['required', 'string', 'max:50'],
         ];
     }
 }
*** End Patch


stdout:
Success. Updated the following files:
M app/Http/Requests/Api/Storefront/V1/StoreCartRequest.php
M app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php
M app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php
M app/Http/Requests/Api/Storefront/V1/DestroyCartLineRequest.php
M app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php
M app/Http/Requests/Api/Storefront/V1/SetCheckoutAddressRequest.php
M app/Http/Requests/Api/Storefront/V1/ApplyCheckoutDiscountRequest.php

type: update

unified_diff:
@@ -25,2 +25,3 @@
             'cart_id' => ['required', 'integer', 'exists:carts,id'],
+            'email' => ['required', 'email'],
         ];

unified_diff:
@@ -24,3 +24,3 @@
         return [
-            'email' => ['required', 'email'],
+            'email' => ['nullable', 'email'],
             'shipping_address' => ['required', 'array'],
@@ -33,2 +33,3 @@
             'shipping_address.country' => ['required', 'string', 'size:2'],
+            'shipping_address.country_code' => ['nullable', 'string', 'size:2'],
             'shipping_address.postal_code' => ['required', 'string'],

unified_diff:
@@ -24,5 +24,11 @@
         return [
-            //
+            'cart_version' => ['required_without:expected_version', 'integer', 'min:1'],
+            'expected_version' => ['required_without:cart_version', 'integer', 'min:1'],
         ];
     }
+
+    public function expectedVersion(): int
+    {
+        return (int) ($this->validated('cart_version') ?? $this->validated('expected_version'));
+    }
 }

unified_diff:
@@ -4,3 +4,5 @@

+use App\Models\Store;
 use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;

@@ -23,4 +25,11 @@
     {
+        $currencyRules = ['nullable', 'string', 'size:3'];
+        $store = app()->bound('current_store') ? app('current_store') : null;
+
+        if ($store instanceof Store) {
+            $currencyRules[] = Rule::in([$store->default_currency]);
+        }
+
         return [
-            //
+            'currency' => $currencyRules,
         ];

unified_diff:
@@ -26,2 +26,3 @@
             'quantity' => ['required', 'integer', 'min:1'],
+            'cart_version' => ['nullable', 'integer', 'min:1'],
             'expected_version' => ['nullable', 'integer', 'min:1'],
@@ -29,2 +30,9 @@
     }
+
+    public function expectedVersion(): ?int
+    {
+        $version = $this->validated('cart_version') ?? $this->validated('expected_version');
+
+        return $version === null ? null : (int) $version;
+    }
 }

unified_diff:
@@ -25,5 +25,11 @@
             'quantity' => ['required', 'integer', 'min:0'],
-            'expected_version' => ['nullable', 'integer', 'min:1'],
+            'cart_version' => ['required_without:expected_version', 'integer', 'min:1'],
+            'expected_version' => ['required_without:cart_version', 'integer', 'min:1'],
         ];
     }
+
+    public function expectedVersion(): int
+    {
+        return (int) ($this->validated('cart_version') ?? $this->validated('expected_version'));
+    }
 }


output: {"output":"Success. Updated the following files:\nM app/Http/Requests/Api/Storefront/V1/StoreCartRequest.php\nM app/Http/Requests/Api/Storefront/V1/StoreCartLineRequest.php\nM app/Http/Requests/Api/Storefront/V1/UpdateCartLineRequest.php\nM app/Http/Requests/Api/Storefront/V1/DestroyCartLineRequest.php\nM app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php\nM app/Http/Requests/Api/Storefront/V1/SetCheckoutAddressRequest.php\nM app/Http/Requests/Api/Storefront/V1/ApplyCheckoutDiscountRequest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: app/Http/Controllers/Api/Storefront/V1/CartController.php
@@
 use App\Http\Controllers\Controller;
+use App\Http\Requests\Api\Storefront\V1\StoreCartRequest;
 use App\Http\Resources\Storefront\V1\CartResource;
 use App\Models\Cart;
 use App\Models\Store;
@@
 class CartController extends Controller
 {
-    public function store(CartService $carts): JsonResponse
+    public function store(StoreCartRequest $request, CartService $carts): JsonResponse
     {
+        $request->validated();
+
         return CartResource::make($this->loadCart($carts->create($this->currentStore())))
             ->response()
             ->setStatusCode(201);
*** Update File: app/Http/Controllers/Api/Storefront/V1/CartLineController.php
@@
 use App\Exceptions\InsufficientInventoryException;
 use App\Exceptions\InvalidCartOperationException;
 use App\Http\Controllers\Controller;
+use App\Http\Requests\Api\Storefront\V1\DestroyCartLineRequest;
 use App\Http\Requests\Api\Storefront\V1\StoreCartLineRequest;
 use App\Http\Requests\Api\Storefront\V1\UpdateCartLineRequest;
@@
                 $cart,
                 (int) $request->validated('variant_id'),
                 (int) $request->validated('quantity'),
-                $request->validated('expected_version'),
+                $request->expectedVersion(),
             );

-            return CartResource::make($this->loadCart($cart->refresh()));
+            return CartResource::make($this->loadCart($cart->refresh()))
+                ->response()
+                ->setStatusCode(201);
         } catch (CartVersionMismatchException $exception) {
             return $this->versionMismatch($exception, $cart);
         } catch (InsufficientInventoryException|InvalidCartOperationException $exception) {
@@
                 $cart,
                 $cartLine->getKey(),
                 (int) $request->validated('quantity'),
-                $request->validated('expected_version'),
+                $request->expectedVersion(),
             );

             return CartResource::make($this->loadCart($cart->refresh()));
@@
-    public function destroy(Cart $cart, CartLine $cartLine, CartService $carts): CartResource
+    public function destroy(DestroyCartLineRequest $request, Cart $cart, CartLine $cartLine, CartService $carts): CartResource|JsonResponse
     {
         abort_unless((int) $cartLine->cart_id === (int) $cart->getKey(), 404);

-        $carts->removeLine($cart, $cartLine->getKey());
+        try {
+            $carts->removeLine($cart, $cartLine->getKey(), $request->expectedVersion());
+        } catch (CartVersionMismatchException $exception) {
+            return $this->versionMismatch($exception, $cart);
+        } catch (InvalidCartOperationException $exception) {
+            return response()->json(['message' => $exception->getMessage()], 422);
+        }

         return CartResource::make($this->loadCart($cart->refresh()));
     }
@@
         return response()->json([
             'message' => $exception->getMessage(),
+            'expected_cart_version' => $exception->expectedVersion,
+            'current_cart_version' => $exception->currentVersion,
             'cart' => CartResource::make($this->loadCart($cart->refresh())),
         ], 409);
     }
*** Update File: app/Http/Controllers/Api/Storefront/V1/CheckoutController.php
@@
     public function store(StoreCheckoutRequest $request, CheckoutService $checkouts): CheckoutResource|JsonResponse
     {
         $cart = Cart::query()->findOrFail($request->validated('cart_id'));

         try {
+            $checkout = $checkouts->createFromCart($cart);
+            $checkout->forceFill([
+                'email' => (string) $request->validated('email'),
+            ])->save();
+            app(PricingEngine::class)->calculate($checkout);
+
             return CheckoutResource::make($this->loadCheckout(
-                $checkouts->createFromCart($cart),
+                $checkout->refresh(),
             ))->response()->setStatusCode(201);
         } catch (InvalidCheckoutTransitionException $exception) {
             return response()->json(['message' => $exception->getMessage()], 422);
         }
@@
     public function address(SetCheckoutAddressRequest $request, Checkout $checkout, CheckoutService $checkouts): CheckoutResource|JsonResponse
     {
+        $addressData = $request->validated();
+        $addressData['email'] ??= $checkout->email;
+
         try {
-            return CheckoutResource::make($this->loadCheckout($checkouts->setAddress($checkout, $request->validated())));
+            return CheckoutResource::make($this->loadCheckout($checkouts->setAddress($checkout, $addressData)));
         } catch (InvalidCheckoutTransitionException $exception) {
             return response()->json(['message' => $exception->getMessage()], 422);
         }
@@
             ], 422);
         }
     }
+
+    public function destroyDiscount(Checkout $checkout, PricingEngine $pricing): CheckoutResource
+    {
+        abort_if($checkout->discount_code === null, 404);
+
+        $checkout->forceFill(['discount_code' => null])->save();
+        $pricing->calculate($checkout);
+
+        return CheckoutResource::make($this->loadCheckout($checkout->refresh()));
+    }

     public function paymentMethod(SelectCheckoutPaymentRequest $request, Checkout $checkout, CheckoutService $checkouts): CheckoutResource|JsonResponse
     {
*** Update File: routes/api.php
@@
 Route::middleware(['store.resolve', 'throttle:api.storefront'])
     ->prefix('storefront/v1')
     ->name('api.storefront.v1.')
     ->group(function (): void {
         Route::post('carts', [CartController::class, 'store'])->name('carts.store');
@@
-        Route::post('checkouts', [CheckoutController::class, 'store'])->name('checkouts.store');
-        Route::get('checkouts/{checkout}', [CheckoutController::class, 'show'])->name('checkouts.show');
-        Route::put('checkouts/{checkout}/address', [CheckoutController::class, 'address'])->name('checkouts.address');
-        Route::put('checkouts/{checkout}/shipping-method', [CheckoutController::class, 'shippingMethod'])->name('checkouts.shipping-method');
-        Route::post('checkouts/{checkout}/apply-discount', [CheckoutController::class, 'applyDiscount'])->name('checkouts.apply-discount');
-        Route::put('checkouts/{checkout}/payment-method', [CheckoutController::class, 'paymentMethod'])->name('checkouts.payment-method');
+        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');
+        });
     });
*** Update File: app/Providers/AppServiceProvider.php
@@
         });

         RateLimiter::for('checkout', function (Request $request): Limit {
-            return Limit::perMinute(10)->by($request->session()->getId() ?: $request->ip());
+            $sessionId = $request->hasSession() ? $request->session()->getId() : null;
+
+            return Limit::perMinute(10)->by($sessionId ?: $request->ip());
         });
*** End Patch