feat(checkout): enforce editing policy on items

This commit is contained in:
2026-08-20 10:56:19 -03:00
parent d36a929f8c
commit c8ce3b0da3
12 changed files with 272 additions and 17 deletions

View File

@@ -161,7 +161,26 @@ class CartService
|| ($updateVariant && $cartItem->variant_id !== $variantId);
if (! $hasChanges) {
return $this->loadCart($checkoutCart);
return $this->loadCart($checkoutCart, $tenant, true);
}
if (
(int) $cartItem->cantidad !== $quantity
&& ! $tenant->checkout_editing_policy->allowsQuantityChanges()
) {
throw ValidationException::withMessages([
'cantidad' => __('api.cart.editing_disabled'),
]);
}
if (
$updateVariant
&& $cartItem->variant_id !== $variantId
&& ! $tenant->checkout_editing_policy->allowsVariantChanges()
) {
throw ValidationException::withMessages([
'variant_id' => __('api.cart.variant_change_disabled'),
]);
}
$checkoutCart->updateItem(
@@ -184,7 +203,7 @@ class CartService
]);
$this->reservations->syncPurchaseExpiration($purchase);
return $this->loadCart($checkoutCart);
return $this->loadCart($checkoutCart, $tenant, true);
});
}
@@ -203,6 +222,78 @@ class CartService
return $this->loadCart($cart, $tenant);
}
public function removeCheckoutItem(
Tenant $tenant,
Request $request,
Cart $cart,
int $cartItemId,
): Cart {
if (! $tenant->checkout_editing_policy->allowsRemoval()) {
throw ValidationException::withMessages([
'cart_item' => __('api.cart.editing_disabled'),
]);
}
$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): 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.');
}
$checkoutCart->removeItem($cartItemId);
$purchase->telepagosQr()->delete();
$purchase->update([
'status' => Purchase::STATUS_CREATED,
'payment_method' => null,
'transfer_payer_dni' => null,
'total' => $checkoutCart->getTotalAmount(),
'expires_at' => now()->addMinutes(
max(1, (int) config('purchase.checkout_expiration_minutes', 30)),
),
]);
$this->reservations->syncPurchaseExpiration($purchase);
return $this->loadCart($checkoutCart, $tenant, true);
});
}
public function makeGuestTokenCookie(string $guestToken): Cookie
{
return cookie(
@@ -230,7 +321,7 @@ class CartService
return $cart;
}
protected function loadCart(Cart $cart, Tenant $tenant): Cart
protected function loadCart(Cart $cart, Tenant $tenant, bool $isCheckout = false): Cart
{
$relations = [
'items.catalogItem.attachments',
@@ -243,7 +334,11 @@ class CartService
'items.variant.eventDate',
];
if ($tenant->cart_editing_policy->allowsVariantChanges()) {
$editingPolicy = $isCheckout
? $tenant->checkout_editing_policy
: $tenant->cart_editing_policy;
if ($editingPolicy->allowsVariantChanges()) {
$relations = [
...$relations,
'items.catalogItem.variants' => fn ($query) => $query->orderBy('id'),