PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.5.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.5.4
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 / Modules / PaymentMethods / PayPalGateway / SubscriptionManager.php

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

389 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 $config = $subscription->config ?: [];
190
191 // update subscription in table
192 $data = array_filter([
193 'vendor_subscription_id' => $vendorSubscriptionId,
194 'vendor_plan_id' => $vendorPlanId,
195 'current_payment_method' => 'paypal',
196 'vendor_customer_id' => $vendorCustomerId,
197 'next_billing_date' => $nextBillingDate,
198 'status' => $this->getCorrectSubscriptionStatus(Arr::get($paypalSubscription, 'status')),
199 'config' => array_merge($config, ['is_trial_days_simulated' => 'yes'])
200 ]);
201
202 Subscription::query()->where('id', $subscriptionId)->update($data);
203
204 $billingInfo = PaymentHelper::parsePaymentMethodDetails('paypal', [
205 'email' => Arr::get($paypalSubscription, 'subscriber.email_address'),
206 'payer_id' => Arr::get($paypalSubscription, 'subscriber.payer_id'),
207 'name' => Arr::get($paypalSubscription, 'subscriber.name.given_name') . ' ' . Arr::get($paypalSubscription, 'subscriber.name.surname'),
208 'address' => Arr::get($paypalSubscription, 'subscriber.shipping_address.address')
209 ]);
210 // update subscription meta for active payment method
211
212 $subscription->updateMeta('active_payment_method', $billingInfo);
213
214 $gateway = App::gateway($oldPaymentMethod);
215 if ($gateway && $gateway->subscriptions) {
216 $gateway->subscriptions->cancel(
217 $oldVendorSubscriptionId,
218 [
219 'reason' => __('Subscription switched to PayPal', 'fluent-cart'),
220 'mode' => $order->mode
221 ]
222 );
223 }
224
225 // add or update subscription meta of old subscriptions
226 $paymentSource = SubscriptionMeta::query()
227 ->where('subscription_id', $subscription->id)
228 ->where('meta_key', 'active_payment_method')
229 ->first();
230
231 if ($paymentSource) {
232 $paymentSource = $paymentSource->meta_value;
233 }
234
235 $oldSubData = [
236 'payment_method' => $oldPaymentMethod,
237 'vendor_subscription_id' => $oldVendorSubscriptionId,
238 'vendor_customer_id' => $oldVendorCustomerId,
239 'vendor_plan_id' => $oldVendorPlanId,
240 'reason' => 'switch_payment_method',
241 'payment_source' => $paymentSource ?? '',
242 'canceled_at' => DateTime::gmtNow(),
243 ];
244
245 self::addOldSubscriptionMeta($subscription, $oldSubData);
246
247 wp_send_json([
248 'status' => 'success',
249 'message' => __('Subscription updated successfully', 'fluent-cart'),
250 'data' => $vendorSubscriptionId
251 ], 200);
252
253 }
254
255 /**
256 * Confirm subscription reactivation
257 *
258 * @param array $data | newVendorSubscriptionId (string) required
259 * @param int $subscriptionId
260 * @return void
261 */
262 public static function addOldSubscriptionMeta($subscription, $oldSubData)
263 {
264 $defaults = [
265 'payment_method' => '',
266 'vendor_subscription_id' => '',
267 'vendor_customer_id' => '',
268 'vendor_plan_id' => '',
269 'bill_count' => 0,
270 'payment_source' => '',
271 'canceled_at' => null,
272 'reason' => '',
273 'expire_at' => null,
274 ];
275 $oldSubscription = array_merge($defaults, $oldSubData);
276
277 // get if exists
278 $oldSubscriptions = SubscriptionMeta::query()
279 ->where('subscription_id', '=', $subscription->id)
280 ->where('meta_key', '=', 'old_subscriptions')
281 ->first();
282
283 if ($oldSubscriptions && $oldSubscriptions->meta_value) {
284 if (is_string($oldSubscriptions->meta_value)) {
285 $oldSubscriptions = $oldSubscriptions->meta_value;
286 }
287 $oldSubscriptions = (array)$oldSubscriptions ?: [];
288 $oldSubscriptions[] = $oldSubscription;
289 } else {
290 $oldSubscriptions = [$oldSubscription];
291 }
292
293 SubscriptionMeta::updateOrCreate([
294 'subscription_id' => $subscription->id,
295 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
296 'meta_key' => 'old_subscriptions'
297 ], [
298 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
299 'meta_value' => $oldSubscriptions
300 ]);
301 }
302
303 public function getSubscriptionItemForUpdate($subscriptionModel, $variation)
304 {
305 $trialDays = 0;
306
307 // trial days is the difference between the next billing date and the current date in days
308 $nextBillingDate = $subscriptionModel->next_billing_date;
309 $nextBillingTimestamp = strtotime($nextBillingDate);
310
311 if ($nextBillingTimestamp && $nextBillingTimestamp > time()) {
312 $trialDays = ceil(($nextBillingTimestamp - time()) / 86400);
313 }
314
315 $billCount = $subscriptionModel->calculateBillCount();
316
317
318 $trialDays = SubscriptionHelper::checkTrailDaysLoopHole($subscriptionModel, $trialDays);
319
320 // max trial days is 365
321 if ($trialDays > 365) {
322 $trialDays = 365;
323 }
324
325 $billTimes = Arr::get($subscriptionModel, 'bill_times', 0);
326 if ($billTimes && $billCount) {
327 $billTimes = $billTimes - $billCount;
328 } else {
329 $billTimes = 0;
330 }
331
332 $expireAt = null;
333 // expire at is the end date of the subscription, if bill times is 0 then it is null
334 if ($billTimes) {
335 $expireAt = SubscriptionHelper::getSubscriptionCancelAtTimeStamp($trialDays, $billTimes, $subscriptionModel->billing_interval);
336 }
337
338 $processedSubscriptionItem = [
339 'billing_interval' => Arr::get($subscriptionModel, 'billing_interval'),
340 'recurring_amount' => Arr::get($subscriptionModel, 'recurring_total'),
341 'line_total' => intval(Arr::get($subscriptionModel, 'recurring_total')),
342 'id' => Arr::get($subscriptionModel, 'variation_id'),
343 'trial_days' => $trialDays,
344 'product_id' => Arr::get($subscriptionModel, 'product_id'),
345 'parent_order_id' => Arr::get($subscriptionModel, 'parent_order_id'),
346 'item_name' => Arr::get($subscriptionModel, 'item_name'),
347 'expire_at' => $expireAt,
348 'bill_times' => $billTimes,
349 ];
350
351 if ($trialDays > 0) {
352 $processedSubscriptionItem['trial_end'] = $nextBillingTimestamp;
353 }
354
355 return $processedSubscriptionItem;
356
357 }
358
359 public function getCorrectSubscriptionStatus($status): string
360 {
361 $status = strtolower($status);
362 if ('active' == $status) {
363 $status = Status::SUBSCRIPTION_ACTIVE;
364 } else if ('trialing' == $status) {
365 $status = Status::SUBSCRIPTION_TRIALING;
366 } else if ('cancelled' == $status || 'canceled' == $status) {
367 $status = Status::SUBSCRIPTION_CANCELED;
368 } else if ('expired' == $status) {
369 $status = Status::SUBSCRIPTION_EXPIRED;
370 } else if ('paused' == $status) {
371 $status = Status::SUBSCRIPTION_PAUSED;
372 } else if ('expiring' == $status) {
373 $status = Status::SUBSCRIPTION_EXPIRING;
374 } else if ('suspended' == $status) {
375 $status = Status::SUBSCRIPTION_PAUSED;
376 }
377 return $status;
378 }
379
380 public function sendError($message, $code = 422): void
381 {
382 wp_send_json([
383 'status' => 'failed',
384 'message' => $message
385 ], $code);
386 }
387
388 }
389