Move a checkout that branches on two vendors' SDKs behind one PaymentGateway interface with an adapter per vendor, so a fake or a third vendor plugs in without editing the checkout.
A shop app takes card payments through two vendors, and the checkout talks to both SDKs directly: it converts rupees to paise for one, reads a status string from it, reads an integer code from the other, and branches on the provider's name in pay and refund. A third vendor, or a test, means editing the checkout again. The vendors' SDKs are given and cannot be changed. Move the checkout behind a PaymentGateway interface and write one adapter per vendor.
The behaviour to keep, exactly:
| Thing | Value |
|---|---|
pay |
paid <amount>, ref <ref> when the vendor accepts, declined <amount> when it declines |
refund |
refunded <amount> of <order id>, and the vendor is told the refund in its own unit |
Alpha (AlphaSdk) |
paise (rupees × 100), customer token tok_<customer>, the order id as idempotency_key; accepts when the status is succeeded |
Beta (BetaSdk) |
whole rupees, card card_<customer>, the order id as reference; accepts when the code is 0 |
Build this API (names are Python's; JavaScript and TypeScript use the same names, with refund, charge and pay as written):
PaymentDeclined: the exception for a decline (an Error subclass in JavaScript and TypeScript).AlphaAdapter(sdk) and BetaAdapter(sdk): each has charge(customer, amount, idempotency_key), which returns the payment reference (a string) or raises PaymentDeclined, and refund(payment_ref, amount). amount is always in rupees.Checkout(gateway) with pay(order_id, customer, amount) and refund(order_id, amount). It remembers the reference of each paid order, and it uses the order id as the idempotency key. It catches PaymentDeclined and nothing else, and it never names a vendor.The tests check every old number, that each SDK sees its own unit, that a fake gateway written inside a test works, that a third vendor written inside a test plugs in without any change to Checkout, and that no provider name is left in the code.
Write your code, then run 9 example tests and 4 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and the runtime restarts, your code stays.