PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.0
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 trunk All 48 releases
fluent-cart / api / Resource / CouponResource.php

CouponResource.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.0, at api/Resource/CouponResource.php

381 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\Api\Resource;
4
5 use FluentCart\App\Helpers\Helper;
6 use FluentCart\App\Models\Coupon;
7 use FluentCart\App\Services\Coupon\CouponServiceAdmin;
8 use FluentCart\App\Services\DateTime\DateTime;
9 use FluentCart\Framework\Database\Orm\Builder;
10 use FluentCart\Framework\Support\Arr;
11 use FluentCart\Framework\Support\Collection;
12 use WP_Error;
13
14 /**
15 * Class CouponResource
16 *
17 * @package FluentCart\Api\Resource
18 */
19 class CouponResource extends BaseResourceApi
20 {
21
22 /**
23 * Get the query builder instance for the Coupons model.
24 *
25 * @return Builder
26 */
27
28 public static function getQuery(): Builder
29 {
30 return Coupon::query();
31 }
32
33 public static function get(array $params = [])
34 {
35 $coupons = static::getQuery()
36 ->withCount([
37 'appliedCoupons as total_items' => function ($query) {
38 $query->selectRaw('count(*)');
39 }
40 ])
41 ->orderBy(
42 sanitize_sql_orderby(Arr::get($params, 'order_by', 'id')),
43 sanitize_sql_orderby(Arr::get($params, 'order_type', 'DESC'))
44 )
45 ->paginate(Arr::get($params, 'per_page', 15), ['*'], 'page', Arr::get($params, 'page'));
46
47 return [
48 'data' => $coupons,
49 ];
50 }
51
52 public static function find($id, $params = [])
53 {
54 $type = Arr::get($params, 'type', '');
55
56 if (!empty($type) && $type === 'byCode') {
57 $code = Arr::get($params, 'code', null);
58 $coupon = static::getQuery()->whereRaw("BINARY `code` = ?", [$code])->first();
59 return $coupon;
60 }
61
62 return static::getQuery()->find($id);
63 }
64
65 public static function viewDetails($id)
66 {
67 $coupon = static::getQuery()->with(['activities.user'])->find($id);
68
69 if (!empty($coupon->end_date) && $coupon->end_date !== '0000-00-00 00:00:00') {
70 $endDate = DateTime::anyTimeToGmt($coupon->end_date);
71 $now = new DateTime();
72
73 if ($endDate < $now && $coupon->getStatus() !== 'expired') {
74 $coupon->setStatus('expired');
75 $coupon->save();
76 }
77 }
78 return $coupon;
79
80 }
81
82
83 public static function create($data, $params = [])
84 {
85 // New coupons default the spend basis to subtotal (the documented behavior) when a client
86 // omits it. Only applies on create — there is no prior behavior to preserve for a new row.
87 if (!is_array(Arr::get($data, 'conditions'))) {
88 $data['conditions'] = [];
89 }
90 if (empty($data['conditions']['min_amount_basis'])) {
91 $data['conditions']['min_amount_basis'] = 'subtotal';
92 }
93
94 $data = self::formatAmount($data);
95 $isCreated = static::getQuery()->create($data);
96
97 if ($isCreated) {
98 return static::makeSuccessResponse(
99 $isCreated,
100 __('Coupon created successfully!', 'fluent-cart')
101 );
102 }
103
104 return static::makeErrorResponse([
105 ['code' => 400, 'message' => __('Coupon creation failed.', 'fluent-cart')]
106 ]);
107 }
108
109 public static function listCoupon()
110 {
111 return static::getQuery()
112 ->where('status', 'active')
113 ->pluck('code')
114 ->toArray();
115 }
116
117 public static function update($data, $id, $params = [])
118 {
119 if (Arr::get($data, 'max_uses', null) == '') {
120 $data['max_uses'] = null;
121 }
122 if (Arr::get($data, 'max_per_customer', null) == '') {
123 $data['max_per_customer'] = null;
124 }
125 if (Arr::get($data, 'max_discount_amount', null) == '') {
126 $data['max_discount_amount'] = null;
127 }
128 if (Arr::get($data, 'min_purchase_amount', null) == '') {
129 $data['min_purchase_amount'] = null;
130 }
131
132 if (!$id) {
133 return static::makeErrorResponse([
134 ['code' => 403, 'message' => __('Please edit a valid coupon!', 'fluent-cart')]
135 ]);
136 }
137
138 $hasCoupon = self::find($id);
139
140 if (!$hasCoupon) {
141 return static::makeErrorResponse([
142 ['code' => 404, 'message' => __('Coupon not found, please reload the page and try again!', 'fluent-cart')]
143 ]);
144 }
145
146 // If the client sends conditions without a valid spend basis (e.g. an older REST client that
147 // predates the field), keep whatever is already stored so an unrelated edit never silently
148 // flips the coupon's spend basis. Legacy coupons with no stored basis stay absent and fall
149 // back to 'total' at validation.
150 if (is_array(Arr::get($data, 'conditions')) && empty($data['conditions']['min_amount_basis'])) {
151 $existingBasis = Arr::get($hasCoupon->conditions, 'min_amount_basis');
152 if (!empty($existingBasis)) {
153 $data['conditions']['min_amount_basis'] = $existingBasis;
154 }
155 }
156
157 $data = self::formatAmount($data);
158
159 $isUpdated = $hasCoupon->update($data);
160
161 if ($isUpdated) {
162 return static::makeSuccessResponse($hasCoupon, __('Coupon updated successfully!', 'fluent-cart'));
163 }
164
165 return static::makeErrorResponse([
166 ['code' => 400, 'message' => __('Coupon update failed.', 'fluent-cart')]
167 ]);
168 }
169
170 public static function delete($id, $params = [])
171 {
172 if (!$id) {
173 return static::makeErrorResponse([
174 ['code' => 403, 'message' => __('Please use a valid coupon ID!', 'fluent-cart')]
175 ]);
176 }
177
178 $hasCoupon = self::find($id);
179
180 if ($hasCoupon) {
181 if ($hasCoupon->delete()) {
182 return static::makeSuccessResponse('', __('Coupon successfully deleted.', 'fluent-cart'));
183 }
184
185 return static::makeErrorResponse([
186 ['code' => 400, 'message' => __('Coupon deletion failed!', 'fluent-cart')]
187 ]);
188 }
189
190 return static::makeErrorResponse([
191 ['code' => 404, 'message' => __('Coupon not found in database, failed to remove.', 'fluent-cart')]
192 ]);
193 }
194
195 private static function formatAmount($data)
196 {
197 if (empty(Arr::get($data, 'conditions.max_discount_amount', ''))) {
198 $data['max_discount_amount'] = null;
199 }
200 if (Arr::get($data, 'type') !== 'percentage') {
201 $data['amount'] = Helper::toCent(Arr::get($data, 'amount', 0));
202 }
203 if (!empty(Arr::get($data, 'conditions.max_discount_amount'))) {
204 $data['conditions']['max_discount_amount'] = Helper::toCent($data['conditions']['max_discount_amount']);
205 }
206 $data['conditions']['min_purchase_amount'] = Helper::toCent(Arr::get($data['conditions'], 'min_purchase_amount', 0));
207 return $data;
208 }
209
210 public static function applyCoupon($data, $returnCouponService = true)
211 {
212 $couponCode = Arr::get($data, 'coupon_code');
213 $lineItems = Arr::except(Arr::get($data, 'order_items', []), ['*']);
214 $orderId = Arr::get($data, 'order_uuid', null);
215 $getAppliedCouponLists = Arr::get($data, 'applied_coupons', []);
216 $previouslyAppliedCouponCodes = new Collection();
217 if (!empty($orderId)) {
218 $order = OrderResource::find($orderId, ['with' => 'appliedCoupons']);
219 $previouslyAppliedCouponCodes = $order->appliedCoupons;
220 $previouslyAppliedCouponCodes = static::makeCouponFromAppliedCoupons($previouslyAppliedCouponCodes);
221 }
222 $appliedKeys = $previouslyAppliedCouponCodes->pluck('id')->toArray();
223 $getAppliedCouponLists = array_diff($getAppliedCouponLists, $appliedKeys);
224
225 if (!empty($getAppliedCouponLists)) {
226 $getAppliedCouponLists = Coupon::query()->whereIn('id', $getAppliedCouponLists)->get()->keyBy('code')->toArray();
227 $previouslyAppliedCouponCodes = $previouslyAppliedCouponCodes->merge($getAppliedCouponLists);
228 }
229
230 $couponService = new CouponServiceAdmin($lineItems, null, $previouslyAppliedCouponCodes->keys()->toArray());
231 $couponApplied = $couponService->applyCoupon($couponCode);
232 if (is_wp_error($couponApplied)) {
233 return $couponApplied;
234 }
235
236 $calculated_items = $couponService->getCalculatedLineItems();
237
238 $applied_coupons = $couponService->getDiscountData();
239
240 $errors = $couponService->getCouponErrors();
241
242
243 if($errors->has($couponCode)){
244 return static::makeErrorResponse([
245 ['code' => 400, 'message' => $errors->get($couponCode)->get_error_message()]
246 ]);
247 }
248
249 if ($returnCouponService) {
250 return [
251 'applied_coupons' => $applied_coupons,
252 'calculated_items' => $calculated_items,
253 ];
254 }
255
256 return new WP_Error(__('Something went wrong while updating the cart.', 'fluent-cart'));
257 }
258
259 public static function cancelCoupon($data, $returnCouponService = true)
260 {
261 $couponId = Arr::get($data, 'id', null);
262 $couponCode = Arr::get($data, 'coupon_code', null);
263 $lineItems = Arr::except(Arr::get($data, 'order_items', []), ['*']);
264 $orderId = Arr::get($data, 'order_uuid', null);
265 $getAppliedCouponLists = Arr::get($data, 'applied_coupons', []);
266 $previouslyAppliedCouponCodes = new Collection();
267
268 if (!empty($orderId)) {
269 $order = OrderResource::find($orderId, ['with' => ['order_items', 'appliedCoupons']]);
270 if (!empty($couponId)) {
271 $order->appliedCoupons()->where('id', $couponId)->delete();
272 Coupon::query()->where('code', $couponCode)->decrement('use_count', 1);
273 }
274 }
275
276
277 if (!empty($getAppliedCouponLists)) {
278 $getAppliedCouponLists = Coupon::query()->whereIn('id', $getAppliedCouponLists)->get()->keyBy('code')->toArray();
279 $previouslyAppliedCouponCodes = Collection::make($getAppliedCouponLists);
280 }
281
282
283 $couponService = new CouponServiceAdmin($lineItems, null, $previouslyAppliedCouponCodes->keys()->toArray());
284
285 $isCancelled = $couponService->cancelCoupon($couponCode);
286
287 if (is_wp_error($isCancelled)) {
288 return $isCancelled;
289 }
290
291
292 $calculated_items = $couponService->getCalculatedLineItems();
293
294 $applied_coupons = $couponService->getDiscountData();
295
296 if ($returnCouponService) {
297 return [
298 'applied_coupons' => $applied_coupons,
299 'calculated_items' => $calculated_items,
300 ];
301 }
302
303 return new WP_Error(__('Something went wrong while updating the cart.', 'fluent-cart'));
304 }
305
306 public static function reapplyCoupon($data, $returnCouponService = true)
307 {
308 $lineItems = Arr::except(Arr::get($data, 'order_items', []), ['*']);
309 $orderId = Arr::get($data, 'order_uuid', null);
310 $appliedCouponIds = Arr::get($data, 'applied_coupons', []);
311 $previouslyAppliedCouponCodes = null;
312
313 if (!empty($orderId)) {
314 $order = OrderResource::find($orderId, ['with' => 'appliedCoupons']);
315 if (empty($lineItems)) {
316 $order->appliedCoupons()->delete();
317 } else {
318 $previouslyAppliedCouponCodes = $order->appliedCoupons;
319 }
320 }
321
322 // When the request carries coupon IDs (current UI state, may include unsaved coupons),
323 // load them directly so freshly applied coupons survive item adjustments before save.
324 if (!empty($appliedCouponIds)) {
325 $couponCodes = Coupon::query()->whereIn('id', $appliedCouponIds)->pluck('code')->toArray();
326 $couponService = new CouponServiceAdmin($lineItems, null, $couponCodes);
327 } else {
328 $previouslyAppliedCouponCodes = static::makeCouponFromAppliedCoupons($previouslyAppliedCouponCodes);
329 $couponService = new CouponServiceAdmin($lineItems, $previouslyAppliedCouponCodes);
330 }
331
332 $couponService->reapplyCoupons();
333
334 $calculated_items = $couponService->getCalculatedLineItems();
335
336 $applied_coupons = $couponService->getDiscountData();
337
338 if ($returnCouponService) {
339 return [
340 'applied_coupons' => $applied_coupons,
341 'calculated_items' => $calculated_items,
342 ];
343 }
344
345 return new WP_Error(__('Something went wrong while updating the cart.', 'fluent-cart'));
346 }
347
348 private static function makeCouponFromAppliedCoupons($previouslyAppliedCouponCodes)
349 {
350 if (empty($previouslyAppliedCouponCodes)) {
351 return new Collection();
352 }
353
354 return $previouslyAppliedCouponCodes->mapWithKeys(function ($coupon) {
355 return [
356 $coupon['code'] => [
357 'id' => Arr::get($coupon, 'coupon_id', ''),
358 'title' => $coupon['title'],
359 'priority' => $coupon['priority'] ?? 1,
360 'code' => $coupon['code'],
361 'amount' => $coupon['amount'],
362 'type' => 'fixed',
363 'stackable' => 'yes',
364 'conditions' => [
365 'min_purchase_amount' => 0,
366 'max_purchase_amount' => 0,
367 'included_products' => [],
368 'excluded_products' => [],
369 'included_categories' => [],
370 'excluded_categories' => [],
371 'allowed_user_ids' => [],
372 'allowed_roles' => [],
373 'max_uses' => 0,
374 'max_per_customer' => 0,
375 ]
376 ]
377 ];
378 });
379 }
380 }
381