PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.20
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.20
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 / Processor.php

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

548 lines 21.5 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\Helpers\CurrenciesHelper;
6 use FluentCart\App\Models\Cart;
7 use FluentCart\App\Modules\PaymentMethods\StripeGateway\API\API;
8 use FluentCart\App\Services\Payments\PaymentInstance;
9 use FluentCart\App\Helpers\Helper;
10 use FluentCart\Framework\Support\Arr;
11
12 class Processor
13 {
14
15 public function handleSubscription(PaymentInstance $paymentInstance, $paymentArgs)
16 {
17 $stripeSettings = new StripeSettingsBase();
18 $checkoutMode = $stripeSettings->get('checkout_mode') ?? 'onsite';
19
20 // If hosted mode, create Checkout Session for subscription
21 if ($checkoutMode === 'hosted') {
22 return $this->handleHostedSubscriptionCheckout($paymentInstance, $paymentArgs);
23 }
24
25 // Original onsite subscription flow
26 $orderType = $paymentInstance->order->type;
27 $fcCustomer = $paymentInstance->order->customer;
28 $billingAddress = $paymentInstance->order->billing_address;
29
30 $subscriptionModel = $paymentInstance->subscription;
31
32 if (!$subscriptionModel) {
33 return new \WP_Error('no_subscription', __('No subscription found.', 'fluent-cart'));
34 }
35
36 $stripeCustomer = StripeHelper::createOrGetStripeCustomer($paymentInstance->order->customer);
37
38 if (is_wp_error($stripeCustomer)) {
39 return $stripeCustomer;
40 }
41
42 $feeTotal = $orderType !== 'renewal' ? (int)$paymentInstance->order->fee_total : 0;
43 $initialAmount = (int)$subscriptionModel->signup_fee + $paymentInstance->getExtraAddonAmount() + $feeTotal;
44
45 if ($orderType == 'renewal') {
46 $stripePlan = Plan::getStripePricing([
47 'order_id' => $subscriptionModel->parent_order_id,
48 'product_id' => $subscriptionModel->product_id,
49 'variation_id' => $subscriptionModel->variation_id,
50 'billing_interval' => $subscriptionModel->billing_interval,
51 'recurring_total' => $subscriptionModel->getCurrentRenewalAmount(),
52 'currency' => $paymentInstance->order->currency,
53 'trial_days' => $subscriptionModel->getReactivationTrialDays(), // No trial for renewals
54 'interval_count' => 1 // per month / year / week
55 ]);
56
57 $initialAmount = 0;
58 } else {
59 $stripePlan = Plan::getStripePricing([
60 'order_id' => $subscriptionModel->parent_order_id,
61 'product_id' => $subscriptionModel->product_id,
62 'variation_id' => $subscriptionModel->variation_id,
63 'billing_interval' => $subscriptionModel->billing_interval,
64 'recurring_total' => $subscriptionModel->recurring_total,
65 'currency' => $paymentInstance->order->currency,
66 'trial_days' => (int)$subscriptionModel->trial_days,
67 'interval_count' => 1 // per month / year / week
68 ]);
69 }
70
71 if (is_wp_error($stripePlan)) {
72 return $stripePlan;
73 }
74
75 $stripeSubscriptionData = [
76 'customer' => Arr::get($stripeCustomer, 'id', ''),
77 'payment_behavior' => 'default_incomplete',
78 'payment_settings' => [
79 'save_default_payment_method' => 'on_subscription'
80 ],
81 'items' => [
82 [
83 'plan' => $stripePlan['id'],
84 'quantity' => $subscriptionModel->quantity ?: 1,
85 ]
86 ],
87 'expand' => [
88 'latest_invoice.confirmation_secret',
89 'pending_setup_intent'
90 ],
91 'metadata' => apply_filters('fluent_cart/payments/stripe_metadata_subscription', [
92 'fct_ref_id' => $paymentInstance->order->uuid,
93 'email' => $paymentInstance->order->customer->email,
94 'name' => $paymentInstance->order->full_name,
95 'subscription_item' => $subscriptionModel->item_name,
96 'order_reference' => 'fct_order_id_' . $paymentInstance->order->id,
97 ], [
98 'order' => $paymentInstance->order,
99 'transaction' => $paymentInstance->transaction,
100 'subscription' => $subscriptionModel
101 ]),
102 ];
103
104 if (Arr::get($stripePlan, 'trial_period_days')) {
105 $stripeSubscriptionData['trial_end'] = strtotime('+' . Arr::get($stripePlan, 'trial_period_days') . ' days');
106 }
107
108 // Maybe we have initial amount
109 if ($initialAmount) {
110 $addonPrice = Plan::getOneTimeAddonPrice([
111 'product_id' => $subscriptionModel->product_id,
112 'currency' => $paymentInstance->order->currency,
113 'amount' => (int)$initialAmount,
114 'variation_id' => $subscriptionModel->variation_id,
115 'order_id' => $subscriptionModel->parent_order_id,
116 ]);
117
118 if (is_wp_error($addonPrice)) {
119 return $addonPrice;
120 }
121
122 $stripeSubscriptionData['add_invoice_items'] = [
123 [
124 'price' => $addonPrice['id'],
125 'quantity' => 1
126 ]
127 ];
128 }
129
130 if ($expireAt = $paymentInstance->getSubscriptionCancelAtTimeStamp()) {
131 // $stripeSubscriptionData['cancel_at'] = $expireAt;
132 }
133
134 $stripeSubscription = (new API())->createStripeObject('subscriptions', $stripeSubscriptionData);
135
136 if (is_wp_error($stripeSubscription)) {
137 return $stripeSubscription;
138 }
139
140 $vendorChargeId = Arr::get($stripeSubscription, 'latest_invoice.payment_intent');
141 if (!$vendorChargeId) {
142 $vendorChargeId = Arr::get($stripeSubscription, 'pending_setup_intent.id');
143 }
144
145 if ($vendorChargeId) {
146 $paymentInstance->transaction->update(['vendor_charge_id' => $vendorChargeId]);
147 }
148
149 $vendorSubscriptionId = Arr::get($stripeSubscription, 'id');
150
151 $subscriptionModel->update([
152 'vendor_subscription_id' => $vendorSubscriptionId,
153 'vendor_customer_id' => $stripeSubscription['customer']
154 ]);
155
156 if ($stripeSubscription['pending_setup_intent'] != null) {
157 $paymentArgs['vendor_subscription_info'] = [
158 'type' => 'setup',
159 'clientSecret' => Arr::get($stripeSubscription, 'pending_setup_intent.client_secret')
160 ];
161 } else {
162 $paymentArgs['vendor_subscription_info'] = [
163 'type' => 'payment',
164 'clientSecret' => Arr::get($stripeSubscription, 'latest_invoice.confirmation_secret.client_secret')
165 ];
166 }
167
168 $customerData = [
169 'name' => $fcCustomer->first_name . ' ' . $fcCustomer->last_name,
170 'email' => $fcCustomer->email,
171 'address_1' => $billingAddress->address_1,
172 'address_2' => $billingAddress->address_2,
173 'city' => $billingAddress->city,
174 'state' => $billingAddress->state,
175 'postcode' => $billingAddress->postcode,
176 'country' => $billingAddress->country
177 ];
178
179 return [
180 'nextAction' => 'stripe',
181 'actionName' => 'custom',
182 'status' => 'success',
183 'message' => __('Order has been placed successfully', 'fluent-cart'),
184 'payment_args' => $paymentArgs,
185 'response' => $stripeSubscription,
186 'fc_customer' => $customerData
187 ];
188 }
189
190
191 /**
192 * Handle single payment for stripe (onsite or hosted)
193 *
194 * @return \WP_Error|array
195 */
196 public function handleSinglePayment(PaymentInstance $paymentInstance, $paymentArgs = [])
197 {
198 $stripeSettings = new StripeSettingsBase();
199 $checkoutMode = $stripeSettings->get('checkout_mode') ?? 'onsite';
200
201 if ($checkoutMode === 'hosted') {
202 return $this->handleHostedCheckout($paymentInstance, $paymentArgs);
203 }
204
205 // Original onsite payment flow
206 $order = $paymentInstance->order;
207 $transaction = $paymentInstance->transaction;
208 $fcCustomer = $paymentInstance->order->customer;
209 $billingAddress = $order->billing_address;
210
211 $transactionCurrency = $transaction->currency;
212 $intentAmount = (int)$transaction->total;
213
214 if ($transactionCurrency && CurrenciesHelper::isZeroDecimal($transactionCurrency)) {
215 $intentAmount = (int)($intentAmount / 100);
216 }
217
218 $intentData = [
219 'amount' => $intentAmount,
220 'currency' => $transactionCurrency,
221 'automatic_payment_methods' => ['enabled' => 'true'],
222 'metadata' => apply_filters('fluent_cart/payments/stripe_metadata_onetime', [
223 'fct_ref_id' => $order->uuid,
224 'Name' => $order->customer->full_name,
225 'Email' => $order->customer->email,
226 'order_reference' => 'fct_order_id_' . $paymentInstance->order->id,
227 ], [
228 'order' => $order,
229 'transaction' => $transaction
230 ]),
231 ];
232
233 $itemCount = 1;
234 foreach($paymentInstance->order->order_items as $item) {
235 $intentData['metadata']['item ' . $itemCount] = 'Name: ' . $item->title . ', ' . 'Qty: ' . $item->quantity . ', Price: ' . Helper::toDecimal($item->line_total, false, null, true, true, false);
236 if (count($intentData['metadata']) > 49) {
237 break;
238 }
239 $itemCount++;
240 }
241
242 if (!empty($paymentArgs['customer'])) {
243 $intentData['customer'] = $paymentArgs['customer'];
244 } else {
245 $stripeCustomer = StripeHelper::createOrGetStripeCustomer($order->customer);
246 if (is_wp_error($stripeCustomer)) {
247 return $stripeCustomer;
248 }
249 $intentData['customer'] = $stripeCustomer['id'];
250 }
251
252 if (!empty($paymentArgs['setup_future_usage'])) {
253 $intentData['setup_future_usage'] = $paymentArgs['setup_future_usage'];
254 }
255
256 $paymentArgs['public_key'] = (new StripeSettingsBase())->getPublicKey();
257
258 $intentData = apply_filters('fluent_cart/payments/stripe_onetime_intent_args', $intentData, [
259 'order' => $order,
260 'transaction' => $transaction
261 ]);
262
263 $intent = (new API())->createStripeObject('payment_intents', $intentData);
264
265 if (is_wp_error($intent)) {
266 return $intent;
267 }
268
269 $transaction->update([
270 'vendor_charge_id' => $intent['id']
271 ]);
272
273 $customerData = [
274 'name' => $fcCustomer->first_name . ' ' . $fcCustomer->last_name,
275 'email' => $fcCustomer->email,
276 'address_1' => $billingAddress->address_1,
277 'address_2' => $billingAddress->address_2,
278 'city' => $billingAddress->city,
279 'state' => $billingAddress->state,
280 'postcode' => $billingAddress->postcode,
281 'country' => $billingAddress->country
282 ];
283
284 return [
285 'status' => 'success',
286 'nextAction' => 'stripe',
287 'actionName' => 'custom',
288 'message' => __('Order has been placed successfully', 'fluent-cart'),
289 'response' => $intent,
290 'payment_args' => $paymentArgs,
291 'fc_customer' => $customerData
292 ];
293 }
294
295
296 private function handleHostedCheckout(PaymentInstance $paymentInstance, $paymentArgs = [])
297 {
298 $order = $paymentInstance->order;
299 $transaction = $paymentInstance->transaction;
300 $fcCustomer = $order->customer;
301 $billingAddress = $order->billing_address;
302
303 $transactionCurrency = $transaction->currency;
304 $chargeAmount = (int)$transaction->total;
305
306 if ($transactionCurrency && CurrenciesHelper::isZeroDecimal($transactionCurrency)) {
307 $chargeAmount = (int)($chargeAmount / 100);
308 }
309
310 // Create or get Stripe customer
311 $stripeCustomer = StripeHelper::createOrGetStripeCustomer($fcCustomer);
312 if (is_wp_error($stripeCustomer)) {
313 return $stripeCustomer;
314 }
315
316 // Use a single line item with the total amount to avoid complexity
317 // This is simpler and prevents any calculation mismatches
318 $storeName = (new \FluentCart\Api\StoreSettings())->get('store_name');
319 $lineItems = [
320 [
321 'price_data' => [
322 'currency' => strtolower($transactionCurrency),
323 'product_data' => [
324 'name' => $storeName . ' - Order #' . $order->uuid,
325 'description' => sprintf(__('Order total including all items, shipping (If any), and taxes (If any)', 'fluent-cart')),
326 ],
327 'unit_amount' => $chargeAmount,
328 ],
329 'quantity' => 1,
330 ]
331 ];
332
333 $sessionData = [
334 'customer' => $stripeCustomer['id'],
335 'client_reference_id' => $order->uuid,
336 'line_items' => $lineItems,
337 'mode' => 'payment',
338 'success_url' => Arr::get($paymentArgs, 'success_url') . '&fct_stripe_hosted=1&trx_hash=' . $transaction->uuid,
339 'cancel_url' => StripeHelper::getCancelUrl(),
340 'metadata' => [
341 'fct_ref_id' => $order->uuid,
342 'transaction_hash' => $transaction->uuid,
343 'order_reference' => 'fct_order_id_' . $order->id,
344 ],
345 ];
346
347 $itemCount = 1;
348 foreach($order->order_items as $item) {
349 $sessionData['metadata']['item ' . $itemCount] = 'Name: ' . $item->title . ', ' . 'Qty: ' . $item->quantity . ', Price: ' . Helper::toDecimal($item->line_total, false, null, true, true, false);
350 if (count($sessionData['metadata']) > 49) {
351 break;
352 }
353
354 $itemCount++;
355 }
356
357 $sessionData = apply_filters('fluent_cart/payments/stripe_checkout_session_args', $sessionData, [
358 'order' => $order,
359 'transaction' => $transaction
360 ]);
361
362 $session = (new API())->createStripeObject('checkout/sessions', $sessionData);
363
364 if (is_wp_error($session)) {
365 return $session;
366 }
367
368 $transaction->update([
369 'meta' => array_merge($transaction->meta ?? [], [
370 'session_id' => $session['id']
371 ])
372 ]);
373
374 return [
375 'status' => 'success',
376 'nextAction' => 'stripe',
377 'actionName' => 'redirect',
378 'message' => __('Redirecting to Stripe checkout...', 'fluent-cart'),
379 'response' => $session,
380 'payment_args' => array_merge($paymentArgs, [
381 'checkout_url' => $session['url'],
382 'session_id' => $session['id']
383 ])
384 ];
385 }
386
387
388 private function handleHostedSubscriptionCheckout(PaymentInstance $paymentInstance, $paymentArgs = [])
389 {
390 $order = $paymentInstance->order;
391 $transaction = $paymentInstance->transaction;
392 $subscriptionModel = $paymentInstance->subscription;
393 $fcCustomer = $order->customer;
394
395 if (!$subscriptionModel) {
396 return new \WP_Error('no_subscription', __('No subscription found.', 'fluent-cart'));
397 }
398
399 $transactionCurrency = $transaction->currency;
400 $orderType = $order->type;
401
402 // Create or get Stripe customer
403 $stripeCustomer = StripeHelper::createOrGetStripeCustomer($fcCustomer);
404 if (is_wp_error($stripeCustomer)) {
405 return $stripeCustomer;
406 }
407
408 // Get or create Stripe price/plan
409 if ($orderType == 'renewal') {
410 $stripePlan = Plan::getStripePricing([
411 'product_id' => $subscriptionModel->product_id,
412 'variation_id' => $subscriptionModel->variation_id,
413 'billing_interval' => $subscriptionModel->billing_interval,
414 'recurring_total' => $subscriptionModel->getCurrentRenewalAmount(),
415 'currency' => $order->currency,
416 'trial_days' => $subscriptionModel->getReactivationTrialDays(),
417 'interval_count' => 1,
418 'order_id' => $subscriptionModel->parent_order_id,
419 ]);
420 } else {
421 $stripePlan = Plan::getStripePricing([
422 'product_id' => $subscriptionModel->product_id,
423 'variation_id' => $subscriptionModel->variation_id,
424 'billing_interval' => $subscriptionModel->billing_interval,
425 'recurring_total' => $subscriptionModel->recurring_total,
426 'currency' => $order->currency,
427 'trial_days' => (int)$subscriptionModel->trial_days,
428 'interval_count' => 1,
429 'order_id' => $subscriptionModel->parent_order_id,
430 ]);
431 }
432
433 if (is_wp_error($stripePlan)) {
434 return $stripePlan;
435 }
436
437
438 $feeTotal = $orderType !== 'renewal' ? (int)$paymentInstance->order->fee_total : 0;
439 $initialAmount = (int)$subscriptionModel->signup_fee + $paymentInstance->getExtraAddonAmount() + $feeTotal;
440
441 if ($orderType == 'renewal') {
442 $initialAmount = 0;
443 }
444
445 $recurringTotal = (int)$subscriptionModel->recurring_total;
446 if ($transactionCurrency && CurrenciesHelper::isZeroDecimal($transactionCurrency)) {
447 $initialAmount = (int)($initialAmount / 100);
448 $recurringTotal = (int)($recurringTotal / 100);
449 }
450
451 $lineItems = [
452 [
453 'price' => $stripePlan['id'],
454 'quantity' => $subscriptionModel->quantity ?: 1,
455 ]
456 ];
457
458 $subscriptionData = [
459 'metadata' => [
460 'fct_ref_id' => $order->uuid,
461 'email' => $fcCustomer->email,
462 'name' => $order->full_name,
463 'order_reference' => 'fct_order_id_' . $order->id,
464 'subscription_item' => $subscriptionModel->item_name,
465 ],
466 ];
467
468 // Handle trial period if set in plan (same as onsite lines 94-96)
469 if (!empty($stripePlan['trial_period_days'])) {
470 $subscriptionData['trial_period_days'] = $stripePlan['trial_period_days'];
471 }
472
473 if ($initialAmount > 0) {
474 $addonPrice = Plan::getOneTimeAddonPrice([
475 'product_id' => $subscriptionModel->product_id,
476 'currency' => $order->currency,
477 'amount' => (int)$initialAmount,
478 'name' => __('Signup fee / initial payment', 'fluent-cart'),
479 'variation_id' => $subscriptionModel->variation_id,
480 'order_id' => $subscriptionModel->parent_order_id,
481
482 ]);
483
484 if (is_wp_error($addonPrice)) {
485 return $addonPrice;
486 };
487
488 $lineItems[] = [
489 'price' => $addonPrice['id'],
490 'quantity' => 1
491 ];
492 }
493
494 $sessionData = [
495 'customer' => $stripeCustomer['id'],
496 'client_reference_id' => $order->uuid,
497 'line_items' => $lineItems,
498 'mode' => 'subscription',
499 'consent_collection' => ['payment_method_reuse_agreement' => ['position' => 'hidden']],
500 'success_url' => Arr::get($paymentArgs, 'success_url') . '&fct_stripe_hosted=1&trx_hash=' . $transaction->uuid,
501 'cancel_url' => StripeHelper::getCancelUrl(),
502 'subscription_data' => $subscriptionData,
503 'metadata' => [
504 'fct_ref_id' => $order->uuid,
505 'subscription_item' => $subscriptionModel->item_name,
506 'transaction_hash' => $transaction->uuid,
507 'order_reference' => 'fct_order_id_' . $order->id,
508 ],
509 ];
510
511 $sessionData = apply_filters('fluent_cart/payments/stripe_subscription_checkout_session_args', $sessionData, [
512 'order' => $order,
513 'transaction' => $transaction,
514 'subscription' => $subscriptionModel
515 ]);
516
517 $session = (new API())->createStripeObject('checkout/sessions', $sessionData);
518
519 if (is_wp_error($session)) {
520 return $session;
521 }
522
523 $subscriptionModel->update([
524 'vendor_customer_id' => $stripeCustomer['id']
525 ]);
526
527 $transaction->update([
528 'vendor_charge_id' => Arr::get($session, 'payment_intent', Arr::get($session, 'id')),
529 'meta' => array_merge($transaction->meta ?? [], [
530 'session_id' => $session['id']
531 ])
532 ]);
533
534 return [
535 'status' => 'success',
536 'nextAction' => 'stripe',
537 'actionName' => 'redirect',
538 'message' => __('Redirecting to Stripe checkout...', 'fluent-cart'),
539 'response' => $session,
540 'payment_args' => array_merge($paymentArgs, [
541 'checkout_url' => $session['url'],
542 'session_id' => $session['id']
543 ])
544 ];
545 }
546
547 }
548