Skip to content

Custom Payment Gateways Guide

This guide explains how to create custom payment gateways for the store system.

Overview

All payment gateways must extend the BaseGateway class and implement its abstract methods. The gateway system is designed to be flexible and supports both redirect-based and direct payment processing.

Creating a New Gateway

Create a new class in the app/Gateways directory. The filename should end with Gateway.php.

Here's a boilerplate example:

php
<?php

namespace App\Gateways;

use App\Models\Order;
use App\Models\Payment;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class CustomGateway extends BaseGateway
{
	protected string $id = "custom"; // Unique identifier for the gateway
	protected string $name = "Custom Gateway"; // Display name
	protected string $template = "gateways.buttons.default"; // Template for payment button

	public function initialize(): void
	{
		$this->loadConfig();

		// Add any initialization logic here
		// This is called when the gateway is loaded
	}

	public function getConfig(): array
	{
		return [
			"enabled" => [
				"type" => "boolean",
				"label" => "Enable Gateway",
				"required" => true,
			],
			"api_key" => [
				"type" => "text",
				"label" => "API Key",
				"required" => true,
				"secret" => true, // Masks the field in the admin panel
			],
			"test_mode" => [
				"type" => "boolean",
				"label" => "Test Mode",
				"required" => false,
				"help" => "Enable test mode for development",
			],
		];
	}

	public function createPayment(Order $order, int $amount, string $currency): ?Payment
	{
		// Create a payment record and initiate the payment process
		// Amount is in cents (100 = $1.00)

		return Payment::create([
			"order_id" => $order->id,
			"payment_method" => $this->getId(),
			"gateway_id" => "unique_payment_id",
			"approve_url" => "payment_url", // URL where user should be redirected
			"status" => "pending",
		]);
	}

	public function capturePayment(Request $request): RedirectResponse
	{
		// Handle the payment capture/confirmation
		// This is called when user returns from payment page

		// On success:
		$this->completePayment($gatewayId);
		return redirect()->route("payments.success");

		// On failure:
		return redirect()->route("payments.cancel")->with("error", "Payment failed");
	}

	public function handleWebhook(Request $request): JsonResponse
	{
		// Handle webhook notifications from the payment provider

		// Verify webhook authenticity
		// Process the webhook
		// Update payment status if needed

		return response()->json(["status" => "success"]);
	}
}

Key Components

1. Gateway Properties

  • $id: Unique identifier for the gateway (used in database and URLs)
  • $name: Display name shown in admin panel
  • $template: Blade template path for the payment button (defaults to "gateways.buttons.default")

2. Configuration

The getConfig() method defines the gateway's configuration schema. Each field supports these properties:

  • type: Field type ("text", "boolean")
  • label: Display label in admin panel
  • required: Whether the field is required
  • secret: If true, the field will be masked in the UI
  • help: Optional help text shown below the field

Configuration values are automatically saved to the database and loaded when the gateway initializes.

3. Core Methods

initialize()

Called when the gateway is loaded. Use this to:

  • Load configuration
  • Set up API clients
  • Initialize external libraries

createPayment()

Creates a new payment record and initiates the payment process. Parameters:

  • $order: Order model instance
  • $amount: Payment amount in cents
  • $currency: Currency code (e.g., "USD")

Returns a Payment model instance or null if creation fails.

capturePayment()

Handles the payment confirmation after user returns from payment page. Should:

  • Verify the payment status
  • Complete or cancel the payment
  • Redirect user to success/failure page

handleWebhook()

Processes webhook notifications from the payment provider. Should:

  • Verify webhook authenticity
  • Update payment status
  • Return appropriate response

4. Helper Methods

The base gateway provides several helper methods:

  • loadConfig(): Loads gateway configuration from database
  • saveConfig(array $config): Saves gateway configuration
  • isEnabled(): Checks if gateway is enabled
  • completePayment(string $gatewayId): Marks a payment as complete

Payment Confirmation Approaches

Overview

Gateways can implement payment confirmation through two methods:

  1. Return URL Capture
  2. Webhook Notifications

You can implement either one or both methods. Implementing both provides the most reliable payment processing:

  • Return URL capture enables instant order activation when the user returns
  • Webhook handling ensures payments are processed even if the user closes their browser during payment

Return URL Capture

The capturePayment() method handles payment confirmation when users return from the payment page:

php
public function capturePayment(Request $request): RedirectResponse
{
    // Get payment ID from return URL parameters
    $gatewayId = $request->get("payment_id");

    // Verify payment status with provider's API
    $response = $this->api->getPayment($gatewayId);

    if ($response->isPaid()) {
        $this->completePayment($gatewayId);
        return redirect()->route("payments.success");
    }

    return redirect()
        ->route("payments.cancel")
        ->with("error", "Payment failed");
}

Webhook Handling

The handleWebhook() method processes asynchronous payment notifications:

php
public function handleWebhook(Request $request): JsonResponse
{
    // Verify webhook authenticity
    if (!$this->verifyWebhookSignature($request)) {
        return response()->json(["error" => "Invalid signature"], 400);
    }

    $event = $request->json()->all();
    $gatewayId = $event["payment_id"];

    // Check if payment is already processed
    $payment = Payment::where("gateway_id", $gatewayId)->first();
    if ($payment->status === "completed") {
        return response()->json(["status" => "success"]);
    }

    // Process the payment
    if ($event["status"] === "completed") {
        $this->completePayment($gatewayId);
    }

    return response()->json(["status" => "success"]);
}

See the PayPalGateway class for a real implementation using both approaches.

Technical API Reference

BaseGateway Class

Abstract class that all payment gateways must extend.

php
abstract class BaseGateway
{
    public array $config;
    protected string $id;
    protected string $name;
    protected string $template = "gateways.buttons.default";

    public function __construct(array $config = [])
    public function getId(): string
    public function getName(): string
    public function getTemplate(): string
    public function isEnabled(): bool
    public function loadConfig(): void
    public function saveConfig(array $config): void
    public function completePayment(string $gatewayId)

    // Abstract methods that must be implemented
    abstract public function initialize(): void;
    abstract public function getConfig(): array;
    abstract public function createPayment(Order $order, int $amount, string $currency): ?Payment;
    abstract public function capturePayment(Request $request): RedirectResponse;
    abstract public function handleWebhook(Request $request): JsonResponse;
}

Required Method Implementations

initialize(): void

Called when the gateway is loaded. Must call loadConfig() and set up any necessary API clients or services.

php
public function initialize(): void {
    $this->loadConfig();
    // Initialize API clients, set base URLs, etc.
}

getConfig(): array

Defines the gateway's configuration schema. Must return an array of field definitions.

php
public function getConfig(): array {
    return [
        "field_name" => [
            "type" => "text|boolean",      // Field type
            "label" => "Display Label",     // Admin panel label
            "required" => true|false,       // Is field required?
            "secret" => true|false,         // Should field be masked?
            "help" => "Help text"          // Optional help text
        ],
        // ... more fields
    ];
}

createPayment(Order $order, int $amount, string $currency): ?Payment

Creates a new payment record and initiates the payment process.

Parameters:

  • $order: The Order model instance being paid for
  • $amount: Payment amount in cents (e.g., 1000 for $10.00)
  • $currency: Three-letter currency code (e.g., "USD")

Returns:

  • Payment|null: The created Payment model or null if creation failed

Required Payment model fields:

php
[
	"order_id" => $order->id,
	"payment_method" => $this->getId(),
	"gateway_id" => "unique_payment_id", // Provider's transaction ID
	"approve_url" => "payment_url", // URL for redirect
	"status" => "pending", // Initial status
];

capturePayment(Request $request): RedirectResponse

Handles payment confirmation after user returns from payment page.

Parameters:

  • $request: The HTTP request containing payment provider's response

Returns:

  • RedirectResponse: Redirect to success/cancel route

Example success flow:

php
$this->completePayment($gatewayId);
return redirect()->route("payments.success");

Example failure flow:

php
return redirect()
	->route("payments.cancel")
	->with("error", "Payment failed: " . $error);

handleWebhook(Request $request): JsonResponse

Processes webhook notifications from the payment provider.

Parameters:

  • $request: The HTTP request containing webhook data

Returns:

  • JsonResponse: Response to send back to payment provider

Security requirements:

  • Must verify webhook authenticity using signatures/tokens
  • Must handle idempotency (avoid duplicate processing)
  • Must return appropriate HTTP status codes

Example success response:

php
return response()->json(["status" => "success"]);

Example error response:

php
return response()->json(["status" => "error", "message" => "Invalid signature"], 400);

Helper Methods Available

loadConfig(): void

Loads gateway configuration from database into $this->config.

saveConfig(array $config): void

Saves gateway configuration to database. Preserves existing values when gateway is disabled.

completePayment(string $gatewayId)

Marks a payment as complete. Call this when payment is confirmed.

Parameters:

  • $gatewayId: The payment provider's transaction ID

getId(): string

Returns gateway's unique identifier.

getName(): string

Returns gateway's display name.

getTemplate(): string

Returns path to gateway's button template.

isEnabled(): bool

Returns whether the gateway is enabled in admin panel.