KobiZeka
KobiZeka
Developer Docs
📦 Plugin Development Guide

Payment Plugin
Development Guide

Build custom payment integrations for the KobiZeka™ Hotel Management System. This guide covers the complete plugin architecture, from creating a simple offline payment method to building a full 3D Secure online payment gateway.

ℹ️ Version
This guide applies to KobiZeka HMS v1.x and later. The plugin system uses PHP 8.0+ features including typed properties, union types, and named arguments.

Introduction

The KobiZeka payment plugin system is designed to be modular and self-contained. Each plugin lives in its own directory under app/plugins/Payment/ and is automatically discovered by the PaymentPluginManager.

Plugins can be:

The system supports:

Architecture

The payment system consists of three main components:

Component Description
PaymentProviderInterface Contract that all plugins must implement
PaymentPluginManager Singleton that discovers, loads, and manages all plugins
PluginTranslationTrait Provides multi-language support via __p() method
PaymentController Admin panel controller for settings & management

Plugin Lifecycle

  1. Discovery — Manager scans app/plugins/Payment/*/Provider.php
  2. Registration — New plugins are auto-registered in the payment_plugins table
  3. Configuration — Admin enables plugin and fills in settings (API keys, etc.)
  4. Checkout — Active plugins appear on the reservation confirmation page
  5. Payment — Guest selects method → createPayment() → redirect/process
  6. Callback — Gateway sends result → handleCallback() → transaction saved

Directory Structure

app/plugins/Payment/ ├── PaymentProviderInterface.php ← Interface contract ├── PaymentPluginManager.php ← Plugin manager (singleton) ├── PluginTranslationTrait.php ← i18n trait │ ├── YourPlugin/Your plugin folder │ ├── Provider.php ← Main plugin class │ └── lang/ ← Translations │ ├── tr.php │ ├── en.php │ └── de.php │ ├── PayAtHotel/ ← Built-in: offline │ ├── Provider.php │ └── lang/ (tr, en, de) │ ├── BankTransfer/ ← Built-in: offline │ ├── Provider.php │ └── lang/ (tr, en, de) │ ├── Iyzico/ ← Built-in: online (3D Secure) │ ├── Provider.php │ └── lang/ (tr, en, de) │ └── PayTR/ ← Built-in: online (3D Secure) ├── Provider.php └── lang/ (tr, en, de)
✨ Auto-Discovery
Simply create a new folder with a Provider.php file implementing PaymentProviderInterface. The system will automatically detect it on the next page load.

Quick Start

1

Create Plugin Directory

Create a new folder: app/plugins/Payment/MyGateway/

2

Create Provider.php

Implement PaymentProviderInterface with all required methods.

3

Add Language Files

Create lang/tr.php, lang/en.php, lang/de.php for translations.

4

Activate in Admin Panel

Navigate to Payment → Plugins. Your plugin appears automatically. Enable it and configure settings.

PaymentProviderInterface

Every plugin must implement this interface. Here is the complete contract:

// app/plugins/Payment/PaymentProviderInterface.php

namespace App\Plugins\Payment;

interface PaymentProviderInterface
{
    // ─── Identity ───
    public function getSlug(): string;         // Unique ID: 'my_gateway'
    public function getDisplayName(): string;  // 'My Gateway'
    public function getDescription(): string;  // Short description
    public function getIcon(): string;         // FontAwesome: 'fa-credit-card'
    public function getVersion(): string;      // '1.0.0'
    public function getAuthor(): string;       // 'Your Company'
    public function getType(): string;         // 'online' or 'offline'

    // ─── Configuration ───
    public function getSettingsFields(): array; // Admin settings form fields
    public function isConfigured(): bool;      // Are API keys set?

    // ─── Checkout ───
    public function renderCheckoutInfo(array $reservation): string;

    // ─── Payment ───
    public function createPayment(array $reservation, float $amount): array;
    public function handleCallback(array $data): array;

    // ─── Refunds ───
    public function supportsRefund(): bool;
    public function refund(string $transactionId, float $amount): array;
}

Method Reference

MethodReturnDescription
getSlug()stringUnique identifier. Use snake_case. Must be unique across all plugins.
getDisplayName()stringHuman-readable name. Use $this->__p('name') for i18n.
getDescription()stringShort description. Use $this->__p('description') for i18n.
getIcon()stringFontAwesome icon class without fas prefix.
getType()string'online' for payment gateways, 'offline' for manual methods.
getSettingsFields()arrayArray of field definitions for the admin settings form.
isConfigured()boolReturn true if all required settings are filled. Offline plugins can always return true.
createPayment()arrayInitiate payment. Return ['redirect_url' => '...'] for online, [] for offline.
handleCallback()arrayProcess gateway callback. Return ['success' => bool, 'transaction_id' => '...'].
supportsRefund()boolWhether this plugin supports programmatic refunds.
refund()arrayProcess refund. Return ['success' => bool, 'message' => '...'].

Provider.php

The main plugin class. Must implement PaymentProviderInterface and use PluginTranslationTrait for multi-language support.

YourPlugin/Provider.phpnamespace App\Plugins\Payment\MyGateway;

use App\Plugins\Payment\PaymentProviderInterface;
use App\Plugins\Payment\PluginTranslationTrait;

class Provider implements PaymentProviderInterface
{
    use PluginTranslationTrait;

    public function getSlug(): string       { return 'my_gateway'; }
    public function getDisplayName(): string{ return $this->__p('name'); }
    public function getDescription(): string{ return $this->__p('description'); }
    public function getIcon(): string       { return 'fa-credit-card'; }
    public function getVersion(): string    { return '1.0.0'; }
    public function getAuthor(): string     { return 'Your Company'; }
    public function getType(): string       { return 'online'; }

    // ... see full examples below
}

Settings Fields

The getSettingsFields() method returns an array of field definitions. The admin panel automatically generates a settings form from these fields.

public function getSettingsFields(): array
{
    return [
        [
            'key'      => 'api_key',          // Setting key (stored in DB)
            'label'    => $this->__p('api_key'),  // Label (translated)
            'type'     => 'text',              // text | textarea | select | checkbox
            'default'  => '',                  // Default value
            'required' => true,                // Show as required in UI
        ],
        [
            'key'      => 'secret_key',
            'label'    => $this->__p('secret_key'),
            'type'     => 'text',
            'default'  => '',
            'required' => true,
        ],
        [
            'key'      => 'sandbox',
            'label'    => 'Sandbox Mode',
            'type'     => 'checkbox',
            'default'  => '1',
            'required' => false,
        ],
    ];
}

Supported Field Types

TypeRenders AsNotes
textText inputFor API keys, titles, URLs
textareaMulti-line textareaFor descriptions, instructions
selectDropdownAdd 'options' => ['val' => 'Label']
checkboxToggle checkboxValue is '1' or '0'

Checkout Rendering

The renderCheckoutInfo() method returns HTML that is displayed on the reservation confirmation page when the guest selects this payment method.

public function renderCheckoutInfo(array $reservation): string
{
    $html = '<div style="padding:16px;background:#f8f9fa;border-radius:10px">';
    $html .= '<h4>' . e($this->__p('name')) . '</h4>';
    $html .= '<p>You will be redirected to our secure payment page.</p>';
    $html .= '</div>';
    return $html;
}
⚠️ Security
Always use the e() helper to escape user-supplied data in your HTML output. Never trust data from $reservation without escaping.

Payment Flow

Online Plugins

For online payment gateways, the flow is:

public function createPayment(array $reservation, float $amount): array
{
    // 1. Read your API settings
    $mgr = \App\Plugins\Payment\PaymentPluginManager::getInstance();
    $apiKey = $mgr->getPluginSetting($this->getSlug(), 'api_key', '');

    // 2. Build payment request to gateway API
    $callbackUrl = base_url('payment/callback/' . $this->getSlug());

    // 3. Call gateway API...
    // $response = $gateway->createPayment(...);

    // 4. Return redirect URL
    return [
        'redirect_url'    => $response['payment_page_url'],
        'transaction_id'  => $response['token'],
    ];
}

Callback Handling

public function handleCallback(array $data): array
{
    // $data contains POST/GET parameters from the gateway

    // Verify the payment with gateway API...

    return [
        'success'        => true,
        'transaction_id' => $data['token'],
        'amount'         => 250.00,
        'currency'       => 'TRY',
        'message'        => 'Payment successful',
    ];
}

Offline Plugins

Offline plugins have simpler implementations:

// No redirect needed
public function createPayment(array $reservation, float $amount): array
{
    return [];
}

// No callback to handle
public function handleCallback(array $data): array
{
    return ['success' => false, 'message' => 'N/A'];
}

Refunds

If your gateway supports programmatic refunds, implement the refund methods:

public function supportsRefund(): bool { return true; }

public function refund(string $transactionId, float $amount): array
{
    // Call gateway refund API...
    return [
        'success' => true,
        'message' => 'Refund processed',
        'refund_id' => 'ref_abc123',
    ];
}

PluginTranslationTrait

The translation trait provides each plugin with its own isolated translation system. No need to modify core language files.

How It Works

  1. Trait detects the current admin locale via admin_locale()
  2. Loads the matching lang/{locale}.php from the plugin's own directory
  3. Falls back through: requested locale → en → tr
  4. Automatically reloads when locale changes (no caching issues)

Language Files

Each language file returns a simple key-value array:

YourPlugin/lang/en.php<?php
return [
    'name'         => 'My Gateway',
    'description'  => 'Secure online payments via My Gateway.',
    'api_key'      => 'API Key',
    'secret_key'   => 'Secret Key',
    'sandbox_note' => 'Use sandbox URL for testing',
];
YourPlugin/lang/tr.php<?php
return [
    'name'         => 'Ödeme Geçidi',
    'description'  => 'My Gateway ile güvenli online ödeme.',
    'api_key'      => 'API Anahtarı',
    'secret_key'   => 'Gizli Anahtar',
    'sandbox_note' => 'Test için sandbox URL kullanın',
];

Using __p()

Call $this->__p('key') anywhere in your Provider class:

// In identity methods
public function getDisplayName(): string { return $this->__p('name'); }

// In settings field labels
['label' => $this->__p('api_key')]

// In checkout rendering
$html .= '<p>' . e($this->__p('checkout_instructions')) . '</p>';
ℹ️ Fallback Behavior
If a key is not found in any language file, __p() returns the key itself. This prevents broken output and makes it easy to spot missing translations.

Plugin Manager API

Access the plugin manager from anywhere via the singleton:

$mgr = \App\Plugins\Payment\PaymentPluginManager::getInstance();
MethodReturnsDescription
getAllPlugins()arrayAll discovered plugins (slug → Provider instance)
getActivePlugins()arrayOnly enabled plugins, sorted by admin-defined order
getPlugin($slug)?ProviderGet a specific plugin by slug
getPluginDbRecord($slug)?arrayDB record with live-translated display_name & description
getPluginSettings($slug)arrayDecoded JSON settings from DB
getPluginSetting($slug, $key, $default)stringSingle setting value
isActive($slug)boolWhether plugin is enabled
getCheckoutMethods()arrayActive & configured plugins for frontend checkout

Database Schema

payment_plugins

ColumnTypeDescription
idINT AUTO_INCREMENTPrimary key
slugVARCHAR(50) UNIQUEPlugin identifier
display_nameVARCHAR(100)Cached display name (overridden at runtime by Provider)
descriptionTEXTCached description (overridden at runtime by Provider)
iconVARCHAR(50)FontAwesome icon class
typeENUM('online','offline')Plugin type
versionVARCHAR(20)Plugin version
authorVARCHAR(100)Plugin author
is_activeTINYINT(1)Enabled/disabled
is_built_inTINYINT(1)System plugin flag
settingsJSONPlugin settings as JSON object
sort_orderINTDisplay order in admin & frontend

payment_transactions

ColumnTypeDescription
idINT AUTO_INCREMENTPrimary key
reservation_idINTFK to reservations table
plugin_slugVARCHAR(50)Which plugin processed this
transaction_idVARCHAR(255)Gateway's transaction reference
amountDECIMAL(10,2)Transaction amount
currencyVARCHAR(3)ISO currency code
statusENUM('pending','success','failed','refunded')Transaction status
raw_responseJSONFull gateway response for debugging
created_atDATETIMETransaction timestamp

Helper Functions

These global functions are available in your plugin:

FunctionDescription
e($string)HTML escape (htmlspecialchars)
base_url($path)Full URL: https://hotel.com/path
admin_url($path)Admin URL: https://hotel.com/admin/path
setting($key, $default)Read site setting from DB
format_money($amount)Format as currency: ₺1.250,00
admin_locale()Current admin panel language: 'tr', 'en', 'de'
get_locale()Current frontend language
csrf_field()CSRF hidden input for forms
csrf_token()CSRF token string

Security Guidelines

🔒 Critical
Payment plugins handle sensitive financial data. Follow these guidelines strictly.

Example: Offline Plugin

A complete offline payment plugin (e.g., "Pay at Hotel"):

PayAtHotel/Provider.php<?php
namespace App\Plugins\Payment\PayAtHotel;

use App\Plugins\Payment\PaymentProviderInterface;
use App\Plugins\Payment\PluginTranslationTrait;

class Provider implements PaymentProviderInterface
{
    use PluginTranslationTrait;

    public function getSlug(): string        { return 'pay_at_hotel'; }
    public function getDisplayName(): string { return $this->__p('name'); }
    public function getDescription(): string { return $this->__p('description'); }
    public function getIcon(): string        { return 'fa-hotel'; }
    public function getVersion(): string     { return '1.0.0'; }
    public function getAuthor(): string      { return 'KobiZeka'; }
    public function getType(): string        { return 'offline'; }

    public function getSettingsFields(): array
    {
        return [
            ['key' => 'title', 'label' => $this->__p('title_label'),
             'type' => 'text', 'default' => 'pay_at_hotel', 'required' => false],
            ['key' => 'accepted_methods', 'label' => $this->__p('accepted_methods'),
             'type' => 'text', 'default' => 'Cash, Credit Card', 'required' => false],
        ];
    }

    public function isConfigured(): bool { return true; }

    public function renderCheckoutInfo(array $reservation): string
    {
        return '<div class="checkout-info">'
            . '<p>' . e($this->__p('description')) . '</p>'
            . '</div>';
    }

    public function createPayment(array $reservation, float $amount): array
    {
        return []; // No redirect for offline
    }

    public function handleCallback(array $data): array
    {
        return ['success' => false, 'message' => 'N/A'];
    }

    public function supportsRefund(): bool { return false; }
    public function refund(string $tid, float $amt): array { return ['success' => false]; }
}

Full Plugin Template

Copy this template to start building your own plugin:

app/plugins/Payment/MyGateway/ ├── Provider.php └── lang/ ├── tr.php ├── en.php └── de.php
🚀 Ready to Build

Create your plugin folder, implement PaymentProviderInterface, add translation files, and your plugin will appear automatically in the admin panel. No core files need to be modified.


KobiZeka™ Hotel Management System
Payment Plugin Development Guide v1.0

Need help? Contact developer@kobizeka.com