DEV Community

Cover image for Strategy Pattern in Laravel: Clean Payment Processing
Laravel Mastery
Laravel Mastery

Posted on

Strategy Pattern in Laravel: Clean Payment Processing

The Problem 😫
Ever seen code like this?

public function processPayment(Request $request)
{
    $method = $request->payment_method;

    if ($method === 'credit_card') {
        // 50 lines of credit card logic...
    } elseif ($method === 'paypal') {
        // 40 lines of PayPal logic...
    } elseif ($method === 'crypto') {
        // 60 lines of crypto logic...
    } // ... more conditions
}
Enter fullscreen mode Exit fullscreen mode

This creates:

  • 200+ line methods
  • Untestable code
  • Maintenance nightmares
  • SOLID violations

The Solution: Strategy Pattern ✨

The Strategy Pattern encapsulates algorithms into separate classes, making them interchangeable.

Step 1: Define the Interface

interface PaymentStrategyInterface
{
    public function pay(float $amount, array $details): array;
    public function validate(array $details): bool;
    public function refund(string $transactionId, float $amount): array;
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create Concrete Strategies

class CreditCardStrategy implements PaymentStrategyInterface
{
    public function pay(float $amount, array $details): array
    {
        // Credit card processing logic
        return [
            'success' => true,
            'transaction_id' => 'txn_' . uniqid(),
        ];
    }

    public function validate(array $details): bool
    {
        return !empty($details['card_number']) && 
               !empty($details['cvv']);
    }

    public function refund(string $transactionId, float $amount): array
    {
        // Refund logic
        return ['success' => true];
    }
}

class PayPalStrategy implements PaymentStrategyInterface
{
    public function pay(float $amount, array $details): array
    {
        // PayPal processing logic
        return [
            'success' => true,
            'transaction_id' => 'pp_' . uniqid(),
        ];
    }

    public function validate(array $details): bool
    {
        return !empty($details['email']) && 
               filter_var($details['email'], FILTER_VALIDATE_EMAIL);
    }

    public function refund(string $transactionId, float $amount): array
    {
        return ['success' => true];
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create Strategy Factory

class PaymentStrategyFactory
{
    public function create(string $method): PaymentStrategyInterface
    {
        return match($method) {
            'credit_card' => new CreditCardStrategy(),
            'paypal' => new PayPalStrategy(),
            'crypto' => new CryptoStrategy(),
            default => throw new \InvalidArgumentException("Unknown method: {$method}"),
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Use in Controller

class OrderController extends Controller
{
    public function __construct(
        private PaymentStrategyFactory $factory
    ) {}

    public function processPayment(Request $request)
    {
        $strategy = $this->factory->create($request->payment_method);

        if (!$strategy->validate($request->payment_details)) {
            return response()->json(['error' => 'Invalid details'], 400);
        }

        $result = $strategy->pay(
            $request->amount,
            $request->payment_details
        );

        return response()->json($result);
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits 🎯
βœ… Single Responsibility - Each strategy handles one method
βœ… Open/Closed - Add new methods without modifying existing code
βœ… Easy Testing - Test each strategy independently
βœ… Maintainable - Clear separation of concerns
βœ… Flexible - Swap strategies at runtime

Testing Example

class CreditCardStrategyTest extends TestCase
{
    public function test_validates_card_details(): void
    {
        $strategy = new CreditCardStrategy();

        $valid = $strategy->validate([
            'card_number' => '4532015112830366',
            'cvv' => '123',
        ]);

        $this->assertTrue($valid);
    }
}
Enter fullscreen mode Exit fullscreen mode

Real-World Use Cases πŸ’‘

Use Strategy Pattern for:

  • Payments - Credit cards, PayPal, Crypto
  • Storage - S3, Local, DigitalOcean
  • Notifications - Email, SMS, Slack
  • Authentication - OAuth, LDAP, JWT
  • Export - PDF, Excel, CSV

Read the Full Article πŸ“–
Want the complete implementation with advanced techniques, more examples, and best practices?
Read the full article on Medium
The full version includes:

  • Complete payment gateway integrations
  • Advanced dynamic strategy selection
  • Comprehensive unit tests
  • Service provider configuration
  • Error handling patterns
  • Real production examples

Quick Takeaway
Transform this mess:

if ($method === 'x') { /* 50 lines */ }
elseif ($method === 'y') { /* 40 lines */ }
Enter fullscreen mode Exit fullscreen mode

Into this elegance:

$strategy = $factory->create($method);
$result = $strategy->pay($amount, $details);
Enter fullscreen mode Exit fullscreen mode

Your code will thank you! πŸš€

What design patterns do you use in Laravel? Share in the comments!

Top comments (0)