Skip to main content

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.

  1. Port (Interface): A IPaymentProvider interface will be defined in the application layer. This port will define a set of provider-agnostic methods, such as CreatePaymentIntent, CapturePayment, and HandleWebhook.

  2. Adapters (Implementations): For each third-party provider, a corresponding adapter will be implemented in the infrastructure layer. For example, StripeAdapter and PayPalAdapter will both implement the IPaymentProvider interface, translating the generic application-layer calls into specific API requests for their respective services.

  3. Selection at Runtime: The payment-gateway-service will 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 IPaymentProvider interface. 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 IPaymentProvider port, 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.