PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.28
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.28
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 / Hooks / Cart / WebCheckoutHandler.php

WebCheckoutHandler.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.28, at app/Hooks/Cart/WebCheckoutHandler.php

1,375 lines 50.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\Hooks\Cart;
4
5 use FluentCart\Api\Checkout\CheckoutApi;
6 use FluentCart\Api\PaymentMethods;
7 use FluentCart\Api\Resource\FrontendResource\CartResource;
8 use FluentCart\Api\Resource\CustomerResource;
9 use FluentCart\Api\Resource\FrontendResource\CustomerAddressResource;
10 use FluentCart\App\App;
11 use FluentCart\App\Helpers\AddressHelper;
12 use FluentCart\App\Helpers\CartCheckoutHelper;
13 use FluentCart\App\Helpers\CartHelper;
14 use FluentCart\App\Helpers\Helper;
15 use FluentCart\App\Helpers\UtmHelper;
16 use FluentCart\App\Models\Product;
17 use FluentCart\App\Models\ProductVariation;
18 use FluentCart\App\Models\ShippingMethod;
19 use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
20 use FluentCart\App\Services\Localization\LocalizationManager;
21 use FluentCart\App\Services\Renderer\AddressSelectRenderer;
22 use FluentCart\App\Services\Renderer\CartDrawerRenderer;
23 use FluentCart\App\Services\Renderer\CartRenderer;
24 use FluentCart\App\Services\Renderer\CartSummaryRender;
25 use FluentCart\App\Services\Renderer\CheckoutFieldsSchema;
26 use FluentCart\App\Services\Renderer\CheckoutRenderer;
27 use FluentCart\App\Services\Renderer\ProductModalRenderer;
28 use FluentCart\App\Services\Renderer\ShippingMethodsRender;
29 use FluentCart\Framework\Support\Arr;
30 use FluentCart\App\Services\Renderer\ModalCheckoutRenderer;
31
32 class WebCheckoutHandler
33 {
34 public function register()
35 {
36 add_action('wp_ajax_fluent_cart_place_order', [$this, 'handlePlaceOrderAjax']);
37 add_action('wp_ajax_nopriv_fluent_cart_place_order', [$this, 'handlePlaceOrderAjax']);
38
39 add_action('wp_ajax_fluent_cart_checkout_routes', [$this, 'globalCheckoutRouteHandler']);
40 add_action('wp_ajax_nopriv_fluent_cart_checkout_routes', [$this, 'globalCheckoutRouteHandler']);
41
42 add_action('fluent_cart/order_bump_succeed', function ($data) {
43 $cart = Arr::get($data, 'cart', null);
44 $order = Arr::get($data, 'order', null);
45
46 if (!$cart || !$order) {
47 return;
48 }
49
50 $order->addLog('Order Bump Succeeded', 'Order Bump done from variation ID: ' . Arr::get($cart->checkout_data, 'order_bump.upgraded_from', '') . ' to variation ID: ' . Arr::get($cart->checkout_data, 'order_bump.upgraded_to', ''));
51 $order->updateMeta('_order_bump', [
52 'upgraded_from' => Arr::get($cart->checkout_data, 'order_bump.upgraded_from', ''),
53 'upgraded_to' => Arr::get($cart->checkout_data, 'order_bump.upgraded_to', '')
54 ]);
55
56 });
57
58 }
59
60 public function globalCheckoutRouteHandler()
61 {
62 nocache_headers();
63
64 $startedAt = microtime(true);
65
66 $action = App::request()->get('fc_checkout_action');
67 $result = [];
68 switch ($action) {
69 case 'apply_coupon':
70 $result = $this->handleApplyCouponAjax();
71 break;
72 case 'remove_coupon':
73 $result = $this->handleRemoveCouponAjax();
74 break;
75 case 'get_checkout_summary_view':
76 case 'reapply_coupon':
77 $result = $this->handleGetCheckoutSummaryViewAjax();
78 break;
79 case 'get_order_info':
80 $result = $this->handleGetOrderInfoAjax();
81 break;
82 case 'save_checkout_data':
83 $result = $this->patchCheckoutData();
84 break;
85 case 'get_country_info':
86 $result = $this->handleGetCountryInfoAjax();
87 break;
88 case 'update_address_select':
89 $result = $this->handleUpdateAddressSelectAjax();
90 break;
91
92 case 'get_shipping_methods_list_view':
93 $result = $this->handleGetShippingMethodsListViewAjax();
94 break;
95 case 'fluent_cart_cart_update':
96 $result = $this->handleCartUpdateAjax();
97 break;
98 case 'fluent_cart_cart_status':
99 $result = $this->handleCartStatusAjax();
100 break;
101 case 'apply_order_bump':
102 $result = $this->handleOrderBumpRequest();
103 break;
104 case 'get_product_modal_view':
105 $result = $this->getProductModalView();
106 break;
107 }
108
109 if (is_wp_error($result)) {
110 wp_send_json([
111 'message' => Arr::get($result, 'message') ?? $result->get_error_message()
112 ], 422);
113 }
114
115 if (is_array($result)) {
116 $result['_bench'] = microtime(true) - $startedAt;
117 }
118
119 wp_send_json($result, 200);
120 }
121
122 public function handlePlaceOrderAjax()
123 {
124 nocache_headers();
125 $data = App::request()->all();
126
127 CheckoutApi::placeOrder($data, true);
128 }
129
130 public function getShippingChargeData($cart): array
131 {
132 $shippingMethodId = App::request()->getSafe('shipping_method_id', 'sanitize_text_field');
133
134 if (!$shippingMethodId) {
135 $shippingMethodId = Arr::get($cart->checkout_data, 'shipping_data.shipping_method_id');
136 }
137
138 $shippingMethod = ShippingMethod::query()->find($shippingMethodId);
139
140 $charge = 0;
141 if (!empty($shippingMethod)) {
142 $shippingCountry = Arr::get($cart->checkout_data, 'form_data.shipping_country');
143 $shippingState = Arr::get($cart->checkout_data, 'form_data.shipping_state');
144
145 $lists = AddressHelper::getAvailableShippingMethodLists(['country_code' => $shippingCountry, 'state' => $shippingState]);
146
147 if (isset($lists['available_shipping_methods'])) {
148 $availableShippingMethods = Arr::get($lists, 'available_shipping_methods', []);
149 $shippingMethodIds = Arr::pluck($availableShippingMethods, 'id');
150
151 if (in_array($shippingMethodId, $shippingMethodIds)) {
152 $shippingMethod = ShippingMethod::query()->find($shippingMethodId);
153 if (!empty($shippingMethod)) {
154 $charge = CartHelper::calculateShippingMethodCharge($shippingMethod);
155 }
156 } else {
157 CartHelper::resetShippingCharge();
158 }
159 } else {
160 CartHelper::resetShippingCharge();
161 }
162 }
163
164 return [
165 'charge' => $charge,
166 'formatted_charge' => Helper::toDecimal($charge),
167 'shippingMethodId' => $shippingMethodId
168 ];
169 }
170
171 public function handleGetOrderInfoAjax()
172 {
173 $method = App::request()->getSafe('method', 'sanitize_text_field');
174 $paymentManager = GatewayManager::getInstance()->get($method);
175 return $paymentManager->getOrderInfo(App::request()->all());
176 }
177
178 public function handleApplyCouponAjax()
179 {
180
181 $couponCode = App::request()->getSafe('coupon_code', 'sanitize_text_field');
182 $cart = CartHelper::getCart(null, false);
183
184 if (!$cart) {
185 return new \WP_Error('no_cart', __('No active cart found', 'fluent-cart'));
186 }
187
188 $result = $cart->applyCoupon($couponCode);
189
190 if (is_wp_error($result)) {
191 return $result;
192 }
193
194 $cartCouponCodes = is_array($cart->coupons) ? $cart->coupons : [];
195 $couponWasApplied = false;
196 foreach ($cartCouponCodes as $existingCode) {
197 if (strcasecmp((string) $existingCode, (string) $couponCode) === 0) {
198 $couponWasApplied = true;
199 break;
200 }
201 }
202
203 if (!$couponWasApplied) {
204 $perCouponResults = is_array($result) ? Arr::get($result, 'coupon_results', []) : [];
205 $errorMessage = '';
206 foreach ($perCouponResults as $resultCode => $couponResult) {
207 if (strcasecmp((string) $resultCode, (string) $couponCode) === 0) {
208 $errorMessage = Arr::get($couponResult, 'error', '');
209 if ($errorMessage !== '') {
210 break;
211 }
212 }
213 }
214 if ($errorMessage === '') {
215 $errorMessage = __('No matching coupon found for this code.', 'fluent-cart');
216 }
217 return new \WP_Error('coupon_not_applied', $errorMessage);
218 }
219
220 ob_start();
221 (new CartSummaryRender($cart))->render($withWrapper = false);
222 $summary = ob_get_clean();
223
224 $cartTotal = $cart->getEstimatedTotal();
225
226 $modalCheckoutRender = (new ModalCheckoutRenderer($cart));
227 $enableModalCheckout = App::request()->get('modal_checkout');
228
229 $fragments = [];
230
231 $fragments[] = [
232 'selector' => '[data-fluent-cart-checkout-page-cart-items-wrapper]',
233 'content' => $summary,
234 'type' => 'replace'
235 ];
236
237 if ($enableModalCheckout === 'yes') {
238 $fragments[] = [
239 'selector' => '[data-fluent-cart-checkout-payment-methods]',
240 'content' => $modalCheckoutRender->getFragment('payment_methods'),
241 'type' => 'replace'
242 ];
243 } else {
244 $fragments[] = [
245 'selector' => '[data-fluent-cart-checkout-payment-methods]',
246 'content' => (new CheckoutRenderer($cart))->getFragment('payment_methods'),
247 'type' => 'replace'
248 ];
249 }
250
251 $fragments[] = [
252 'selector' => '[data-fct-modal-checkout-summary-group]',
253 'content' => $modalCheckoutRender->getFragment('summary_group'),
254 'type' => 'replace'
255 ];
256
257 return [
258 'fragments' => $fragments,
259 'cart' => $cart,
260 'total' => $cartTotal,
261 'formatted_total' => Helper::toDecimal($cartTotal),
262 'applied_coupons' => $cart->coupons,
263 'has_subscriptions' => $cart->hasSubscription()
264 ];
265 }
266
267 public function handleRemoveCouponAjax()
268 {
269 $couponCode = App::request()->getSafe('coupon_code', 'sanitize_text_field');
270 $cart = CartHelper::getCart(null, false);
271
272 if (!$cart) {
273 return new \WP_Error('no_cart', __('No active cart found', 'fluent-cart'));
274 }
275
276 $cart = $cart->removeCoupon($couponCode);
277
278 if (is_wp_error($cart)) {
279 return $cart;
280 }
281
282 ob_start();
283 (new CartSummaryRender($cart))->render($withWrapper = false);
284 $summary = ob_get_clean();
285
286 $cartTotal = $cart->getEstimatedTotal();
287
288 $modalCheckoutRender = (new ModalCheckoutRenderer($cart));
289 $enableModalCheckout = App::request()->get('modal_checkout');
290
291 $fragments = [];
292
293 $fragments[] = [
294 'selector' => '[data-fluent-cart-checkout-page-cart-items-wrapper]',
295 'content' => $summary,
296 'type' => 'replace'
297 ];
298
299 if ($enableModalCheckout === 'yes') {
300 $fragments[] = [
301 'selector' => '[data-fluent-cart-checkout-payment-methods]',
302 'content' => $modalCheckoutRender->getFragment('payment_methods'),
303 'type' => 'replace'
304 ];
305 } else {
306 $fragments[] = [
307 'selector' => '[data-fluent-cart-checkout-payment-methods]',
308 'content' => (new CheckoutRenderer($cart))->getFragment('payment_methods'),
309 'type' => 'replace'
310 ];
311 }
312
313 $fragments[] = [
314 'selector' => '[data-fct-modal-checkout-summary-group]',
315 'content' => $modalCheckoutRender->getFragment('summary_group'),
316 'type' => 'replace'
317 ];
318
319
320 return [
321 'fragments' => $fragments,
322 'total' => $cartTotal,
323 'formatted_total' => Helper::toDecimal($cartTotal),
324 'applied_coupons' => $cart->coupons,
325 'has_subscriptions' => $cart->hasSubscription()
326 ];
327 }
328
329 public function handleGetCheckoutSummaryViewAjax()
330 {
331 $cart = CartHelper::getCart(null, false);
332 if (!$cart) {
333 return new \WP_Error('no_cart', __('No active cart found', 'fluent-cart'));
334 }
335
336 $cart = $cart->reValidateCoupons();
337
338 $checkOutHelper = CartCheckoutHelper::make();
339 $checkOutHelper->setCart($cart);
340
341 $shippingChargeData = $this->getShippingChargeData($cart);
342 $shippingCharge = Arr::get($shippingChargeData, 'charge');
343 $shippingMethodId = Arr::get($shippingChargeData, 'shippingMethodId');
344 $formattedShippingCharge = Arr::get($shippingChargeData, 'formatted_charge');
345 $subtotal = $checkOutHelper->getItemsAmountSubtotal(false);
346
347 $formattedSubtotal = $checkOutHelper->getItemsAmountSubtotal(true);
348
349 $totalPrice = $checkOutHelper->getItemsAmountTotal(false) + $shippingCharge;
350
351 // Include fee total
352 $feeTotal = $cart->getFeeTotal();
353 if ($feeTotal > 0) {
354 $totalPrice += $feeTotal;
355 }
356
357 if (!empty($cart->checkout_data['tax_data'])) {
358 $taxTotal = (int) Arr::get($cart->checkout_data, 'tax_data.tax_total', 0);
359 $totalPrice += $taxTotal;
360 }
361
362 $oldShippingCharge = Arr::get($cart->checkout_data, 'shipping_data.shipping_charge', 0);
363
364 if (!empty($shippingCharge)) {
365 $cart->checkout_data = array_merge($cart->checkout_data, [
366 'shipping_data' => [
367 'shipping_method_id' => $shippingMethodId,
368 'shipping_charge' => $shippingCharge
369 ]
370 ]);
371 } else {
372 $cart->checkout_data = array_merge($cart->checkout_data, [
373 'shipping_data' => [
374 'shipping_method_id' => null,
375 'shipping_charge' => 0
376 ]
377 ]);
378 }
379
380 $cart->save();
381
382 // shipping charge changed
383 if ($shippingCharge !== $oldShippingCharge) {
384 do_action('fluent_cart/checkout/shipping_data_changed', [
385 'cart' => $cart
386 ]);
387 }
388
389
390 $totalPrice = apply_filters('fluent_cart/cart/estimated_total', $totalPrice, [
391 'cart' => $cart
392 ]);
393
394 $fragments = [];
395
396 $cartRender = (new CheckoutRenderer($cart));
397 $modalCheckoutRender = (new ModalCheckoutRenderer($cart));
398 $enableModalCheckout = App::request()->getSafe('modal_checkout', 'sanitize_text_field');
399
400 ob_start();
401 (new CartSummaryRender($cart))->render(false);
402 $cartSummaryInner = ob_get_clean();
403
404 $fragments[] = [
405 'selector' => '[data-fluent-cart-checkout-page-cart-items-wrapper]',
406 'content' => $cartSummaryInner,
407 'type' => 'replace'
408 ];
409
410 if ($enableModalCheckout === 'yes') {
411 $fragments[] = [
412 'selector' => '[data-fluent-cart-checkout-payment-methods]',
413 'content' => $modalCheckoutRender->getFragment('payment_methods'),
414 'type' => 'replace'
415 ];
416 } else {
417 $fragments[] = [
418 'selector' => '[data-fluent-cart-checkout-payment-methods]',
419 'content' => $cartRender->getFragment('payment_methods'),
420 'type' => 'replace'
421 ];
422 }
423
424 return [
425 'fragments' => $fragments,
426 'total' => $totalPrice,
427 'applied_coupons' => $cart->coupons,
428 'shipping_charge' => $shippingCharge,
429 'formatted_shipping_charge' => $formattedShippingCharge,
430 'has_subscriptions' => $cart->hasSubscription(),
431 'shipping_method_id' => $shippingMethodId
432 ];
433
434 }
435
436 public function handleUpdateAddressSelectAjax()
437 {
438 $customerAddressId = App::request()->get('customer_address_id');
439 $address = CustomerAddressResource::find($customerAddressId, ['with' => App::request()->get('with', [])]);
440
441 if (!$address) {
442 return [
443 'message' => __('Address not found', 'fluent-cart')
444 ];
445 }
446
447 //update address into cart
448 $addressId = Arr::get($address, 'address.id');
449 $country = Arr::get($address, 'address.country');
450 $state = Arr::get($address, 'address.state');
451 $type = Arr::get($address, 'address.type', 'billing');
452 $cart = CartHelper::getCart(App::request()->get('fct_cart_hash'));
453
454 $oldTaxTotal = Arr::get($cart->checkout_data, 'tax_data.tax_total', 0);
455 $oldShippingCharge = Arr::get($cart->checkout_data, 'shipping_data.shipping_charge', 0);
456
457 $checkoutData = Arr::wrap($cart->checkout_data);
458 Arr::set($checkoutData, 'form_data.' . $type . '_address_id', $addressId);
459 Arr::set($checkoutData, 'form_data.' . $type . '_country', $country);
460 Arr::set($checkoutData, 'form_data.' . $type . '_state', $state);
461
462 if ($type === 'billing' && Arr::get($checkoutData, 'form_data.ship_to_different', 'no') === 'no') {
463 Arr::set($checkoutData, 'form_data.shipping_address_id', $addressId);
464 Arr::set($checkoutData, 'form_data.shipping_country', $country);
465 Arr::set($checkoutData, 'form_data.shipping_state', $state);
466 }
467
468 $cart->checkout_data = $checkoutData;
469
470 $cart->save();
471
472 $customerId = Arr::get($address, 'address.customer_id');
473
474 $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
475 if (empty($customer) || $customer->id != $customerId) {
476 return [
477 'message' => __('You are not authorized to view this address', 'fluent-cart')
478 ];
479 }
480
481 $formattedAddress = Arr::get($address, 'address.formatted_address');
482
483 // Use output buffering to generate HTML
484 ob_start();
485
486 // Extract the address parts
487 $addressParts = [
488 trim(Arr::get($formattedAddress, 'address_1') ?? ''),
489 trim(Arr::get($formattedAddress, 'address_2') ?? ''),
490 trim(Arr::get($formattedAddress, 'city') ?? ''),
491 trim(Arr::get($formattedAddress, 'state') ?? ''),
492 trim(Arr::get($formattedAddress, 'country') ?? ''),
493 ];
494
495 // Filter out empty or null parts
496 $addressParts = array_filter($addressParts, function ($part) {
497 return $part !== '';
498 });
499
500 // Join parts with comma and space
501
502
503 do_action('fluent_cart/checkout/form_data_changed', [
504 'cart' => $cart
505 ]);
506
507 ob_start();
508 (new CartSummaryRender($cart))->render(false);
509 $cartSummaryInner = ob_get_clean();
510
511 $newTaxTotal = Arr::get($cart->checkout_data, 'tax_data.tax_total', 0);
512 $newShippingCharge = Arr::get($cart->checkout_data, 'shipping_data.shipping_charge', 0);
513
514 $checkoutData = [
515 'message' => __('Address Attached', 'fluent-cart'),
516 'fragments' => [
517 [
518 'selector' => '[data-fluent-cart-checkout-page-cart-items-wrapper]',
519 'content' => $cartSummaryInner,
520 'type' => 'replace'
521 ]
522 ],
523 'tax_total_changes' => $oldTaxTotal != $newTaxTotal,
524 'shipping_charge_changes' => $oldShippingCharge != $newShippingCharge
525 ];
526
527 return apply_filters('fluent_cart/checkout/checkout_data_changed', $checkoutData, ['cart' => $cart]);
528
529 }
530
531 public function handleGetCountryInfoAjax()
532 {
533 $timezone = App::request()->getSafe('timezone', 'sanitize_text_field');
534
535 $code = App::request()->getSafe('country_code', 'sanitize_text_field');
536 $countryInfo = LocalizationManager::getCountryInfoFromRequest($timezone, $code);
537
538 return [
539 'country_info' => $countryInfo
540 ];
541 }
542
543 public function getShippingMethodsListView(array $data)
544 {
545 $availableShippingMethods = AddressHelper::getAvailableShippingMethodLists($data);
546 $shippingMethods = Arr::get($availableShippingMethods, 'available_shipping_methods');
547 $countryCode = Arr::get($availableShippingMethods, 'country_code');
548 $status = Arr::get($availableShippingMethods, 'status');
549
550 if ($status === false) {
551 return false;
552 }
553
554 $cart = CartHelper::getCart();
555
556 $cartRender = (new CheckoutRenderer($cart));
557
558 ob_start();
559 $cartRender->getFragment('shipping_methods');
560 $content = ob_get_clean();
561
562 return [
563 'view' => $content,
564 'country_code' => $countryCode,
565 'shipping_methods' => $shippingMethods
566 ];
567 }
568
569 public function handleGetShippingMethodsListViewAjax()
570 {
571 $data = [
572 'country_code' => App::request()->get('country_code'),
573 'state' => App::request()->get('state'),
574 'timezone' => App::request()->get('timezone')
575 ];
576
577 $cart = CartHelper::getCart();
578
579 if (!$cart) {
580 return new \WP_Error('no_cart', __('No active cart found', 'fluent-cart'));
581 }
582
583 $availableShippingMethods = AddressHelper::getShippingMethods($data['country_code'], $data['state'], $data['timezone']);
584
585 ob_start();
586
587 $selectedId = Arr::get($cart->checkout_data, 'shipping_data.shipping_method_id', '');
588
589 if (!$availableShippingMethods || is_wp_error($availableShippingMethods)) {
590 (new ShippingMethodsRender($availableShippingMethods, $selectedId))->render();
591 } else {
592 foreach ($availableShippingMethods as $method) {
593 $method->charge_amount = CartHelper::calculateShippingMethodCharge($method, $cart->cart_data);
594 }
595
596 (new ShippingMethodsRender($availableShippingMethods, $selectedId))->render();
597 }
598
599 $shippingMethodsView = ob_get_clean();
600
601 return [
602 'status' => true,
603 'fragments' => [
604 [
605 'selector' => '[data-fluent-cart-checkout-page-shipping-methods-wrapper]',
606 'content' => $shippingMethodsView,
607 'type' => 'replace'
608 ]
609 ],
610 'country_code' => $data['country_code'],
611 ];
612 }
613
614 public function handleCartStatusAjax()
615 {
616 return CartResource::getStatus();
617 }
618
619 public function handleCartUpdateAjax()
620 {
621 $requestData = App::request()->all();
622
623 $data = [
624 'item_id' => (int)Arr::get($requestData, 'item_id'),
625 'quantity' => (int)Arr::get($requestData, 'quantity', 0),
626 'by_input' => Arr::get($requestData, 'by_input', false),
627 'is_custom' => Arr::get($requestData, 'is_custom', false)
628 ];
629
630 $cart = CartResource::update($data, '', $requestData);
631
632 if (is_wp_error($cart)) {
633 return $cart;
634 }
635
636 do_action('fluent_cart/checkout/cart_amount_updated', [
637 'cart' => $cart
638 ]);
639
640 $itemCount = 0;
641
642 if ($cart) {
643 $itemCount = count($cart->cart_data ?? []);
644 }
645
646 $cartItems = Arr::get(CartResource::getStatus(), 'cart_data', []);
647
648 $defaultOpen = Arr::get($requestData, 'open_cart', false);
649 $isAdminBarEnabled = Arr::get($requestData, 'is_admin_bar_enabled', false);
650
651 $fragments = [];
652 if (!empty($cartItems)) {
653 ob_start();
654 (new CartDrawerRenderer($cartItems, [
655 'item_count' => $itemCount,
656 'open_cart' => $defaultOpen,
657 'is_admin_bar_enabled' => $isAdminBarEnabled
658 ]))->render();
659 $cartDrawerView = ob_get_clean();
660
661 ob_start();
662 (new CartRenderer($cartItems))->renderItems($cartItems);
663 $cartDrawerItemsView = ob_get_clean();
664
665 ob_start();
666 (new CartRenderer($cartItems))->renderTotal();
667 $cartDrawerItemsTotalView = ob_get_clean();
668
669 ob_start();
670 (new CartDrawerRenderer($cartItems, [
671 'item_count' => $itemCount,
672 'open_cart' => $defaultOpen,
673 'is_admin_bar_enabled' => $isAdminBarEnabled
674 ]))->renderItemCount();
675 $cartItemCount = ob_get_clean();
676
677 $fragments = [
678 [
679 'selector' => '[data-fluent-cart-cart-drawer-container]',
680 'content' => $cartDrawerView,
681 'type' => 'replace'
682 ],
683 [
684 'selector' => '[data-fluent-cart-cart-content-wrapper]',
685 'content' => $cartDrawerItemsView,
686 'type' => 'replace'
687 ],
688 [
689 'selector' => '[data-fluent-cart-cart-total-wrapper]',
690 'content' => $cartDrawerItemsTotalView,
691 'type' => 'replace'
692 ],
693 [
694 'selector' => '.fluent-cart-cart-badge-count',
695 'content' => $itemCount > 0 ? $cartItemCount : '',
696 'type' => 'replace'
697 ]
698 ];
699 }
700
701 if (empty($cartItems)) {
702 ob_start();
703 (new CartRenderer($cartItems))->renderEmpty();
704 $cartDrawerItemsView = ob_get_clean();
705 $fragments[] = [
706 'selector' => '[data-fluent-cart-cart-content-wrapper]',
707 'content' => $cartDrawerItemsView,
708 'type' => 'replace'
709 ];
710 }
711
712
713 return [
714 'message' => __('Cart updated successfully', 'fluent-cart'),
715 'data' => apply_filters('fluent_cart/checkout/cart_updated', [
716 'cart' => $cart,
717 ]),
718 'fragments' => $fragments
719 ];
720 }
721
722 public function patchCheckoutData()
723 {
724 $cart = CartHelper::getCart();
725 if (!$cart) {
726 return new \WP_Error('no_cart', __('No active cart found', 'fluent-cart'));
727 }
728
729 $allData = App::request()->all();
730 $dataKey = (string) Arr::get($allData, 'data_key');
731 $dataValue = (string) Arr::get($allData, 'data_value');
732
733 if ($dataKey) {
734 $allData[$dataKey] = $dataValue;
735 }
736
737 $allData = AddressHelper::maybePushAddressDataForCheckout($allData, 'billing');
738
739 if (Arr::get($allData, 'ship_to_different') === 'yes') {
740 $allData = AddressHelper::maybePushAddressDataForCheckout($allData, 'shipping');
741 } else {
742 $allData = AddressHelper::mergeBillingWithShipping($allData);
743 }
744
745 $validKeys = [
746 'ship_to_different' => 'form_data.ship_to_different',
747 'billing_email' => 'form_data.billing_email',
748 'billing_address_id' => 'form_data.billing_address_id',
749 'shipping_address_id' => 'form_data.shipping_address_id',
750 'billing_company' => 'form_data.billing_company',
751 'order_notes' => 'form_data.order_notes',
752 'shipping_method_id' => 'shipping_data.shipping_method_id',
753 '_fct_pay_method' => 'form_data._fct_pay_method',
754 'billing_company_name' => 'form_data.billing_company_name',
755 'fct_billing_tax_id' => 'tax_data.vat_number'
756 ];
757
758 $addressFieldKeys = [
759 'full_name',
760 'country',
761 'address_1',
762 'address_2',
763 'state',
764 'city',
765 'postcode',
766 'company_name',
767 'phone'
768 ];
769
770 foreach ($addressFieldKeys as $addressFieldKey) {
771 $validKeys['billing_' . $addressFieldKey] = 'form_data.billing_' . $addressFieldKey;
772 $validKeys['shipping_' . $addressFieldKey] = 'form_data.shipping_' . $addressFieldKey;
773 }
774
775 $prevFlatData = [];
776 $prevCheckoutData = $cart->checkout_data;
777 foreach ($validKeys as $dataName => $dataPath) {
778 $prevFlatData[$dataName] = Arr::get($prevCheckoutData, $dataPath, null);
779 }
780 // $prevFlatData = array_filter($prevFlatData);
781 $prevFlatData = array_filter($prevFlatData, function ($value) {
782 return $value !== null;
783 });
784 $validData = Arr::only($allData, array_keys($validKeys));
785
786
787 $normalizeData = [];
788
789 foreach ($validData as $key => $value) {
790 $prevValue = Arr::get($prevFlatData, $key, null);
791 if ($prevValue != $value) {
792 $normalizeData[$key] = $value;
793 }
794 }
795
796 if (!$normalizeData) {
797 return [
798 'message' => __('No changes detected', 'fluent-cart')
799 ];
800 }
801
802 $normalizeData = $this->normalizeCheckoutChangeData($normalizeData, $allData);
803
804 // Validate these data and filter out invalid data points and also push the
805 // address data into this array.
806
807
808 $checkoutData = $cart->checkout_data;
809 foreach ($normalizeData as $normalizeKey => $normalizeValue) {
810 Arr::set($checkoutData, $validKeys[$normalizeKey], $normalizeValue);
811 }
812
813 $fillData = [
814 'checkout_data' => $checkoutData,
815 'cart_data' => $cart->cart_data,
816 'hook_changes' => [
817 'shipping' => false,
818 'tax' => false
819 ]
820 ];
821
822
823 $fillData = apply_filters('fluent_cart/checkout/before_patch_checkout_data', $fillData, [
824 'cart' => $cart,
825 'prev_data' => $prevFlatData,
826 'changes' => $normalizeData,
827 'all_data' => $allData
828 ]);
829
830 $hookChanges = Arr::get($fillData, 'hook_changes', []);
831 unset($fillData['hook_changes']);
832
833 if (isset($normalizeData['billing_email'])) {
834 $cart->email = $normalizeData['billing_email'];
835 }
836
837 if (isset($normalizeData['billing_full_name'])) {
838 $fullName = $normalizeData['billing_full_name'];
839 $nameParts = explode(' ', $fullName);
840 $cart->first_name = array_shift($nameParts);
841 $cart->last_name = implode(' ', $nameParts);
842 }
843
844 $cart->fill($fillData);
845 $cart->clearFeeCache();
846
847 $cart = CartHelper::getCart();
848 $sanitizedUtmData = UtmHelper::getUtmDataOfRequest();
849 $cart->utm_data = $sanitizedUtmData;
850
851 $cart->save();
852
853 $fragments = [];
854
855 $cartRender = (new CheckoutRenderer($cart));
856 $modalCheckoutRender = (new ModalCheckoutRenderer($cart));
857 $enableModalCheckout = App::request()->get('modal_checkout');
858
859 if (!empty($hookChanges['shipping'])) {
860 $fragments[] = [
861 'selector' => '[data-fluent-cart-checkout-page-shipping-methods-wrapper]',
862 'content' => $cartRender->getFragment('shipping_methods'),
863 'type' => 'replace'
864 ];
865 }
866
867 if (array_filter($hookChanges)) {
868 $fragments[] = [
869 'selector' => '[data-fluent-cart-checkout-page-cart-items-wrapper]',
870 'content' => $cartRender->getFragment('cart_summary_fragment'),
871 'type' => 'replace'
872 ];
873
874
875 // also update the payment methods
876 if ($enableModalCheckout === 'yes') {
877 $fragments[] = [
878 'selector' => '[data-fluent-cart-checkout-payment-methods]',
879 'content' => $modalCheckoutRender->getFragment('payment_methods'),
880 'type' => 'replace'
881 ];
882 } else {
883 $fragments[] = [
884 'selector' => '[data-fluent-cart-checkout-payment-methods]',
885 'content' => $cartRender->getFragment('payment_methods'),
886 'type' => 'replace'
887 ];
888 }
889
890
891 $fragments[] = [
892 'selector' => '[data-fct-modal-checkout-summary-group]',
893 'content' => $modalCheckoutRender->getFragment('summary_group'),
894 'type' => 'replace'
895 ];
896 }
897
898 // Re-render cart summary on payment method change (fees may depend on payment method)
899 if (isset($normalizeData['_fct_pay_method'])) {
900 $hasSummaryFragment = false;
901 foreach ($fragments as $fragment) {
902 if (Arr::get($fragment, 'selector') === '[data-fluent-cart-checkout-page-cart-items-wrapper]') {
903 $hasSummaryFragment = true;
904 break;
905 }
906 }
907
908 if (!$hasSummaryFragment) {
909 $fragments[] = [
910 'selector' => '[data-fluent-cart-checkout-page-cart-items-wrapper]',
911 'content' => $cartRender->getFragment('cart_summary_fragment'),
912 'type' => 'replace'
913 ];
914
915 $fragments[] = [
916 'selector' => '[data-fct-modal-checkout-summary-group]',
917 'content' => $modalCheckoutRender->getFragment('summary_group'),
918 'type' => 'replace'
919 ];
920 }
921 }
922
923 if (($dataKey === 'billing_address_id' || $dataKey === 'shipping_address_id') && $dataValue !== '') {
924 // get current customer
925
926 $customer = CustomerResource::getCurrentCustomer();
927
928 $type = $dataKey === 'billing_address_id' ? 'billing' : 'shipping';
929 $requiredShipping = $cart->requireShipping();
930 $config = [
931 'type' => $type,
932 'product_type' => $requiredShipping ? 'physical' : 'digital',
933 'with_shipping' => $requiredShipping
934 ];
935 if ($type === 'billing') {
936 $config['with_shipping'] = Arr::get($allData, 'ship_to_different', 'no') !== 'yes';
937 $config['billing_address_id'] = $dataValue;
938 } else {
939 $config['shipping_address_id'] = $dataValue;
940 }
941 $addresses = AddressHelper::getCustomerValidatedAddresses($config, $customer);
942 $address = AddressHelper::getPrimaryAddress($addresses, $config, $customer, $type);
943 $requirementsFields = CheckoutFieldsSchema::getCheckoutFieldsRequirements(
944 $type,
945 Arr::get($config, 'product_type'),
946 Arr::get($config, 'with_shipping')
947 );
948 ob_start();
949 (new AddressSelectRenderer($addresses, $address, $requirementsFields, $type))->renderAddressInfo();
950 $addressRender = ob_get_clean();
951
952 $fragments[] = [
953 'selector' => '#' . $type . '_address_wrapper [data-fluent-cart-checkout-page-form-address-info-wrapper]',
954 'content' => $addressRender,
955 'type' => 'replace'
956 ];
957 }
958
959 $fragments = apply_filters('fluent_cart/checkout/after_patch_checkout_data_fragments', $fragments, [
960 'cart' => $cart,
961 'changes' => $normalizeData
962 ]);
963
964 return [
965 'message' => __('Data saved successfully', 'fluent-cart'),
966 'changes' => $normalizeData,
967 'fragments' => $fragments,
968 'cart' => $cart
969 ];
970 }
971
972 public function handleSaveCustomerDataAjax()
973 {
974
975 $key = sanitize_text_field(App::request()->get('data_key'));
976 $value = sanitize_text_field(App::request()->get('data_value'));
977
978 $cart = CartHelper::getCart();
979 if (!$cart) {
980 return new \WP_Error('no_cart', __('No active cart found', 'fluent-cart'));
981 }
982
983 $addressFieldKeys = [
984 'full_name',
985 'country',
986 'address_1',
987 'address_2',
988 'state',
989 'city',
990 'postcode'
991 ];
992
993 $validKeys = [
994 'ship_to_different' => 'form_data.ship_to_different',
995 'billing_email' => 'form_data.billing_email',
996 'billing_address_id' => 'form_data.billing_address_id',
997 'shipping_address_id' => 'form_data.shipping_address_id',
998 'billing_company' => 'form_data.billing_company',
999 'order_notes' => 'form_data.order_notes',
1000 'shipping_method_id' => 'shipping_data.shipping_method_id',
1001 '_fct_pay_method' => 'form_data._fct_pay_method',
1002 ];
1003
1004 foreach ($addressFieldKeys as $addressFieldKey) {
1005 $validKeys['billing_' . $addressFieldKey] = 'form_data.billing_' . $addressFieldKey;
1006 $validKeys['shipping_' . $addressFieldKey] = 'form_data.shipping_' . $addressFieldKey;
1007 }
1008
1009 if (!isset($validKeys[$key])) {
1010 return new \WP_Error('invalid_key', __('Invalid data key', 'fluent-cart'));
1011 }
1012
1013 $checkoutData = Arr::wrap($cart->checkout_data);
1014
1015 $prevValue = Arr::get($checkoutData, $validKeys[$key], '');
1016 if ($prevValue === $value) {
1017 return [
1018 'message' => __('Updated', 'fluent-cart')
1019 ];
1020 }
1021
1022 $oldCheckoutData = $checkoutData;
1023 Arr::set($checkoutData, $validKeys[$key], $value);
1024
1025 $fillData = apply_filters('fluent_cart/checkout/before_patch_checkout_data', [
1026 'checkout_data' => $checkoutData,
1027 'cart_data' => $cart->cart_data,
1028 'hook_changes' => [
1029 'shipping' => false,
1030 'tax' => false
1031 ]
1032 ], [
1033 'cart' => $cart,
1034 'key' => $key,
1035 'value' => $value,
1036 'prev_value' => $prevValue,
1037 'old_checkout_data' => $oldCheckoutData
1038 ]);
1039
1040 $changes = Arr::get($fillData, 'hook_changes', []);
1041 unset($fillData['hook_changes']);
1042
1043
1044 $cart->fill($fillData);
1045 $cart->save();
1046 $fragments = [];
1047
1048
1049 if (Arr::get($changes, 'shipping', false)) {
1050 $fragments[] = [
1051 'selector' => '[data-fluent-cart-checkout-page-shipping-methods-wrapper]',
1052 'content' => (new CheckoutRenderer($cart))->getFragment('shipping_methods'),
1053 'type' => 'replace'
1054 ];
1055 }
1056 //(new CartSummaryRender($cart))->render(false);
1057
1058 ob_start();
1059 (new CartSummaryRender($cart))->render(false);
1060 $render = ob_get_clean();
1061 if (array_filter($changes)) {
1062 $fragments[] = [
1063 'selector' => '[data-fluent-cart-checkout-page-cart-items-wrapper]',
1064 //'content' => (new CheckoutRender($cart))->getFragment('cart_summary_fragment'),
1065 'content' => $render,
1066 'type' => 'replace'
1067 ];
1068 }
1069
1070 return [
1071 'fragments' => $fragments,
1072 'message' => __('Data saved successfully', 'fluent-cart'),
1073 'tax_total_Changes' => Arr::get($changes, 'tax', false),
1074 'shipping_charge_changes' => Arr::get($changes, 'shipping', false),
1075 'notify' => false,
1076 'cart' => $cart
1077 ];
1078
1079 // Shipping Module
1080
1081 add_action('fluent_cart/checkout/customer_data_saved', function ($data) {
1082 // Recalculate tax if needed
1083
1084 do_action('fluent_cart/checkout/tax_data_changed', $data);
1085
1086 });
1087
1088 do_action('fluent_cart/checkout/customer_data_saved', [
1089 'cart' => $cart,
1090 'key' => $key,
1091 'value' => $value,
1092 'old_value' => $prevValue,
1093 'old_data' => $oldCheckoutData
1094 ]);
1095
1096 $didTaxChanges = did_action('fluent_cart/checkout/tax_data_changed');
1097 $didShippingChanges = did_action('fluent_cart/checkout/shipping_data_changed');
1098
1099
1100 if ($cart) {
1101 $checkoutRender = new CheckoutRenderer($cart);
1102 $shippingChanged = false;
1103 // we have to decide for which keys we will update shipping option
1104 $fragments = [];
1105 if ($cart->requireShipping()) {
1106 $watchingKeys = ['ship_to_different', 'billing_address', 'billing_country', 'billing_state'];
1107 if (Arr::get($cart->checkout_data, 'form_data.ship_to_different', null) === 'yes') {
1108 $watchingKeys = ['ship_to_different', 'shipping_address', 'shipping_country', 'shipping_state'];
1109 }
1110
1111 if (in_array($key, $watchingKeys)) {
1112 $fragments[] = [
1113 'selector' => '[data-fluent-cart-checkout-page-shipping-methods-wrapper]',
1114 'content' => $checkoutRender->getFragment('shipping_methods'),
1115 'type' => 'replace'
1116 ];
1117 }
1118
1119 $shippingChanged = true;
1120 }
1121
1122 $oldTaxTotal = Arr::get($cart->checkout_data, 'tax_data.tax_total', 0);
1123 $oldShippingCharge = Arr::get($cart->checkout_data, 'shipping_data.shipping_charge', 0);
1124
1125 $countryCode = Arr::get($cart->checkout_data, 'form_data.shipping_country');
1126
1127 if (!$countryCode) {
1128 $countryCode = Arr::get($cart->checkout_data, 'form_data.billing_country');
1129 }
1130
1131 if (Arr::get($cart->checkout_data, 'form_data.ship_to_different', 'no') === 'no') {
1132 $countryCode = Arr::get($cart->checkout_data, 'form_data.billing_country');
1133 }
1134
1135 $data = [
1136 'country_code' => $countryCode,
1137 'timezone' => ''
1138 ];
1139
1140 $list = $this->getShippingMethodsListView($data);
1141
1142 if ($list === false) {
1143 $cart->checkout_data = array_merge($cart->checkout_data, [
1144 'shipping_data' => [
1145 'shipping_method_id' => null,
1146 'shipping_charge' => 0
1147 ]
1148 ]);
1149 $cart->save();
1150 }
1151
1152 do_action('fluent_cart/checkout/form_data_changed', [
1153 'cart' => $cart
1154 ]);
1155
1156 $fragments[] = [
1157 'selector' => '[data-fluent-cart-checkout-page-cart-items-wrapper]',
1158 'content' => $checkoutRender->getFragment('cart_summary_fragment'),
1159 'type' => 'replace'
1160 ];
1161
1162 $newTaxTotal = Arr::get($cart->checkout_data, 'tax_data.tax_total', 0);
1163 $newShippingCharge = Arr::get($cart->checkout_data, 'shipping_data.shipping_charge', 0);
1164
1165 $checkoutData = [
1166 'fragments' => $fragments,
1167 'message' => __('Data saved successfully', 'fluent-cart'),
1168 'tax_total_Changes' => $oldTaxTotal != $newTaxTotal,
1169 'shipping_charge_changes' => $oldShippingCharge != $newShippingCharge,
1170 'notify' => false
1171 ];
1172
1173 return apply_filters('fluent_cart/checkout/checkout_data_changed', $checkoutData, ['cart' => $cart]);
1174 }
1175
1176 return [
1177 'message' => __('Failed to save data', 'fluent-cart'),
1178 'notify' => false
1179 ];
1180 }
1181
1182 public function handleOrderBumpRequest()
1183 {
1184 $requestData = App::request()->all();
1185
1186 $cart = CartHelper::getCart();
1187 if (!$cart) {
1188 return new \WP_Error('no_cart', __('No active cart found', 'fluent-cart'));
1189 }
1190
1191 $checkoutData = $cart->checkout_data;
1192 if (!empty($checkoutData['upgrade_data']) || !empty($checkoutData['is_locked'])) {
1193 return new \WP_Error('invalid_request', __('This cart is locked or already has an upgrade applied.', 'fluent-cart'));
1194 }
1195
1196 $upgradeFromVariationId = (int) Arr::get($requestData, 'upgrade_form', 0);
1197 $targetVariationId = (int) Arr::get($requestData, 'upgrade_to', 0);
1198 $bumpId = (int) Arr::get($requestData, 'bump_id', 0);
1199
1200 if ($bumpId) {
1201 $response = new \WP_Error('invalid_bump', __('Could not apply item at this time.', 'fluent-cart'));
1202 return apply_filters('fluent_cart/apply_order_bump', $response, [
1203 'bump_id' => $bumpId,
1204 'cart' => $cart,
1205 'request_data' => $requestData
1206 ]);
1207 }
1208
1209 if (!$upgradeFromVariationId || !$targetVariationId) {
1210 return new \WP_Error('invalid_request', __('Invalid upgrade request.', 'fluent-cart'));
1211 }
1212
1213 $productVariation = ProductVariation::query()->find($targetVariationId);
1214
1215 if (!$productVariation || !$productVariation->canPurchase()) {
1216 return new \WP_Error('invalid_variation', __('The selected product variation is not available for purchase.', 'fluent-cart'));
1217 }
1218
1219 $cart->removeItem($upgradeFromVariationId);
1220 $cart = $cart->addByVariation($productVariation, [
1221 'quantity' => 1,
1222 'append' => false
1223 ]);
1224
1225 if (is_wp_error($cart)) {
1226 return $cart;
1227 }
1228
1229 $isUpgraded = Arr::get($requestData, 'is_upgraded') === 'yes';
1230 if ($isUpgraded) {
1231 $checkoutData['order_bump'] = [
1232 'upgraded_from' => $upgradeFromVariationId,
1233 'upgraded_to' => $targetVariationId
1234 ];
1235 $existingActions = Arr::get($checkoutData, '__on_success_actions__', []);
1236 if (!is_array($existingActions)) {
1237 $existingActions = [];
1238 }
1239 $existingActions[] = 'fluent_cart/order_bump_succeed';
1240 $checkoutData['__on_success_actions__'] = $existingActions;
1241 } else {
1242 unset($checkoutData['order_bump']);
1243 $existingActions = Arr::get($checkoutData, '__on_success_actions__', []);
1244 if (!is_array($existingActions)) {
1245 $existingActions = [];
1246 }
1247
1248 if (!empty($existingActions) && in_array('fluent_cart/order_bump_succeed', $existingActions)) {
1249 $existingActions = array_diff($existingActions, ['fluent_cart/order_bump_succeed']);
1250 $existingActions = array_values($existingActions);
1251 }
1252
1253 if (is_array($existingActions)) {
1254 $existingActions = array_filter($existingActions, function ($action) {
1255 return $action !== 'fluent_cart/order_bump_succeed';
1256 });
1257 }
1258
1259 $checkoutData['__on_success_actions__'] = $existingActions;
1260 }
1261
1262 $cart->checkout_data = $checkoutData;
1263 $cart->save();
1264
1265 do_action('fluent_cart/checkout/cart_amount_updated', [
1266 'cart' => $cart
1267 ]);
1268
1269 return [
1270 'message' => $isUpgraded ? __('Item has been applied successfully', 'fluent-cart') : __('Item has been reverted successfully', 'fluent-cart')
1271 ];
1272 }
1273
1274
1275 public function normalizeCheckoutChangeData($changedData, $allData): array
1276 {
1277
1278 $sanitizedData = [];
1279 $errors = [];
1280 foreach ($changedData as $dataKey => $dataValue) {
1281 if (in_array($dataKey, ['billing_full_name', 'shipping_full_name'])) {
1282 $sanitizedData[$dataKey] = sanitize_text_field($dataValue);
1283 } else if (in_array($dataKey, ['billing_email', 'shipping_email'])) {
1284 if (empty($dataValue)) {
1285 $sanitizedData[$dataKey] = '';
1286 } else {
1287 $sanitizedData[$dataKey] = sanitize_email($dataValue ?? '');
1288 }
1289 } else if (in_array($dataKey, ['billing_company_name', 'shipping_company_name'])) {
1290 $sanitizedData[$dataKey] = sanitize_text_field($dataValue);
1291 } else if ($dataKey === 'billing_state' && !empty($dataValue)) {
1292 $billingCountry = Arr::get($allData, 'billing_country');
1293
1294 $states = LocalizationManager::getInstance()->statesOptions($billingCountry);
1295 $countryStates = array_values(array_column($states, 'value'));
1296 if (!empty($states) && !in_array($dataValue, $countryStates)) {
1297 $errors[$dataKey] = __('Invalid state code.', 'fluent-cart');
1298 $sanitizedData[$dataKey] = null;
1299 } else {
1300 $sanitizedData[$dataKey] = $dataValue;
1301 }
1302 } else if ($dataKey === 'shipping_state' && !empty($dataValue)) {
1303 $shipToDifferent = ((Arr::get($allData, 'ship_to_different', 'no') === 'yes') == 'yes');
1304 $shippingCountry = $shipToDifferent ? Arr::get($allData, 'shipping_country') : Arr::get($allData, 'billing_country');
1305 $states = LocalizationManager::getInstance()->statesOptions($shippingCountry);
1306 if (!empty($states) && !in_array($dataValue, array_column($states, 'value'))) {
1307 $errors[$dataKey] = __('Invalid state code.', 'fluent-cart');
1308 $sanitizedData[$dataKey] = null;
1309 } else {
1310 $sanitizedData[$dataKey] = $dataValue;
1311 }
1312 } else if ($dataKey === '_fct_pay_method') {
1313 $value = sanitize_text_field($dataValue);
1314 $methods = PaymentMethods::getActiveMeta();
1315 $methods = array_column($methods, 'route');
1316 if (!in_array($value, $methods)) {
1317 $value = null;
1318 $errors[$dataKey] = __('Invalid payment method.', 'fluent-cart');
1319 }
1320 $sanitizedData[$dataKey] = $value;
1321 } else if ($dataKey === 'shipping_method_id') {
1322 $shipToDifferent = ((Arr::get($allData, 'ship_to_different', 'no') === 'yes') == 'yes');
1323 $countryKey = $shipToDifferent ? 'shipping_country' : 'billing_country';
1324 $stateKey = $shipToDifferent ? 'shipping_state' : 'billing_state';
1325 $shippingCountry = Arr::has($changedData, $countryKey) ? Arr::get($changedData, $countryKey) : Arr::get($allData, $countryKey);
1326 $shippingState = Arr::has($changedData, $stateKey) ? Arr::get($changedData, $stateKey) : Arr::get($allData, $stateKey);
1327 $availableShippingMethods = AddressHelper::getShippingMethods($shippingCountry, $shippingState);
1328
1329 $found = false;
1330 foreach ($availableShippingMethods as $method) {
1331 if ($method->id == $dataValue) {
1332 $found = true;
1333 break;
1334 }
1335 }
1336
1337 if (!$found) {
1338 $errors[$dataKey] = __('Invalid shipping method.', 'fluent-cart');
1339 $sanitizedData[$dataKey] = null;
1340 } else {
1341 $sanitizedData[$dataKey] = sanitize_text_field($dataValue);
1342 }
1343 } else {
1344 $sanitizedData[$dataKey] = sanitize_text_field($dataValue);
1345 }
1346 }
1347
1348 return $sanitizedData;
1349 }
1350
1351 private function pushAddressData($data, $type = 'billing')
1352 {
1353 return AddressHelper::maybePushAddressDataForCheckout($data, $type);
1354 }
1355
1356 public function getProductModalView()
1357 {
1358 $productId = App::request()->getSafe('product_id', 'intval');
1359 $product = Product::query()->find($productId);
1360
1361 if (!$product) {
1362 return $this->sendError([
1363 'message' => __('Product not found', 'fluent-cart')
1364 ]);
1365 }
1366 ob_start();
1367 (new ProductModalRenderer($product))->render();
1368 $view = ob_get_clean();
1369 return [
1370 'view' => $view
1371 ];
1372 }
1373
1374 }
1375