Card 04/ 08

ExampleDifficulty: Intermediate2 min

Adapter Example: A Legacy Payment Gateway Behind a New Interface

The checkout expects pay(amount); the old gateway wants make_transaction(cents, currency).

A legacy payment gateway is a natural Adapter, and this example maps the template onto it. The checkout is written against a PaymentProcessor with pay(amount), taking a decimal amount in pounds and returning whether it succeeded. The gateway offers make_transaction(cents, currency) and returns a dictionary with a status, so the adapter must convert the units and the result as well as the name.

The template's participants in the payment example
TemplatePayment example
TargetPaymentProcessor, with pay(amount)
AdapteeLegacyGateway, with make_transaction(cents, currency)
AdapterLegacyGatewayAdapter, which holds the gateway and a currency
ClientCheckout, which calls pay and nothing else
py
from abc import ABC, abstractmethod
from decimal import Decimal


class PaymentProcessor(ABC):
    @abstractmethod
    def pay(self, amount: Decimal) -> bool: ...


class LegacyGateway:
    def make_transaction(self, cents: int, currency: str) -> dict:
        print(f"legacy gateway: charging {cents} {currency} cents")
        return {"status": "OK", "reference": "TX-1042"}


class LegacyGatewayAdapter(PaymentProcessor):
    def __init__(self, gateway: LegacyGateway, currency: str) -> None:
        self._gateway = gateway
        self._currency = currency

    def pay(self, amount: Decimal) -> bool:
        cents = int(amount * 100)
        response = self._gateway.make_transaction(cents, self._currency)
        return response["status"] == "OK"


class Checkout:
    def __init__(self, processor: PaymentProcessor) -> None:
        self._processor = processor

    def complete(self, total: Decimal) -> str:
        return "paid" if self._processor.pay(total) else "declined"


checkout = Checkout(LegacyGatewayAdapter(LegacyGateway(), "GBP"))
print(checkout.complete(Decimal("24.99")))
OutputRan with python:3.12-slim (Python 3.12.13)
legacy gateway: charging 2499 GBP cents
paid
The adapter turns pounds into pence, supplies the currency, and reduces the gateway's reply to a boolean.

Three translations happen in pay. The amount is converted from pounds to pence, which is a change of meaning and not only of name. The currency, which the checkout never mentions, is supplied by the adapter from its constructor. The gateway's dictionary is reduced to the boolean the target promises. The output shows the gateway charged 2499 pence and the checkout saw "paid".

The amount is a Decimal, because multiplying a binary floating-point price by 100 can land just below a whole number: in Python 3.12, 0.29 * 100 is 28.999999999999996, and int turns it into 28 pence. The adapter holds that conversion in one place, where it can be tested once.