PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.6
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.6
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
← All changes | app/Hooks/Cart/WebCheckoutHandler.php +89 -231 1.3.28 → 1.6.6 View file →
@@ -16,8 +16,9 @@
16 16 use FluentCart\App\Models\Product;
17 17 use FluentCart\App\Models\ProductVariation;
18 18 use FluentCart\App\Models\ShippingMethod;
19 19 use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
20 +use FluentCart\App\Modules\Tax\TaxModule;
20 21 use FluentCart\App\Services\Localization\LocalizationManager;
21 22 use FluentCart\App\Services\Renderer\AddressSelectRenderer;
22 23 use FluentCart\App\Services\Renderer\CartDrawerRenderer;
23 24 use FluentCart\App\Services\Renderer\CartRenderer;
@@ -60,8 +61,14 @@
60 61 public function globalCheckoutRouteHandler()
61 62 {
62 63 nocache_headers();
63 64
65 + $nonce = isset($_SERVER['HTTP_X_WP_NONCE']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_X_WP_NONCE'])) : '';
66 + if (!wp_verify_nonce($nonce, 'fluentcart') && !wp_verify_nonce($nonce, 'wp_rest')) {
67 + wp_send_json(['message' => __('Invalid nonce', 'fluent-cart')], 403);
68 + return;
69 + }
70 +
64 71 $startedAt = microtime(true);
65 72
66 73 $action = App::request()->get('fc_checkout_action');
67 74 $result = [];
@@ -253,8 +260,14 @@
253 260 'content' => $modalCheckoutRender->getFragment('summary_group'),
254 261 'type' => 'replace'
255 262 ];
256 263
264 + $fragments[] = [
265 + 'selector' => '[data-fct-modal-checkout-summary]',
266 + 'content' => $modalCheckoutRender->getFragment('checkout_summary'),
267 + 'type' => 'replace'
268 + ];
269 +
257 270 return [
258 271 'fragments' => $fragments,
259 272 'cart' => $cart,
260 273 'total' => $cartTotal,
@@ -315,9 +328,15 @@
315 328 'content' => $modalCheckoutRender->getFragment('summary_group'),
316 329 'type' => 'replace'
317 330 ];
318 331
332 + $fragments[] = [
333 + 'selector' => '[data-fct-modal-checkout-summary]',
334 + 'content' => $modalCheckoutRender->getFragment('checkout_summary'),
335 + 'type' => 'replace'
336 + ];
319 337
338 +
320 339 return [
321 340 'fragments' => $fragments,
322 341 'total' => $cartTotal,
323 342 'formatted_total' => Helper::toDecimal($cartTotal),
@@ -390,8 +409,14 @@
390 409 $totalPrice = apply_filters('fluent_cart/cart/estimated_total', $totalPrice, [
391 410 'cart' => $cart
392 411 ]);
393 412
413 + // Prorate credit and upgrade discount (plan upgrade) are post-tax adjustments on
414 + // the payable total.
415 + $totalPrice = max(0, $totalPrice
416 + - (int) Arr::get($cart->checkout_data, 'prorate_credit.amount', 0)
417 + - (int) Arr::get($cart->checkout_data, 'upgrade_discount.amount', 0));
418 +
394 419 $fragments = [];
395 420
396 421 $cartRender = (new CheckoutRenderer($cart));
397 422 $modalCheckoutRender = (new ModalCheckoutRenderer($cart));
@@ -453,8 +478,9 @@
453 478
454 479 $oldTaxTotal = Arr::get($cart->checkout_data, 'tax_data.tax_total', 0);
455 480 $oldShippingCharge = Arr::get($cart->checkout_data, 'shipping_data.shipping_charge', 0);
456 481
482 + $previousFormData = Arr::get($cart->checkout_data, 'form_data', []);
457 483 $checkoutData = Arr::wrap($cart->checkout_data);
458 484 Arr::set($checkoutData, 'form_data.' . $type . '_address_id', $addressId);
459 485 Arr::set($checkoutData, 'form_data.' . $type . '_country', $country);
460 486 Arr::set($checkoutData, 'form_data.' . $type . '_state', $state);
@@ -464,8 +490,14 @@
464 490 Arr::set($checkoutData, 'form_data.shipping_country', $country);
465 491 Arr::set($checkoutData, 'form_data.shipping_state', $state);
466 492 }
467 493
494 + $checkoutData = (new TaxModule())->maybeInvalidateVatValidationForCountryChange($checkoutData, [
495 + 'ship_to_different' => Arr::get($previousFormData, 'ship_to_different', 'no'),
496 + 'billing_country' => Arr::get($previousFormData, 'billing_country', ''),
497 + 'shipping_country' => Arr::get($previousFormData, 'shipping_country', ''),
498 + ]);
499 +
468 500 $cart->checkout_data = $checkoutData;
469 501
470 502 $cart->save();
471 503
@@ -585,8 +617,10 @@
585 617 ob_start();
586 618
587 619 $selectedId = Arr::get($cart->checkout_data, 'shipping_data.shipping_method_id', '');
588 620
621 + $selectedId = CartHelper::resolveAutoSelectShippingMethod($cart, $availableShippingMethods ?: [], $selectedId);
622 +
589 623 if (!$availableShippingMethods || is_wp_error($availableShippingMethods)) {
590 624 (new ShippingMethodsRender($availableShippingMethods, $selectedId))->render();
591 625 } else {
592 626 foreach ($availableShippingMethods as $method) {
@@ -607,8 +641,9 @@
607 641 'type' => 'replace'
608 642 ]
609 643 ],
610 644 'country_code' => $data['country_code'],
645 + 'shipping_method_id' => $selectedId ?: null,
611 646 ];
612 647 }
613 648
614 649 public function handleCartStatusAjax()
@@ -726,10 +761,10 @@
726 761 return new \WP_Error('no_cart', __('No active cart found', 'fluent-cart'));
727 762 }
728 763
729 764 $allData = App::request()->all();
730 - $dataKey = (string) Arr::get($allData, 'data_key');
731 - $dataValue = (string) Arr::get($allData, 'data_value');
765 + $dataKey = sanitize_text_field((string) Arr::get($allData, 'data_key'));
766 + $dataValue = sanitize_text_field((string) Arr::get($allData, 'data_value'));
732 767
733 768 if ($dataKey) {
734 769 $allData[$dataKey] = $dataValue;
735 770 }
@@ -751,9 +786,11 @@
751 786 'order_notes' => 'form_data.order_notes',
752 787 'shipping_method_id' => 'shipping_data.shipping_method_id',
753 788 '_fct_pay_method' => 'form_data._fct_pay_method',
754 789 'billing_company_name' => 'form_data.billing_company_name',
755 - 'fct_billing_tax_id' => 'tax_data.vat_number'
790 + 'billing_legal_registration_id' => 'form_data.billing_legal_registration_id',
791 + 'is_business' => 'form_data.is_business',
792 + 'fct_billing_tax_id' => 'tax_data.vat_number',
756 793 ];
757 794
758 795 $addressFieldKeys = [
759 796 'full_name',
@@ -792,8 +829,16 @@
792 829 $normalizeData[$key] = $value;
793 830 }
794 831 }
795 832
833 + // Force-check the explicit data_key field — bulk POST may have already stored it as a side-effect.
834 + if ($dataKey && isset($validKeys[$dataKey]) && !isset($normalizeData[$dataKey])) {
835 + $storedValue = (string) Arr::get($prevCheckoutData, $validKeys[$dataKey], '');
836 + if ($storedValue !== $dataValue) {
837 + $normalizeData[$dataKey] = $dataValue;
838 + }
839 + }
840 +
796 841 if (!$normalizeData) {
797 842 return [
798 843 'message' => __('No changes detected', 'fluent-cart')
799 844 ];
@@ -809,8 +854,9 @@
809 854 foreach ($normalizeData as $normalizeKey => $normalizeValue) {
810 855 Arr::set($checkoutData, $validKeys[$normalizeKey], $normalizeValue);
811 856 }
812 857
858 + $oldTaxTotal = (int) Arr::get($cart->checkout_data, 'tax_data.tax_total', 0);
813 859 $fillData = [
814 860 'checkout_data' => $checkoutData,
815 861 'cart_data' => $cart->cart_data,
816 862 'hook_changes' => [
@@ -841,8 +887,10 @@
841 887 $cart->last_name = implode(' ', $nameParts);
842 888 }
843 889
844 890 $cart->fill($fillData);
891 + $newTaxTotal = (int) Arr::get($cart->checkout_data, 'tax_data.tax_total', 0);
892 + $taxAmountChanged = $oldTaxTotal !== $newTaxTotal;
845 893 $cart->clearFeeCache();
846 894
847 895 $cart = CartHelper::getCart();
848 896 $sanitizedUtmData = UtmHelper::getUtmDataOfRequest();
@@ -870,30 +918,36 @@
870 918 'content' => $cartRender->getFragment('cart_summary_fragment'),
871 919 'type' => 'replace'
872 920 ];
873 921
922 + if ($taxAmountChanged || !empty($hookChanges['shipping'])) {
923 + // also update the payment methods
924 + if ($enableModalCheckout === 'yes') {
925 + $fragments[] = [
926 + 'selector' => '[data-fluent-cart-checkout-payment-methods]',
927 + 'content' => $modalCheckoutRender->getFragment('payment_methods'),
928 + 'type' => 'replace'
929 + ];
930 + } else {
931 + $fragments[] = [
932 + 'selector' => '[data-fluent-cart-checkout-payment-methods]',
933 + 'content' => $cartRender->getFragment('payment_methods'),
934 + 'type' => 'replace'
935 + ];
936 + }
874 937
875 - // also update the payment methods
876 - if ($enableModalCheckout === 'yes') {
877 938 $fragments[] = [
878 - 'selector' => '[data-fluent-cart-checkout-payment-methods]',
879 - 'content' => $modalCheckoutRender->getFragment('payment_methods'),
880 - 'type' => 'replace'
939 + 'selector' => '[data-fct-modal-checkout-summary-group]',
940 + 'content' => $modalCheckoutRender->getFragment('summary_group'),
941 + 'type' => 'replace'
881 942 ];
882 - } else {
943 +
883 944 $fragments[] = [
884 - 'selector' => '[data-fluent-cart-checkout-payment-methods]',
885 - 'content' => $cartRender->getFragment('payment_methods'),
886 - 'type' => 'replace'
945 + 'selector' => '[data-fct-modal-checkout-summary]',
946 + 'content' => $modalCheckoutRender->getFragment('checkout_summary'),
947 + 'type' => 'replace'
887 948 ];
888 949 }
889 -
890 -
891 - $fragments[] = [
892 - 'selector' => '[data-fct-modal-checkout-summary-group]',
893 - 'content' => $modalCheckoutRender->getFragment('summary_group'),
894 - 'type' => 'replace'
895 - ];
896 950 }
897 951
898 952 // Re-render cart summary on payment method change (fees may depend on payment method)
899 953 if (isset($normalizeData['_fct_pay_method'])) {
@@ -916,8 +970,14 @@
916 970 'selector' => '[data-fct-modal-checkout-summary-group]',
917 971 'content' => $modalCheckoutRender->getFragment('summary_group'),
918 972 'type' => 'replace'
919 973 ];
974 +
975 + $fragments[] = [
976 + 'selector' => '[data-fct-modal-checkout-summary]',
977 + 'content' => $modalCheckoutRender->getFragment('checkout_summary'),
978 + 'type' => 'replace'
979 + ];
920 980 }
921 981 }
922 982
923 983 if (($dataKey === 'billing_address_id' || $dataKey === 'shipping_address_id') && $dataValue !== '') {
@@ -968,218 +1028,8 @@
968 1028 'cart' => $cart
969 1029 ];
970 1030 }
971 1031
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 1032 public function handleOrderBumpRequest()
1183 1033 {
1184 1034 $requestData = App::request()->all();
1185 1035
@@ -1188,9 +1038,9 @@
1188 1038 return new \WP_Error('no_cart', __('No active cart found', 'fluent-cart'));
1189 1039 }
1190 1040
1191 1041 $checkoutData = $cart->checkout_data;
1192 - if (!empty($checkoutData['upgrade_data']) || !empty($checkoutData['is_locked'])) {
1042 + if (!$cart->acceptsAdditionalItems()) {
1193 1043 return new \WP_Error('invalid_request', __('This cart is locked or already has an upgrade applied.', 'fluent-cart'));
1194 1044 }
1195 1045
1196 1046 $upgradeFromVariationId = (int) Arr::get($requestData, 'upgrade_form', 0);
@@ -1205,8 +1055,14 @@
1205 1055 'request_data' => $requestData
1206 1056 ]);
1207 1057 }
1208 1058
1059 + // A locked cart may still take bumps through the accepts_additional_items
1060 + // filter, but must never let an item it pinned be swapped out.
1061 + if (Arr::get($checkoutData, 'is_locked') === 'yes') {
1062 + return new \WP_Error('invalid_request', __('This cart is locked and cannot be modified.', 'fluent-cart'));
1063 + }
1064 +
1209 1065 if (!$upgradeFromVariationId || !$targetVariationId) {
1210 1066 return new \WP_Error('invalid_request', __('Invalid upgrade request.', 'fluent-cart'));
1211 1067 }
1212 1068
@@ -1285,10 +1141,12 @@
1285 1141 $sanitizedData[$dataKey] = '';
1286 1142 } else {
1287 1143 $sanitizedData[$dataKey] = sanitize_email($dataValue ?? '');
1288 1144 }
1289 - } else if (in_array($dataKey, ['billing_company_name', 'shipping_company_name'])) {
1145 + } else if (in_array($dataKey, ['billing_company_name', 'shipping_company_name', 'billing_legal_registration_id'])) {
1290 1146 $sanitizedData[$dataKey] = sanitize_text_field($dataValue);
1147 + } else if ($dataKey === 'is_business') {
1148 + $sanitizedData[$dataKey] = $dataValue === 'yes' ? 'yes' : 'no';
1291 1149 } else if ($dataKey === 'billing_state' && !empty($dataValue)) {
1292 1150 $billingCountry = Arr::get($allData, 'billing_country');
1293 1151
1294 1152 $states = LocalizationManager::getInstance()->statesOptions($billingCountry);