# Search Results

## Sanctum SPA Authentication Protecting Routes
Sanctum | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sanctum.md

### Protecting Routes

To protect routes so that all incoming requests must be authenticated, you should attach the `sanctum` authentication guard to your API routes within your `routes/api.php` file. This guard will ensure that incoming requests are authenticated as either stateful authenticated requests from your SPA or contain a valid API token header if the request is from a third party:

```php
use Illuminate\Http\Request;

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');
```

---

## Passport Token Scopes Assigning Scopes to Tokens When Issuing Personal Access Tokens
Passport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md

#### When Issuing Personal Access Tokens

If you are issuing personal access tokens using the `App\Models\User` model's `createToken` method, you may pass the array of desired scopes as the second argument to the method:

```php
$token = $user->createToken('My Token', ['orders:create'])->accessToken;
```

---

## Expectations `json()`
Expectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md

### `json()`

The `json()` modifier decodes the current expectation `$value` from JSON to an array.

```php
expect('{"name":"Nuno","credit":1000.00}')
    ->json()
    ->toHaveCount(2)
    ->name->toBe('Nuno')
    ->credit->toBeFloat();

expect('not-a-json')->json(); //Fails
```

---

## Migrating From Phpunit Guide
Migrating From Phpunit Guide | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/migrating-from-phpunit-guide.md

---
title: Migrating from PHPUnit
description: Migrating from PHPUnit to Pest is a simple process that can be completed in just a few steps.
---

# Migrating from PHPUnit

Pest is built on top of PHPUnit, so migrating from PHPUnit to Pest is a simple process that can be completed in just a few steps. Once you have Pest installed, you should require the `pestphp/pest-plugin-drift` package as a "dev" dependency in your project.

```bash
composer require pestphp/pest-plugin-drift --dev
```

Drift is a simple yet powerful plugin that will automatically convert your PHPUnit tests to Pest, simply by running the `--drift` option.

```bash
./vendor/bin/pest --drift
```

So, typically, a PHPUnit test looks like this:

```php
<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    public function test_that_true_is_true(): void
    {
        $this->assertTrue(true);
    }
}
```

Should look like this after running `--drift`:

```php
test('true is true', function () {
    expect(true)->toBeTrue();
});
```

---
## Convert Phpunit tests only for certain folder

If you want to convert Phpunit tests only for certain folder, you can pass _path_ as first argument when calling `--drift`:

Example call, if you want to run the conversion for folder `/tests/Helpers`:
```console
/vendor/bin/pest --drift tests/Helpers
```

Output:
```console
/vendor/bin/pest --drift tests/Helpers

✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔

INFO  The [tests/Helpers] directory has been migrated to PEST with XY files changed.
```

---

The output will contain a summary of the conversion process, as well as a list of the files that were converted.

While most of your tests should be converted automatically, and you should be able to run them without any issues, there are some cases where you may need to manually convert some of your tests.

---

Of course, this particular chapter is only for those who are migrating from PHPUnit. Next, let's learn how you can contribute to the growth of Pest: [Community Guide](/docs/community-guide)

---

## Passport Personal Access Tokens Managing Personal Access Tokens
Passport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md

### Managing Personal Access Tokens

Once you have created a personal access client, you may issue tokens for a given user using the `createToken` method on the `App\Models\User` model instance. The `createToken` method accepts the name of the token as its first argument and an optional array of [scopes](#token-scopes) as its second argument:

```php
use App\Models\User;
use Illuminate\Support\Facades\Date;
use Laravel\Passport\Token;

$user = User::find($userId);

// Creating a token without scopes...
$token = $user->createToken('My Token')->accessToken;

// Creating a token with scopes...
$token = $user->createToken('My Token', ['user:read', 'orders:create'])->accessToken;

// Creating a token with all scopes...
$token = $user->createToken('My Token', ['*'])->accessToken;

// Retrieving all the valid personal access tokens that belong to the user...
$tokens = $user->tokens()
    ->with('client')
    ->where('revoked', false)
    ->where('expires_at', '>', Date::now())
    ->get()
    ->filter(fn (Token $token) => $token->client->hasGrantType('personal_access'));
```

---

## Passport Personal Access Tokens
Passport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md

## Personal Access Tokens

Sometimes, your users may want to issue access tokens to themselves without going through the typical authorization code redirect flow. Allowing users to issue tokens to themselves via your application's UI can be useful for allowing users to experiment with your API or may serve as a simpler approach to issuing access tokens in general.

> [!NOTE]
> If your application is using Passport primarily to issue personal access tokens, consider using [Laravel Sanctum](/docs/{{version}}/sanctum), Laravel's light-weight first-party library for issuing API access tokens.

---

## Migrations Indexes Foreign Key Constraints
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

### Foreign Key Constraints

Laravel also provides support for creating foreign key constraints, which are used to force referential integrity at the database level. For example, let's define a `user_id` column on the `posts` table that references the `id` column on a `users` table:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('posts', function (Blueprint $table) {
    $table->unsignedBigInteger('user_id');

    $table->foreign('user_id')->references('id')->on('users');
});
```

Since this syntax is rather verbose, Laravel provides additional, terser methods that use conventions to provide a better developer experience. When using the `foreignId` method to create your column, the example above can be rewritten like so:

```php
Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained();
});
```

The `foreignId` method creates an `UNSIGNED BIGINT` equivalent column, while the `constrained` method will use conventions to determine the table and column being referenced. If your table name does not match Laravel's conventions, you may manually provide it to the `constrained` method. In addition, the name that should be assigned to the generated index may be specified as well:

```php
Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained(
        table: 'users', indexName: 'posts_user_id'
    );
});
```

You may also specify the desired action for the "on delete" and "on update" properties of the constraint:

```php
$table->foreignId('user_id')
    ->constrained()
    ->onUpdate('cascade')
    ->onDelete('cascade');
```

An alternative, expressive syntax is also provided for these actions:

| Method                        | Description                                       |
| ----------------------------- | ------------------------------------------------- |
| `$table->cascadeOnUpdate();`  | Updates should cascade.                           |
| `$table->restrictOnUpdate();` | Updates should be restricted.                     |
| `$table->nullOnUpdate();`     | Updates should set the foreign key value to null. |
| `$table->noActionOnUpdate();` | No action on updates.                             |
| `$table->cascadeOnDelete();`  | Deletes should cascade.                           |
| `$table->restrictOnDelete();` | Deletes should be restricted.                     |
| `$table->nullOnDelete();`     | Deletes should set the foreign key value to null. |
| `$table->noActionOnDelete();` | Prevents deletes if child records exist.          |

Any additional [column modifiers](#column-modifiers) must be called before the `constrained` method:

```php
$table->foreignId('user_id')
    ->nullable()
    ->constrained();
```

---

## Passport Personal Access Tokens Customizing the User Provider
Passport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md

### Customizing the User Provider

If your application uses more than one [authentication user provider](/docs/{{version}}/authentication#introduction), you may specify which user provider the personal access grant client uses by providing a `--provider` option when creating the client via the `artisan passport:client --personal` command. The given provider name should match a valid provider defined in your application's `config/auth.php` configuration file. You can then [protect your route using middleware](#multiple-authentication-guards) to ensure that only users from the guard's specified provider are authorized.

---

## Expectations `toBeJson()`
Expectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md

### `toBeJson()`

This expectation ensures that `$value` is a JSON string.

```php
expect('{"hello":"world"}')->toBeJson();
```

---

## Packages Resources Migrations
Packages | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/packages.md

### Migrations

If your package contains [database migrations](/docs/{{version}}/migrations), you may use the `publishesMigrations` method to inform Laravel that the given directory or file contains migrations. When Laravel publishes the migrations, it will automatically update the timestamp within their filename to reflect the current date and time:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->publishesMigrations([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ]);
}
```

---

## Stress Testing The Stress function Requests Upload Data Rate
Stress Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/stress-testing.md

### Requests Upload Data Rate

Returns the request upload data rate in bytes per second.

```php
$result->requests()->upload()->data()->rate();
```

---

## Http Tests Testing JSON APIs
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

## Testing JSON APIs

Laravel also provides several helpers for testing JSON APIs and their responses. For example, the `json`, `getJson`, `postJson`, `putJson`, `patchJson`, `deleteJson`, and `optionsJson` methods may be used to issue JSON requests with various HTTP verbs. You may also easily pass data and headers to these methods. To get started, let's write a test to make a `POST` request to `/api/user` and assert that the expected JSON data was returned:

```php tab=Pest
<?php

test('making an api request', function () {
    $response = $this->postJson('/api/user', ['name' => 'Sally']);

    $response
        ->assertStatus(201)
        ->assertJson([
            'created' => true,
        ]);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     */
    public function test_making_an_api_request(): void
    {
        $response = $this->postJson('/api/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertJson([
                'created' => true,
            ]);
    }
}
```

In addition, JSON response data may be accessed as array variables on the response, making it convenient for you to inspect the individual values returned within a JSON response:

```php tab=Pest
expect($response['created'])->toBeTrue();
```

```php tab=PHPUnit
$this->assertTrue($response['created']);
```

> [!NOTE]
> The `assertJson` method converts the response to an array to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.

---

## Migrations Indexes Foreign Key Constraints Dropping Foreign Keys
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

#### Dropping Foreign Keys

To drop a foreign key, you may use the `dropForeign` method, passing the name of the foreign key constraint to be deleted as an argument. Foreign key constraints use the same naming convention as indexes. In other words, the foreign key constraint name is based on the name of the table and the columns in the constraint, followed by a "\_foreign" suffix:

```php
$table->dropForeign('posts_user_id_foreign');
```

Alternatively, you may pass an array containing the column name that holds the foreign key to the `dropForeign` method. The array will be converted to a foreign key constraint name using Laravel's constraint naming conventions:

```php
$table->dropForeign(['user_id']);
```

---

## Migrations Indexes Foreign Key Constraints Toggling Foreign Key Constraints
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

#### Toggling Foreign Key Constraints

You may enable or disable foreign key constraints within your migrations by using the following methods:

```php
Schema::enableForeignKeyConstraints();

Schema::disableForeignKeyConstraints();

Schema::withoutForeignKeyConstraints(function () {
    // Constraints disabled within this closure...
});
```

> [!WARNING]
> SQLite disables foreign key constraints by default. When using SQLite, make sure to [enable foreign key support](/docs/{{version}}/database#configuration) in your database configuration before attempting to create them in your migrations.

---

## Passport Personal Access Tokens Creating a Personal Access Client
Passport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md

### Creating a Personal Access Client

Before your application can issue personal access tokens, you will need to create a personal access client. You may do this by executing the `passport:client` Artisan command with the `--personal` option. If you have already run the `passport:install` command, you do not need to run this command:

```shell
php artisan passport:client --personal
```

---

## Validation Available Validation Rules json
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### json

The field under validation must be a valid JSON string.

---

## Migrations Events
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

## Events

For convenience, each migration operation will dispatch an [event](/docs/{{version}}/events). All of the following events extend the base `Illuminate\Database\Events\MigrationEvent` class:

| Class                                            | Description                                      |
| ------------------------------------------------ | ------------------------------------------------ |
| `Illuminate\Database\Events\MigrationsStarted`   | A batch of migrations is about to be executed.   |
| `Illuminate\Database\Events\MigrationsEnded`     | A batch of migrations has finished executing.    |
| `Illuminate\Database\Events\MigrationStarted`    | A single migration is about to be executed.      |
| `Illuminate\Database\Events\MigrationEnded`      | A single migration has finished executing.       |
| `Illuminate\Database\Events\NoPendingMigrations` | A migration command found no pending migrations. |
| `Illuminate\Database\Events\SchemaDumped`        | A database schema dump has completed.            |
| `Illuminate\Database\Events\SchemaLoaded`        | An existing database schema dump has loaded.     |

_[Truncated]_


Output:
Wall time: 3.9291 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Sanctum SPA Authentication Protecting Routes\nSanctum | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sanctum.md\n\n\n### Protecting Routes\n\nTo protect routes so that all incoming requests must be authenticated, you should attach the `sanctum` authentication guard to your API routes within your `routes/api.php` file. This guard will ensure that incoming requests are authenticated as either stateful authenticated requests from your SPA or contain a valid API token header if the request is from a third party:\n\n```php\nuse Illuminate\\Http\\Request;\n\nRoute::get('/user', function (Request $request) {\n    return $request->user();\n})->middleware('auth:sanctum');\n```\n\n---\n\n## Passport Token Scopes Assigning Scopes to Tokens When Issuing Personal Access Tokens\nPassport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md\n\n\n#### When Issuing Personal Access Tokens\n\nIf you are issuing personal access tokens using the `App\\Models\\User` model's `createToken` method, you may pass the array of desired scopes as the second argument to the method:\n\n```php\n$token = $user->createToken('My Token', ['orders:create'])->accessToken;\n```\n\n---\n\n## Expectations `json()`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `json()`\n\nThe `json()` modifier decodes the current expectation `$value` from JSON to an array.\n\n```php\nexpect('{\"name\":\"Nuno\",\"credit\":1000.00}')\n    ->json()\n    ->toHaveCount(2)\n    ->name->toBe('Nuno')\n    ->credit->toBeFloat();\n\nexpect('not-a-json')->json(); //Fails\n```\n\n---\n\n## Migrating From Phpunit Guide\nMigrating From Phpunit Guide | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/migrating-from-phpunit-guide.md\n\n---\ntitle: Migrating from PHPUnit\ndescription: Migrating from PHPUnit to Pest is a simple process that can be completed in just a few steps.\n---\n\n# Migrating from PHPUnit\n\nPest is built on top of PHPUnit, so migrating from PHPUnit to Pest is a simple process that can be completed in just a few steps. Once you have Pest installed, you should require the `pestphp/pest-plugin-drift` package as a \"dev\" dependency in your project.\n\n```bash\ncomposer require pestphp/pest-plugin-drift --dev\n```\n\nDrift is a simple yet powerful plugin that will automatically convert your PHPUnit tests to Pest, simply by running the `--drift` option.\n\n```bash\n./vendor/bin/pest --drift\n```\n\nSo, typically, a PHPUnit test looks like this:\n\n```php\n<?php\n\nnamespace Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    public function test_that_true_is_true(): void\n    {\n        $this->assertTrue(true);\n    }\n}\n```\n\nShould look like this after running `--drift`:\n\n```php\ntest('true is true', function () {\n    expect(true)->toBeTrue();\n});\n```\n\n---\n## Convert Phpunit tests only for certain folder\n\nIf you want to convert Phpunit tests only for certain folder, you can pass _path_ as first argument when calling `--drift`:\n\nExample call, if you want to run the conversion for folder `/tests/Helpers`: \n```console \n/vendor/bin/pest --drift tests/Helpers\n```\n\nOutput:\n```console\n/vendor/bin/pest --drift tests/Helpers\n\n✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔\n\nINFO  The [tests/Helpers] directory has been migrated to PEST with XY files changed.\n```\n\n---\n\nThe output will contain a summary of the conversion process, as well as a list of the files that were converted.\n\nWhile most of your tests should be converted automatically, and you should be able to run them without any issues, there are some cases where you may need to manually convert some of your tests.\n\n---\n\nOf course, this particular chapter is only for those who are migrating from PHPUnit. Next, let's learn how you can contribute to the growth of Pest: [Community Guide](/docs/community-guide)\n\n\n---\n\n## Passport Personal Access Tokens Managing Personal Access Tokens\nPassport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md\n\n\n### Managing Personal Access Tokens\n\nOnce you have created a personal access client, you may issue tokens for a given user using the `createToken` method on the `App\\Models\\User` model instance. The `createToken` method accepts the name of the token as its first argument and an optional array of [scopes](#token-scopes) as its second argument:\n\n```php\nuse App\\Models\\User;\nuse Illuminate\\Support\\Facades\\Date;\nuse Laravel\\Passport\\Token;\n\n$user = User::find($userId);\n\n// Creating a token without scopes...\n$token = $user->createToken('My Token')->accessToken;\n\n// Creating a token with scopes...\n$token = $user->createToken('My Token', ['user:read', 'orders:create'])->accessToken;\n\n// Creating a token with all scopes...\n$token = $user->createToken('My Token', ['*'])->accessToken;\n\n// Retrieving all the valid personal access tokens that belong to the user...\n$tokens = $user->tokens()\n    ->with('client')\n    ->where('revoked', false)\n    ->where('expires_at', '>', Date::now())\n    ->get()\n    ->filter(fn (Token $token) => $token->client->hasGrantType('personal_access'));\n```\n\n---\n\n## Passport Personal Access Tokens\nPassport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md\n\n\n## Personal Access Tokens\n\nSometimes, your users may want to issue access tokens to themselves without going through the typical authorization code redirect flow. Allowing users to issue tokens to themselves via your application's UI can be useful for allowing users to experiment with your API or may serve as a simpler approach to issuing access tokens in general.\n\n> [!NOTE]\n> If your application is using Passport primarily to issue personal access tokens, consider using [Laravel Sanctum](/docs/{{version}}/sanctum), Laravel's light-weight first-party library for issuing API access tokens.\n\n---\n\n## Migrations Indexes Foreign Key Constraints\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n### Foreign Key Constraints\n\nLaravel also provides support for creating foreign key constraints, which are used to force referential integrity at the database level. For example, let's define a `user_id` column on the `posts` table that references the `id` column on a `users` table:\n\n```php\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nSchema::table('posts', function (Blueprint $table) {\n    $table->unsignedBigInteger('user_id');\n\n    $table->foreign('user_id')->references('id')->on('users');\n});\n```\n\nSince this syntax is rather verbose, Laravel provides additional, terser methods that use conventions to provide a better developer experience. When using the `foreignId` method to create your column, the example above can be rewritten like so:\n\n```php\nSchema::table('posts', function (Blueprint $table) {\n    $table->foreignId('user_id')->constrained();\n});\n```\n\nThe `foreignId` method creates an `UNSIGNED BIGINT` equivalent column, while the `constrained` method will use conventions to determine the table and column being referenced. If your table name does not match Laravel's conventions, you may manually provide it to the `constrained` method. In addition, the name that should be assigned to the generated index may be specified as well:\n\n```php\nSchema::table('posts', function (Blueprint $table) {\n    $table->foreignId('user_id')->constrained(\n        table: 'users', indexName: 'posts_user_id'\n    );\n});\n```\n\nYou may also specify the desired action for the \"on delete\" and \"on update\" properties of the constraint:\n\n```php\n$table->foreignId('user_id')\n    ->constrained()\n    ->onUpdate('cascade')\n    ->onDelete('cascade');\n```\n\nAn alternative, expressive syntax is also provided for these actions:\n\n
\n\n| Method                        | Description                                       |\n| ----------------------------- | ------------------------------------------------- |\n| `$table->cascadeOnUpdate();`  | Updates should cascade.                           |\n| `$table->restrictOnUpdate();` | Updates should be restricted.                     |\n| `$table->nullOnUpdate();`     | Updates should set the foreign key value to null. |\n| `$table->noActionOnUpdate();` | No action on updates.                             |\n| `$table->cascadeOnDelete();`  | Deletes should cascade.                           |\n| `$table->restrictOnDelete();` | Deletes should be restricted.                     |\n| `$table->nullOnDelete();`     | Deletes should set the foreign key value to null. |\n| `$table->noActionOnDelete();` | Prevents deletes if child records exist.          |\n\n
\n\nAny additional [column modifiers](#column-modifiers) must be called before the `constrained` method:\n\n```php\n$table->foreignId('user_id')\n    ->nullable()\n    ->constrained();\n```\n\n---\n\n## Passport Personal Access Tokens Customizing the User Provider\nPassport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md\n\n\n### Customizing the User Provider\n\nIf your application uses more than one [authentication user provider](/docs/{{version}}/authentication#introduction), you may specify which user provider the personal access grant client uses by providing a `--provider` option when creating the client via the `artisan passport:client --personal` command. The given provider name should match a valid provider defined in your application's `config/auth.php` configuration file. You can then [protect your route using middleware](#multiple-authentication-guards) to ensure that only users from the guard's specified provider are authorized.\n\n---\n\n## Expectations `toBeJson()`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `toBeJson()`\n\nThis expectation ensures that `$value` is a JSON string.\n\n```php\nexpect('{\"hello\":\"world\"}')->toBeJson();\n```\n\n---\n\n## Packages Resources Migrations\nPackages | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/packages.md\n\n\n### Migrations\n\nIf your package contains [database migrations](/docs/{{version}}/migrations), you may use the `publishesMigrations` method to inform Laravel that the given directory or file contains migrations. When Laravel publishes the migrations, it will automatically update the timestamp within their filename to reflect the current date and time:\n\n```php\n/**\n * Bootstrap any package services.\n */\npublic function boot(): void\n{\n    $this->publishesMigrations([\n        __DIR__.'/../database/migrations' => database_path('migrations'),\n    ]);\n}\n```\n\n---\n\n## Stress Testing The Stress function Requests Upload Data Rate\nStress Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/stress-testing.md\n\n\n### Requests Upload Data Rate\n\nReturns the request upload data rate in bytes per second.\n\n```php\n$result->requests()->upload()->data()->rate();\n```\n\n---\n\n## Http Tests Testing JSON APIs\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n## Testing JSON APIs\n\nLaravel also provides several helpers for testing JSON APIs and their responses. For example, the `json`, `getJson`, `postJson`, `putJson`, `patchJson`, `deleteJson`, and `optionsJson` methods may be used to issue JSON requests with various HTTP verbs. You may also easily pass data and headers to these methods. To get started, let's write a test to make a `POST` request to `/api/user` and assert that the expected JSON data was returned:\n\n```php tab=Pest\n<?php\n\ntest('making an api request', function () {\n    $response = $this->postJson('/api/user', ['name' => 'Sally']);\n\n    $response\n        ->assertStatus(201)\n        ->assertJson([\n            'created' => true,\n        ]);\n});\n```\n\n```php tab=PHPUnit\n<?php\n\nnamespace Tests\\Feature;\n\nuse Tests\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    /**\n     * A basic functional test example.\n     */\n    public function test_making_an_api_request(): void\n    {\n        $response = $this->postJson('/api/user', ['name' => 'Sally']);\n\n        $response\n            ->assertStatus(201)\n            ->assertJson([\n                'created' => true,\n            ]);\n    }\n}\n```\n\nIn addition, JSON response data may be accessed as array variables on the response, making it convenient for you to inspect the individual values returned within a JSON response:\n\n```php tab=Pest\nexpect($response['created'])->toBeTrue();\n```\n\n```php tab=PHPUnit\n$this->assertTrue($response['created']);\n```\n\n> [!NOTE]\n> The `assertJson` method converts the response to an array to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.\n\n---\n\n## Migrations Indexes Foreign Key Constraints Dropping Foreign Keys\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### Dropping Foreign Keys\n\nTo drop a foreign key, you may use the `dropForeign` method, passing the name of the foreign key constraint to be deleted as an argument. Foreign key constraints use the same naming convention as indexes. In other words, the foreign key constraint name is based on the name of the table and the columns in the constraint, followed by a \"\\_foreign\" suffix:\n\n```php\n$table->dropForeign('posts_user_id_foreign');\n```\n\nAlternatively, you may pass an array containing the column name that holds the foreign key to the `dropForeign` method. The array will be converted to a foreign key constraint name using Laravel's constraint naming conventions:\n\n```php\n$table->dropForeign(['user_id']);\n```\n\n---\n\n## Migrations Indexes Foreign Key Constraints Toggling Foreign Key Constraints\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n#### Toggling Foreign Key Constraints\n\nYou may enable or disable foreign key constraints within your migrations by using the following methods:\n\n```php\nSchema::enableForeignKeyConstraints();\n\nSchema::disableForeignKeyConstraints();\n\nSchema::withoutForeignKeyConstraints(function () {\n    // Constraints disabled within this closure...\n});\n```\n\n> [!WARNING]\n> SQLite disables foreign key constraints by default. When using SQLite, make sure to [enable foreign key support](/docs/{{version}}/database#configuration) in your database configuration before attempting to create them in your migrations.\n\n---\n\n## Passport Personal Access Tokens Creating a Personal Access Client\nPassport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md\n\n\n### Creating a Personal Access Client\n\nBefore your application can issue personal access tokens, you will need to create a personal access client. You may do this by executing the `passport:client` Artisan command with the `--personal` option. If you have already run the `passport:install` command, you do not need to run this command:\n\n```shell\nphp artisan passport:client --personal\n```\n\n---\n\n## Validation Available Validation Rules json\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### json\n\nThe field under validation must be a valid JSON string.\n\n---\n\n## Migrations Events\nMigrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md\n\n\n## Events\n\nFor convenience, each migration operation will dispatch an [event](/docs/{{version}}/events). All of the following events extend the base `Illuminate\\Database\\Events\\MigrationEvent` class:\n\n
\n\n| Class                                            | Description                                      |\n| ------------------------------------------------ | ------------------------------------------------ |\n| `Illuminate\\Database\\Events\\MigrationsStarted`   | A batch of migrations is about to be executed.   |\n| `Illuminate\\Database\\Events\\MigrationsEnded`     | A batch of migrations has finished executing.    |\n| `Illuminate\\Database\\Events\\MigrationStarted`    | A single migration is about to be executed.      |\n| `Illuminate\\Database\\Events\\MigrationEnded`      | A single migration has finished executing.       |\n| `Illuminate\\Database\\Events\\NoPendingMigrations` | A migration command found no pending migrations. |\n| `Illuminate\\Database\\Events\\SchemaDumped`        | A database schema dump has completed.            |\n| `Illuminate\\Database\\Events\\SchemaLoaded`        | An existing database schema dump has loaded.     |\n\n
\n\n_[Truncated]_"}]


sed -n '1,220p' database/migrations/2026_05_04_180719_create_personal_access_tokens_table.php


sed -n '1,220p' app/Models/PersonalAccessToken.php


sed -n '1,260p' app/Http/Controllers/Api/Storefront/V1/CheckoutController.php


sed -n '1,180p' tests/Feature/Api/StorefrontCheckoutApiTest.php


sed -n '1,220p' database/migrations/2026_05_04_180719_create_personal_access_tokens_table.php

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('personal_access_tokens', function (Blueprint $table) {
            $table->id();
            $table->morphs('tokenable');
            $table->string('name');
            $table->string('token', 64)->unique();
            $table->text('abilities')->nullable();
            $table->timestamp('last_used_at')->nullable();
            $table->timestamp('expires_at')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('personal_access_tokens');
    }
};


sed -n '1,220p' app/Models/PersonalAccessToken.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class PersonalAccessToken extends Model
{
    /**
     * @var list<string>
     */
    protected $fillable = [
        'tokenable_type',
        'tokenable_id',
        'name',
        'token',
        'abilities',
        'last_used_at',
        'expires_at',
    ];

    /**
     * @return MorphTo<Model, $this>
     */
    public function tokenable(): MorphTo
    {
        return $this->morphTo();
    }

    public function can(string $ability): bool
    {
        $abilities = $this->abilities ?? [];

        return in_array('*', $abilities, true) || in_array($ability, $abilities, true);
    }

    public function isExpired(): bool
    {
        return $this->expires_at !== null && $this->expires_at->isPast();
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'abilities' => 'array',
            'last_used_at' => 'datetime',
            'expires_at' => 'datetime',
        ];
    }
}


sed -n '1,260p' app/Http/Controllers/Api/Storefront/V1/CheckoutController.php

<?php

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

use App\Exceptions\InsufficientInventoryException;
use App\Exceptions\InvalidCheckoutTransitionException;
use App\Exceptions\InvalidDiscountException;
use App\Exceptions\PaymentFailedException;
use App\Exceptions\UnserviceableShippingAddressException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Storefront\V1\ApplyCheckoutDiscountRequest;
use App\Http\Requests\Api\Storefront\V1\CompleteCheckoutPaymentRequest;
use App\Http\Requests\Api\Storefront\V1\SelectCheckoutPaymentRequest;
use App\Http\Requests\Api\Storefront\V1\SetCheckoutAddressRequest;
use App\Http\Requests\Api\Storefront\V1\SetCheckoutShippingRequest;
use App\Http\Requests\Api\Storefront\V1\StoreCheckoutRequest;
use App\Http\Resources\Storefront\V1\CheckoutResource;
use App\Http\Resources\Storefront\V1\OrderResource;
use App\Models\Cart;
use App\Models\Checkout;
use App\Models\Order;
use App\Models\ShippingRate;
use App\Services\CheckoutService;
use App\Services\PricingEngine;
use App\Services\ShippingCalculator;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;

class CheckoutController extends Controller
{
    public function store(StoreCheckoutRequest $request, CheckoutService $checkouts, PricingEngine $pricing): CheckoutResource|JsonResponse
    {
        $cart = Cart::query()->findOrFail($request->validated('cart_id'));

        try {
            $checkout = $checkouts->createFromCart($cart);
            $checkout->forceFill([
                'email' => (string) $request->validated('email'),
            ])->save();
            $pricing->calculate($checkout);

            return CheckoutResource::make($this->loadCheckout(
                $checkout->refresh(),
            ))->response()->setStatusCode(201);
        } catch (InvalidCheckoutTransitionException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function show(Checkout $checkout): CheckoutResource
    {
        return CheckoutResource::make($this->loadCheckout($checkout));
    }

    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, $addressData)));
        } catch (InvalidCheckoutTransitionException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function shippingMethod(SetCheckoutShippingRequest $request, Checkout $checkout, CheckoutService $checkouts): CheckoutResource|JsonResponse
    {
        try {
            return CheckoutResource::make($this->loadCheckout($checkouts->setShippingMethod(
                $checkout,
                $request->validated('shipping_rate_id'),
            )));
        } catch (InvalidCheckoutTransitionException|UnserviceableShippingAddressException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function applyDiscount(ApplyCheckoutDiscountRequest $request, Checkout $checkout, PricingEngine $pricing): CheckoutResource|JsonResponse
    {
        $checkout->forceFill([
            'discount_code' => trim((string) $request->validated('code')) ?: null,
        ])->save();

        try {
            $pricing->calculate($checkout);

            return CheckoutResource::make($this->loadCheckout($checkout->refresh()));
        } catch (InvalidDiscountException $exception) {
            $checkout->forceFill(['discount_code' => null])->save();
            $pricing->calculate($checkout);

            return response()->json([
                'message' => $exception->getMessage(),
                'reason' => $exception->reasonCode,
            ], 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
    {
        try {
            return CheckoutResource::make($this->loadCheckout($checkouts->selectPaymentMethod(
                $checkout,
                (string) $request->validated('payment_method'),
            )));
        } catch (InsufficientInventoryException|InvalidCheckoutTransitionException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function pay(CompleteCheckoutPaymentRequest $request, Checkout $checkout, CheckoutService $checkouts): OrderResource|JsonResponse
    {
        try {
            if ($checkout->payment_method !== $request->validated('payment_method')) {
                $checkout = $checkouts->selectPaymentMethod($checkout, (string) $request->validated('payment_method'));
            }

            $order = $checkouts->completeCheckout($checkout, [
                'card_number' => $request->validated('card_number'),
                'cardholder_name' => $request->validated('card_holder'),
                'expiry' => $request->validated('card_expiry'),
                'cvc' => $request->validated('card_cvc'),
            ]);

            return OrderResource::make($this->loadOrder($order))
                ->response()
                ->setStatusCode(200);
        } catch (PaymentFailedException $exception) {
            return response()->json(['message' => $exception->getMessage()], 402);
        } catch (InsufficientInventoryException|InvalidCheckoutTransitionException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    private function loadCheckout(Checkout $checkout): Checkout
    {
        $checkout = $checkout->load([
            'cart.lines.variant.product',
            'cart.lines.variant.optionValues.option',
            'store',
        ]);

        $checkout->setRelation('availableRates', $this->availableRates($checkout));

        return $checkout;
    }

    private function loadOrder(Order $order): Order
    {
        return $order->load(['lines', 'payments', 'fulfillments.lines']);
    }

    /**
     * @return Collection<int, ShippingRate>
     */
    private function availableRates(Checkout $checkout): Collection
    {
        if (! is_array($checkout->shipping_address_json) || $checkout->shipping_address_json === []) {
            return collect();
        }

        return app(ShippingCalculator::class)
            ->getAvailableRates($checkout->store, $checkout->shipping_address_json)
            ->map(function (ShippingRate $rate) use ($checkout): ShippingRate {
                $rate->setAttribute('calculated_amount', app(ShippingCalculator::class)->calculate($rate, $checkout->cart));

                return $rate;
            });
    }
}


sed -n '1,180p' tests/Feature/Api/StorefrontCheckoutApiTest.php

<?php

use App\Enums\CheckoutStatus;
use App\Models\Checkout;
use App\Models\InventoryItem;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\ShippingRate;
use App\Models\Store;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function storefrontApiCheckoutStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function storefrontApiCheckoutVariant(Store $store): ProductVariant
{
    $product = Product::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('handle', 'classic-cotton-t-shirt')
        ->firstOrFail();

    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->oldest('position')
        ->firstOrFail();

    InventoryItem::withoutGlobalScopes()
        ->where('variant_id', $variant->getKey())
        ->update([
            'quantity_on_hand' => 20,
            'quantity_reserved' => 0,
        ]);

    return $variant->refresh();
}

/**
 * @return array<string, string>
 */
function storefrontApiCheckoutAddress(string $country = 'DE'): array
{
    return [
        'first_name' => 'Test',
        'last_name' => 'Buyer',
        'address1' => 'Main Street 1',
        'city' => 'Berlin',
        'province_code' => 'BE',
        'country' => $country,
        'country_code' => $country,
        'postal_code' => '10115',
    ];
}

test('storefront checkout api progresses through address shipping discount removal and payment selection', function (): void {
    $store = storefrontApiCheckoutStore();
    $variant = storefrontApiCheckoutVariant($store);
    $api = fn () => $this->withServerVariables(['REMOTE_ADDR' => '10.0.0.21'])->withHeader('Host', 'shop.test');

    $cartId = $api()
        ->postJson('/api/storefront/v1/carts')
        ->assertCreated()['data']['id'];

    $api()
        ->postJson("/api/storefront/v1/carts/{$cartId}/lines", [
            'variant_id' => $variant->getKey(),
            'quantity' => 2,
        ])
        ->assertCreated();

    $checkoutResponse = $api()
        ->postJson('/api/storefront/v1/checkouts', [
            'cart_id' => $cartId,
            'email' => 'buyer@example.test',
        ]);

    $checkoutResponse
        ->assertCreated()
        ->assertJsonPath('data.status', 'started')
        ->assertJsonPath('data.email', 'buyer@example.test')
        ->assertJsonPath('data.totals.subtotal', 4998)
        ->assertJsonPath('data.cart.line_count', 2);

    $checkoutId = $checkoutResponse['data']['id'];

    $addressResponse = $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/address", [
            'shipping_address' => storefrontApiCheckoutAddress(),
        ]);

    $addressResponse
        ->assertOk()
        ->assertJsonPath('data.status', 'addressed')
        ->assertJsonPath('data.shipping_address.country', 'DE')
        ->assertJsonCount(3, 'data.available_shipping_rates');

    $shippingRateId = $addressResponse['data']['available_shipping_rates'][0]['id'];

    $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/shipping-method", [
            'shipping_rate_id' => $shippingRateId,
        ])
        ->assertOk()
        ->assertJsonPath('data.status', 'shipping_selected')
        ->assertJsonPath('data.shipping_method_id', $shippingRateId)
        ->assertJsonPath('data.totals.shipping', 499);

    $api()
        ->postJson("/api/storefront/v1/checkouts/{$checkoutId}/apply-discount", ['code' => 'SAVE10'])
        ->assertOk()
        ->assertJsonPath('data.discount_code', 'SAVE10')
        ->assertJsonPath('data.totals.discount', 500);

    $api()
        ->deleteJson("/api/storefront/v1/checkouts/{$checkoutId}/discount")
        ->assertOk()
        ->assertJsonPath('data.discount_code', null)
        ->assertJsonPath('data.totals.discount', 0);

    $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/payment-method", [
            'payment_method' => 'credit_card',
        ])
        ->assertOk()
        ->assertJsonPath('data.status', 'payment_selected')
        ->assertJsonPath('data.payment_method', 'credit_card');

    $checkout = Checkout::withoutGlobalScopes()->findOrFail($checkoutId);
    $inventory = InventoryItem::withoutGlobalScopes()->where('variant_id', $variant->getKey())->firstOrFail();

    expect($checkout->status)->toBe(CheckoutStatus::PaymentSelected)
        ->and($inventory->quantity_reserved)->toBe(2);
});

test('storefront checkout api rejects invalid addresses shipping methods and discounts', function (): void {
    $store = storefrontApiCheckoutStore();
    $variant = storefrontApiCheckoutVariant($store);
    $api = fn () => $this->withServerVariables(['REMOTE_ADDR' => '10.0.0.22'])->withHeader('Host', 'shop.test');

    $cartId = $api()
        ->postJson('/api/storefront/v1/carts')
        ->assertCreated()['data']['id'];

    $api()
        ->postJson("/api/storefront/v1/carts/{$cartId}/lines", [
            'variant_id' => $variant->getKey(),
            'quantity' => 1,
        ])
        ->assertCreated();

    $checkoutId = $api()
        ->postJson('/api/storefront/v1/checkouts', [
            'cart_id' => $cartId,
            'email' => 'buyer@example.test',
        ])
        ->assertCreated()['data']['id'];

    $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/address", [
            'shipping_address' => array_diff_key(storefrontApiCheckoutAddress(), ['first_name' => true]),
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors('shipping_address.first_name');

    $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/address", [
            'shipping_address' => storefrontApiCheckoutAddress(),
        ])
        ->assertOk();

    $otherStoreRate = ShippingRate::withoutGlobalScopes()


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

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('personal_access_tokens', function (Blueprint $table) {
            $table->id();
            $table->morphs('tokenable');
            $table->string('name');
            $table->string('token', 64)->unique();
            $table->text('abilities')->nullable();
            $table->timestamp('last_used_at')->nullable();
            $table->timestamp('expires_at')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('personal_access_tokens');
    }
};


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

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class PersonalAccessToken extends Model
{
    /**
     * @var list<string>
     */
    protected $fillable = [
        'tokenable_type',
        'tokenable_id',
        'name',
        'token',
        'abilities',
        'last_used_at',
        'expires_at',
    ];

    /**
     * @return MorphTo<Model, $this>
     */
    public function tokenable(): MorphTo
    {
        return $this->morphTo();
    }

    public function can(string $ability): bool
    {
        $abilities = $this->abilities ?? [];

        return in_array('*', $abilities, true) || in_array($ability, $abilities, true);
    }

    public function isExpired(): bool
    {
        return $this->expires_at !== null && $this->expires_at->isPast();
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'abilities' => 'array',
            'last_used_at' => 'datetime',
            'expires_at' => 'datetime',
        ];
    }
}


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

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

use App\Exceptions\InsufficientInventoryException;
use App\Exceptions\InvalidCheckoutTransitionException;
use App\Exceptions\InvalidDiscountException;
use App\Exceptions\PaymentFailedException;
use App\Exceptions\UnserviceableShippingAddressException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Storefront\V1\ApplyCheckoutDiscountRequest;
use App\Http\Requests\Api\Storefront\V1\CompleteCheckoutPaymentRequest;
use App\Http\Requests\Api\Storefront\V1\SelectCheckoutPaymentRequest;
use App\Http\Requests\Api\Storefront\V1\SetCheckoutAddressRequest;
use App\Http\Requests\Api\Storefront\V1\SetCheckoutShippingRequest;
use App\Http\Requests\Api\Storefront\V1\StoreCheckoutRequest;
use App\Http\Resources\Storefront\V1\CheckoutResource;
use App\Http\Resources\Storefront\V1\OrderResource;
use App\Models\Cart;
use App\Models\Checkout;
use App\Models\Order;
use App\Models\ShippingRate;
use App\Services\CheckoutService;
use App\Services\PricingEngine;
use App\Services\ShippingCalculator;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;

class CheckoutController extends Controller
{
    public function store(StoreCheckoutRequest $request, CheckoutService $checkouts, PricingEngine $pricing): CheckoutResource|JsonResponse
    {
        $cart = Cart::query()->findOrFail($request->validated('cart_id'));

        try {
            $checkout = $checkouts->createFromCart($cart);
            $checkout->forceFill([
                'email' => (string) $request->validated('email'),
            ])->save();
            $pricing->calculate($checkout);

            return CheckoutResource::make($this->loadCheckout(
                $checkout->refresh(),
            ))->response()->setStatusCode(201);
        } catch (InvalidCheckoutTransitionException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function show(Checkout $checkout): CheckoutResource
    {
        return CheckoutResource::make($this->loadCheckout($checkout));
    }

    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, $addressData)));
        } catch (InvalidCheckoutTransitionException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function shippingMethod(SetCheckoutShippingRequest $request, Checkout $checkout, CheckoutService $checkouts): CheckoutResource|JsonResponse
    {
        try {
            return CheckoutResource::make($this->loadCheckout($checkouts->setShippingMethod(
                $checkout,
                $request->validated('shipping_rate_id'),
            )));
        } catch (InvalidCheckoutTransitionException|UnserviceableShippingAddressException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function applyDiscount(ApplyCheckoutDiscountRequest $request, Checkout $checkout, PricingEngine $pricing): CheckoutResource|JsonResponse
    {
        $checkout->forceFill([
            'discount_code' => trim((string) $request->validated('code')) ?: null,
        ])->save();

        try {
            $pricing->calculate($checkout);

            return CheckoutResource::make($this->loadCheckout($checkout->refresh()));
        } catch (InvalidDiscountException $exception) {
            $checkout->forceFill(['discount_code' => null])->save();
            $pricing->calculate($checkout);

            return response()->json([
                'message' => $exception->getMessage(),
                'reason' => $exception->reasonCode,
            ], 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
    {
        try {
            return CheckoutResource::make($this->loadCheckout($checkouts->selectPaymentMethod(
                $checkout,
                (string) $request->validated('payment_method'),
            )));
        } catch (InsufficientInventoryException|InvalidCheckoutTransitionException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function pay(CompleteCheckoutPaymentRequest $request, Checkout $checkout, CheckoutService $checkouts): OrderResource|JsonResponse
    {
        try {
            if ($checkout->payment_method !== $request->validated('payment_method')) {
                $checkout = $checkouts->selectPaymentMethod($checkout, (string) $request->validated('payment_method'));
            }

            $order = $checkouts->completeCheckout($checkout, [
                'card_number' => $request->validated('card_number'),
                'cardholder_name' => $request->validated('card_holder'),
                'expiry' => $request->validated('card_expiry'),
                'cvc' => $request->validated('card_cvc'),
            ]);

            return OrderResource::make($this->loadOrder($order))
                ->response()
                ->setStatusCode(200);
        } catch (PaymentFailedException $exception) {
            return response()->json(['message' => $exception->getMessage()], 402);
        } catch (InsufficientInventoryException|InvalidCheckoutTransitionException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    private function loadCheckout(Checkout $checkout): Checkout
    {
        $checkout = $checkout->load([
            'cart.lines.variant.product',
            'cart.lines.variant.optionValues.option',
            'store',
        ]);

        $checkout->setRelation('availableRates', $this->availableRates($checkout));

        return $checkout;
    }

    private function loadOrder(Order $order): Order
    {
        return $order->load(['lines', 'payments', 'fulfillments.lines']);
    }

    /**
     * @return Collection<int, ShippingRate>
     */
    private function availableRates(Checkout $checkout): Collection
    {
        if (! is_array($checkout->shipping_address_json) || $checkout->shipping_address_json === []) {
            return collect();
        }

        return app(ShippingCalculator::class)
            ->getAvailableRates($checkout->store, $checkout->shipping_address_json)
            ->map(function (ShippingRate $rate) use ($checkout): ShippingRate {
                $rate->setAttribute('calculated_amount', app(ShippingCalculator::class)->calculate($rate, $checkout->cart));

                return $rate;
            });
    }
}


Output:
Chunk ID: 50a643
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1505
Output:
<?php

use App\Enums\CheckoutStatus;
use App\Models\Checkout;
use App\Models\InventoryItem;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\ShippingRate;
use App\Models\Store;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function storefrontApiCheckoutStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function storefrontApiCheckoutVariant(Store $store): ProductVariant
{
    $product = Product::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('handle', 'classic-cotton-t-shirt')
        ->firstOrFail();

    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->oldest('position')
        ->firstOrFail();

    InventoryItem::withoutGlobalScopes()
        ->where('variant_id', $variant->getKey())
        ->update([
            'quantity_on_hand' => 20,
            'quantity_reserved' => 0,
        ]);

    return $variant->refresh();
}

/**
 * @return array<string, string>
 */
function storefrontApiCheckoutAddress(string $country = 'DE'): array
{
    return [
        'first_name' => 'Test',
        'last_name' => 'Buyer',
        'address1' => 'Main Street 1',
        'city' => 'Berlin',
        'province_code' => 'BE',
        'country' => $country,
        'country_code' => $country,
        'postal_code' => '10115',
    ];
}

test('storefront checkout api progresses through address shipping discount removal and payment selection', function (): void {
    $store = storefrontApiCheckoutStore();
    $variant = storefrontApiCheckoutVariant($store);
    $api = fn () => $this->withServerVariables(['REMOTE_ADDR' => '10.0.0.21'])->withHeader('Host', 'shop.test');

    $cartId = $api()
        ->postJson('/api/storefront/v1/carts')
        ->assertCreated()['data']['id'];

    $api()
        ->postJson("/api/storefront/v1/carts/{$cartId}/lines", [
            'variant_id' => $variant->getKey(),
            'quantity' => 2,
        ])
        ->assertCreated();

    $checkoutResponse = $api()
        ->postJson('/api/storefront/v1/checkouts', [
            'cart_id' => $cartId,
            'email' => 'buyer@example.test',
        ]);

    $checkoutResponse
        ->assertCreated()
        ->assertJsonPath('data.status', 'started')
        ->assertJsonPath('data.email', 'buyer@example.test')
        ->assertJsonPath('data.totals.subtotal', 4998)
        ->assertJsonPath('data.cart.line_count', 2);

    $checkoutId = $checkoutResponse['data']['id'];

    $addressResponse = $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/address", [
            'shipping_address' => storefrontApiCheckoutAddress(),
        ]);

    $addressResponse
        ->assertOk()
        ->assertJsonPath('data.status', 'addressed')
        ->assertJsonPath('data.shipping_address.country', 'DE')
        ->assertJsonCount(3, 'data.available_shipping_rates');

    $shippingRateId = $addressResponse['data']['available_shipping_rates'][0]['id'];

    $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/shipping-method", [
            'shipping_rate_id' => $shippingRateId,
        ])
        ->assertOk()
        ->assertJsonPath('data.status', 'shipping_selected')
        ->assertJsonPath('data.shipping_method_id', $shippingRateId)
        ->assertJsonPath('data.totals.shipping', 499);

    $api()
        ->postJson("/api/storefront/v1/checkouts/{$checkoutId}/apply-discount", ['code' => 'SAVE10'])
        ->assertOk()
        ->assertJsonPath('data.discount_code', 'SAVE10')
        ->assertJsonPath('data.totals.discount', 500);

    $api()
        ->deleteJson("/api/storefront/v1/checkouts/{$checkoutId}/discount")
        ->assertOk()
        ->assertJsonPath('data.discount_code', null)
        ->assertJsonPath('data.totals.discount', 0);

    $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/payment-method", [
            'payment_method' => 'credit_card',
        ])
        ->assertOk()
        ->assertJsonPath('data.status', 'payment_selected')
        ->assertJsonPath('data.payment_method', 'credit_card');

    $checkout = Checkout::withoutGlobalScopes()->findOrFail($checkoutId);
    $inventory = InventoryItem::withoutGlobalScopes()->where('variant_id', $variant->getKey())->firstOrFail();

    expect($checkout->status)->toBe(CheckoutStatus::PaymentSelected)
        ->and($inventory->quantity_reserved)->toBe(2);
});

test('storefront checkout api rejects invalid addresses shipping methods and discounts', function (): void {
    $store = storefrontApiCheckoutStore();
    $variant = storefrontApiCheckoutVariant($store);
    $api = fn () => $this->withServerVariables(['REMOTE_ADDR' => '10.0.0.22'])->withHeader('Host', 'shop.test');

    $cartId = $api()
        ->postJson('/api/storefront/v1/carts')
        ->assertCreated()['data']['id'];

    $api()
        ->postJson("/api/storefront/v1/carts/{$cartId}/lines", [
            'variant_id' => $variant->getKey(),
            'quantity' => 1,
        ])
        ->assertCreated();

    $checkoutId = $api()
        ->postJson('/api/storefront/v1/checkouts', [
            'cart_id' => $cartId,
            'email' => 'buyer@example.test',
        ])
        ->assertCreated()['data']['id'];

    $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/address", [
            'shipping_address' => array_diff_key(storefrontApiCheckoutAddress(), ['first_name' => true]),
        ])
        ->assertUnprocessable()
        ->assertJsonValidationErrors('shipping_address.first_name');

    $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/address", [
            'shipping_address' => storefrontApiCheckoutAddress(),
        ])
        ->assertOk();

    $otherStoreRate = ShippingRate::withoutGlobalScopes()


sed -n '180,420p' tests/Feature/Api/StorefrontCheckoutApiTest.php


sed -n '180,420p' tests/Feature/Api/StorefrontCheckoutApiTest.php

    $otherStoreRate = ShippingRate::withoutGlobalScopes()
        ->whereHas('zone', fn ($query) => $query->withoutGlobalScopes()->where('store_id', Store::query()->whereKeyNot($store->getKey())->firstOrFail()->getKey()))
        ->firstOrFail();

    $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/shipping-method", [
            'shipping_rate_id' => $otherStoreRate->getKey(),
        ])
        ->assertUnprocessable();

    $api()
        ->postJson("/api/storefront/v1/checkouts/{$checkoutId}/apply-discount", ['code' => 'NOTREAL'])
        ->assertUnprocessable()
        ->assertJsonPath('reason', 'discount_not_found');
});


Output:
Chunk ID: 819f7b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 164
Output:
    $otherStoreRate = ShippingRate::withoutGlobalScopes()
        ->whereHas('zone', fn ($query) => $query->withoutGlobalScopes()->where('store_id', Store::query()->whereKeyNot($store->getKey())->firstOrFail()->getKey()))
        ->firstOrFail();

    $api()
        ->putJson("/api/storefront/v1/checkouts/{$checkoutId}/shipping-method", [
            'shipping_rate_id' => $otherStoreRate->getKey(),
        ])
        ->assertUnprocessable();

    $api()
        ->postJson("/api/storefront/v1/checkouts/{$checkoutId}/apply-discount", ['code' => 'NOTREAL'])
        ->assertUnprocessable()
        ->assertJsonPath('reason', 'discount_not_found');
});


sed -n '1,240p' app/Http/Controllers/Api/Storefront/V1/CartController.php


sed -n '1,220p' app/Http/Controllers/Api/Storefront/V1/CartLineController.php


rg -n "cart_token|access_token|session\(\['cart_id'|cart_id" app/Http app/Services app/Models database/migrations tests/Feature/Api/Storefront*


sed -n '1,240p' app/Http/Controllers/Api/Storefront/V1/CartController.php

<?php

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

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;
use App\Services\CartService;
use Illuminate\Http\JsonResponse;

class CartController extends Controller
{
    public function store(StoreCartRequest $request, CartService $carts): JsonResponse
    {
        $request->validated();

        return CartResource::make($this->loadCart($carts->create($this->currentStore())))
            ->response()
            ->setStatusCode(201);
    }

    public function show(Cart $cart): CartResource
    {
        return CartResource::make($this->loadCart($cart));
    }

    private function currentStore(): Store
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        return $store;
    }

    private function loadCart(Cart $cart): Cart
    {
        return $cart->load([
            'lines.variant.product',
            'lines.variant.optionValues.option',
        ]);
    }
}


sed -n '1,220p' app/Http/Controllers/Api/Storefront/V1/CartLineController.php

<?php

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

use App\Exceptions\CartVersionMismatchException;
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;
use App\Http\Resources\Storefront\V1\CartResource;
use App\Models\Cart;
use App\Models\CartLine;
use App\Services\CartService;
use Illuminate\Http\JsonResponse;

class CartLineController extends Controller
{
    public function store(StoreCartLineRequest $request, Cart $cart, CartService $carts): CartResource|JsonResponse
    {
        try {
            $carts->addLine(
                $cart,
                (int) $request->validated('variant_id'),
                (int) $request->validated('quantity'),
                $request->expectedVersion(),
            );

            return CartResource::make($this->loadCart($cart->refresh()))
                ->response()
                ->setStatusCode(201);
        } catch (CartVersionMismatchException $exception) {
            return $this->versionMismatch($exception, $cart);
        } catch (InsufficientInventoryException|InvalidCartOperationException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function update(UpdateCartLineRequest $request, Cart $cart, CartLine $cartLine, CartService $carts): CartResource|JsonResponse
    {
        abort_unless((int) $cartLine->cart_id === (int) $cart->getKey(), 404);

        try {
            $carts->updateLineQuantity(
                $cart,
                $cartLine->getKey(),
                (int) $request->validated('quantity'),
                $request->expectedVersion(),
            );

            return CartResource::make($this->loadCart($cart->refresh()));
        } catch (CartVersionMismatchException $exception) {
            return $this->versionMismatch($exception, $cart);
        } catch (InsufficientInventoryException|InvalidCartOperationException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

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

        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()));
    }

    private function versionMismatch(CartVersionMismatchException $exception, Cart $cart): JsonResponse
    {
        return response()->json([
            'message' => $exception->getMessage(),
            'expected_cart_version' => $exception->expectedVersion,
            'current_cart_version' => $exception->currentVersion,
            'cart' => CartResource::make($this->loadCart($cart->refresh())),
        ], 409);
    }

    private function loadCart(Cart $cart): Cart
    {
        return $cart->load([
            'lines.variant.product',
            'lines.variant.optionValues.option',
        ]);
    }
}


rg -n "cart_token|access_token|session\(\['cart_id'|cart_id" app/Http app/Services app/Models database/migrations tests/Feature/Api/Storefront*

rg -n "cart_token|access_token|session\\(\\['cart_id'|cart_id" app/Http app/Services app/Models database/migrations 'tests/Feature/Api/Storefront*'

Question: cart_token|access_token|session\(\['cart_id'|cart_id

tests/Feature/Api/StorefrontCheckoutApiTest.php:82:            'cart_id' => $cartId,
tests/Feature/Api/StorefrontCheckoutApiTest.php:162:            'cart_id' => $cartId,
tests/Feature/Api/StorefrontOrderApiTest.php:70:            'cart_id' => $cartId,
tests/Feature/Api/StorefrontOrderApiTest.php:116:    $token = $payResponse['data']['access_token'];
app/Services/DiscountService.php:164:            ->where('cart_id', $cart->getKey())
app/Services/ShippingCalculator.php:111:            ->where('cart_id', $cart->getKey())
app/Services/ShippingCalculator.php:132:            ->where('cart_id', $cart->getKey())
app/Services/PricingEngine.php:34:                ->where('cart_id', $cart->getKey())
app/Services/PricingEngine.php:55:                ->where('cart_id', $cart->getKey())
app/Services/PricingEngine.php:94:            ->where('cart_id', $checkout->cart_id)
app/Models/OauthToken.php:22:        'access_token_hash',
app/Services/CartService.php:37:        $sessionCartId = session('cart_id');
app/Services/CartService.php:58:                session()->forget('cart_id');
app/Services/CartService.php:77:        session(['cart_id' => $cart->getKey()]);
app/Services/CartService.php:97:        $sessionCartId = session('cart_id');
app/Services/CartService.php:131:                ->where('cart_id', $lockedCart->getKey())
app/Services/CartService.php:140:                    'cart_id' => $lockedCart->getKey(),
app/Services/CartService.php:168:                ->where('cart_id', $lockedCart->getKey())
app/Services/CartService.php:189:                ->where('cart_id', $lockedCart->getKey())
app/Services/CartService.php:204:                ->where('cart_id', $guestCart->getKey())
app/Services/CartService.php:208:                        ->where('cart_id', $customerCart->getKey())
app/Services/CartService.php:221:                    $guestLine->forceFill(['cart_id' => $customerCart->getKey()])->save();
app/Models/CartLine.php:21:        'cart_id',
app/Http/Controllers/Api/Admin/V1/PageController.php:103:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Services/CheckoutService.php:40:            if (! CartLine::withoutGlobalScopes()->where('cart_id', $cart->getKey())->exists()) {
app/Services/CheckoutService.php:45:                ->where('cart_id', $cart->getKey())
app/Services/CheckoutService.php:57:                'cart_id' => $cart->getKey(),
app/Services/CheckoutService.php:162:                ->where('cart_id', $checkout->cart_id)
app/Services/CheckoutService.php:193:                    ->where('cart_id', $checkout->cart_id)
app/Http/Controllers/Api/Admin/V1/ThemeSettingsController.php:46:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Services/OrderService.php:487:            ->where('cart_id', $checkout->cart_id)
app/Http/Controllers/Api/Admin/V1/ShippingZoneController.php:60:        if (! $request->attributes->has('sanctum_personal_access_token')) {
database/migrations/2026_05_04_034611_create_oauth_tokens_table.php:18:            $table->string('access_token_hash')->unique('idx_oauth_tokens_access_hash');
app/Http/Controllers/Api/Admin/V1/TaxSettingsController.php:59:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/CustomerController.php:56:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:46:        if (! $request->attributes->has('sanctum_personal_access_token')) {
database/migrations/2026_05_03_225405_create_cart_lines_table.php:16:            $table->foreignId('cart_id')->constrained()->cascadeOnDelete();
database/migrations/2026_05_03_225405_create_cart_lines_table.php:24:            $table->index('cart_id');
database/migrations/2026_05_03_225405_create_cart_lines_table.php:25:            $table->unique(['cart_id', 'variant_id']);
app/Http/Controllers/Api/Admin/V1/StoreSettingsController.php:90:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/ProductController.php:231:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php:25:            'cart_id' => ['required', 'integer', 'exists:carts,id'],
app/Http/Controllers/Api/Admin/V1/ThemeController.php:67:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/DiscountController.php:77:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php:40:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/StoreMembershipController.php:16:        $token = $request->attributes->get('sanctum_personal_access_token');
app/Http/Controllers/Api/Admin/V1/SearchIndexController.php:68:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Models/Checkout.php:21:        'cart_id',
app/Http/Controllers/Api/Admin/V1/CollectionController.php:110:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/ShippingRateController.php:54:        if (! $request->attributes->has('sanctum_personal_access_token')) {
database/migrations/2026_05_03_225409_create_checkouts_table.php:17:            $table->foreignId('cart_id')->constrained()->cascadeOnDelete();
database/migrations/2026_05_03_225409_create_checkouts_table.php:32:            $table->index('cart_id');
app/Http/Controllers/Api/Admin/V1/OrderController.php:62:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Storefront/V1/CartLineController.php:42:        abort_unless((int) $cartLine->cart_id === (int) $cart->getKey(), 404);
app/Http/Controllers/Api/Storefront/V1/CartLineController.php:62:        abort_unless((int) $cartLine->cart_id === (int) $cart->getKey(), 404);
database/migrations/2026_05_04_180719_create_personal_access_tokens_table.php:14:        Schema::create('personal_access_tokens', function (Blueprint $table) {
database/migrations/2026_05_04_180719_create_personal_access_tokens_table.php:31:        Schema::dropIfExists('personal_access_tokens');
app/Http/Controllers/Api/Admin/V1/StoreInviteController.php:42:        if ($request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:33:        $cart = Cart::query()->findOrFail($request->validated('cart_id'));
app/Http/Controllers/Api/Admin/V1/OrderExportController.php:50:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/AnalyticsSummaryController.php:66:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Middleware/AuthenticatePlatformApi.php:46:        $token = $request->attributes->get('sanctum_personal_access_token');
app/Http/Middleware/AuthenticatePlatformApi.php:67:        $request->attributes->set('sanctum_personal_access_token', $token);
app/Http/Middleware/AuthenticatePlatformApi.php:68:        app()->instance('sanctum_personal_access_token', $token);
app/Http/Middleware/AuthenticateAdminApi.php:59:        $token = $request->attributes->get('sanctum_personal_access_token');
app/Http/Middleware/AuthenticateAdminApi.php:80:        $request->attributes->set('sanctum_personal_access_token', $token);
app/Http/Middleware/AuthenticateAdminApi.php:81:        app()->instance('sanctum_personal_access_token', $token);
app/Http/Resources/Storefront/V1/OrderResource.php:22:            'access_token' => OrderAccessToken::make($this->resource),
app/Http/Resources/Storefront/V1/CheckoutResource.php:20:            'cart_id' => $this->cart_id,


Output:
Chunk ID: 79ac72
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 280
Output:
<?php

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

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;
use App\Services\CartService;
use Illuminate\Http\JsonResponse;

class CartController extends Controller
{
    public function store(StoreCartRequest $request, CartService $carts): JsonResponse
    {
        $request->validated();

        return CartResource::make($this->loadCart($carts->create($this->currentStore())))
            ->response()
            ->setStatusCode(201);
    }

    public function show(Cart $cart): CartResource
    {
        return CartResource::make($this->loadCart($cart));
    }

    private function currentStore(): Store
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        return $store;
    }

    private function loadCart(Cart $cart): Cart
    {
        return $cart->load([
            'lines.variant.product',
            'lines.variant.optionValues.option',
        ]);
    }
}


Output:
Chunk ID: 68128b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 901
Output:
<?php

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

use App\Exceptions\CartVersionMismatchException;
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;
use App\Http\Resources\Storefront\V1\CartResource;
use App\Models\Cart;
use App\Models\CartLine;
use App\Services\CartService;
use Illuminate\Http\JsonResponse;

class CartLineController extends Controller
{
    public function store(StoreCartLineRequest $request, Cart $cart, CartService $carts): CartResource|JsonResponse
    {
        try {
            $carts->addLine(
                $cart,
                (int) $request->validated('variant_id'),
                (int) $request->validated('quantity'),
                $request->expectedVersion(),
            );

            return CartResource::make($this->loadCart($cart->refresh()))
                ->response()
                ->setStatusCode(201);
        } catch (CartVersionMismatchException $exception) {
            return $this->versionMismatch($exception, $cart);
        } catch (InsufficientInventoryException|InvalidCartOperationException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function update(UpdateCartLineRequest $request, Cart $cart, CartLine $cartLine, CartService $carts): CartResource|JsonResponse
    {
        abort_unless((int) $cartLine->cart_id === (int) $cart->getKey(), 404);

        try {
            $carts->updateLineQuantity(
                $cart,
                $cartLine->getKey(),
                (int) $request->validated('quantity'),
                $request->expectedVersion(),
            );

            return CartResource::make($this->loadCart($cart->refresh()));
        } catch (CartVersionMismatchException $exception) {
            return $this->versionMismatch($exception, $cart);
        } catch (InsufficientInventoryException|InvalidCartOperationException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

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

        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()));
    }

    private function versionMismatch(CartVersionMismatchException $exception, Cart $cart): JsonResponse
    {
        return response()->json([
            'message' => $exception->getMessage(),
            'expected_cart_version' => $exception->expectedVersion,
            'current_cart_version' => $exception->currentVersion,
            'cart' => CartResource::make($this->loadCart($cart->refresh())),
        ], 409);
    }

    private function loadCart(Cart $cart): Cart
    {
        return $cart->load([
            'lines.variant.product',
            'lines.variant.optionValues.option',
        ]);
    }
}


Output:
Chunk ID: 3ee69f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1956
Output:
tests/Feature/Api/StorefrontCheckoutApiTest.php:82:            'cart_id' => $cartId,
tests/Feature/Api/StorefrontCheckoutApiTest.php:162:            'cart_id' => $cartId,
tests/Feature/Api/StorefrontOrderApiTest.php:70:            'cart_id' => $cartId,
tests/Feature/Api/StorefrontOrderApiTest.php:116:    $token = $payResponse['data']['access_token'];
app/Services/DiscountService.php:164:            ->where('cart_id', $cart->getKey())
app/Services/ShippingCalculator.php:111:            ->where('cart_id', $cart->getKey())
app/Services/ShippingCalculator.php:132:            ->where('cart_id', $cart->getKey())
app/Services/PricingEngine.php:34:                ->where('cart_id', $cart->getKey())
app/Services/PricingEngine.php:55:                ->where('cart_id', $cart->getKey())
app/Services/PricingEngine.php:94:            ->where('cart_id', $checkout->cart_id)
app/Models/OauthToken.php:22:        'access_token_hash',
app/Services/CartService.php:37:        $sessionCartId = session('cart_id');
app/Services/CartService.php:58:                session()->forget('cart_id');
app/Services/CartService.php:77:        session(['cart_id' => $cart->getKey()]);
app/Services/CartService.php:97:        $sessionCartId = session('cart_id');
app/Services/CartService.php:131:                ->where('cart_id', $lockedCart->getKey())
app/Services/CartService.php:140:                    'cart_id' => $lockedCart->getKey(),
app/Services/CartService.php:168:                ->where('cart_id', $lockedCart->getKey())
app/Services/CartService.php:189:                ->where('cart_id', $lockedCart->getKey())
app/Services/CartService.php:204:                ->where('cart_id', $guestCart->getKey())
app/Services/CartService.php:208:                        ->where('cart_id', $customerCart->getKey())
app/Services/CartService.php:221:                    $guestLine->forceFill(['cart_id' => $customerCart->getKey()])->save();
app/Models/CartLine.php:21:        'cart_id',
app/Http/Controllers/Api/Admin/V1/PageController.php:103:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Services/CheckoutService.php:40:            if (! CartLine::withoutGlobalScopes()->where('cart_id', $cart->getKey())->exists()) {
app/Services/CheckoutService.php:45:                ->where('cart_id', $cart->getKey())
app/Services/CheckoutService.php:57:                'cart_id' => $cart->getKey(),
app/Services/CheckoutService.php:162:                ->where('cart_id', $checkout->cart_id)
app/Services/CheckoutService.php:193:                    ->where('cart_id', $checkout->cart_id)
app/Http/Controllers/Api/Admin/V1/ThemeSettingsController.php:46:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Services/OrderService.php:487:            ->where('cart_id', $checkout->cart_id)
app/Http/Controllers/Api/Admin/V1/ShippingZoneController.php:60:        if (! $request->attributes->has('sanctum_personal_access_token')) {
database/migrations/2026_05_04_034611_create_oauth_tokens_table.php:18:            $table->string('access_token_hash')->unique('idx_oauth_tokens_access_hash');
app/Http/Controllers/Api/Admin/V1/TaxSettingsController.php:59:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/CustomerController.php:56:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:46:        if (! $request->attributes->has('sanctum_personal_access_token')) {
database/migrations/2026_05_03_225405_create_cart_lines_table.php:16:            $table->foreignId('cart_id')->constrained()->cascadeOnDelete();
database/migrations/2026_05_03_225405_create_cart_lines_table.php:24:            $table->index('cart_id');
database/migrations/2026_05_03_225405_create_cart_lines_table.php:25:            $table->unique(['cart_id', 'variant_id']);
app/Http/Controllers/Api/Admin/V1/StoreSettingsController.php:90:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/ProductController.php:231:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php:25:            'cart_id' => ['required', 'integer', 'exists:carts,id'],
app/Http/Controllers/Api/Admin/V1/ThemeController.php:67:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/DiscountController.php:77:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/OrderFulfillmentController.php:40:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/StoreMembershipController.php:16:        $token = $request->attributes->get('sanctum_personal_access_token');
app/Http/Controllers/Api/Admin/V1/SearchIndexController.php:68:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Models/Checkout.php:21:        'cart_id',
app/Http/Controllers/Api/Admin/V1/CollectionController.php:110:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/ShippingRateController.php:54:        if (! $request->attributes->has('sanctum_personal_access_token')) {
database/migrations/2026_05_03_225409_create_checkouts_table.php:17:            $table->foreignId('cart_id')->constrained()->cascadeOnDelete();
database/migrations/2026_05_03_225409_create_checkouts_table.php:32:            $table->index('cart_id');
app/Http/Controllers/Api/Admin/V1/OrderController.php:62:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Storefront/V1/CartLineController.php:42:        abort_unless((int) $cartLine->cart_id === (int) $cart->getKey(), 404);
app/Http/Controllers/Api/Storefront/V1/CartLineController.php:62:        abort_unless((int) $cartLine->cart_id === (int) $cart->getKey(), 404);
database/migrations/2026_05_04_180719_create_personal_access_tokens_table.php:14:        Schema::create('personal_access_tokens', function (Blueprint $table) {
database/migrations/2026_05_04_180719_create_personal_access_tokens_table.php:31:        Schema::dropIfExists('personal_access_tokens');
app/Http/Controllers/Api/Admin/V1/StoreInviteController.php:42:        if ($request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:33:        $cart = Cart::query()->findOrFail($request->validated('cart_id'));
app/Http/Controllers/Api/Admin/V1/OrderExportController.php:50:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Controllers/Api/Admin/V1/AnalyticsSummaryController.php:66:        if (! $request->attributes->has('sanctum_personal_access_token')) {
app/Http/Middleware/AuthenticatePlatformApi.php:46:        $token = $request->attributes->get('sanctum_personal_access_token');
app/Http/Middleware/AuthenticatePlatformApi.php:67:        $request->attributes->set('sanctum_personal_access_token', $token);
app/Http/Middleware/AuthenticatePlatformApi.php:68:        app()->instance('sanctum_personal_access_token', $token);
app/Http/Middleware/AuthenticateAdminApi.php:59:        $token = $request->attributes->get('sanctum_personal_access_token');
app/Http/Middleware/AuthenticateAdminApi.php:80:        $request->attributes->set('sanctum_personal_access_token', $token);
app/Http/Middleware/AuthenticateAdminApi.php:81:        app()->instance('sanctum_personal_access_token', $token);
app/Http/Resources/Storefront/V1/OrderResource.php:22:            'access_token' => OrderAccessToken::make($this->resource),
app/Http/Resources/Storefront/V1/CheckoutResource.php:20:            'cart_id' => $this->cart_id,


sed -n '1,130p' app/Services/CartService.php && sed -n '1,80p' app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php


sed -n '1,130p' app/Services/CartService.php && sed -n '1,80p' app/Http/Requests/Api/Storefront/V1/StoreCheckoutRequest.php

StoreCheckoutRequest.php

<?php

namespace App\Services;

use App\Enums\CartStatus;
use App\Enums\ProductStatus;
use App\Enums\VariantStatus;
use App\Exceptions\CartVersionMismatchException;
use App\Exceptions\InvalidCartOperationException;
use App\Models\Cart;
use App\Models\CartLine;
use App\Models\Customer;
use App\Models\InventoryItem;
use App\Models\ProductVariant;
use App\Models\Store;
use Illuminate\Support\Facades\DB;

class CartService
{
    public function __construct(
        private readonly InventoryService $inventory,
    ) {}

    public function create(Store $store, ?Customer $customer = null): Cart
    {
        return Cart::withoutGlobalScopes()->create([
            'store_id' => $store->getKey(),
            'customer_id' => $customer?->getKey(),
            'currency' => $store->default_currency,
            'cart_version' => 1,
            'status' => CartStatus::Active,
        ]);
    }

    public function getOrCreateForSession(Store $store, ?Customer $customer = null): Cart
    {
        $sessionCartId = session('cart_id');

        if ($customer instanceof Customer) {
            $customerCart = Cart::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->where('customer_id', $customer->getKey())
                ->where('status', CartStatus::Active)
                ->latest('id')
                ->first() ?? $this->create($store, $customer);

            if ($sessionCartId) {
                $guestCart = Cart::withoutGlobalScopes()
                    ->where('store_id', $store->getKey())
                    ->whereNull('customer_id')
                    ->where('status', CartStatus::Active)
                    ->find($sessionCartId);

                if ($guestCart instanceof Cart && $guestCart->isNot($customerCart)) {
                    $customerCart = $this->mergeOnLogin($guestCart, $customerCart);
                }

                session()->forget('cart_id');
            }

            return $customerCart->refresh();
        }

        if ($sessionCartId) {
            $cart = Cart::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->whereNull('customer_id')
                ->where('status', CartStatus::Active)
                ->find($sessionCartId);

            if ($cart instanceof Cart) {
                return $cart;
            }
        }

        $cart = $this->create($store);
        session(['cart_id' => $cart->getKey()]);

        return $cart;
    }

    public function currentForSession(Store $store, ?Customer $customer = null): ?Cart
    {
        if ($customer instanceof Customer) {
            $customerCart = Cart::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->where('customer_id', $customer->getKey())
                ->where('status', CartStatus::Active)
                ->latest('id')
                ->first();

            if ($customerCart instanceof Cart) {
                return $customerCart;
            }
        }

        $sessionCartId = session('cart_id');

        if (! $sessionCartId) {
            return null;
        }

        return Cart::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('status', CartStatus::Active)
            ->where(function ($query) use ($customer): void {
                if ($customer instanceof Customer) {
                    $query
                        ->whereNull('customer_id')
                        ->orWhere('customer_id', $customer->getKey());

                    return;
                }

                $query->whereNull('customer_id');
            })
            ->find($sessionCartId);
    }

    public function addLine(Cart $cart, int $variantId, int $quantity, ?int $expectedVersion = null): CartLine
    {
        $this->assertPositiveQuantity($quantity);

        return DB::transaction(function () use ($cart, $variantId, $quantity, $expectedVersion): CartLine {
            $lockedCart = $this->freshCart($cart);
            $this->assertExpectedVersion($lockedCart, $expectedVersion);
            $this->assertCartIsActive($lockedCart);

            $variant = $this->purchasableVariant($lockedCart, $variantId);
            $line = CartLine::withoutGlobalScopes()
<?php

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

use Illuminate\Foundation\Http\FormRequest;

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

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