Skip to content

Actions

components.payment_gateway.subcomponents.accounts.business_logic.actions.account_actions

AccountActions

AccountActions(adyen_client)

This class contains all the actions used to manage the lifecycle of an account.

Implements the following Nullable patterns: - Nullables: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#nullables ⧉ - Parameterless instantiation: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#instantiation ⧉

Tags
Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_actions.py
def __init__(
    self,
    adyen_client: "AdyenBalanceAccountsApiClient | None",
) -> None:
    self._adyen_client = adyen_client

activate_account

activate_account(session, /, id)

Activate an account.

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_actions.py
def activate_account(
    self,
    session: Session,
    /,
    id: AccountId,
) -> None:
    """
    Activate an account.
    """
    from shared.services.adyen.openapi.balance_platform_service_v2 import (
        BalanceAccountUpdateRequest,
    )

    with raise_if_account_not_found(id):
        account = AccountModelBroker.get_account(session, id=id)

    raise_on_terminated_account(account)
    raise_on_invalid_account_status_transition(account, AccountStatus.active)

    self.adyen_client.update_balance_account(
        account.external_id,
        request=BalanceAccountUpdateRequest(
            status="active",
        ),
        idempotency_key=None,  # TODO use this for retries
    )
    AccountModelBroker.set_account_status(
        session, id=account.id, status=AccountStatus.active
    )

adyen_client property

adyen_client

Ensures the Adyen client is available when accessing it.

create classmethod

create()

Normal factory

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_actions.py
@classmethod
def create(cls) -> "AccountActions":
    """Normal factory"""
    from shared.services.adyen.clients.adyen_balance_accounts_api_client import (
        AdyenBalanceAccountsApiClient,
    )
    from shared.services.adyen.clients.exceptions import (
        AdyenClientMissingCredentialsException,
    )

    try:
        adyen_client = AdyenBalanceAccountsApiClient.create()
    except AdyenClientMissingCredentialsException:
        adyen_client = None
    return cls(adyen_client)

create_account

create_account(
    session,
    /,
    account_holder_id,
    description,
    reference=None,
)

Create an account for an account holder.

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_actions.py
def create_account(
    self,
    session: Session,
    /,
    account_holder_id: AccountHolderId,
    description: str,
    reference: str | None = None,
) -> AccountId:
    """
    Create an account for an account holder.
    """
    from components.payment_gateway.subcomponents.accounts.adapters.adyen.helpers import (
        to_balance_account_info,
    )

    with raise_if_account_holder_not_found(account_holder_id):
        account_holder = AccountHolderModelBroker.get_account_holder(
            session,
            id=account_holder_id,
        )

    # 1. Create Adyen request payload from our model
    balance_account_info = to_balance_account_info(
        external_account_holder_id=account_holder.external_id,
        description=description,
        reference=reference,
    )
    assert balance_account_info.description is not None

    # 2. Call Adyen API
    balance_account = self.adyen_client.create_balance_account(
        request=balance_account_info,
        idempotency_key=None,  # TODO use this for retries
    )

    # 3. Create our Account entity from API result
    account = AccountModelBroker.create_account(
        session,
        description=balance_account_info.description,
        reference=balance_account_info.reference,
        provider=PaymentServiceProvider.adyen,
        external_id=balance_account.id,
        account_holder_id=account_holder_id,
        status=AccountStatus(balance_account.status),
    )
    return AccountId(account.id)

create_null classmethod

create_null(track_adyen_requests=None)

Null factory

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_actions.py
@classmethod
def create_null(
    cls,
    track_adyen_requests: list[tuple[str, dict, dict]] | None = None,  # type: ignore[type-arg]
) -> "AccountActions":
    """Null factory"""
    from shared.services.adyen.clients.adyen_balance_accounts_api_client import (
        AdyenBalanceAccountsApiClient,
    )

    return cls(
        AdyenBalanceAccountsApiClient.create_null(
            track_requests=track_adyen_requests
        )
    )

deactivate_account

deactivate_account(session, /, id)

Deactivate an account.

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_actions.py
def deactivate_account(
    self,
    session: Session,
    /,
    id: AccountId,
) -> None:
    """
    Deactivate an account.
    """
    from shared.services.adyen.openapi.balance_platform_service_v2 import (
        BalanceAccountUpdateRequest,
    )

    with raise_if_account_not_found(id):
        account = AccountModelBroker.get_account(session, id=id)

    raise_on_terminated_account(account)
    raise_on_invalid_account_status_transition(account, AccountStatus.inactive)

    self.adyen_client.update_balance_account(
        account.external_id,
        request=BalanceAccountUpdateRequest(
            status="inactive",
        ),
        idempotency_key=None,  # TODO use this for retries
    )
    AccountModelBroker.set_account_status(
        session, id=account.id, status=AccountStatus.inactive
    )

terminate_account

terminate_account(session, /, id)

Terminate an account.

The operation is idempotent, i.e. it has no effect on already terminated entities.

Accounts in terminal state cannot be modified or used anymore. Any attempt to use or retrieve a terminated account will raise an AccountTerminatedException.

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_actions.py
def terminate_account(
    self,
    session: Session,
    /,
    id: AccountId,
) -> None:
    """
    Terminate an account.

    The operation is idempotent, i.e. it has no effect on already terminated
    entities.

    Accounts in terminal state cannot be modified or used anymore. Any
    attempt to use or retrieve a terminated account will raise an
    `AccountTerminatedException`.
    """
    from shared.services.adyen.openapi.balance_platform_service_v2 import (
        BalanceAccountUpdateRequest,
    )

    with raise_if_account_not_found(id):
        account = AccountModelBroker.get_account(session, id=id)

    if account.status != AccountStatus.closed:
        # Terminating an account should close it at Adyen
        self.adyen_client.update_balance_account(
            account.external_id,
            request=BalanceAccountUpdateRequest(
                status="closed",
            ),
            idempotency_key=None,  # TODO use this for retries
        )
        AccountModelBroker.set_account_status(
            session, id=account.id, status=AccountStatus.closed
        )

    if not account.is_terminated:
        # Terminating an account should set its termination date on first call only
        AccountModelBroker.terminate_account(session, id=id)

update_account

update_account(session, /, id, description, reference=None)

Update the balance account description and reference.

New description/name for the account will be truncated to 300 characters. New optional reference for the account will be truncated to 150 characters.

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_actions.py
def update_account(
    self,
    session: Session,
    /,
    id: AccountId,
    description: str,
    reference: str | None = None,
) -> None:
    """
    Update the balance account description and reference.

    New description/name for the account will be truncated to 300 characters.
    New optional reference for the account will be truncated to 150 characters.
    """
    from components.payment_gateway.subcomponents.accounts.adapters.adyen.helpers import (
        to_balance_account_info,
    )
    from shared.services.adyen.openapi.balance_platform_service_v2 import (
        BalanceAccountUpdateRequest,
    )

    with raise_if_account_not_found(id):
        account = AccountModelBroker.get_account(session, id=id)

    raise_on_terminated_account(account)

    balance_account_info = to_balance_account_info(
        external_account_holder_id=account.account_holder.external_id,
        description=description,
        reference=reference,
    )
    assert balance_account_info.description is not None

    self.adyen_client.update_balance_account(
        account.external_id,
        request=BalanceAccountUpdateRequest(
            description=balance_account_info.description,
            reference=balance_account_info.reference,
        ),
        idempotency_key=None,  # TODO use this for retries
    )

    # Update local database with new description and reference
    AccountModelBroker.update_account(
        session,
        id=account.id,
        description=balance_account_info.description,
        reference=balance_account_info.reference,
    )

components.payment_gateway.subcomponents.accounts.business_logic.actions.account_holder_actions

AccountHolderActions

AccountHolderActions(
    adyen_account_holders_client,
    adyen_legal_entities_client,
)

This class contains all the actions related to account holders.

Implements the following Nullable patterns: - Nullables: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#nullables ⧉ - Parameterless instantiation: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#instantiation ⧉

Tags
Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_holder_actions.py
def __init__(
    self,
    adyen_account_holders_client: "AdyenAccountHoldersApiClient | None",
    adyen_legal_entities_client: "AdyenLegalEntitiesApiClient | None",
) -> None:
    self._adyen_account_holders_client = adyen_account_holders_client
    self._adyen_legal_entities_client = adyen_legal_entities_client

adyen_account_holders_client property

adyen_account_holders_client

Ensures the Adyen client is available when accessing it.

adyen_legal_entities_client

Ensures the Adyen client is available when accessing it.

create classmethod

create()

Normal factory

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_holder_actions.py
@classmethod
def create(cls) -> "AccountHolderActions":
    """Normal factory"""
    from shared.services.adyen.clients.adyen_account_holders_api_client import (
        AdyenAccountHoldersApiClient,
    )
    from shared.services.adyen.clients.adyen_legal_entities_api_client import (
        AdyenLegalEntitiesApiClient,
    )
    from shared.services.adyen.clients.exceptions import (
        AdyenClientMissingCredentialsException,
    )

    try:
        adyen_account_holders_client = AdyenAccountHoldersApiClient.create()
    except AdyenClientMissingCredentialsException:
        adyen_account_holders_client = None

    try:
        adyen_legal_entities_client = AdyenLegalEntitiesApiClient.create()
    except AdyenClientMissingCredentialsException:
        adyen_legal_entities_client = None

    return cls(
        adyen_account_holders_client,
        adyen_legal_entities_client,
    )

create_null classmethod

create_null()

Null factory

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_holder_actions.py
@classmethod
def create_null(cls) -> "AccountHolderActions":
    """Null factory"""
    from shared.services.adyen.clients.adyen_account_holders_api_client import (
        AdyenAccountHoldersApiClient,
    )
    from shared.services.adyen.clients.adyen_legal_entities_api_client import (
        AdyenLegalEntitiesApiClient,
    )

    return cls(
        AdyenAccountHoldersApiClient.create_null(),
        AdyenLegalEntitiesApiClient.create_null(),
    )

declare_account_holder

declare_account_holder(
    session, /, description, external_id, reference=None
)

Declare an account holder.

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_holder_actions.py
def declare_account_holder(
    self,
    session: Session,
    /,
    description: str,
    external_id: str,
    reference: str | None = None,
) -> AccountHolderId:
    """
    Declare an account holder.
    """

    account_holder = AccountHolderModelBroker.create_account_holder(
        session,
        description=description,
        provider=PaymentServiceProvider.adyen,
        external_id=external_id,
        reference=reference,
    )
    return AccountHolderId(account_holder.id)

terminate_account_holder

terminate_account_holder(session, /, id)

Terminate an account holder.

The operation is idempotent, i.e. it has no effect on already terminated entities.

Account holders in terminal state cannot be modified or used anymore. Any attempt to use or retrieve a terminated account holder will raise an AccountHolderTerminatedException.

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_holder_actions.py
def terminate_account_holder(
    self,
    session: Session,
    /,
    id: AccountHolderId,
) -> None:
    """
    Terminate an account holder.

    The operation is idempotent, i.e. it has no effect on already terminated
    entities.

    Account holders in terminal state cannot be modified or used anymore.
    Any attempt to use or retrieve a terminated account holder will raise an
    `AccountHolderTerminatedException`.
    """

    with raise_if_account_holder_not_found(id):
        account_holder = AccountHolderModelBroker.get_account_holder(session, id=id)

    if not account_holder.is_terminated:
        AccountHolderModelBroker.terminate_account_holder(session, id=id)
update_account_holder_legal_name(
    session, /, id, legal_name
)

Update the legal name of an account holder.

This method updates the legal name of an account holder in both the database and the third party API.

Source code in components/payment_gateway/subcomponents/accounts/business_logic/actions/account_holder_actions.py
def update_account_holder_legal_name(
    self,
    session: Session,
    /,
    id: AccountHolderId,
    legal_name: str,
) -> None:
    """
    Update the legal name of an account holder.

    This method updates the legal name of an account holder in both the
    database and the third party API.
    """
    from shared.services.adyen.openapi.legal_entity_service_v3 import (
        LegalEntityInfo,
    )

    with raise_if_account_holder_not_found(id):
        account_holder = AccountHolderModelBroker.get_account_holder(session, id=id)

    raise_on_terminated_account_holder(account_holder)

    # 1. Get legal entity ID from Adyen  # TODO @frederic.bonnet 2025-10-01 store it in the database? Depends on how it's done with other PSPs
    account_holder_data = self.adyen_account_holders_client.get_account_holder(
        id=account_holder.external_id
    )

    # 2. Get legal entity data from Adyen
    current_entity = self.adyen_legal_entities_client.get_legal_entity(
        id=account_holder_data.legalEntityId,
        idempotency_key=None,
    )

    # 3. Update legal entity name on Adyen
    assert current_entity.organization is not None, (
        "Legal entity must have an organization"
    )
    current_entity.organization.legalName = legal_name
    self.adyen_legal_entities_client.update_legal_entity(
        id=current_entity.id,
        request=LegalEntityInfo(
            type=current_entity.type, organization=current_entity.organization
        ),
    )