PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.23
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.23
1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 All 49 releases
fluent-cart / app / Services / Coupon / Concerns / CanCalculateLineTotal.php

CanCalculateLineTotal.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.23, at app/Services/Coupon/Concerns/CanCalculateLineTotal.php

177 lines 5.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\Coupon\Concerns;
4
5 use FluentCart\Api\CurrencySettings;
6 use FluentCart\Api\Resource\FrontendResource\CartResource;
7 use FluentCart\App\App;
8 use FluentCart\App\Helpers\Helper;
9 use FluentCart\App\Models\Coupon;
10 use FluentCart\Framework\Support\Arr;
11 use FluentCart\Framework\Support\Collection;
12
13 trait CanCalculateLineTotal
14 {
15 protected array $discountData = [];
16
17 public function calculateLineItems()
18 {
19 $lineItems = $this->prepareLineItems($this->lineItems);
20
21 $this->discountData = [];
22 // $firstTime determines whether to use the original price or the discounted price as the current price for calculations
23
24 foreach ($this->applicableCoupons as $coupon) {
25 $validated = $this->validate($coupon->code);
26 if (is_wp_error($validated)) {
27 $this->couponErrors->put($coupon->code, $validated);
28 continue;
29 }
30
31 $this->applyCouponToAllItems($coupon, $lineItems);
32 $this->previouslyAppliedCouponCodes[] = $coupon->code;
33 $this->previouslyAppliedCoupons->put($coupon->code, $coupon);
34 }
35
36 $this->formatDiscountNumbers();
37 $this->calculatedLineItems = $lineItems;
38
39 }
40
41 protected function prepareLineItems(array $lineItems = []): array
42 {
43 foreach ($lineItems as $index => $lineItem) {
44 $lineItems[$index]['price'] = $lineItem['unit_price'];
45 $lineItems[$index]['discount_total'] = 0;
46 }
47
48 return $lineItems;
49 }
50
51 protected function formatDiscountNumbers()
52 {
53 foreach ($this->discountData as $code => $discount) {
54 $formattedTitle = $code;
55
56 if ($discount['type'] === 'percentage') {
57 $formattedTitle .= ' (' . $discount['actual_amount'] . '%)';
58 }
59
60 $this->discountData[$code]['formatted_discount'] = CurrencySettings::getPriceHtml($discount['discount']);
61 $this->discountData[$code]['actual_formatted_discount'] = CurrencySettings::getPriceHtml(
62 $discount['discount']
63 );
64 $this->discountData[$code]['formatted_title'] = $formattedTitle;
65
66 }
67 }
68
69 public function getDiscountData(): array
70 {
71 return $this->discountData;
72 }
73
74 private function applyCouponToAllItems(Coupon $coupon, array &$lineItems)
75 {
76 $this->ensureCouponExistInDiscountData($coupon);
77
78 $filteredItems = Collection::make($lineItems)->filter(function ($item) use ($coupon) {
79 return $this->isApplicableToProduct($coupon, $item['post_id'], $item['id']);
80 });
81
82 $totalNetPrice = $filteredItems->reduce(function ($carry, $item) {
83 return $carry + (($item['unit_price'] * $item['quantity']) - $item['discount_total']);
84 }, 0);
85
86 $applicableAmount = $this->calculateApplicableAmount($coupon, $totalNetPrice);
87
88 $lineItems = Collection::make($lineItems)->keyBy('id')->toArray();
89
90
91 $discountAmount = $this->distributeDiscount($filteredItems, $lineItems, $applicableAmount);
92
93 $this->discountData[$coupon->code]['discount'] += round($discountAmount, 2);
94 $this->discountData[$coupon->code]['unit_amount'] = round($discountAmount, 2);
95 $this->discountData[$coupon->code]['actual_quantity'] = $filteredItems->sum('quantity');
96
97 }
98
99 private function distributeDiscount(Collection $filteredItems, array &$lineItems, float &$remainingDiscount, int $depth = 0): float
100 {
101 $totalApplied = 0;
102 $totalNetPrice = $filteredItems->reduce(function ($carry, $item) use ($lineItems) {
103 $id = $item['id'];
104 $line = Arr::get($lineItems, $id);
105
106 if (empty($line)) {
107 return $carry;
108 }
109
110 return $carry + (($line['unit_price'] * $line['quantity']) - $line['discount_total']);
111 }, 0);
112
113 if ($totalNetPrice <= 0 || $remainingDiscount <= 0.01 || $depth > 10) {
114 return 0;
115 }
116
117 foreach ($filteredItems as $item) {
118 $id = $item['id'];
119 if (!isset($lineItems[$id])) continue;
120
121 $lineItem = &$lineItems[$id];
122 $itemNetTotal = ($lineItem['unit_price'] * $lineItem['quantity']) - $lineItem['discount_total'];
123 if ($itemNetTotal <= 0) {
124 continue;
125 }
126
127 $itemShare = ($itemNetTotal / $totalNetPrice) * $remainingDiscount;
128 $itemShare = round($itemShare, 2);
129
130 $applied = min($itemShare, $itemNetTotal, $remainingDiscount);
131 $lineItem['discount_total'] += $applied;
132 $remainingDiscount -= $applied;
133 $totalApplied += $applied;
134
135 if ($remainingDiscount <= 0.01) {
136 break;
137 }
138 }
139
140 // Recursive call if leftover remains and there's still room
141 if ($remainingDiscount > 0.01) {
142 $totalApplied += $this->distributeDiscount($filteredItems, $lineItems, $remainingDiscount, $depth + 1);
143 }
144
145 return $totalApplied;
146 }
147
148
149 public function calculateApplicableAmount(Coupon $coupon, $totalPrice)
150 {
151 $amount = $coupon->amount;
152
153 $cart = CartResource::get([
154 'hash' => App::request()->get(Helper::INSTANT_CHECKOUT_URL_PARAM)
155 ]);
156
157 if ($cart) {
158 $upgradeDiscount = Arr::get($cart, 'checkout_data.manual_discount.amount', 0);
159 $totalPrice = $totalPrice - $upgradeDiscount;
160 if ($totalPrice < 0) {
161 $totalPrice = 0;
162 }
163 }
164
165 if ($coupon->type === 'percentage') {
166 $amount = ($amount / 100) * $totalPrice;
167 }
168 $maxAmount = Arr::get($coupon->conditions, 'max_discount_amount', 0);
169 if (!empty($maxAmount) && is_numeric($maxAmount)) {
170 return min($totalPrice, $amount, $maxAmount);
171 }
172
173 return min($totalPrice, $amount);
174 }
175
176 }
177