PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.5.3
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.5.3
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.5.3, at app/Http/Controllers/FrontendControllers/CustomerSubscriptionController.php

510 lines 18.3 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 'reactivate_url' => $subscription->getReactivateUrl(),
124 // item_name resolves to "<product> - <attributes>" (or the raw name),
125 // so it carries the labeled combination on a single line.
126 'title' => $subscription->display_item_name,
127 'subtitle' => '',
128 'can_upgrade' => $subscription->canUpgrade(),
129 'can_switch_payment_method' => $subscription->canSwitchPaymentMethod(),
130 'switchable_payment_methods' => $subscription->switchablePaymentMethods(),
131 'can_update_payment_method' => $subscription->canUpdatePaymentMethod(),
132 'order' => [
133 'uuid' => $subscription->order ? $subscription->order->uuid : ''
134 ],
135 'billing_addresses' => $subscription->billing_addresses,
136 'recurring_amount' => $subscription->recurring_amount,
137 'can_early_pay' => EarlyPaymentFeature::canPay($subscription),
138 'remaining_installments' => max(0, $subscription->bill_times - $subscription->bill_count),
139 'card_update_url' => $subscription->url,
140 ];
141
142
143 // Let's find all the transactions related to this order
144 $orderIds = array_filter([$subscription->order->id, $subscription->order->parent_id]);
145 if ($subscription->order->type === Status::ORDER_TYPE_SUBSCRIPTION) {
146 $renewalOrderIds = $subscription->order->renewals->pluck('id')->toArray();
147 $orderIds = array_merge($orderIds, $renewalOrderIds);
148 $orderIds = array_values(array_unique($orderIds));
149 }
150
151 $transactions = OrderTransaction::query()
152 ->whereIn('order_id', $orderIds)
153 ->with(['order'])
154 ->orderBy('id', 'DESC')
155 ->get();
156
157 $formattedData['transactions'] = $transactions->map(function ($transaction) {
158 return OrderService::transformTransaction($transaction);
159 });
160
161 $formattedData = apply_filters('fluent_cart/customer_portal/subscription_data', $formattedData, [
162 'subscription' => $subscription,
163 'customer' => $customer
164 ]);
165
166 $sectionParts = apply_filters('fluent_cart/customer/subscription_details_section_parts', [
167 'end_of_subscription' => ''
168 ], [
169 'subscription' => $subscription,
170 'formattedData' => $formattedData
171 ]);
172
173 $sectionParts = array_map('wp_kses_post', $sectionParts);
174
175 return $this->sendSuccess([
176 'message' => __('Success', 'fluent-cart'),
177 'subscription' => $formattedData,
178 'section_parts' => $sectionParts
179 ]);
180 }
181
182 public function updatePaymentMethod(Request $request, $subscription_uuid)
183 {
184 $errorResponse = $this->checkUserLoggedIn();
185 if ($errorResponse !== null) {
186 return $errorResponse;
187 }
188
189 $customer = CustomerResource::getCurrentCustomer();
190 if (!$customer) {
191 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
192 }
193
194 $data = $request->input('data');
195 $method = $data['method'];
196 $subscriptionUuid = $subscription_uuid;
197
198 if (!$method || !$subscriptionUuid) {
199 return $this->sendError([
200 'message' => __('Method and Subscription Id required.', 'fluent-cart')
201 ]);
202 }
203
204 $subscription = Subscription::query()
205 ->where('uuid', $subscriptionUuid)
206 ->where('customer_id', $customer->id)
207 ->first();
208
209 if (empty($subscription)) {
210 return $this->sendError([
211 'message' => __('Subscription not found', 'fluent-cart')
212 ]);
213 }
214
215 if (App::gateway()->has($method)) {
216 try {
217 App::gateway($method)->subscriptions->cardUpdate($data, $subscription->id);
218 } catch (\Exception $e) {
219 return $this->sendError([
220 'message' => $e->getMessage()
221 ]);
222 }
223 }
224
225 return $this->sendError([
226 'message' => __('Could not update payment method', 'fluent-cart')
227 ]);
228 }
229
230 public function getOrCreatePlan(Request $request, $subscription_uuid)
231 {
232 $errorResponse = $this->checkUserLoggedIn();
233 if ($errorResponse !== null) {
234 return $errorResponse;
235 }
236
237 $customer = CustomerResource::getCurrentCustomer();
238 if (!$customer) {
239 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
240 }
241
242 $data = $request->input('data');
243 $method = sanitize_text_field(Arr::get($data, 'method', ''));
244 $reason = sanitize_text_field(Arr::get($data, 'reason', ''));
245
246 if (!$method || !$subscription_uuid) {
247 return $this->sendError([
248 'message' => __('Missing required parameters', 'fluent-cart')
249 ]);
250 }
251
252 $subscription = Subscription::query()
253 ->where('uuid', $subscription_uuid)
254 ->where('customer_id', $customer->id)
255 ->first();
256 if (empty($subscription)) {
257 return $this->sendError([
258 'message' => __('Subscription not found', 'fluent-cart')
259 ]);
260 }
261
262 if (APP::gateway()->has($method)) {
263 try {
264 APP::gateway($method)->subscriptions->getOrCreateNewPlan($subscription->id, $reason);
265 } catch (\Exception $e) {
266 return $this->sendError([
267 'message' => $e->getMessage()
268 ]);
269 }
270 }
271
272 return $this->sendError([
273 'message' => __('Could not get or create plan', 'fluent-cart')
274 ]);
275 }
276
277 public function switchPaymentMethod(Request $request, $subscription_uuid)
278 {
279 $errorResponse = $this->checkUserLoggedIn();
280
281 if ($errorResponse !== null) {
282 return $errorResponse;
283 }
284
285 $data = $request->input('data');
286 $newPaymentMethod = sanitize_text_field(Arr::get($data, 'newPaymentMethod', ''));
287 $currentPaymentMethod = sanitize_text_field(Arr::get($data, 'currentPaymentMethod', ''));
288
289 $customer = CustomerResource::getCurrentCustomer();
290 if (!$customer) {
291 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
292 }
293
294 if (!$newPaymentMethod || !$currentPaymentMethod || !$subscription_uuid) {
295 return $this->sendError([
296 'message' => __('Missing required parameters', 'fluent-cart')
297 ]);
298 }
299
300 $subscription = Subscription::query()
301 ->where('uuid', $subscription_uuid)
302 ->where('customer_id', $customer->id)
303 ->first();
304 if (empty($subscription)) {
305 return $this->sendError([
306 'message' => __('Subscription not found', 'fluent-cart')
307 ]);
308 }
309
310 if (App::gateway()->has($newPaymentMethod)) {
311 try {
312 App::gateway($newPaymentMethod)->subscriptions->switchPaymentMethod($data, $subscription->id);
313 } catch (\Exception $e) {
314 return $this->sendError([
315 'message' => $e->getMessage()
316 ]);
317 }
318 }
319
320 return $this->sendError([
321 'message' => __('Could not switch payment method', 'fluent-cart')
322 ]);
323
324 }
325
326 // needed in case of two step switch payment method
327 public function confirmSubscriptionSwitch(Request $request, $subscription_uuid)
328 {
329 $errorResponse = $this->checkUserLoggedIn();
330
331 if ($errorResponse !== null) {
332 return $errorResponse;
333 }
334
335 $data = $request->input('data');
336 $newVendorSubscriptionId = sanitize_text_field(Arr::get($data, 'newVendorSubscriptionId', ''));
337 $method = sanitize_text_field(Arr::get($data, 'method', ''));
338
339 $customer = CustomerResource::getCurrentCustomer();
340 if (!$customer) {
341 return $this->sendError(['message' => __('Customer not found', 'fluent-cart')]);
342 }
343
344 if (!$newVendorSubscriptionId || !$method || !$subscription_uuid) {
345 return $this->sendError([
346 'message' => __('Missing required parameters', 'fluent-cart')
347 ]);
348 }
349
350 $subscription = Subscription::query()
351 ->where('uuid', $subscription_uuid)
352 ->where('customer_id', $customer->id)
353 ->first();
354 if (empty($subscription)) {
355 return $this->sendError([
356 'message' => __('Subscription not found', 'fluent-cart')
357 ]);
358 }
359
360 if (APP::gateway()->has($method)) {
361 try {
362 APP::gateway($method)->subscriptions->confirmSubscriptionSwitch($data, $subscription->id);
363 } catch (\Exception $e) {
364 return $this->sendError([
365 'message' => $e->getMessage()
366 ]);
367 }
368 }
369
370 return $this->sendError([
371 'message' => __('Could not confirm subscription switch', 'fluent-cart')
372 ]);
373
374 }
375
376 public function cancelAutoRenew(Request $request, $subscription_uuid)
377 {
378 $errorResponse = $this->checkUserLoggedIn();
379 if ($errorResponse !== null) {
380 return $errorResponse;
381 }
382
383 $customer = CustomerResource::getCurrentCustomer();
384 if (!$customer) {
385 return $this->sendError([
386 'message' => __('Customer not found', 'fluent-cart')
387 ]);
388 }
389
390 $subscription = Subscription::query()
391 ->where('uuid', $subscription_uuid)
392 ->where('customer_id', $customer->id)
393 ->first();
394
395 if (empty($subscription)) {
396 return $this->sendError([
397 'message' => __('Subscription not found', 'fluent-cart')
398 ]);
399 }
400
401 $result = $subscription->cancelRemoteSubscription([
402 'reason' => 'cancelled_by_customer',
403 'note' => __('Cancelled by Customer from customer portal', 'fluent-cart'),
404 ]);
405
406 if (is_wp_error($result)) {
407 return $this->sendError([
408 'message' => __('Unable to cancel subscription at this time. Please try again later or contact support.', 'fluent-cart')
409 ]);
410 }
411
412 return [
413 'message' => __('Your subscription has been successfully cancelled', 'fluent-cart')
414 ];
415
416 }
417
418 public function initiateEarlyPayment(Request $request, $subscription_uuid)
419 {
420 $errorResponse = $this->checkUserLoggedIn();
421 if ($errorResponse !== null) {
422 return $errorResponse;
423 }
424
425 $customer = CustomerResource::getCurrentCustomer();
426 if (!$customer) {
427 return $this->sendError([
428 'message' => __('Customer not found', 'fluent-cart')
429 ]);
430 }
431
432 $subscription = Subscription::query()
433 ->where('uuid', $subscription_uuid)
434 ->where('customer_id', $customer->id)
435 ->first();
436
437 if (!$subscription) {
438 return $this->sendError([
439 'message' => __('Subscription not found', 'fluent-cart')
440 ]);
441 }
442
443 if (!EarlyPaymentFeature::canPay($subscription)) {
444 return $this->sendError([
445 'message' => __('Early payment is not available for this subscription.', 'fluent-cart')
446 ]);
447 }
448
449 $url = add_query_arg([
450 'fluent-cart' => 'early-installment-payment',
451 'subscription_hash' => $subscription->uuid,
452 ], home_url('/'));
453
454 return $this->sendSuccess([
455 'message' => __('Early payment URL generated.', 'fluent-cart'),
456 'checkout_url' => $url,
457 ]);
458 }
459
460 private function canEarlyPay(Subscription $subscription): bool
461 {
462 return App::isProActive()
463 && $subscription->bill_times > 0
464 && $subscription->bill_count < $subscription->bill_times
465 && in_array($subscription->status, [Status::SUBSCRIPTION_ACTIVE, Status::SUBSCRIPTION_TRIALING]);
466 }
467
468 public function getSetupIntentRemainingAttempts($subscription_uuid)
469 {
470 $errorResponse = $this->checkUserLoggedIn();
471
472 if ($errorResponse !== null) {
473 return $errorResponse;
474 }
475
476 $customer = CustomerResource::getCurrentCustomer();
477 if (!$customer) {
478 return $this->sendError([
479 'message' => __('Customer not found', 'fluent-cart')
480 ]);
481 }
482
483 $subscription = Subscription::query()
484 ->where('uuid', $subscription_uuid)
485 ->where('customer_id', $customer->id)
486 ->first();
487
488 if (empty($subscription)) {
489 return $this->sendError([
490 'message' => __('Subscription not found', 'fluent-cart')
491 ]);
492 }
493
494 // Only return attempts if current payment method is stripe
495 if ($subscription->current_payment_method !== 'stripe' || empty($subscription->vendor_customer_id)) {
496 return $this->sendError([
497 'message' => __('Payment method not supported', 'fluent-cart')
498 ]);
499 }
500
501 $subscriptionsManager = new SubscriptionsManager();
502 $remaining = $subscriptionsManager->getRemainingRateLimit($subscription->vendor_customer_id);
503
504 return $this->sendSuccess([
505 'remaining' => max(0, $remaining)
506 ]);
507 }
508
509 }
510