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 / CouponServiceAdmin.php

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

232 lines 7.9 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;
4
5 use FluentCart\App\Models\Coupon;
6 use FluentCart\App\Models\Product;
7 use FluentCart\App\Services\Coupon\Concerns\CanCalculateLineTotal;
8 use FluentCart\App\Services\Coupon\Concerns\CanValidateCoupon;
9 use FluentCart\Framework\Support\Arr;
10 use FluentCart\Framework\Support\Collection;
11 use WP_Error;
12
13 /*
14 * Class CouponService was removed as all coupon logic goes to DiscountService.php
15 * we had to bring it back and make it as CouponServiceAdmin
16 *
17 * @devnotes: for admin order this is used to apply/cancel/reapply coupons.
18 * we may refactore this later
19 */
20
21 class CouponServiceAdmin
22 {
23 use CanValidateCoupon, CanCalculateLineTotal;
24
25 protected ?Collection $previouslyAppliedCoupons;
26 protected array $previouslyAppliedCouponCodes = [];
27 protected ?Collection $applicableCoupons;
28 protected ?Collection $appliedCoupons;
29 protected array $applicableCouponCodes = [];
30 protected array $cancelableCouponCodes = [];
31 protected array $productIds = [];
32 protected ?Collection $products;
33 protected array $lineItems = [];
34 protected array $calculatedLineItems = [];
35 protected bool $usingCart = true;
36 protected ?Collection $couponErrors = null;
37 protected string $customerEmail = '';
38
39
40 /**
41 * @param array $lineItems
42 * @param ?Collection $previouslyAppliedCoupons
43 * @param array $applicableCouponCodes
44 * @param string $customerEmail
45 */
46 public function __construct(array $lineItems = [], ?Collection $previouslyAppliedCoupons = null, $applicableCouponCodes = [], $customerEmail = '')
47 {
48 $this->customerEmail = (string) $customerEmail;
49 $this->appliedCoupons = $previouslyAppliedCoupons;
50 $this->couponErrors = new Collection();
51
52 //Set Data from the parameters
53 $this->lineItems = $lineItems;
54 $this->previouslyAppliedCoupons = $previouslyAppliedCoupons === null ? new Collection() : $previouslyAppliedCoupons->keyBy('code');
55
56 $this->previouslyAppliedCouponCodes = $this->previouslyAppliedCoupons->pluck('code')->toArray();
57 $this->applicableCouponCodes = array_merge($this->applicableCouponCodes, $applicableCouponCodes);
58 $this->init();
59 }
60
61 private function init()
62 {
63 // The admin UI submits subtotal as null for variation line items (the request
64 // guard deliberately preserves that null instead of floatval-ing it to 0), and
65 // the min/max purchase-amount gates in CanValidateCoupon sum these subtotals
66 // via OrderService::getItemsAmountTotal. An empty subtotal must therefore fall
67 // back to the item's real unit_price * quantity — the same contract
68 // OrderService::calculateItemTotal applies when the key is absent.
69 foreach ($this->lineItems as $index => $lineItem) {
70 if (!is_numeric(Arr::get($lineItem, 'subtotal'))) {
71 $this->lineItems[$index]['subtotal'] = intval(Arr::get($lineItem, 'unit_price', 0)) * max(1, intval(Arr::get($lineItem, 'quantity', 1)));
72 }
73 }
74
75 $this->calculatedLineItems = $this->lineItems;
76 $this->productIds = (new Collection($this->lineItems))
77 ->pluck('post_id')
78 ->toArray();
79
80 $this->productIds = array_unique($this->productIds);
81 $this->products = Product::query()
82 ->select('ID')
83 ->whereIn('ID', $this->productIds)
84 ->with('categories')
85 ->get()
86 ->keyBy('ID');
87 }
88
89
90 public function initializeCoupons(array $couponCodes = [])
91 {
92 $originalOrder = array_unique($this->applicableCouponCodes);
93
94 //Remove the cancelable code from the applicable coupon codes;
95 $this->applicableCouponCodes = array_merge(
96 $this->previouslyAppliedCouponCodes,
97 $this->applicableCouponCodes,
98 $couponCodes
99 );
100
101 $this->applicableCouponCodes = array_unique($this->applicableCouponCodes);
102
103 $orderMap = array_flip($originalOrder); // ['d' => 0, 'b' => 1, 'a' => 2]
104
105 usort($this->applicableCouponCodes, function ($a, $b) use ($orderMap) {
106 $indexA = $orderMap[$a] ?? PHP_INT_MAX;
107 $indexB = $orderMap[$b] ?? PHP_INT_MAX;
108 return $indexA <=> $indexB;
109 });
110
111 //Remove from the collection list if it should be canceled.
112 foreach ($this->cancelableCouponCodes as $couponCode) {
113 $this->applicableCouponCodes = array_diff($this->applicableCouponCodes, [$couponCode]);
114 }
115
116 if (empty($this->applicableCouponCodes)) {
117 $this->applicableCoupons = new Collection();
118 } else {
119 $this->applicableCoupons = $this->populateCouponsFromModel($this->applicableCouponCodes);
120 }
121
122
123 $sortedCoupons = new Collection();
124 foreach ($this->applicableCouponCodes as $couponCode) {
125 if ($this->applicableCoupons->has($couponCode)) {
126 $sortedCoupons->put($couponCode, $this->applicableCoupons->get($couponCode));
127 }
128 }
129
130 $this->applicableCoupons = $sortedCoupons;
131
132 $this->appliedCoupons = $this->appliedCoupons ?? new Collection();
133 $this->appliedCoupons = $this->appliedCoupons->keyBy('code');
134 $appliedCoupons = $this->appliedCoupons;
135
136 $this->applicableCoupons = $this->applicableCoupons->map(function ($coupon) use ($appliedCoupons) {
137 if ($appliedCoupons->has($coupon->code)) {
138 $data = $appliedCoupons->get($coupon->code);
139 $data['id'] = $coupon->id;
140
141 return $this->populateCouponFromArray($data);
142 }
143
144 return $coupon;
145 });
146
147 $this->applicableCouponCodes = $this->applicableCoupons->keys()->toArray();
148
149 }
150
151 public function getCouponErrors(): ?Collection
152 {
153 return $this->couponErrors;
154 }
155
156 protected function populateCouponFromArray($data): Coupon
157 {
158 $coupon = new Coupon();
159 $coupon->fill($data);
160 $coupon->id = $data['id'];
161 return $coupon;
162 }
163
164 protected function populateCouponsFromModel($couponCodes = [], $model = Coupon::class)
165 {
166 return $model::query()
167 ->whereIn('code', $couponCodes) // Lowercase the array for comparison
168 ->orderBy('priority')//Order by is important here
169 ->get()
170 ->keyBy('code');
171 }
172
173 public function applyCoupon(string $couponCode): ?WP_Error
174 {
175 return $this->apply([$couponCode]);
176 }
177
178 protected function apply(array $couponCodes = []): ?WP_Error
179 {
180 $this->initializeCoupons($couponCodes);
181
182 foreach ($couponCodes as $couponCode) {
183 if (!$this->applicableCoupons->has($couponCode)) {
184 $this->couponErrors->put($couponCode, $this->makeError(__('Coupon Not Found', 'fluent-cart'), 404));
185 }
186 }
187
188 $this->calculateLineItems();
189 return null;
190 }
191
192 public function getCalculatedLineItems(): array
193 {
194 return $this->calculatedLineItems;
195 }
196
197 public function cancelCoupon(string $couponCode)
198 {
199 $this->cancelableCouponCodes = array_merge(
200 $this->cancelableCouponCodes,
201 [$couponCode]
202 );
203
204 $this->initializeCoupons();
205 $this->calculateLineItems();
206 }
207
208 public function reapplyCoupons()
209 {
210 $this->initializeCoupons();
211 $this->calculateLineItems();
212 }
213
214 protected function ensureCouponExistInDiscountData(Coupon $coupon)
215 {
216 if (empty($this->discountData[$coupon->code])) {
217 $this->discountData[$coupon->code] = [
218 'id' => $coupon->id,
219 'title' => $coupon->title,
220 'amount' => 0,
221 'formatted_discount' => '',
222 'unit_amount' => 0,
223 'actual_quantity' => 0,
224 'discount' => 0,
225 'type' => $coupon->type,
226 'actual_amount' => $coupon->amount,
227 ];
228 }
229 }
230
231 }
232