Skip to content

Adapters

components.payment_gateway.subcomponents.authorizations.adapters.adyen

authorisation_relay

AdyenAuthorisationRelay

Bases: Subscriber

This class implements the main Adyen Authorisation Relay connector between their webhooks and our business logic.

It provides both an entry point to respond to authorisation webhooks, and a TransferNotificationRequest topic subscriber to process card transfer events on previously handled authorisation requests.

Meta

We keep the British English spelling of "authorisation" everywhere it relates to the Adyen API for consistency, however the rest of the code is PSP-agnostic and hence we use US English spelling everywhere else.

authorization_request_processing_policy instance-attribute
authorization_request_processing_policy
on_authorisation_request
on_authorisation_request(session, message)

Process an incoming Adyen authorisation request event.

This method is called when an authorization request webhook is received from Adyen. It converts the webhook payload to a PendingTransaction before calling the core policy.

Source code in components/payment_gateway/subcomponents/authorizations/adapters/adyen/authorisation_relay.py
@obs.event_subscriber()
def on_authorisation_request(
    self,
    session: Session,
    message: AuthorisationRequest,
) -> AuthorizationResult:
    """Process an incoming Adyen authorisation request event.

    This method is called when an authorization request webhook is received
    from Adyen. It converts the webhook payload to a PendingTransaction
    before calling the core policy.
    """
    logger = current_logger.bind(
        account_holder_external_id=message.accountHolder.id,
        account_external_id=message.balanceAccount.id,
        card_external_id=message.paymentInstrument.id,
        transaction_id=message.schemeUniqueTransactionId,
        merchant_name=message.merchantData.nameLocation.name,
        amount=message.amount.value,
        currency=message.amount.currency,
    )
    try:
        pending_transaction = to_pending_transaction(session, message)
        return self.authorization_request_processing_policy.authorize_pending_transaction(
            session,
            pending_transaction,
        )

    except Exception as e:
        logger.exception(
            "Error processing authorisation request",
            exception=e,
        )
        alert_on_error_processing_authorisation_request(
            request=message,
            message="An unexpected error occurred while processing the authorisation request",
        )
        raise
receive
receive(message)

Receive card transfer events and forward them to the core policy for lifecycle management.

Source code in components/payment_gateway/subcomponents/authorizations/adapters/adyen/authorisation_relay.py
@override
@obs.event_subscriber()
def receive(self, message: TransferNotificationRequest) -> None:
    """Receive card transfer events and forward them to the core policy
    for lifecycle management."""
    from shared.helpers.db import current_session

    logger = current_logger.bind(
        type=message.type,
        transfer_type=message.data.type,
        transfer_id=message.data.id,
        transfer_sequence_number=message.data.sequenceNumber,
    )
    try:
        match message.data.type:
            case "payment":
                self.authorization_request_processing_policy.on_payment_event(
                    current_session,
                    message.data,
                )
                current_session.commit()
            case _:
                # Ignore other types of transfers
                pass
    except Exception as e:
        current_session.rollback()
        logger.exception(
            "Error processing transfer",
            exception=e,
        )
        alert_on_error_processing_transfer_notification(
            notification=message,
            message="An unexpected error occurred while processing the transfer notification",
        )
register_policy
register_policy(authorization_request_processing_policy)

Register the policy to be used for processing authorisation requests.

Any authorisation request will be denied until this method is called.

Source code in components/payment_gateway/subcomponents/authorizations/adapters/adyen/authorisation_relay.py
@obs.api_call()
def register_policy(
    self,
    authorization_request_processing_policy: AuthorizationRequestProcessingPolicy,
) -> None:
    """Register the policy to be used for processing authorisation requests.

    Any authorisation request will be denied until this method is called.
    """
    self.authorization_request_processing_policy = (
        authorization_request_processing_policy
    )

helpers

authorization_request_external_id_from_transfer_data

authorization_request_external_id_from_transfer_data(data)
Source code in components/payment_gateway/subcomponents/authorizations/adapters/adyen/helpers.py
def authorization_request_external_id_from_transfer_data(
    data: TransferData,
) -> str | None:
    if data.category != "issuedCard" or not data.categoryData:
        return None
    if not isinstance(data.categoryData, IssuedCard):
        # This can happen when deserializing from a JSON string
        data.categoryData = IssuedCard.from_dict(data.categoryData)  # type: ignore[arg-type]
    return data.categoryData.schemeUniqueTransactionId

to_pending_transaction

to_pending_transaction(session, data)

Convert an Adyen authorisation request to a pending transaction.

Source code in components/payment_gateway/subcomponents/authorizations/adapters/adyen/helpers.py
def to_pending_transaction(
    session: Session, data: AuthorisationRequest
) -> PendingTransaction:
    """Convert an Adyen authorisation request to a pending transaction."""
    with raise_if_card_not_found_for_external_id(data.paymentInstrument.id):
        card_id = CardModelBroker.get_card_id_by_external_id(
            session,
            provider=PaymentServiceProvider.adyen,
            external_id=data.paymentInstrument.id,
        )
    return PendingTransaction(
        amount=-data.amount.value,  # Adyen sends negative amounts
        currency=CurrencyCode(data.amount.currency),
        card_id=CardId(card_id),
        merchant_info=PendingTransactionMerchantInfo(
            merchant_id=data.merchantData.merchantId.strip(),
            acquirer_id=data.merchantData.acquirerId.strip(),
            mcc=data.merchantData.mcc,
            name=data.merchantData.nameLocation.name.strip(),
            postal_code=data.merchantData.postalCode.strip()
            if data.merchantData.postalCode
            else None,
            city=data.merchantData.nameLocation.city.strip()
            if data.merchantData.nameLocation.city
            else None,
            country=data.merchantData.nameLocation.country,
        ),
        provider=PaymentServiceProvider.adyen,
        external_id=data.schemeUniqueTransactionId,
    )