/home/alixis5/integrate.sellofinance.com/src/Services
NameSizeModeActions
ApplicationService.php290940644editdlrm
AuthService.php127280644editdlrm
CatalogAggregator.php57910644editdlrm
CustomerBundleLoader.php39520644editdlrm
EligibilityEvaluator.php176030644editdlrm
GoogleAuthService.php82850644editdlrm
MeritRanker.php62790644editdlrm
OnboardingService.php229470644editdlrm
ProductPolicyLoader.php38180644editdlrm
VerificationService.php37520644editdlrm
Edit: /home/alixis5/integrate.sellofinance.com/src/Services/AuthService.php (12728B)
accountRepo->create([ 'name' => $fullName, 'type' => Account::TYPE_CUSTOMER, 'emailAddress' => $validated['emailAddress'], ]); $accountId = $account->id; } catch (EspoDuplicateException $e) { $hasCustomerDup = false; foreach ($e->getDuplicates() as $dup) { if (($dup['type'] ?? '') === Account::TYPE_CUSTOMER) { $hasCustomerDup = true; break; } } if ($hasCustomerDup) { throw new \RuntimeException('DUPLICATE_EMAIL', 409, $e); } $account = $this->accountRepo->create([ 'name' => $fullName, 'type' => Account::TYPE_CUSTOMER, 'emailAddress' => $validated['emailAddress'], ], true); $accountId = $account->id; } // Step 2: Create AppUser — deduplicates on emailAddress and/or phoneNumber $appUserData = [ 'firstName' => $validated['firstName'] ?? null, 'lastName' => $validated['lastName'], 'emailAddress' => $validated['emailAddress'], 'phoneNumber' => $validated['phoneNumber'] ?? null, 'password' => $this->hashPassword($validated['password']), 'userType' => AppUser::TYPE_CUSTOMER, 'status' => AppUser::STATUS_PENDING, 'signupType' => $validated['signupType'] ?? AppUser::SIGNUP_EMAIL, 'preferredLanguage' => $validated['preferredLanguage'] ?? AppUser::LANG_EN, 'emailVerified' => false, 'mobileVerified' => false, 'mfaEnabled' => false, 'accountId' => $accountId, ]; try { $appUser = $this->appUserRepo->create($appUserData); $appUserId = $appUser->id; } catch (EspoDuplicateException $e) { $hasCustomerDup = false; foreach ($e->getDuplicates() as $dup) { if (($dup['userType'] ?? '') === AppUser::TYPE_CUSTOMER) { $hasCustomerDup = true; break; } } if ($hasCustomerDup) { try { $this->accountRepo->delete($accountId); } catch (\Throwable) {} $fields = $e->getDuplicateFields($appUserData, ['phoneNumber', 'emailAddress']); if (\in_array('phoneNumber', $fields, true)) throw new \RuntimeException('DUPLICATE_PHONE', 409, $e); throw new \RuntimeException('DUPLICATE_EMAIL', 409, $e); } try { $appUser = $this->appUserRepo->create($appUserData, true); $appUserId = $appUser->id; } catch (\Throwable $err) { try { $this->accountRepo->delete($accountId); } catch (\Throwable) {} throw new \RuntimeException($err->getMessage(), (int) $err->getCode(), $err); } } catch (EspoApiException $e) { try { $this->accountRepo->delete($accountId); } catch (\Throwable) {} if ($e->getCode() === 403) throw new \RuntimeException('ESPO_VALIDATION:' . $e->getMessage(), 403, $e); throw new \RuntimeException($e->getMessage(), (int) $e->getCode(), $e); } // Step 3: Create CustomerProfile try { $profile = $this->profileRepo->create([ 'name' => $fullName, 'appUserId' => $appUserId, 'accountId' => $accountId, 'profileType' => CustomerProfile::TYPE_INDIVIDUAL, 'status' => CustomerProfile::STATUS_DRAFT, 'onboardingStatus' => CustomerProfile::ONBOARDING_NOT_STARTED, 'preferredCurrency' => 'AED', 'creditConsentGiven' => false, 'marketingConsentGiven'=> false, 'existingBankCustomer' => false, ]); } catch (\Throwable $e) { try { $this->appUserRepo->delete($appUserId); } catch (\Throwable) {} try { $this->accountRepo->delete($accountId); } catch (\Throwable) {} throw new \RuntimeException($e->getMessage(), (int) $e->getCode(), $e); } $token = $this->jwt->issue([ 'sub' => $appUserId, 'accountId' => $accountId, 'profileId' => $profile->id, 'userType' => AppUser::TYPE_CUSTOMER, ]); return [ 'token' => $token, 'expiresIn' => (int) $_ENV['JWT_EXPIRY_SECONDS'], 'user' => $this->buildUserResponse($appUser, $profile), ]; } // ── Login ────────────────────────────────────────────────────── public function login(array $validated): array { $appUser = $this->appUserRepo->findByEmail($validated['emailAddress']); if ($appUser === null) { throw new \RuntimeException('INVALID_CREDENTIALS'); } // Customer portal only accepts customer-type users if ($appUser->userType !== AppUser::TYPE_CUSTOMER) { throw new \RuntimeException('INVALID_CREDENTIALS'); } if (!$this->verifyPassword($validated['password'], $appUser->password ?? '')) { $this->appUserRepo->incrementFailedLogins($appUser->id); throw new \RuntimeException('INVALID_CREDENTIALS'); } if ($appUser->status === AppUser::STATUS_SUSPENDED) { throw new \RuntimeException('ACCOUNT_SUSPENDED'); } if ($appUser->status === AppUser::STATUS_LOCKED) { throw new \RuntimeException('ACCOUNT_LOCKED'); } $this->appUserRepo->recordSuccessfulLogin($appUser->id); $profile = $this->profileRepo->findByAppUserId($appUser->id); // Auto-create a profile if one doesn't exist (e.g. user created directly in EspoCRM) if ($profile === null && $appUser->userType === AppUser::TYPE_CUSTOMER) { $fullName = trim(($appUser->firstName ?? '') . ' ' . $appUser->lastName); $profile = $this->profileRepo->create([ 'name' => $fullName, 'appUserId' => $appUser->id, 'accountId' => $appUser->accountId, 'profileType' => CustomerProfile::TYPE_INDIVIDUAL, 'status' => CustomerProfile::STATUS_DRAFT, 'onboardingStatus' => CustomerProfile::ONBOARDING_NOT_STARTED, 'preferredCurrency' => 'AED', 'creditConsentGiven' => false, 'marketingConsentGiven' => false, 'existingBankCustomer' => false, ]); } $token = $this->jwt->issue([ 'sub' => $appUser->id, 'accountId' => $appUser->accountId, 'profileId' => $profile?->id, 'userType' => $appUser->userType, ]); return [ 'token' => $token, 'expiresIn' => (int) $_ENV['JWT_EXPIRY_SECONDS'], 'user' => $this->buildUserResponse($appUser, $profile), ]; } /** * Update the authenticated user's editable AppUser fields. * Whitelisted: firstName, lastName, phoneNumber, preferredLanguage. * Returns the same shape as login() to make client-side state replacement trivial. * * @param array $patch * @return array { user: ... } * @throws \RuntimeException 404 if user not found */ public function updateMe(string $appUserId, array $patch): array { $allowed = array_intersect_key($patch, array_flip([ 'firstName', 'lastName', 'phoneNumber', 'preferredLanguage', ])); if (empty($allowed)) { // Nothing valid to update — return current state without a write. $user = $this->appUserRepo->findById($appUserId); if ($user === null) { throw new \RuntimeException('User not found', 404); } $profile = $this->profileRepo->findByAppUserId($appUserId); return ['user' => $this->buildUserResponse($user, $profile)]; } $updated = $this->appUserRepo->update($appUserId, $allowed); $profile = $this->profileRepo->findByAppUserId($appUserId); return ['user' => $this->buildUserResponse($updated, $profile)]; } // ── Private helpers ──────────────────────────────────────────── private function buildUserResponse(AppUser $user, ?CustomerProfile $profile): array { return [ 'id' => $user->id, 'accountId' => $user->accountId, // Persisted to localStorage by Angular 'firstName' => $user->firstName, 'lastName' => $user->lastName, 'emailAddress' => $user->emailAddress, 'phoneNumber' => $user->phoneNumber, 'emailVerified' => $user->emailVerified, 'mobileVerified' => $user->mobileVerified, 'userType' => $user->userType, 'status' => $user->status, 'preferredLanguage' => $user->preferredLanguage, 'ref' => $user->ref, 'createdAt' => $user->createdAt, 'profile' => $profile ? [ 'id' => $profile->id, 'onboardingStatus' => $profile->onboardingStatus, 'onboardingStep' => $profile->onboardingStep, 'profileCompletenessPercent' => $profile->profileCompletenessPercent, 'profileType' => $profile->profileType, ] : null, ]; } private function hashPassword(string $plaintext): string { return password_hash($plaintext, PASSWORD_BCRYPT, ['cost' => 12]); } private function verifyPassword(string $plaintext, string $hash): bool { return password_verify($plaintext, $hash); } }