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

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

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