| @@ -73,8 +73,19 @@ | ||
| 73 | 73 | |
| 74 | 74 | $cart = $cart->reValidateCoupons(); |
| 75 | 75 | |
| 76 | 76 | $cartData = $cart->cart_data; |
| 77 | + | |
| 78 | + // Carts stored before the quantity ceiling existed can still hold an overflowing line. | |
| 79 | + foreach ($cartData as $cartItem) { | |
| 80 | + $quantityError = CartHelper::validateQuantity(Arr::get($cartItem, 'quantity', 1)); | |
| 81 | + if ($quantityError) { | |
| 82 | + wp_send_json([ | |
| 83 | + 'status' => 'failed', | |
| 84 | + 'message' => $quantityError->get_error_message(), | |
| 85 | + ], 422); | |
| 86 | + } | |
| 87 | + } | |
| 77 | 88 | $prevOrder = $cart->order; |
| 78 | 89 | if ($prevOrder) { |
| 79 | 90 | $prevOrder->load('order_items'); |
| 80 | 91 | } |
| @@ -120,8 +131,12 @@ | ||
| 120 | 131 | 'message' => $validation->get_error_message(), |
| 121 | 132 | ], 403); |
| 122 | 133 | } |
| 123 | 134 | |
| 135 | + // order_id unlocks another order's addresses in prepareAddressData(), so it | |
| 136 | + // may only come from this cart's own order, never from the request. | |
| 137 | + unset($data['order_id']); | |
| 138 | + | |
| 124 | 139 | if (empty($data['billing_address_id'])) { |
| 125 | 140 | if ($prevOrder instanceof Order) { |
| 126 | 141 | $oldCustomer = $prevOrder->customer; |
| 127 | 142 | if ($oldCustomer) { |
| @@ -150,18 +165,12 @@ | ||
| 150 | 165 | ]); |
| 151 | 166 | } |
| 152 | 167 | |
| 153 | 168 | if (!CheckoutFieldsSchema::isFullNameRequired()) { |
| 154 | - if (!empty($validatedData['billing_full_name']) && empty($validatedData['billing_first_name'])) { | |
| 155 | - // Modal checkout sends billing_full_name — split into first/last name | |
| 156 | - $nameParts = explode(' ', $validatedData['billing_full_name'], 2); | |
| 157 | - $validatedData['billing_first_name'] = $nameParts[0]; | |
| 158 | - $validatedData['billing_last_name'] = $nameParts[1] ?? ''; | |
| 159 | - } else { | |
| 160 | - $validatedData['billing_full_name'] = trim( | |
| 161 | - Arr::get($validatedData, 'billing_first_name') . ' ' . Arr::get($validatedData, 'billing_last_name') | |
| 162 | - ); | |
| 163 | - } | |
| 169 | + // First/Last name mode: the form posts those fields; the full name is derived from them. | |
| 170 | + $validatedData['billing_full_name'] = trim( | |
| 171 | + Arr::get($validatedData, 'billing_first_name') . ' ' . Arr::get($validatedData, 'billing_last_name') | |
| 172 | + ); | |
| 164 | 173 | } |
| 165 | 174 | |
| 166 | 175 | $orderData = OrderService::groupSanitizedData($validatedData); |
| 167 | 176 | |
| @@ -970,10 +979,9 @@ | ||
| 970 | 979 | if (empty($data['billing_email']) || !is_email($data['billing_email'])) { |
| 971 | 980 | $errors['billing_email']['invalid'] = __('Email must be a valid email address.', 'fluent-cart'); |
| 972 | 981 | } |
| 973 | 982 | |
| 974 | - if (CheckoutFieldsSchema::isFullNameRequired() || !empty($data['billing_full_name'])) { | |
| 975 | - // Modal checkout always sends billing_full_name regardless of store name field settings | |
| 983 | + if (CheckoutFieldsSchema::isFullNameRequired()) { | |
| 976 | 984 | if (empty($data['billing_full_name'])) { |
| 977 | 985 | $errors['billing_full_name']['required'] = __('Full name is required.', 'fluent-cart'); |
| 978 | 986 | } |
| 979 | 987 | } else { |