Skip to content

components.payment_gateway.public.accounts

This module defines the public API for the accounts subcomponent.

Only business logic is exposed here. Basic entities and enums are exposed in separate modules to avoid loading the entire subcomponent with its models and dependencies when they are not needed.

Classes

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/protected/business_logic/actions/account_actions.py
def __init__(
    self,
    adyen_client: "AdyenBalanceAccountsApiClient | None",
) -> None:
    self._adyen_client = adyen_client

Attributes

adyen_client property
adyen_client

Ensures the Adyen client is available when accessing it.

Functions

activate_account
activate_account(session, /, id)

Activate an account.

Note

This operation is currently only supported for Adyen.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/actions/account_actions.py
@obs.api_call()
def activate_account(
    self,
    session: Session,
    /,
    id: AccountId,
) -> None:
    """
    Activate an account.

    Note:
        This operation is currently only supported for Adyen.
    """
    from shared.services.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        AdyenBalanceAccountUpdateRequest,
    )

    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)

    provider = get_provider_for_workspace(account.workspace_key)
    if provider == PaymentServiceProvider.adyen:
        # Update account status on Adyen side
        self.adyen_client.update_balance_account(
            account.external_id,
            request=AdyenBalanceAccountUpdateRequest(
                status="active",
            ),
            idempotency_key=None,  # TODO use this for retries
        )

    AccountModelBroker.set_account_status(
        session, id=account.id, status=AccountStatus.active
    )
create classmethod
create()

Normal factory

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

    try:
        adyen_client = AdyenBalanceAccountsApiClient.create(
            credentials=get_adyen_balance_accounts_api_client_credentials()
        )
    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.

This will create a new account on the PSP workspace linked to the given account holder.

Note

This operation is currently only supported for Adyen.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/actions/account_actions.py
@obs.api_call()
def create_account(
    self,
    session: Session,
    /,
    account_holder_id: AccountHolderId,
    description: str,
    reference: str | None = None,
) -> AccountId:
    """
    Create an account for an account holder.

    This will create a new account on the PSP workspace linked to the given account holder.

    Note:
        This operation is currently only supported for Adyen.
    """
    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,
        )

    # Only Adyen is supported for now
    raise_on_provider_not_supported(
        account_holder.workspace_key, PaymentServiceProvider.adyen
    )

    # 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 entity from API result
    account = AccountModelBroker.create_account(
        session,
        workspace_key=account_holder.workspace_key,
        external_id=balance_account.id,
        description=balance_account_info.description,
        reference=balance_account_info.reference,
        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/protected/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.payment_providers.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/protected/business_logic/actions/account_actions.py
@obs.api_call()
def deactivate_account(
    self,
    session: Session,
    /,
    id: AccountId,
) -> None:
    """
    Deactivate an account.
    """
    from shared.services.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        AdyenBalanceAccountUpdateRequest,
    )

    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)

    provider = get_provider_for_workspace(account.workspace_key)
    if provider == PaymentServiceProvider.adyen:
        # Update account status on Adyen side
        self.adyen_client.update_balance_account(
            account.external_id,
            request=AdyenBalanceAccountUpdateRequest(
                status="inactive",
            ),
            idempotency_key=None,  # TODO use this for retries
        )

    AccountModelBroker.set_account_status(
        session, id=account.id, status=AccountStatus.inactive
    )
declare_account
declare_account(
    session,
    /,
    external_id,
    account_holder_id,
    description,
    reference=None,
)

Declare an account for an account holder.

The account must exist in the PSP workspace.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/actions/account_actions.py
@obs.api_call()
def declare_account(
    self,
    session: Session,
    /,
    external_id: str,
    account_holder_id: AccountHolderId,
    description: str,
    reference: str | None = None,
) -> AccountId:
    """
    Declare an account for an account holder.

    The account must exist in the PSP workspace.
    """
    with raise_if_account_holder_not_found(account_holder_id):
        account_holder = AccountHolderModelBroker.get_account_holder(
            session,
            id=account_holder_id,
        )

    account = AccountModelBroker.create_account(
        session,
        workspace_key=account_holder.workspace_key,
        external_id=external_id,
        description=description,
        reference=reference,
        account_holder_id=account_holder_id,
        status=AccountStatus.active,
    )
    return AccountId(account.id)
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/protected/business_logic/actions/account_actions.py
@obs.api_call()
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.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        AdyenBalanceAccountUpdateRequest,
    )

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

    if account.status != AccountStatus.closed:
        provider = get_provider_for_workspace(account.workspace_key)
        if provider == PaymentServiceProvider.adyen:
            # Terminating an account should also close it on Adyen side
            self.adyen_client.update_balance_account(
                account.external_id,
                request=AdyenBalanceAccountUpdateRequest(
                    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/protected/business_logic/actions/account_actions.py
@obs.api_call()
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.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        AdyenBalanceAccountUpdateRequest,
    )

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

    raise_on_terminated_account(account)

    provider = get_provider_for_workspace(account.workspace_key)
    if provider == PaymentServiceProvider.adyen:
        # 1. Create Adyen request payload from our model
        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

        # 2. Call Adyen API
        self.adyen_client.update_balance_account(
            account.external_id,
            request=AdyenBalanceAccountUpdateRequest(
                description=balance_account_info.description,
                reference=balance_account_info.reference,
            ),
            idempotency_key=None,  # TODO use this for retries
        )

        # 3. Update entity from API result
        AccountModelBroker.update_account(
            session,
            id=account.id,
            description=balance_account_info.description,
            reference=balance_account_info.reference,
        )
    else:
        # Update entity from provided description and reference
        AccountModelBroker.update_account(
            session,
            id=account.id,
            description=description,
            reference=reference,
        )

AccountBalance dataclass

AccountBalance(id, name, available_amount, currency, as_of)

Bases: DataClassJsonMixin

A balance snapshot for a single PSP account.

Attributes

as_of instance-attribute
as_of

Timestamp at which the PSP captured this snapshot.

available_amount instance-attribute
available_amount

Amount in minor currency units (e.g. cents for CAD).

currency instance-attribute
currency

ISO 4217 currency code (e.g. "CAD").

id instance-attribute
id

ID of the account

name instance-attribute
name

Name of the account at PSP level.

AccountBalanceQueries

AccountBalanceQueries(adapter_registry)

Public queries for fetching account balances across PSPs.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_balance_queries.py
def __init__(self, adapter_registry: AccountBalanceAdapterRegistry) -> None:
    self.adapter_registry = adapter_registry

Attributes

adapter_registry instance-attribute
adapter_registry = adapter_registry

Functions

create classmethod
create()

Normal factory.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_balance_queries.py
@classmethod
def create(cls) -> "AccountBalanceQueries":
    """Normal factory."""
    return cls(adapter_registry=AccountBalanceAdapterRegistry.create())
create_null classmethod
create_null(
    *,
    track_jpmorgan_requests=None,
    jpmorgan_responses=None,
    track_revolut_requests=None,
    revolut_responses=None,
    revolut_simulator=None
)

Null factory for tests.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_balance_queries.py
@classmethod
def create_null(
    cls,
    *,
    track_jpmorgan_requests: list[JPMorganTrackedRequest] | None = None,
    jpmorgan_responses: list[JPMorganConfiguredResponse] | None = None,
    track_revolut_requests: list[RevolutTrackedRequest] | None = None,
    revolut_responses: list[RevolutConfiguredResponse] | None = None,
    revolut_simulator: RevolutBusinessSimulator | None = None,
) -> "AccountBalanceQueries":
    """Null factory for tests."""
    return cls(
        adapter_registry=AccountBalanceAdapterRegistry.create_null(
            track_jpmorgan_requests=track_jpmorgan_requests,
            jpmorgan_responses=jpmorgan_responses,
            track_revolut_requests=track_revolut_requests,
            revolut_responses=revolut_responses,
            revolut_simulator=revolut_simulator,
        )
    )
list_account_balances
list_account_balances(*, workspace_key)

Return all balances exposed by the PSP backing workspace_key.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_balance_queries.py
@obs.api_call()
def list_account_balances(self, *, workspace_key: str) -> list[AccountBalance]:
    """Return all balances exposed by the PSP backing ``workspace_key``."""
    provider = get_provider_for_workspace(workspace_key)
    if provider is None:
        raise WorkspaceNotRegisteredException(workspace_key)
    adapter = self.adapter_registry.get_account_balance_adapter(provider)
    return adapter.list_account_balances(workspace_key=workspace_key)

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/protected/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

Attributes

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.

Functions

create classmethod
create()

Normal factory

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

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

    try:
        adyen_legal_entities_client = AdyenLegalEntitiesApiClient.create(
            credentials=get_adyen_legal_entities_api_client_credentials()
        )
    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/protected/business_logic/actions/account_holder_actions.py
@classmethod
def create_null(cls) -> "AccountHolderActions":
    """Null factory"""
    from shared.services.payment_providers.adyen.clients.adyen_account_holders_api_client import (
        AdyenAccountHoldersApiClient,
    )
    from shared.services.payment_providers.adyen.clients.adyen_legal_entities_api_client import (
        AdyenLegalEntitiesApiClient,
    )

    return cls(
        AdyenAccountHoldersApiClient.create_null(),
        AdyenLegalEntitiesApiClient.create_null(),
    )
declare_account_holder
declare_account_holder(
    session,
    /,
    workspace_key,
    external_id,
    description,
    reference=None,
)

Declare an account holder.

The account holder must exist in the PSP workspace.

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

    The account holder must exist in the PSP workspace.
    """
    account_holder = AccountHolderModelBroker.create_account_holder(
        session,
        workspace_key=workspace_key,
        external_id=external_id,
        description=description,
        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/protected/business_logic/actions/account_holder_actions.py
@obs.api_call()
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/protected/business_logic/actions/account_holder_actions.py
@obs.api_call()
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.payment_providers.adyen.openapi.legal_entity_service_v3 import (
        AdyenLegalEntityInfo,
    )

    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=AdyenLegalEntityInfo(
            type=current_entity.type, organization=current_entity.organization
        ),
    )

AccountHolderNotFoundException

Bases: PaymentAccountException

Exception raised when trying to use a non-existing Account Holder.

AccountHolderQueries

This class contains all the queries related to account holders.

Functions

get_account_holder
get_account_holder(session, /, id)

Get an account holder entity from its ID.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_holder_queries.py
@obs.api_call()
def get_account_holder(
    self,
    session: Session,
    /,
    id: AccountHolderId,
) -> AccountHolder:
    """
    Get an account holder entity from its ID.
    """
    with raise_if_account_holder_not_found(id):
        account_holder = AccountHolderModelBroker.get_account_holder(session, id=id)

    raise_on_terminated_account_holder(account_holder)

    return AccountHolder(
        id=AccountHolderId(account_holder.id),
        workspace_key=account_holder.workspace_key,
        external_id=account_holder.external_id,
        description=account_holder.description,
        reference=account_holder.reference,
    )
get_account_holder_id_by_external_id
get_account_holder_id_by_external_id(
    session, /, workspace_key, external_id
)

Get an account holder entity from its external ID.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_holder_queries.py
@obs.api_call()
def get_account_holder_id_by_external_id(
    self,
    session: Session,
    /,
    workspace_key: str,
    external_id: str,
) -> AccountHolderId:
    """
    Get an account holder entity from its external ID.
    """
    with raise_if_account_holder_not_found_for_external_id(external_id):
        account_holder_id = (
            AccountHolderModelBroker.get_account_holder_id_by_external_id(
                session,
                workspace_key=workspace_key,
                external_id=external_id,
            )
        )

    return AccountHolderId(account_holder_id)

AccountHolderTerminatedException

Bases: PaymentAccountException

Exception raised when trying to use a terminated Account Holder.

AccountNotFoundException

Bases: PaymentAccountException

Exception raised when trying to use a non-existing Account.

AccountNotFoundOnProviderException

AccountNotFoundOnProviderException(external_id)

Bases: PaymentAccountException

Exception raised when the PSP does not know the account.

Distinct from AccountNotFoundException (account unknown to Alan): here Alan knows the account but its PSP id is not (or no longer) found at the provider.

Source code in components/payment_gateway/subcomponents/accounts/protected/exceptions.py
def __init__(self, external_id: str) -> None:
    super().__init__(
        f"Account with external id {external_id} not found on provider"
    )
    self.external_id = external_id

Attributes

external_id instance-attribute
external_id = external_id

AccountQueries

This class contains all the queries related to accounts.

Functions

get_account
get_account(session, /, id)

Get an account entity from its ID.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_queries.py
@obs.api_call()
def get_account(
    self,
    session: Session,
    /,
    id: AccountId,
) -> Account:
    """
    Get an account entity from its ID.
    """
    with raise_if_account_not_found(id):
        account = AccountModelBroker.get_account(session, id=id)

    raise_on_terminated_account(account)

    return _to_account(account)
get_account_id_by_external_id
get_account_id_by_external_id(
    session, /, workspace_key, external_id
)

Get an account ID by its external ID.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_queries.py
@obs.api_call()
def get_account_id_by_external_id(
    self,
    session: Session,
    /,
    workspace_key: str,
    external_id: str,
) -> AccountId:
    """
    Get an account ID by its external ID.
    """
    account_id = AccountModelBroker.get_account_id_by_external_id(
        session,
        workspace_key=workspace_key,
        external_id=external_id,
    )

    return AccountId(account_id)
get_account_ids_by_workspace
get_account_ids_by_workspace(session, /, workspace_key)

Get all active account IDs for a workspace.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_queries.py
@obs.api_call()
def get_account_ids_by_workspace(
    self,
    session: Session,
    /,
    workspace_key: str,
) -> list[AccountId]:
    """Get all active account IDs for a workspace."""
    return [
        AccountId(account.id)
        for account in AccountModelBroker.list_accounts_by_workspace(
            session, workspace_key=workspace_key
        )
    ]
get_account_ids_for_account_holder
get_account_ids_for_account_holder(
    session, /, account_holder_id
)

Get all the account IDs for an account holder.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_queries.py
@obs.api_call()
def get_account_ids_for_account_holder(
    self,
    session: Session,
    /,
    account_holder_id: AccountHolderId,
) -> list[AccountId]:
    """
    Get all the account IDs for an account holder.
    """
    return [
        AccountId(account_id)
        for account_id in AccountModelBroker.list_account_ids_for_account_holder(
            session, account_holder_id=account_holder_id
        )
    ]
get_account_including_terminated
get_account_including_terminated(session, /, id)

Get an account entity from its ID, including terminated ones.

Use this for read-only contexts (eg admin/marmot pages) where seeing a terminated account's data is a legitimate use case.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_queries.py
@obs.api_call()
def get_account_including_terminated(
    self,
    session: Session,
    /,
    id: AccountId,
) -> Account:
    """
    Get an account entity from its ID, including terminated ones.

    Use this for read-only contexts (eg admin/marmot pages) where seeing
    a terminated account's data is a legitimate use case.
    """
    with raise_if_account_not_found(id):
        account = AccountModelBroker.get_account(session, id=id)

    return _to_account(account)
get_accounts_for_account_holder
get_accounts_for_account_holder(
    session, /, account_holder_id
)

Get all the accounts for an account holder.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/account_queries.py
@obs.api_call()
def get_accounts_for_account_holder(
    self,
    session: Session,
    /,
    account_holder_id: AccountHolderId,
) -> list[Account]:
    """
    Get all the accounts for an account holder.
    """
    return [
        _to_account(account)
        for account in AccountModelBroker.list_accounts_for_account_holder(
            session, account_holder_id=account_holder_id
        )
    ]

AccountTerminatedException

Bases: PaymentAccountException

Exception raised when trying to use a terminated Account.

InvalidAccountStatusTransitionException

Bases: PaymentAccountException, ValueError

Exception raised when attempting an invalid Account status transition.

PaymentAccountException

Bases: PaymentGatewayException

Base class for all Account exceptions.

ProviderAccount dataclass

ProviderAccount(id, name, status, balances)

Bases: DataClassJsonMixin

A single provider account, with the balances it currently holds.

Attributes

balances instance-attribute
balances

Balances held by the account, one entry per currency.

id instance-attribute
id

ID of the account at PSP level.

name instance-attribute
name

Name of the account at PSP level.

status instance-attribute
status

Status of the account at PSP level (e.g. "active").

ProviderAccountQueries

ProviderAccountQueries(adapter_registry)

Public queries for fetching a single provider account, with its live balances.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/provider_account_queries.py
def __init__(self, adapter_registry: ProviderAccountAdapterRegistry) -> None:
    self.adapter_registry = adapter_registry

Attributes

adapter_registry instance-attribute
adapter_registry = adapter_registry

Functions

create classmethod
create()

Normal factory.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/provider_account_queries.py
@classmethod
def create(cls) -> "ProviderAccountQueries":
    """Normal factory."""
    return cls(adapter_registry=ProviderAccountAdapterRegistry.create())
create_null classmethod
create_null(*, track_requests=None, responses=None)

Null factory for tests.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/provider_account_queries.py
@classmethod
def create_null(
    cls,
    *,
    track_requests: list[tuple[str, dict, dict]] | None = None,  # type: ignore[type-arg]
    responses: list[tuple[int, dict]] | None = None,  # type: ignore[type-arg]
) -> "ProviderAccountQueries":
    """Null factory for tests."""
    return cls(
        adapter_registry=ProviderAccountAdapterRegistry.create_null(
            track_requests=track_requests,
            responses=responses,
        )
    )
get_account
get_account(*, account_id)

Return the provider account, with its live balances, backing account_id.

Resolves the PSP workspace and the account's external id from the local Account (terminated accounts included, so a closed Flex company account still exposes its balance), then fetches the account live from the PSP.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/queries/provider_account_queries.py
@obs.api_call()
def get_account(self, *, account_id: AccountId) -> ProviderAccount:
    """Return the provider account, with its live balances, backing ``account_id``.

    Resolves the PSP workspace and the account's external id from the local
    ``Account`` (terminated accounts included, so a closed Flex company
    account still exposes its balance), then fetches the account live from
    the PSP.
    """
    with raise_if_account_not_found(account_id):
        account = AccountModelBroker.get_account(current_session, id=account_id)

    provider = get_provider_for_workspace(account.workspace_key)
    if provider is None:
        raise WorkspaceNotRegisteredException(account.workspace_key)

    adapter = self.adapter_registry.get_account_adapter(provider)
    return adapter.get_account(external_id=account.external_id)

SepaBeneficiaryLogic

This class is the public interface to the SEPA beneficiaries logic.

Functions

declare_sepa_beneficiary
declare_sepa_beneficiary(
    session,
    /,
    account_id,
    external_id,
    issued_at,
    name,
    iban,
    status=SepaBeneficiaryStatus.enabled,
)

Declare a new SEPA beneficiary on the given account.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/sepa_beneficiaries.py
@obs.api_call()
def declare_sepa_beneficiary(
    self,
    session: Session,
    /,
    account_id: AccountId,
    external_id: str,
    issued_at: datetime,
    name: str,
    iban: str,
    status: SepaBeneficiaryStatus = SepaBeneficiaryStatus.enabled,
) -> SepaBeneficiary:
    """
    Declare a new SEPA beneficiary on the given account.
    """
    with raise_if_account_not_found(account_id):
        account = AccountModelBroker.get_account(session, account_id)

    sepa_beneficiary = SepaBeneficiaryModelBroker.create_sepa_beneficiary(
        session,
        workspace_key=account.workspace_key,
        external_id=external_id,
        account_id=account_id,
        issued_at=issued_at,
        name=name,
        iban=iban,
        status=status,
    )

    return _to_sepa_beneficiary(sepa_beneficiary_model=sepa_beneficiary)
get_sepa_beneficiary
get_sepa_beneficiary(session, /, id)

Get a SEPA beneficiary entity from its ID.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/sepa_beneficiaries.py
@obs.api_call()
def get_sepa_beneficiary(
    self,
    session: Session,
    /,
    id: SepaBeneficiaryId,
) -> SepaBeneficiary:
    """
    Get a SEPA beneficiary entity from its ID.
    """
    with raise_if_sepa_beneficiary_not_found(id):
        sepa_beneficiary = SepaBeneficiaryModelBroker.get_sepa_beneficiary(
            session,
            id=id,
        )

    return _to_sepa_beneficiary(sepa_beneficiary_model=sepa_beneficiary)
get_sepa_beneficiary_by_external_id
get_sepa_beneficiary_by_external_id(
    session, /, workspace_key, external_id
)

Find a SEPA beneficiary by its external ID.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/sepa_beneficiaries.py
@obs.api_call()
def get_sepa_beneficiary_by_external_id(
    self,
    session: Session,
    /,
    workspace_key: str,
    external_id: str,
) -> Optional[SepaBeneficiary]:
    """
    Find a SEPA beneficiary by its external ID.
    """
    with raise_if_sepa_beneficiary_not_found_for_external_id(external_id):
        sepa_beneficiary = (
            SepaBeneficiaryModelBroker.find_sepa_beneficiary_by_external_id(
                session,
                workspace_key=workspace_key,
                external_id=external_id,
            )
        )

    return (
        _to_sepa_beneficiary(sepa_beneficiary_model=sepa_beneficiary)
        if sepa_beneficiary is not None
        else None
    )
set_sepa_beneficiary_status
set_sepa_beneficiary_status(session, /, id, status)

Update the status of the given SEPA beneficiary.

Source code in components/payment_gateway/subcomponents/accounts/protected/business_logic/sepa_beneficiaries.py
@obs.api_call()
def set_sepa_beneficiary_status(
    self,
    session: Session,
    /,
    id: SepaBeneficiaryId,
    status: SepaBeneficiaryStatus,
) -> None:
    """
    Update the status of the given SEPA beneficiary.
    """
    with raise_if_sepa_beneficiary_not_found(id):
        SepaBeneficiaryModelBroker.set_sepa_beneficiary_status(
            session,
            id=id,
            status=status,
        )