11 KiB
PipraPay Module Plugin Developer Guide
- Version: v1.0
- Author: Fattain Naime
- Summary: PipraPay exposes a lightweight plugin system that allows you to extend the core gateway with module plugins. Modules run alongside payment gateways and can listen to lifecycle hooks, schedule background work, expose admin UI, and react to transaction or invoice changes. This document explains how the system works and how to build, package, and maintain a new module plugin.
Files and Directory Layout
Module plugins live under pp-content/plugins/modules/<plugin-slug>/. A typical module contains:
pp-content/plugins/modules/<plugin-slug>/
+-- meta.json # Required manifest used when importing from ZIP
+-- <plugin-slug>-class.php # Required entry point that populates $plugin_meta and loads helpers
+-- functions.php # Optional; registers hooks with add_action()
+-- views/ # Optional admin UI or partial templates
+-- assets/icon.png # 256x256 icon surfaced in the admin panel
+-- other PHP/JS/CSS files # Any module-specific code (IPN endpoints, utilities, etc.)
meta.json
The importer expects the following keys:
{
"type": "plugins",
"slug": "my-module",
"name": "My Module",
"mrdr": "modules"
}
typemust beplugins.slugbecomes the directory name and database identifier.mrdris the plugin directory root (modulesfor module plugins).
<slug>-class.php
Each plugin must expose $plugin_meta (used for the admin listing) and may load additional files:
if (!defined('pp_allowed_access')) {
die('Direct access not allowed');
}
$plugin_meta = [
'Plugin Name' => 'My Module',
'Description' => 'What your plugin does.',
'Version' => '1.0.0',
'Author' => 'Acme Devs'
];
$funcFile = __DIR__ . '/functions.php';
if (file_exists($funcFile)) {
require_once $funcFile;
}
function my_module_admin_page() {
$viewFile = __DIR__ . '/views/admin-ui.php';
if (file_exists($viewFile)) {
include $viewFile;
} else {
echo "<div class='alert alert-warning'>Admin UI not found.</div>";
}
}
parsePluginHeader() reads $plugin_meta, so keep the keys consistent with the sample above.
functions.php
Place all hook registrations and module business logic inside this file. Always guard against direct access with the pp_allowed_access constant. Use add_action() to subscribe to hooks (details below). Split complex logic into smaller functions or dedicated classes when helpful.
How PipraPay Loads Module Plugins
- Import (optional). Uploading a ZIP through Admin -> Add New uses the importer defined in
pp-include/pp-model.phpto unpack intopp-content/plugins/<type>/<mrdr>/<slug>/and validatemeta.json. - Activation. The admin panel toggles plugin records via the
pp_pluginstable. When you activate a module, PipraPay stores the slug, marks itactive, and remembers the directory. - Execution. Whenever
pp_trigger_hook()runs, it finds all active plugins, includes theirfunctions.php, and then executes callbacks that were registered withadd_action()(or matching<hook>_<plugin_slug>functions). This happens at runtime for each hook invocation, so make sure hook registration is idempotent. - Admin view loading. When you open a module inside the admin navigation the system loads
pp-include/pp-resource/plugin-loader.php, requires<slug>-class.php, and calls<slug>_admin_page().
Hook System
The hook API lives in pp-include/pp-controller.php:
add_action($hook, $callback)- registers a callback for a hook.do_action($hook, ...$args)- runs all callbacks for a hook.pp_trigger_hook($hook, ...$args)- loads every active plugin, requires itsfunctions.php, then delegates todo_action(). After firing registered callbacks, PipraPay also looks for a function named<hook>_<plugin_slug>and calls it as a fallback.
Hooks have no priority or argument count enforcement, so write callbacks that can accept optional parameters (function foo($arg = null) or function foo(...$args)).
Hook Reference
| Hook | Trigger location | Arguments | Typical use |
|---|---|---|---|
pp_cron |
index.php?cron |
none | Schedule tasks, auto-update checks, queue processing. |
pp_admin_initialize |
Early in admin/index.php |
none | Inject admin guards (2FA), load extra assets, enforce restrictions. |
pp_transaction_ipn |
Multiple transaction updates (manual approval, cron auto-verify, slip upload) | $transactionId |
Send notifications, push webhooks, reconcile ledgers. |
pp_invoice_ipn |
Invoice status changes (IPN handler, bulk updates, manual edits) | $invoiceId |
Notify customers, send receipts, sync accounting. |
pp_invoice_initialize |
After invoice theme render (invoice/index.php) |
none | Inject custom JavaScript, tracking pixels, or preload data when a public invoice is opened. |
pp_payment_initialize |
After checkout theme render (payment/index.php) |
none | Adjust checkout UI, preload payment metadata, fire analytics. |
pp_payment_link_initialize |
When a payment link page is shown (payment-link/index.php) |
none | Inject custom scripts or banner content for payment links. |
Tip:
pp_transaction_ipnandpp_invoice_ipnare invoked frequently; keep handlers lightweight and fail-safe so they never block the core workflow.
Working with Plugin Settings
Use the helper functions in pp-include/pp-controller.php:
pp_get_plugin_setting($slug)- returns the JSON blob stored for your plugin.pp_set_plugin_setting($slug, $array)- persists new settings to thepluginstable.pp_get_plugin_info($slug)- metadata such as display name and directory.
The admin panel expects AJAX forms to submit with action=plugin_update-submit and plugin_slug=<slug>. After activation you can render a settings form in views/admin-ui.php similar to the SMTP module. When posted, the core handler stores all remaining fields as JSON.
Accessing Platform Data
Useful helpers available to modules include:
pp_get_site_url()- base URL with the current host.pp_get_settings()- global settings row (currency, API keys, etc.).pp_get_transation($id)- fetches a payment transaction by PipraPay ID (or accepts a custom SQL fragment).pp_get_invoice($invoiceId)/pp_get_invoice_items($invoiceId)- fetch invoice header and line items.pp_get_payment_link($linkId)and related item fetchers.pp_verify_transaction()- cross-checks a transaction against SMS gateway data.pp_set_transaction_byid()/pp_set_transaction_byslip()- mark transactions as completed, refunded, or slip-verified (these firepp_transaction_ipn).
Inspect pp-include/pp-controller.php for many more helpers (customer lookup, file utilities, etc.).
Building a New Module Plugin
- Choose a slug. Stick to lowercase letters, numbers, and dashes (
my-new-module). - Create the directory.
pp-content/plugins/modules/my-new-module/. - Add
meta.json. Follow the schema shown above. - Create
my-new-module-class.php. Populate$plugin_meta, requirefunctions.php, and definemy_new_module_admin_page()if you need admin UI. - Create
functions.php. Guard with thepp_allowed_accessconstant, register hooks viaadd_action(), and implement callback logic. Retrieve data using the helper functions listed earlier. - (Optional) Add
views/admin-ui.php. Include a form that posts withaction=plugin_update-submitand saves settings through AJAX. - Add an icon. Place a PNG at
assets/icon.pngso the module is recognizable in the admin menu. - Activate the plugin. In the admin panel go to More -> Modules, click your plugin, and press Activate. The first activation creates a database record if one does not already exist.
- Test hooks. Trigger the relevant flows (new transaction, invoice payment, cron job) to ensure your callbacks run.
Example: Minimal IPN Handler
<?php
if (!defined('pp_allowed_access')) {
die('Direct access not allowed');
}
add_action('pp_transaction_ipn', 'my_new_module_handle_transaction');
function my_new_module_handle_transaction($transactionId)
{
$transaction = pp_get_transation($transactionId);
if (!$transaction['status']) {
return;
}
$data = $transaction['response'][0];
$payload = [
'pp_id' => $data['pp_id'],
'amount' => $data['transaction_amount'],
'currency' => $data['transaction_currency'],
'status' => $data['transaction_status'],
];
$ch = curl_init('https://example.com/webhook');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 8,
]);
curl_exec($ch);
curl_close($ch);
}
Best Practices
- Stay idempotent. Hooks like
pp_transaction_ipncan fire multiple times. Track delivery state in your own storage if repeating work is problematic. - Avoid long blocking calls. Offload slow work to queues or keep timeouts low so you never stall payment processing.
- Validate input. Use
escape_string(),filter_var(), or built-in sanitizers before storing data supplied by users. - Respect
pp_allowed_access. Never execute plugin code when the core constant is missing. - Graceful failures. Catch exceptions, return early when settings are incomplete, and write to PHP error logs instead of echoing raw errors.
- Secure endpoints. If you expose additional public PHP files (for example, an IPN endpoint), validate tokens or signatures before executing logic.
- Use settings APIs. Store configuration via
pp_set_plugin_setting()instead of writing your own files so backups and migrations capture everything.
Debugging Tips
- Enable PHP error logging (
pp-config.php) and tail the log while developing. - Temporarily add
error_log()calls inside your callbacks to confirm hook order and payloads. - Use the built-in bulk actions (Send IPN) inside the admin Transactions grid to manually re-fire
pp_transaction_ipnduring testing.
Packaging & Distribution
- Ship modules as ZIP files that contain the plugin folder at the root. The importer understands both
my-module/(folder) and flat archives. - Ensure all files are UTF-8 (ASCII-safe) and avoid BOM markers.
- Include a short
readme.txtif you want to surface installation notes inside the admin UI.
Following these guidelines keeps your module plugins consistent with the bundled examples (SMTP mailer, webhook dispatch, Telegram notifications, etc.) and ensures they interoperate cleanly with core PipraPay updates.
© PipraPay — Module Plugin Developer Documentation