PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.19
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.19
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 / StripeGateway / SwitchCustomerMethod.php

SwitchCustomerMethod.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.19, at app/Modules/PaymentMethods/StripeGateway/SwitchCustomerMethod.php

311 lines 11.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\StripeGateway;
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\StripeGateway\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 SwitchCustomerMethod
19 {
20 private $subscriptions;
21
22 public function __construct()
23 {
24 $this->subscriptions = new SubscriptionsManager();
25 }
26
27 /**
28 * @throws \Exception
29 */
30 public function switchPayMethod($data, $subscriptionId)
31 {
32 if (!$this->validateRequest($data, $subscriptionId)) {
33 throw new \Exception('Invalid request');
34 }
35
36 $subscriptionModel = Subscription::query()->where('id', $subscriptionId)->first();
37 $currentPaymentMethod = sanitize_text_field(Arr::get($data, 'currentPaymentMethod'));
38 $pm = Arr::get($data, 'vendorPaymentMethod');
39 $verificationStatus = sanitize_text_field(Arr::get($data, 'verification_status', ''));
40 $customerId = sanitize_text_field(Arr::get($data, 'customer_id'));
41
42 if (!$customerId) {
43 // this is part of preventing duplicating customer
44 if ('stripe' === $subscriptionModel->current_payment_method) {
45 $customerId = $subscriptionModel->vendor_customer_id;
46 }
47 if (!$customerId) {
48 $customerId = $this->subscriptions->getOrCreateStripeCustomer($pm);
49 }
50 }
51
52 $paymentMethodId = Arr::get($pm, 'id');
53
54 if ('verify' === $verificationStatus) {
55 $this->subscriptions::verifyPaymentMethod($paymentMethodId, $customerId, true);
56 }
57
58 $this->attachPaymentMethodToCustomer($customerId, $paymentMethodId);
59
60 $order = Order::query()->where('id', $subscriptionModel->parent_order_id)->first();
61 $variation = ProductVariation::query()->findOrFail($subscriptionModel->variation_id);
62 $processedSubscriptionItem = $this->getSubscriptionItem($subscriptionModel, $variation);
63
64 $data = wp_parse_args($processedSubscriptionItem, [
65 'order_id' => $subscriptionModel->parent_order_id,
66 'product_id' => $subscriptionModel->product_id,
67 'variation_id' => $subscriptionModel->variation_id,
68 'billing_interval' => $subscriptionModel->billing_interval,
69 'recurring_total' => $subscriptionModel->recurring_total,
70 'currency' => $subscriptionModel->order->currency,
71 'trial_days' => (int)$subscriptionModel->trial_days,
72 'interval_count' => 1 // per month / year / week
73 ]);
74
75
76 $plan = Plan::getStripePricing($data);
77
78 if (is_wp_error($plan)) {
79 $this->sendError($plan->get_error_message());
80 }
81
82 $newSub = $this->createStripeSubscription($customerId, $paymentMethodId, $plan, $processedSubscriptionItem, $order);
83
84 if (is_wp_error($newSub)) {
85 $this->sendError($newSub->get_error_message());
86 }
87
88 $newSubStatus = StripeHelper::transformSubscriptionStatus($newSub);
89
90
91 if ($newSubStatus == 'incomplete') {
92 wp_send_json([
93 'status' => 'failed',
94 'message' => __('Could not switch payment method, please try again later', 'fluent-cart'),
95 'data' => Arr::get($newSub, 'id')
96 ], 200);
97 }
98
99 $oldVendorSubId = $subscriptionModel->vendor_subscription_id;
100 $oldVendorCusId = $subscriptionModel->vendor_customer_id;
101 $oldData = [
102 'vendor_subscription_id' => $subscriptionModel->vendor_subscription_id,
103 'vendor_customer_id' => $subscriptionModel->vendor_customer_id,
104 'vendor_plan_id' => $subscriptionModel->vendor_plan_id,
105 'old_payment_gateway' => $currentPaymentMethod,
106 'payment_source' => SubscriptionMeta::query()
107 ->where('subscription_id', $subscriptionModel->id)
108 ->where('meta_key', 'active_payment_method')
109 ->value('meta_value'),
110 'reason' => 'switch_payment_method',
111 'canceled_at' => DateTime::gmtNow(),
112 ];
113
114 $this->updateSubscription($subscriptionId, $newSub, $plan, $customerId);
115 $this->updateBillingInfo($subscriptionId, $pm);
116 $this->handleOldSubscription($oldData, $newSub, $subscriptionModel);
117
118 wp_send_json([
119 'status' => 'success',
120 'message' => __('Payment Method updated successfully', 'fluent-cart'),
121 'data' => Arr::get($newSub, 'id')
122 ], 200);
123 }
124
125 private function validateRequest($data, $subscriptionId): bool
126 {
127 $currentPaymentMethod = sanitize_text_field(Arr::get($data, 'currentPaymentMethod'));
128 if (!$currentPaymentMethod || !$subscriptionId) {
129 return false;
130 }
131 return true;
132 }
133
134 private function attachPaymentMethodToCustomer($customerId, $paymentMethodId)
135 {
136 // check if payment method is already attached
137 $existingPaymentMethods = (new API())->getStripeObject('customers/' . $customerId . '/payment_methods');
138
139 if (is_wp_error($existingPaymentMethods)) {
140 $this->sendError($existingPaymentMethods->get_error_message());
141 }
142
143 foreach ($existingPaymentMethods['data'] as $method) {
144 if (Arr::get($method, 'id') === $paymentMethodId) {
145 return;
146 }
147 }
148
149 $response = (new API())->createStripeObject('payment_methods/' . $paymentMethodId . '/attach', [
150 'customer' => $customerId
151 ]);
152
153 if (is_wp_error($response)) {
154 $this->sendError($response->get_error_message());
155 }
156 }
157
158
159 /**
160 * @throws \Exception
161 */
162 private function createStripeSubscription($customerId, $paymentMethodId, $plan, $processedSubscriptionItem, $order)
163 {
164 $stripeSubscriptionData = [
165 'customer' => $customerId,
166 'payment_behavior' => 'default_incomplete',
167 'payment_settings' => [
168 'save_default_payment_method' => 'on_subscription'
169 ],
170 'items' => [
171 [
172 'plan' => Arr::get($plan, 'id'),
173 'quantity' => 1,
174 ]
175 ],
176 'default_payment_method' => $paymentMethodId,
177 'expand' => [
178 'latest_invoice.confirmation_secret',
179 'pending_setup_intent'
180 ],
181 'metadata' => [
182 'fct_ref_id' => $order->uuid,
183 'email' => $order->customer->email,
184 'name' => $order->full_name,
185 'order_reference' => 'fct_order_id_' . $order->id,
186 ]
187 ];
188
189 if (!empty($processedSubscriptionItem['expire_at'])) {
190 // $stripeSubscriptionData['cancel_at'] = $processedSubscriptionItem['expire_at'];
191 }
192
193 if (!empty($processedSubscriptionItem['trial_end'])) {
194 $stripeSubscriptionData['trial_end'] = $processedSubscriptionItem['trial_end'];
195 }
196
197 $newSub = (new API())->createStripeObject('subscriptions', $stripeSubscriptionData);
198
199 if (is_wp_error($newSub)) {
200 throw new \Exception(esc_html($newSub->get_error_message()));
201 }
202
203 return $newSub;
204 }
205
206 private function updateSubscription($subscriptionId, $newSub, $plan, $customerId)
207 {
208 $subscriptionModel = Subscription::query()->where('id', $subscriptionId)->first();
209 $config = $subscriptionModel->config ?: [];
210
211 $subscriptionModel->update([
212 'vendor_subscription_id' => Arr::get($newSub, 'id'),
213 'vendor_plan_id' => Arr::get($newSub, 'id'),
214 'current_payment_method' => 'stripe',
215 'vendor_customer_id' => $customerId,
216 'status' => StripeHelper::transformSubscriptionStatus($newSub),
217 'config' => array_merge($config, [
218 'is_trial_days_simulated' => 'yes'
219 ])
220 ]);
221 }
222
223 private function updateBillingInfo($subscriptionId, $pm)
224 {
225 $billingInfo = PaymentHelper::parsePaymentMethodDetails('stripe', $pm);
226
227 SubscriptionMeta::updateOrCreate([
228 'subscription_id' => $subscriptionId,
229 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
230 'meta_key' => 'active_payment_method'
231 ], [
232 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
233 'meta_value' => json_encode($billingInfo)
234 ]);
235 }
236
237 private function handleOldSubscription($oldData, $newSub, $subscriptionModel)
238 {
239 SubscriptionsManager::addOldSubscriptionMeta($subscriptionModel->id, $oldData);
240
241 $gateway = App::gateway(Arr::get($oldData, 'old_payment_gateway'));
242 if ($gateway && $gateway->subscriptions) {
243 $gateway->subscriptions->cancel(Arr::get($oldData, 'vendor_subscription_id'), [
244 'mode' => $subscriptionModel->order->mode
245 ]);
246 }
247 }
248
249 public function getSubscriptionItem($subscription, $variation)
250 {
251 $trialDays = 0;
252 $nextBillingTimestamp = null;
253
254 // trial days is the difference between the next billing date and the current date in days
255 $nextBillingDate = Arr::get($subscription, 'next_billing_date');
256
257 if ($nextBillingDate) {
258 $now = DateTime::gmtNow()->getTimestamp();
259 $nextBillingTimestamp = DateTime::anyTimeToGmt($nextBillingDate)->getTimestamp();
260
261 if ($nextBillingTimestamp <= $now) {
262 $trialDays = 0;
263 } else {
264 $trialDays = ceil(($nextBillingTimestamp - $now) / 86400);
265 }
266 }
267
268 // there is loophole for getting 1 day trial and continue the subscription in loop, so we need to check for that
269 $trialDays = SubscriptionHelper::checkTrailDaysLoopHole($subscription, $trialDays);
270
271 $billTimes = Arr::get($subscription, 'bill_times');
272
273 $billCount = OrderTransaction::query()->where('subscription_id', $subscription->id)
274 ->where('transaction_type', Status::TRANSACTION_TYPE_CHARGE)
275 ->where('status', Status::TRANSACTION_SUCCEEDED)
276 ->count();
277
278 if ($billTimes && $billCount) {
279 $billTimes = $billTimes - $billCount;
280 } else {
281 $billTimes = 0;
282 }
283
284 $processedSubscriptionItem = [
285 'billing_interval' => Arr::get($subscription, 'billing_interval'),
286 'recurring_amount' => intval(Arr::get($subscription, 'recurring_amount')),
287 'trial_days' => $trialDays,
288 'bill_times' => $billTimes,
289 'product_id' => Arr::get($subscription, 'product_id'),
290 'parent_order_id' => Arr::get($subscription, 'parent_order_id'),
291 'item_name' => Arr::get($subscription, 'item_name'),
292 'expire_at' => null,
293 ];
294
295 if ($trialDays > 0) {
296 $processedSubscriptionItem['trial_end'] = $nextBillingTimestamp;
297 }
298
299 return $processedSubscriptionItem;
300 }
301
302 public function sendError($message, $code = 423)
303 {
304 wp_send_json([
305 'status' => 'failed',
306 'message' => $message
307 ], $code);
308 }
309
310 }
311