PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.5.3
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.5.3
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.5.3, at app/Modules/PaymentMethods/StripeGateway/Processor.php

624 lines 26.2 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 // Anchor trial_end to a STABLE point (the charge transaction's creation time,
106 // which is preserved across re-submissions) instead of "now". A volatile
107 // trial_end would make a retried create send a different body, and Stripe
108 // rejects a reused Idempotency-Key whose parameters changed (400), bricking
109 // the order for the key's 24h lifetime. Anchoring keeps retries byte-identical.
110 $trialDays = (int) Arr::get($stripePlan, 'trial_period_days');
111 $anchorTs = $paymentInstance->transaction && $paymentInstance->transaction->created_at
112 ? strtotime($paymentInstance->transaction->created_at . ' UTC')
113 : time();
114 $trialEnd = strtotime('+' . $trialDays . ' days', $anchorTs);
115 // Stripe requires trial_end in the future; only a stale late retry could fall
116 // behind, and that order would already carry a fresh transaction/key anyway.
117 if ($trialEnd <= time() + MINUTE_IN_SECONDS) {
118 $trialEnd = strtotime('+' . $trialDays . ' days');
119 }
120 $stripeSubscriptionData['trial_end'] = $trialEnd;
121 }
122
123 // Maybe we have initial amount
124 if ($initialAmount) {
125 $addonPrice = Plan::getOneTimeAddonPrice([
126 'product_id' => $subscriptionModel->product_id,
127 'currency' => $paymentInstance->order->currency,
128 'amount' => (int)$initialAmount,
129 'variation_id' => $subscriptionModel->variation_id,
130 'order_id' => $subscriptionModel->parent_order_id,
131 ]);
132
133 if (is_wp_error($addonPrice)) {
134 return $addonPrice;
135 }
136
137 $stripeSubscriptionData['add_invoice_items'] = [
138 [
139 'price' => $addonPrice['id'],
140 'quantity' => 1
141 ]
142 ];
143 }
144
145 if ($expireAt = $paymentInstance->getSubscriptionCancelAtTimeStamp()) {
146 // $stripeSubscriptionData['cancel_at'] = $expireAt;
147 }
148
149 // Duplicate-charge defense — key construction contract in
150 // .claude/skills/coding-rules/payment-idempotency.md. Seed dedupes duplicates
151 // and frees retries; fingerprint = charge-material params so an edited order
152 // gets a fresh key instead of a same-key/changed-parameters 400 (the abandoned
153 // incomplete subscription auto-expires). Params, not transaction->total: a
154 // recurring coupon can change the plan while the first charge stays $0.
155 // Metadata excluded — volatile filters must not change the key on a duplicate.
156 $idempotencyFingerprint = [
157 'customer' => Arr::get($stripeSubscriptionData, 'customer'),
158 'items' => Arr::get($stripeSubscriptionData, 'items'),
159 'add_invoice_items' => Arr::get($stripeSubscriptionData, 'add_invoice_items'),
160 'trial_end' => Arr::get($stripeSubscriptionData, 'trial_end'),
161 ];
162 $idempotencySeed = $paymentInstance->getIdempotencySeed();
163 $idempotencyKey = $idempotencySeed
164 ? 'fct_stripe_sub_' . md5($idempotencySeed . '|' . wp_json_encode($idempotencyFingerprint))
165 : null;
166
167 $stripeSubscription = (new API())->createStripeObject('subscriptions', $stripeSubscriptionData, 'current', $idempotencyKey);
168
169 if (is_wp_error($stripeSubscription)) {
170 return $stripeSubscription;
171 }
172
173 $vendorChargeId = Arr::get($stripeSubscription, 'latest_invoice.payment_intent');
174 if (!$vendorChargeId) {
175 $vendorChargeId = Arr::get($stripeSubscription, 'pending_setup_intent.id');
176 }
177
178 if ($vendorChargeId) {
179 $paymentInstance->transaction->update(['vendor_charge_id' => $vendorChargeId]);
180 }
181
182 $vendorSubscriptionId = Arr::get($stripeSubscription, 'id');
183
184 $subscriptionModel->update([
185 'vendor_subscription_id' => $vendorSubscriptionId,
186 'vendor_customer_id' => $stripeSubscription['customer']
187 ]);
188
189 if ($stripeSubscription['pending_setup_intent'] != null) {
190 $paymentArgs['vendor_subscription_info'] = [
191 'type' => 'setup',
192 'clientSecret' => Arr::get($stripeSubscription, 'pending_setup_intent.client_secret'),
193 'trx_hash' => $paymentInstance->transaction->uuid,
194 ];
195 } else {
196 $paymentArgs['vendor_subscription_info'] = [
197 'type' => 'payment',
198 'clientSecret' => Arr::get($stripeSubscription, 'latest_invoice.confirmation_secret.client_secret')
199 ];
200 }
201
202 $customerData = [
203 'name' => $fcCustomer->first_name . ' ' . $fcCustomer->last_name,
204 'email' => $fcCustomer->email,
205 'address_1' => $billingAddress->address_1,
206 'address_2' => $billingAddress->address_2,
207 'city' => $billingAddress->city,
208 'state' => $billingAddress->state,
209 'postcode' => $billingAddress->postcode,
210 'country' => $billingAddress->country
211 ];
212
213 return [
214 'nextAction' => 'stripe',
215 'actionName' => 'custom',
216 'status' => 'success',
217 'message' => __('Order has been placed successfully', 'fluent-cart'),
218 'payment_args' => $paymentArgs,
219 'response' => $stripeSubscription,
220 'fc_customer' => $customerData
221 ];
222 }
223
224
225 /**
226 * Handle single payment for stripe (onsite or hosted)
227 *
228 * @return \WP_Error|array
229 */
230 public function handleSinglePayment(PaymentInstance $paymentInstance, $paymentArgs = [])
231 {
232 $stripeSettings = new StripeSettingsBase();
233 $checkoutMode = $stripeSettings->get('checkout_mode') ?? 'onsite';
234
235 if ($checkoutMode === 'hosted') {
236 return $this->handleHostedCheckout($paymentInstance, $paymentArgs);
237 }
238
239 // Original onsite payment flow
240 $order = $paymentInstance->order;
241 $transaction = $paymentInstance->transaction;
242 $fcCustomer = $paymentInstance->order->customer;
243 $billingAddress = $order->billing_address;
244
245 $transactionCurrency = $transaction->currency;
246 $intentAmount = (int)$transaction->total;
247
248 if ($transactionCurrency && CurrenciesHelper::isZeroDecimal($transactionCurrency)) {
249 $intentAmount = (int)($intentAmount / 100);
250 }
251
252 $intentData = [
253 'amount' => $intentAmount,
254 'currency' => $transactionCurrency,
255 'automatic_payment_methods' => ['enabled' => 'true'],
256 'metadata' => apply_filters('fluent_cart/payments/stripe_metadata_onetime', [
257 'fct_ref_id' => $order->uuid,
258 'Name' => $order->customer->full_name,
259 'Email' => $order->customer->email,
260 'order_reference' => 'fct_order_id_' . $paymentInstance->order->id,
261 ], [
262 'order' => $order,
263 'transaction' => $transaction
264 ]),
265 ];
266
267 $itemCount = 1;
268 foreach($paymentInstance->order->order_items as $item) {
269 $intentData['metadata']['item ' . $itemCount] = 'Name: ' . $item->title . ', ' . 'Qty: ' . $item->quantity . ', Price: ' . Helper::toDecimal($item->line_total, false, null, true, true, false);
270 if (count($intentData['metadata']) > 49) {
271 break;
272 }
273 $itemCount++;
274 }
275
276 if (!empty($paymentArgs['customer'])) {
277 $intentData['customer'] = $paymentArgs['customer'];
278 } else {
279 $stripeCustomer = StripeHelper::createOrGetStripeCustomer($order->customer);
280 if (is_wp_error($stripeCustomer)) {
281 return $stripeCustomer;
282 }
283 $intentData['customer'] = $stripeCustomer['id'];
284 }
285
286 if (!empty($paymentArgs['setup_future_usage'])) {
287 $intentData['setup_future_usage'] = $paymentArgs['setup_future_usage'];
288 }
289
290 $paymentArgs['public_key'] = (new StripeSettingsBase())->getPublicKey();
291
292 $intentData = apply_filters('fluent_cart/payments/stripe_onetime_intent_args', $intentData, [
293 'order' => $order,
294 'transaction' => $transaction
295 ]);
296
297 // Same duplicate-charge defense for one-time onsite payments. Customer is in
298 // the fingerprint because a guest editing their email between attempts maps to
299 // a different Stripe customer — same key there would 400 for the key's 24h
300 // lifetime. Built AFTER the intent-args filter so filtered amounts are what
301 // get fingerprinted.
302 $idempotencyFingerprint = [
303 'amount' => Arr::get($intentData, 'amount'),
304 'currency' => Arr::get($intentData, 'currency'),
305 'customer' => Arr::get($intentData, 'customer'),
306 ];
307 $idempotencySeed = $paymentInstance->getIdempotencySeed();
308 $idempotencyKey = $idempotencySeed
309 ? 'fct_stripe_pi_' . md5($idempotencySeed . '|' . wp_json_encode($idempotencyFingerprint))
310 : null;
311
312 $intent = (new API())->createStripeObject('payment_intents', $intentData, 'current', $idempotencyKey);
313
314 if (is_wp_error($intent)) {
315 return $intent;
316 }
317
318 $transaction->update([
319 'vendor_charge_id' => $intent['id']
320 ]);
321
322 $customerData = [
323 'name' => $fcCustomer->first_name . ' ' . $fcCustomer->last_name,
324 'email' => $fcCustomer->email,
325 'address_1' => $billingAddress->address_1,
326 'address_2' => $billingAddress->address_2,
327 'city' => $billingAddress->city,
328 'state' => $billingAddress->state,
329 'postcode' => $billingAddress->postcode,
330 'country' => $billingAddress->country
331 ];
332
333 return [
334 'status' => 'success',
335 'nextAction' => 'stripe',
336 'actionName' => 'custom',
337 'message' => __('Order has been placed successfully', 'fluent-cart'),
338 'response' => $intent,
339 'payment_args' => $paymentArgs,
340 'fc_customer' => $customerData
341 ];
342 }
343
344
345 private function handleHostedCheckout(PaymentInstance $paymentInstance, $paymentArgs = [])
346 {
347 $order = $paymentInstance->order;
348 $transaction = $paymentInstance->transaction;
349 $fcCustomer = $order->customer;
350 $billingAddress = $order->billing_address;
351
352 $transactionCurrency = $transaction->currency;
353 $chargeAmount = (int)$transaction->total;
354
355 if ($transactionCurrency && CurrenciesHelper::isZeroDecimal($transactionCurrency)) {
356 $chargeAmount = (int)($chargeAmount / 100);
357 }
358
359 // Create or get Stripe customer
360 $stripeCustomer = StripeHelper::createOrGetStripeCustomer($fcCustomer);
361 if (is_wp_error($stripeCustomer)) {
362 return $stripeCustomer;
363 }
364
365 // Use a single line item with the total amount to avoid complexity
366 // This is simpler and prevents any calculation mismatches
367 $storeName = (new \FluentCart\Api\StoreSettings())->get('store_name');
368 $lineItems = [
369 [
370 'price_data' => [
371 'currency' => strtolower($transactionCurrency),
372 'product_data' => [
373 'name' => $storeName . ' - Order #' . $order->uuid,
374 'description' => sprintf(__('Order total including all items, shipping (If any), and taxes (If any)', 'fluent-cart')),
375 ],
376 'unit_amount' => $chargeAmount,
377 ],
378 'quantity' => 1,
379 ]
380 ];
381
382 $sessionData = [
383 'customer' => $stripeCustomer['id'],
384 'client_reference_id' => $order->uuid,
385 'line_items' => $lineItems,
386 'mode' => 'payment',
387 'success_url' => Arr::get($paymentArgs, 'success_url') . '&fct_stripe_hosted=1&trx_hash=' . $transaction->uuid,
388 'cancel_url' => StripeHelper::getCancelUrl(),
389 'metadata' => [
390 'fct_ref_id' => $order->uuid,
391 'transaction_hash' => $transaction->uuid,
392 'order_reference' => 'fct_order_id_' . $order->id,
393 ],
394 ];
395
396 $itemCount = 1;
397 foreach($order->order_items as $item) {
398 $sessionData['metadata']['item ' . $itemCount] = 'Name: ' . $item->title . ', ' . 'Qty: ' . $item->quantity . ', Price: ' . Helper::toDecimal($item->line_total, false, null, true, true, false);
399 if (count($sessionData['metadata']) > 49) {
400 break;
401 }
402
403 $itemCount++;
404 }
405
406 $sessionData = apply_filters('fluent_cart/payments/stripe_checkout_session_args', $sessionData, [
407 'order' => $order,
408 'transaction' => $transaction
409 ]);
410
411 // Same duplicate-charge defense as every other Stripe create path: a pure
412 // duplicate replays the key and gets the original session back; an edited-cart
413 // resubmit gets a fresh key instead of a same-key/changed-parameters 400.
414 $idempotencyFingerprint = [
415 'customer' => Arr::get($sessionData, 'customer'),
416 'line_items' => Arr::get($sessionData, 'line_items'),
417 'mode' => Arr::get($sessionData, 'mode'),
418 ];
419 $idempotencySeed = $paymentInstance->getIdempotencySeed();
420 $idempotencyKey = $idempotencySeed
421 ? 'fct_stripe_cs_' . md5($idempotencySeed . '|' . wp_json_encode($idempotencyFingerprint))
422 : null;
423
424 $session = (new API())->createStripeObject('checkout/sessions', $sessionData, 'current', $idempotencyKey);
425
426 if (is_wp_error($session)) {
427 return $session;
428 }
429
430 $transaction->update([
431 'meta' => array_merge($transaction->meta ?? [], [
432 'session_id' => $session['id']
433 ])
434 ]);
435
436 return [
437 'status' => 'success',
438 'nextAction' => 'stripe',
439 'actionName' => 'redirect',
440 'message' => __('Redirecting to Stripe checkout...', 'fluent-cart'),
441 'response' => $session,
442 'payment_args' => array_merge($paymentArgs, [
443 'checkout_url' => $session['url'],
444 'session_id' => $session['id']
445 ])
446 ];
447 }
448
449
450 private function handleHostedSubscriptionCheckout(PaymentInstance $paymentInstance, $paymentArgs = [])
451 {
452 $order = $paymentInstance->order;
453 $transaction = $paymentInstance->transaction;
454 $subscriptionModel = $paymentInstance->subscription;
455 $fcCustomer = $order->customer;
456
457 if (!$subscriptionModel) {
458 return new \WP_Error('no_subscription', __('No subscription found.', 'fluent-cart'));
459 }
460
461 $transactionCurrency = $transaction->currency;
462 $orderType = $order->type;
463
464 // Create or get Stripe customer
465 $stripeCustomer = StripeHelper::createOrGetStripeCustomer($fcCustomer);
466 if (is_wp_error($stripeCustomer)) {
467 return $stripeCustomer;
468 }
469
470 // Get or create Stripe price/plan
471 if ($orderType == 'renewal') {
472 $stripePlan = Plan::getStripePricing([
473 'product_id' => $subscriptionModel->product_id,
474 'variation_id' => $subscriptionModel->variation_id,
475 'billing_interval' => $subscriptionModel->billing_interval,
476 'recurring_total' => $subscriptionModel->getCurrentRenewalAmount(),
477 'currency' => $order->currency,
478 'trial_days' => $subscriptionModel->getReactivationTrialDays(),
479 'interval_count' => 1,
480 'order_id' => $subscriptionModel->parent_order_id,
481 ]);
482 } else {
483 $stripePlan = Plan::getStripePricing([
484 'product_id' => $subscriptionModel->product_id,
485 'variation_id' => $subscriptionModel->variation_id,
486 'billing_interval' => $subscriptionModel->billing_interval,
487 'recurring_total' => $subscriptionModel->recurring_total,
488 'currency' => $order->currency,
489 'trial_days' => (int)$subscriptionModel->trial_days,
490 'interval_count' => 1,
491 'order_id' => $subscriptionModel->parent_order_id,
492 ]);
493 }
494
495 if (is_wp_error($stripePlan)) {
496 return $stripePlan;
497 }
498
499
500 $feeTotal = $orderType !== 'renewal' ? (int)$paymentInstance->order->fee_total : 0;
501 $initialAmount = (int)$subscriptionModel->signup_fee + $paymentInstance->getExtraAddonAmount() + $feeTotal;
502
503 if ($orderType == 'renewal') {
504 $initialAmount = 0;
505 }
506
507 $recurringTotal = (int)$subscriptionModel->recurring_total;
508 if ($transactionCurrency && CurrenciesHelper::isZeroDecimal($transactionCurrency)) {
509 $initialAmount = (int)($initialAmount / 100);
510 $recurringTotal = (int)($recurringTotal / 100);
511 }
512
513 $lineItems = [
514 [
515 'price' => $stripePlan['id'],
516 'quantity' => $subscriptionModel->quantity ?: 1,
517 ]
518 ];
519
520 $subscriptionData = [
521 'metadata' => [
522 'fct_ref_id' => $order->uuid,
523 'email' => $fcCustomer->email,
524 'name' => $order->full_name,
525 'order_reference' => 'fct_order_id_' . $order->id,
526 'subscription_item' => $subscriptionModel->item_name,
527 ],
528 ];
529
530 // Handle trial period if set in plan (same as onsite lines 94-96)
531 if (!empty($stripePlan['trial_period_days'])) {
532 $subscriptionData['trial_period_days'] = $stripePlan['trial_period_days'];
533 }
534
535 if ($initialAmount > 0) {
536 $addonPrice = Plan::getOneTimeAddonPrice([
537 'product_id' => $subscriptionModel->product_id,
538 'currency' => $order->currency,
539 'amount' => (int)$initialAmount,
540 'name' => __('Signup fee / initial payment', 'fluent-cart'),
541 'variation_id' => $subscriptionModel->variation_id,
542 'order_id' => $subscriptionModel->parent_order_id,
543
544 ]);
545
546 if (is_wp_error($addonPrice)) {
547 return $addonPrice;
548 };
549
550 $lineItems[] = [
551 'price' => $addonPrice['id'],
552 'quantity' => 1
553 ];
554 }
555
556 $sessionData = [
557 'customer' => $stripeCustomer['id'],
558 'client_reference_id' => $order->uuid,
559 'line_items' => $lineItems,
560 'mode' => 'subscription',
561 'consent_collection' => ['payment_method_reuse_agreement' => ['position' => 'hidden']],
562 'success_url' => Arr::get($paymentArgs, 'success_url') . '&fct_stripe_hosted=1&trx_hash=' . $transaction->uuid,
563 'cancel_url' => StripeHelper::getCancelUrl(),
564 'subscription_data' => $subscriptionData,
565 'metadata' => [
566 'fct_ref_id' => $order->uuid,
567 'subscription_item' => $subscriptionModel->item_name,
568 'transaction_hash' => $transaction->uuid,
569 'order_reference' => 'fct_order_id_' . $order->id,
570 ],
571 ];
572
573 $sessionData = apply_filters('fluent_cart/payments/stripe_subscription_checkout_session_args', $sessionData, [
574 'order' => $order,
575 'transaction' => $transaction,
576 'subscription' => $subscriptionModel
577 ]);
578
579 // Same duplicate-subscription defense as the onsite path, applied to the hosted
580 // Checkout Session. Metadata is excluded so a volatile metadata filter cannot
581 // change the key on a genuine duplicate and reopen the double-charge window.
582 $idempotencyFingerprint = [
583 'customer' => Arr::get($sessionData, 'customer'),
584 'line_items' => Arr::get($sessionData, 'line_items'),
585 'mode' => Arr::get($sessionData, 'mode'),
586 'subscription_data' => Arr::get($sessionData, 'subscription_data'),
587 ];
588 $idempotencySeed = $paymentInstance->getIdempotencySeed();
589 $idempotencyKey = $idempotencySeed
590 ? 'fct_stripe_sub_cs_' . md5($idempotencySeed . '|' . wp_json_encode($idempotencyFingerprint))
591 : null;
592
593 $session = (new API())->createStripeObject('checkout/sessions', $sessionData, 'current', $idempotencyKey);
594
595 if (is_wp_error($session)) {
596 return $session;
597 }
598
599 $subscriptionModel->update([
600 'vendor_customer_id' => $stripeCustomer['id']
601 ]);
602
603 $transaction->update([
604 'vendor_charge_id' => Arr::get($session, 'payment_intent', Arr::get($session, 'id')),
605 'meta' => array_merge($transaction->meta ?? [], [
606 'session_id' => $session['id']
607 ])
608 ]);
609
610 return [
611 'status' => 'success',
612 'nextAction' => 'stripe',
613 'actionName' => 'redirect',
614 'message' => __('Redirecting to Stripe checkout...', 'fluent-cart'),
615 'response' => $session,
616 'payment_args' => array_merge($paymentArgs, [
617 'checkout_url' => $session['url'],
618 'session_id' => $session['id']
619 ])
620 ];
621 }
622
623 }
624