Skip to main content
All three plugins implement one identical, validated Nomba protocol. This page documents the shared contract; the per-platform pages cover how each hooks it into WooCommerce, PrestaShop, and VirtueMart. Source of truth: integrations/README.md plus the NombaClient / NombaSignature classes in each plugin folder.

The lifecycle

The two independent confirmation paths, the shopper’s return and the asynchronous webhook, both converge on the same rule: grant value only against what the Nomba API reports, never on a payload’s say-so. Whichever arrives first completes the order; the other short-circuits as a duplicate.

Verification-first, always

1

Create the order

POST {prefix}/order with { order: { amount, currency, orderReference, customerEmail, callbackUrl } }. The plugin stores the orderReference and the mode (test or live) on the order, then redirects the shopper to data.checkoutLink. An optional sub-account goes in order.accountId to route funds.
2

Verify on return

On the return URL, the plugin calls GET {prefix}/transaction?idType=...&id={orderReference} and completes the order only when the API reports it paid (data.success true; data.status == "SUCCESS" on the single-transaction endpoint) with a matching amount. A mismatch or an unpaid result leaves the order held for the webhook. The return page never grants value on its own.
3

Re-verify on the webhook

Nomba POSTs a signed payment_success event. The plugin checks the nine-field HMAC signature and the timestamp freshness window first, rejecting with 401 on any failure. Only then does it call GET {prefix}/transaction again and cross-check the verified amount and currency, not the webhook payload, before completing (or holding) the order.
4

Handle the other events

payment_failed marks an unpaid order failed, and payment_reversal marks a paid order refunded. Both act on the signed event without an extra API call; the re-verification applies to payment_success only.
5

Refund from admin

POST {prefix}/refund with transactionId (and an optional amount for partials), using the mode the order was paid in.

The nine-field webhook signature

The inbound Nomba signature is not an HMAC over the raw body. It is a Base64 HMAC-SHA256, keyed by the merchant’s webhook signature key, over nine colon-joined fields, with the timestamp taken from the nomba-timestamp header and the signature read from nomba-signature:
The verification is timing-safe (hash_equals) and enforces a 300-second freshness/replay window (TOLERANCE_SECONDS = 300). A request missing the signature or timestamp header is rejected. This matches the shared NombaSignature class in each plugin, for example woocommerce/includes/class-nomba-signature.php:
This is the same colon-delimited field-HMAC scheme Duro’s own backend verifies for inbound Nomba webhooks. See the Duro security model for the platform-side implementation.

The Nomba API surface

Live base https://api.nomba.com and sandbox base https://sandbox.nomba.com both use the /v1/checkout prefix; only the host differs. Auth is POST /v1/auth/token/issue on both. Also handled: payment_failed (order marked failed) and payment_reversal (paid order marked refunded).
Use the parent (business) Account ID from the dashboard in the accountId header; a sub-account ID is rejected at authentication (HTTP 403). Set the optional sub-account only in the create-order body (order.accountId) to route funds.
Credentials per merchant (all from the Nomba dashboard): Client ID, Client secret (private key), Account ID, and webhook signature key. Each plugin stores these in its own settings screen. Sandbox test card: 5434621074252808, any expiry/CVV/PIN, OTP 9999.

Error states

The plugins fail closed and surface every failure. From the WooCommerce plugin’s error table (woocommerce/README.md), representative of all three:

Local tooling

integrations/tools/ holds everything needed to exercise a plugin without credentials, and is never shipped in a plugin zip.
  • mock-nomba/ is a local stand-in for the Nomba API (server.php) with endpoints mirroring Nomba (/v1/auth/token/issue, /v1/checkout/order, /v1/checkout/transaction, /v1/checkout/refund) plus a local /pay/{reference} checkout page and a test-only POST /mock/seed route that pre-creates a paid transaction for the re-verifying webhook path to confirm against. send-webhook.php signs and posts webhooks, with --tamper and --timestamp flags to force the 401 rejection cases.
  • demo/woocommerce/ and demo/prestashop/ are Docker Compose stores that run a full local checkout, webhook, and refund loop against the mock server, no credentials needed. Point a plugin at the mock with a base-URL override constant (for example NOMBA_WC_API_BASE).
  • sandbox-smoke/ proves the same lifecycle against the real Nomba sandbox (token, checkout, card payment, verify, refund) using test keys from the Nomba dashboard.
  • tests/ holds the PHPUnit suites (88 Nomba-plugin tests) run against a stubbed HTTP layer.
Base URLs are overridable for local testing (NOMBA_WC_API_BASE / NOMBA_API_BASE constants, or platform filters), which is how the demo stores redirect the plugin at the mock server. Against the real sandbox there is no /mock/seed: the signer only completes an order if the referenced transaction is genuinely paid, because the webhook re-verifies through the real {prefix}/transaction endpoint before granting value.

Build

Zips land in dist/. The script stages each plugin (build.sh copies only the shipped files, no tests) and zips it: WooCommerce as nomba-for-woocommerce/, PrestaShop as a nomba/ folder, Joomla with nomba.php + nomba.xml at the zip root as VirtueMart’s installer expects, plus the parallel Duro packages. Per-platform install, configure, transact, refund, and webhook detail:

WooCommerce

PrestaShop

Joomla / VirtueMart