diff --git a/src/application/abstractions/repositories/i_user_repository.py b/src/application/abstractions/repositories/i_user_repository.py index 47cfee7..dd21860 100644 --- a/src/application/abstractions/repositories/i_user_repository.py +++ b/src/application/abstractions/repositories/i_user_repository.py @@ -20,6 +20,11 @@ class IUserRepository(ABC): raise NotImplementedError + @abstractmethod + async def get_user_by_id(self, user_id: str) -> UserEntity: + raise NotImplementedError + + @abstractmethod async def exists_verified_by_passport(self,*,passport_data: str,exclude_user_id: str) -> bool: raise NotImplementedError diff --git a/src/application/commands/create_kyc_command.py b/src/application/commands/create_kyc_command.py index eccf5b3..30e6f45 100644 --- a/src/application/commands/create_kyc_command.py +++ b/src/application/commands/create_kyc_command.py @@ -6,7 +6,7 @@ from src.application.abstractions import IUnitOfWork from src.application.contracts import IBeorgService,ILogger from src.application.domain.dto import BeorgKycCreateResponse,BeorgKycResultResponse,KycPersonalData,KycSessionResponse from src.application.domain.entities import KycEntity -from src.application.domain.exceptions import ApplicationException,KycFailedException,KycNotCompletedException,KycPassportAlreadyVerifiedException,KycPersonalDataIncompleteException,KycSessionExpiredException,KycSessionMissingUserTokenException +from src.application.domain.exceptions import ApplicationException,KycFailedException,KycLegalEntityForbiddenException,KycNotCompletedException,KycPassportAlreadyVerifiedException,KycPersonalDataIncompleteException,KycSessionExpiredException,KycSessionMissingUserTokenException from src.application.services import ensure_adult,extract_personal_data,normalize_passport_data,parse_birth_date @@ -41,6 +41,9 @@ class PassKycCommand: now = _utc_now() self._logger.info(f'KYC session creation started for user {user_id}') async with self._unit_of_work as unit_of_work: + user = await unit_of_work.user_repository.get_user_by_id(user_id) + if user.account_type == 'legal_entity': + raise KycLegalEntityForbiddenException() await unit_of_work.kyc_repository.expire_started_sessions(user_id=user_id,now=now) existing = await unit_of_work.kyc_repository.get_active_session(user_id=user_id,now=now) if existing is not None: diff --git a/src/application/domain/entities/user.py b/src/application/domain/entities/user.py index 6c8e3a9..e39071d 100644 --- a/src/application/domain/entities/user.py +++ b/src/application/domain/entities/user.py @@ -29,3 +29,5 @@ class UserEntity: created_at: datetime | None = None updated_at: datetime | None = None kyc_verified_at: datetime | None = None + + account_type: str = 'individual' diff --git a/src/application/domain/exceptions/__init__.py b/src/application/domain/exceptions/__init__.py index 8f521c4..389d3fe 100644 --- a/src/application/domain/exceptions/__init__.py +++ b/src/application/domain/exceptions/__init__.py @@ -1,2 +1,2 @@ from src.application.domain.exceptions.application_exceptions import ApplicationException -from src.application.domain.exceptions.kyc_exceptions import BeorgConfigException,BeorgRejectedException,BeorgUnavailableException,CsrfRequestRequiredException,InvalidTokenException,JwtDecodeFailedException,KycAgeRestrictedException,KycBirthDateInvalidException,KycFailedException,KycNotCompletedException,KycPassportAlreadyVerifiedException,KycPersonalDataIncompleteException,KycSessionExpiredException,KycSessionMissingUserTokenException,NotAuthenticatedException,UnauthorizedException,UserNotFoundException \ No newline at end of file +from src.application.domain.exceptions.kyc_exceptions import BeorgConfigException,BeorgRejectedException,BeorgUnavailableException,CsrfRequestRequiredException,InvalidTokenException,JwtDecodeFailedException,KycAgeRestrictedException,KycBirthDateInvalidException,KycFailedException,KycLegalEntityForbiddenException,KycNotCompletedException,KycPassportAlreadyVerifiedException,KycPersonalDataIncompleteException,KycSessionExpiredException,KycSessionMissingUserTokenException,NotAuthenticatedException,UnauthorizedException,UserNotFoundException \ No newline at end of file diff --git a/src/application/domain/exceptions/kyc_exceptions.py b/src/application/domain/exceptions/kyc_exceptions.py index 87da82b..8da63c2 100644 --- a/src/application/domain/exceptions/kyc_exceptions.py +++ b/src/application/domain/exceptions/kyc_exceptions.py @@ -106,3 +106,13 @@ class BeorgConfigException(ApplicationException): def __init__(self) -> None: super().__init__(status_code=500,message='Beorg service is not configured',error_code='beorg_not_configured') + + +class KycLegalEntityForbiddenException(ApplicationException): + + def __init__(self) -> None: + super().__init__( + status_code=403, + message='KYC via Beorg is not available for legal entity accounts', + error_code='kyc_legal_entity_forbidden', + ) diff --git a/src/infrastructure/database/models/user.py b/src/infrastructure/database/models/user.py index 4641062..387846d 100644 --- a/src/infrastructure/database/models/user.py +++ b/src/infrastructure/database/models/user.py @@ -28,3 +28,5 @@ class UserModel(Base, UlidPrimaryKeyMixin, AuditTimestampsMixin, SoftDeleteMixin kyc_verified: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='false', default=False) kyc_verified_at: Mapped[DateTime | None] = mapped_column(DateTime(timezone=True), nullable=True) + + account_type: Mapped[str] = mapped_column(String(20), nullable=False, server_default='individual', default='individual') diff --git a/src/infrastructure/database/repositories/user_repository.py b/src/infrastructure/database/repositories/user_repository.py index c7c0eac..d81c4fd 100644 --- a/src/infrastructure/database/repositories/user_repository.py +++ b/src/infrastructure/database/repositories/user_repository.py @@ -19,7 +19,7 @@ class UserRepository(IUserRepository): async def create_user(self,email: str,password_hash: str) -> UserEntity: - user = UserModel(email=email,password_hash=password_hash) + user = UserModel(email=email, password_hash=password_hash, account_type='individual') self._session.add(user) await self._session.flush() return self._to_entity(user) @@ -38,6 +38,13 @@ class UserRepository(IUserRepository): return result.scalar_one_or_none() is not None + async def get_user_by_id(self, user_id: str) -> UserEntity: + user = await self._session.get(UserModel, user_id) + if user is None or user.is_deleted: + raise UserNotFoundException() + return self._to_entity(user) + + async def exists_verified_by_passport(self,*,passport_data: str,exclude_user_id: str) -> bool: passport_digits = func.regexp_replace(UserModel.passport_data,'[^0-9]','','g') result = await self._session.execute( @@ -104,6 +111,7 @@ class UserRepository(IUserRepository): created_at=user.created_at, updated_at=user.updated_at, kyc_verified_at=user.kyc_verified_at, + account_type=user.account_type, )