command: sed -n '1,180p' routes/web.php; sed -n '1,130p' bootstrap/app.php; sed -n '1,160p' app/Auth/StoreScopedPasswordBrokerManager.php; sed -n '1,200p' app/Livewire/Storefront/Account/Auth/ForgotPassword.php; sed -n '1,220p' app/Livewire/Storefront/Account/Auth/ResetPassword.php; sed -n '1,160p' app/Livewire/Admin/Auth/Login.php

web.php

cmd: sed -n '1,180p' routes/web.php

app.php

cmd: sed -n '1,130p' bootstrap/app.php

Login.php

stdout:
<?php

use App\Livewire\Admin\Analytics\Index as AdminAnalyticsIndex;
use App\Livewire\Admin\Apps\Index as AdminAppsIndex;
use App\Livewire\Admin\Apps\Show as AdminAppsShow;
use App\Livewire\Admin\Auth\ForgotPassword as AdminForgotPassword;
use App\Livewire\Admin\Auth\Login as AdminLogin;
use App\Livewire\Admin\Auth\ResetPassword as AdminResetPassword;
use App\Livewire\Admin\Collections\Create as AdminCollectionsCreate;
use App\Livewire\Admin\Collections\Edit as AdminCollectionsEdit;
use App\Livewire\Admin\Collections\Index as AdminCollectionsIndex;
use App\Livewire\Admin\Customers\Index as AdminCustomersIndex;
use App\Livewire\Admin\Customers\Show as AdminCustomersShow;
use App\Livewire\Admin\Dashboard as AdminDashboard;
use App\Livewire\Admin\Developers\Index as AdminDevelopersIndex;
use App\Livewire\Admin\Discounts\Form as AdminDiscountForm;
use App\Livewire\Admin\Discounts\Index as AdminDiscountsIndex;
use App\Livewire\Admin\Inventory\Index as AdminInventoryIndex;
use App\Livewire\Admin\Navigation\Index as AdminNavigationIndex;
use App\Livewire\Admin\Orders\Index as AdminOrdersIndex;
use App\Livewire\Admin\Orders\Show as AdminOrdersShow;
use App\Livewire\Admin\Pages\Create as AdminPagesCreate;
use App\Livewire\Admin\Pages\Edit as AdminPagesEdit;
use App\Livewire\Admin\Pages\Index as AdminPagesIndex;
use App\Livewire\Admin\Products\Form as AdminProductForm;
use App\Livewire\Admin\Products\Index as AdminProductsIndex;
use App\Livewire\Admin\Search\Settings as AdminSearchSettings;
use App\Livewire\Admin\Settings\General as AdminSettingsGeneral;
use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
use App\Livewire\Admin\Themes\Editor as AdminThemesEditor;
use App\Livewire\Admin\Themes\Index as AdminThemesIndex;
use App\Livewire\Storefront\Account\Addresses\Index as AccountAddresses;
use App\Livewire\Storefront\Account\Auth\ForgotPassword as CustomerForgotPassword;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Account\Auth\ResetPassword as CustomerResetPassword;
use App\Livewire\Storefront\Account\Dashboard as AccountDashboard;
use App\Livewire\Storefront\Account\Orders\Index as AccountOrders;
use App\Livewire\Storefront\Account\Orders\Show as AccountOrderShow;
use App\Livewire\Storefront\Cart\Show as CartShow;
use App\Livewire\Storefront\Checkout\Confirmation as CheckoutConfirmation;
use App\Livewire\Storefront\Checkout\Show as CheckoutShow;
use App\Livewire\Storefront\Collections\Index as CollectionsIndex;
use App\Livewire\Storefront\Collections\Show as CollectionShow;
use App\Livewire\Storefront\Home;
use App\Livewire\Storefront\Pages\Show as PageShow;
use App\Livewire\Storefront\Products\Show as ProductShow;
use App\Livewire\Storefront\Search\Index as SearchIndex;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::view('dashboard', 'dashboard')
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

require __DIR__.'/settings.php';

Route::get('/favicon.ico', fn (): \Symfony\Component\HttpFoundation\Response => response('', 204))->name('favicon');

Route::middleware('store.resolve')->group(function (): void {
    Route::livewire('/', Home::class)->name('home');
    Route::livewire('/collections', CollectionsIndex::class)->name('collections.index');
    Route::livewire('/collections/{handle}', CollectionShow::class)->name('collection.show');
    Route::livewire('/products/{handle}', ProductShow::class)->name('product.show');
    Route::livewire('/cart', CartShow::class)->name('cart.show');
    Route::livewire('/search', SearchIndex::class)->name('search');
    Route::livewire('/pages/{handle}', PageShow::class)->name('page.show');
    Route::livewire('/checkout/{checkoutId}', CheckoutShow::class)->name('checkout.show');
    Route::livewire('/checkout/{checkoutId}/confirmation', CheckoutConfirmation::class)->name('checkout.confirmation');
    Route::livewire('/account/login', CustomerLogin::class)->middleware('throttle:login')->name('account.login');
    Route::livewire('/account/register', CustomerRegister::class)->name('account.register');
    Route::livewire('/forgot-password', CustomerForgotPassword::class)->name('password.request');
    Route::livewire('/reset-password/{token}', CustomerResetPassword::class)->name('password.reset');
    Route::post('/account/logout', function (): \Illuminate\Http\RedirectResponse {
        Auth::guard('customer')->logout();
        request()->session()->invalidate();
        request()->session()->regenerateToken();

        return redirect()->route('home');
    })->name('account.logout');

    Route::middleware('auth:customer')->group(function (): void {
        Route::livewire('/account', AccountDashboard::class)->name('account.dashboard');
        Route::livewire('/account/orders', AccountOrders::class)->name('account.orders');
        Route::livewire('/account/orders/{orderNumber}', AccountOrderShow::class)->name('account.order.show');
        Route::livewire('/account/addresses', AccountAddresses::class)->name('account.addresses');
    });
});

Route::livewire('/admin/login', AdminLogin::class)->middleware('throttle:login')->name('admin.login');
Route::livewire('/admin/forgot-password', AdminForgotPassword::class)->middleware('throttle:login')->name('admin.password.request');
Route::livewire('/admin/reset-password/{token}', AdminResetPassword::class)->name('admin.password.reset');
Route::post('/admin/logout', function (): \Illuminate\Http\RedirectResponse {
    Auth::guard('web')->logout();
    request()->session()->invalidate();
    request()->session()->regenerateToken();

    return redirect()->route('admin.login');
})->name('admin.logout');

Route::get('/oauth/authorize', fn (): \Illuminate\Http\JsonResponse => response()->json(['message' => 'OAuth is not enabled in this deployment.'], 501))->middleware('auth')->name('oauth.authorize');
Route::post('/oauth/token', fn (): \Illuminate\Http\JsonResponse => response()->json(['message' => 'OAuth is not enabled in this deployment.'], 501))->name('oauth.token');

Route::prefix('admin')->middleware(['auth', 'verified', 'store.resolve', 'role.check:owner,admin,staff,support'])->group(function (): void {
    Route::livewire('/', AdminDashboard::class)->name('admin.dashboard');
    Route::livewire('/products', AdminProductsIndex::class)->name('admin.products.index');
    Route::livewire('/products/create', AdminProductForm::class)->middleware('role.check:owner,admin,staff')->name('admin.products.create');
    Route::livewire('/products/{product}/edit', AdminProductForm::class)->middleware('role.check:owner,admin,staff')->name('admin.products.edit');
    Route::livewire('/orders', AdminOrdersIndex::class)->name('admin.orders.index');
    Route::livewire('/orders/{order}', AdminOrdersShow::class)->name('admin.orders.show');
    Route::livewire('/customers', AdminCustomersIndex::class)->name('admin.customers.index');
    Route::livewire('/customers/{customer}', AdminCustomersShow::class)->name('admin.customers.show');
    Route::livewire('/discounts', AdminDiscountsIndex::class)->name('admin.discounts.index');
    Route::livewire('/discounts/create', AdminDiscountForm::class)->middleware('role.check:owner,admin,staff')->name('admin.discounts.create');
    Route::livewire('/discounts/{discount}/edit', AdminDiscountForm::class)->middleware('role.check:owner,admin,staff')->name('admin.discounts.edit');
    Route::livewire('/settings', AdminSettingsGeneral::class)->middleware('role.check:owner,admin')->name('admin.settings');
    Route::livewire('/settings/domains', \App\Livewire\Admin\Settings\Domains::class)->middleware('role.check:owner,admin')->name('admin.settings.domains');
    Route::livewire('/settings/shipping', AdminSettingsShipping::class)->middleware('role.check:owner,admin')->name('admin.settings.shipping');
    Route::livewire('/settings/taxes', AdminSettingsTaxes::class)->middleware('role.check:owner,admin')->name('admin.settings.taxes');
    Route::livewire('/inventory', AdminInventoryIndex::class)->name('admin.inventory.index');
    Route::livewire('/collections', AdminCollectionsIndex::class)->name('admin.collections.index');
    Route::livewire('/collections/create', AdminCollectionsCreate::class)->middleware('role.check:owner,admin,staff')->name('admin.collections.create');
    Route::livewire('/collections/{collection}/edit', AdminCollectionsEdit::class)->middleware('role.check:owner,admin,staff')->name('admin.collections.edit');
    Route::livewire('/themes', AdminThemesIndex::class)->name('admin.themes.index');
    Route::livewire('/themes/{theme}/editor', AdminThemesEditor::class)->name('admin.themes.editor');
    Route::livewire('/pages', AdminPagesIndex::class)->name('admin.pages.index');
    Route::livewire('/pages/create', AdminPagesCreate::class)->middleware('role.check:owner,admin')->name('admin.pages.create');
    Route::livewire('/pages/{page}/edit', AdminPagesEdit::class)->middleware('role.check:owner,admin')->name('admin.pages.edit');
    Route::livewire('/navigation', AdminNavigationIndex::class)->name('admin.navigation.index');
    Route::livewire('/apps', AdminAppsIndex::class)->name('admin.apps.index');
    Route::livewire('/apps/{installation}', AdminAppsShow::class)->name('admin.apps.show');
    Route::livewire('/developers', AdminDevelopersIndex::class)->middleware('role.check:owner,admin')->name('admin.developers.index');
    Route::livewire('/analytics', AdminAnalyticsIndex::class)->name('admin.analytics.index');
    Route::livewire('/search/settings', AdminSearchSettings::class)->name('admin.search.settings');
});
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->prependToPriorityList(
            before: \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
            prepend: App\Http\Middleware\ResolveStore::class,
        );

        $middleware->group('storefront', [
            'store.resolve:storefront',
        ]);

        $middleware->group('admin', [
            'store.resolve:admin',
        ]);

        $middleware->alias([
            'store.resolve' => App\Http\Middleware\ResolveStore::class,
            'role.check' => App\Http\Middleware\EnsureStoreRole::class,
            'api.ability' => App\Http\Middleware\EnsureApiAbility::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();
<?php

namespace App\Auth;

use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Auth\Passwords\PasswordBrokerManager;
use Illuminate\Contracts\Auth\PasswordBrokerFactory;

class StoreScopedPasswordBrokerManager extends PasswordBrokerManager implements PasswordBrokerFactory
{
    protected function createTokenRepository(array $config)
    {
        $key = $this->app['config']['app.key'];

        if (str_starts_with($key, 'base64:')) {
            $key = base64_decode(substr($key, 7));
        }

        if (($config['table'] ?? null) === 'customer_password_reset_tokens') {
            return new StoreScopedTokenRepository(
                $this->app['db']->connection($config['connection'] ?? null),
                $this->app['hash'],
                $config['table'],
                $key,
                ($config['expire'] ?? 60) * 60,
                $config['throttle'] ?? 0,
            );
        }

        return new DatabaseTokenRepository(
            $this->app['db']->connection($config['connection'] ?? null),
            $this->app['hash'],
            $config['table'],
            $key,
            ($config['expire'] ?? 60) * 60,
            $config['throttle'] ?? 0,
        );
    }
}
<?php

namespace App\Livewire\Storefront\Account\Auth;

use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ForgotPassword extends Component
{
    public string $email = '';

    public string $message = '';

    public function sendResetLink(): void
    {
        $this->validate(['email' => ['required', 'email']]);
        Password::broker('customers')->sendResetLink(['email' => $this->email]);
        $this->message = 'If an account exists for that email, a reset link has been sent.';
    }

    public function render(): mixed
    {
        return view('livewire.storefront.account.auth.forgot-password')->layout('layouts.storefront');
    }
}
<?php

namespace App\Livewire\Storefront\Account\Auth;

use App\Models\Customer;
use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ResetPassword extends Component
{
    public string $token;

    public string $email = '';

    public string $password = '';

    public string $passwordConfirmation = '';

    public function mount(string $token): void
    {
        $this->token = $token;
        $this->email = request()->string('email')->toString();
    }

    public function resetPassword(): void
    {
        $data = $this->validate(['email' => ['required', 'email'], 'password' => ['required', 'min:8', 'same:passwordConfirmation']]);
        $status = Password::broker('customers')->reset(['email' => $data['email'], 'password' => $data['password'], 'password_confirmation' => $this->passwordConfirmation, 'token' => $this->token], function (Customer $customer, string $password): void {
            $customer->password_hash = $password;
            $customer->save();
        });

        if ($status !== Password::PASSWORD_RESET) {
            $this->addError('email', __($status));

            return;
        }

        $this->redirect(route('account.login'), navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.storefront.account.auth.reset-password')->layout('layouts.storefront');
    }
}
<?php

namespace App\Livewire\Admin\Auth;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Component;

class Login extends Component
{
    public string $email = '';

    public string $password = '';

    public bool $remember = false;

    public function login(): void
    {
        $credentials = $this->validate(['email' => ['required', 'email'], 'password' => ['required', 'string']]);
        $key = 'admin-login|'.request()->ip();

        if (RateLimiter::tooManyAttempts($key, 5)) {
            $this->addError('email', 'Too many attempts. Try again later.');

            return;
        }

        RateLimiter::hit($key, 60);

        if (! Auth::guard('web')->attempt([...$credentials, 'status' => 'active'], $this->remember)) {
            $this->addError('email', 'Invalid credentials');

            return;
        }

        RateLimiter::clear($key);
        session()->regenerate();
        $store = auth()->user()->stores()->first();

        if ($store !== null) {
            session()->put('current_store_id', $store->getKey());
        }

        $this->redirect(route('admin.dashboard'), navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.login')->layout('layouts.auth');
    }
}


text:
Script completed
Wall time 0.1 seconds
Output:

text:
<?php

use App\Livewire\Admin\Analytics\Index as AdminAnalyticsIndex;
use App\Livewire\Admin\Apps\Index as AdminAppsIndex;
use App\Livewire\Admin\Apps\Show as AdminAppsShow;
use App\Livewire\Admin\Auth\ForgotPassword as AdminForgotPassword;
use App\Livewire\Admin\Auth\Login as AdminLogin;
use App\Livewire\Admin\Auth\ResetPassword as AdminResetPassword;
use App\Livewire\Admin\Collections\Create as AdminCollectionsCreate;
use App\Livewire\Admin\Collections\Edit as AdminCollectionsEdit;
use App\Livewire\Admin\Collections\Index as AdminCollectionsIndex;
use App\Livewire\Admin\Customers\Index as AdminCustomersIndex;
use App\Livewire\Admin\Customers\Show as AdminCustomersShow;
use App\Livewire\Admin\Dashboard as AdminDashboard;
use App\Livewire\Admin\Developers\Index as AdminDevelopersIndex;
use App\Livewire\Admin\Discounts\Form as AdminDiscountForm;
use App\Livewire\Admin\Discounts\Index as AdminDiscountsIndex;
use App\Livewire\Admin\Inventory\Index as AdminInventoryIndex;
use App\Livewire\Admin\Navigation\Index as AdminNavigationIndex;
use App\Livewire\Admin\Orders\Index as AdminOrdersIndex;
use App\Livewire\Admin\Orders\Show as AdminOrdersShow;
use App\Livewire\Admin\Pages\Create as AdminPagesCreate;
use App\Livewire\Admin\Pages\Edit as AdminPagesEdit;
use App\Livewire\Admin\Pages\Index as AdminPagesIndex;
use App\Livewire\Admin\Products\Form as AdminProductForm;
use App\Livewire\Admin\Products\Index as AdminProductsIndex;
use App\Livewire\Admin\Search\Settings as AdminSearchSettings;
use App\Livewire\Admin\Settings\General as AdminSettingsGeneral;
use App\Livewire\Admin\Settings\Shipping as AdminSettingsShipping;
use App\Livewire\Admin\Settings\Taxes as AdminSettingsTaxes;
use App\Livewire\Admin\Themes\Editor as AdminThemesEditor;
use App\Livewire\Admin\Themes\Index as AdminThemesIndex;
use App\Livewire\Storefront\Account\Addresses\Index as AccountAddresses;
use App\Livewire\Storefront\Account\Auth\ForgotPassword as CustomerForgotPassword;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Account\Auth\ResetPassword as CustomerResetPassword;
use App\Livewire\Storefront\Account\Dashboard as AccountDashboard;
use App\Livewire\Storefront\Account\Orders\Index as AccountOrders;
use App\Livewire\Storefront\Account\Orders\Show as AccountOrderShow;
use App\Livewire\Storefront\Cart\Show as CartShow;
use App\Livewire\Storefront\Checkout\Confirmation as CheckoutConfirmation;
use App\Livewire\Storefront\Checkout\Show as CheckoutShow;
use App\Livewire\Storefront\Collections\Index as CollectionsIndex;
use App\Livewire\Storefront\Collections\Show as CollectionShow;
use App\Livewire\Storefront\Home;
use App\Livewire\Storefront\Pages\Show as PageShow;
use App\Livewire\Storefront\Products\Show as ProductShow;
use App\Livewire\Storefront\Search\Index as SearchIndex;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::view('dashboard', 'dashboard')
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

require __DIR__.'/settings.php';

Route::get('/favicon.ico', fn (): \Symfony\Component\HttpFoundation\Response => response('', 204))->name('favicon');

Route::middleware('store.resolve')->group(function (): void {
    Route::livewire('/', Home::class)->name('home');
    Route::livewire('/collections', CollectionsIndex::class)->name('collections.index');
    Route::livewire('/collections/{handle}', CollectionShow::class)->name('collection.show');
    Route::livewire('/products/{handle}', ProductShow::class)->name('product.show');
    Route::livewire('/cart', CartShow::class)->name('cart.show');
    Route::livewire('/search', SearchIndex::class)->name('search');
    Route::livewire('/pages/{handle}', PageShow::class)->name('page.show');
    Route::livewire('/checkout/{checkoutId}', CheckoutShow::class)->name('checkout.show');
    Route::livewire('/checkout/{checkoutId}/confirmation', CheckoutConfirmation::class)->name('checkout.confirmation');
    Route::livewire('/account/login', CustomerLogin::class)->middleware('throttle:login')->name('account.login');
    Route::livewire('/account/register', CustomerRegister::class)->name('account.register');
    Route::livewire('/forgot-password', CustomerForgotPassword::class)->name('password.request');
    Route::livewire('/reset-password/{token}', CustomerResetPassword::class)->name('password.reset');
    Route::post('/account/logout', function (): \Illuminate\Http\RedirectResponse {
        Auth::guard('customer')->logout();
        request()->session()->invalidate();
        request()->session()->regenerateToken();

        return redirect()->route('home');
    })->name('account.logout');

    Route::middleware('auth:customer')->group(function (): void {
        Route::livewire('/account', AccountDashboard::class)->name('account.dashboard');
        Route::livewire('/account/orders', AccountOrders::class)->name('account.orders');
        Route::livewire('/account/orders/{orderNumber}', AccountOrderShow::class)->name('account.order.show');
        Route::livewire('/account/addresses', AccountAddresses::class)->name('account.addresses');
    });
});

Route::livewire('/admin/login', AdminLogin::class)->middleware('throttle:login')->name('admin.login');
Route::livewire('/admin/forgot-password', AdminForgotPassword::class)->middleware('throttle:login')->name('admin.password.request');
Route::livewire('/admin/reset-password/{token}', AdminResetPassword::class)->name('admin.password.reset');
Route::post('/admin/logout', function (): \Illuminate\Http\RedirectResponse {
    Auth::guard('web')->logout();
    request()->session()->invalidate();
    request()->session()->regenerateToken();

    return redirect()->route('admin.login');
})->name('admin.logout');

Route::get('/oauth/authorize', fn (): \Illuminate\Http\JsonResponse => response()->json(['message' => 'OAuth is not enabled in this deployment.'], 501))->middleware('auth')->name('oauth.authorize');
Route::post('/oauth/token', fn (): \Illuminate\Http\JsonResponse => response()->json(['message' => 'OAuth is not enabled in this deployment.'], 501))->name('oauth.token');

Route::prefix('admin')->middleware(['auth', 'verified', 'store.resolve', 'role.check:owner,admin,staff,support'])->group(function (): void {
    Route::livewire('/', AdminDashboard::class)->name('admin.dashboard');
    Route::livewire('/products', AdminProductsIndex::class)->name('admin.products.index');
    Route::livewire('/products/create', AdminProductForm::class)->middleware('role.check:owner,admin,staff')->name('admin.products.create');
    Route::livewire('/products/{product}/edit', AdminProductForm::class)->middleware('role.check:owner,admin,staff')->name('admin.products.edit');
    Route::livewire('/orders', AdminOrdersIndex::class)->name('admin.orders.index');
    Route::livewire('/orders/{order}', AdminOrdersShow::class)->name('admin.orders.show');
    Route::livewire('/customers', AdminCustomersIndex::class)->name('admin.customers.index');
    Route::livewire('/customers/{customer}', AdminCustomersShow::class)->name('admin.customers.show');
    Route::livewire('/discounts', AdminDiscountsIndex::class)->name('admin.discounts.index');
    Route::livewire('/discounts/create', AdminDiscountForm::class)->middleware('role.check:owner,admin,staff')->name('admin.discounts.create');
    Route::livewire('/discounts/{discount}/edit', AdminDiscountForm::class)->middleware('role.check:owner,admin,staff')->name('admin.discounts.edit');
    Route::livewire('/settings', AdminSettingsGeneral::class)->middleware('role.check:owner,admin')->name('admin.settings');
    Route::livewire('/settings/domains', \App\Livewire\Admin\Settings\Domains::class)->middleware('role.check:owner,admin')->name('admin.settings.domains');
    Route::livewire('/settings/shipping', AdminSettingsShipping::class)->middleware('role.check:owner,admin')->name('admin.settings.shipping');
    Route::livewire('/settings/taxes', AdminSettingsTaxes::class)->middleware('role.check:owner,admin')->name('admin.settings.taxes');
    Route::livewire('/inventory', AdminInventoryIndex::class)->name('admin.inventory.index');
    Route::livewire('/collections', AdminCollectionsIndex::class)->name('admin.collections.index');
    Route::livewire('/collections/create', AdminCollectionsCreate::class)->middleware('role.check:owner,admin,staff')->name('admin.collections.create');
    Route::livewire('/collections/{collection}/edit', AdminCollectionsEdit::class)->middleware('role.check:owner,admin,staff')->name('admin.collections.edit');
    Route::livewire('/themes', AdminThemesIndex::class)->name('admin.themes.index');
    Route::livewire('/themes/{theme}/editor', AdminThemesEditor::class)->name('admin.themes.editor');
    Route::livewire('/pages', AdminPagesIndex::class)->name('admin.pages.index');
    Route::livewire('/pages/create', AdminPagesCreate::class)->middleware('role.check:owner,admin')->name('admin.pages.create');
    Route::livewire('/pages/{page}/edit', AdminPagesEdit::class)->middleware('role.check:owner,admin')->name('admin.pages.edit');
    Route::livewire('/navigation', AdminNavigationIndex::class)->name('admin.navigation.index');
    Route::livewire('/apps', AdminAppsIndex::class)->name('admin.apps.index');
    Route::livewire('/apps/{installation}', AdminAppsShow::class)->name('admin.apps.show');
    Route::livewire('/developers', AdminDevelopersIndex::class)->middleware('role.check:owner,admin')->name('admin.developers.index');
    Route::livewire('/analytics', AdminAnalyticsIndex::class)->name('admin.analytics.index');
    Route::livewire('/search/settings', AdminSearchSettings::class)->name('admin.search.settings');
});
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->prependToPriorityList(
            before: \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
            prepend: App\Http\Middleware\ResolveStore::class,
        );

        $middleware->group('storefront', [
            'store.resolve:storefront',
        ]);

        $middleware->group('admin', [
            'store.resolve:admin',
        ]);

        $middleware->alias([
            'store.resolve' => App\Http\Middleware\ResolveStore::class,
            'role.check' => App\Http\Middleware\EnsureStoreRole::class,
            'api.ability' => App\Http\Middleware\EnsureApiAbility::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();
<?php

namespace App\Auth;

use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Auth\Passwords\PasswordBrokerManager;
use Illuminate\Contracts\Auth\PasswordBrokerFactory;

class StoreScopedPasswordBrokerManager extends PasswordBrokerManager implements PasswordBrokerFactory
{
    protected function createTokenRepository(array $config)
    {
        $key = $this->app['config']['app.key'];

        if (str_starts_with($key, 'base64:')) {
            $key = base64_decode(substr($key, 7));
        }

        if (($config['table'] ?? null) === 'customer_password_reset_tokens') {
            return new StoreScopedTokenRepository(
                $this->app['db']->connection($config['connection'] ?? null),
                $this->app['hash'],
                $config['table'],
                $key,
                ($config['expire'] ?? 60) * 60,
                $config['throttle'] ?? 0,
            );
        }

        return new DatabaseTokenRepository(
            $this->app['db']->connection($config['connection'] ?? null),
            $this->app['hash'],
            $config['table'],
            $key,
            ($config['expire'] ?? 60) * 60,
            $config['throttle'] ?? 0,
        );
    }
}
<?php

namespace App\Livewire\Storefront\Account\Auth;

use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ForgotPassword extends Component
{
    public string $email = '';

    public string $message = '';

    public function sendResetLink(): void
    {
        $this->validate(['email' => ['required', 'email']]);
        Password::broker('customers')->sendResetLink(['email' => $this->email]);
        $this->message = 'If an account exists for that email, a reset link has been sent.';
    }

    public function render(): mixed
    {
        return view('livewire.storefront.account.auth.forgot-password')->layout('layouts.storefront');
    }
}
<?php

namespace App\Livewire\Storefront\Account\Auth;

use App\Models\Customer;
use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ResetPassword extends Component
{
    public string $token;

    public string $email = '';

    public string $password = '';

    public string $passwordConfirmation = '';

    public function mount(string $token): void
    {
        $this->token = $token;
        $this->email = request()->string('email')->toString();
    }

    public function resetPassword(): void
    {
        $data = $this->validate(['email' => ['required', 'email'], 'password' => ['required', 'min:8', 'same:passwordConfirmation']]);
        $status = Password::broker('customers')->reset(['email' => $data['email'], 'password' => $data['password'], 'password_confirmation' => $this->passwordConfirmation, 'token' => $this->token], function (Customer $customer, string $password): void {
            $customer->password_hash = $password;
            $customer->save();
        });

        if ($status !== Password::PASSWORD_RESET) {
            $this->addError('email', __($status));

            return;
        }

        $this->redirect(route('account.login'), navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.storefront.account.auth.reset-password')->layout('layouts.storefront');
    }
}
<?php

namespace App\Livewire\Admin\Auth;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Component;

class Login extends Component
{
    public string $email = '';

    public string $password = '';

    public bool $remember = false;

    public function login(): void
    {
        $credentials = $this->validate(['email' => ['required', 'email'], 'password' => ['required', 'string']]);
        $key = 'admin-login|'.request()->ip();

        if (RateLimiter::tooManyAttempts($key, 5)) {
            $this->addError('email', 'Too many attempts. Try again later.');

            return;
        }

        RateLimiter::hit($key, 60);

        if (! Auth::guard('web')->attempt([...$credentials, 'status' => 'active'], $this->remember)) {
            $this->addError('email', 'Invalid credentials');

            return;
        }

        RateLimiter::clear($key);
        session()->regenerate();
        $store = auth()->user()->stores()->first();

        if ($store !== null) {
            session()->put('current_store_id', $store->getKey());
        }

        $this->redirect(route('admin.dashboard'), navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.login')->layout('layouts.auth');
    }
}


exec

status: completed

input:
const r = await tools.exec_command({cmd:"php artisan route:list --except-vendor | rg -n \"forgot|reset|register|login|account\"","workdir":"/Users/wesner/Herd/shop","yield_time_ms":10000,"max_output_tokens":12000});
text(r.output);


type: CommandExecution

command: php artisan route:list --except-vendor | rg -n "forgot|reset|register|login|account"

type: unknown

status: completed


text:
Script completed
Wall time 0.3 seconds
Output:

text: 2:  POST      account/logout .................................... account.logout


status: completed

input:
const r = await tools.exec_command({cmd:"sed -n '1,180p' app/Livewire/Admin/Auth/ForgotPassword.php; sed -n '1,180p' app/Livewire/Admin/Auth/ResetPassword.php; sed -n '1,180p' config/auth.php; sed -n '1,120p' database/migrations/2026_08_20_000000_create_shop_schema.php","workdir":"/Users/wesner/Herd/shop","yield_time_ms":10000,"max_output_tokens":25000});
text(r.output);


command: sed -n '1,180p' app/Livewire/Admin/Auth/ForgotPassword.php; sed -n '1,180p' app/Livewire/Admin/Auth/ResetPassword.php; sed -n '1,180p' config/auth.php; sed -n '1,120p' database/migrations/2026_08_20_000000_create_shop_schema.php

ForgotPassword.php

type: read

auth.php

cmd: sed -n '1,180p' config/auth.php

stdout:
<?php

namespace App\Livewire\Admin\Auth;

use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ForgotPassword extends Component
{
    public string $email = '';

    public string $message = '';

    public function sendResetLink(): void
    {
        $this->validate(['email' => ['required', 'email']]);
        Password::broker('users')->sendResetLink(['email' => $this->email]);
        $this->message = 'If an account exists for that email, a reset link has been sent.';
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.forgot-password')->layout('layouts.auth');
    }
}
<?php

namespace App\Livewire\Admin\Auth;

use App\Models\User;
use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ResetPassword extends Component
{
    public string $token;

    public string $email = '';

    public string $password = '';

    public string $passwordConfirmation = '';

    public function mount(string $token): void
    {
        $this->token = $token;
        $this->email = request()->string('email')->toString();
    }

    public function resetPassword(): void
    {
        $data = $this->validate(['email' => ['required', 'email'], 'password' => ['required', 'min:8', 'same:passwordConfirmation']]);
        $status = Password::broker('users')->reset(['email' => $data['email'], 'password' => $data['password'], 'password_confirmation' => $this->passwordConfirmation, 'token' => $this->token], function (User $user, string $password): void {
            $user->password = $password;
            $user->save();
        });

        if ($status !== Password::PASSWORD_RESET) {
            $this->addError('email', __($status));

            return;
        }

        $this->redirect(route('admin.login'), navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.reset-password')->layout('layouts.auth');
    }
}
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option defines the default authentication "guard" and password
    | reset "broker" for your application. You may change these values
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => env('AUTH_GUARD', 'web'),
        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | which utilizes session storage plus the Eloquent user provider.
    |
    | All authentication guards have a user provider, which defines how the
    | users are actually retrieved out of your database or other storage
    | system used by the application. Typically, Eloquent is utilized.
    |
    | Supported: "session"
    |
    */

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'customer' => [
            'driver' => 'session',
            'provider' => 'customers',
        ],
        'sanctum' => [
            'driver' => 'sanctum',
            'provider' => 'users',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication guards have a user provider, which defines how the
    | users are actually retrieved out of your database or other storage
    | system used by the application. Typically, Eloquent is utilized.
    |
    | If you have multiple user tables or models you may configure multiple
    | providers to represent the model / table. These providers may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => env('AUTH_MODEL', App\Models\User::class),
        ],
        'customers' => [
            'driver' => 'customer',
            'model' => App\Models\Customer::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | These configuration options specify the behavior of Laravel's password
    | reset functionality, including the table utilized for token storage
    | and the user provider that is invoked to actually retrieve users.
    |
    | The expiry time is the number of minutes that each reset token will be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    | The throttle setting is the number of seconds a user must wait before
    | generating more password reset tokens. This prevents the user from
    | quickly generating a very large amount of password reset tokens.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
            'expire' => 60,
            'throttle' => 60,
        ],
        'customers' => [
            'provider' => 'customers',
            'table' => 'customer_password_reset_tokens',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the number of seconds before a password confirmation
    | window expires and users are asked to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),

];
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('organizations', function (Blueprint $table): void {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->string('billing_email')->nullable()->index();
            $table->string('status')->default('active')->index();
            $table->timestamps();
        });

        Schema::create('stores', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('organization_id')->constrained()->cascadeOnDelete();
            $table->string('name');
            $table->string('handle')->unique();
            $table->string('status')->default('active')->index();
            $table->string('default_currency', 3)->default('EUR');
            $table->string('default_locale', 10)->default('en');
            $table->string('timezone')->default('UTC');
            $table->string('primary_domain')->nullable();
            $table->json('metadata')->nullable();
            $table->timestamps();
            $table->index(['organization_id', 'status']);
        });

        Schema::create('store_domains', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->string('hostname')->unique();
            $table->string('type')->default('storefront');
            $table->boolean('is_primary')->default(false);
            $table->timestamps();
            $table->index(['store_id', 'is_primary']);
        });

        Schema::table('users', function (Blueprint $table): void {
            $table->string('status')->default('active')->index();
            $table->timestamp('last_login_at')->nullable();
        });

        Schema::create('store_users', function (Blueprint $table): void {
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('role')->default('staff');
            $table->timestamps();
            $table->primary(['store_id', 'user_id']);
            $table->index('user_id');
            $table->index(['store_id', 'role']);
        });

        Schema::create('store_settings', function (Blueprint $table): void {
            $table->foreignId('store_id')->primary()->constrained()->cascadeOnDelete();
            $table->json('general_json')->nullable();
            $table->json('checkout_json')->nullable();
            $table->json('notification_json')->nullable();
            $table->json('social_json')->nullable();
            $table->timestamps();
        });

        Schema::create('products', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->string('title');
            $table->string('handle');
            $table->text('description')->nullable();
            $table->string('vendor')->nullable()->index();
            $table->string('product_type')->nullable()->index();
            $table->json('tags')->nullable();
            $table->string('status')->default('draft')->index();
            $table->timestamp('published_at')->nullable();
            $table->unsignedInteger('sales_count')->default(0);
            $table->json('metadata')->nullable();
            $table->timestamps();
            $table->unique(['store_id', 'handle']);
            $table->index(['store_id', 'status']);
            $table->index(['store_id', 'published_at']);
        });

        Schema::create('product_options', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('product_id')->constrained()->cascadeOnDelete();
            $table->string('name');
            $table->unsignedInteger('position')->default(0);
            $table->timestamps();
            $table->unique(['product_id', 'position']);
        });

        Schema::create('product_option_values', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('product_option_id')->constrained()->cascadeOnDelete();
            $table->string('value');
            $table->unsignedInteger('position')->default(0);
            $table->timestamps();
            $table->unique(['product_option_id', 'position']);
        });

        Schema::create('product_variants', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('product_id')->constrained()->cascadeOnDelete();
            $table->string('title')->default('Default');
            $table->string('sku')->nullable()->index();
            $table->string('barcode')->nullable()->index();
            $table->unsignedInteger('price_amount');
            $table->unsignedInteger('compare_at_amount')->nullable();
            $table->unsignedInteger('cost_amount')->nullable();
            $table->unsignedInteger('weight_grams')->default(0);
            $table->boolean('requires_shipping')->default(true);
            $table->boolean('is_default')->default(false);
            $table->unsignedInteger('position')->default(0);
            $table->json('metadata')->nullable();
            $table->timestamps();


text:
Script completed
Wall time 0.1 seconds
Output:

text:
<?php

namespace App\Livewire\Admin\Auth;

use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ForgotPassword extends Component
{
    public string $email = '';

    public string $message = '';

    public function sendResetLink(): void
    {
        $this->validate(['email' => ['required', 'email']]);
        Password::broker('users')->sendResetLink(['email' => $this->email]);
        $this->message = 'If an account exists for that email, a reset link has been sent.';
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.forgot-password')->layout('layouts.auth');
    }
}
<?php

namespace App\Livewire\Admin\Auth;

use App\Models\User;
use Illuminate\Support\Facades\Password;
use Livewire\Component;

class ResetPassword extends Component
{
    public string $token;

    public string $email = '';

    public string $password = '';

    public string $passwordConfirmation = '';

    public function mount(string $token): void
    {
        $this->token = $token;
        $this->email = request()->string('email')->toString();
    }

    public function resetPassword(): void
    {
        $data = $this->validate(['email' => ['required', 'email'], 'password' => ['required', 'min:8', 'same:passwordConfirmation']]);
        $status = Password::broker('users')->reset(['email' => $data['email'], 'password' => $data['password'], 'password_confirmation' => $this->passwordConfirmation, 'token' => $this->token], function (User $user, string $password): void {
            $user->password = $password;
            $user->save();
        });

        if ($status !== Password::PASSWORD_RESET) {
            $this->addError('email', __($status));

            return;
        }

        $this->redirect(route('admin.login'), navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.admin.auth.reset-password')->layout('layouts.auth');
    }
}
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option defines the default authentication "guard" and password
    | reset "broker" for your application. You may change these values
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => env('AUTH_GUARD', 'web'),
        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | which utilizes session storage plus the Eloquent user provider.
    |
    | All authentication guards have a user provider, which defines how the
    | users are actually retrieved out of your database or other storage
    | system used by the application. Typically, Eloquent is utilized.
    |
    | Supported: "session"
    |
    */

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'customer' => [
            'driver' => 'session',
            'provider' => 'customers',
        ],
        'sanctum' => [
            'driver' => 'sanctum',
            'provider' => 'users',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication guards have a user provider, which defines how the
    | users are actually retrieved out of your database or other storage
    | system used by the application. Typically, Eloquent is utilized.
    |
    | If you have multiple user tables or models you may configure multiple
    | providers to represent the model / table. These providers may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => env('AUTH_MODEL', App\Models\User::class),
        ],
        'customers' => [
            'driver' => 'customer',
            'model' => App\Models\Customer::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | These configuration options specify the behavior of Laravel's password
    | reset functionality, including the table utilized for token storage
    | and the user provider that is invoked to actually retrieve users.
    |
    | The expiry time is the number of minutes that each reset token will be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    | The throttle setting is the number of seconds a user must wait before
    | generating more password reset tokens. This prevents the user from
    | quickly generating a very large amount of password reset tokens.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
            'expire' => 60,
            'throttle' => 60,
        ],
        'customers' => [
            'provider' => 'customers',
            'table' => 'customer_password_reset_tokens',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the number of seconds before a password confirmation
    | window expires and users are asked to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),

];
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('organizations', function (Blueprint $table): void {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->string('billing_email')->nullable()->index();
            $table->string('status')->default('active')->index();
            $table->timestamps();
        });

        Schema::create('stores', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('organization_id')->constrained()->cascadeOnDelete();
            $table->string('name');
            $table->string('handle')->unique();
            $table->string('status')->default('active')->index();
            $table->string('default_currency', 3)->default('EUR');
            $table->string('default_locale', 10)->default('en');
            $table->string('timezone')->default('UTC');
            $table->string('primary_domain')->nullable();
            $table->json('metadata')->nullable();
            $table->timestamps();
            $table->index(['organization_id', 'status']);
        });

        Schema::create('store_domains', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->string('hostname')->unique();
            $table->string('type')->default('storefront');
            $table->boolean('is_primary')->default(false);
            $table->timestamps();
            $table->index(['store_id', 'is_primary']);
        });

        Schema::table('users', function (Blueprint $table): void {
            $table->string('status')->default('active')->index();
            $table->timestamp('last_login_at')->nullable();
        });

        Schema::create('store_users', function (Blueprint $table): void {
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('role')->default('staff');
            $table->timestamps();
            $table->primary(['store_id', 'user_id']);
            $table->index('user_id');
            $table->index(['store_id', 'role']);
        });

        Schema::create('store_settings', function (Blueprint $table): void {
            $table->foreignId('store_id')->primary()->constrained()->cascadeOnDelete();
            $table->json('general_json')->nullable();
            $table->json('checkout_json')->nullable();
            $table->json('notification_json')->nullable();
            $table->json('social_json')->nullable();
            $table->timestamps();
        });

        Schema::create('products', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->string('title');
            $table->string('handle');
            $table->text('description')->nullable();
            $table->string('vendor')->nullable()->index();
            $table->string('product_type')->nullable()->index();
            $table->json('tags')->nullable();
            $table->string('status')->default('draft')->index();
            $table->timestamp('published_at')->nullable();
            $table->unsignedInteger('sales_count')->default(0);
            $table->json('metadata')->nullable();
            $table->timestamps();
            $table->unique(['store_id', 'handle']);
            $table->index(['store_id', 'status']);
            $table->index(['store_id', 'published_at']);
        });

        Schema::create('product_options', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('product_id')->constrained()->cascadeOnDelete();
            $table->string('name');
            $table->unsignedInteger('position')->default(0);
            $table->timestamps();
            $table->unique(['product_id', 'position']);
        });

        Schema::create('product_option_values', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('product_option_id')->constrained()->cascadeOnDelete();
            $table->string('value');
            $table->unsignedInteger('position')->default(0);
            $table->timestamps();
            $table->unique(['product_option_id', 'position']);
        });

        Schema::create('product_variants', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('product_id')->constrained()->cascadeOnDelete();
            $table->string('title')->default('Default');
            $table->string('sku')->nullable()->index();
            $table->string('barcode')->nullable()->index();
            $table->unsignedInteger('price_amount');
            $table->unsignedInteger('compare_at_amount')->nullable();
            $table->unsignedInteger('cost_amount')->nullable();
            $table->unsignedInteger('weight_grams')->default(0);
            $table->boolean('requires_shipping')->default(true);
            $table->boolean('is_default')->default(false);
            $table->unsignedInteger('position')->default(0);
            $table->json('metadata')->nullable();
            $table->timestamps();


exec

status: completed

input:
const r = await tools.exec_command({cmd:"rg -n -A30 \"Schema::create\\('customers'|Schema::create\\('payments'|Schema::create\\('product_media'|Schema::create\\('theme_files'|Schema::create\\('cart_lines'|Schema::create\\('checkouts'|Schema::create\\('refunds'\" database/migrations/2026_08_20_000000_create_shop_schema.php; sed -n '1,100p' app/Models/Payment.php; sed -n '1,120p' app/Services/PaymentService.php; sed -n '1,120p' app/Services/MockPaymentProvider.php","workdir":"/Users/wesner/Herd/shop","yield_time_ms":10000,"max_output_tokens":30000});
text(r.output);


command: rg -n -A30 "Schema::create\('customers'|Schema::create\('payments'|Schema::create\('product_media'|Schema::create\('theme_files'|Schema::create\('cart_lines'|Schema::create\('checkouts'|Schema::create\('refunds'" database/migrations/2026_08_20_000000_create_shop_schema.php; sed -n '1,100p' app/Models/Payment.php; sed -n '1,120p' app/Services/PaymentService.php; sed -n '1,120p' app/Services/MockPaymentProvider.php

type: search

query: Schema::create\('customers'|Schema::create\('payments'|Schema::create\('product_media'|Schema::create\('theme_files'|Schema::create\('cart_lines'|Schema::create\('checkouts'|Schema::create\('refunds'

Payment.php

cmd: sed -n '1,100p' app/Models/Payment.php

MockPaymentProvider.php

stdout:
165:        Schema::create('product_media', function (Blueprint $table): void {
166-            $table->id();
167-            $table->foreignId('product_id')->constrained()->cascadeOnDelete();
168-            $table->string('path');
169-            $table->string('url')->nullable();
170-            $table->string('alt_text')->nullable();
171-            $table->string('status')->default('ready');
172-            $table->unsignedInteger('position')->default(0);
173-            $table->json('metadata')->nullable();
174-            $table->timestamps();
175-            $table->index(['product_id', 'position']);
176-            $table->index('status');
177-        });
178-
179-        Schema::create('themes', function (Blueprint $table): void {
180-            $table->id();
181-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
182-            $table->string('name');
183-            $table->string('status')->default('draft')->index();
184-            $table->string('version')->default('1.0.0');
185-            $table->json('settings')->nullable();
186-            $table->timestamps();
187-            $table->index(['store_id', 'status']);
188-        });
189-
190:        Schema::create('theme_files', function (Blueprint $table): void {
191-            $table->id();
192-            $table->foreignId('theme_id')->constrained()->cascadeOnDelete();
193-            $table->string('path');
194-            $table->longText('content')->nullable();
195-            $table->timestamps();
196-            $table->unique(['theme_id', 'path']);
197-        });
198-
199-        Schema::create('theme_settings', function (Blueprint $table): void {
200-            $table->id();
201-            $table->foreignId('theme_id')->constrained()->cascadeOnDelete();
202-            $table->string('key');
203-            $table->json('value')->nullable();
204-            $table->timestamps();
205-            $table->unique(['theme_id', 'key']);
206-        });
207-
208-        Schema::create('pages', function (Blueprint $table): void {
209-            $table->id();
210-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
211-            $table->string('title');
212-            $table->string('handle');
213-            $table->longText('content')->nullable();
214-            $table->string('status')->default('draft')->index();
215-            $table->timestamp('published_at')->nullable();
216-            $table->timestamps();
217-            $table->unique(['store_id', 'handle']);
218-        });
219-
220-        Schema::create('navigation_menus', function (Blueprint $table): void {
--
313:        Schema::create('customers', function (Blueprint $table): void {
314-            $table->id();
315-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
316-            $table->string('first_name');
317-            $table->string('last_name');
318-            $table->string('email');
319-            $table->string('password_hash')->nullable();
320-            $table->string('status')->default('active')->index();
321-            $table->timestamp('email_verified_at')->nullable();
322-            $table->timestamp('last_login_at')->nullable();
323-            $table->rememberToken();
324-            $table->json('metadata')->nullable();
325-            $table->timestamps();
326-            $table->unique(['store_id', 'email']);
327-            $table->index('store_id');
328-        });
329-
330-        Schema::create('customer_addresses', function (Blueprint $table): void {
331-            $table->id();
332-            $table->foreignId('customer_id')->constrained()->cascadeOnDelete();
333-            $table->string('label')->nullable();
334-            $table->json('address_json');
335-            $table->boolean('is_default')->default(false);
336-            $table->timestamps();
337-            $table->index(['customer_id', 'is_default']);
338-        });
339-
340-        Schema::create('carts', function (Blueprint $table): void {
341-            $table->id();
342-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
343-            $table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete();
--
352:        Schema::create('cart_lines', function (Blueprint $table): void {
353-            $table->id();
354-            $table->foreignId('cart_id')->constrained()->cascadeOnDelete();
355-            $table->foreignId('variant_id')->constrained('product_variants')->restrictOnDelete();
356-            $table->unsignedInteger('quantity');
357-            $table->unsignedInteger('unit_price_amount');
358-            $table->unsignedInteger('line_subtotal_amount');
359-            $table->unsignedInteger('line_discount_amount')->default(0);
360-            $table->unsignedInteger('line_total_amount');
361-            $table->timestamps();
362-            $table->unique(['cart_id', 'variant_id']);
363-            $table->index('cart_id');
364-        });
365-
366:        Schema::create('checkouts', function (Blueprint $table): void {
367-            $table->id();
368-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
369-            $table->foreignId('cart_id')->constrained()->restrictOnDelete();
370-            $table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete();
371-            $table->string('status')->default('started')->index();
372-            $table->string('email');
373-            $table->string('payment_method')->nullable();
374-            $table->json('shipping_address_json')->nullable();
375-            $table->json('billing_address_json')->nullable();
376-            $table->foreignId('shipping_rate_id')->nullable()->constrained('shipping_rates')->nullOnDelete();
377-            $table->string('discount_code')->nullable();
378-            $table->json('totals_json')->nullable();
379-            $table->json('tax_provider_snapshot_json')->nullable();
380-            $table->timestamp('expires_at')->nullable()->index();
381-            $table->timestamps();
382-            $table->index('cart_id');
383-            $table->index('customer_id');
384-        });
385-
386-        Schema::create('orders', function (Blueprint $table): void {
387-            $table->id();
388-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
389-            $table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete();
390-            $table->foreignId('checkout_id')->nullable()->constrained()->nullOnDelete();
391-            $table->string('order_number');
392-            $table->string('currency', 3)->default('EUR');
393-            $table->string('status')->default('pending')->index();
394-            $table->string('financial_status')->default('pending')->index();
395-            $table->string('fulfillment_status')->default('unfulfilled')->index();
396-            $table->string('payment_method')->nullable();
--
433:        Schema::create('payments', function (Blueprint $table): void {
434-            $table->id();
435-            $table->foreignId('order_id')->constrained()->cascadeOnDelete();
436-            $table->string('provider')->default('mock');
437-            $table->string('provider_payment_id')->nullable();
438-            $table->string('method');
439-            $table->string('status')->default('pending')->index();
440-            $table->unsignedInteger('amount');
441-            $table->text('raw_json_encrypted')->nullable();
442-            $table->timestamps();
443-            $table->index(['provider', 'provider_payment_id']);
444-            $table->index('order_id');
445-        });
446-
447:        Schema::create('refunds', function (Blueprint $table): void {
448-            $table->id();
449-            $table->foreignId('order_id')->constrained()->cascadeOnDelete();
450-            $table->foreignId('payment_id')->nullable()->constrained()->nullOnDelete();
451-            $table->unsignedInteger('amount');
452-            $table->string('status')->default('pending');
453-            $table->text('reason')->nullable();
454-            $table->boolean('restock')->default(false);
455-            $table->timestamps();
456-            $table->index(['order_id', 'status']);
457-        });
458-
459-        Schema::create('fulfillments', function (Blueprint $table): void {
460-            $table->id();
461-            $table->foreignId('order_id')->constrained()->cascadeOnDelete();
462-            $table->string('status')->default('pending')->index();
463-            $table->string('tracking_company')->nullable();
464-            $table->string('tracking_number')->nullable();
465-            $table->text('tracking_url')->nullable();
466-            $table->timestamp('shipped_at')->nullable();
467-            $table->timestamp('delivered_at')->nullable();
468-            $table->timestamp('fulfilled_at')->nullable();
469-            $table->timestamps();
470-            $table->index(['tracking_company', 'tracking_number']);
471-        });
472-
473-        Schema::create('fulfillment_lines', function (Blueprint $table): void {
474-            $table->id();
475-            $table->foreignId('fulfillment_id')->constrained()->cascadeOnDelete();
476-            $table->foreignId('order_line_id')->constrained()->cascadeOnDelete();
477-            $table->unsignedInteger('quantity');
<?php

namespace App\Models;

use App\Enums\PaymentMethod;
use App\Enums\PaymentStatus;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Payment extends Model
{
    protected $fillable = ['order_id', 'provider', 'provider_payment_id', 'method', 'status', 'amount', 'currency', 'raw_json_encrypted'];

    protected $hidden = ['raw_json_encrypted'];

    protected function casts(): array
    {
        return ['method' => PaymentMethod::class, 'status' => PaymentStatus::class, 'raw_json_encrypted' => 'encrypted'];
    }

    public function order(): BelongsTo
    {
        return $this->belongsTo(Order::class);
    }
}
<?php

namespace App\Services;

use App\Contracts\PaymentProvider;
use App\Enums\CheckoutStatus;
use App\Enums\PaymentMethod;
use App\Enums\PaymentStatus;
use App\Models\Checkout;
use App\Models\Discount;
use App\Models\Order;
use App\Models\Payment;
use Illuminate\Support\Facades\DB;

class PaymentService
{
    public function __construct(private readonly PaymentProvider $provider, private readonly InventoryService $inventory, private readonly OrderService $orders, private readonly PricingEngine $pricing) {}

    public function pay(Checkout $checkout, PaymentMethod $method, array $details = []): ?Order
    {
        return DB::transaction(function () use ($checkout, $method, $details): ?Order {
            $existing = Order::withoutGlobalScopes()->where('checkout_id', $checkout->getKey())->first();

            if ($existing !== null) {
                return $existing->load(['lines', 'payments']);
            }

            if ($checkout->status !== CheckoutStatus::PaymentSelected) {
                throw new \LogicException('Checkout must have a selected payment method before payment.');
            }

            $checkout->update(['payment_method' => $method]);
            $this->pricing->calculate($checkout->refresh());
            $checkout->load('cart.lines.variant.inventory');

            $result = $this->provider->charge($checkout, $method, $details);

            if ($result->status === PaymentStatus::Failed) {
                foreach ($checkout->cart->lines as $line) {
                    if ($line->variant->inventory !== null) {
                        $this->inventory->release($line->variant->inventory, $line->quantity);
                    }
                }

                $checkout->update(['status' => CheckoutStatus::ShippingSelected]);

                return null;
            }

            $order = $this->orders->createFromCheckout($checkout, $result);
            Payment::create(['order_id' => $order->getKey(), 'provider' => 'mock', 'provider_payment_id' => $result->reference, 'method' => $method, 'status' => $result->status, 'amount' => $order->total_amount, 'currency' => $order->currency, 'raw_json_encrypted' => json_encode(['reference' => $result->reference, 'message' => $result->message])]);

            if ($checkout->discount_code !== null) {
                Discount::withoutGlobalScopes()->where('store_id', $checkout->store_id)->whereRaw('lower(code) = ?', [strtolower($checkout->discount_code)])->increment('usage_count');
            }

            return $order->refresh()->load(['lines', 'payments']);
        });
    }
}
<?php

namespace App\Services;

use App\Contracts\PaymentProvider;
use App\Enums\PaymentMethod;
use App\Enums\PaymentStatus;
use App\Models\Checkout;
use App\Models\Payment;
use App\ValueObjects\PaymentResult;
use App\ValueObjects\RefundResult;
use Illuminate\Support\Str;

class MockPaymentProvider implements PaymentProvider
{
    public function charge(Checkout $checkout, PaymentMethod $method, array $details): PaymentResult
    {
        if ($method === PaymentMethod::BankTransfer) {
            return new PaymentResult(PaymentStatus::Pending, 'mock_'.Str::lower(Str::random(16)), 'Bank transfer instructions generated.');
        }

        if ($method === PaymentMethod::CreditCard) {
            $number = preg_replace('/\D+/', '', (string) ($details['card_number'] ?? ''));

            return match ($number) {
                '4000000000000002' => new PaymentResult(PaymentStatus::Failed, 'mock_'.Str::lower(Str::random(16)), 'Your card was declined.'),
                '4000000000009995' => new PaymentResult(PaymentStatus::Failed, 'mock_'.Str::lower(Str::random(16)), 'Your card has insufficient funds.'),
                default => new PaymentResult(PaymentStatus::Captured, 'mock_'.Str::lower(Str::random(16)), 'Payment captured.'),
            };
        }

        return new PaymentResult(PaymentStatus::Captured, 'mock_'.Str::lower(Str::random(16)), 'Payment captured.');
    }

    public function refund(Payment $payment, int $amount): RefundResult
    {
        return new RefundResult(true, 'mock_refund_'.Str::lower(Str::random(16)), 'Refund issued.');
    }
}


text:
Script completed
Wall time 0.1 seconds
Output:

text:
165:        Schema::create('product_media', function (Blueprint $table): void {
166-            $table->id();
167-            $table->foreignId('product_id')->constrained()->cascadeOnDelete();
168-            $table->string('path');
169-            $table->string('url')->nullable();
170-            $table->string('alt_text')->nullable();
171-            $table->string('status')->default('ready');
172-            $table->unsignedInteger('position')->default(0);
173-            $table->json('metadata')->nullable();
174-            $table->timestamps();
175-            $table->index(['product_id', 'position']);
176-            $table->index('status');
177-        });
178-
179-        Schema::create('themes', function (Blueprint $table): void {
180-            $table->id();
181-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
182-            $table->string('name');
183-            $table->string('status')->default('draft')->index();
184-            $table->string('version')->default('1.0.0');
185-            $table->json('settings')->nullable();
186-            $table->timestamps();
187-            $table->index(['store_id', 'status']);
188-        });
189-
190:        Schema::create('theme_files', function (Blueprint $table): void {
191-            $table->id();
192-            $table->foreignId('theme_id')->constrained()->cascadeOnDelete();
193-            $table->string('path');
194-            $table->longText('content')->nullable();
195-            $table->timestamps();
196-            $table->unique(['theme_id', 'path']);
197-        });
198-
199-        Schema::create('theme_settings', function (Blueprint $table): void {
200-            $table->id();
201-            $table->foreignId('theme_id')->constrained()->cascadeOnDelete();
202-            $table->string('key');
203-            $table->json('value')->nullable();
204-            $table->timestamps();
205-            $table->unique(['theme_id', 'key']);
206-        });
207-
208-        Schema::create('pages', function (Blueprint $table): void {
209-            $table->id();
210-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
211-            $table->string('title');
212-            $table->string('handle');
213-            $table->longText('content')->nullable();
214-            $table->string('status')->default('draft')->index();
215-            $table->timestamp('published_at')->nullable();
216-            $table->timestamps();
217-            $table->unique(['store_id', 'handle']);
218-        });
219-
220-        Schema::create('navigation_menus', function (Blueprint $table): void {
--
313:        Schema::create('customers', function (Blueprint $table): void {
314-            $table->id();
315-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
316-            $table->string('first_name');
317-            $table->string('last_name');
318-            $table->string('email');
319-            $table->string('password_hash')->nullable();
320-            $table->string('status')->default('active')->index();
321-            $table->timestamp('email_verified_at')->nullable();
322-            $table->timestamp('last_login_at')->nullable();
323-            $table->rememberToken();
324-            $table->json('metadata')->nullable();
325-            $table->timestamps();
326-            $table->unique(['store_id', 'email']);
327-            $table->index('store_id');
328-        });
329-
330-        Schema::create('customer_addresses', function (Blueprint $table): void {
331-            $table->id();
332-            $table->foreignId('customer_id')->constrained()->cascadeOnDelete();
333-            $table->string('label')->nullable();
334-            $table->json('address_json');
335-            $table->boolean('is_default')->default(false);
336-            $table->timestamps();
337-            $table->index(['customer_id', 'is_default']);
338-        });
339-
340-        Schema::create('carts', function (Blueprint $table): void {
341-            $table->id();
342-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
343-            $table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete();
--
352:        Schema::create('cart_lines', function (Blueprint $table): void {
353-            $table->id();
354-            $table->foreignId('cart_id')->constrained()->cascadeOnDelete();
355-            $table->foreignId('variant_id')->constrained('product_variants')->restrictOnDelete();
356-            $table->unsignedInteger('quantity');
357-            $table->unsignedInteger('unit_price_amount');
358-            $table->unsignedInteger('line_subtotal_amount');
359-            $table->unsignedInteger('line_discount_amount')->default(0);
360-            $table->unsignedInteger('line_total_amount');
361-            $table->timestamps();
362-            $table->unique(['cart_id', 'variant_id']);
363-            $table->index('cart_id');
364-        });
365-
366:        Schema::create('checkouts', function (Blueprint $table): void {
367-            $table->id();
368-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
369-            $table->foreignId('cart_id')->constrained()->restrictOnDelete();
370-            $table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete();
371-            $table->string('status')->default('started')->index();
372-            $table->string('email');
373-            $table->string('payment_method')->nullable();
374-            $table->json('shipping_address_json')->nullable();
375-            $table->json('billing_address_json')->nullable();
376-            $table->foreignId('shipping_rate_id')->nullable()->constrained('shipping_rates')->nullOnDelete();
377-            $table->string('discount_code')->nullable();
378-            $table->json('totals_json')->nullable();
379-            $table->json('tax_provider_snapshot_json')->nullable();
380-            $table->timestamp('expires_at')->nullable()->index();
381-            $table->timestamps();
382-            $table->index('cart_id');
383-            $table->index('customer_id');
384-        });
385-
386-        Schema::create('orders', function (Blueprint $table): void {
387-            $table->id();
388-            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
389-            $table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete();
390-            $table->foreignId('checkout_id')->nullable()->constrained()->nullOnDelete();
391-            $table->string('order_number');
392-            $table->string('currency', 3)->default('EUR');
393-            $table->string('status')->default('pending')->index();
394-            $table->string('financial_status')->default('pending')->index();
395-            $table->string('fulfillment_status')->default('unfulfilled')->index();
396-            $table->string('payment_method')->nullable();
--
433:        Schema::create('payments', function (Blueprint $table): void {
434-            $table->id();
435-            $table->foreignId('order_id')->constrained()->cascadeOnDelete();
436-            $table->string('provider')->default('mock');
437-            $table->string('provider_payment_id')->nullable();
438-            $table->string('method');
439-            $table->string('status')->default('pending')->index();
440-            $table->unsignedInteger('amount');
441-            $table->text('raw_json_encrypted')->nullable();
442-            $table->timestamps();
443-            $table->index(['provider', 'provider_payment_id']);
444-            $table->index('order_id');
445-        });
446-
447:        Schema::create('refunds', function (Blueprint $table): void {
448-            $table->id();
449-            $table->foreignId('order_id')->constrained()->cascadeOnDelete();
450-            $table->foreignId('payment_id')->nullable()->constrained()->nullOnDelete();
451-            $table->unsignedInteger('amount');
452-            $table->string('status')->default('pending');
453-            $table->text('reason')->nullable();
454-            $table->boolean('restock')->default(false);
455-            $table->timestamps();
456-            $table->index(['order_id', 'status']);
457-        });
458-
459-        Schema::create('fulfillments', function (Blueprint $table): void {
460-            $table->id();
461-            $table->foreignId('order_id')->constrained()->cascadeOnDelete();
462-            $table->string('status')->default('pending')->index();
463-            $table->string('tracking_company')->nullable();
464-            $table->string('tracking_number')->nullable();
465-            $table->text('tracking_url')->nullable();
466-            $table->timestamp('shipped_at')->nullable();
467-            $table->timestamp('delivered_at')->nullable();
468-            $table->timestamp('fulfilled_at')->nullable();
469-            $table->timestamps();
470-            $table->index(['tracking_company', 'tracking_number']);
471-        });
472-
473-        Schema::create('fulfillment_lines', function (Blueprint $table): void {
474-            $table->id();
475-            $table->foreignId('fulfillment_id')->constrained()->cascadeOnDelete();
476-            $table->foreignId('order_line_id')->constrained()->cascadeOnDelete();
477-            $table->unsignedInteger('quantity');
<?php

namespace App\Models;

use App\Enums\PaymentMethod;
use App\Enums\PaymentStatus;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Payment extends Model
{
    protected $fillable = ['order_id', 'provider', 'provider_payment_id', 'method', 'status', 'amount', 'currency', 'raw_json_encrypted'];

    protected $hidden = ['raw_json_encrypted'];

    protected function casts(): array
    {
        return ['method' => PaymentMethod::class, 'status' => PaymentStatus::class, 'raw_json_encrypted' => 'encrypted'];
    }

    public function order(): BelongsTo
    {
        return $this->belongsTo(Order::class);
    }
}
<?php

namespace App\Services;

use App\Contracts\PaymentProvider;
use App\Enums\CheckoutStatus;
use App\Enums\PaymentMethod;
use App\Enums\PaymentStatus;
use App\Models\Checkout;
use App\Models\Discount;
use App\Models\Order;
use App\Models\Payment;
use Illuminate\Support\Facades\DB;

class PaymentService
{
    public function __construct(private readonly PaymentProvider $provider, private readonly InventoryService $inventory, private readonly OrderService $orders, private readonly PricingEngine $pricing) {}

    public function pay(Checkout $checkout, PaymentMethod $method, array $details = []): ?Order
    {
        return DB::transaction(function () use ($checkout, $method, $details): ?Order {
            $existing = Order::withoutGlobalScopes()->where('checkout_id', $checkout->getKey())->first();

            if ($existing !== null) {
                return $existing->load(['lines', 'payments']);
            }

            if ($checkout->status !== CheckoutStatus::PaymentSelected) {
                throw new \LogicException('Checkout must have a selected payment method before payment.');
            }

            $checkout->update(['payment_method' => $method]);
            $this->pricing->calculate($checkout->refresh());
            $checkout->load('cart.lines.variant.inventory');

            $result = $this->provider->charge($checkout, $method, $details);

            if ($result->status === PaymentStatus::Failed) {
                foreach ($checkout->cart->lines as $line) {
                    if ($line->variant->inventory !== null) {
                        $this->inventory->release($line->variant->inventory, $line->quantity);
                    }
                }

                $checkout->update(['status' => CheckoutStatus::ShippingSelected]);

                return null;
            }

            $order = $this->orders->createFromCheckout($checkout, $result);
            Payment::create(['order_id' => $order->getKey(), 'provider' => 'mock', 'provider_payment_id' => $result->reference, 'method' => $method, 'status' => $result->status, 'amount' => $order->total_amount, 'currency' => $order->currency, 'raw_json_encrypted' => json_encode(['reference' => $result->reference, 'message' => $result->message])]);

            if ($checkout->discount_code !== null) {
                Discount::withoutGlobalScopes()->where('store_id', $checkout->store_id)->whereRaw('lower(code) = ?', [strtolower($checkout->discount_code)])->increment('usage_count');
            }

            return $order->refresh()->load(['lines', 'payments']);
        });
    }
}
<?php

namespace App\Services;

use App\Contracts\PaymentProvider;
use App\Enums\PaymentMethod;
use App\Enums\PaymentStatus;
use App\Models\Checkout;
use App\Models\Payment;
use App\ValueObjects\PaymentResult;
use App\ValueObjects\RefundResult;
use Illuminate\Support\Str;

class MockPaymentProvider implements PaymentProvider
{
    public function charge(Checkout $checkout, PaymentMethod $method, array $details): PaymentResult
    {
        if ($method === PaymentMethod::BankTransfer) {
            return new PaymentResult(PaymentStatus::Pending, 'mock_'.Str::lower(Str::random(16)), 'Bank transfer instructions generated.');
        }

        if ($method === PaymentMethod::CreditCard) {
            $number = preg_replace('/\D+/', '', (string) ($details['card_number'] ?? ''));

            return match ($number) {
                '4000000000000002' => new PaymentResult(PaymentStatus::Failed, 'mock_'.Str::lower(Str::random(16)), 'Your card was declined.'),
                '4000000000009995' => new PaymentResult(PaymentStatus::Failed, 'mock_'.Str::lower(Str::random(16)), 'Your card has insufficient funds.'),
                default => new PaymentResult(PaymentStatus::Captured, 'mock_'.Str::lower(Str::random(16)), 'Payment captured.'),
            };
        }

        return new PaymentResult(PaymentStatus::Captured, 'mock_'.Str::lower(Str::random(16)), 'Payment captured.');
    }

    public function refund(Payment $payment, int $amount): RefundResult
    {
        return new RefundResult(true, 'mock_refund_'.Str::lower(Str::random(16)), 'Refund issued.');
    }
}


exec

status: completed

input:
const r = await tools.exec_command({cmd:"rg -n \"raw_json_encrypted|raw_json\" app tests database | head -100; sed -n '1,140p' app/Services/PricingEngine.php; sed -n '1,160p' app/Services/TaxCalculator.php; sed -n '1,180p' app/Services/ShippingCalculator.php; sed -n '1,160p' app/Services/AnalyticsService.php; sed -n '1,180p' app/Jobs/AggregateAnalytics.php; sed -n '1,130p' app/Http/Controllers/Api/StorefrontAnalyticsController.php","workdir":"/Users/wesner/Herd/shop","yield_time_ms":10000,"max_output_tokens":30000});
text(r.output);