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

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

614 lines 22.1 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
239 return $this->sendError([
240 'message' => __('Could not update payment method', 'fluent-cart')
241 ]);
242 }
243
244 public function getOrCreatePlan(Request $request, $subscription_uuid)
245 {
246 $errorResponse = $this->checkUserLoggedIn();
247 if ($errorResponse !== null) {
248 return $errorResponse;
249 }
250
251 $customer = CustomerResource::getCurrentCustomer();
252 if (!$customer) {
253 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
254 }
255
256 $data = $request->input('data');
257 $method = sanitize_text_field(Arr::get($data, 'method', ''));
258 $reason = sanitize_text_field(Arr::get($data, 'reason', ''));
259
260 if (!$method || !$subscription_uuid) {
261 return $this->sendError([
262 'message' => __('Missing required parameters', 'fluent-cart')
263 ]);
264 }
265
266 $subscription = Subscription::query()
267 ->where('uuid', $subscription_uuid)
268 ->where('customer_id', $customer->id)
269 ->first();
270 if (empty($subscription)) {
271 return $this->sendError([
272 'message' => __('Subscription not found', 'fluent-cart')
273 ]);
274 }
275
276 if (APP::gateway()->has($method)) {
277 try {
278 APP::gateway($method)->subscriptions->getOrCreateNewPlan($subscription->id, $reason);
279 } catch (\Exception $e) {
280 return $this->sendError([
281 'message' => $e->getMessage()
282 ]);
283 }
284 }
285
286 return $this->sendError([
287 'message' => __('Could not get or create plan', 'fluent-cart')
288 ]);
289 }
290
291 public function switchPaymentMethod(Request $request, $subscription_uuid)
292 {
293 $errorResponse = $this->checkUserLoggedIn();
294
295 if ($errorResponse !== null) {
296 return $errorResponse;
297 }
298
299 $data = $request->input('data');
300 $newPaymentMethod = sanitize_text_field(Arr::get($data, 'newPaymentMethod', ''));
301 $currentPaymentMethod = sanitize_text_field(Arr::get($data, 'currentPaymentMethod', ''));
302
303 $customer = CustomerResource::getCurrentCustomer();
304 if (!$customer) {
305 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
306 }
307
308 if (!$newPaymentMethod || !$currentPaymentMethod || !$subscription_uuid) {
309 return $this->sendError([
310 'message' => __('Missing required parameters', 'fluent-cart')
311 ]);
312 }
313
314 $subscription = Subscription::query()
315 ->where('uuid', $subscription_uuid)
316 ->where('customer_id', $customer->id)
317 ->first();
318 if (empty($subscription)) {
319 return $this->sendError([
320 'message' => __('Subscription not found', 'fluent-cart')
321 ]);
322 }
323
324 if (!$subscription->canSwitchPaymentMethod()) {
325 return $this->sendError([
326 'message' => __('This subscription cannot be moved to another payment gateway. Update the payment method on file instead.', 'fluent-cart')
327 ]);
328 }
329
330 if (App::gateway()->has($newPaymentMethod)) {
331 try {
332 App::gateway($newPaymentMethod)->subscriptions->switchPaymentMethod($data, $subscription->id);
333 } catch (\Exception $e) {
334 return $this->sendError([
335 'message' => $e->getMessage()
336 ]);
337 }
338 }
339
340 return $this->sendError([
341 'message' => __('Could not switch payment method', 'fluent-cart')
342 ]);
343
344 }
345
346 // needed in case of two step switch payment method
347 public function confirmSubscriptionSwitch(Request $request, $subscription_uuid)
348 {
349 $errorResponse = $this->checkUserLoggedIn();
350
351 if ($errorResponse !== null) {
352 return $errorResponse;
353 }
354
355 $data = $request->input('data');
356 $newVendorSubscriptionId = sanitize_text_field(Arr::get($data, 'newVendorSubscriptionId', ''));
357 $method = sanitize_text_field(Arr::get($data, 'method', ''));
358
359 $customer = CustomerResource::getCurrentCustomer();
360 if (!$customer) {
361 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
362 }
363
364 if (!$newVendorSubscriptionId || !$method || !$subscription_uuid) {
365 return $this->sendError([
366 'message' => __('Missing required parameters', 'fluent-cart')
367 ]);
368 }
369
370 $subscription = Subscription::query()
371 ->where('uuid', $subscription_uuid)
372 ->where('customer_id', $customer->id)
373 ->first();
374 if (empty($subscription)) {
375 return $this->sendError([
376 'message' => __('Subscription not found', 'fluent-cart')
377 ]);
378 }
379
380 if (!$subscription->canSwitchPaymentMethod()) {
381 return $this->sendError([
382 'message' => __('This subscription cannot be moved to another payment gateway. Update the payment method on file instead.', 'fluent-cart')
383 ]);
384 }
385
386 if (APP::gateway()->has($method)) {
387 try {
388 APP::gateway($method)->subscriptions->confirmSubscriptionSwitch($data, $subscription->id);
389 } catch (\Exception $e) {
390 return $this->sendError([
391 'message' => $e->getMessage()
392 ]);
393 }
394 }
395
396 return $this->sendError([
397 'message' => __('Could not confirm subscription switch', 'fluent-cart')
398 ]);
399
400 }
401
402 public function cancelAutoRenew(Request $request, $subscription_uuid)
403 {
404 $errorResponse = $this->checkUserLoggedIn();
405 if ($errorResponse !== null) {
406 return $errorResponse;
407 }
408
409 $customer = CustomerResource::getCurrentCustomer();
410 if (!$customer) {
411 return $this->sendError([
412 'message' => __('Customer not found', 'fluent-cart')
413 ]);
414 }
415
416 $subscription = Subscription::query()
417 ->where('uuid', $subscription_uuid)
418 ->where('customer_id', $customer->id)
419 ->first();
420
421 if (empty($subscription)) {
422 return $this->sendError([
423 'message' => __('Subscription not found', 'fluent-cart')
424 ]);
425 }
426
427 $result = $subscription->cancelRemoteSubscription([
428 'reason' => 'cancelled_by_customer',
429 'note' => __('Cancelled by Customer from customer portal', 'fluent-cart'),
430 ]);
431
432 if (is_wp_error($result)) {
433 return $this->sendError([
434 'message' => __('Unable to cancel subscription at this time. Please try again later or contact support.', 'fluent-cart')
435 ]);
436 }
437
438 return [
439 'message' => __('Your subscription has been successfully cancelled', 'fluent-cart')
440 ];
441
442 }
443
444 public function initiateEarlyPayment(Request $request, $subscription_uuid)
445 {
446 $errorResponse = $this->checkUserLoggedIn();
447 if ($errorResponse !== null) {
448 return $errorResponse;
449 }
450
451 $customer = CustomerResource::getCurrentCustomer();
452 if (!$customer) {
453 return $this->sendError([
454 'message' => __('Customer not found', 'fluent-cart')
455 ]);
456 }
457
458 $subscription = Subscription::query()
459 ->where('uuid', $subscription_uuid)
460 ->where('customer_id', $customer->id)
461 ->first();
462
463 if (!$subscription) {
464 return $this->sendError([
465 'message' => __('Subscription not found', 'fluent-cart')
466 ]);
467 }
468
469 if (!EarlyPaymentFeature::canPay($subscription)) {
470 return $this->sendError([
471 'message' => __('Early payment is not available for this subscription.', 'fluent-cart')
472 ]);
473 }
474
475 $url = add_query_arg([
476 'fluent-cart' => 'early-installment-payment',
477 'subscription_hash' => $subscription->uuid,
478 ], home_url('/'));
479
480 return $this->sendSuccess([
481 'message' => __('Early payment URL generated.', 'fluent-cart'),
482 'checkout_url' => $url,
483 ]);
484 }
485
486 private function canEarlyPay(Subscription $subscription): bool
487 {
488 return App::isProActive()
489 && $subscription->bill_times > 0
490 && $subscription->bill_count < $subscription->bill_times
491 && in_array($subscription->status, [Status::SUBSCRIPTION_ACTIVE, Status::SUBSCRIPTION_TRIALING]);
492 }
493
494 public function pauseSubscription(Request $request, $subscription_uuid)
495 {
496 $errorResponse = $this->checkUserLoggedIn();
497 if ($errorResponse !== null) {
498 return $errorResponse;
499 }
500
501 $customer = CustomerResource::getCurrentCustomer();
502 if (!$customer) {
503 return $this->sendError([
504 'message' => __('Customer not found', 'fluent-cart')
505 ]);
506 }
507
508 $subscription = Subscription::query()
509 ->where('uuid', $subscription_uuid)
510 ->where('customer_id', $customer->id)
511 ->first();
512
513 if (empty($subscription)) {
514 return $this->sendError([
515 'message' => __('Subscription not found', 'fluent-cart')
516 ]);
517 }
518
519 $reason = __('Paused by customer from customer portal', 'fluent-cart');
520 $result = $subscription->pauseSubscription($reason);
521
522 if (is_wp_error($result)) {
523 return $this->sendError([
524 'message' => $result->get_error_message()
525 ]);
526 }
527
528 return [
529 'message' => __('Your subscription has been successfully paused', 'fluent-cart')
530 ];
531 }
532
533 public function resumeSubscription(Request $request, $subscription_uuid)
534 {
535 $errorResponse = $this->checkUserLoggedIn();
536 if ($errorResponse !== null) {
537 return $errorResponse;
538 }
539
540 $customer = CustomerResource::getCurrentCustomer();
541 if (!$customer) {
542 return $this->sendError([
543 'message' => __('Customer not found', 'fluent-cart')
544 ]);
545 }
546
547 $subscription = Subscription::query()
548 ->where('uuid', $subscription_uuid)
549 ->where('customer_id', $customer->id)
550 ->first();
551
552 if (empty($subscription)) {
553 return $this->sendError([
554 'message' => __('Subscription not found', 'fluent-cart')
555 ]);
556 }
557
558 $reason = __('Resumed by customer from customer portal', 'fluent-cart');
559 $result = $subscription->resumeSubscription($reason);
560
561 if (is_wp_error($result)) {
562 return $this->sendError([
563 'message' => $result->get_error_message()
564 ]);
565 }
566
567 return [
568 'message' => __('Your subscription has been successfully resumed', 'fluent-cart')
569 ];
570 }
571
572 public function getSetupIntentRemainingAttempts($subscription_uuid)
573 {
574 $errorResponse = $this->checkUserLoggedIn();
575
576 if ($errorResponse !== null) {
577 return $errorResponse;
578 }
579
580 $customer = CustomerResource::getCurrentCustomer();
581 if (!$customer) {
582 return $this->sendError([
583 'message' => __('Customer not found', 'fluent-cart')
584 ]);
585 }
586
587 $subscription = Subscription::query()
588 ->where('uuid', $subscription_uuid)
589 ->where('customer_id', $customer->id)
590 ->first();
591
592 if (empty($subscription)) {
593 return $this->sendError([
594 'message' => __('Subscription not found', 'fluent-cart')
595 ]);
596 }
597
598 // Only return attempts if current payment method is stripe
599 if ($subscription->current_payment_method !== 'stripe' || empty($subscription->vendor_customer_id)) {
600 return $this->sendError([
601 'message' => __('Payment method not supported', 'fluent-cart')
602 ]);
603 }
604
605 $subscriptionsManager = new SubscriptionsManager();
606 $remaining = $subscriptionsManager->getRemainingRateLimit($subscription->vendor_customer_id);
607
608 return $this->sendSuccess([
609 'remaining' => max(0, $remaining)
610 ]);
611 }
612
613 }
614