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.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 / app / Http / Controllers / FrontendControllers / CustomerSubscriptionController.php

CustomerSubscriptionController.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.1, at app/Http/Controllers/FrontendControllers/CustomerSubscriptionController.php

634 lines 22.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\Http\Controllers\FrontendControllers;
4
5 use FluentCart\Api\Resource\CustomerResource;
6 use FluentCart\App\App;
7 use FluentCart\App\Helpers\Status;
8 use FluentCart\App\Models\OrderTransaction;
9 use FluentCart\App\Models\Subscription;
10 use FluentCart\App\Modules\PaymentMethods\StripeGateway\SubscriptionsManager;
11 use FluentCart\App\Modules\Subscriptions\Services\EarlyPaymentFeature;
12 use FluentCart\App\Services\OrderService;
13 use FluentCart\Framework\Http\Request\Request;
14 use FluentCart\Framework\Support\Arr;
15
16
17 class CustomerSubscriptionController extends BaseFrontendController
18 {
19 public function getSubscriptions(Request $request): \WP_REST_Response
20 {
21 // Call the method and store the response
22 $errorResponse = $this->checkUserLoggedIn();
23
24 // Check if there is an error response and return it if exists
25 if ($errorResponse !== null) {
26 // Return error if user is not logged in
27 return $this->sendSuccess([
28 'message' => __('Success', 'fluent-cart'),
29 'subscriptions' => [
30 'data' => [],
31 'total' => 0,
32 'per_page' => 10,
33 'current_page' => 1,
34 'last_page' => 1
35 ]
36 ]);
37 }
38
39 $perPage = (int)$request->get('per_page', 10);
40 $page = (int)$request->get('page', 1);
41
42 // Get the current logged-in customer
43 $customer = CustomerResource::getCurrentCustomer();
44
45 if (!$customer) {
46 return $this->sendError([
47 'message' => __('Customer not found', 'fluent-cart')
48 ]);
49 }
50
51
52 // Query the Subscription model to retrieve subscriptions for the logged-in customer
53 // Include associated order and product data using 'with' for eager loading
54 // Sort the results by 'id' in descending order and fetch the data
55 $subscriptions = Subscription::query()
56 ->where('customer_id', $customer->id)
57 ->with(['product'])
58 ->whereNotIn('status', [Status::SUBSCRIPTION_PENDING, Status::SUBSCRIPTION_INTENDED])
59 ->orderBy('id', 'desc')
60 ->paginate($perPage, ['*'], 'page', $page);
61
62
63 $formattedSubscriptions = $subscriptions->map(function ($subscription) {
64 return OrderService::transformSubscription($subscription);
65 });
66
67 return $this->sendSuccess([
68 'message' => __('Success', 'fluent-cart'),
69 'subscriptions' => [
70 'data' => $formattedSubscriptions,
71 'total' => $subscriptions->total(),
72 'per_page' => $subscriptions->perPage(),
73 'current_page' => $subscriptions->currentPage(),
74 'last_page' => $subscriptions->lastPage()
75 ],
76 ]);
77 }
78
79 public function getSubscription($subscription_uuid): \WP_REST_Response
80 {
81 $errorResponse = $this->checkUserLoggedIn();
82
83 if ($errorResponse !== null) {
84 return $errorResponse;
85 }
86
87 // Get the current logged-in customer
88 $customer = CustomerResource::getCurrentCustomer();
89
90 if (!$customer) {
91 return $this->sendError([
92 'message' => __('Customer not found', 'fluent-cart')
93 ]);
94 }
95
96 $subscription = Subscription::query()
97 ->where('customer_id', $customer->id)
98 ->with(['product', 'variation', 'billing_addresses'])
99 ->where('uuid', $subscription_uuid)
100 ->first();
101
102 if (!$subscription || in_array($subscription->status, [Status::SUBSCRIPTION_PENDING, Status::SUBSCRIPTION_INTENDED])) {
103 return $this->sendError([
104 'message' => __('Subscription not found', 'fluent-cart')
105 ]);
106 }
107
108 $formattedData = [
109 'uuid' => $subscription->uuid,
110 'status' => $subscription->status,
111 'overridden_status' => $subscription->overridden_status,
112 'vendor_subscription_id' => $subscription->vendor_subscription_id,
113 'next_billing_date' => $subscription->next_billing_date,
114 'billing_info' => $subscription->billingInfo,
115 'current_payment_method' => $subscription->current_payment_method,
116 'payment_method' => $subscription->payment_method,
117 'payment_info' => $subscription->payment_info,
118 'bill_times' => $subscription->bill_times,
119 'bill_count' => $subscription->bill_count,
120 'variation_id' => $subscription->variation_id,
121 'product_id' => $subscription->product_id,
122 'config' => $subscription->config,
123 'collection_method' => $subscription->collection_method,
124 'reactivate_url' => $subscription->getReactivateUrl(),
125 // item_name resolves to "<product> - <attributes>" (or the raw name),
126 // so it carries the labeled combination on a single line.
127 'title' => $subscription->display_item_name,
128 'subtitle' => '',
129 'can_upgrade' => $subscription->canUpgrade(),
130 'can_switch_payment_method' => $subscription->canSwitchPaymentMethod(),
131 'switchable_payment_methods' => $subscription->switchablePaymentMethods(),
132 'can_update_payment_method' => $subscription->canUpdatePaymentMethod(),
133 'can_pause' => $subscription->canPause(),
134 'can_resume' => $subscription->canResume(),
135 'order' => [
136 'uuid' => $subscription->order ? $subscription->order->uuid : ''
137 ],
138 'billing_addresses' => $subscription->billing_addresses,
139 'recurring_amount' => $subscription->recurring_amount,
140 'can_early_pay' => EarlyPaymentFeature::canPay($subscription),
141 'remaining_installments' => max(0, $subscription->bill_times - $subscription->bill_count),
142 'card_update_url' => $subscription->url,
143 // Auto-charged (system) subscriptions: the saved card is charged
144 // automatically on each renewal date — the portal says so, and surfaces
145 // the last failure so the customer knows to update the card.
146 'is_auto_charged' => $subscription->isSystem(),
147 'auto_charge_error' => Arr::get((array) $subscription->system_charge_state, 'last_error', ''),
148 ];
149
150
151 // Let's find all the transactions related to this order
152 $orderIds = array_filter([$subscription->order->id, $subscription->order->parent_id]);
153 if ($subscription->order->type === Status::ORDER_TYPE_SUBSCRIPTION) {
154 $renewalOrderIds = $subscription->order->renewals->pluck('id')->toArray();
155 $orderIds = array_merge($orderIds, $renewalOrderIds);
156 $orderIds = array_values(array_unique($orderIds));
157 }
158
159 $transactions = OrderTransaction::query()
160 ->whereIn('order_id', $orderIds)
161 ->with(['order'])
162 ->orderBy('id', 'DESC')
163 ->get();
164
165 $formattedData['transactions'] = $transactions->map(function ($transaction) {
166 $transformedTransaction = OrderService::transformTransaction($transaction);
167 if ($transaction->status === Status::TRANSACTION_PENDING
168 && !empty($transformedTransaction['custom_checkout_url'])
169 ) {
170 $transformedTransaction['show_pay_now'] = true;
171 }
172 return $transformedTransaction;
173 });
174
175 $formattedData = apply_filters('fluent_cart/customer_portal/subscription_data', $formattedData, [
176 'subscription' => $subscription,
177 'customer' => $customer
178 ]);
179
180 $sectionParts = apply_filters('fluent_cart/customer/subscription_details_section_parts', [
181 'end_of_subscription' => ''
182 ], [
183 'subscription' => $subscription,
184 'formattedData' => $formattedData
185 ]);
186
187 $sectionParts = array_map('wp_kses_post', $sectionParts);
188
189 return $this->sendSuccess([
190 'message' => __('Success', 'fluent-cart'),
191 'subscription' => $formattedData,
192 'section_parts' => $sectionParts
193 ]);
194 }
195
196 public function updatePaymentMethod(Request $request, $subscription_uuid)
197 {
198 $errorResponse = $this->checkUserLoggedIn();
199 if ($errorResponse !== null) {
200 return $errorResponse;
201 }
202
203 $customer = CustomerResource::getCurrentCustomer();
204 if (!$customer) {
205 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
206 }
207
208 $data = $request->input('data');
209 $method = $data['method'];
210 $subscriptionUuid = $subscription_uuid;
211
212 if (!$method || !$subscriptionUuid) {
213 return $this->sendError([
214 'message' => __('Method and Subscription Id required.', 'fluent-cart')
215 ]);
216 }
217
218 $subscription = Subscription::query()
219 ->where('uuid', $subscriptionUuid)
220 ->where('customer_id', $customer->id)
221 ->first();
222
223 if (empty($subscription)) {
224 return $this->sendError([
225 'message' => __('Subscription not found', 'fluent-cart')
226 ]);
227 }
228
229 if (App::gateway()->has($method)) {
230 try {
231 App::gateway($method)->subscriptions->cardUpdate($data, $subscription->id);
232 } catch (\Exception $e) {
233 return $this->sendError([
234 'message' => $e->getMessage()
235 ]);
236 }
237
238 return $this->sendSuccess([
239 'status' => 'success',
240 'message' => __('Payment method updated successfully', 'fluent-cart')
241 ]);
242 }
243
244 return $this->sendError([
245 'message' => __('Could not update payment method', 'fluent-cart')
246 ]);
247 }
248
249 public function getOrCreatePlan(Request $request, $subscription_uuid)
250 {
251 $errorResponse = $this->checkUserLoggedIn();
252 if ($errorResponse !== null) {
253 return $errorResponse;
254 }
255
256 $customer = CustomerResource::getCurrentCustomer();
257 if (!$customer) {
258 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
259 }
260
261 $data = $request->input('data');
262 $method = sanitize_text_field(Arr::get($data, 'method', ''));
263 $reason = sanitize_text_field(Arr::get($data, 'reason', ''));
264
265 if (!$method || !$subscription_uuid) {
266 return $this->sendError([
267 'message' => __('Missing required parameters', 'fluent-cart')
268 ]);
269 }
270
271 $subscription = Subscription::query()
272 ->where('uuid', $subscription_uuid)
273 ->where('customer_id', $customer->id)
274 ->first();
275 if (empty($subscription)) {
276 return $this->sendError([
277 'message' => __('Subscription not found', 'fluent-cart')
278 ]);
279 }
280
281 if (APP::gateway()->has($method)) {
282 try {
283 APP::gateway($method)->subscriptions->getOrCreateNewPlan($subscription->id, $reason);
284 } catch (\Exception $e) {
285 return $this->sendError([
286 'message' => $e->getMessage()
287 ]);
288 }
289
290 return $this->sendSuccess([
291 'status' => 'success',
292 'message' => __('Plan created successfully', 'fluent-cart')
293 ]);
294 }
295
296 return $this->sendError([
297 'message' => __('Could not get or create plan', 'fluent-cart')
298 ]);
299 }
300
301 public function switchPaymentMethod(Request $request, $subscription_uuid)
302 {
303 $errorResponse = $this->checkUserLoggedIn();
304
305 if ($errorResponse !== null) {
306 return $errorResponse;
307 }
308
309 $data = $request->input('data');
310 $newPaymentMethod = sanitize_text_field(Arr::get($data, 'newPaymentMethod', ''));
311 $currentPaymentMethod = sanitize_text_field(Arr::get($data, 'currentPaymentMethod', ''));
312
313 $customer = CustomerResource::getCurrentCustomer();
314 if (!$customer) {
315 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
316 }
317
318 if (!$newPaymentMethod || !$currentPaymentMethod || !$subscription_uuid) {
319 return $this->sendError([
320 'message' => __('Missing required parameters', 'fluent-cart')
321 ]);
322 }
323
324 $subscription = Subscription::query()
325 ->where('uuid', $subscription_uuid)
326 ->where('customer_id', $customer->id)
327 ->first();
328 if (empty($subscription)) {
329 return $this->sendError([
330 'message' => __('Subscription not found', 'fluent-cart')
331 ]);
332 }
333
334 if (!$subscription->canSwitchPaymentMethod()) {
335 return $this->sendError([
336 'message' => __('This subscription cannot be moved to another payment gateway. Update the payment method on file instead.', 'fluent-cart')
337 ]);
338 }
339
340 if (App::gateway()->has($newPaymentMethod)) {
341 try {
342 App::gateway($newPaymentMethod)->subscriptions->switchPaymentMethod($data, $subscription->id);
343 } catch (\Exception $e) {
344 return $this->sendError([
345 'message' => $e->getMessage()
346 ]);
347 }
348
349 return $this->sendSuccess([
350 'status' => 'success',
351 'message' => __('Payment method switched successfully', 'fluent-cart')
352 ]);
353 }
354
355 return $this->sendError([
356 'message' => __('Could not switch payment method', 'fluent-cart')
357 ]);
358
359 }
360
361 // needed in case of two step switch payment method
362 public function confirmSubscriptionSwitch(Request $request, $subscription_uuid)
363 {
364 $errorResponse = $this->checkUserLoggedIn();
365
366 if ($errorResponse !== null) {
367 return $errorResponse;
368 }
369
370 $data = $request->input('data');
371 $newVendorSubscriptionId = sanitize_text_field(Arr::get($data, 'newVendorSubscriptionId', ''));
372 $method = sanitize_text_field(Arr::get($data, 'method', ''));
373
374 $customer = CustomerResource::getCurrentCustomer();
375 if (!$customer) {
376 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
377 }
378
379 if (!$newVendorSubscriptionId || !$method || !$subscription_uuid) {
380 return $this->sendError([
381 'message' => __('Missing required parameters', 'fluent-cart')
382 ]);
383 }
384
385 $subscription = Subscription::query()
386 ->where('uuid', $subscription_uuid)
387 ->where('customer_id', $customer->id)
388 ->first();
389 if (empty($subscription)) {
390 return $this->sendError([
391 'message' => __('Subscription not found', 'fluent-cart')
392 ]);
393 }
394
395 if (!$subscription->canSwitchPaymentMethod()) {
396 return $this->sendError([
397 'message' => __('This subscription cannot be moved to another payment gateway. Update the payment method on file instead.', 'fluent-cart')
398 ]);
399 }
400
401 if (APP::gateway()->has($method)) {
402 try {
403 APP::gateway($method)->subscriptions->confirmSubscriptionSwitch($data, $subscription->id);
404 } catch (\Exception $e) {
405 return $this->sendError([
406 'message' => $e->getMessage()
407 ]);
408 }
409
410 return $this->sendSuccess([
411 'status' => 'success',
412 'message' => __('Subscription switch confirmed successfully', 'fluent-cart')
413 ]);
414 }
415
416 return $this->sendError([
417 'message' => __('Could not confirm subscription switch', 'fluent-cart')
418 ]);
419
420 }
421
422 public function cancelAutoRenew(Request $request, $subscription_uuid)
423 {
424 $errorResponse = $this->checkUserLoggedIn();
425 if ($errorResponse !== null) {
426 return $errorResponse;
427 }
428
429 $customer = CustomerResource::getCurrentCustomer();
430 if (!$customer) {
431 return $this->sendError([
432 'message' => __('Customer not found', 'fluent-cart')
433 ]);
434 }
435
436 $subscription = Subscription::query()
437 ->where('uuid', $subscription_uuid)
438 ->where('customer_id', $customer->id)
439 ->first();
440
441 if (empty($subscription)) {
442 return $this->sendError([
443 'message' => __('Subscription not found', 'fluent-cart')
444 ]);
445 }
446
447 $result = $subscription->cancelRemoteSubscription([
448 'reason' => 'cancelled_by_customer',
449 'note' => __('Cancelled by Customer from customer portal', 'fluent-cart'),
450 ]);
451
452 if (is_wp_error($result)) {
453 return $this->sendError([
454 'message' => __('Unable to cancel subscription at this time. Please try again later or contact support.', 'fluent-cart')
455 ]);
456 }
457
458 return [
459 'message' => __('Your subscription has been successfully cancelled', 'fluent-cart')
460 ];
461
462 }
463
464 public function initiateEarlyPayment(Request $request, $subscription_uuid)
465 {
466 $errorResponse = $this->checkUserLoggedIn();
467 if ($errorResponse !== null) {
468 return $errorResponse;
469 }
470
471 $customer = CustomerResource::getCurrentCustomer();
472 if (!$customer) {
473 return $this->sendError([
474 'message' => __('Customer not found', 'fluent-cart')
475 ]);
476 }
477
478 $subscription = Subscription::query()
479 ->where('uuid', $subscription_uuid)
480 ->where('customer_id', $customer->id)
481 ->first();
482
483 if (!$subscription) {
484 return $this->sendError([
485 'message' => __('Subscription not found', 'fluent-cart')
486 ]);
487 }
488
489 if (!EarlyPaymentFeature::canPay($subscription)) {
490 return $this->sendError([
491 'message' => __('Early payment is not available for this subscription.', 'fluent-cart')
492 ]);
493 }
494
495 $url = add_query_arg([
496 'fluent-cart' => 'early-installment-payment',
497 'subscription_hash' => $subscription->uuid,
498 ], home_url('/'));
499
500 return $this->sendSuccess([
501 'message' => __('Early payment URL generated.', 'fluent-cart'),
502 'checkout_url' => $url,
503 ]);
504 }
505
506 private function canEarlyPay(Subscription $subscription): bool
507 {
508 return App::isProActive()
509 && $subscription->bill_times > 0
510 && $subscription->bill_count < $subscription->bill_times
511 && in_array($subscription->status, [Status::SUBSCRIPTION_ACTIVE, Status::SUBSCRIPTION_TRIALING]);
512 }
513
514 public function pauseSubscription(Request $request, $subscription_uuid)
515 {
516 $errorResponse = $this->checkUserLoggedIn();
517 if ($errorResponse !== null) {
518 return $errorResponse;
519 }
520
521 $customer = CustomerResource::getCurrentCustomer();
522 if (!$customer) {
523 return $this->sendError([
524 'message' => __('Customer not found', 'fluent-cart')
525 ]);
526 }
527
528 $subscription = Subscription::query()
529 ->where('uuid', $subscription_uuid)
530 ->where('customer_id', $customer->id)
531 ->first();
532
533 if (empty($subscription)) {
534 return $this->sendError([
535 'message' => __('Subscription not found', 'fluent-cart')
536 ]);
537 }
538
539 $reason = __('Paused by customer from customer portal', 'fluent-cart');
540 $result = $subscription->pauseSubscription($reason);
541
542 if (is_wp_error($result)) {
543 return $this->sendError([
544 'message' => $result->get_error_message()
545 ]);
546 }
547
548 return [
549 'message' => __('Your subscription has been successfully paused', 'fluent-cart')
550 ];
551 }
552
553 public function resumeSubscription(Request $request, $subscription_uuid)
554 {
555 $errorResponse = $this->checkUserLoggedIn();
556 if ($errorResponse !== null) {
557 return $errorResponse;
558 }
559
560 $customer = CustomerResource::getCurrentCustomer();
561 if (!$customer) {
562 return $this->sendError([
563 'message' => __('Customer not found', 'fluent-cart')
564 ]);
565 }
566
567 $subscription = Subscription::query()
568 ->where('uuid', $subscription_uuid)
569 ->where('customer_id', $customer->id)
570 ->first();
571
572 if (empty($subscription)) {
573 return $this->sendError([
574 'message' => __('Subscription not found', 'fluent-cart')
575 ]);
576 }
577
578 $reason = __('Resumed by customer from customer portal', 'fluent-cart');
579 $result = $subscription->resumeSubscription($reason);
580
581 if (is_wp_error($result)) {
582 return $this->sendError([
583 'message' => $result->get_error_message()
584 ]);
585 }
586
587 return [
588 'message' => __('Your subscription has been successfully resumed', 'fluent-cart')
589 ];
590 }
591
592 public function getSetupIntentRemainingAttempts($subscription_uuid)
593 {
594 $errorResponse = $this->checkUserLoggedIn();
595
596 if ($errorResponse !== null) {
597 return $errorResponse;
598 }
599
600 $customer = CustomerResource::getCurrentCustomer();
601 if (!$customer) {
602 return $this->sendError([
603 'message' => __('Customer not found', 'fluent-cart')
604 ]);
605 }
606
607 $subscription = Subscription::query()
608 ->where('uuid', $subscription_uuid)
609 ->where('customer_id', $customer->id)
610 ->first();
611
612 if (empty($subscription)) {
613 return $this->sendError([
614 'message' => __('Subscription not found', 'fluent-cart')
615 ]);
616 }
617
618 // Only return attempts if current payment method is stripe
619 if ($subscription->current_payment_method !== 'stripe' || empty($subscription->vendor_customer_id)) {
620 return $this->sendError([
621 'message' => __('Payment method not supported', 'fluent-cart')
622 ]);
623 }
624
625 $subscriptionsManager = new SubscriptionsManager();
626 $remaining = $subscriptionsManager->getRemainingRateLimit($subscription->vendor_customer_id);
627
628 return $this->sendSuccess([
629 'remaining' => max(0, $remaining)
630 ]);
631 }
632
633 }
634