refactor(cart): own checkout item editing

This commit is contained in:
2026-08-19 12:24:48 -03:00
parent aed99bd05e
commit 3206e293eb
13 changed files with 330 additions and 302 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Domains\Cart\Controllers;
use App\Domains\Cart\Models\Cart;
use App\Domains\Cart\Models\CartItem;
use App\Domains\Cart\Requests\AddCartItemRequest;
use App\Domains\Cart\Requests\UpdateCartItemQuantityRequest;
@@ -77,6 +78,46 @@ class CartController extends Controller
]);
}
public function updateCheckoutItem(
UpdateCartItemQuantityRequest $request,
Tenant $tenant,
Cart $cart,
CartItem $cartItem,
): CartResource {
$updatesVariant = $request->exists('variant_id');
return CartResource::make(
$this->cartService->updateCheckoutItem(
$tenant,
$request,
$cart,
$cartItem->getKey(),
(int) $request->validated('cantidad'),
$updatesVariant
? ($request->validated('variant_id') !== null
? (int) $request->validated('variant_id')
: null)
: $cartItem->variant_id,
$updatesVariant,
),
)->additional([
'code' => $updatesVariant ? 'cart.item_updated' : 'cart.quantity_updated',
'message' => $updatesVariant
? __('api.cart.item_updated')
: __('api.cart.quantity_updated'),
]);
}
public function prepareCheckoutEditing(
Request $request,
Tenant $tenant,
Cart $cart,
): CartResource {
return CartResource::make(
$this->cartService->prepareCheckoutEditing($tenant, $request, $cart),
);
}
public function removeItem(Request $request, Tenant $tenant, CartItem $cartItem): CartResource
{
return CartResource::make(

View File

@@ -143,6 +143,7 @@ class Cart extends Model
int $quantity,
?int $variantId = null,
bool $updateVariant = false,
?int $excludedPurchaseId = null,
): CartItem {
if ($quantity <= 0) {
throw ValidationException::withMessages([
@@ -155,6 +156,7 @@ class Cart extends Model
$quantity,
$variantId,
$updateVariant,
$excludedPurchaseId,
): CartItem {
/** @var CartItem $item */
$item = $this->items()
@@ -182,6 +184,7 @@ class Cart extends Model
$this->assertUserPurchaseLimit(
$nextSelection,
$otherVariantsQuantity + $quantity,
$excludedPurchaseId,
);
app(StockReservationService::class)->release($item, $currentSelection, $item->cantidad);
@@ -228,6 +231,7 @@ class Cart extends Model
$this->assertUserPurchaseLimit(
$currentSelection,
$otherVariantsQuantity + $quantity,
$excludedPurchaseId,
);
}
@@ -350,6 +354,7 @@ class Cart extends Model
private function assertUserPurchaseLimit(
CatalogItem|Variant $selectedItem,
int $cartQuantity,
?int $excludedPurchaseId = null,
): void {
if ($this->user_id === null) {
return;
@@ -363,6 +368,7 @@ class Cart extends Model
$catalogItem,
$this->user_id,
$cartQuantity,
$excludedPurchaseId,
field: 'cantidad',
);
}

View File

@@ -4,15 +4,23 @@ namespace App\Domains\Cart\Services;
use App\Domains\Auth\Models\User;
use App\Domains\Cart\Models\Cart;
use App\Domains\Catalog\Services\StockReservationService;
use App\Domains\Purchase\Models\Purchase;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class CartService
{
public function __construct(
private readonly StockReservationService $reservations,
) {}
public function show(Tenant $tenant, Request $request): Cart
{
$resolvedIdentity = $this->resolveIdentity($request);
@@ -66,6 +74,139 @@ class CartService
return $this->loadCart($cart);
}
public function updateCheckoutItem(
Tenant $tenant,
Request $request,
Cart $cart,
int $cartItemId,
int $quantity,
?int $variantId,
bool $updateVariant,
): Cart {
$user = $request->user() ?? Auth::guard('sanctum')->user();
if (! $user instanceof User) {
throw new NotFoundHttpException('Checkout cart not found.');
}
return DB::transaction(function () use (
$tenant,
$user,
$cart,
$cartItemId,
$quantity,
$variantId,
$updateVariant,
): Cart {
/** @var Purchase|null $purchase */
$purchase = Purchase::query()
->where('cart_id', $cart->getKey())
->where('tenant_codigo', $tenant->codigo)
->where('user_id', $user->getKey())
->where('status', Purchase::STATUS_CREATED)
->whereDoesntHave('items')
->lockForUpdate()
->first();
if ($purchase === null) {
throw new NotFoundHttpException('Checkout cart not found.');
}
if ($purchase->expires_at !== null && $purchase->expires_at->isPast()) {
throw ValidationException::withMessages([
'cart' => __('api.purchase.not_editable'),
]);
}
/** @var Cart|null $checkoutCart */
$checkoutCart = Cart::query()
->whereKey($cart->getKey())
->where('tenant_codigo', $tenant->codigo)
->where('user_id', $user->getKey())
->where('status', 'checkout')
->lockForUpdate()
->first();
if ($checkoutCart === null) {
throw new NotFoundHttpException('Checkout cart not found.');
}
$checkoutCart->updateItem(
$cartItemId,
$quantity,
$variantId,
$updateVariant,
$purchase->getKey(),
);
$purchase->update(['total' => $checkoutCart->getTotalAmount()]);
return $this->loadCart($checkoutCart);
});
}
public function prepareCheckoutEditing(
Tenant $tenant,
Request $request,
Cart $cart,
): Cart {
$user = $request->user() ?? Auth::guard('sanctum')->user();
if (! $user instanceof User) {
throw new NotFoundHttpException('Checkout cart not found.');
}
return DB::transaction(function () use ($tenant, $user, $cart): Cart {
/** @var Purchase|null $purchase */
$purchase = Purchase::query()
->where('cart_id', $cart->getKey())
->where('tenant_codigo', $tenant->codigo)
->where('user_id', $user->getKey())
->whereIn('status', [
Purchase::STATUS_CREATED,
Purchase::STATUS_PENDING_PAYMENT,
])
->whereDoesntHave('items')
->lockForUpdate()
->first();
if ($purchase === null) {
throw new NotFoundHttpException('Checkout cart not found.');
}
if ($purchase->expires_at !== null && $purchase->expires_at->isPast()) {
throw ValidationException::withMessages([
'cart' => __('api.purchase.not_editable'),
]);
}
/** @var Cart|null $checkoutCart */
$checkoutCart = Cart::query()
->whereKey($cart->getKey())
->where('tenant_codigo', $tenant->codigo)
->where('user_id', $user->getKey())
->where('status', 'checkout')
->lockForUpdate()
->first();
if ($checkoutCart === null) {
throw new NotFoundHttpException('Checkout cart not found.');
}
$purchase->telepagosQr()->delete();
$purchase->update([
'status' => Purchase::STATUS_CREATED,
'payment_method' => null,
'transfer_payer_dni' => null,
'expires_at' => now()->addMinutes(
max(1, (int) config('purchase.checkout_expiration_minutes', 30)),
),
]);
$this->reservations->syncPurchaseExpiration($purchase);
return $this->loadCart($checkoutCart);
});
}
public function removeItem(Tenant $tenant, Request $request, int $cartItemId): Cart
{
$identity = $this->requireIdentity($request);

View File

@@ -10,3 +10,10 @@ Route::prefix('tenants/{tenant:codigo}')
Route::patch('cart/items/{cartItem}', [CartController::class, 'updateItemQuantity']);
Route::delete('cart/items/{cartItem}', [CartController::class, 'removeItem']);
});
Route::prefix('tenants/{tenant:codigo}')
->middleware('auth:sanctum')
->group(function (): void {
Route::post('checkout-carts/{cart}/edit', [CartController::class, 'prepareCheckoutEditing']);
Route::patch('checkout-carts/{cart}/items/{cartItem}', [CartController::class, 'updateCheckoutItem']);
});