PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.1
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.6.1, at app/Services/Coupon/Concerns/CanCalculateLineTotal.php

185 lines 6.2 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
38 foreach ($lineItems as $id => $lineItem) {
39 $lineItems[$id]['coupon_discount'] = (int) $lineItem['discount_total'];
40 }
41
42 $this->calculatedLineItems = $lineItems;
43
44 }
45
46 protected function prepareLineItems(array $lineItems = []): array
47 {
48 foreach ($lineItems as $index => $lineItem) {
49 $lineItems[$index]['price'] = $lineItem['unit_price'];
50 $lineItems[$index]['discount_total'] = 0;
51 }
52
53 return $lineItems;
54 }
55
56 protected function formatDiscountNumbers()
57 {
58 foreach ($this->discountData as $code => $discount) {
59 $formattedTitle = $code;
60
61 if ($discount['type'] === 'percentage') {
62 $formattedTitle .= ' (' . $discount['actual_amount'] . '%)';
63 }
64
65 $this->discountData[$code]['formatted_discount'] = CurrencySettings::getPriceHtml($discount['discount']);
66 $this->discountData[$code]['actual_formatted_discount'] = CurrencySettings::getPriceHtml(
67 $discount['discount']
68 );
69 $this->discountData[$code]['formatted_title'] = $formattedTitle;
70
71 }
72 }
73
74 public function getDiscountData(): array
75 {
76 return $this->discountData;
77 }
78
79 private function applyCouponToAllItems(Coupon $coupon, array &$lineItems)
80 {
81 $this->ensureCouponExistInDiscountData($coupon);
82
83 $filteredItems = Collection::make($lineItems)->filter(function ($item) use ($coupon) {
84 return $this->isApplicableToProduct($coupon, $item['post_id'], $item['id']);
85 });
86
87 $totalNetPrice = $filteredItems->reduce(function ($carry, $item) {
88 return $carry + (($item['unit_price'] * $item['quantity']) - $item['discount_total']);
89 }, 0);
90
91 $applicableAmount = $this->calculateApplicableAmount($coupon, $totalNetPrice);
92
93 $lineItems = Collection::make($lineItems)->keyBy('id')->toArray();
94
95
96 $discountAmount = $this->distributeDiscount($filteredItems, $lineItems, $applicableAmount);
97
98 $this->discountData[$coupon->code]['discount'] += round($discountAmount, 2);
99 $this->discountData[$coupon->code]['unit_amount'] = round($discountAmount, 2);
100 $this->discountData[$coupon->code]['actual_quantity'] = $filteredItems->sum('quantity');
101
102 }
103
104 private function distributeDiscount(Collection $filteredItems, array &$lineItems, float &$remainingDiscount, int $depth = 0): float
105 {
106 $totalApplied = 0;
107 $totalNetPrice = $filteredItems->reduce(function ($carry, $item) use ($lineItems) {
108 $id = $item['id'];
109 $line = Arr::get($lineItems, $id);
110
111 if (empty($line)) {
112 return $carry;
113 }
114
115 return $carry + (($line['unit_price'] * $line['quantity']) - $line['discount_total']);
116 }, 0);
117
118 if ($totalNetPrice <= 0 || $remainingDiscount <= 0.01 || $depth > 10) {
119 return 0;
120 }
121
122 foreach ($filteredItems as $item) {
123 $id = $item['id'];
124 if (!isset($lineItems[$id])) continue;
125
126 $lineItem = &$lineItems[$id];
127 $itemNetTotal = ($lineItem['unit_price'] * $lineItem['quantity']) - $lineItem['discount_total'];
128 if ($itemNetTotal <= 0) {
129 continue;
130 }
131
132 $itemShare = ($itemNetTotal / $totalNetPrice) * $remainingDiscount;
133 $itemShare = round($itemShare, 2);
134
135 $applied = min($itemShare, $itemNetTotal, $remainingDiscount);
136 $lineItem['discount_total'] += $applied;
137 $remainingDiscount -= $applied;
138 $totalApplied += $applied;
139
140 if ($remainingDiscount <= 0.01) {
141 break;
142 }
143 }
144
145 // Recursive call if leftover remains and there's still room
146 if ($remainingDiscount > 0.01) {
147 $totalApplied += $this->distributeDiscount($filteredItems, $lineItems, $remainingDiscount, $depth + 1);
148 }
149
150 return $totalApplied;
151 }
152
153
154 public function calculateApplicableAmount(Coupon $coupon, $totalPrice)
155 {
156 $amount = $coupon->amount;
157
158 $cart = CartResource::get([
159 'hash' => App::request()->get(Helper::INSTANT_CHECKOUT_URL_PARAM)
160 ]);
161
162 if ($cart) {
163 // Prorate credit is a post-tax adjustment to the payable total, not a
164 // discount, so it must NOT reduce the coupon-applicable base. Only a
165 // real manual discount reduces the base here.
166 $manualDiscount = Arr::get($cart, 'checkout_data.manual_discount.amount', 0);
167 $totalPrice = $totalPrice - $manualDiscount;
168 if ($totalPrice < 0) {
169 $totalPrice = 0;
170 }
171 }
172
173 if ($coupon->type === 'percentage') {
174 $amount = ($amount / 100) * $totalPrice;
175 }
176 $maxAmount = Arr::get($coupon->conditions, 'max_discount_amount', 0);
177 if (!empty($maxAmount) && is_numeric($maxAmount)) {
178 return min($totalPrice, $amount, $maxAmount);
179 }
180
181 return min($totalPrice, $amount);
182 }
183
184 }
185