Skip to content

Actions

components.payment_gateway.subcomponents.payments.protected.business_logic.actions.payment_actions

PaymentActions

PaymentActions(
    payment_method_adapter_registry,
    payment_request_propagation,
)

Actions for payment operations, such as: - SEPA Direct Debit - SEPA Credit Transfer - Canada Electronic Funds Transfer

Tags
Source code in components/payment_gateway/subcomponents/payments/protected/business_logic/actions/payment_actions.py
def __init__(
    self,
    payment_method_adapter_registry: PaymentMethodAdapterRegistry,
    payment_request_propagation: Propagation,
) -> None:
    self.payment_method_adapter_registry = payment_method_adapter_registry
    self.payment_request_propagation = payment_request_propagation

create classmethod

create()

Normal factory

Source code in components/payment_gateway/subcomponents/payments/protected/business_logic/actions/payment_actions.py
@classmethod
def create(cls) -> "PaymentActions":
    """Normal factory"""
    return cls(
        payment_method_adapter_registry=PaymentMethodAdapterRegistry.create(),
        payment_request_propagation=Propagation.REQUIRES_NEW,
    )

create_null classmethod

create_null(track_adyen_requests=None)

Null factory

Source code in components/payment_gateway/subcomponents/payments/protected/business_logic/actions/payment_actions.py
@classmethod
def create_null(
    cls,
    track_adyen_requests: list[tuple[str, dict, dict]] | None = None,  # type: ignore[type-arg]
) -> "PaymentActions":
    """Null factory"""
    return cls(
        payment_method_adapter_registry=PaymentMethodAdapterRegistry.create_null(
            track_adyen_requests=track_adyen_requests,
        ),
        payment_request_propagation=Propagation.REQUIRED,
    )

initiate_direct_debit_request

initiate_direct_debit_request(
    session,
    /,
    *,
    debtor_financial_instrument_id,
    creditor_account_id,
    amount,
    currency,
    reference,
    description=None,
)

Initiates a Direct Debit payment request.

  • Debtor Financial Instrument: External Bank Account we're transferring funds from.
  • Creditor Account: Provider Account we're transferring funds to, managed by Alan on a PSP Workspace.

Parameters:

Name Type Description Default
session Session

SQLAlchemy session to use for DB operations.

required
debtor_financial_instrument_id FinancialInstrumentId

ID of the financial instrument owning the account that will be debited.

required
creditor_account_id AccountId

ID of the account that will receive the funds.

required
amount int

Payment amount in minor currency units (e.g., cents for EUR). Must be positive.

required
currency CurrencyCode

ISO 4217 currency code (e.g., 'USD', 'EUR').

required
reference str

Unique reference for the payment request. Used for tracking and reconciliation. Must be short (max 35 characters) to be safely used across all payment providers.

required
description str | None

Optional human-readable description for the payment. Defaults to None.

None

Returns:

Type Description
PaymentRequestId

The ID of the newly created payment request.

Note

This operation is currently only supported for Adyen.

Tags
Source code in components/payment_gateway/subcomponents/payments/protected/business_logic/actions/payment_actions.py
@obs.api_call()
def initiate_direct_debit_request(
    self,
    session: Session,
    /,
    *,
    debtor_financial_instrument_id: FinancialInstrumentId,
    creditor_account_id: AccountId,
    amount: int,
    currency: CurrencyCode,
    reference: str,
    description: str | None = None,
) -> PaymentRequestId:
    """Initiates a Direct Debit payment request.

    - Debtor Financial Instrument: External Bank Account we're transferring funds from.
    - Creditor Account: Provider Account we're transferring funds to, managed by Alan on a PSP Workspace.

    Args:
        session: SQLAlchemy session to use for DB operations.
        debtor_financial_instrument_id: ID of the financial instrument owning the account that will be debited.
        creditor_account_id: ID of the account that will receive the funds.
        amount: Payment amount in minor currency units (e.g., cents for EUR). Must be positive.
        currency: ISO 4217 currency code (e.g., 'USD', 'EUR').
        reference: Unique reference for the payment request. Used for tracking and reconciliation. Must be short (max 35 characters) to be safely used across all payment providers.
        description: Optional human-readable description for the payment. Defaults to None.

    Returns:
        The ID of the newly created payment request.

    Note:
        This operation is currently only supported for Adyen.

    Tags:
        - @payment_flow: Flex Top-Up
        - @payment_method: Direct Debit
        - @adyen: payments_api
    """
    with raise_if_financial_instrument_not_found(debtor_financial_instrument_id):
        debtor_financial_instrument = (
            FinancialInstrumentModelBroker.get_financial_instrument(
                session,
                debtor_financial_instrument_id,
                with_legal_entity=True,
            )
        )
    raise_on_terminated_financial_instrument(debtor_financial_instrument)
    raise_on_terminated_legal_entity(debtor_financial_instrument.legal_entity)

    # 1. Get Account to retrieve the payment service provider
    with raise_if_account_not_found(creditor_account_id):
        creditor_account = AccountModelBroker.get_account(
            session, creditor_account_id
        )
    raise_on_terminated_account(creditor_account)

    payment_service_provider = get_provider_for_workspace(
        creditor_account.workspace_key
    )
    if payment_service_provider is None:
        raise WorkspaceNotRegisteredException(creditor_account.workspace_key)
    payment_method_adapter = (
        self.payment_method_adapter_registry.get_direct_debit_adapter(
            payment_service_provider
        )
    )

    with transaction(propagation=self.payment_request_propagation) as session:
        # 2. Call the adapter to initiate the transfer
        external_id, raw_response = (
            payment_method_adapter.initiate_direct_debit_request(
                debtor_financial_instrument=debtor_financial_instrument,
                creditor_account=creditor_account,
                amount=amount,
                currency=currency,
                reference=reference,
                description=description,
            )
        )

        # 3. Record the PaymentRequest with the response
        # TODO @frederic.bonnet 2026-01-15: Should we record the payment request on failure? => keep consistent with initiate_wire_transfer_request
        payment_request = PaymentRequestModelBroker.create_payment_request(
            session,
            external_id=external_id,
            workspace_key=creditor_account.workspace_key,
            account_id=creditor_account_id,
            payment_type=PaymentRequestType.direct_debit,
            reference=reference,
            amount=amount,
            currency=currency,
            raw=raw_response,
        )

    return PaymentRequestId(payment_request.id)

initiate_wire_transfer_request

initiate_wire_transfer_request(
    session,
    /,
    *,
    debtor_account_id,
    creditor_financial_instrument_id,
    amount,
    currency,
    reference,
    description=None,
)

Initiates a Wire Transfer payment request.

  • Debtor Account: Provider Account we're transferring funds from, managed by Alan on a PSP Workspace.
  • Creditor Financial Instrument: External Bank Account we're transferring funds to, belonging to the counterparty.

Parameters:

Name Type Description Default
session Session

SQLAlchemy session to use for DB operations.

required
debtor_account_id AccountId

ID of the account that will be debited.

required
creditor_financial_instrument_id FinancialInstrumentId

ID of the financial instrument that will receive the funds.

required
amount int

Payment amount in minor currency units (e.g., cents for EUR). Must be positive.

required
currency CurrencyCode

ISO 4217 currency code (e.g., 'USD', 'EUR').

required
reference str

Unique reference for the payment request. Used for tracking and reconciliation. Must be short (max 35 characters) to be safely used across all payment providers.

required
description str | None

Optional human-readable description for the payment. Defaults to None.

None

Returns:

Type Description
PaymentRequestId

The ID of the newly created payment request.

Note

This operation is currently only supported for JPMorgan.

Tags
Source code in components/payment_gateway/subcomponents/payments/protected/business_logic/actions/payment_actions.py
@obs.api_call()
def initiate_wire_transfer_request(
    self,
    session: Session,
    /,
    *,
    debtor_account_id: AccountId,
    creditor_financial_instrument_id: FinancialInstrumentId,
    amount: int,
    currency: CurrencyCode,
    reference: str,
    description: str | None = None,
) -> PaymentRequestId:
    """
    Initiates a Wire Transfer payment request.

    - Debtor Account: Provider Account we're transferring funds from, managed by Alan on a PSP Workspace.
    - Creditor Financial Instrument: External Bank Account we're transferring funds to, belonging to the counterparty.

    Args:
        session: SQLAlchemy session to use for DB operations.
        debtor_account_id: ID of the account that will be debited.
        creditor_financial_instrument_id: ID of the financial instrument that will receive the funds.
        amount: Payment amount in minor currency units (e.g., cents for EUR). Must be positive.
        currency: ISO 4217 currency code (e.g., 'USD', 'EUR').
        reference: Unique reference for the payment request. Used for tracking and reconciliation. Must be short (max 35 characters) to be safely used across all payment providers.
        description: Optional human-readable description for the payment. Defaults to None.

    Returns:
        The ID of the newly created payment request.

    Note:
        This operation is currently only supported for JPMorgan.

    Tags:
        - @payment_flow: FR Claims Payout
        - @payment_flow: CA Claims Payout
        - @payment_method: Wire Transfer
        - @jpmorgan: global_payments_api
    """
    with raise_if_financial_instrument_not_found(creditor_financial_instrument_id):
        creditor_financial_instrument = (
            FinancialInstrumentModelBroker.get_financial_instrument(
                session,
                creditor_financial_instrument_id,
                with_legal_entity=True,
            )
        )
    raise_on_terminated_financial_instrument(creditor_financial_instrument)
    raise_on_terminated_legal_entity(creditor_financial_instrument.legal_entity)

    # 1. Get Account to retrieve the payment service provider
    with raise_if_account_not_found(debtor_account_id):
        debtor_account = AccountModelBroker.get_account(session, debtor_account_id)
    raise_on_terminated_account(debtor_account)

    payment_service_provider = get_provider_for_workspace(
        debtor_account.workspace_key
    )
    if payment_service_provider is None:
        raise WorkspaceNotRegisteredException(debtor_account.workspace_key)
    payment_method_adapter = (
        self.payment_method_adapter_registry.get_wire_transfer_adapter(
            payment_service_provider
        )
    )

    with transaction(propagation=self.payment_request_propagation) as session:
        # 2. Call the adapter to initiate the transfer
        external_id, raw_response, bank_transfer_id = (
            payment_method_adapter.initiate_wire_transfer_request(
                debtor_account=debtor_account,
                creditor_financial_instrument=creditor_financial_instrument,
                amount=amount,
                currency=currency,
                reference=reference,
                description=description,
            )
        )

        # 3. Record the PaymentRequest with the response
        # TODO @frederic.bonnet 2026-01-15: Should we record the payment request on failure? => keep consistent with initiate_direct_debit_request
        payment_request = PaymentRequestModelBroker.create_payment_request(
            session,
            external_id=external_id,
            workspace_key=debtor_account.workspace_key,
            account_id=debtor_account_id,
            payment_type=PaymentRequestType.wire_transfer,
            reference=reference,
            amount=amount,
            currency=currency,
            raw=raw_response,
            bank_transfer_id=bank_transfer_id,
        )

    return PaymentRequestId(payment_request.id)

initiate_wire_transfer_request_old

initiate_wire_transfer_request_old(
    session,
    /,
    *,
    debtor_account_id,
    creditor_legal_entity_id,
    creditor_account_type,
    creditor_account_id,
    amount,
    currency,
    reference,
    description=None,
)

Initiates a Wire Transfer payment request through the appropriate adapter.

Source code in components/payment_gateway/subcomponents/payments/protected/business_logic/actions/payment_actions.py
@obs.api_call()
def initiate_wire_transfer_request_old(
    self,
    session: Session,
    /,
    *,
    debtor_account_id: AccountId,
    creditor_legal_entity_id: LegalEntityId,
    creditor_account_type: FinancialInstrumentType,
    creditor_account_id: str,
    amount: int,
    currency: CurrencyCode,
    reference: str,
    description: str | None = None,
) -> tuple[str | None, BankTransferId]:
    """
    Initiates a Wire Transfer payment request through the appropriate adapter.
    """
    with raise_if_legal_entity_not_found(creditor_legal_entity_id):
        creditor_legal_entity = LegalEntityModelBroker.get_legal_entity(
            session, id=creditor_legal_entity_id
        )
    raise_on_terminated_legal_entity(creditor_legal_entity)

    # 1. Get Account to retrieve the payment service provider
    with raise_if_account_not_found(debtor_account_id):
        debtor_account = AccountModelBroker.get_account(session, debtor_account_id)

    payment_service_provider = get_provider_for_workspace(
        debtor_account.workspace_key
    )
    if payment_service_provider is None:
        raise WorkspaceNotRegisteredException(debtor_account.workspace_key)
    payment_method_adapter = (
        self.payment_method_adapter_registry.get_wire_transfer_adapter(
            payment_service_provider
        )
    )

    with transaction(propagation=Propagation.REQUIRES_NEW) as session:
        # 2. Call the payout service provider to initiate the transfer
        try:
            transaction_id, raw_response = (
                payment_method_adapter.initiate_wire_transfer_request_old(
                    creditor=creditor_legal_entity,
                    creditor_account_type=creditor_account_type,
                    creditor_account_id=creditor_account_id,
                    amount=amount,
                    currency=currency,
                    reference=reference,
                    description=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

    with transaction(propagation=Propagation.REQUIRES_NEW) as session:
        # 3. Record a BankTransfer
        # This will create 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.

        # We use a shared transfer history per provider workspace for simplicity
        transfer_history, _ = TransferHistoryModelBroker.upsert_transfer_history(
            session,
            private_type=f"{payment_service_provider}:{debtor_account.workspace_key}",
            private_ref=debtor_account.workspace_key,
        )
        bank_transfer, _ = BankTransferModelBroker.record_bank_transfer(
            session,
            workspace_key=debtor_account.workspace_key,
            external_id=reference,
            effective_date=datetime.now(),
            direction=TransferDirection.OUTGOING,
            transfer_history_id=transfer_history.id,
            account_id=debtor_account_id,
        )

        # 4. Record a TransferUpdate with the response (even if failed)
        TransferUpdateModelBroker.record_transfer_update(
            session,
            workspace_key=debtor_account.workspace_key,
            external_transfer_id=reference,
            sequence_number=1,
            transfer_id=bank_transfer.id,
            transfer_type=TransferUpdateTransferType.BANK,
            direction=TransferDirection.OUTGOING,
            occurred_at=datetime.now(),
            amount=amount,
            currency=currency,
            status=status,
            raw=raw_response,
            external_transaction_id=transaction_id,
        )

    return (
        transaction_id,
        BankTransferId(bank_transfer.id),
    )

payment_method_adapter_registry instance-attribute

payment_method_adapter_registry = (
    payment_method_adapter_registry
)

Registry of adapters for the different payment methods.

payment_request_propagation instance-attribute

payment_request_propagation = payment_request_propagation

Transaction propagation behavior to use for the adapter calls and payment request persistence.