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

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