Multi-Channel Payments with Strategy Pattern
Handling multiple payment gateways (Stripe, PayPal, Momo) often leads to messy conditional code:
if ($type === 'stripe') {
// stripe logic
} elseif ($type === 'paypal') {
// paypal logic
}
The Strategy Pattern solves this by defining a family of algorithms (payment methods), encapsulating each one, and making them interchangeable.
1. The Interface
Define a contract that all payment gateways must follow.
namespace App\Contracts;
interface PaymentGateway
{
public function charge(float $amount, array $details): string; // Returns Transaction ID
public function refund(string $transactionId): bool;
}
2. Concrete Strategies
Implement the interface for each provider.
Stripe Strategy
namespace App\Services\Payments;
use App\Contracts\PaymentGateway;
use Stripe\StripeClient;
class StripePayment implements PaymentGateway
{
protected $stripe;
public function __construct() {
$this->stripe = new StripeClient(config('services.stripe.secret'));
}
public function charge(float $amount, array $details): string
{
$charge = $this->stripe->charges->create([
'amount' => $amount * 100, // Cents
'currency' => 'usd',
'source' => $details['token'],
]);
return $charge->id;
}
public function refund(string $transactionId): bool
{
// Implementation
return true;
}
}
PayPal Strategy
namespace App\Services\Payments;
use App\Contracts\PaymentGateway;
class PaypalPayment implements PaymentGateway
{
public function charge(float $amount, array $details): string
{
// PayPal specific logic
return 'paypal_txn_id';
}
public function refund(string $transactionId): bool
{
return true;
}
}
3. The Context (PaymentManager)
We need a way to choose the correct strategy at runtime. Laravel's Manager pattern (like Cache::driver()) is perfect for this.
namespace App\Services\Payments;
use Illuminate\Support\Manager;
class PaymentManager extends Manager
{
public function getDefaultDriver()
{
return config('payment.default');
}
protected function createStripeDriver()
{
return new StripePayment();
}
protected function createPaypalDriver()
{
return new PaypalPayment();
}
}
Register this in a Service Provider:
// AppServiceProvider
$this->app->singleton(PaymentManager::class, function ($app) {
return new PaymentManager($app);
});
4. Usage
Now, switching gateways is elegant.
use App\Services\Payments\PaymentManager;
class CheckoutController extends Controller
{
public function store(Request $request, PaymentManager $payment)
{
// Get driver based on user selection, e.g., 'stripe' or 'paypal'
$gateway = $payment->driver($request->payment_method);
try {
$transactionId = $gateway->charge(100.00, $request->all());
Order::create(['transaction_id' => $transactionId]);
return response()->json(['status' => 'success']);
} catch (\Exception $e) {
return response()->json(['error' => 'Payment failed'], 422);
}
}
}
Factory Alternative
If you don't want to use the full Manager class, a simple Factory works too:
class PaymentFactory
{
public static function make(string $type): PaymentGateway
{
return match ($type) {
'stripe' => app(StripePayment::class),
'paypal' => app(PaypalPayment::class),
default => throw new \Exception("Unknown payment method"),
};
}
}
Handling Different Input Requirements
Stripe needs a token, PayPal might need a return URL. How do we standardize charge($amount, $details)?
Use a Data Transfer Object (DTO) instead of an array.
class PaymentData
{
public function __construct(
public float $amount,
public ?string $token = null,
public ?string $payerId = null,
) {}
}
Update interface to strict type: charge(PaymentData $data).
Benefits
- Open/Closed Principle: Add new gateways (Momo, ZaloPay) by adding a new class, not changing existing code.
- Testability: You can easily mock
PaymentGatewayinterface in tests. - Clarity: Logic for each provider is isolated in its own file.
Summary
The Strategy Pattern is essentially "Polymorphism" applied to algorithms. In Laravel, combined with the Service Container, it provides a clean, extensive way to manage multiple implementations of the same business capability—like payments, shipping calculations, or notification channels.