Skip to content

Actions

components.payment_gateway.subcomponents.cards.protected.business_logic.actions.card_holder_actions

CardHolderActions

CardHolderActions(card_authentication_actions, card_logic)

This class contains all the actions related to card 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 ⧉

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_holder_actions.py
def __init__(
    self,
    card_authentication_actions: CardAuthenticationActions,
    card_logic: CardLogic,
) -> None:
    self.card_authentication_actions = card_authentication_actions
    self.card_logic = card_logic

card_authentication_actions instance-attribute

card_authentication_actions = card_authentication_actions

card_logic instance-attribute

card_logic = card_logic

create classmethod

create()

Normal factory

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_holder_actions.py
@classmethod
def create(cls) -> "CardHolderActions":
    """Normal factory"""
    return cls(
        card_authentication_actions=CardAuthenticationActions.create(),
        card_logic=CardLogic.create(),
    )

create_null classmethod

create_null()

Null factory

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_holder_actions.py
@classmethod
def create_null(cls) -> "CardHolderActions":
    """Null factory"""
    return cls(
        card_authentication_actions=CardAuthenticationActions.create_null(),
        card_logic=CardLogic.create_null(),
    )

declare_card_holder

declare_card_holder(
    session,
    /,
    workspace_key,
    external_id,
    first_name,
    last_name,
    display_name=None,
    short_name=None,
)

Declare a card holder.

The card holder must exist in the PSP workspace if an external ID is provided.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_holder_actions.py
@obs.api_call()
def declare_card_holder(
    self,
    session: Session,
    /,
    workspace_key: str,
    external_id: str | None,
    first_name: str,
    last_name: str,
    display_name: str | None = None,
    short_name: str | None = None,
) -> CardHolderId:
    """
    Declare a card holder.

    The card holder must exist in the PSP workspace if an external ID is provided.
    """
    card_holder = CardHolderModelBroker.create_card_holder(
        session,
        workspace_key=workspace_key,
        external_id=external_id,
        first_name=first_name,
        last_name=last_name,
        display_name=display_name,
        short_name=short_name,
    )
    return CardHolderId(card_holder.id)

terminate_card_holder

terminate_card_holder(session, /, id)

Terminate a card holder.

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

Card holders in terminal state cannot be modified or used anymore. Any attempt to use or retrieve a terminated card holder will raise a CardHolderTerminatedException.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_holder_actions.py
@obs.api_call()
def terminate_card_holder(
    self,
    session: Session,
    /,
    id: CardHolderId,
) -> None:
    """
    Terminate a card holder.

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

    Card holders in terminal state cannot be modified or used anymore. Any
    attempt to use or retrieve a terminated card holder will raise a
    `CardHolderTerminatedException`.
    """
    with raise_if_card_holder_not_found(id):
        card_holder = CardHolderModelBroker.get_card_holder(session, id=id)

    if not card_holder.is_terminated:
        CardHolderModelBroker.terminate_card_holder(session, id=id)

update_card_holder_contact_info

update_card_holder_contact_info(
    session, /, id, phone_number, email
)

Update the card holder contact info on the PSP. Phone number is needed to add cards to digital wallets. Email is optional as a second option for adding cards to digit wallets.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_holder_actions.py
@obs.api_call()
def update_card_holder_contact_info(
    self,
    session: Session,
    /,
    id: CardHolderId,
    phone_number: str,
    email: str | None,
) -> None:
    """
    Update the card holder contact info on the PSP.
    Phone number is needed to add cards to digital wallets.
    Email is optional as a second option for adding cards to digit wallets.
    """
    with raise_if_card_holder_not_found(id):
        card_holder = CardHolderModelBroker.get_card_holder(session, id=id)

        if len(card_holder.cards) == 0 or phone_number is None:
            return

        active_card = max(card_holder.cards, key=lambda card: card.issued_at)

        self.card_logic.edit_card_authentication_info(
            session,
            id=CardId(active_card.id),
            phone_number=phone_number,
            email=email,
        )

update_card_holder_identity

update_card_holder_identity(
    session,
    /,
    id,
    first_name,
    last_name,
    display_name,
    short_name,
)

Update the identity of a card holder.

The current identity of a card holder is used when issuing a card. The identity of a card holder may change over time, but cards are immutable.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_holder_actions.py
@obs.api_call()
def update_card_holder_identity(
    self,
    session: Session,
    /,
    id: CardHolderId,
    first_name: str,
    last_name: str,
    display_name: str | None,
    short_name: str | None,
) -> None:
    """
    Update the identity of a card holder.

    The current identity of a card holder is used when issuing a card.
    The identity of a card holder may change over time, but cards are immutable.
    """
    with raise_if_card_holder_not_found(id):
        CardHolderModelBroker.update_card_holder(
            session,
            id=id,
            first_name=first_name,
            last_name=last_name,
            display_name=display_name,
            short_name=short_name,
        )

components.payment_gateway.subcomponents.cards.protected.business_logic.actions.card_incidents_actions

CardIncidentsActions

CardIncidentsActions(adyen_client)

This class contains all the actions relative to card incidents.

Incidents should eventually lead to the replacement of the card.

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

adyen_client property

adyen_client

Ensures the Adyen client is available when accessing it.

create classmethod

create()

Normal factory

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_incidents_actions.py
@classmethod
def create(cls) -> "CardIncidentsActions":
    """Normal factory"""
    from shared.services.payment_providers.adyen.clients.adyen_payment_instruments_api_client import (
        AdyenPaymentInstrumentsApiClient,
    )
    from shared.services.payment_providers.adyen.clients.exceptions import (
        AdyenClientMissingCredentialsException,
    )

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

create_null classmethod

create_null(track_adyen_requests=None)

Null factory

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_incidents_actions.py
@classmethod
def create_null(
    cls,
    track_adyen_requests: list[tuple[str, dict, dict]] | None = None,  # type: ignore[type-arg]
) -> "CardIncidentsActions":
    """Null factory"""
    from shared.services.payment_providers.adyen.clients.adyen_payment_instruments_api_client import (
        AdyenPaymentInstrumentsApiClient,
    )

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

declare_card_damaged

declare_card_damaged(
    session, /, id, suspension_source=None
)

Declare a card as damaged.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_incidents_actions.py
@obs.api_call()
def declare_card_damaged(
    self,
    session: Session,
    /,
    id: CardId,
    suspension_source: CardSuspensionSource | None = None,
) -> None:
    """
    Declare a card as damaged.
    """
    from shared.services.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        PaymentInstrumentUpdateRequest,
    )

    with raise_if_card_not_found(id):
        card = CardModelBroker.get_card(session, id=id)

    raise_on_terminated_card(card)
    raise_on_invalid_card_status_transition(card, CardStatus.suspended)

    self.adyen_client.update_payment_instrument(
        card.external_id,
        request=PaymentInstrumentUpdateRequest(
            status="suspended",
            statusReason="damaged",
        ),
        idempotency_key=None,  # TODO use this for retries
    )
    CardModelBroker.set_card_status(
        session,
        id=card.id,
        status=CardStatus.suspended,
        reason="damaged",
        suspension_source=suspension_source,
    )

declare_card_lost

declare_card_lost(session, /, id, suspension_source=None)

Declare a card as lost.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_incidents_actions.py
@obs.api_call()
def declare_card_lost(
    self,
    session: Session,
    /,
    id: CardId,
    suspension_source: CardSuspensionSource | None = None,
) -> None:
    """
    Declare a card as lost.
    """
    from shared.services.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        PaymentInstrumentUpdateRequest,
    )

    with raise_if_card_not_found(id):
        card = CardModelBroker.get_card(session, id=id)

    raise_on_terminated_card(card)
    raise_on_invalid_card_status_transition(card, CardStatus.suspended)

    self.adyen_client.update_payment_instrument(
        card.external_id,
        request=PaymentInstrumentUpdateRequest(
            status="suspended",
            statusReason="lost",
        ),
        idempotency_key=None,  # TODO use this for retries
    )
    CardModelBroker.set_card_status(
        session,
        id=card.id,
        status=CardStatus.suspended,
        reason="lost",
        suspension_source=suspension_source,
    )

declare_card_stolen

declare_card_stolen(session, /, id, suspension_source=None)

Declare a card as stolen.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_incidents_actions.py
@obs.api_call()
def declare_card_stolen(
    self,
    session: Session,
    /,
    id: CardId,
    suspension_source: CardSuspensionSource | None = None,
) -> None:
    """
    Declare a card as stolen.
    """
    from shared.services.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        PaymentInstrumentUpdateRequest,
    )

    with raise_if_card_not_found(id):
        card = CardModelBroker.get_card(session, id=id)

    raise_on_terminated_card(card)
    raise_on_invalid_card_status_transition(card, CardStatus.suspended)

    self.adyen_client.update_payment_instrument(
        card.external_id,
        request=PaymentInstrumentUpdateRequest(
            status="suspended",
            statusReason="stolen",
        ),
        idempotency_key=None,  # TODO use this for retries
    )
    CardModelBroker.set_card_status(
        session,
        id=card.id,
        status=CardStatus.suspended,
        reason="stolen",
        suspension_source=suspension_source,
    )

declare_card_temporarily_suspended

declare_card_temporarily_suspended(
    session, /, id, suspension_source=None
)

Declare a card as temporarily suspended.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_incidents_actions.py
@obs.api_call()
def declare_card_temporarily_suspended(
    self,
    session: Session,
    /,
    id: CardId,
    suspension_source: CardSuspensionSource | None = None,
) -> None:
    """
    Declare a card as temporarily suspended.
    """
    from shared.services.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        PaymentInstrumentUpdateRequest,
    )

    with raise_if_card_not_found(id):
        card = CardModelBroker.get_card(session, id=id)

    raise_on_terminated_card(card)
    raise_on_invalid_card_status_transition(card, CardStatus.suspended)

    self.adyen_client.update_payment_instrument(
        card.external_id,
        request=PaymentInstrumentUpdateRequest(
            status="suspended",
            statusReason="other",
            statusComment="temporarily suspended",
        ),
        idempotency_key=None,  # TODO use this for retries
    )
    CardModelBroker.set_card_status(
        session,
        id=card.id,
        status=CardStatus.suspended,
        reason="temporarily suspended",
        suspension_source=suspension_source,
    )