Skip to content

Actions

components.payment_gateway.subcomponents.cards.business_logic.actions.card_authentication_actions

CARD_ISSUING_CONFIGURATIONS_CONFIG_KEY module-attribute

CARD_ISSUING_CONFIGURATIONS_CONFIG_KEY = (
    "ADYEN_CARD_ISSUING_CONFIGURATIONS"
)

CardAuthenticationActions

CardAuthenticationActions(
    adyen_client,
    default_card_configuration_key,
    card_configurations,
    card_reveal_queries,
)

This class contains all the actions relative to card authentication.

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/business_logic/actions/card_authentication_actions.py
def __init__(
    self,
    adyen_client: "AdyenPaymentInstrumentsApiClient | None",
    default_card_configuration_key: str | None,
    card_configurations: dict[str, dict[str, str]],
    card_reveal_queries: CardRevealQueries,
) -> None:
    self._adyen_client = adyen_client
    self.default_card_configuration_key = default_card_configuration_key
    self.card_configurations = card_configurations
    self.card_reveal_queries = card_reveal_queries

adyen_client property

adyen_client

Ensures the Adyen client is available when accessing it.

card_configurations instance-attribute

card_configurations = card_configurations

card_reveal_queries instance-attribute

card_reveal_queries = card_reveal_queries

create classmethod

create()

Normal factory

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_authentication_actions.py
@classmethod
def create(cls) -> "CardAuthenticationActions":
    """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
    try:
        default_card_configuration_key = current_config[
            DEFAULT_CARD_ISSUING_CONFIGURATION_KEY_CONFIG_KEY
        ]
        card_configurations = current_config[CARD_ISSUING_CONFIGURATIONS_CONFIG_KEY]
    except KeyError:
        default_card_configuration_key = None
        card_configurations = {}

    return cls(
        adyen_client=adyen_client,
        default_card_configuration_key=default_card_configuration_key,
        card_configurations=card_configurations,
        card_reveal_queries=CardRevealQueries.create(),
    )

create_null classmethod

create_null(track_adyen_requests=None)

Null factory

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

    adyen_client = AdyenPaymentInstrumentsApiClient.create_null(
        track_requests=track_adyen_requests
    )

    # Dummy configuration for null client
    default_card_configuration_key = "dummy_configuration"
    card_configurations = {
        "dummy_configuration": {
            "country_code": "FR",
            "brand": "dummy_brand",
            "brand_variant": "dummy_brand_variant",
            "profile_id": "dummy_profile_id",
        },
    }

    return cls(
        adyen_client=adyen_client,
        default_card_configuration_key=default_card_configuration_key,
        card_configurations=card_configurations,
        card_reveal_queries=CardRevealQueries.create_null(),
    )

default_card_configuration_key instance-attribute

default_card_configuration_key = (
    default_card_configuration_key
)

edit_card_authentication_info

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

Edit the contact info for card card authentication.

Note

This operation is currently only supported for Adyen.

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_authentication_actions.py
def edit_card_authentication_info(
    self,
    session: Session,
    /,
    id: CardId,
    phone_number: str,
    email: str | None,
) -> None:
    """
    Edit the contact info for card card authentication.

    Note:
        This operation is currently only supported for Adyen.
    """
    from components.payment_gateway.subcomponents.cards.adapters.adyen.helpers import (
        _to_authentication,
    )
    from shared.services.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        AdyenCardInfo,
        AdyenPaymentInstrumentUpdateRequest,
    )

    with raise_if_card_not_found(id):
        card = CardModelBroker.get_card(session, id=id)
    with raise_if_card_configuration_not_found(card.configuration_key):
        configuration_key = mandatory(
            card.configuration_key or self.default_card_configuration_key
        )
        card_configuration = self.card_configurations[configuration_key]
    raise_on_terminated_card(card)

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

    # Adyen requires both the phone number/email and the password to
    # be updated at the same time, so get the password here
    password = self.card_reveal_queries.reveal_card_default_password(id)
    authentication = _to_authentication(
        phone_number=phone_number, email=email, password=password
    )

    card_info = AdyenCardInfo(
        authentication=authentication,
        cardholderName=card.display_name,
        brand=card_configuration["brand"],
        brandVariant=card_configuration["brand_variant"],
        formFactor=("virtual" if card.is_virtual else "physical"),
    )

    self.adyen_client.update_payment_instrument(
        card.external_id,
        request=AdyenPaymentInstrumentUpdateRequest(
            card=card_info,
        ),
        idempotency_key=None,  # TODO use this for retries
    )

DEFAULT_CARD_ISSUING_CONFIGURATION_KEY_CONFIG_KEY module-attribute

DEFAULT_CARD_ISSUING_CONFIGURATION_KEY_CONFIG_KEY = (
    "ADYEN_DEFAULT_CARD_ISSUING_CONFIGURATION_KEY"
)

components.payment_gateway.subcomponents.cards.business_logic.actions.card_delivery_actions

CardDeliveryActions

This class contains all the actions related to the delivery of a card.

create_card_order

create_card_order(
    session,
    /,
    workspace_key,
    external_card_id,
    delivery_status,
    shipping_method,
    tracking_number=None,
)

Create a card order upon reception of a card order creation event.

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_delivery_actions.py
def create_card_order(
    self,
    session: Session,
    /,
    workspace_key: str,
    external_card_id: str,
    delivery_status: CardDeliveryStatus,
    shipping_method: str,
    tracking_number: str | None = None,
) -> CardOrderId:
    """
    Create a card order upon reception of a card order creation event.
    """

    with raise_if_card_not_found_for_external_id(external_card_id):
        card_id = CardModelBroker.get_card_id_by_external_id(
            session,
            workspace_key=workspace_key,
            external_id=external_card_id,
        )

    # TODO what should we do if an order already exists for the same card or the same external ID?
    card_order = CardOrderModelBroker.create_card_order(
        session,
        card_id=card_id,
        delivery_status=delivery_status,
        shipping_method=shipping_method,
        tracking_number=tracking_number,
    )
    return CardOrderId(card_order.id)

declare_card_not_received

declare_card_not_received(session, /, id)

Declare that a card has not been received by the card holder.

This can happen if the card has been lost or stolen during the shipment.

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_delivery_actions.py
def declare_card_not_received(
    self,
    session: Session,
    /,
    id: CardId,
) -> None:
    """
    Declare that a card has not been received by the card holder.

    This can happen if the card has been lost or stolen during the shipment.
    """

    # TODO
    raise NotImplementedError()

declare_card_received

declare_card_received(session, /, id)

Declare that a card has been received by the card holder.

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_delivery_actions.py
def declare_card_received(
    self,
    session: Session,
    /,
    id: CardId,
) -> None:
    """
    Declare that a card has been received by the card holder.
    """

    # TODO
    raise NotImplementedError()

update_card_order

update_card_order(
    session,
    /,
    workspace_key,
    external_card_id,
    delivery_status,
    tracking_number=None,
)

Update a card order upon reception of a card order update event.

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_delivery_actions.py
def update_card_order(
    self,
    session: Session,
    /,
    workspace_key: str,
    external_card_id: str,
    delivery_status: CardDeliveryStatus,
    tracking_number: str | None = None,
) -> None:
    """
    Update a card order upon reception of a card order update event.
    """

    with raise_if_card_not_found_for_external_id(external_card_id):
        card_id = CardModelBroker.get_card_id_by_external_id(
            session,
            workspace_key=workspace_key,
            external_id=external_card_id,
        )

    with raise_if_card_order_not_found_for_card_id(card_id):
        card_order = CardOrderModelBroker.get_card_order_for_card(
            session, card_id=card_id
        )

    CardOrderModelBroker.set_card_order_delivery_status(
        session,
        id=card_order.id,
        delivery_status=delivery_status,
    )
    if tracking_number is not None:
        CardOrderModelBroker.set_card_order_tracking_number(
            session,
            id=card_order.id,
            tracking_number=tracking_number,
        )

components.payment_gateway.subcomponents.cards.business_logic.actions.card_renewal_actions

CardRenewalActions

This class contains all the actions related to the renewal of a card.

renew_card

renew_card(id)

Renew a card.

Renewal can be triggered by the business logic or the card holder. For example, if the card is about to expire or is declared lost, stolen or damaged.

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_renewal_actions.py
def renew_card(
    self,
    id: CardId,
) -> None:
    """
    Renew a card.

    Renewal can be triggered by the business logic or the card holder. For example,
    if the card is about to expire or is declared lost, stolen or damaged.
    """

    # TODO
    raise NotImplementedError()

components.payment_gateway.subcomponents.cards.business_logic.actions.card_status_actions

CardStatusActions

CardStatusActions(adyen_client)

This class contains all the actions used to change the status of a 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/business_logic/actions/card_status_actions.py
def __init__(
    self,
    adyen_client: "AdyenPaymentInstrumentsApiClient | None",
) -> None:
    self._adyen_client = adyen_client

activate_card

activate_card(session, /, id)

Activate a card.

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_status_actions.py
def activate_card(
    self,
    session: Session,
    /,
    id: CardId,
) -> None:
    """
    Activate a card.
    """
    from shared.services.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        AdyenPaymentInstrumentUpdateRequest,
    )

    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.active)

    self.adyen_client.update_payment_instrument(
        card.external_id,
        request=AdyenPaymentInstrumentUpdateRequest(
            status="active",
        ),
        idempotency_key=None,  # TODO use this for retries
    )
    CardModelBroker.set_card_status(session, id=card.id, status=CardStatus.active)

adyen_client property

adyen_client

Ensures the Adyen client is available when accessing it.

close_card

close_card(session, /, id, reason=None)

Close a card. This is non-revertible.

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_status_actions.py
def close_card(
    self,
    session: Session,
    /,
    id: CardId,
    reason: str | None = None,
) -> None:
    """
    Close a card. This is non-revertible.
    """
    from shared.services.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        AdyenPaymentInstrumentUpdateRequest,
    )

    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.closed)

    self.adyen_client.update_payment_instrument(
        card.external_id,
        request=AdyenPaymentInstrumentUpdateRequest(
            status="closed",
            statusReason="endOfLife",
            statusComment=reason,
        ),
        idempotency_key=None,  # TODO use this for retries
    )
    CardModelBroker.set_card_status(session, id=card.id, status=CardStatus.closed)

create classmethod

create()

Normal factory

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_status_actions.py
@classmethod
def create(cls) -> "CardStatusActions":
    """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/business_logic/actions/card_status_actions.py
@classmethod
def create_null(
    cls,
    track_adyen_requests: list[tuple[str, dict, dict]] | None = None,  # type: ignore[type-arg]
) -> "CardStatusActions":
    """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
        )
    )

suspend_card

suspend_card(
    session, /, id, reason=None, suspension_source=None
)

Suspend a card.

Source code in components/payment_gateway/subcomponents/cards/business_logic/actions/card_status_actions.py
def suspend_card(
    self,
    session: Session,
    /,
    id: CardId,
    reason: str | None = None,
    suspension_source: CardSuspensionSource | None = None,
) -> None:
    """
    Suspend a card.
    """
    from shared.services.payment_providers.adyen.openapi.balance_platform_service_v2 import (
        AdyenPaymentInstrumentUpdateRequest,
    )

    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=AdyenPaymentInstrumentUpdateRequest(
            status="suspended",
            statusReason="other" if reason is not None else None,
            statusComment=reason,
        ),
        idempotency_key=None,  # TODO use this for retries
    )
    CardModelBroker.set_card_status(
        session,
        id=card.id,
        status=CardStatus.suspended,
        reason=reason,
        suspension_source=suspension_source,
    )