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

@@ -4,6 +4,7 @@ namespace App\Domains\Purchase\Controllers;
use App\Domains\Integration\Services\TelepagosIntegrationService;
use App\Domains\Purchase\Models\Purchase;
use App\Domains\Purchase\Models\PurchaseItem;
use App\Domains\Purchase\Requests\PaymentIntentRequest;
use App\Domains\Purchase\Requests\StartCheckoutRequest;
use App\Domains\Purchase\Requests\UpdatePurchaseCustomerRequest;
@@ -112,6 +113,20 @@ class PurchaseController extends Controller
);
}
public function removeItem(
Request $request,
Tenant $tenant,
Purchase $compra,
PurchaseItem $item,
CheckoutService $checkoutService,
): PurchaseResource {
$compra = $this->resolveScopedPurchase($tenant, $request->user()->id, $compra);
return PurchaseResource::make(
$checkoutService->removeItem($compra, $item),
);
}
public function paymentIntent(
PaymentIntentRequest $request,
Tenant $tenant,

View File

@@ -30,7 +30,7 @@ class PurchaseItemResource extends JsonResource
? $this->sourceCatalogItem
: null;
$includeVariants = $tenant instanceof Tenant
&& $tenant->cart_editing_policy->allowsVariantChanges()
&& $tenant->checkout_editing_policy->allowsVariantChanges()
&& $catalogItem !== null
&& $catalogItem->relationLoaded('variants');
@@ -67,6 +67,9 @@ class PurchaseItemResource extends JsonResource
$imageUrl = $displayImage
? $this->resolveImageUrl($selectedItem, $catalogItem)
: null;
$includeVariants = $tenant instanceof Tenant
&& $tenant->checkout_editing_policy->allowsVariantChanges()
&& $catalogItem?->relationLoaded('variants');
return [
'id' => $this->id,
@@ -82,6 +85,16 @@ class PurchaseItemResource extends JsonResource
'imagen' => $imageUrl,
'attributes' => $variant === null ? [] : $this->resolveAttributes($variant),
],
'variants' => $this->when(
$includeVariants,
fn () => $catalogItem
->visibleVariants($this->variant_id)
->map(fn (Variant $availableVariant): array => $this->variantData(
$catalogItem,
$availableVariant,
))
->values(),
),
];
}

View File

@@ -61,13 +61,13 @@ class EditCheckoutService
$tenant = $purchase->tenant()->firstOrFail();
$finalQuantity = $quantity ?? (int) $purchaseItem->cantidad;
if ($quantity !== null && ! $tenant->cart_editing_policy->allowsQuantityChanges()) {
if ($quantity !== null && ! $tenant->checkout_editing_policy->allowsQuantityChanges()) {
throw ValidationException::withMessages([
'quantity' => __('api.cart.editing_disabled'),
]);
}
if ($updateVariant && ! $tenant->cart_editing_policy->allowsVariantChanges()) {
if ($updateVariant && ! $tenant->checkout_editing_policy->allowsVariantChanges()) {
throw ValidationException::withMessages([
'variant_id' => __('api.purchase.variant_change_disabled'),
]);
@@ -184,7 +184,7 @@ class EditCheckoutService
$purchase = $this->lockPurchase($purchase);
$this->assertEditable($purchase);
if (! $purchase->tenant()->firstOrFail()->cart_editing_policy->allowsModification()) {
if (! $purchase->tenant()->firstOrFail()->checkout_editing_policy->allowsModification()) {
throw ValidationException::withMessages([
'purchase' => __('api.cart.editing_disabled'),
]);
@@ -204,6 +204,31 @@ class EditCheckoutService
});
}
public function removeItem(Purchase $purchase, PurchaseItem $purchaseItem): Purchase
{
return DB::transaction(function () use ($purchase, $purchaseItem): Purchase {
$purchase = $this->lockPurchase($purchase);
$this->assertEditable($purchase);
if (! $purchase->tenant()->firstOrFail()->checkout_editing_policy->allowsRemoval()) {
throw ValidationException::withMessages([
'item' => __('api.cart.editing_disabled'),
]);
}
$purchaseItem = $this->lockPurchaseItem($purchase, $purchaseItem);
$selection = $this->selections->resolvePurchaseItem($purchase->tenant, $purchaseItem);
$this->inventory->release($selection, (int) $purchaseItem->cantidad);
$this->sourceCart->removeItem($purchase, $purchaseItem);
$purchaseItem->delete();
$purchase->update([
'total' => $purchase->calculateCurrentTotalAmount(),
]);
return $this->loadPurchase($purchase);
});
}
private function adjustReservation(
Purchase $purchase,
PurchaseItem $purchaseItem,

View File

@@ -23,7 +23,7 @@ class PurchaseResponseLoader
]);
}
if (! $purchase->tenant->cart_editing_policy->allowsVariantChanges()) {
if (! $purchase->tenant->checkout_editing_policy->allowsVariantChanges()) {
return $purchase;
}
@@ -35,6 +35,13 @@ class PurchaseResponseLoader
'items.sourceCatalogItem.variants.definitions.itemAttribute.attribute.options',
'items.sourceCatalogItem.variants.eventDates',
'items.sourceCatalogItem.variants.eventDate',
'cart.items.catalogItem.itemAttributes.attribute',
'cart.items.catalogItem.variants' => fn ($query) => $query->orderBy('id'),
'cart.items.catalogItem.variants.inventory',
'cart.items.catalogItem.variants.definitions' => fn ($query) => $query->orderBy('id'),
'cart.items.catalogItem.variants.definitions.itemAttribute.attribute.options',
'cart.items.catalogItem.variants.eventDates',
'cart.items.catalogItem.variants.eventDate',
]);
return $purchase;

View File

@@ -130,6 +130,20 @@ class SourceCartService
]);
}
public function removeItem(Purchase $purchase, PurchaseItem $purchaseItem): void
{
$sourceCart = $this->findSourceCart($purchase);
if ($sourceCart === null) {
return;
}
$sourceCart->items()
->where('catalog_item_id', $purchaseItem->source_catalog_item_id)
->where('variant_id', $purchaseItem->source_variant_id)
->delete();
}
public function finalize(Purchase $purchase): void
{
$sourceCart = $this->findSourceCart($purchase);

View File

@@ -4,6 +4,7 @@ namespace App\Domains\Purchase\Services;
use App\Domains\Catalog\Services\StockReservationService;
use App\Domains\Purchase\Models\Purchase;
use App\Domains\Purchase\Models\PurchaseItem;
use App\Domains\Purchase\Services\Checkout\CompleteCheckoutService;
use App\Domains\Purchase\Services\Checkout\EditCheckoutService;
use App\Domains\Purchase\Services\Checkout\ReleaseCheckoutService;
@@ -69,6 +70,11 @@ class CheckoutService
return $this->editor->prepareItemEditing($purchase);
}
public function removeItem(Purchase $purchase, PurchaseItem $purchaseItem): Purchase
{
return $this->editor->removeItem($purchase, $purchaseItem);
}
public function confirmPurchase(Purchase $purchase): void
{
$this->completer->confirm($purchase);

View File

@@ -9,6 +9,7 @@ Route::prefix('tenants/{tenant:codigo}')->middleware('auth:sanctum')->group(func
Route::get('compras/{compra}', [PurchaseController::class, 'show']);
Route::post('compras/{compra}/edit-items', [PurchaseController::class, 'prepareItemEditing']);
Route::patch('compras/{compra}/items/{item}', [PurchaseController::class, 'updateItem']);
Route::delete('compras/{compra}/items/{item}', [PurchaseController::class, 'removeItem']);
Route::patch('compras/{compra}/customer-data', [PurchaseController::class, 'updateCustomerData']);
Route::post('compras/{compra}/payment-intent', [PurchaseController::class, 'paymentIntent']);
Route::post('compras/{compra}/complete', [PurchaseController::class, 'complete']);