Skip to content

Instantly share code, notes, and snippets.

@atomjoy
Last active August 28, 2025 19:01
Show Gist options
  • Save atomjoy/132e6d21c1a6a2942223df2d597c083d to your computer and use it in GitHub Desktop.
Save atomjoy/132e6d21c1a6a2942223df2d597c083d to your computer and use it in GitHub Desktop.
How to set up a payment system in Laravel that supports multiple payment gateways with interfaces and collection.

Laravel Payment Gateways

How to set up a payment system in Laravel that supports multiple payment gateways with interfaces and collection.

Payment Gateway Interface

<?php

namespace App\Payments\Contracts;

interface PaymentGatewayInterface
{
    /**
     * Gateway name
     *
     * @return string
     */
    public function name(): string;

    /**
     * Proccess payment here with order amount.
     *
     * @param PaymentOrderInterface $order Order object
     * @return string $id Transaction id
     */
    public function process(PaymentOrderInterface $order): string;

    /**
     * Proccess refund here with transaction id.
     *
     * @param string $id Transaction id
     * @return string $id Refund id
     */
    public function refund(string $id): string;
}

PaymentOrderInterface

<?php

namespace App\Payments\Contracts;

interface PaymentOrderInterface
{
    function paymentOrderId(): string;
    function paymentOrderCost(): int;
    function paymentOrderFirstname(): string;
    function paymentOrderLastname(): string;
    function paymentOrderPhone(): string;
    function paymentOrderEmail(): string;
    function paymentOrderCurrency(): string;
}

Service Provider

<?php

namespace App\Providers;

use App\Payments\Contracts\PaymentGatewayInterface;
use App\Payments\Gateways\PayuGateway;
use App\Payments\Gateways\PayPalGateway;
use App\Payments\Gateways\StripeGateway;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // Bind payment gateways collection to interface
        $this->app->bind(
            PaymentGatewayInterface::class,
            function ($app) {
                return collect([
                    'payu' => app(PayuGateway::class),
                    'paypal' => app(PayPalGateway::class),
                    'stripe' => app(StripeGateway::class),
                ]);
            }
        );
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        //
    }
}

Payu Payment Gateway

<?php

namespace App\Payments\Gateways;

use App\Payments\Contracts\PaymentGatewayInterface;
use App\Payments\Contracts\PaymentOrderInterface;

class PayuGateway implements PaymentGatewayInterface
{
    public function name(): string
    {
        return 'Payu';
    }

    public function process(PaymentOrderInterface $order): string
    {
        // Proccess payment here

        return $order->paymentOrderId();
    }

    public function refund(string $id): string
    {
        // Proccess refund here

        return $id;
    }
}

PayPal Payment Gateway

<?php

namespace App\Payments\Gateways;

use App\Payments\Contracts\PaymentGatewayInterface;
use App\Payments\Contracts\PaymentOrderInterface;

class PayPalGateway implements PaymentGatewayInterface
{
    public function name(): string
    {
        return 'PayPal';
    }

    public function process(PaymentOrderInterface $order): string
    {
        // Proccess payment here

        return $order->paymentOrderId();
    }

    public function refund(string $id): string
    {
        // Proccess refund here

        return $id;
    }
}

Stripe Payment Gateway

<?php

namespace App\Payments\Gateways;

use App\Payments\Contracts\PaymentGatewayInterface;
use App\Payments\Contracts\PaymentOrderInterface;

class StripeGateway implements PaymentGatewayInterface
{
    public function name(): string
    {
        return 'Stripe';
    }

    public function process(PaymentOrderInterface $order): string
    {
        // Proccess payment here

        return $order->paymentOrderId();
    }

    public function refund(string $id): string
    {
        // Proccess refund here

        return $id;
    }
}

PaymentOrder

<?php

namespace App\Payments;

use App\Payments\Contracts\PaymentOrderInterface;

class PaymentOrder implements PaymentOrderInterface
{
    // public function __construct(protected $order = null) {}

    function paymentOrderId(): string
    {
        return (string) 12321;
    }

    function paymentOrderCost(): int
    {
        return 2499;
    }

    function paymentOrderFirstname(): string
    {
        return 'Alex';
    }

    function paymentOrderLastname(): string
    {
        return 'Moore';
    }

    function paymentOrderPhone(): string
    {
        return '+48100200300';
    }

    function paymentOrderEmail(): string
    {
        return '[email protected]';
    }

    function paymentOrderCurrency(): string
    {
        return 'PLN';
    }
}

Payment Controller

<?php

namespace App\Http\Controllers\Payments;

use App\Http\Controllers\Controller;
use App\Payments\Contracts\PaymentGatewayInterface;
use App\Payments\PaymentOrder;

class PaymentController extends Controller
{
    protected $defaultGateway = 'payu';

    protected $allowedGateways = ['payu', 'paypal', 'stripe'];

    public function process($gateway)
    {
        $payment = app(PaymentGatewayInterface::class)->get($this->getGateway($gateway));

        if ($payment == null) {
            throw new Exception("Invalid gateway name.", 422);
        }

        $paymentOrder = new PaymentOrder(); // Order example here

        $transactionId = $payment->process($paymentOrder);

        return response()->json([
            'message' => 'Payment success.',
            'gateway' => $payment->name(),
            'transaction_id' => $transactionId
        ]);
    }

    public function refund($gateway)
    {
        $payment = app(PaymentGatewayInterface::class)->get($this->getGateway($gateway));

        if ($payment == null) {
            throw new Exception("Invalid gateway name.", 422);
        }

        $id = $payment->refund(123456);

        return response()->json([
            'message' => 'Refund success.',
            'gateway' => $payment->name(),
            'transaction_id' => $id
        ]);
    }

    public function getGateway($gateway)
    {
        if (in_array($gateway, $this->allowedGateways)) {
            return $gateway;
        }

        return $this->defaultGateway;
    }
}

Payment routes

<?php

use App\Http\Controllers\Payments\PaymentController;
use Illuminate\Support\Facades\Route;

Route::get('/payment/{gateway}', [PaymentController::class, 'process']);
Route::get('/refund/{gateway}', [PaymentController::class, 'refund']);

PaymentGatewaysEnum

<?php

namespace App\Payments\Enums;

enum PaymentGatewaysEnum: string
{
    case PAYU = 'payu';
    case PAYPAL = 'paypal';
    case STRIPE = 'stripe';

    public function label(): string
    {
        return match ($this) {
            static::PAYU => 'Payu',
            static::PAYPAL => 'PayPal',
            static::STRIPE => 'Stripe',
            default => throw new \Exception('Unknown enum value requested for the label.'),
        };
    }
}

PaymentStatusEnum

<?php

namespace App\Payments\Enums;

enum PaymentStatusEnum: string
{
    case NEW = 'new';
    case PENDING = 'pending';
    case WAITING = 'waiting';
    case COMPLETED = 'completed';
    case CANCELLED = 'cancelled';
    case REFUNDED = 'refunded';
    case REJECTED = 'rejected';
    case FAILED = 'failed';
}

RefundStatusEnum

<?php

namespace App\Payments\Enums;

enum RefundStatusEnum: string
{
    case PENDING = 'pending';
    case CANCELED = 'cancelled';
    case FINALIZED = 'finalized';
}

OrderStatusEnum

https://woocommerce.com/document/managing-orders/order-statuses

<?php

namespace App\Enums;

enum OrderStatusEnum: string
{
    case PENDING = 'pending';
    case WAITING = 'waiting';
    case PROCESSING = 'processing';
    case COMPLETED = 'completed';
    case CANCELLED = 'cancelled';
    case REFUNDED = 'refunded';
    case FAILED = 'failed';
    case DRAFT = 'draft';

    public function label(): string
    {
        return match ($this) {
            self::PENDING => 'Pending for payment',
            self::WAITING => 'Waiting for confirmation',
            self::PROCESSING => 'In progress',
            self::COMPLETED => 'Fulfilled',
            self::CANCELLED => 'Cancelled',
            self::REFUNDED => 'Refunded',
            self::FAILED => 'Failed',
            self::DRAFT => 'Draft',
        };
    }

    public function description(): string
    {
        return match ($this) {
            self::PENDING => 'The order has been placed, but the payment is still pending or hasn not been initiated. This is common for methods like bank transfers.',
            self::WAITING => 'The payment has been made but requires manual confirmation by the store owner or payment provider.',
            self::PROCESSING => 'The payment has been successfully validated, and the order is now in the fulfillment stage, like shipping. They will remain in this state until you manually change their state to another one. All orders require processing except those in which all products are both Virtual and Downloadable.',
            self::COMPLETED => 'The payment was successful, and the order has been fulfilled and delivered.',
            self::CANCELLED => 'The order has been canceled by the admin or the customer.',
            self::REFUNDED => 'The order has been canceled, and a refund has been issued to the customer.',
            self::FAILED => 'The payment was declined by the payment gateway or the customers bank.',
            self::DRAFT => 'For instance, if a customer places a custom order for a product that requires confirmation of size or color, you can save the order as a draft while waiting for their response.',
        };
    }

    public function color(): string
    {
        return match ($this) {
            self::PENDING => 'gray',
            self::WAITING => 'yellow',
            self::PROCESSING => 'blue',
            self::COMPLETED => 'green',
            self::CANCELLED => 'red',
            self::REFUNDED => 'purple',
            self::FAILED => 'red',
            self::DRAFT => 'orange',
        };
    }

    public function finalized(): bool
    {
        return in_array($this, [self::COMPLETED, self::CANCELLED, self::REFUNDED, self::FAILED]);
    }

    public static function options(): array
    {
        static $options = null;

        if ($options === null) {
            $options = collect(self::cases())->map(fn($case) => [
                'value' => $case->value,
                'label' => $case->label(),
                'color' => $case->color(),
                'description' => $case->description(),
            ])->toArray();
        }

        return $options;
    }
}

CurrencyEnum

<?php

namespace App\Payments\Enums;

enum CurrencyEnum: string
{
    case PLN = 'PLN';    
    case EUR = 'EUR';
    case USD = 'USD';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment