Payment (PSP) Connectors
- Version
- 4.0 preview
- Updated
How to add a payment method to CBX — an integration with a payment service provider (PSP) — by dropping a connector folder into the customization layer. A connector is a small, convention-based set of files (no class hierarchy to learn): metadata functions, an admin settings form, a checkout "bridge" that hands off to the PSP, a result page, and (if the PSP calls back) an IPN handler. CBX ships ~20 built-in connectors (PayPal, bank transfer, cash on delivery, and more) you can copy from.
This is a customization-first shadow mechanism: a connector folder in data/customization/psp_connectors/
is used instead of a built-in one of the same name, and new names simply add new methods.
Read com_configbox_customization_overview.md first. All paths are relative to the component root
docroot/components/com_configbox/. Source references are point-in-time (component 3.4.1) — verify
against the code.
1. What a connector is
A connector is a folder named after the payment method (lowercase, snake_case), e.g.
paypal_wpstandard, banktransfer. The folder name is the connector name and is used to build every
file path, function name and class name (so keep it stable and filesystem-safe — it's run through
sanitizeFileName(), helpers/psp.php:31).
Resolution is customization-first (ConfigboxPspHelper::getPspConnectorFolder(), :29-41):
if (is_dir(self::getCustomDir().'/'.$subFolder)) { return customDir/<name>; } // customization wins
else { return defaultDir/<name>; } // else built-in
getConnectorNames() (:16-22) merges both dirs, so your custom connector appears in the payment-method
admin alongside the built-ins; a custom folder with a built-in's name replaces that built-in.
2. The folder contract (files, functions, classes)
Everything is convention. For a connector named <name>:
| File | Required? | What it provides |
|---|---|---|
administration.php | yes | Plain functions describing the connector (title, setting keys, IPN support). See §3. |
settings.php | if it has settings | The admin settings form template (the inputs for the keys in _get_setting_keys()). See §4. |
bridge.php | yes | The checkout hand-off template — emits the form/redirect to the PSP. Rendered as ConfigboxViewCheckoutpspbridge. See §5. |
result.php | usually | The return/confirmation page template, rendered as ConfigboxViewPaymentresult. See §6. |
ipn.php | only if IPN | The instant payment notification handler class. See §7. |
language/ | optional | Connector-specific language files. |
There is no base class. A connector is files + named functions + (for IPN) one class — by convention, resolved by
ConfigboxPspHelper. This is deliberately lightweight; follow a built-in connector as your template.
3. administration.php — the metadata functions
Plain functions, prefixed with the connector name (this is how the helper finds them). From the
built-in paypal_wpstandard/administration.php:
<?php
function paypal_wpstandard_get_title() { // shown in the admin payment-method list
return 'PayPal Payments Standard';
}
function paypal_wpstandard_get_setting_keys() { // POST keys stored as this method's settings
return array('paypalid', 'testmode');
}
function paypal_wpstandard_has_instant_payment_notification() {// does the PSP call back? (IPN)
return true;
}
function paypal_wpstandard_get_product_url() { // optional: a "learn more" link
return 'https://www.paypal.com/…';
}
| Function | Read by | Purpose |
|---|---|---|
<name>_get_title() | getPspConnectorTitle() (:79-95) | Display name (falls back to the folder name). |
<name>_get_setting_keys() | getPspConnectorSettingKeys() (:50-72) | The keys taken from POST when the payment method is saved; these become the connector's settings, available in settings.php, the order record's payment object, and the IPN class. |
<name>_has_instant_payment_notification() | pspSupportsIpn() (:102-119) | true ⇒ CBX expects an ipn.php (§7). |
The smallest possible
administration.phpis just<name>_get_title()+<name>_has_instant_payment_notification()returningfalse(e.g. the built-inbanktransfer). A method with no PSP hand-off needs no settings and no IPN.
4. settings.php — the admin settings form
A plain PHP template rendering the inputs for the keys in _get_setting_keys(). $this is the
payment-method admin view; read current values with $this->settings->get('<key>') and name each input
after its setting key so it's picked up from POST on save. From paypal_wpstandard/settings.php:
<?php defined('CB_VALID_ENTRY') or die(); ?>
<div class="kenedo-property property-type-string">
<div class="property-label"><?php echo KText::_('PayPal email address'); ?></div>
<div class="property-body">
<input class="form-control" type="text" name="paypalid"
value="<?php echo hsc($this->settings->get('paypalid')); ?>" />
</div>
</div>
The input name="paypalid" matches a key returned by paypal_wpstandard_get_setting_keys(), so saving the
payment method stores it. Escape values with hsc().
5. bridge.php — the checkout hand-off
After checkout, CBX renders the connector's bridge.php as the view
ConfigboxViewCheckoutpspbridge. This is where you POST/redirect to the PSP. For a redirect PSP you
typically emit a form (auto-submitted by the checkout JS) with the PSP's fields and your settings; for an
offline method you just link to the result page. From banktransfer/bridge.php (offline — no PSP):
<?php
defined('CB_VALID_ENTRY') or die();
/** @var $this ConfigboxViewCheckoutpspbridge */
?>
<div id="checkoutform">
<a class="trigger-redirect-to-psp"
href="<?php echo KLink::getRoute('index.php?option=com_configbox&view=paymentresult&connector_name=banktransfer'); ?>"></a>
</div>
The a.trigger-redirect-to-psp element is the convention the checkout JS follows to advance. A real PSP
bridge builds a <form> whose action is the PSP endpoint and whose hidden inputs carry the amount, order
id, return URLs, and credentials from $this->settings. Copy a redirecting built-in (e.g.
paypal_wpstandard/bridge.php) as your starting point.
6. result.php — the return / confirmation page
Rendered as ConfigboxViewPaymentresult (the view=paymentresult URL the bridge points at). $this
exposes the order and shop context — $this->orderRecord (incl. payableAmount, id), $this->shopdata,
and link vars ($this->linkToOrder, $this->linkToCustomerProfile, $this->linkToDefaultProductListing).
From banktransfer/result.php (abridged):
<?php
defined('CB_VALID_ENTRY') or die();
/** @var ConfigboxViewPaymentresult $this */
?>
<div id="com_configbox" class="cb-content">
<div id="view-paymentresult">
<h2><?php echo KText::_('Payment via bank transaction'); ?></h2>
<p><?php echo KText::sprintf('Please transfer %s to the following account:', cbprice($this->orderRecord->payableAmount)); ?></p>
<!-- … account details from $this->shopdata, escaped with hsc() … -->
<ul class="continue-links">
<li><a href="<?php echo $this->linkToOrder; ?>"><?php echo KText::_('See order details'); ?></a></li>
</ul>
</div>
</div>
Use cbprice() for money and hsc() for everything dynamic.
7. ipn.php — instant payment notification (when the PSP calls back)
If _has_instant_payment_notification() returns true, CBX routes the PSP's server-to-server
callback to your IPN class. getIpnObject() (:126-155) loads ipn.php and instantiates the class:
$className = 'Ipn' . ucwords(strtolower($connectorName)); // :146
⚠️ The IPN class name has a quirk.
ucwords()capitalizes after spaces, not underscores, and the name is first lowercased. So for connectorpaypal_wpstandardthe expected class isIpnpaypal_wpstandard(one leading capital, the rest lowercase, underscore preserved) — notIpnPaypalWpstandard. Name your IPN class exactly'Ipn'.ucwords(strtolower('<name>')); copy a built-in'sipn.phpand rename to be safe. A mismatch throws "IPN class not found" (:148-151).
The IPN class verifies the PSP's callback (authenticity + amount), then sets the order's status — usually
by resolving a status code and updating the order. Settings from _get_setting_keys() are available to the
IPN object (e.g. to pick test vs. live credentials). Use a built-in IPN handler (e.g.
paypal_wpstandard/ipn.php) as the structural reference; the verification details are PSP-specific.
Security note: the IPN endpoint is public and unauthenticated by CBX — anyone can POST to it. Your handler is the only thing standing between a forged callback and a "paid" order. Always verify the notification with the PSP (signature/round-trip), check the amount and currency against the order, and be idempotent (the PSP may retry). SQL here is hand-built mysqli — quote/validate everything from the request.
8. Worked example — a minimal "Invoice on account" method
An offline method (no PSP, no IPN): the customer is told they'll be invoiced. Folder
data/customization/psp_connectors/invoiceaccount/.
// administration.php
<?php
function invoiceaccount_get_title() {
return KText::_('Invoice on account');
}
function invoiceaccount_has_instant_payment_notification() {
return false; // offline — no callback, no ipn.php needed
}
// (no _get_setting_keys → no settings.php needed)
// bridge.php (rendered as ConfigboxViewCheckoutpspbridge)
<?php
defined('CB_VALID_ENTRY') or die();
/** @var $this ConfigboxViewCheckoutpspbridge */
?>
<div id="checkoutform">
<a class="trigger-redirect-to-psp"
href="<?php echo KLink::getRoute('index.php?option=com_configbox&view=paymentresult&connector_name=invoiceaccount'); ?>"></a>
</div>
// result.php (rendered as ConfigboxViewPaymentresult)
<?php
defined('CB_VALID_ENTRY') or die();
/** @var ConfigboxViewPaymentresult $this */
?>
<div id="com_configbox" class="cb-content"><div id="view-paymentresult">
<h2><?php echo KText::_('Thank you for your order'); ?></h2>
<p><?php echo KText::sprintf('We will invoice the amount of %s. Order number %s.',
cbprice($this->orderRecord->payableAmount), $this->orderRecord->id); ?></p>
<ul class="continue-links">
<li><a href="<?php echo $this->linkToOrder; ?>"><?php echo KText::_('See order details'); ?></a></li>
</ul>
</div></div>
Three files, no class. The method now appears in the admin payment-method list (title from
administration.php) and works end-to-end at checkout. Enable/assign it like any payment method
(functional/com_configbox_commerce_setup.md).
9. Deployment checklist
data/customization/psp_connectors/<name>/
administration.php ← <name>_get_title(), _get_setting_keys(), _has_instant_payment_notification()
settings.php ← admin settings form (only if there are setting keys)
bridge.php ← checkout hand-off (ConfigboxViewCheckoutpspbridge)
result.php ← return/confirmation page (ConfigboxViewPaymentresult)
ipn.php ← class Ipn<ucwords(strtolower(name))> (only if IPN supported)
language/ ← optional connector language files
- Pick a stable, lowercase folder name = the connector name (drives all paths/functions/classes).
- Write
administration.phpwith the three core functions (title, setting keys, IPN flag). - If it has settings, add
settings.phpwith inputs named after the keys; read with$this->settings->get(). - Write
bridge.php(PSP redirect form, or a link toview=paymentresultfor offline). - Write
result.phpfor the customer-facing confirmation. - If the PSP calls back, set
_has_instant_payment_notification()totrue, addipn.phpwith classIpn<ucwords(strtolower(name))>(§7), and verify every callback (auth + amount + idempotency). - Configure the payment method in the admin (assign to groups/zones) — see the commerce-setup doc.
- Verify manually end-to-end — checkout → bridge → PSP → IPN → status change → result page — in the PSP's sandbox. CBX has zero automated tests.
10. Conventions & gotchas
- Folder name = connector name = path/function/class prefix. Keep it lowercase,
snake_case, stable. - Customization-first. A custom folder shadows a built-in of the same name install-wide; new names add new methods.
- Functions are name-prefixed (
<name>_get_title, …) — that's how the helper resolves them. - IPN class name quirk —
'Ipn'.ucwords(strtolower($name))capitalizes after spaces only, sopaypal_wpstandard→Ipnpaypal_wpstandard(§7). Copy + rename from a built-in. - IPN is public + unauthenticated — verify with the PSP, check amount/currency, be idempotent. This is the security-critical surface of any payment integration.
- Templates, not classes (except IPN).
bridge.php/result.phpare view templates with$thisbound to the respective core view;settings.phpto the admin view. - Escape & quote.
hsc()for output,cbprice()for money; hand-built mysqli — never trust request data in the IPN handler. - Copy a built-in that matches your PSP's flow (redirect vs. offline vs. API) as your starting point —
the ~20 in
psp_connectors/cover most patterns.
See also
psp_connectors/— the ~20 built-in connectors (banktransfer, paypal_wpstandard, cashondelivery, …) to copy from.helpers/psp.php—ConfigboxPspHelper: resolution (getPspConnectorFolder,:29), metadata (:50,:79,:102), IPN loading (getIpnObject,:126).functional/com_configbox_commerce_setup.md— configuring/assigning payment methods in the admin.com_configbox_customization_overview.md— the extension-point map.com_configbox_events_and_observers.md—onConfigboxGetPaymentOptions(offer payment options programmatically) andonConfigBoxSetStatus(react to the status change an IPN triggers).