Xgenious/ docs

IPN, Routes & Settings

Step 4: IPN/Callback Controller

<?php
declare(strict_types=1);

namespace MyPaymentGateway\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class MyIpnController extends Controller
{
    public function handle(Request $request)
    {
        // 1. Verify authenticity
        if (!MyGateway::verifyWebhook($request)) {
            report('MyPaymentGateway: Invalid webhook signature');
            return response()->json(['success' => false], 401);
        }

        // 2. Parse provider data
        $parsed = MyGateway::parseWebhook($request);

        if ($parsed['record_id'] <= 0 || $parsed['payment_type'] === '') {
            return response()->json(['success' => false], 400);
        }

        // 3. Delegate to core completion service
        $result = function_exists('plugin_apply_filters')
            ? plugin_apply_filters('payment.complete', ['ok' => false], [
                'type'           => $parsed['payment_type'],
                'id'             => $parsed['record_id'],
                'transaction_id' => $parsed['transaction_id'],
            ])
            : ['ok' => false];

        if (is_array($result) && !empty($result['ok'])) {
            return response()->json(['success' => true], 200);
        }

        return response()->json(['success' => false], 422);
    }
}

Step 5: Routes

routes/api.php — Public IPN endpoint (no CSRF, no session):

use MyPaymentGateway\Http\Controllers\MyIpnController;

Route::post('mygateway/ipn', [MyIpnController::class, 'handle'])
    ->name('mygateway.ipn');

routes/web.php — Protected routes:

use MyPaymentGateway\Http\Controllers\MyCheckoutController;
use MyPaymentGateway\Http\Controllers\MyAdminController;

Route::group(['middleware' => ['auth', 'setlang']], function () {
    Route::get('mygateway/pay', [MyCheckoutController::class, 'pay'])
        ->name('mygateway.pay')->middleware('signed');
});

Route::group([
    'prefix'     => 'admin/mygateway',
    'as'         => 'mygateway.admin.',
    'middleware'  => ['auth:admin', 'setlang'],
], function () {
    Route::match(['get', 'post'], '/', [MyAdminController::class, 'settings'])
        ->name('settings')->permission('mygateway-settings');
});

Step 6: Admin Settings Controller

<?php
declare(strict_types=1);

namespace MyPaymentGateway\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class MyAdminController extends Controller
{
    private const FIELDS = [
        'mygateway_gateway'       => 'nullable|string|max:10',
        'mygateway_test_mode'     => 'nullable|string|max:10',
        'mygateway_api_key'       => 'nullable|string|max:1000',
        'mygateway_secret_key'    => 'nullable|string|max:1000',
        'mygateway_preview_logo'  => 'nullable|string|max:255',
    ];

    public function settings(Request $request)
    {
        if ($request->isMethod('post')) {
            $request->validate(self::FIELDS);
            foreach (array_keys(self::FIELDS) as $field) {
                update_static_option($field, $request->input($field));
            }
            toastr_success(__('Settings updated.'));
            return back();
        }

        $settings = [];
        foreach (array_keys(self::FIELDS) as $field) {
            $settings[$field] = get_static_option($field, '');
        }

        return view('my-payment-gateway::admin.settings', compact('settings'));
    }
}

Step 7: Admin Settings View

@extends('backend.layout.master-tailwind')

@section('content')
<div class="card">
    <div class="card-header">
        <h5>{{ __('My Gateway Settings') }}</h5>
    </div>
    <div class="card-body">
        <form method="POST" action="{{ route('mygateway.admin.settings') }}">
            @csrf

            <div class="mb-3">
                <label class="form-label">{{ __('Enable Gateway') }}</label>
                <input type="checkbox" name="mygateway_gateway" value="on"
                    {{ ($settings['mygateway_gateway'] ?? '') === 'on' ? 'checked' : '' }}>
            </div>

            <div class="mb-3">
                <label class="form-label">{{ __('Test Mode') }}</label>
                <input type="checkbox" name="mygateway_test_mode" value="on"
                    {{ ($settings['mygateway_test_mode'] ?? '') === 'on' ? 'checked' : '' }}>
            </div>

            <div class="mb-3">
                <label class="form-label">{{ __('API Key') }}</label>
                <input type="text" name="mygateway_api_key" class="form-control"
                    value="{{ $settings['mygateway_api_key'] ?? '' }}">
            </div>

            <div class="mb-3">
                <label class="form-label">{{ __('Secret Key') }}</label>
                <input type="password" name="mygateway_secret_key" class="form-control"
                    value="{{ $settings['mygateway_secret_key'] ?? '' }}">
            </div>

            <button type="submit" class="btn btn-primary">{{ __('Save Settings') }}</button>
        </form>
    </div>
</div>
@endsection

Settings Naming Convention

All settings use the static_options table with consistent naming:

KeyPurpose
{prefix}_gatewayEnable/disable ('on'/empty)
{prefix}_test_modeTest mode toggle
{prefix}_api_keyProvider API key
{prefix}_secret_keyProvider secret key
{prefix}_preview_logoGateway logo attachment ID

The gateway only appears in checkout when {prefix}_gateway is non-empty.


Last updated: September 2026

Still stuck?
Our support team is ready to help you get set up.
Get support