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

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

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