0045: Swappable Payment Providers
Date: 2025-11-08
Status: Proposed
Context
The payment-gateway-service must integrate with various third-party payment providers (e.g., Stripe, PayPal, Adyen). Hardcoding the integration logic for a single provider would tightly couple our service to that provider's specific API and data models, making it difficult and time-consuming to add new providers or switch between them in the future.
We need an architecture that isolates the core business logic of payment processing from the specific implementation details of any given provider.
Decision
We will adopt the Ports and Adapters pattern (as established globally in ADR-0002) for integrating with payment providers.
-
Port (Interface): A
IPaymentProviderinterface will be defined in theapplicationlayer. This port will define a set of provider-agnostic methods, such asCreatePaymentIntent,CapturePayment, andHandleWebhook. -
Adapters (Implementations): For each third-party provider, a corresponding adapter will be implemented in the
infrastructurelayer. For example,StripeAdapterandPayPalAdapterwill both implement theIPaymentProviderinterface, translating the generic application-layer calls into specific API requests for their respective services. -
Selection at Runtime: The
payment-gateway-servicewill use a factory or configuration-based strategy to select and instantiate the appropriate adapter at runtime based on the request context or tenant configuration.
Consequences
Positive
- Decoupling: The core application logic is completely decoupled from any specific payment provider.
- Extensibility: Adding a new payment provider is a well-defined task: simply create a new adapter that implements the
IPaymentProviderinterface. No changes are needed in the core application logic. - Testability: The application logic can be easily unit-tested by providing a mock implementation of the
IPaymentProviderport, eliminating the need for live external services during tests. - Flexibility: Tenants could potentially be configured to use different payment providers without requiring any code changes.
Negative
- Abstraction Cost: A generic interface must be designed that can accommodate the common features of various payment providers. This can sometimes be challenging if providers have very different models.
- Increased Boilerplate: Each new provider requires a new set of adapter code, which adds to the overall codebase. This is a standard trade-off for the flexibility gained.