DEV Community

Cover image for Implementing Payment Gateways with the Liskov Substitution Principle
Md. Asif Rahman
Md. Asif Rahman

Posted on • Edited on

Implementing Payment Gateways with the Liskov Substitution Principle

Introduction

In Laravel, integrating payment gateways while maintaining clean, maintainable, and extensible code can be simplified by applying the Liskov Substitution Principle (LSP), one of the SOLID principles of Object-Oriented Programming (OOP).

LSP suggests that objects of a derived class should be able to replace objects of the base class without affecting the correctness of the program. In the context of Laravel payment gateway integration, you can achieve this by creating a common interface or abstract class for all payment gateways. This ensures that different payment gateways can be easily swapped in and out without disrupting the core functionality of your application.

So, did you get anything?

Image description

Ok, No problem.
Let's do it practically. I am using Laravel here but concept is the same for other languages and frameworks.

The Scenario: Processing Payments

Imagine you are building an e-commerce platform, and you need to support multiple payment gateways, such as credit card payments and PayPal. You want to ensure that you can seamlessly switch between these gateways without making extensive changes to your codebase.

Folder Structure:

Image description
Note: You can follow any folder structure as per your need

Implementation Steps

1. Create a Base PaymentGateway Class

Start by creating a base PaymentGateway class that defines the common payment processing logic:



<?php

namespace App\Components\PaymentGateways;

class PaymentGateway
{
    public function processPayment($amount)
    {
        // Common payment processing logic
        $transactionId = $this->generateTransactionId();
        $this->logPayment($transactionId, $amount);

        return "Processed a payment of $$amount with Transaction ID: $transactionId";
    }

    protected function generateTransactionId()
    {
        // Generate a unique transaction ID
        return uniqid();
    }

    protected function logPayment($transactionId, $amount)
    {
        // Log payment details
        // Example: Log to a database or external service
    }
}




Enter fullscreen mode Exit fullscreen mode

In base PaymentGateway class, we have added common payment processing logic, including generating a unique transaction ID and logging payment details. This common logic will be shared by all payment gateway subclasses.

Step 2: Implement Subclasses for Payment Gateways

The subclasses are CreditCardPaymentGateway and PaypalPaymentGateway.

CreditCardPaymentGateway Class:



<?php

namespace App\Components\PaymentGateways;

use App\Components\PaymentGateways\PaymentGateway;

class CreditCardPaymentGateway extends PaymentGateway
{
    public function processPayment($amount)
    {
        // Credit card payment specific logic
        $creditCardFee = $this->calculateCreditCardFee($amount);

        // Call the parent class's processPayment method to execute common logic
        $result = parent::processPayment($amount + $creditCardFee);

        // Additional credit card specific logic can be added here if necessary

        return $result;
    }

    protected function calculateCreditCardFee($amount)
    {
        // Calculate and return credit card processing fee
        return $amount * 0.03; // 3% fee as an example
    }
}



Enter fullscreen mode Exit fullscreen mode

In this code, we've defined the CreditCardPaymentGateway class, which serves as a specialized payment gateway for handling credit card payments within our Laravel application. It inherits from the PaymentGateway base class, which likely encapsulates shared payment processing functionality.

The primary function within this class is the processPayment($amount) method. Here, we introduce credit card-specific processing logic. Notably, it calculates a credit card processing fee by invoking the calculateCreditCardFee($amount) method. In this example, we've applied a 3% fee rate for illustrative purposes.

Additionally, the CreditCardPaymentGateway class adheres to the Liskov Substitution Principle (LSP) by calling the processPayment method of the parent class (PaymentGateway). This ensures that any common payment processing logic, such as logging or transaction tracking, from the base class is also executed.

Let's create another subclass called PaypalPaymentGateway

PayPalPaymentGateway Class:



<?php
namespace App\Components\PaymentGateways;

use App\Components\PaymentGateways\PaymentGateway;

class PaypalPaymentGateway extends PaymentGateway
{
    public function processPayment($amount)
    {
        // PayPal payment specific logic
        $paypalFee = $this->calculatePayPalFee($amount);

        // Call the parent class's processPayment method to execute common logic
        $result = parent::processPayment($amount + $paypalFee);

        // Additional PayPal specific logic can be added here if necessary

        return $result;
    }

    protected function calculatePayPalFee($amount)
    {
        // Calculate and return PayPal processing fee
        return $amount * 0.02; // 2% fee as an example
    }
}



Enter fullscreen mode Exit fullscreen mode

Similarly, we've implemented the PayPalPaymentGateway class to manage PayPal payments within our Laravel application. This class extends the PaymentGateway base class to leverage shared payment processing capabilities.

The focal point of this class is the processPayment($amount) method, where we've incorporated PayPal-specific processing steps. It calculates the PayPal processing fee by making use of the calculatePayPalFee($amount) method, which assumes a 2% fee rate as an example.

Just like the CreditCardPaymentGateway, the PayPalPaymentGateway class adheres to the Liskov Substitution Principle (LSP) by invoking the processPayment method of the parent class (PaymentGateway). This ensures that common payment processing logic shared across various payment gateways is consistently executed.

These two classes provide a structured and extensible approach for integrating different payment gateways into our Laravel application, promoting code maintainability and ease of use.

Step 3: Configure the payment gateways in Laravel's route service provider as we pass gateway name from url.



<?php

namespace App\Providers;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;

class RouteServiceProvider extends ServiceProvider
{
    /**
     * The path to your application's "home" route.
     *
     * Typically, users are redirected here after authentication.
     *
     * @var string
     */
    public const HOME = '/home';

    /**
     * Define your route model bindings, pattern filters, and other route configuration.
     */
    public function boot(): void
    {

        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });

        $this->routes(function () {
            Route::middleware('api')
                ->prefix('api')
                ->group(base_path('routes/api.php'));

            Route::middleware('web')
                ->group(base_path('routes/web.php'));
        });

        //Here is the logic to bind route params to respective class to intentiate gateway classes

        Route::bind('gateway', function ($gateway) {
            // Define the namespace where your payment gateway classes reside.
            $namespace = 'App\\Components\\PaymentGateways\\';

            // Create the fully qualified class name based on the route parameter.
            $className = $namespace . Str::studly($gateway) . 'PaymentGateway';

            // Check if the class exists.
            if (class_exists($className)) {
                // Instantiate the class dynamically.
                return app()->make($className);
            }
            // Handle the case when the provided $gateway parameter does not correspond to a valid class.
            return abort(404); // Or any other appropriate action.
        });
    }
}




Enter fullscreen mode Exit fullscreen mode

Step 4: Create a route to handle the payment



use Illuminate\Support\Facades\Route;
use App\Http\Controllers\CheckoutController;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/checkout/{gateway}', [CheckoutController::class, 'processPayment']);



Enter fullscreen mode Exit fullscreen mode

Step 5: Create a CheckoutController to process the payment.



// app/Http/Controllers/CheckoutController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\PaymentGateway;

class CheckoutController extends Controller {
    public function processPayment(Request $request, PaymentGateway $gateway) {
        // Common checkout logic

        $amount = 100; // Example amount

        // Process payment using the provided gateway
        $result = $gateway->processPayment($amount);

        return view('checkout', ['result' => $result]);
    }
}



Enter fullscreen mode Exit fullscreen mode

Step 6: Create a view file just to see the result



<!-- resources/views/checkout.blade.php -->
<!DOCTYPE html>
<html>
<head>
    <title>Checkout Result</title>
</head>
<body>
    <h1>Payment Result</h1>
    <p>{{ $result }}</p>
</body>
</html>


Enter fullscreen mode Exit fullscreen mode

With this code, you can now make payments using different gateways by specifying the gateway in the URL. For example, /checkout/credit-card or /checkout/paypal. The controller will use the appropriate payment gateway without needing to change the controller's code, demonstrating the Liskov Substitution Principle.

Conclusion

In this practical guide, we've demonstrated how to integrate payment gateways into a Laravel application while adhering to the principles of Object-Oriented Programming (OOP), specifically the Liskov Substitution Principle (LSP). By following these steps, you can maintain clean, maintainable, and extensible code for handling payments in your e-commerce platform.

Summary of Achievements

1. Created a Base PaymentGateway Class: We established a PaymentGateway base class containing common payment processing logic, such as generating transaction IDs and logging payment details. This base class acts as a blueprint for all payment gateway implementations.

2. Implemented Subclasses for Payment Gateways: We developed two specialized payment gateway subclasses, CreditCardPaymentGateway and PayPalPaymentGateway. These subclasses extend the PaymentGateway base class and provide payment-specific logic while preserving the ability to replace one gateway with another seamlessly.

3. Configured Payment Gateways: Laravel's service provider was utilized to bind instances of the payment gateway classes. This setup allows for easy switching between payment gateways by changing configuration settings.

4. Created a Route and Controller: We defined a route that accepts the selected payment gateway as a parameter and a controller that handles the payment processing. The controller utilizes the selected payment gateway instance to process payments.

5. Developed a Checkout View: A simple view was created to display the payment processing result to the user.

By applying the Liskov Substitution Principle and following these steps, your Laravel application can accommodate various payment gateways without extensive code changes, enhancing code maintainability, extensibility, and flexibility in handling payments for your e-commerce platform. This approach simplifies the integration of new payment gateways in the future, ensuring a smooth payment processing experience for your users.

Now, you have a solid foundation to expand your payment processing capabilities and adapt to changing payment methods or providers as your business needs evolve.

Here is the code https://github.com/asifzcpe/laravel-solid-principles

Top comments (2)

Collapse
 
websilvercraft profile image
websilvercraft

I had a slightly different approach, in order to be able to use different payment processors depending on context: How to Add and Implement Payment Processing Interfaces in Laravel 11: The Part 1 with Hardcoded Binding

Collapse
 
shahadathossen profile image
Shahadat Hossen

Great explanation. So easy to understand.