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

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