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
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 panelrequired
: Whether the field is requiredsecret
: If true, the field will be masked in the UIhelp
: 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 databasesaveConfig(array $config)
: Saves gateway configurationisEnabled()
: Checks if gateway is enabledcompletePayment(string $gatewayId)
: Marks a payment as complete
Payment Confirmation Approaches
Overview
Gateways can implement payment confirmation through two methods:
- Return URL Capture
- 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:
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:
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.
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.
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.
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:
[
"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:
$this->completePayment($gatewayId);
return redirect()->route("payments.success");
Example failure flow:
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:
return response()->json(["status" => "success"]);
Example error response:
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.