π§ Mastering The Singleton Pattern In Laravel For Payment Gateways (Stripe, SSLCommerz, Bkash)


Integrating payment gateways like Stripe, SSLCommerz, or Bkash is a common need in web development. But if not done right, it can lead to messy, repeated code and poor resource management.
In this blog, we’ll explore how to implement a clean, scalable, and reusable payment system using the Singleton Pattern, Strategy Pattern, and Dependency Injection in Laravel.
π What You’ll Learn
β
What is the Singleton Pattern
β
Why and when to use it in Laravel
β
How to integrate multiple payment gateways (Stripe, SSLCommerz, Bkash)
β
How to make your architecture SOLID, testable, and extensible
β
Full code example with folder structure
π What is the Singleton Pattern?
The Singleton Pattern ensures a class has only one instance throughout the application and provides a global access point to it.
Instead of creating a new object every time, we use one shared instance.
β Why Use Singleton for Payment Gateways?
-
Avoids repeated SDK initialization
-
Saves memory and ensures consistent behavior
-
Centralizes and simplifies gateway logic
-
Works perfectly with Laravel’s Service Container
π§± Recommended Folder Structure
To keep things modular and clean:
app/
βββ Gateways/
β βββ Contracts/
β β βββ PaymentGatewayInterface.php
β βββ StripeGateway.php
β βββ SSLCommerzGateway.php
β βββ BkashGateway.php
βββ Services/
β βββ PaymentGatewayService.php
π§© Step 1: Define the Payment Gateway Interface
app/Gateways/Contracts/PaymentGatewayInterface.php
namespace App\Gateways\Contracts;
interface PaymentGatewayInterface
{
public function charge(float $amount, string $currency): array;
public function refund(string $transactionId): array;
}
This defines a contract that all gateways (Stripe, SSLCommerz, Bkash) must follow.
π³ Step 2: Create Gateway Implementations
Each gateway implements the PaymentGatewayInterface
.
πΉ Stripe Gateway
app/Gateways/StripeGateway.php
namespace App\Gateways;
use App\Gateways\Contracts\PaymentGatewayInterface;
class StripeGateway implements PaymentGatewayInterface
{
protected $config;
public function __construct()
{
$this->config = config('services.stripe');
}
public function charge(float $amount, string $currency): array
{
return [
'gateway' => 'stripe',
'status' => 'success',
'transaction_id' => uniqid('stripe_txn_'),
'amount' => $amount,
'currency' => $currency,
];
}
public function refund(string $transactionId): array
{
return [
'gateway' => 'stripe',
'status' => 'refunded',
'transaction_id' => $transactionId,
];
}
}
πΉ SSLCommerz Gateway
app/Gateways/SSLCommerzGateway.php
namespace App\Gateways;
use App\Gateways\Contracts\PaymentGatewayInterface;
class SSLCommerzGateway implements PaymentGatewayInterface
{
protected $config;
public function __construct()
{
$this->config = config('services.sslcommerz');
}
public function charge(float $amount, string $currency): array
{
return [
'gateway' => 'sslcommerz',
'status' => 'success',
'transaction_id' => uniqid('ssl_txn_'),
'amount' => $amount,
'currency' => $currency,
];
}
public function refund(string $transactionId): array
{
return [
'gateway' => 'sslcommerz',
'status' => 'refunded',
'transaction_id' => $transactionId,
];
}
}
πΉ Bkash Gateway
app/Gateways/BkashGateway.php
namespace App\Gateways;
use App\Gateways\Contracts\PaymentGatewayInterface;
class BkashGateway implements PaymentGatewayInterface
{
protected $config;
public function __construct()
{
$this->config = config('services.bkash');
}
public function charge(float $amount, string $currency): array
{
return [
'gateway' => 'bkash',
'status' => 'success',
'transaction_id' => uniqid('bkash_txn_'),
'amount' => $amount,
'currency' => $currency,
];
}
public function refund(string $transactionId): array
{
return [
'gateway' => 'bkash',
'status' => 'refunded',
'transaction_id' => $transactionId,
];
}
}
π οΈ Step 3: Create the PaymentGatewayService (Singleton-Friendly)
app/Services/PaymentGatewayService.php
namespace App\Services;
use App\Gateways\Contracts\PaymentGatewayInterface;
class PaymentGatewayService
{
protected PaymentGatewayInterface $gateway;
public function __construct(PaymentGatewayInterface $gateway)
{
$this->gateway = $gateway;
}
public function charge(float $amount, string $currency = 'BDT'): array
{
return $this->gateway->charge($amount, $currency);
}
public function refund(string $transactionId): array
{
return $this->gateway->refund($transactionId);
}
}
Now, Laravel will inject the gateway using Dependency Injection, making it testable and extensible.
π§© Step 4: Register Bindings in Service Container
app/Providers/AppServiceProvider.php
use App\Gateways\Contracts\PaymentGatewayInterface;
use App\Gateways\StripeGateway;
public function register()
{
// Bind the default gateway
$this->app->bind(PaymentGatewayInterface::class, function () {
return new StripeGateway(); // You can switch to SSLCommerzGateway or BkashGateway
});
$this->app->singleton(\App\Services\PaymentGatewayService::class);
}
You can make this dynamic using a config or based on request data if needed.
π Step 5: Use in Controller (With DI)
use App\Services\PaymentGatewayService;
class PaymentController extends Controller
{
public function pay(PaymentGatewayService $paymentService)
{
$response = $paymentService->charge(1500, 'BDT');
return response()->json($response);
}
public function refund(PaymentGatewayService $paymentService, string $transactionId)
{
$response = $paymentService->refund($transactionId);
return response()->json($response);
}
// Optional: Dynamically switch gateway
public function payWithBkash()
{
$bkash = new \App\Gateways\BkashGateway();
$payment = new PaymentGatewayService($bkash);
return response()->json($payment->charge(2000));
}
}
π§ Benefits of This Architecture
Benefit | Description |
---|---|
β Singleton-Friendly | Only one instance of each gateway client is created |
β Strategy Pattern | Easily switch between Stripe, SSLCommerz, and Bkash |
β Laravel-native | Uses DI and Service Container |
β Extensible | Add more gateways without changing core logic |
β Testable | Easily mock PaymentGatewayInterface in tests |
π§Ύ Summary
Concept | Description |
---|---|
Singleton | Reuses a single instance of the gateway service |
Interface | All gateways follow a shared contract |
Strategy | Gateways are interchangeable at runtime |
DI | Laravel resolves dependencies automatically |
Gateways | Stripe, SSLCommerz, Bkash supported out of the box |
π What’s Next?
You can enhance this setup by:
-
Logging all transactions
-
Queuing large payment operations
-
Adding retry logic
-
Generating digital invoices
Related Blogs
Alright, Laravel developers, let's talk shop. We love Laravel for its elegance, convention over configuration, and how quickly it lets us build. But as our applications grow, even in a framework as opinionated as Laravel, we can fall into traps: repeating the same logic across different "service" classes, inconsistent data handling, or struggling to manage complex workflows.

Queues are essential for building scalable and performant Laravel applications. Whether you're sending emails, processing files, or executing time-consuming tasks, queues offload work and keep your app fast. But how do you monitor and manage them effectively?

As Laravel developers, one of the critical lessons we eventually learn is: not everything should happen in real-time. Whether it's sending emails, processing images, syncing third-party data, or running analytics β pushing these resource-heavy or time-consuming tasks to the background is essential for a performant and responsive application.
