| @@ -135,13 +135,17 @@ | ||
| 135 | 135 | $email = Arr::get($data, 'email'); |
| 136 | 136 | $data = static::resolveCustomerName($data); |
| 137 | 137 | |
| 138 | 138 | $data['purchase_value'] = []; |
| 139 | - $customer = static::getQuery()->firstOrCreate( | |
| 140 | - ['email' => $email], | |
| 141 | - $data | |
| 142 | - ); | |
| 143 | 139 | |
| 140 | + // Preserve an established account link; otherwise reuse the email row | |
| 141 | + // without linking it. Only the verification flow can claim guest history. | |
| 142 | + $ownerId = (int) Arr::get($data, 'user_id'); | |
| 143 | + $customer = $ownerId ? static::getQuery()->where('user_id', $ownerId)->orderBy('id')->first() : null; | |
| 144 | + if (!$customer) { | |
| 145 | + $customer = static::getQuery()->firstOrCreate(['email' => $email], $data); | |
| 146 | + } | |
| 147 | + | |
| 144 | 148 | if (empty($customer)) { |
| 145 | 149 | return static::makeErrorResponse([ |
| 146 | 150 | ['code' => 400, 'message' => __('Customer creation failed.', 'fluent-cart')] |
| 147 | 151 | ]); |
| @@ -146,13 +150,22 @@ | ||
| 146 | 150 | ['code' => 400, 'message' => __('Customer creation failed.', 'fluent-cart')] |
| 147 | 151 | ]); |
| 148 | 152 | } |
| 149 | 153 | |
| 150 | - $isUserAttached = false; | |
| 151 | - $user = get_user_by('email', $email); | |
| 152 | - if ($user) { | |
| 153 | - $customer->update(['user_id' => $user->ID]); | |
| 154 | - $isUserAttached = true; | |
| 154 | + // Linking happens only where identity is established. A row that | |
| 155 | + // already existed is never claimed here: firstOrCreate() may have found | |
| 156 | + // somebody else's record by its address. A fresh row is linked to the | |
| 157 | + // account holding its email only for an actor with authority over that | |
| 158 | + // account (an admin screen, the MCP tools) or when the caller supplied | |
| 159 | + // the user_id it established itself (a signed-in checkout, the User | |
| 160 | + // API). An anonymous caller — a guest at checkout — links nothing. | |
| 161 | + $isUserAttached = (bool) $customer->user_id; | |
| 162 | + if ($customer->wasRecentlyCreated && !$isUserAttached) { | |
| 163 | + $user = get_user_by('email', $email); | |
| 164 | + if ($user && get_current_user_id() && current_user_can('edit_user', $user->ID)) { | |
| 165 | + $customer->update(['user_id' => $user->ID]); | |
| 166 | + $isUserAttached = true; | |
| 167 | + } | |
| 155 | 168 | } |
| 156 | 169 | |
| 157 | 170 | if (Arr::get($data, 'wp_user') === 'yes' && !$isUserAttached) { |
| 158 | 171 | $isUserCreated = \FluentCart\App\Services\AuthService::createUserFromCustomer($customer); |
| @@ -303,19 +316,19 @@ | ||
| 303 | 316 | } |
| 304 | 317 | |
| 305 | 318 | return static::makeErrorResponse([ |
| 306 | 319 | ['code' => 400, 'message' => __('Customer update failed.', 'fluent-cart')] |
| 307 | - ]); | |
| 320 | + ], 400); | |
| 308 | 321 | } |
| 309 | 322 | |
| 310 | 323 | return static::makeErrorResponse([ |
| 311 | 324 | ['code' => 400, 'message' => __('Customer does not have any changes to update.', 'fluent-cart')] |
| 312 | - ]); | |
| 325 | + ], 400); | |
| 313 | 326 | } |
| 314 | 327 | |
| 315 | 328 | return static::makeErrorResponse([ |
| 316 | 329 | ['code' => 404, 'message' => __('Customer not found, please reload the page and try again!', 'fluent-cart')] |
| 317 | - ]); | |
| 330 | + ], 404); | |
| 318 | 331 | } |
| 319 | 332 | |
| 320 | 333 | /** |
| 321 | 334 | * Update the status of multiple customers with the given parameters. |
| @@ -410,29 +423,22 @@ | ||
| 410 | 423 | } |
| 411 | 424 | |
| 412 | 425 | $currentUser = get_user_by('ID', get_current_user_id()); |
| 413 | 426 | |
| 414 | - // Try to get the existing customer | |
| 415 | - $query = Customer::query()->where('user_id', $currentUser->ID) | |
| 416 | - ->orWhere('email', $currentUser->user_email) | |
| 417 | - ->with(['billing_address', 'shipping_address']); | |
| 427 | + // Reading the current customer must not claim a record by email. | |
| 428 | + $existingCustomer = Customer::query()->where('user_id', $currentUser->ID) | |
| 429 | + ->orderBy('id', 'ASC') | |
| 430 | + ->with(['billing_address', 'shipping_address']) | |
| 431 | + ->first(); | |
| 418 | 432 | |
| 419 | - | |
| 420 | - $existingCustomer = $query->first(); | |
| 421 | - | |
| 422 | - // Return if found | |
| 423 | 433 | if ($existingCustomer) { |
| 424 | - if ($existingCustomer->user_id != $currentUser->ID) { | |
| 425 | - // Update the user_id if it doesn't match | |
| 426 | - $existingCustomer->user_id = $currentUser->ID; | |
| 427 | - $existingCustomer->save(); | |
| 428 | - } | |
| 429 | - | |
| 430 | 434 | static::$currentCustomerRuntimeCache = $existingCustomer; |
| 431 | 435 | return $existingCustomer; |
| 432 | 436 | } |
| 433 | 437 | |
| 434 | - if (!$createIfNotExists) { | |
| 438 | + if (!$createIfNotExists || Customer::query()->where('email', $currentUser->user_email)->exists()) { | |
| 439 | + // Do not create a duplicate or expose an unclaimed customer to a getter. | |
| 440 | + // Email confirmation links the existing row before dashboard access. | |
| 435 | 441 | return null; |
| 436 | 442 | } |
| 437 | 443 | |
| 438 | 444 | $userId = $currentUser->ID; |