Skip to main content
This guide covers everything required to build, install, and maintain a payment gateway plugin for OwnPay. Every example is derived from the live Stripe gateway implementation at modules/gateways/stripe/.

How gateway plugins work

When OwnPay boots, PluginLoader::loadActive() scans modules/gateways/, discovers all active gateway directories, validates each manifest.json, token-scans every PHP file for dangerous functions, and instantiates the entrypoint class. Because a gateway class implements both PluginInterface and GatewayAdapterInterface, the loader automatically calls GatewayBridge::registerAdapter($instance) after booting all plugins.
Credentials are never passed raw. GatewayBridge calls GatewayConfigRepository::findCredentialsBySlug(), decrypts the stored JSON blob, and passes the plain-text key-value map to your adapter methods.

Directory structure

Place your gateway inside modules/gateways/{your-slug}/:

The manifest.json file

The entrypoint class

A gateway plugin’s entrypoint class must implement both OwnPay\Plugin\PluginInterface and OwnPay\Gateway\GatewayAdapterInterface:

Method contracts

initiate(array $params, array $credentials): array

Called to start a payment session. $params has this guaranteed shape:
Return one of:
  • ['redirect_url' => 'https://gateway.example.com/pay/SESSION_ID']
  • ['form_html' => '<form>...</form>']
  • ['session_id' => 'SESSION_ID']

verify(array $callbackData, array $credentials): array

Security Rule: Never trust the payment status from $callbackData. Always make a server-to-server API call.

verifyWebhook(string $rawBody, array $headers, array $credentials): bool

Use hash_equals() for all HMAC comparisons. Include replay protection by rejecting stale timestamps.

refund(string $gatewayTrxId, string $amount, array $credentials): array

$gatewayTrxId is the value your verify() returned. $amount is a BCMath-safe decimal string.

supports(string $feature): bool

Multi-file plugins

Declare a namespace in manifest.json. The PluginLoader registers a PSR-4 autoloader mapping {namespace}\{SubClass} to {plugin_dir}/{SubClass}.php.

Database migrations

Create a migrations/ directory with .sql files. Plugin tables must be prefixed with op_plugin_.

Content Security Policy

If your gateway loads external JavaScript, declare the required domains in csp inside manifest.json.

Security requirements

  • Never trust callback data - always verify server-side
  • Use hash_equals() for all HMAC comparisons
  • Reject webhook timestamps older than 5 minutes
  • If webhook_secret is missing, verifyWebhook() must return false
  • No eval() or OS commands
  • No direct op_* table access

Installation and activation

  1. Package your plugin directory as a ZIP
  2. Upload via Admin > Gateways > “Install Plugin”
  3. Activate via the gateway list
  4. Configure credentials via the gateway settings form

Checklist

  • manifest.json present with all required fields
  • Entrypoint implements both PluginInterface and GatewayAdapterInterface
  • slug() returns the exact value in manifest.json
  • initiate() returns a valid response shape
  • verify() makes a server-side API call
  • verifyWebhook() uses hash_equals() and enforces timestamp replay window
  • Plugin tables use the op_plugin_ prefix
  • No use of eval(), exec(), shell_exec()
  • CSP domains declared in manifest.json
Last modified on July 15, 2026