| @@ -21,8 +21,9 @@ | ||
| 21 | 21 | use FluentCart\App\Models\Order; |
| 22 | 22 | use FluentCart\App\Models\OrderAddress; |
| 23 | 23 | use FluentCart\App\Models\ShippingMethod; |
| 24 | 24 | use FluentCart\App\Services\CheckoutService; |
| 25 | +use FluentCart\App\Services\CustomerIdentity\EmailVerificationService; | |
| 25 | 26 | use FluentCart\App\Services\Localization\LocalizationManager; |
| 26 | 27 | use FluentCart\App\Services\OrderService; |
| 27 | 28 | use FluentCart\App\Services\Payments\PaymentHelper; |
| 28 | 29 | use FluentCart\App\Services\Payments\PaymentInstance; |
| @@ -163,9 +164,14 @@ | ||
| 163 | 164 | } |
| 164 | 165 | |
| 165 | 166 | $orderData = OrderService::groupSanitizedData($validatedData); |
| 166 | 167 | |
| 167 | - $shippingMethodId = Arr::get($orderData, 'others.fc_shipping_method'); | |
| 168 | + // The form posts the method twice: the checked radio (fc_shipping_method) and its | |
| 169 | + // hidden mirror (fc_selected_shipping_method). validateData() checks only the mirror | |
| 170 | + // against the address's zones, so pricing from the radio let a request pass with one | |
| 171 | + // method and be charged by another, from a zone the address is not in. Read from | |
| 172 | + // $validatedData, not the sanitized copy in others: that is the exact integer checked. | |
| 173 | + $shippingMethodId = (int) Arr::get($validatedData, 'fc_selected_shipping_method', 0); | |
| 168 | 174 | |
| 169 | 175 | $shippingMethod = null; |
| 170 | 176 | $shippingCharge = 0; |
| 171 | 177 | if (!$cartCheckoutService->isAllDigital()) { |
| @@ -291,14 +297,16 @@ | ||
| 291 | 297 | } |
| 292 | 298 | |
| 293 | 299 | private static function getOrCreateCustomer(CartCheckoutHelper $cartCheckoutHelper, $orderData) |
| 294 | 300 | { |
| 295 | - $customerEmail = static::getCustomerEmail($orderData['billing_address']); | |
| 296 | - if (is_user_logged_in()) { | |
| 297 | - $customerEmail = wp_get_current_user()->user_email; | |
| 298 | - Arr::set($orderData, 'billing_address.email', $customerEmail); | |
| 301 | + $customer = is_user_logged_in() ? ApiCustomerResource::getCurrentCustomer() : null; | |
| 302 | + $email = static::getCustomerEmail($orderData['billing_address']); | |
| 303 | + Arr::set($orderData, 'billing_address.email', $email); | |
| 304 | + if (!$customer) { | |
| 305 | + // Reuse the email's customer for the purchase without granting ownership. | |
| 306 | + $customer = Customer::query()->where('email', $email)->orderBy('id')->first(); | |
| 299 | 307 | } |
| 300 | - $customer = $cartCheckoutHelper->getCustomer($customerEmail); | |
| 308 | + | |
| 301 | 309 | return static::createCustomerWithAddress( |
| 302 | 310 | $customer, |
| 303 | 311 | $orderData, |
| 304 | 312 | $orderData['billing_address'], |
| @@ -431,15 +439,12 @@ | ||
| 431 | 439 | |
| 432 | 440 | static::syncCustomerNames($order, $args); |
| 433 | 441 | $cart = CartHelper::getCart(); |
| 434 | 442 | |
| 435 | - $utmData = []; | |
| 436 | - if (!empty($cart) && is_array($cart->utm_data) && count($cart->utm_data) > 0) { | |
| 437 | - $utmData = $cart->utm_data; | |
| 438 | - } | |
| 439 | - | |
| 440 | - $requestUtmData = UtmHelper::getUtmDataOfRequest(); | |
| 441 | - $utmData = wp_parse_args($requestUtmData, $utmData); | |
| 443 | + $utmData = UtmHelper::resolveUtmData( | |
| 444 | + UtmHelper::getUtmDataOfRequest(), | |
| 445 | + !empty($cart) ? $cart->utm_data : [] | |
| 446 | + ); | |
| 442 | 447 | UtmHelper::addUtmToOrder($order->id, $utmData); |
| 443 | 448 | |
| 444 | 449 | $prevOrder = Arr::get($args, 'prev_order', null); |
| 445 | 450 | |
| @@ -526,9 +531,10 @@ | ||
| 526 | 531 | private static function syncCustomerNames($order, $args) |
| 527 | 532 | { |
| 528 | 533 | $customer = $order->customer; |
| 529 | 534 | |
| 530 | - if (empty($customer)) { | |
| 535 | + if (empty($customer) || !is_user_logged_in() || (int) $customer->user_id !== get_current_user_id() | |
| 536 | + || EmailVerificationService::isRequired(get_current_user_id())) { | |
| 531 | 537 | return; |
| 532 | 538 | } |
| 533 | 539 | |
| 534 | 540 | $firstName = Arr::get($args, 'billing_address.first_name'); |
| @@ -538,18 +544,13 @@ | ||
| 538 | 544 | 'first_name' => $firstName, |
| 539 | 545 | 'last_name' => $lastName, |
| 540 | 546 | ]); |
| 541 | 547 | |
| 542 | - $user = get_user_by('email', $customer->email); | |
| 543 | - | |
| 544 | - if (empty($user)) { | |
| 545 | - return; | |
| 548 | + // Keep profile updates tied to the buyer's stored account link too. | |
| 549 | + if (is_user_logged_in() && (int) $customer->user_id === get_current_user_id()) { | |
| 550 | + update_user_meta(get_current_user_id(), 'first_name', $firstName); | |
| 551 | + update_user_meta(get_current_user_id(), 'last_name', $lastName); | |
| 546 | 552 | } |
| 547 | - | |
| 548 | - if (is_user_logged_in() && $user->ID === get_current_user_id()) { | |
| 549 | - update_user_meta($user->ID, 'first_name', $firstName); | |
| 550 | - update_user_meta($user->ID, 'last_name', $lastName); | |
| 551 | - } | |
| 552 | 553 | } |
| 553 | 554 | |
| 554 | 555 | public static function updateStock($order) |
| 555 | 556 | { |
| @@ -577,16 +578,18 @@ | ||
| 577 | 578 | if ($current_user->ID) { |
| 578 | 579 | $billingAddress['email'] = $current_user->user_email; |
| 579 | 580 | $billingAddress['user_id'] = $current_user->ID; |
| 580 | 581 | } else { |
| 581 | - static::handleUserCreation($orderData, $billingAddress); | |
| 582 | + unset($billingAddress['user_id']); | |
| 582 | 583 | } |
| 583 | 584 | |
| 584 | 585 | $customer = CustomerResource::create($billingAddress); |
| 585 | 586 | $customer = Arr::get($customer, 'data', null); |
| 586 | 587 | $customerId = Arr::get($customer, 'id', null); |
| 587 | - static::createCustomerAddress($billingAddress, $customerId); | |
| 588 | - static::createCustomerAddress($shippingAddress, $customerId); | |
| 588 | + if ($customer && $customer->wasRecentlyCreated) { | |
| 589 | + static::createCustomerAddress($billingAddress, $customerId); | |
| 590 | + static::createCustomerAddress($shippingAddress, $customerId); | |
| 591 | + } | |
| 589 | 592 | |
| 590 | 593 | return $customer; |
| 591 | 594 | } |
| 592 | 595 | |
| @@ -591,17 +594,13 @@ | ||
| 591 | 594 | } |
| 592 | 595 | |
| 593 | 596 | private static function updateExistingCustomer($customer, $orderData, $billingAddress, $shippingAddress) |
| 594 | 597 | { |
| 595 | - if (empty($customer->user_id)) { | |
| 596 | - $currentLoggedInUser = wp_get_current_user(); | |
| 597 | - if ($currentLoggedInUser && $currentLoggedInUser->user_email === $customer->email) { | |
| 598 | - $userId = get_current_user_id(); | |
| 599 | - $customer->update(['user_id' => $userId]); | |
| 600 | - $billingAddress['user_id'] = $userId; | |
| 601 | - } | |
| 598 | + // Order addresses come from this checkout; saved profile data needs proof. | |
| 599 | + if (!is_user_logged_in() || (int) $customer->user_id !== get_current_user_id() | |
| 600 | + || EmailVerificationService::isRequired(get_current_user_id())) { | |
| 601 | + return; | |
| 602 | 602 | } |
| 603 | - | |
| 604 | 603 | $customer->load(['billing_address', 'shipping_address']); |
| 605 | 604 | |
| 606 | 605 | if ($customer->billing_address->count() < 1) { |
| 607 | 606 | static::createCustomerAddress($billingAddress, $customer->id); |
| @@ -608,25 +607,10 @@ | ||
| 608 | 607 | } |
| 609 | 608 | if ($customer->shipping_address->count() < 1) { |
| 610 | 609 | static::createCustomerAddress($shippingAddress, $customer->id); |
| 611 | 610 | } |
| 612 | - | |
| 613 | - static::handleUserCreation($orderData, $billingAddress, $customer); | |
| 614 | 611 | } |
| 615 | 612 | |
| 616 | - private static function handleUserCreation($orderData, &$billingAddress, $customer = null) | |
| 617 | - { | |
| 618 | - $userEmail = Arr::get($billingAddress, 'email'); | |
| 619 | - $user = get_user_by('email', $userEmail); | |
| 620 | - | |
| 621 | - if ($user) { | |
| 622 | - $billingAddress['user_id'] = $user->ID; | |
| 623 | - if ($customer) { | |
| 624 | - $customer->update(['user_id' => $user->ID]); | |
| 625 | - } | |
| 626 | - } | |
| 627 | - } | |
| 628 | - | |
| 629 | 613 | private static function getCustomerEmail($billingAddress) |
| 630 | 614 | { |
| 631 | 615 | return is_user_logged_in() ? wp_get_current_user()->user_email : $billingAddress['email']; |
| 632 | 616 | } |
| @@ -1006,9 +990,16 @@ | ||
| 1006 | 990 | |
| 1007 | 991 | |
| 1008 | 992 | if ($cart->requireShipping()) { |
| 1009 | 993 | if (!empty($data['fc_selected_shipping_method'])) { |
| 1010 | - $selectedMethod = $data['fc_selected_shipping_method']; | |
| 994 | + // One integer, decided here, is both what is checked and what placeOrder() prices. | |
| 995 | + // A loose compare let PHP 7.4 match "1<b>2" to method 1, and sanitize_text_field() | |
| 996 | + // then turned the same string into "12", so the order was priced by method 12. | |
| 997 | + $rawMethod = $data['fc_selected_shipping_method']; | |
| 998 | + $isPlainId = (is_string($rawMethod) || is_int($rawMethod)) && (string) absint($rawMethod) === (string) $rawMethod; | |
| 999 | + $selectedMethod = $isPlainId ? absint($rawMethod) : 0; | |
| 1000 | + $data['fc_selected_shipping_method'] = $selectedMethod; | |
| 1001 | + | |
| 1011 | 1002 | $shippingCountry = Arr::get($data, 'billing_country', ''); |
| 1012 | 1003 | $shippingState = Arr::get($data, 'billing_state', ''); |
| 1013 | 1004 | $shipToDifferent = Arr::get($data, 'ship_to_different', 'no') === 'yes'; |
| 1014 | 1005 | |
| @@ -1024,9 +1015,9 @@ | ||
| 1024 | 1015 | $errors['shipping_method']['unavailable'] = __('We don\'t ship to this address. Please select a different address.', 'fluent-cart'); |
| 1025 | 1016 | } else { |
| 1026 | 1017 | $found = false; |
| 1027 | 1018 | foreach ($availableShippingMethods as $shippingMethod) { |
| 1028 | - if ($shippingMethod->id == $selectedMethod) { | |
| 1019 | + if ((int) $shippingMethod->id === $selectedMethod) { | |
| 1029 | 1020 | $found = true; |
| 1030 | 1021 | break; |
| 1031 | 1022 | } |
| 1032 | 1023 | } |