Skip to content

Business logic

Cards

CardLogic

CardLogic(
    card_queries,
    card_reveal_queries,
    card_authentication_actions,
)

This class is the public interface to the card logic.

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/cards.py
def __init__(
    self,
    card_queries: CardQueries,
    card_reveal_queries: CardRevealQueries,
    card_authentication_actions: CardAuthenticationActions,
) -> None:
    self.card_queries = card_queries
    self.card_reveal_queries = card_reveal_queries
    self.card_authentication_actions = card_authentication_actions

card_authentication_actions instance-attribute

card_authentication_actions = card_authentication_actions

card_queries instance-attribute

card_queries = card_queries

card_reveal_queries instance-attribute

card_reveal_queries = card_reveal_queries

create classmethod

create()

Normal factory

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@classmethod
def create(cls) -> "CardLogic":
    """Normal factory"""
    return cls(
        card_queries=CardQueries(),
        card_reveal_queries=CardRevealQueries.create(),
        card_authentication_actions=CardAuthenticationActions.create(),
    )

create_null classmethod

create_null()

Null factory

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@classmethod
def create_null(cls) -> "CardLogic":
    """Null factory"""
    return cls(
        card_queries=CardQueries(),
        card_reveal_queries=CardRevealQueries.create_null(),
        card_authentication_actions=CardAuthenticationActions.create_null(),
    )

edit_card_authentication_info

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

Update the card authentication info. The phone number is mandatory, the email is optional

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def edit_card_authentication_info(
    self,
    session: Session,
    /,
    id: CardId,
    phone_number: str,
    email: str | None,
) -> None:
    """
    Update the card authentication info. The phone number is mandatory, the email is optional
    """
    self.card_authentication_actions.edit_card_authentication_info(
        session,
        id,
        phone_number,
        email,
    )

get_card

get_card(session, id)

Get a card entity from its ID.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def get_card(
    self,
    session: Session,
    id: CardId,
) -> Card:
    """
    Get a card entity from its ID.
    """
    return self.card_queries.get_card(
        session,
        id,
    )

get_card_ids_for_account

get_card_ids_for_account(session, account_id)

Get all the card IDs for an account.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def get_card_ids_for_account(
    self,
    session: Session,
    account_id: AccountId,
) -> list[CardId]:
    """
    Get all the card IDs for an account.
    """
    return self.card_queries.get_card_ids_for_account(
        session,
        account_id,
    )

get_card_ids_for_card_holder

get_card_ids_for_card_holder(session, card_holder_id)

Get all the card IDs for a card holder.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def get_card_ids_for_card_holder(
    self,
    session: Session,
    card_holder_id: CardHolderId,
) -> list[CardId]:
    """
    Get all the card IDs for a card holder.
    """
    return self.card_queries.get_card_ids_for_card_holder(
        session,
        card_holder_id,
    )

get_card_pan_reveal_public_key

get_card_pan_reveal_public_key()

Get the base-64 public key used for client-side encryption of card PAN reveal requests.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def get_card_pan_reveal_public_key(self) -> str:
    """
    Get the base-64 public key used for client-side encryption of card PAN reveal requests.
    """
    return self.card_reveal_queries.get_card_pan_reveal_public_key()

get_card_pin_reveal_public_key

get_card_pin_reveal_public_key()

Get the base-64 public key used for client-side encryption of card PIN reveal requests.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def get_card_pin_reveal_public_key(self) -> str:
    """
    Get the base-64 public key used for client-side encryption of card PIN reveal requests.
    """
    return self.card_reveal_queries.get_card_pin_reveal_public_key()

get_cards_for_account

get_cards_for_account(session, account_id)

Get all the cards for an account.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def get_cards_for_account(
    self,
    session: Session,
    account_id: AccountId,
) -> list[Card]:
    """
    Get all the cards for an account.
    """
    return self.card_queries.get_cards_for_account(
        session,
        account_id,
    )

get_cards_for_card_holder

get_cards_for_card_holder(session, card_holder_id)

Get all the cards for a card holder.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def get_cards_for_card_holder(
    self,
    session: Session,
    card_holder_id: CardHolderId,
) -> list[Card]:
    """
    Get all the cards for a card holder.
    """
    return self.card_queries.get_cards_for_card_holder(
        session,
        card_holder_id,
    )

reveal_card_default_password

reveal_card_default_password(id)

Reveal the default password of a card.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def reveal_card_default_password(
    self,
    /,
    id: CardId,
) -> str:
    """
    Reveal the default password of a card.
    """
    return self.card_reveal_queries.reveal_card_default_password(id)

reveal_card_pan

reveal_card_pan(session, id, encrypted_aes_key)

Reveal the PAN of a card.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def reveal_card_pan(
    self,
    session: Session,
    id: CardId,
    encrypted_aes_key: str,
) -> CardRevealPANData:
    """
    Reveal the PAN of a card.
    """
    return self.card_reveal_queries.reveal_card_pan(
        session,
        id,
        encrypted_aes_key,
    )

reveal_card_pin

reveal_card_pin(session, id, encrypted_aes_key)

Reveal the PAN of a card.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/cards.py
@obs.api_call()
def reveal_card_pin(
    self,
    session: Session,
    id: CardId,
    encrypted_aes_key: str,
) -> CardRevealPINData:
    """
    Reveal the PAN of a card.
    """
    return self.card_reveal_queries.reveal_card_pin(
        session,
        id,
        encrypted_aes_key,
    )

CARD_ISSUING_CONFIGURATIONS_CONFIG_KEY module-attribute

CARD_ISSUING_CONFIGURATIONS_CONFIG_KEY = (
    "ADYEN_CARD_ISSUING_CONFIGURATIONS"
)

CARD_PASSWORD_SALT_CONFIG_KEY module-attribute

CARD_PASSWORD_SALT_CONFIG_KEY = (
    "ADYEN_CARD_PASSWORD_SALT_SECRET_NAME"
)

CARD_PASSWORD_SALT_DEFAULT_KEY module-attribute

CARD_PASSWORD_SALT_DEFAULT_KEY = 'ADYEN_CARD_PASSWORD_SALT'

DEFAULT_CARD_ISSUING_CONFIGURATION_KEY_CONFIG_KEY module-attribute

DEFAULT_CARD_ISSUING_CONFIGURATION_KEY_CONFIG_KEY = (
    "ADYEN_DEFAULT_CARD_ISSUING_CONFIGURATION_KEY"
)

CardLifecycleActions

CardLifecycleActions(
    adyen_client,
    default_card_configuration_key,
    card_configurations,
    password_salt,
)

This class contains all the actions used to manage the lifecycle 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/protected/business_logic/actions/card_lifecycle_actions.py
def __init__(
    self,
    adyen_client: "AdyenPaymentInstrumentsApiClient | None",
    default_card_configuration_key: str | None,
    card_configurations: dict[str, dict[str, str]],
    password_salt: str | None,
) -> None:
    self._adyen_client = adyen_client
    self.default_card_configuration_key = default_card_configuration_key
    self.card_configurations = card_configurations
    self.password_salt = password_salt

adyen_client property

adyen_client

Ensures the Adyen client is available when accessing it.

card_configurations instance-attribute

card_configurations = card_configurations

default_card_configuration_key instance-attribute

default_card_configuration_key = (
    default_card_configuration_key
)

password_salt instance-attribute

password_salt = password_salt

create classmethod

create()

Normal factory

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_lifecycle_actions.py
@classmethod
def create(cls) -> "CardLifecycleActions":
    """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 = {}

    try:
        password_salt = raw_secret_from_config(
            config_key=CARD_PASSWORD_SALT_CONFIG_KEY,
            default_secret_value=current_config.get(CARD_PASSWORD_SALT_DEFAULT_KEY),
        )
    except KeyError:
        password_salt = None

    return cls(
        adyen_client=adyen_client,
        default_card_configuration_key=default_card_configuration_key,
        card_configurations=card_configurations,
        password_salt=password_salt,
    )

create_card

create_card(
    session,
    /,
    card_holder_id,
    account_id,
    form_factor,
    phone_number,
    email,
    shipment_info=None,
    issued_at=None,
    issuance_reason=None,
    description=None,
    reference=None,
    configuration_key=None,
)

Create a card for a card holder.

Note

This operation is currently only supported for Adyen.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/actions/card_lifecycle_actions.py
@obs.api_call()
def create_card(
    self,
    session: Session,
    /,
    card_holder_id: CardHolderId,
    account_id: AccountId,
    form_factor: CardFormFactor,
    phone_number: str,
    email: str | None,
    shipment_info: CardShipmentInfo | None = None,
    issued_at: datetime | None = None,
    issuance_reason: str | None = None,
    description: str | None = None,
    reference: str | None = None,
    configuration_key: str | None = None,
) -> CardId:
    """
    Create a card for a card holder.

    Note:
        This operation is currently only supported for Adyen.
    """
    # TODO: pass the phone number here
    from components.payment_gateway.subcomponents.cards.adapters.adyen.helpers import (
        generate_card_password,
        to_adyen_payment_instrument_info,
    )

    if form_factor == CardFormFactor.physical and not shipment_info:
        raise MissingCardShipmentInfoException(
            "Shipment info is required for physical cards"
        )

    if self.password_salt is None:
        raise MissingCardPasswordSaltException()

    with raise_if_card_configuration_not_found(configuration_key):
        configuration_key = mandatory(
            configuration_key
            if configuration_key in self.card_configurations
            else self.default_card_configuration_key
        )
        card_configuration = self.card_configurations[configuration_key]

    with raise_if_card_holder_not_found(card_holder_id):
        card_holder = CardHolderModelBroker.get_card_holder(
            session, id=card_holder_id
        )
    with raise_if_account_not_found(account_id):
        account = AccountModelBroker.get_account(session, id=account_id)

    raise_on_inconsistent_workspace_keys(
        account.workspace_key, card_holder.workspace_key
    )

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

    # 1. Generate a UUID for the card model we're going to create; we will
    #    use it to generate the 3DS password deterministically without
    #    having to store it in the DB
    #
    #    See below for full context:
    #    https://github.com/alan-eu/Topics/discussions/24840?sort=old
    card_id = uuid.uuid4()

    # 2. Generate card password for the card ID + salt
    password = generate_card_password(card_id, self.password_salt)

    # 3. Create Adyen request payload from our model
    payment_instrument_info = to_adyen_payment_instrument_info(
        card_holder=card_holder,
        account=account,
        form_factor=form_factor,
        country_code=card_configuration["country_code"],
        brand=card_configuration["brand"],
        brand_variant=card_configuration["brand_variant"],
        profile_id=card_configuration["profile_id"],
        phone_number=phone_number,
        email=email,
        shipment_info=shipment_info,
        password=password,
        description=description,
        reference=reference,
    )

    # 4. Call Adyen API
    payment_instrument = self.adyen_client.create_payment_instrument(
        request=payment_instrument_info,
        idempotency_key=None,  # TODO use this for retries
    )

    # 5. Create our Card entity from API result
    assert payment_instrument.card
    expiration_date = date(
        int(payment_instrument.card.expiration.year),  # type: ignore[union-attr,arg-type]
        int(payment_instrument.card.expiration.month),  # type: ignore[union-attr,arg-type]
        1,
    ) + relativedelta(months=1, days=-1)  # Last day of expiration month
    card = CardModelBroker.create_card(
        session,
        id=card_id,
        workspace_key=account.workspace_key,
        external_id=payment_instrument.id,
        display_name=payment_instrument.card.cardholderName,
        expiration_date=expiration_date,
        last_four_digits=mandatory(payment_instrument.card.lastFour),
        is_virtual=(form_factor == CardFormFactor.virtual),
        configuration_key=configuration_key,
        card_holder_id=card_holder.id,
        account_id=account.id,
        status=CardStatus.inactive,
        issued_at=issued_at if issued_at else datetime.now(),
        issuance_reason=issuance_reason,
        description=payment_instrument.description,
        reference=payment_instrument.reference,
    )
    return CardId(card.id)

create_null classmethod

create_null(
    default_card_configuration_key=None,
    card_configurations=None,
    track_adyen_requests=None,
)

Null factory

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

    default_card_configuration_key = (
        default_card_configuration_key
        or current_config.get(
            DEFAULT_CARD_ISSUING_CONFIGURATION_KEY_CONFIG_KEY,
            "dummy_configuration",
        )
    )
    card_configurations = card_configurations or current_config.get(
        CARD_ISSUING_CONFIGURATIONS_CONFIG_KEY,
        {
            "dummy_configuration": {
                "country_code": "FR",
                "brand": "dummy_brand",
                "brand_variant": "dummy_brand_variant",
                "profile_id": "dummy_profile_id",
            },
        },
    )
    password_salt = "dummy_password_salt"  # gitleaks:allow

    return cls(
        adyen_client=adyen_client,
        default_card_configuration_key=default_card_configuration_key,
        card_configurations=card_configurations,
        password_salt=password_salt,
    )

terminate_card

terminate_card(session, /, id)

Terminate a card.

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

Cards in terminal state cannot be modified or used anymore. Any attempt to use or retrieve a terminated card will raise a CardTerminatedException.

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

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

    Cards in terminal state cannot be modified or used anymore. Any attempt
    to use or retrieve a terminated card will raise a
    `CardTerminatedException`.
    """
    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)

    if card.status != CardStatus.closed:
        provider = get_provider_for_workspace(card.workspace_key)
        if provider == PaymentServiceProvider.adyen:
            # Terminating a card should also close it on Adyen side
            self.adyen_client.update_payment_instrument(
                card.external_id,
                request=AdyenPaymentInstrumentUpdateRequest(
                    status="closed",
                    statusReason="accountClosure",
                    statusComment="Terminated by Alan",
                ),
                idempotency_key=None,  # TODO use this for retries
            )

        CardModelBroker.set_card_status(
            session, id=card.id, status=CardStatus.closed
        )

    if not card.is_terminated:
        # Terminating a card should set its termination date on first call only
        CardModelBroker.terminate_card(session, id=id)

CardStatusLogic

CardStatusLogic(card_status_actions)

This class is the public interface to the card status logic.

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/card_status.py
def __init__(self, card_status_actions: CardStatusActions) -> None:
    self.card_status_actions = card_status_actions

    # TODO WIP we haven't decided on the pattern we'll use for public events. For now let's declare them as topics.
    self.card_suspended_by_provider_topic = Topic[CardSuspendedByProvider]()

card_status_actions instance-attribute

card_status_actions = card_status_actions

card_suspended_by_provider_topic instance-attribute

card_suspended_by_provider_topic = Topic[
    CardSuspendedByProvider
]()

activate_card

activate_card(session, /, id)

Activate a card.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/card_status.py
@obs.api_call()
def activate_card(
    self,
    session: Session,
    /,
    id: CardId,
) -> None:
    """
    Activate a card.
    """
    self.card_status_actions.activate_card(
        session,
        id,
    )

close_card

close_card(session, /, id, reason)

Close a card. This action is non-revertible.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/card_status.py
@obs.api_call()
def close_card(
    self,
    session: Session,
    /,
    id: CardId,
    reason: str,
) -> None:
    """
    Close a card. This action is non-revertible.
    """
    self.card_status_actions.close_card(
        session,
        id,
        reason=reason,
    )

create classmethod

create()

Normal factory

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/card_status.py
@classmethod
def create(cls) -> "CardStatusLogic":
    """Normal factory"""
    return cls(CardStatusActions.create())

create_null classmethod

create_null(track_adyen_requests=None)

Null factory

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/card_status.py
@classmethod
def create_null(
    cls,
    track_adyen_requests: list[tuple[str, dict, dict]] | None = None,  # type: ignore[type-arg]
) -> "CardStatusLogic":
    """Null factory"""
    return cls(
        CardStatusActions.create_null(track_adyen_requests=track_adyen_requests)
    )

suspend_card

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

Suspend a card.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/card_status.py
@obs.api_call()
def suspend_card(
    self,
    session: Session,
    /,
    id: CardId,
    reason: str,
    suspension_source: CardSuspensionSource | None = None,
) -> None:
    """
    Suspend a card.
    """
    self.card_status_actions.suspend_card(
        session,
        id,
        reason=reason,
        suspension_source=suspension_source,
    )

CardSuspendedByProvider dataclass

CardSuspendedByProvider(id, reason)

id instance-attribute

id

reason instance-attribute

reason

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 (
        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="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 (
        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="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 (
        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="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, reason=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,
    reason: str | None = None,
) -> None:
    """
    Declare a card as temporarily suspended.
    """
    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",
            statusComment="temporarily suspended",
        ),
        idempotency_key=None,  # TODO use this for retries
    )
    CardModelBroker.set_card_status(
        session,
        id=card.id,
        status=CardStatus.suspended,
        reason=reason or "temporarily suspended",
        suspension_source=suspension_source,
    )

CardDelivered dataclass

CardDelivered(id)

id instance-attribute

id

CardDeliveryLogic

CardDeliveryLogic()

This class is the public interface to the card delivery logic.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/card_delivery.py
def __init__(self) -> None:
    self.card_order_queries = CardOrderQueries()
    self.card_delivery_actions = CardDeliveryActions()

    # TODO WIP we haven't decided on the pattern we'll use for public events. For now let's declare them as topics.
    self.card_ordered_topic = Topic[CardOrdered]()
    self.card_delivery_status_changed_topic = Topic[CardDeliveryStatusChanged]()
    self.card_delivered_topic = Topic[CardDelivered]()
    self.card_not_delivered_topic = Topic[CardNotDelivered]()

card_delivered_topic instance-attribute

card_delivered_topic = Topic[CardDelivered]()

card_delivery_actions instance-attribute

card_delivery_actions = CardDeliveryActions()

card_delivery_status_changed_topic instance-attribute

card_delivery_status_changed_topic = Topic[
    CardDeliveryStatusChanged
]()

card_not_delivered_topic instance-attribute

card_not_delivered_topic = Topic[CardNotDelivered]()

card_order_queries instance-attribute

card_order_queries = CardOrderQueries()

card_ordered_topic instance-attribute

card_ordered_topic = Topic[CardOrdered]()

declare_card_not_received

declare_card_not_received(session, /, id)
Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/card_delivery.py
@obs.api_call()
def declare_card_not_received(  # noqa: D102
    self,
    session: Session,
    /,
    id: CardId,
) -> None:
    self.card_delivery_actions.declare_card_not_received(
        session,
        id,
    )

declare_card_received

declare_card_received(session, /, id)
Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/card_delivery.py
@obs.api_call()
def declare_card_received(  # noqa: D102
    self,
    session: Session,
    /,
    id: CardId,
) -> None:
    self.card_delivery_actions.declare_card_received(
        session,
        id,
    )

get_card_order

get_card_order(session, /, card_id)

Get the card order info for a card.

Source code in components/payment_gateway/subcomponents/cards/protected/business_logic/card_delivery.py
@obs.api_call()
def get_card_order(
    self,
    session: Session,
    /,
    card_id: CardId,
) -> CardOrder:
    """
    Get the card order info for a card.
    """
    return self.card_order_queries.get_card_order(
        session,
        card_id,
    )

CardDeliveryStatusChanged dataclass

CardDeliveryStatusChanged(id, status)

id instance-attribute

id

status instance-attribute

status

CardNotDelivered dataclass

CardNotDelivered(id)

id instance-attribute

id

CardOrdered dataclass

CardOrdered(id)

id instance-attribute

id