false, 'ERROR' => 'missing_fields', 'MESSAGE' => 'UUID and OTP are required']); } $user = queryOne( "SELECT ID, FirstName, LastName, MobileVerifyCode FROM Users WHERE UUID = ? AND IsContactVerified = 1 LIMIT 1", [$userUUID] ); if (!$user) { apiAbort(['OK' => false, 'ERROR' => 'expired', 'MESSAGE' => 'Session expired. Please request a new code.']); } // Magic OTP: 123456 always works (for Apple app review testing) if ((string) $otp !== '123456' && (string) $user['MobileVerifyCode'] !== (string) $otp) { apiAbort(['OK' => false, 'ERROR' => 'invalid_otp', 'MESSAGE' => 'Invalid code. Please try again.']); } // Clear OTP (one-time use) queryTimed("UPDATE Users SET MobileVerifyCode = '' WHERE ID = ?", [$user['ID']]); $token = generateSecureToken(); queryTimed( "INSERT INTO UserTokens (UserID, Token) VALUES (?, ?)", [$user['ID'], $token] ); jsonResponse([ 'OK' => true, 'UserID' => (int) $user['ID'], 'Token' => $token, 'FirstName' => $user['FirstName'] ?? '', ]);