Skip to content

components.payment_gateway.public.transfers

This module defines the public API for the transfers 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

AccountTransferRouter

Bases: ABC

Router for the account transfer processor to route the account transfers (a.k.a. Adyen's "internalTransfer") to the appropriate entities

Note: the source or the destination account must be a known account to the Payment Gateway

Functions

route abstractmethod
route(direction, reference, account_id, effective_date)

Route an account transfer to the appropriate entities.

Parameters:

Name Type Description Default
direction TransferDirection

The transfer direction

required
reference str

The transfer reference; this is mostly used for tracking and reconciliation

required
account_id AccountId

The ID of the account involved in the transfer

required
effective_date datetime

The effective date at which the transfer occurred

required

Returns:

Type Description
AccountTransferRoutingResult

The entities with which the transfer should be associated.

Source code in components/payment_gateway/subcomponents/transfers/protected/routers.py
@abstractmethod
def route(
    self,
    direction: TransferDirection,
    reference: str,
    account_id: AccountId,
    effective_date: datetime,
) -> AccountTransferRoutingResult:
    """Route an account transfer to the appropriate entities.

    Args:
        direction: The transfer direction
        reference: The transfer reference; this is mostly used for tracking and reconciliation
        account_id: The ID of the account involved in the transfer
        effective_date: The effective date at which the transfer occurred

    Returns:
        The entities with which the transfer should be associated.
    """
    raise NotImplementedError

AccountTransferRoutingException

AccountTransferRoutingException(
    message="An error occurred while routing account transfer",
)

Bases: PaymentGatewayException

Exception raised for errors in the account transfer routing process.

Attributes:

Name Type Description
message

Explanation of the error

Source code in components/payment_gateway/subcomponents/transfers/protected/exceptions.py
def __init__(
    self, message: str = "An error occurred while routing account transfer"
) -> None:
    self.message = f"Error when routing account transfer: {message}"
    super().__init__(self.message)

Attributes

message instance-attribute
message = f'Error when routing account transfer: {message}'

AccountTransferRoutingResult dataclass

AccountTransferRoutingResult(
    transfer_history_id, ledger_id=None
)

Result of the account transfer routing.

Attributes

ledger_id class-attribute instance-attribute
ledger_id = None
transfer_history_id instance-attribute
transfer_history_id

BankTransferLogic

BankTransferLogic(payout_service_provider_registry)

Business logic related to bank transfers like payouts.

Source code in components/payment_gateway/subcomponents/transfers/protected/business_logic/bank_transfers.py
def __init__(
    self,
    payout_service_provider_registry: PayoutServiceProviderRegistry,
) -> None:
    self.payout_service_provider_registry = payout_service_provider_registry

Attributes

payout_service_provider_registry instance-attribute
payout_service_provider_registry = (
    payout_service_provider_registry
)

Functions

create classmethod
create()

Creates a BankTransferLogic instance with real payout service providers.

Source code in components/payment_gateway/subcomponents/transfers/protected/business_logic/bank_transfers.py
@classmethod
def create(cls) -> "BankTransferLogic":
    """
    Creates a BankTransferLogic instance with real payout service providers.
    """
    return BankTransferLogic(PayoutServiceProviderRegistry.create())
create_null classmethod
create_null()

Creates a BankTransferLogic instance with null (no-op) payout service providers.

Source code in components/payment_gateway/subcomponents/transfers/protected/business_logic/bank_transfers.py
@classmethod
def create_null(cls) -> "BankTransferLogic":
    """
    Creates a BankTransferLogic instance with null (no-op) payout service providers.
    """
    return BankTransferLogic(PayoutServiceProviderRegistry.create_null())
initiate_payout_bank_transfer
initiate_payout_bank_transfer(
    debtor_account_id,
    payout_request_id,
    amount_in_cents,
    currency,
    creditor_bank_account,
    recipient,
    payment_description=None,
)

Initiates a payout bank transfer through the appropriate payout service provider.

Source code in components/payment_gateway/subcomponents/transfers/protected/business_logic/bank_transfers.py
def initiate_payout_bank_transfer(
    self,
    debtor_account_id: AccountId,
    payout_request_id: str,
    amount_in_cents: int,
    currency: str,
    creditor_bank_account: BankTransferAccount,
    recipient: RecipientUser,
    payment_description: str | None = None,
) -> PayoutBankTransferTransaction:
    """
    Initiates a payout bank transfer through the appropriate payout service provider.
    """
    # 1. Get Account to retrieve the payment service provider
    debtor_account = AccountModelBroker.get_account(
        current_session,
        id=debtor_account_id,
    )
    payment_service_provider = debtor_account.provider

    # 2. Create a SepaBeneficiary if not existing -> TODO
    #  - use "iban" field to store CaBankAccount.bank_account_info (interac)
    #      - add "iban_type" for interac ?
    #  - use "external_id" field to store CaBankAccount.id

    with transaction(propagation=Propagation.REQUIRES_NEW) as session:
        # 3. Record a BankTransfer
        # This will create a TransferHistory and a BankTransfer in a new transaction
        # even if the call to payment service provider fails. We want to keep track of all
        # attempts to initiate a transfer.
        transfer_history, _ = TransferHistoryModelBroker.record_transfer_history(
            session,
            private_type="global_profile",
            private_ref=str(recipient.profile_id),
        )
        session.flush()
        bank_transfer, _ = BankTransferModelBroker.record_bank_transfer(
            session,
            effective_date=datetime.now(),
            direction=TransferDirection.OUTGOING,
            provider=payment_service_provider,
            transfer_history_id=transfer_history.id,
            external_id=payout_request_id,
            account_id=debtor_account_id,
        )

    bank_transfer_id = bank_transfer.id

    with transaction(propagation=Propagation.REQUIRES_NEW) as session:
        # 4. Call the payout service provider to initiate the transfer
        payout_service_provider = self.payout_service_provider_registry.get(
            payment_service_provider
        )
        try:
            transaction_id, raw_response = payout_service_provider.pay(
                request_id=payout_request_id,
                amount_in_cents=amount_in_cents,
                currency=currency,
                recipient_bank_account=creditor_bank_account,
                recipient=recipient,
                description=payment_description,
            )
            status = PayoutBankTransferStatus.submitted
        except Exception as exception:
            current_logger.error("Failed to initiate payout", exc_info=exception)
            transaction_id = None
            raw_response = {"error": str(exception)}
            status = PayoutBankTransferStatus.submission_failed

        # 5. Record a TransferUpdate with the response (even if failed)
        bank_transfer_update, _ = TransferUpdateModelBroker.record_transfer_update(
            session,
            transfer_id=bank_transfer_id,
            transfer_type=TransferUpdateTransferType.BANK,
            direction=TransferDirection.OUTGOING,
            sequence_number=1,
            occurred_at=datetime.now(),
            amount=amount_in_cents,
            currency=currency,
            status=status,
            provider=payment_service_provider,
            raw=raw_response,
            external_transfer_id=payout_request_id,
            external_transaction_id=transaction_id,
        )

    return PayoutBankTransferTransaction(
        bank_transfer_id=BankTransferId(bank_transfer_update.transfer_id),
        external_transfer_id=bank_transfer_update.external_transfer_id,
        external_transaction_id=bank_transfer_update.external_transaction_id,
        is_submitted=bank_transfer_update.status
        == PayoutBankTransferStatus.submitted,
        amount=bank_transfer_update.amount,
        currency=bank_transfer_update.currency,
    )

BankTransferRouter

Bases: ABC

Router for the bank transfer processor to route the bank transfers (a.k.a. Adyen's "bankTransfer") to the appropriate entities

Functions

route abstractmethod
route(direction, account_id, effective_date)

Route a bank transfer to the appropriate entities.

Parameters:

Name Type Description Default
direction TransferDirection

The transfer direction

required
account_id AccountId

The ID of the account involved in the transfer

required
effective_date datetime

The effective date at which the transfer occurred

required

Returns:

Type Description
BankTransferRoutingResult

The entities with which the transfer should be associated.

Source code in components/payment_gateway/subcomponents/transfers/protected/routers.py
@abstractmethod
def route(
    self,
    direction: TransferDirection,
    account_id: AccountId,
    effective_date: datetime,
) -> BankTransferRoutingResult:
    """Route a bank transfer to the appropriate entities.

    Args:
        direction: The transfer direction
        account_id: The ID of the account involved in the transfer
        effective_date: The effective date at which the transfer occurred

    Returns:
        The entities with which the transfer should be associated.
    """
    raise NotImplementedError

BankTransferRoutingException

BankTransferRoutingException(
    message="An error occurred while routing bank transfer",
)

Bases: PaymentGatewayException

Exception raised for errors in the bank transfer routing process.

Attributes:

Name Type Description
message

Explanation of the error

Source code in components/payment_gateway/subcomponents/transfers/protected/exceptions.py
def __init__(
    self, message: str = "An error occurred while routing bank transfer"
) -> None:
    self.message = f"Error when routing bank transfer: {message}"
    super().__init__(self.message)

Attributes

message instance-attribute
message = f'Error when routing bank transfer: {message}'

BankTransferRoutingResult dataclass

BankTransferRoutingResult(
    transfer_history_id, ledger_id=None
)

Result of the bank transfer routing.

Attributes

ledger_id class-attribute instance-attribute
ledger_id = None

Ledger ID the transfer should affect.

Can be None if the transfer does not affect a Ledger, or if the Transfer History is not associated with a Ledger.

transfer_history_id instance-attribute
transfer_history_id

Transfer History ID the transfer should belong to.

CardTransferRouter

Bases: ABC

Router for the card transfer processor to route the card transfers (a.k.a. Adyen's "payment") to the appropriate entities

Functions

route abstractmethod
route(
    card_id,
    merchant_info,
    effective_date,
    transfer_status,
    transfer_external_id,
)

Route a card transfer to the appropriate entities.

Parameters:

Name Type Description Default
card_id CardId

The ID of the card involved in the transfer

required
merchant_info MerchantInfo

Info about the merchant involved in the transfer

required
effective_date datetime

The effective date at which the transfer occurred

required
transfer_status str

The status of the transfer

required
transfer_external_id str

The external ID of the transfer

required

Returns:

Type Description
CardTransferRoutingResult

The entities with which the transfer should be associated.

Source code in components/payment_gateway/subcomponents/transfers/protected/routers.py
@abstractmethod
def route(
    self,
    card_id: CardId,
    merchant_info: MerchantInfo,
    effective_date: datetime,
    transfer_status: str,
    transfer_external_id: str,
) -> CardTransferRoutingResult:
    """Route a card transfer to the appropriate entities.

    Args:
        card_id: The ID of the card involved in the transfer
        merchant_info: Info about the merchant involved in the transfer
        effective_date: The effective date at which the transfer occurred
        transfer_status: The status of the transfer
        transfer_external_id: The external ID of the transfer

    Returns:
        The entities with which the transfer should be associated.
    """
    raise NotImplementedError

CardTransferRoutingException

CardTransferRoutingException(
    message="An error occurred while routing card transfer",
)

Bases: PaymentGatewayException

Exception raised for errors in the card transfer routing process.

Attributes:

Name Type Description
message

Explanation of the error

Source code in components/payment_gateway/subcomponents/transfers/protected/exceptions.py
def __init__(
    self, message: str = "An error occurred while routing card transfer"
) -> None:
    self.message = f"Error when routing card transfer: {message}"
    super().__init__(self.message)

Attributes

message instance-attribute
message = f'Error when routing card transfer: {message}'

CardTransferRoutingResult dataclass

CardTransferRoutingResult(
    transfer_history_id,
    ledger_id=None,
    expense_category_ids=None,
    line_of_credit_ids=None,
)

Result of the card transfer routing.

Attributes

expense_category_ids class-attribute instance-attribute
expense_category_ids = None

Expense Category IDs the transfer should match.

Can be None if the transfer does not belong to any Expense Category, which can happen for declined or refused transfer.

ledger_id class-attribute instance-attribute
ledger_id = None

Ledger ID the transfer should affect.

Can be None if the transfer does not affect a Ledger, or if the Transfer History is not associated with a Ledger.

line_of_credit_ids class-attribute instance-attribute
line_of_credit_ids = None

Line of Credit IDs the transfer should affect.

Can be None if the transfer does not match any Line of Credit, which can happen for unknown cards or unsupported Expense Categories.

transfer_history_id instance-attribute
transfer_history_id

Transfer History ID the transfer should belong to.

TransferLogic

Functions

audit_accounting_report
audit_accounting_report(
    session,
    account_id,
    accounting_report_document,
    start_date,
)

Audits an accounting report against the Payment Gateway database.

:param account_id: The account id to audit :param accounting_report_document: The accounting report file in CSV format extracted from Payment provider :param start_date: The start date to filter the transfers

Note: This currently works for Adyen accounting reports only

Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def audit_accounting_report(
    self,
    session: Session,
    account_id: AccountId,
    accounting_report_document: BytesIO,
    start_date: datetime | None,
) -> ReportAuditResult:
    """
    Audits an accounting report against the Payment Gateway database.

    :param account_id: The account id to audit
    :param accounting_report_document: The accounting report file in CSV format extracted from Payment provider
    :param start_date: The start date to filter the transfers

    Note: This currently works for Adyen accounting reports only

    """
    from components.payment_gateway.subcomponents.transfers.business_logic.accounting_report.audit_accounting_report import (
        audit_accounting_report as _audit_accounting_report,
    )

    return _audit_accounting_report(
        session,
        account_id=account_id,
        accounting_report_document=accounting_report_document,
        start_date=sanitize_tz_or_none(start_date),
    )
create_internal_transfer
create_internal_transfer(
    session,
    /,
    *,
    source,
    destination,
    amount,
    effective_date,
    description,
    reference,
)

Create a double-entry internal transfer.

This will create a pair of internal transfers with opposite amounts, one negative for the source and one postitive for the destination.

Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def create_internal_transfer(
    self,
    session: Session,
    /,
    *,
    source: TransferHistoryId,
    destination: TransferHistoryId,
    amount: int,
    effective_date: datetime,
    description: str,
    reference: str,
) -> None:
    """
    Create a double-entry internal transfer.

    This will create a pair of internal transfers with opposite amounts, one
    negative for the source and one postitive for the destination.
    """
    if amount <= 0:
        raise ValueError(
            "Amount must be positive. Tip: invert source and destination"
        )

    InternalTransferModelBroker.create_internal_transfer(
        session,
        transfer_history_id=source,
        effective_date=effective_date,
        amount=-amount,
        description=description,
        reference=reference,
    )
    InternalTransferModelBroker.create_internal_transfer(
        session,
        transfer_history_id=destination,
        effective_date=effective_date,
        amount=amount,
        description=description,
        reference=reference,
    )
    session.flush()
create_internal_transfer_entry
create_internal_transfer_entry(
    session,
    /,
    *,
    transfer_history_id,
    amount,
    effective_date,
    description,
    reference,
)

Create a single-entry internal transfer.

Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def create_internal_transfer_entry(
    self,
    session: Session,
    /,
    *,
    transfer_history_id: TransferHistoryId,
    amount: int,
    effective_date: datetime,
    description: str,
    reference: str,
) -> InternalTransferId:
    """
    Create a single-entry internal transfer.
    """
    transfer = InternalTransferModelBroker.create_internal_transfer(
        session,
        transfer_history_id=transfer_history_id,
        effective_date=effective_date,
        amount=amount,
        description=description,
        reference=reference,
    )
    session.flush()
    return InternalTransferId(transfer.id)
create_transfer_history
create_transfer_history(
    session, /, private_type, private_ref
)
Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def create_transfer_history(  # noqa: D102
    self,
    session: Session,
    /,
    private_type: str,
    private_ref: str,
) -> TransferHistoryId:
    transfer_history = TransferHistoryModelBroker.create_transfer_history(
        session,
        private_type=private_type,
        private_ref=private_ref,
    )
    session.flush()
    return TransferHistoryId(transfer_history.id)
get_account_transfer_balance_within_period
get_account_transfer_balance_within_period(
    session,
    /,
    transfer_history_id,
    *,
    period_start_date,
    period_end_date,
)
Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_account_transfer_balance_within_period(  # noqa: D102
    self,
    session: Session,
    /,
    transfer_history_id: TransferHistoryId,
    *,
    period_start_date: datetime,
    period_end_date: datetime,
) -> int:
    # Sanitize datetimes
    period_start_date = sanitize_tz(period_start_date)
    period_end_date = sanitize_tz(period_end_date)

    account_transfer_balance_at_period_start_date = (
        AccountTransferModelBroker.aggregate_account_transfer_balance(
            session,
            transfer_history_id,
            effective_date=period_start_date,
            creation_date=period_start_date,
        )
    )
    account_transfer_balance_at_period_end_date = (
        AccountTransferModelBroker.aggregate_account_transfer_balance(
            session,
            transfer_history_id,
            effective_date=period_end_date,
            creation_date=period_end_date,
        )
    )

    balance_at_start_date = (
        account_transfer_balance_at_period_start_date.received
        + account_transfer_balance_at_period_start_date.reserved
        + account_transfer_balance_at_period_start_date.balance
    )
    balance_at_end_date = (
        account_transfer_balance_at_period_end_date.received
        + account_transfer_balance_at_period_end_date.reserved
        + account_transfer_balance_at_period_end_date.balance
    )

    return balance_at_end_date - balance_at_start_date
get_all_transfers
get_all_transfers(session, /, transfer_history_ids)
Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_all_transfers(  # noqa: D102
    self,
    session: Session,
    /,
    transfer_history_ids: list[TransferHistoryId],
) -> list[Transfer]:
    card_transfers: list[Transfer]
    internal_transfers: list[Transfer]
    bank_transfers: list[Transfer]
    account_transfers: list[Transfer]

    card_transfers = [
        card_transfer_model_to_dataclass(card_transfer)
        for card_transfer in CardTransferModelBroker.list_card_transfers_for_transfer_histories(
            session,
            transfer_history_ids,
        )
    ]

    internal_transfers = [
        internal_transfer_model_to_dataclass(internal_transfer)
        for internal_transfer in InternalTransferModelBroker.list_internal_transfers_for_transfer_histories(
            session,
            transfer_history_ids,
        )
    ]

    bank_transfers = [
        bank_transfer_model_to_dataclass(bank_transfer)
        for bank_transfer in BankTransferModelBroker.list_bank_transfers_for_transfer_histories(
            session,
            transfer_history_ids,
        )
    ]

    account_transfers = [
        account_transfer_model_to_dataclass(account_transfer)
        for account_transfer in AccountTransferModelBroker.list_account_transfers_for_transfer_histories(
            session,
            transfer_history_ids,
        )
    ]

    return sorted(
        card_transfers + internal_transfers + bank_transfers + account_transfers,
        key=lambda t: t.effective_date,
    )
get_balance
get_balance(
    session,
    /,
    transfer_history_id,
    *,
    effective_date,
    creation_date,
)
Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_balance(  # noqa: D102
    self,
    session: Session,
    /,
    transfer_history_id: TransferHistoryId,
    *,
    effective_date: datetime,
    creation_date: datetime,
) -> BalanceInformation:
    # Sanitize datetimes
    effective_date = sanitize_tz(effective_date)
    creation_date = sanitize_tz(creation_date)

    card_transfer_balance = CardTransferModelBroker.aggregate_card_transfer_balance(
        session,
        transfer_history_id,
        effective_date=effective_date,
        creation_date=creation_date,
    )
    internal_transfer_balance = (
        InternalTransferModelBroker.aggregate_internal_transfer_balance(
            session,
            transfer_history_id,
            effective_date=effective_date,
            creation_date=creation_date,
        )
    )
    bank_transfer_balance = BankTransferModelBroker.aggregate_bank_transfer_balance(
        session,
        transfer_history_id,
        effective_date=effective_date,
        creation_date=creation_date,
    )
    account_transfer_balance = (
        AccountTransferModelBroker.aggregate_account_transfer_balance(
            session,
            transfer_history_id,
            effective_date=effective_date,
            creation_date=creation_date,
        )
    )

    return BalanceInformation(
        received=card_transfer_balance.received
        + bank_transfer_balance.received
        + account_transfer_balance.received,
        reserved=card_transfer_balance.reserved
        + bank_transfer_balance.reserved
        + account_transfer_balance.reserved,
        balance=card_transfer_balance.balance
        + bank_transfer_balance.balance
        + account_transfer_balance.balance
        + internal_transfer_balance,
    )
get_bank_transfer_balance_within_period
get_bank_transfer_balance_within_period(
    session,
    /,
    transfer_history_id,
    *,
    period_start_date,
    period_end_date,
)
Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_bank_transfer_balance_within_period(  # noqa: D102
    self,
    session: Session,
    /,
    transfer_history_id: TransferHistoryId,
    *,
    period_start_date: datetime,
    period_end_date: datetime,
) -> int:
    # Sanitize datetimes
    period_start_date = sanitize_tz(period_start_date)
    period_end_date = sanitize_tz(period_end_date)

    bank_transfer_balance_at_period_start_date = (
        BankTransferModelBroker.aggregate_bank_transfer_balance(
            session,
            transfer_history_id,
            effective_date=period_start_date,
            creation_date=period_start_date,
        )
    )
    bank_transfer_balance_at_period_end_date = (
        BankTransferModelBroker.aggregate_bank_transfer_balance(
            session,
            transfer_history_id,
            effective_date=period_end_date,
            creation_date=period_end_date,
        )
    )

    balance_at_start_date = (
        bank_transfer_balance_at_period_start_date.received
        + bank_transfer_balance_at_period_start_date.reserved
        + bank_transfer_balance_at_period_start_date.balance
    )
    balance_at_end_date = (
        bank_transfer_balance_at_period_end_date.received
        + bank_transfer_balance_at_period_end_date.reserved
        + bank_transfer_balance_at_period_end_date.balance
    )

    return balance_at_end_date - balance_at_start_date
get_card_transfers_by_card_ids
get_card_transfers_by_card_ids(
    session,
    /,
    card_ids,
    *,
    without_transfer_history_only=False,
)

Retrieves a list of CardTransfer objects associated with a specific card ID, optionally filtering for those without a transfer history.

Parameters:

Name Type Description Default
session Session

The session to use for the database operations.

required
card_ids Iterable[CardId]

The ID of the card for which to retrieve associated CardTransfer objects.

required
without_transfer_history_only bool

If True, the method will return only those CardTransfer objects that have no associated transfer history. Defaults to False.

False
  • list[CardTransfer]: A list of CardTransfer objects matching the provided criteria. This list may be empty if no matching objects are found.

Note: - The returned CardTransfer objects are ordered by their effective date in descending order, meaning the most recent payments are returned first.

Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_card_transfers_by_card_ids(
    self,
    session: Session,
    /,
    card_ids: Iterable[CardId],
    *,
    without_transfer_history_only: bool = False,
) -> list[CardTransfer]:
    """
    Retrieves a list of CardTransfer objects associated with a specific card ID,
    optionally filtering for those without a transfer history.

    Args:
        session: The session to use for the database operations.
        card_ids: The ID of the card for which to retrieve associated CardTransfer objects.
        without_transfer_history_only (bool, optional): If True, the method will return only those CardTransfer objects
            that have no associated transfer history. Defaults to False.

    Returns:
    - list[CardTransfer]: A list of CardTransfer objects matching the provided criteria.
        This list may be empty if no matching objects are found.

    Note:
    - The returned CardTransfer objects are ordered by their effective date in descending order,
    meaning the most recent payments are returned first.
    """
    card_transfers = CardTransferModelBroker.list_card_transfers_for_cards(
        session,
        card_ids,
        without_transfer_history_only=without_transfer_history_only,
    )
    return [
        card_transfer_model_to_dataclass(card_transfer)
        for card_transfer in card_transfers
    ]
get_internal_transfer_balance_within_period
get_internal_transfer_balance_within_period(
    session,
    /,
    transfer_history_id,
    *,
    period_start_date,
    period_end_date,
)
Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_internal_transfer_balance_within_period(  # noqa: D102
    self,
    session: Session,
    /,
    transfer_history_id: TransferHistoryId,
    *,
    period_start_date: datetime,
    period_end_date: datetime,
) -> int:
    # Sanitize datetimes
    period_start_date = sanitize_tz(period_start_date)
    period_end_date = sanitize_tz(period_end_date)

    internal_transfer_balance_at_period_start_date = (
        InternalTransferModelBroker.aggregate_internal_transfer_balance(
            session,
            transfer_history_id,
            effective_date=period_start_date,
            creation_date=period_start_date,
        )
    )
    internal_transfer_balance_at_period_end_date = (
        InternalTransferModelBroker.aggregate_internal_transfer_balance(
            session,
            transfer_history_id,
            effective_date=period_end_date,
            creation_date=period_end_date,
        )
    )
    return (
        internal_transfer_balance_at_period_end_date
        - internal_transfer_balance_at_period_start_date
    )
get_transfer_by_ref
get_transfer_by_ref(session, /, transfer_ref)
Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_transfer_by_ref(  # noqa: D102
    self,
    session: Session,
    /,
    transfer_ref: str,
) -> CardTransfer | InternalTransfer | BankTransfer | AccountTransfer | None:
    _transfer_id = UUID(
        transfer_ref
    )  # the caller potentially does not know when transfer type is the id referring to
    card_transfer = CardTransferModelBroker.find_card_transfer_by_id(
        session,
        _transfer_id,
    )
    if card_transfer is not None:
        return card_transfer_model_to_dataclass(card_transfer)

    internal_transfer = InternalTransferModelBroker.find_internal_transfer_by_id(
        session,
        _transfer_id,
    )
    if internal_transfer is not None:
        return internal_transfer_model_to_dataclass(internal_transfer)

    account_transfer = AccountTransferModelBroker.find_account_transfer_by_id(
        session,
        _transfer_id,
    )
    if account_transfer is not None:
        return account_transfer_model_to_dataclass(account_transfer)

    bank_transfer = BankTransferModelBroker.find_bank_transfer_by_id(
        session,
        _transfer_id,
    )
    if bank_transfer is not None:
        return bank_transfer_model_to_dataclass(bank_transfer)

    return None
get_transfer_histories
get_transfer_histories(session, /, transfer_history_ids)
Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_transfer_histories(  # noqa: D102
    self, session: Session, /, transfer_history_ids: list[TransferHistoryId]
) -> list[TransferHistory]:
    transfer_histories = TransferHistoryModelBroker.list_transfer_histories(
        session,
        list(transfer_history_ids),
    )

    return [
        TransferHistory(
            id=TransferHistoryId(transfer_history.id),
            private_type=transfer_history.private_type,
            private_ref=transfer_history.private_ref,
            created_at=transfer_history.created_at,
        )
        for transfer_history in transfer_histories
    ]
get_transfer_history_id_by_private_ref
get_transfer_history_id_by_private_ref(
    session, /, private_type, private_ref
)
Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_transfer_history_id_by_private_ref(  # noqa: D102
    self,
    session: Session,
    /,
    private_type: str,
    private_ref: str,
) -> TransferHistoryId:
    transfer_history = (
        TransferHistoryModelBroker.get_transfer_history_by_private_ref(
            session,
            private_type=private_type,
            private_ref=private_ref,
        )
    )

    return TransferHistoryId(transfer_history.id)
get_transfers_by_effective_date
get_transfers_by_effective_date(
    session,
    /,
    transfer_history_ids,
    *,
    start_effective_date,
    end_effective_date=None,
)
Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_transfers_by_effective_date(  # noqa: D102
    self,
    session: Session,
    /,
    transfer_history_ids: list[TransferHistoryId],
    *,
    start_effective_date: datetime,
    end_effective_date: datetime | None = None,
) -> list[Transfer]:
    card_transfers: list[Transfer]
    internal_transfers: list[Transfer]
    bank_transfers: list[Transfer]
    account_transfers: list[Transfer]

    # Sanitize datetimes
    start_effective_date = sanitize_tz(start_effective_date)
    end_effective_date = sanitize_tz_or_none(end_effective_date)

    card_transfers = [
        card_transfer_model_to_dataclass(card_transfer)
        for card_transfer in CardTransferModelBroker.list_card_transfers_for_transfer_histories(
            session,
            transfer_history_ids,
            start_effective_date=start_effective_date,
            end_effective_date=end_effective_date,
        )
    ]

    internal_transfers = [
        internal_transfer_model_to_dataclass(internal_transfer)
        for internal_transfer in InternalTransferModelBroker.list_internal_transfers_for_transfer_histories(
            session,
            transfer_history_ids,
            start_effective_date=start_effective_date,
            end_effective_date=end_effective_date,
        )
    ]

    bank_transfers = [
        bank_transfer_model_to_dataclass(bank_transfer)
        for bank_transfer in BankTransferModelBroker.list_bank_transfers_for_transfer_histories(
            session,
            transfer_history_ids,
            start_effective_date=start_effective_date,
            end_effective_date=end_effective_date,
        )
    ]

    account_transfers = [
        account_transfer_model_to_dataclass(account_transfer)
        for account_transfer in AccountTransferModelBroker.list_account_transfers_for_transfer_histories(
            session,
            transfer_history_ids,
            start_effective_date=start_effective_date,
            end_effective_date=end_effective_date,
        )
    ]

    return sorted(
        card_transfers + internal_transfers + bank_transfers + account_transfers,
        key=lambda t: t.effective_date,
    )
get_transfers_by_event_date
get_transfers_by_event_date(
    session: Session,
    /,
    transfer_history_ids: list[TransferHistoryId],
    *,
    start_effective_date: datetime,
    end_effective_date: datetime | None,
    transfer_types: None = None,
) -> list[Transfer]
get_transfers_by_event_date(
    session: Session,
    /,
    transfer_history_ids: list[TransferHistoryId],
    *,
    start_effective_date: datetime,
    end_effective_date: datetime | None,
    transfer_types: Sequence[type[T]],
) -> list[T]
get_transfers_by_event_date(
    session,
    /,
    transfer_history_ids,
    *,
    start_effective_date,
    end_effective_date,
    transfer_types=None,
)
Use when dealing with card transfers and

you are not only interested in the date at which the card transfer was originated, but want to fetch the individual events associated to it. A Card payment can be theoretically opened for 30 days on mastercard (on Adyen is even more, we have cases of years, a bug on their side). During this period, each event can performa a balance change, therefore the date at which you made a balance change can be different of that of when the card transfer was originated. In Spain, for healthy benefits we have a real time approach for card transfers, therefore the flag filter_by_event_dates allows to retrieve events that happened during a given period, not just card transfers for which we only have the date at which it was started. NON card transfers are more simple and don't need to be treated differently for real time cases vs non-real time.

This method returns each type of transfer ordered by created_at, then it orders by effective_at

Source code in components/payment_gateway/subcomponents/transfers/protected/api.py
@obs.api_call()
def get_transfers_by_event_date(
    self,
    session: Session,
    /,
    transfer_history_ids: list[TransferHistoryId],
    *,
    start_effective_date: datetime,
    end_effective_date: datetime | None,
    transfer_types: Sequence[type[T]] | None = None,
) -> list[Transfer] | list[T]:
    """
        Use when dealing with card transfers and
    you are not only interested in the date at which the card transfer was
    originated, but want to fetch the individual events associated to it. A Card
    payment can be theoretically opened for 30 days on mastercard (on Adyen is even
    more, we have cases of years, a bug on their side). During this period, each
    event can performa a balance change, therefore the date at which you made a
    balance change can be different of that of when the card transfer was
    originated. In Spain, for healthy benefits we have a real time approach for
    card transfers, therefore the flag filter_by_event_dates allows to retrieve
    events that happened during a given period, not just card transfers for which we
    only have the date at which it was started. NON card transfers are more simple
    and don't need to be treated differently for real time cases vs non-real time.

    This method returns each type of transfer ordered by created_at, then it orders by
    effective_at
    """
    card_transfers: list[Transfer]
    internal_transfers: list[Transfer]
    bank_transfers: list[Transfer]
    account_transfers: list[Transfer]

    if transfer_types is None:
        transfer_types = [
            CardTransfer,  # type: ignore[list-item]
            BankTransfer,  # type: ignore[list-item]
            AccountTransfer,  # type: ignore[list-item]
            InternalTransfer,  # type: ignore[list-item]
        ]

    # Sanitize datetimes
    start_effective_date = sanitize_tz(start_effective_date)
    end_effective_date = sanitize_tz_or_none(end_effective_date)

    card_transfers = []
    if CardTransfer in transfer_types:
        card_transfers = [
            card_transfer_model_to_dataclass(
                card_transfer,
                start_event_date=start_effective_date,
                end_event_date=end_effective_date,
            )
            for card_transfer in CardTransferModelBroker.list_card_transfers_for_transfer_histories_by_event_date(
                session,
                transfer_history_ids,
                start_effective_date=start_effective_date,
                end_effective_date=end_effective_date,
                order_by_created_at=True,
            )
        ]

    internal_transfers = []
    if InternalTransfer in transfer_types:
        internal_transfers = [
            internal_transfer_model_to_dataclass(internal_transfer)
            for internal_transfer in InternalTransferModelBroker.list_internal_transfers_for_transfer_histories(
                session,
                transfer_history_ids,
                start_effective_date=start_effective_date,
                end_effective_date=end_effective_date,
                order_by_created_at=True,
            )
        ]

    bank_transfers = []
    if BankTransfer in transfer_types:
        bank_transfers = [
            bank_transfer_model_to_dataclass(bank_transfer)
            for bank_transfer in BankTransferModelBroker.list_bank_transfers_for_transfer_histories(
                session,
                transfer_history_ids,
                start_effective_date=start_effective_date,
                end_effective_date=end_effective_date,
                order_by_created_at=True,
            )
        ]

    account_transfers = []
    if AccountTransfer in transfer_types:
        account_transfers = [
            account_transfer_model_to_dataclass(account_transfer)
            for account_transfer in AccountTransferModelBroker.list_account_transfers_for_transfer_histories(
                session,
                transfer_history_ids,
                start_effective_date=start_effective_date,
                end_effective_date=end_effective_date,
                order_by_created_at=True,
            )
        ]

    return sorted(
        card_transfers + internal_transfers + bank_transfers + account_transfers,
        key=lambda t: t.effective_date,
    )

Functions

calculate_amount_from_events

calculate_amount_from_events(
    events, including_received=False
)

Calculate the amount from a list of transfer events We include the received balance if including_received is True.

Source code in components/payment_gateway/subcomponents/transfers/protected/helpers.py
def calculate_amount_from_events(
    events: Sequence["TransferEvent"], including_received: bool = False
) -> int:
    """
    Calculate the amount from a list of transfer events
    We include the received balance if including_received is True.
    """
    received_amount = sum(event.received for event in events)
    reserved_amount = sum(event.reserved for event in events)
    balance_amount = sum(event.balance for event in events)

    amount_without_received = balance_amount + reserved_amount

    if including_received:
        return amount_without_received + received_amount

    return amount_without_received