PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.3
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.3
1.6.6 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 All 49 releases
fluent-cart / app / Modules / PaymentMethods / PayPalGateway / SubscriptionManager.php

SubscriptionManager.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.3, at app/Modules/PaymentMethods/PayPalGateway/SubscriptionManager.php

388 lines 14.6 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\Modules\PaymentMethods\PayPalGateway;
4
5 use FluentCart\App\App;
6 use FluentCart\App\Helpers\Status;
7 use FluentCart\App\Models\Order;
8 use FluentCart\App\Models\ProductVariation;
9 use FluentCart\App\Models\Subscription;
10 use FluentCart\App\Models\SubscriptionMeta;
11 use FluentCart\App\Modules\PaymentMethods\PayPalGateway\API\API;
12 use FluentCart\App\Services\DateTime\DateTime;
13 use FluentCart\App\Services\Payments\PaymentHelper;
14 use FluentCart\App\Services\Payments\SubscriptionHelper;
15 use FluentCart\Framework\Support\Arr;
16
17 class SubscriptionManager
18 {
19
20 /**
21 * @throws \Exception
22 */
23 public function pauseSubscription($data, $order, $subscription)
24 {
25 if (!current_user_can('manage_options')) {
26 throw new \Exception(esc_html__('Sorry, You do not have permission to pause subscription!', 'fluent-cart'));
27 }
28
29 $vendorSubscriptionId = Arr::get($data, 'vendor_subscription_id');
30 $reason = Arr::get($data, 'reason');
31
32 if (!$vendorSubscriptionId || !$subscription) {
33 throw new \Exception(esc_html__('Sorry, Subscription not found!', 'fluent-cart'));
34 }
35
36 try {
37 (new API())->makeRequest('billing/subscriptions/' . $vendorSubscriptionId . '/suspend', 'v1', 'POST', [
38 'reason' => $reason
39 ]);
40 } catch (\Exception $e) {
41 throw new \Exception(esc_html($e->getMessage()));
42 }
43
44 $subscription = Subscription::query()->where('id', $subscription->id)->first();
45 if ($subscription) {
46 $subscription->status = Status::SUBSCRIPTION_PAUSED;
47 $subscription->save();
48 }
49
50 wp_send_json(array(
51 'message' => __('Subscription has been paused successfully', 'fluent-cart')
52 ), 200);
53 }
54
55 /**
56 * @throws \Exception
57 */
58 public function resumeSubscription($data, $order, $subscription)
59 {
60 if (!current_user_can('manage_options')) {
61 throw new \Exception(esc_html__('Sorry, You do not have permission to resume subscription!', 'fluent-cart'));
62 }
63
64 $vendorSubscriptionId = Arr::get($data, 'vendor_subscription_id');
65 $reason = Arr::get($data, 'reason');
66
67 if (!$vendorSubscriptionId || !$subscription) {
68 throw new \Exception(esc_html__('Sorry, Subscription not found!', 'fluent-cart'));
69 }
70
71 $response = (new API())->makeRequest('billing/subscriptions/' . $vendorSubscriptionId . '/activate', 'v1', 'POST', [
72 'reason' => $reason ?? 'Customer requested'
73 ]);
74
75 if (is_wp_error($response)) {
76 throw new \Exception(esc_html($response->get_error_message()));
77 }
78
79 $subscription = Subscription::query()->where('id', $subscription->id)->first();
80 if ($subscription) {
81 $subscription->status = Status::SUBSCRIPTION_ACTIVE;
82 $subscription->save();
83 }
84
85 wp_send_json(array(
86 'message' => __('Subscription has been resumed successfully', 'fluent-cart')
87 ), 200);
88
89 }
90
91 /**
92 * @throws \Exception
93 */
94 public function getOrCreateNewPlan($subscriptionId, $reason)
95 {
96 if (!$subscriptionId) {
97 wp_send_json([
98 'message' => __('Sorry, Subscription ID is not available!', 'fluent-cart'),
99 ], 423);
100 }
101
102 $subscriptionModel = Subscription::query()->where('id', $subscriptionId)->first();
103 $order = Order::query()->where('id', $subscriptionModel->parent_order_id)->with('order_items')->first();
104 if (!$subscriptionModel || !$order) {
105 wp_send_json([
106 'message' => __('Sorry, Subscription or Order not found!', 'fluent-cart'),
107 ], 423);
108 }
109
110 // get the variation from variation id
111 $variation = ProductVariation::query()->findOrFail($subscriptionModel->variation_id);
112
113 $processedSubscriptionItem = $this->getSubscriptionItemForUpdate($subscriptionModel, $variation);
114
115 $data = wp_parse_args($processedSubscriptionItem, [
116 'order_id' => $subscriptionModel->parent_order_id,
117 'product_id' => $subscriptionModel->product_id,
118 'variation_id' => $subscriptionModel->variation_id,
119 'currency' => $subscriptionModel->order->currency,
120 'interval_count' => 1,
121 'signup_fee' => 0, // default setup fee in cents ($0.00)
122 ]);
123
124 $plan = PayPalHelper::getPayPalPlan($data);
125
126 if (is_wp_error($plan)) {
127 wp_send_json([
128 'status' => 'error',
129 'message' => $plan->get_error_message(),
130 ], 422);
131 }
132
133 wp_send_json([
134 'status' => 'success',
135 'message' => __('Plan created successfully', 'fluent-cart'),
136 'plan' => $plan,
137 ], 200);
138 }
139
140 public function confirmSubscriptionSwitch($data, $subscriptionId)
141 {
142 $newVendorSubscriptionId = Arr::get($data, 'newVendorSubscriptionId');
143 $vendorOrderId = Arr::get($data, 'vendorOrderId');
144
145 $subscription = Subscription::query()->where('id', $subscriptionId)->first();
146 $order = Order::query()->where('id', $subscription->parent_order_id)->first();
147
148 if (!$newVendorSubscriptionId || !$vendorOrderId) {
149 wp_send_json([
150 'status' => 'error',
151 'message' => __('Sorry, New Subscription ID or Vendor Order ID is not available!', 'fluent-cart'),
152 ], 422);
153 }
154
155 // get the subscription from paypal
156 $paypalSubscription = (new API())->verifySubscription($newVendorSubscriptionId);
157
158 if (is_wp_error($paypalSubscription)) {
159 // log that subscription was created but not found in paypal or connecting issue
160 fluent_cart_error_log(
161 __('Subscription was created but not found in paypal or connecting issue', 'fluent-cart'),
162 $paypalSubscription->get_error_message(),
163 [
164 'module_name' => 'Subscription',
165 'module_id' => $subscription->id,
166 'log_type' => 'api'
167 ]
168 );
169 wp_send_json([
170 'status' => 'error',
171 'message' => $paypalSubscription->get_error_message(),
172 ], 422);
173 }
174
175 $oldPaymentMethod = $subscription->current_payment_method;
176 $oldVendorSubscriptionId = $subscription->vendor_subscription_id;
177 $oldVendorCustomerId = $subscription->vendor_customer_id;
178 $oldVendorPlanId = $subscription->vendor_plan_id;
179
180 $vendorSubscriptionId = Arr::get($paypalSubscription, 'id');
181 $vendorPlanId = Arr::get($paypalSubscription, 'plan_id');
182 $vendorCustomerId = Arr::get($paypalSubscription, 'subscriber.payer_id');
183
184 $nextBillingDate = Arr::get($paypalSubscription, 'billing_info.next_billing_time') ?? null;
185 if (!empty($nextBillingDate)) {
186 $nextBillingDate = gmdate('Y-m-d H:i:s', strtotime($nextBillingDate));
187 }
188
189 // update subscription in table
190 $data = array_filter([
191 'vendor_subscription_id' => $vendorSubscriptionId,
192 'vendor_plan_id' => $vendorPlanId,
193 'current_payment_method' => 'paypal',
194 'vendor_customer_id' => $vendorCustomerId,
195 'next_billing_date' => $nextBillingDate,
196 'status' => $this->getCorrectSubscriptionStatus(Arr::get($paypalSubscription, 'status')),
197 ]);
198
199 Subscription::query()->where('id', $subscriptionId)->update($data);
200
201 $subscription->mergeConfig(['is_trial_days_simulated' => 'yes']);
202
203 $billingInfo = PaymentHelper::parsePaymentMethodDetails('paypal', [
204 'email' => Arr::get($paypalSubscription, 'subscriber.email_address'),
205 'payer_id' => Arr::get($paypalSubscription, 'subscriber.payer_id'),
206 'name' => Arr::get($paypalSubscription, 'subscriber.name.given_name') . ' ' . Arr::get($paypalSubscription, 'subscriber.name.surname'),
207 'address' => Arr::get($paypalSubscription, 'subscriber.shipping_address.address')
208 ]);
209 // update subscription meta for active payment method
210
211 $subscription->updateMeta('active_payment_method', $billingInfo);
212
213 $gateway = App::gateway($oldPaymentMethod);
214 if ($gateway && $gateway->subscriptions) {
215 $gateway->subscriptions->cancel(
216 $oldVendorSubscriptionId,
217 [
218 'reason' => __('Subscription switched to PayPal', 'fluent-cart'),
219 'mode' => $order->mode
220 ]
221 );
222 }
223
224 // add or update subscription meta of old subscriptions
225 $paymentSource = SubscriptionMeta::query()
226 ->where('subscription_id', $subscription->id)
227 ->where('meta_key', 'active_payment_method')
228 ->first();
229
230 if ($paymentSource) {
231 $paymentSource = $paymentSource->meta_value;
232 }
233
234 $oldSubData = [
235 'payment_method' => $oldPaymentMethod,
236 'vendor_subscription_id' => $oldVendorSubscriptionId,
237 'vendor_customer_id' => $oldVendorCustomerId,
238 'vendor_plan_id' => $oldVendorPlanId,
239 'reason' => 'switch_payment_method',
240 'payment_source' => $paymentSource ?? '',
241 'canceled_at' => DateTime::gmtNow(),
242 ];
243
244 self::addOldSubscriptionMeta($subscription, $oldSubData);
245
246 wp_send_json([
247 'status' => 'success',
248 'message' => __('Subscription updated successfully', 'fluent-cart'),
249 'data' => $vendorSubscriptionId
250 ], 200);
251
252 }
253
254 /**
255 * Confirm subscription reactivation
256 *
257 * @param array $data | newVendorSubscriptionId (string) required
258 * @param int $subscriptionId
259 * @return void
260 */
261 public static function addOldSubscriptionMeta($subscription, $oldSubData)
262 {
263 $defaults = [
264 'payment_method' => '',
265 'vendor_subscription_id' => '',
266 'vendor_customer_id' => '',
267 'vendor_plan_id' => '',
268 'bill_count' => 0,
269 'payment_source' => '',
270 'canceled_at' => null,
271 'reason' => '',
272 'expire_at' => null,
273 ];
274 $oldSubscription = array_merge($defaults, $oldSubData);
275
276 // get if exists
277 $oldSubscriptions = SubscriptionMeta::query()
278 ->where('subscription_id', '=', $subscription->id)
279 ->where('meta_key', '=', 'old_subscriptions')
280 ->first();
281
282 if ($oldSubscriptions && $oldSubscriptions->meta_value) {
283 if (is_string($oldSubscriptions->meta_value)) {
284 $oldSubscriptions = $oldSubscriptions->meta_value;
285 }
286 $oldSubscriptions = (array)$oldSubscriptions ?: [];
287 $oldSubscriptions[] = $oldSubscription;
288 } else {
289 $oldSubscriptions = [$oldSubscription];
290 }
291
292 SubscriptionMeta::updateOrCreate([
293 'subscription_id' => $subscription->id,
294 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
295 'meta_key' => 'old_subscriptions'
296 ], [
297 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
298 'meta_value' => $oldSubscriptions
299 ]);
300 }
301
302 public function getSubscriptionItemForUpdate($subscriptionModel, $variation)
303 {
304 $trialDays = 0;
305
306 // trial days is the difference between the next billing date and the current date in days
307 $nextBillingDate = $subscriptionModel->next_billing_date;
308 $nextBillingTimestamp = strtotime($nextBillingDate);
309
310 if ($nextBillingTimestamp && $nextBillingTimestamp > time()) {
311 $trialDays = ceil(($nextBillingTimestamp - time()) / 86400);
312 }
313
314 $billCount = $subscriptionModel->calculateBillCount();
315
316
317 $trialDays = SubscriptionHelper::checkTrailDaysLoopHole($subscriptionModel, $trialDays);
318
319 // max trial days is 365
320 if ($trialDays > 365) {
321 $trialDays = 365;
322 }
323
324 $billTimes = Arr::get($subscriptionModel, 'bill_times', 0);
325 if ($billTimes && $billCount) {
326 $billTimes = $billTimes - $billCount;
327 } else {
328 $billTimes = 0;
329 }
330
331 $expireAt = null;
332 // expire at is the end date of the subscription, if bill times is 0 then it is null
333 if ($billTimes) {
334 $expireAt = SubscriptionHelper::getSubscriptionCancelAtTimeStamp($trialDays, $billTimes, $subscriptionModel->billing_interval);
335 }
336
337 $processedSubscriptionItem = [
338 'billing_interval' => Arr::get($subscriptionModel, 'billing_interval'),
339 'recurring_amount' => Arr::get($subscriptionModel, 'recurring_total'),
340 'line_total' => intval(Arr::get($subscriptionModel, 'recurring_total')),
341 'id' => Arr::get($subscriptionModel, 'variation_id'),
342 'trial_days' => $trialDays,
343 'product_id' => Arr::get($subscriptionModel, 'product_id'),
344 'parent_order_id' => Arr::get($subscriptionModel, 'parent_order_id'),
345 'item_name' => Arr::get($subscriptionModel, 'item_name'),
346 'expire_at' => $expireAt,
347 'bill_times' => $billTimes,
348 ];
349
350 if ($trialDays > 0) {
351 $processedSubscriptionItem['trial_end'] = $nextBillingTimestamp;
352 }
353
354 return $processedSubscriptionItem;
355
356 }
357
358 public function getCorrectSubscriptionStatus($status): string
359 {
360 $status = strtolower($status);
361 if ('active' == $status) {
362 $status = Status::SUBSCRIPTION_ACTIVE;
363 } else if ('trialing' == $status) {
364 $status = Status::SUBSCRIPTION_TRIALING;
365 } else if ('cancelled' == $status || 'canceled' == $status) {
366 $status = Status::SUBSCRIPTION_CANCELED;
367 } else if ('expired' == $status) {
368 $status = Status::SUBSCRIPTION_EXPIRED;
369 } else if ('paused' == $status) {
370 $status = Status::SUBSCRIPTION_PAUSED;
371 } else if ('expiring' == $status) {
372 $status = Status::SUBSCRIPTION_EXPIRING;
373 } else if ('suspended' == $status) {
374 $status = Status::SUBSCRIPTION_PAUSED;
375 }
376 return $status;
377 }
378
379 public function sendError($message, $code = 422): void
380 {
381 wp_send_json([
382 'status' => 'failed',
383 'message' => $message
384 ], $code);
385 }
386
387 }
388