Applying the Strategy Pattern to a Payment Service with Dependency Injection

Learn how to cleanly implement flexible payment logic in C# using the Strategy pattern and Dependency Injection

Posted by Hüseyin Sekmenoğlu on April 11, 2025 Architecture & Patterns Backend Development

๐Ÿงฉ Introduction

In modern application development, handling multiple payment methods like Credit Card and PayPal can quickly become a tangle of if-else or switch statements. This tight coupling makes your code harder to maintain and scale. Fortunately, the Strategy Pattern allows us to encapsulate these algorithms separately and choose them at runtime.

In this guide, you will learn how to implement the Strategy pattern in a clean and scalable way using C# with Dependency Injection (DI) support.


๐Ÿ“ฆ The Use Case: Payment Processing for Orders

Here is a basic Order class that contains a payment method and a transaction ID that should be updated once the payment is processed:

public class Order
{
    public int Id { get; set; }
    public string? ProductName { get; set; }
    public decimal Amount { get; set; }
    public string? CustomerEmail { get; set; }
    public PaymentMethod PaymentMethod { get; set; }
    public string? Status { get; set; }
    public string? PaymentTransactionId { get; set; }
}

We want to support two payment methods:

public enum PaymentMethod
{
    CreditCard,
    PayPal
}

๐Ÿง  Step 1: Define the Strategy Interface

Start by defining a shared interface for all payment strategies:

public interface IPaymentStrategy
{
    string ProcessPayment(Order order);
}

๐Ÿ’ณ Step 2: Implement Concrete Payment Strategies

Credit Card Payment

public class CreditCardPaymentStrategy : IPaymentStrategy
{
    public string ProcessPayment(Order order)
    {
        string transactionId = Guid.NewGuid().ToString();
        Console.WriteLine($"Processing credit card payment for Order {order.Id}");
        return transactionId;
    }
}

PayPal Payment

public class PayPalPaymentStrategy : IPaymentStrategy
{
    public string ProcessPayment(Order order)
    {
        string transactionId = Guid.NewGuid().ToString();
        Console.WriteLine($"Processing PayPal payment for Order {order.Id}");
        return transactionId;
    }
}

๐Ÿงญ Step 3: Add a Strategy Resolver

Let the DI container manage the strategies. We'll use a dictionary to map them:

public class PaymentStrategyResolver
{
    private readonly Dictionary<PaymentMethod, IPaymentStrategy> _strategies;

    public PaymentStrategyResolver(IEnumerable<IPaymentStrategy> strategies)
    {
        _strategies = strategies.ToDictionary(
            strategy => strategy switch
            {
                CreditCardPaymentStrategy => PaymentMethod.CreditCard,
                PayPalPaymentStrategy => PaymentMethod.PayPal,
                _ => throw new NotSupportedException("Unsupported payment method")
            }
        );
    }

    public IPaymentStrategy GetPaymentStrategy(PaymentMethod method)
    {
        if (_strategies.TryGetValue(method, out var strategy))
        {
            return strategy;
        }

        throw new NotSupportedException($"Payment method {method} is not supported.");
    }
}

๐Ÿ”ง Step 4: Create the Order Processor

This service handles processing the order, updating the transaction ID and status:

public class OrderProcessor
{
    private readonly PaymentStrategyResolver _resolver;

    public OrderProcessor(PaymentStrategyResolver resolver)
    {
        _resolver = resolver;
    }

    public void Process(Order order)
    {
        var paymentStrategy = _resolver.GetPaymentStrategy(order.PaymentMethod);
        order.PaymentTransactionId = paymentStrategy.ProcessPayment(order);
        order.Status = "Paid";

        Console.WriteLine($"Order {order.Id} completed. Status: {order.Status}, Transaction ID: {order.PaymentTransactionId}");
    }
}

๐Ÿงฑ Step 5: Register Services in Dependency Injection

Register everything in Program.cs (for ASP.NET Core or Console apps using generic host):

builder.Services.AddSingleton<IPaymentStrategy, CreditCardPaymentStrategy>();
builder.Services.AddSingleton<IPaymentStrategy, PayPalPaymentStrategy>();
builder.Services.AddSingleton<PaymentStrategyResolver>();
builder.Services.AddTransient<OrderProcessor>();

๐Ÿš€ Example Usage

var order = new Order
{
    Id = 1,
    ProductName = "Laptop",
    Amount = 1200m,
    PaymentMethod = PaymentMethod.CreditCard
};

var processor = serviceProvider.GetRequiredService<OrderProcessor>();
processor.Process(order);

Console.WriteLine($"Final Transaction ID: {order.PaymentTransactionId}");

โœ… Benefits of This Architecture

  • Extensible: Easily add Apple Pay or Crypto payment support by implementing a new IPaymentStrategy.

  • Testable: Swap in mocks for unit testing without touching the core logic.

  • Decoupled: OrderProcessor knows nothing about how payments are processed.


๐Ÿงช Bonus: Persist to Database with EF Core

If you're using Entity Framework:

public class OrderProcessor
{
    private readonly PaymentStrategyResolver _resolver;
    private readonly ApplicationDbContext _dbContext;

    public OrderProcessor(PaymentStrategyResolver resolver, ApplicationDbContext dbContext)
    {
        _resolver = resolver;
        _dbContext = dbContext;
    }

    public void Process(Order order)
    {
        var paymentStrategy = _resolver.GetPaymentStrategy(order.PaymentMethod);
        order.PaymentTransactionId = paymentStrategy.ProcessPayment(order);
        order.Status = "Paid";

        _dbContext.Orders.Update(order);
        _dbContext.SaveChanges();
    }
}

๐Ÿ“Œ Conclusion

The Strategy Pattern combined with Dependency Injection is a powerful pattern for building maintainable and flexible systems. By applying it to payment processing, you create a clean architecture that adheres to SOLID principles and scales as your application grows.

Github Repo: https://github.com/sekmenhuseyin/Payment-Strategy-Project