fix(cart): reconcile expired cart mutations

This commit is contained in:
2026-08-25 16:33:39 -03:00
parent 3dd378ed06
commit ebcbbb1774
7 changed files with 71 additions and 84 deletions

View File

@@ -5,7 +5,6 @@ namespace App\Domains\Cart\Services;
use App\Domains\Auth\Models\User;
use App\Domains\Cart\Models\Cart;
use App\Domains\Catalog\Exceptions\StockReservationExpiredException;
use App\Domains\Catalog\Models\StockReservation;
use App\Domains\Catalog\Services\ExpireStockReservationsService;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Http\Request;
@@ -98,45 +97,6 @@ class CartService
return $this->loadCart($cart, $tenant);
}
public function restartExpired(Tenant $tenant, Request $request): Cart
{
$identity = $this->requireIdentity($request);
$cart = $this->findCartOrFail($tenant, $identity);
if ($cart->current_stock_reservation_id === null
|| ! app(ExpireStockReservationsService::class)
->expireIfOverdue($cart->current_stock_reservation_id)) {
throw ValidationException::withMessages([
'cart' => __('api.cart.reservation_not_expired'),
]);
}
$newCart = DB::transaction(function () use ($cart, $identity, $tenant): Cart {
/** @var Cart $cart */
$cart = Cart::query()->lockForUpdate()->findOrFail($cart->getKey());
/** @var StockReservation|null $reservation */
$reservation = $cart->current_stock_reservation_id === null
? null
: StockReservation::query()
->lockForUpdate()
->find($cart->current_stock_reservation_id);
if ($reservation?->status !== StockReservation::STATUS_EXPIRED) {
throw ValidationException::withMessages([
'cart' => __('api.cart.reservation_not_expired'),
]);
}
$cart->update([
'status' => Cart::STATUS_ABANDONED,
'current_purchase_id' => null,
]);
return $this->findOrCreateCart($tenant, $identity);
});
return $this->loadCart($newCart, $tenant);
}
public function makeGuestTokenCookie(string $guestToken): Cookie
{
$secure = (bool) config('session.secure');
@@ -293,14 +253,68 @@ class CartService
*/
protected function findOrCreateCart(Tenant $tenant, array $identity): Cart
{
return $this->resolveCart($tenant, $identity)
?? $this->createCart($tenant, $identity);
}
/**
* @param array{user_id: ?int, guest_token: ?string} $identity
*/
protected function resolveCart(
Tenant $tenant,
array $identity,
bool $replaceExpired = true,
): ?Cart {
$cart = $this->findCart($tenant, $identity);
if ($cart?->status === Cart::STATUS_EXPIRED) {
throw new StockReservationExpiredException;
if ($cart?->status === Cart::STATUS_ACTIVE
&& $cart->current_stock_reservation_id !== null
&& app(ExpireStockReservationsService::class)
->expireIfOverdue($cart->current_stock_reservation_id)) {
$cart = $this->findCart($tenant, $identity);
}
if ($cart?->status === Cart::STATUS_EXPIRED) {
if (! $replaceExpired) {
throw new StockReservationExpiredException;
}
return $this->replaceExpiredCart($cart, $tenant, $identity);
}
if ($cart !== null) {
return $cart;
}
return null;
}
/**
* @param array{user_id: ?int, guest_token: ?string} $identity
*/
protected function replaceExpiredCart(Cart $expiredCart, Tenant $tenant, array $identity): Cart
{
return DB::transaction(function () use ($expiredCart, $tenant, $identity): Cart {
/** @var Cart|null $lockedCart */
$lockedCart = Cart::query()->lockForUpdate()->find($expiredCart->getKey());
if ($lockedCart?->status === Cart::STATUS_EXPIRED) {
$lockedCart->update([
'status' => Cart::STATUS_ABANDONED,
'current_purchase_id' => null,
]);
}
return $this->findCart($tenant, $identity)
?? $this->createCart($tenant, $identity);
});
}
/**
* @param array{user_id: ?int, guest_token: ?string} $identity
*/
protected function createCart(Tenant $tenant, array $identity): Cart
{
$attributes = [
'tenant_codigo' => $tenant->codigo,
'status' => Cart::STATUS_ACTIVE,