DEV3 Technologies home

One PaymentGateway interface for Stripe, PayPal and Razorpay

A small contract plus a Laravel Manager that resolves the active payment gateway from config, so controllers never know which provider runs.

LaravelPaymentspaymentsmanagerinterfacestripe

Code

<?php

namespace App\Billing\Contracts;

use App\Billing\Data\WebhookEvent;
use Illuminate\Http\Request;

/** One payment provider. Amounts are integer minor units (cents). */
interface PaymentGateway
{
    public function name(): string;

    /** Create a hosted payment page and return the URL to send the customer to. */
    public function checkout(string $reference, int $amount, string $currency, string $returnUrl): string;

    /** Verify the signature and normalise the provider payload. Must throw on a bad signature. */
    public function parseWebhook(Request $request): WebhookEvent;

    /** Refund everything when $amount is null. */
    public function refund(string $providerRef, ?int $amount = null): void;
}

How it works

What it solves

Supporting more than one payment provider usually leaks if ($gateway === 'stripe') branches through controllers and jobs. A single interface lets the rest of the app speak one vocabulary: start a checkout, read a webhook, refund. Adding a provider later means one new class and one new config line.

How it works

PaymentGateway has four methods, all in integer minor units. checkout returns a redirect URL, parseWebhook verifies the provider signature and returns a neutral WebhookEvent, and refund takes an optional partial amount. Drivers throw on bad signatures so callers can answer 400.

PaymentManager extends Laravel's Manager, which gives you driver() caching and extend() for free. Each createXDriver method builds the class from its slice of config('billing.gateways'). The default driver name comes from config, so you can flip providers per environment, or per tenant if you read it from a settings table.

Adapting it

Create StripeGateway, PaypalGateway and RazorpayGateway classes implementing the contract and register the manager as a singleton. Inject PaymentManager and call $payments->driver($order->gateway) so a stored order keeps using the provider that created it.

Pitfalls

  • Always resolve by the gateway saved on the order, not the current default, or refunds hit the wrong provider.
  • Keep provider-specific fields out of the interface; put them in the driver config.
  • A test double driver (fake) keeps feature tests off the network.

How to use it

  1. Create app/Billing/Contracts/PaymentGateway.php and app/Billing/PaymentManager.php.
  2. Add a config/billing.php with default and a gateways array keyed stripe, paypal, razorpay.
  3. Bind it: $this->app->singleton(PaymentManager::class) in a service provider.
  4. Implement one driver class per provider (each one implements the four contract methods).
  5. Use it: app(PaymentManager::class)->driver($order->gateway)->checkout($order).

Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.

Related snippets