PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
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 1.2.0 All 47 releases
fluent-cart / app / Http / Controllers / FrontendControllers / CustomerSubscriptionController.php

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

635 lines 22.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\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 'currency' => $subscription->currency,
141 'can_early_pay' => EarlyPaymentFeature::canPay($subscription),
142 'remaining_installments' => max(0, $subscription->bill_times - $subscription->bill_count),
143 'card_update_url' => $subscription->url,
144 // Auto-charged (system) subscriptions: the saved card is charged
145 // automatically on each renewal date — the portal says so, and surfaces
146 // the last failure so the customer knows to update the card.
147 'is_auto_charged' => $subscription->isSystem(),
148 'auto_charge_error' => Arr::get((array) $subscription->system_charge_state, 'last_error', ''),
149 ];
150
151
152 // Let's find all the transactions related to this order
153 $orderIds = array_filter([$subscription->order->id, $subscription->order->parent_id]);
154 if ($subscription->order->type === Status::ORDER_TYPE_SUBSCRIPTION) {
155 $renewalOrderIds = $subscription->order->renewals->pluck('id')->toArray();
156 $orderIds = array_merge($orderIds, $renewalOrderIds);
157 $orderIds = array_values(array_unique($orderIds));
158 }
159
160 $transactions = OrderTransaction::query()
161 ->whereIn('order_id', $orderIds)
162 ->with(['order'])
163 ->orderBy('id', 'DESC')
164 ->get();
165
166 $formattedData['transactions'] = $transactions->map(function ($transaction) {
167 $transformedTransaction = OrderService::transformTransaction($transaction);
168 if ($transaction->status === Status::TRANSACTION_PENDING
169 && !empty($transformedTransaction['custom_checkout_url'])
170 ) {
171 $transformedTransaction['show_pay_now'] = true;
172 }
173 return $transformedTransaction;
174 });
175
176 $formattedData = apply_filters('fluent_cart/customer_portal/subscription_data', $formattedData, [
177 'subscription' => $subscription,
178 'customer' => $customer
179 ]);
180
181 $sectionParts = apply_filters('fluent_cart/customer/subscription_details_section_parts', [
182 'end_of_subscription' => ''
183 ], [
184 'subscription' => $subscription,
185 'formattedData' => $formattedData
186 ]);
187
188 $sectionParts = array_map('wp_kses_post', $sectionParts);
189
190 return $this->sendSuccess([
191 'message' => __('Success', 'fluent-cart'),
192 'subscription' => $formattedData,
193 'section_parts' => $sectionParts
194 ]);
195 }
196
197 public function updatePaymentMethod(Request $request, $subscription_uuid)
198 {
199 $errorResponse = $this->checkUserLoggedIn();
200 if ($errorResponse !== null) {
201 return $errorResponse;
202 }
203
204 $customer = CustomerResource::getCurrentCustomer();
205 if (!$customer) {
206 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
207 }
208
209 $data = $request->input('data');
210 $method = $data['method'];
211 $subscriptionUuid = $subscription_uuid;
212
213 if (!$method || !$subscriptionUuid) {
214 return $this->sendError([
215 'message' => __('Method and Subscription Id required.', 'fluent-cart')
216 ]);
217 }
218
219 $subscription = Subscription::query()
220 ->where('uuid', $subscriptionUuid)
221 ->where('customer_id', $customer->id)
222 ->first();
223
224 if (empty($subscription)) {
225 return $this->sendError([
226 'message' => __('Subscription not found', 'fluent-cart')
227 ]);
228 }
229
230 if (App::gateway()->has($method)) {
231 try {
232 App::gateway($method)->subscriptions->cardUpdate($data, $subscription->id);
233 } catch (\Exception $e) {
234 return $this->sendError([
235 'message' => $e->getMessage()
236 ]);
237 }
238
239 return $this->sendSuccess([
240 'status' => 'success',
241 'message' => __('Payment method updated successfully', 'fluent-cart')
242 ]);
243 }
244
245 return $this->sendError([
246 'message' => __('Could not update payment method', 'fluent-cart')
247 ]);
248 }
249
250 public function getOrCreatePlan(Request $request, $subscription_uuid)
251 {
252 $errorResponse = $this->checkUserLoggedIn();
253 if ($errorResponse !== null) {
254 return $errorResponse;
255 }
256
257 $customer = CustomerResource::getCurrentCustomer();
258 if (!$customer) {
259 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
260 }
261
262 $data = $request->input('data');
263 $method = sanitize_text_field(Arr::get($data, 'method', ''));
264 $reason = sanitize_text_field(Arr::get($data, 'reason', ''));
265
266 if (!$method || !$subscription_uuid) {
267 return $this->sendError([
268 'message' => __('Missing required parameters', 'fluent-cart')
269 ]);
270 }
271
272 $subscription = Subscription::query()
273 ->where('uuid', $subscription_uuid)
274 ->where('customer_id', $customer->id)
275 ->first();
276 if (empty($subscription)) {
277 return $this->sendError([
278 'message' => __('Subscription not found', 'fluent-cart')
279 ]);
280 }
281
282 if (APP::gateway()->has($method)) {
283 try {
284 APP::gateway($method)->subscriptions->getOrCreateNewPlan($subscription->id, $reason);
285 } catch (\Exception $e) {
286 return $this->sendError([
287 'message' => $e->getMessage()
288 ]);
289 }
290
291 return $this->sendSuccess([
292 'status' => 'success',
293 'message' => __('Plan created successfully', 'fluent-cart')
294 ]);
295 }
296
297 return $this->sendError([
298 'message' => __('Could not get or create plan', 'fluent-cart')
299 ]);
300 }
301
302 public function switchPaymentMethod(Request $request, $subscription_uuid)
303 {
304 $errorResponse = $this->checkUserLoggedIn();
305
306 if ($errorResponse !== null) {
307 return $errorResponse;
308 }
309
310 $data = $request->input('data');
311 $newPaymentMethod = sanitize_text_field(Arr::get($data, 'newPaymentMethod', ''));
312 $currentPaymentMethod = sanitize_text_field(Arr::get($data, 'currentPaymentMethod', ''));
313
314 $customer = CustomerResource::getCurrentCustomer();
315 if (!$customer) {
316 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
317 }
318
319 if (!$newPaymentMethod || !$currentPaymentMethod || !$subscription_uuid) {
320 return $this->sendError([
321 'message' => __('Missing required parameters', 'fluent-cart')
322 ]);
323 }
324
325 $subscription = Subscription::query()
326 ->where('uuid', $subscription_uuid)
327 ->where('customer_id', $customer->id)
328 ->first();
329 if (empty($subscription)) {
330 return $this->sendError([
331 'message' => __('Subscription not found', 'fluent-cart')
332 ]);
333 }
334
335 if (!$subscription->canSwitchPaymentMethod()) {
336 return $this->sendError([
337 'message' => __('This subscription cannot be moved to another payment gateway. Update the payment method on file instead.', 'fluent-cart')
338 ]);
339 }
340
341 if (App::gateway()->has($newPaymentMethod)) {
342 try {
343 App::gateway($newPaymentMethod)->subscriptions->switchPaymentMethod($data, $subscription->id);
344 } catch (\Exception $e) {
345 return $this->sendError([
346 'message' => $e->getMessage()
347 ]);
348 }
349
350 return $this->sendSuccess([
351 'status' => 'success',
352 'message' => __('Payment method switched successfully', 'fluent-cart')
353 ]);
354 }
355
356 return $this->sendError([
357 'message' => __('Could not switch payment method', 'fluent-cart')
358 ]);
359
360 }
361
362 // needed in case of two step switch payment method
363 public function confirmSubscriptionSwitch(Request $request, $subscription_uuid)
364 {
365 $errorResponse = $this->checkUserLoggedIn();
366
367 if ($errorResponse !== null) {
368 return $errorResponse;
369 }
370
371 $data = $request->input('data');
372 $newVendorSubscriptionId = sanitize_text_field(Arr::get($data, 'newVendorSubscriptionId', ''));
373 $method = sanitize_text_field(Arr::get($data, 'method', ''));
374
375 $customer = CustomerResource::getCurrentCustomer();
376 if (!$customer) {
377 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
378 }
379
380 if (!$newVendorSubscriptionId || !$method || !$subscription_uuid) {
381 return $this->sendError([
382 'message' => __('Missing required parameters', 'fluent-cart')
383 ]);
384 }
385
386 $subscription = Subscription::query()
387 ->where('uuid', $subscription_uuid)
388 ->where('customer_id', $customer->id)
389 ->first();
390 if (empty($subscription)) {
391 return $this->sendError([
392 'message' => __('Subscription not found', 'fluent-cart')
393 ]);
394 }
395
396 if (!$subscription->canSwitchPaymentMethod()) {
397 return $this->sendError([
398 'message' => __('This subscription cannot be moved to another payment gateway. Update the payment method on file instead.', 'fluent-cart')
399 ]);
400 }
401
402 if (APP::gateway()->has($method)) {
403 try {
404 APP::gateway($method)->subscriptions->confirmSubscriptionSwitch($data, $subscription->id);
405 } catch (\Exception $e) {
406 return $this->sendError([
407 'message' => $e->getMessage()
408 ]);
409 }
410
411 return $this->sendSuccess([
412 'status' => 'success',
413 'message' => __('Subscription switch confirmed successfully', 'fluent-cart')
414 ]);
415 }
416
417 return $this->sendError([
418 'message' => __('Could not confirm subscription switch', 'fluent-cart')
419 ]);
420
421 }
422
423 public function cancelAutoRenew(Request $request, $subscription_uuid)
424 {
425 $errorResponse = $this->checkUserLoggedIn();
426 if ($errorResponse !== null) {
427 return $errorResponse;
428 }
429
430 $customer = CustomerResource::getCurrentCustomer();
431 if (!$customer) {
432 return $this->sendError([
433 'message' => __('Customer not found', 'fluent-cart')
434 ]);
435 }
436
437 $subscription = Subscription::query()
438 ->where('uuid', $subscription_uuid)
439 ->where('customer_id', $customer->id)
440 ->first();
441
442 if (empty($subscription)) {
443 return $this->sendError([
444 'message' => __('Subscription not found', 'fluent-cart')
445 ]);
446 }
447
448 $result = $subscription->cancelRemoteSubscription([
449 'reason' => 'cancelled_by_customer',
450 'note' => __('Cancelled by Customer from customer portal', 'fluent-cart'),
451 ]);
452
453 if (is_wp_error($result)) {
454 return $this->sendError([
455 'message' => __('Unable to cancel subscription at this time. Please try again later or contact support.', 'fluent-cart')
456 ]);
457 }
458
459 return [
460 'message' => __('Your subscription has been successfully cancelled', 'fluent-cart')
461 ];
462
463 }
464
465 public function initiateEarlyPayment(Request $request, $subscription_uuid)
466 {
467 $errorResponse = $this->checkUserLoggedIn();
468 if ($errorResponse !== null) {
469 return $errorResponse;
470 }
471
472 $customer = CustomerResource::getCurrentCustomer();
473 if (!$customer) {
474 return $this->sendError([
475 'message' => __('Customer not found', 'fluent-cart')
476 ]);
477 }
478
479 $subscription = Subscription::query()
480 ->where('uuid', $subscription_uuid)
481 ->where('customer_id', $customer->id)
482 ->first();
483
484 if (!$subscription) {
485 return $this->sendError([
486 'message' => __('Subscription not found', 'fluent-cart')
487 ]);
488 }
489
490 if (!EarlyPaymentFeature::canPay($subscription)) {
491 return $this->sendError([
492 'message' => __('Early payment is not available for this subscription.', 'fluent-cart')
493 ]);
494 }
495
496 $url = add_query_arg([
497 'fluent-cart' => 'early-installment-payment',
498 'subscription_hash' => $subscription->uuid,
499 ], home_url('/'));
500
501 return $this->sendSuccess([
502 'message' => __('Early payment URL generated.', 'fluent-cart'),
503 'checkout_url' => $url,
504 ]);
505 }
506
507 private function canEarlyPay(Subscription $subscription): bool
508 {
509 return App::isProActive()
510 && $subscription->bill_times > 0
511 && $subscription->bill_count < $subscription->bill_times
512 && in_array($subscription->status, [Status::SUBSCRIPTION_ACTIVE, Status::SUBSCRIPTION_TRIALING]);
513 }
514
515 public function pauseSubscription(Request $request, $subscription_uuid)
516 {
517 $errorResponse = $this->checkUserLoggedIn();
518 if ($errorResponse !== null) {
519 return $errorResponse;
520 }
521
522 $customer = CustomerResource::getCurrentCustomer();
523 if (!$customer) {
524 return $this->sendError([
525 'message' => __('Customer not found', 'fluent-cart')
526 ]);
527 }
528
529 $subscription = Subscription::query()
530 ->where('uuid', $subscription_uuid)
531 ->where('customer_id', $customer->id)
532 ->first();
533
534 if (empty($subscription)) {
535 return $this->sendError([
536 'message' => __('Subscription not found', 'fluent-cart')
537 ]);
538 }
539
540 $reason = __('Paused by customer from customer portal', 'fluent-cart');
541 $result = $subscription->pauseSubscription($reason);
542
543 if (is_wp_error($result)) {
544 return $this->sendError([
545 'message' => $result->get_error_message()
546 ]);
547 }
548
549 return [
550 'message' => __('Your subscription has been successfully paused', 'fluent-cart')
551 ];
552 }
553
554 public function resumeSubscription(Request $request, $subscription_uuid)
555 {
556 $errorResponse = $this->checkUserLoggedIn();
557 if ($errorResponse !== null) {
558 return $errorResponse;
559 }
560
561 $customer = CustomerResource::getCurrentCustomer();
562 if (!$customer) {
563 return $this->sendError([
564 'message' => __('Customer not found', 'fluent-cart')
565 ]);
566 }
567
568 $subscription = Subscription::query()
569 ->where('uuid', $subscription_uuid)
570 ->where('customer_id', $customer->id)
571 ->first();
572
573 if (empty($subscription)) {
574 return $this->sendError([
575 'message' => __('Subscription not found', 'fluent-cart')
576 ]);
577 }
578
579 $reason = __('Resumed by customer from customer portal', 'fluent-cart');
580 $result = $subscription->resumeSubscription($reason);
581
582 if (is_wp_error($result)) {
583 return $this->sendError([
584 'message' => $result->get_error_message()
585 ]);
586 }
587
588 return [
589 'message' => __('Your subscription has been successfully resumed', 'fluent-cart')
590 ];
591 }
592
593 public function getSetupIntentRemainingAttempts($subscription_uuid)
594 {
595 $errorResponse = $this->checkUserLoggedIn();
596
597 if ($errorResponse !== null) {
598 return $errorResponse;
599 }
600
601 $customer = CustomerResource::getCurrentCustomer();
602 if (!$customer) {
603 return $this->sendError([
604 'message' => __('Customer not found', 'fluent-cart')
605 ]);
606 }
607
608 $subscription = Subscription::query()
609 ->where('uuid', $subscription_uuid)
610 ->where('customer_id', $customer->id)
611 ->first();
612
613 if (empty($subscription)) {
614 return $this->sendError([
615 'message' => __('Subscription not found', 'fluent-cart')
616 ]);
617 }
618
619 // Only return attempts if current payment method is stripe
620 if ($subscription->current_payment_method !== 'stripe' || empty($subscription->vendor_customer_id)) {
621 return $this->sendError([
622 'message' => __('Payment method not supported', 'fluent-cart')
623 ]);
624 }
625
626 $subscriptionsManager = new SubscriptionsManager();
627 $remaining = $subscriptionsManager->getRemainingRateLimit($subscription->vendor_customer_id);
628
629 return $this->sendSuccess([
630 'remaining' => max(0, $remaining)
631 ]);
632 }
633
634 }
635