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

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

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