hi so i have a problem in my laravel code
i am making a discount method that is like buy x get y that shopify have but i am having a problem
first i am going to explain the offer
the offer is buy 2 items and the third one in the cart when you add it it will be discounted
here is my xyoffer model that have the data
class Xyoffer extends Model
{
use HasFactory;
protected $fillable = [
'name',
'description',
'type',
'rule',
'buy_model_type',
'buy_models',
'buy_items',
'buy_amount',
'get_model_type',
'get_models',
'get_items',
'get_amount',
'discount_percentage',
'discount_amount',
'status',
];
protected $casts = [
'buy_models' => 'array',
'buy_items' => 'array',
'get_items' => 'array',
'get_models' => 'array',
];
}
the offer that i stored is buy 2 get 1
now it is working good when the items are different and when i only add 3 items
1 is getting discount and 2 are normal
now the problem is happening when i add one more item
(note: this only happen if the shopify_product_id is in the buy_items and the get_items if they are not it works fine)
when adding one more which means that the total quantity of the items that can be used is 4 the offer is getting canceled and all the items are back to there original state
how can i fix this please help
this is the code that is making the discount
class XyOfferAction
{
use AsAction;
public function handle($cart)
{
DB::beginTransaction();
try {
// Reset all items in the cart to their normal state
$this->removeInvalidDiscounts($cart);
// Fetch all active offers
$offers = XyOffer::all();
// Apply each offer to the cart
foreach ($offers as $offer) {
$this->applyOffer($cart, $offer);
}
// Consolidate cart items after applying offers
$this->consolidateCartItems($cart);
// Commit the transaction
DB::commit();
} catch (\Exception $e) {
// Rollback the transaction in case of any errors
DB::rollBack();
throw $e;
}
}
protected function removeInvalidDiscounts(Cart $cart)
{
// Load cart items to ensure we're working with the latest data
$cart->load('cartItems');
// Reset all items to their normal price and remove any discount flags
foreach ($cart->cartItems as $item) {
$item->price = $item->compare_at; // Assume compare_at holds the normal price
$item->total_price = $item->price * $item->quantity;
$item->is_discounted = false; // Reset the discount flag
$item->save();
}
// Update the cart's totals after resetting prices
$cart->updateTotals();
}
protected function applyOffer(Cart $cart, XyOffer $offer)
{
$buyItemIds = $offer->buy_items;
$getItemIds = $offer->get_items;
// Retrieve the 'buy' items from the cart
$buyItemsInCart = $cart->cartItems()->whereIn('shopify_product_id', $buyItemIds)->get();
$buyQuantity = $buyItemsInCart->sum('quantity');
// Calculate the number of offers that can be applied based on 'buy' quantity
$offerCount = intdiv($buyQuantity, $offer->buy_amount);
// If the offer is applicable
if ($offerCount > 0) {
// Check if the buy items and get items are the same
$sameItemForBuyAndGet = $buyItemIds == $getItemIds;
if ($sameItemForBuyAndGet) {
// Adjust the buyQuantity to exclude the quantities used to trigger the offer
$buyQuantity -= ($offerCount * $offer->buy_amount);
}
// Retrieve the 'get' items from the cart
$getItemsInCart = $cart->cartItems()->whereIn('shopify_product_id', $getItemIds)->get();
foreach ($getItemsInCart as $item) {
// Calculate the quantity eligible for discount
$eligibleForDiscount = $sameItemForBuyAndGet ? $buyQuantity : $item->quantity;
// Calculate how many items can be discounted considering the already discounted ones
$discountableQuantity = min($eligibleForDiscount, ($offerCount * $offer->get_amount));
if ($discountableQuantity > 0) {
// Apply discount to these items
$this->discountItem($item, $discountableQuantity, $offer);
if ($sameItemForBuyAndGet) {
// Reduce the available quantity for discounting for the next iteration
$buyQuantity -= $discountableQuantity;
}
}
// Calculate any remaining 'get' items that are not eligible for discount
$nonDiscountableQuantity = $item->quantity - $discountableQuantity;
// If there are such items
if ($nonDiscountableQuantity > 0) {
// Set the price for these items to their normal amount
$this->applyNormalPrice($item, $nonDiscountableQuantity);
}
}
}
// After applying all offers, update the cart's totals
$cart->updateTotals();
}
protected function applyNormalPrice(CartItem $item, $nonDiscountableQuantity)
{
// If there's a split item, we need to update the non-discounted item's quantity and price
if ($nonDiscountableQuantity > 0 && $item->is_discounted) {
// Create a new CartItem for the non-discounted quantity
$normalItem = $item->replicate();
$normalItem->quantity = $nonDiscountableQuantity;
$normalItem->is_discounted = false;
$normalItem->price = $normalItem->compare_at;
$normalItem->total_price = $normalItem->price * $nonDiscountableQuantity;
$normalItem->save();
}
}
protected function discountItem(CartItem $item, $quantityToDiscount, XyOffer $offer)
{
\Log::debug('Starts Discounting');
// If we are discounting fewer items than the item's quantity
if ($quantityToDiscount < $item->quantity) {
// Create a new CartItem for the non-discounted quantity
$nonDiscountedItem = $item->replicate();
$nonDiscountedItem->quantity = $item->quantity - $quantityToDiscount;
$nonDiscountedItem->is_discounted = false;
$nonDiscountedItem->price = $nonDiscountedItem->compare_at;
$nonDiscountedItem->total_price = $nonDiscountedItem->price * $nonDiscountedItem->quantity;
$nonDiscountedItem->save();
// Update the original item with the discounted quantity
$item->quantity = $quantityToDiscount;
}
// Apply the discount to the original item
$item->is_discounted = true;
if ($offer->discount_percentage == 100) {
$item->price = 0;
$item->total_price = 0;
} else {
$item->price *= (1 - $offer->discount_percentage / 100);
$item->total_price = $item->price * $item->quantity;
}
$item->save();
// After saving the discounted item, store its information in the session
$discountedItemInfo = [
'shopify_product_id' => $item->shopify_product_id,
'title' => $item->title,
'quantity' => $quantityToDiscount,
'message' => 'You have received a discount!', // Custom message
'in_use' => 1,
];
// Retrieve the existing array of discounted items from the session, or initialize a new one if it doesn't exist
$discountedItems = session('discounted_items', []);
$existingItemKey = array_search($item->shopify_product_id, array_column($discountedItems, 'shopify_product_id'));
if ($existingItemKey !== false && $discountedItems[$existingItemKey]['quantity'] === $quantityToDiscount) {
// Item exists in the session with the same quantity, do not re-add it
} else {
// Item does not exist or quantity has changed, add/update the item information in the session
$discountedItems[$existingItemKey] = $discountedItemInfo;
// Put the updated array back into the session
session(['discounted_items' => $discountedItems]);
}
// \Log::info('Discounted items in session:', session('discounted_items', []));
}
protected function consolidateCartItems(Cart $cart)
{
// Group items by shopify_variant_id and is_discounted flag
$groupedItems = $cart->cartItems()
->get()
->groupBy(function ($item) {
return $item->shopify_variant_id . '-' . $item->is_discounted;
});
foreach ($groupedItems as $groupKey => $items) {
// If there's more than one item in the group, consolidate them
if ($items->count() > 1) {
$firstItem = $items->shift(); // Keep the first item
// Sum quantities and total_prices, then delete the other items
$totalQuantity = $items->sum('quantity') + $firstItem->quantity;
$totalPrice = $items->sum('total_price') + $firstItem->total_price;
// Update the first item with the new total quantity and price
$firstItem->quantity = $totalQuantity;
$firstItem->total_price = $totalPrice;
$firstItem->save();
// Delete the other items
foreach ($items as $item) {
$item->delete();
}
}
}
// Update the cart's totals after consolidation
$cart->updateTotals();
}
}