> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useduro.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WooCommerce

> The WordPress / WooCommerce payment gateway for Nomba hosted checkout, HPOS-compatible, with native admin refunds.

`integrations/woocommerce/` is a single WordPress plugin exposing **two independent gateways**, Nomba and Duro; either or both can be enabled. It is HPOS compatible, unit tested (51 tests), and verified against a real WordPress store and the real Nomba sandbox (live card payment and refund). Source: `woocommerce/README.md`, `woocommerce/readme.txt`, and `woocommerce/includes/`.

## Structure

```
woocommerce/
  nomba-for-woocommerce.php          plugin bootstrap, gateway registration
  readme.txt                         WordPress.org manifest, install + FAQ
  uninstall.php                      cleanup on delete
  includes/
    class-wc-gateway-nomba.php       WC_Payment_Gateway subclass (Nomba)
    class-nomba-client.php           token issue (cached) + order/verify/refund
    class-nomba-signature.php        nine-field HMAC verification, 300s window
    class-nomba-webhook.php          REST webhook handler, re-verifies
    class-wc-gateway-duro.php        second gateway (Duro hosted checkout)
    class-duro-client.php / -signature.php / -webhook.php
```

## Install

From `readme.txt`:

1. Upload the plugin zip via **Plugins, Add New, Upload Plugin**, then activate. (Build it with `bash tools/build.sh`, which produces `dist/nomba-woocommerce.zip`.)
2. Open **WooCommerce, Settings, Payments**.
3. Open the **Nomba** payment method and enter the **Account ID**, **Client ID**, and **Client secret** from the Nomba dashboard (API Keys). Use the parent (business) Account ID; a sub-account ID is rejected at authentication. Test keys work with test mode on.
4. Enter the **Webhook signature key** from the dashboard webhook settings. Optionally set a **Sub-account ID** to deposit payments into a sub-account.
5. In the Nomba dashboard, set the webhook URL to `https://your-store.example/wp-json/nomba/v1/webhook`.
6. Enable the gateway and save.

<Warning>
  On a fresh WordPress install with **Plain** permalinks, `/wp-json/...` silently returns the homepage with a 200 instead of routing to the REST API, so the webhook endpoint would appear to accept everything. Set Settings, Permalinks to any option other than Plain (for example Post name) before relying on the webhook.
</Warning>

## Nomba lifecycle

The gateway (`class-wc-gateway-nomba.php`) implements the shared [verification-first lifecycle](/integrations/architecture):

1. **`process_payment`** creates a Nomba checkout order (`POST {prefix}/order`) and redirects the shopper to `data.checkoutLink`. The order stores `_nomba_order_reference` and `_nomba_mode`.
2. **`verify_on_return`** calls the verify endpoint and completes the order **only** on `paid` plus a matching amount.
3. **The webhook** (`POST /wp-json/nomba/v1/webhook`) verifies the nine-field HMAC signature from the `nomba-signature` header with the `nomba-timestamp` header, enforces a 300-second replay window, then **re-verifies** the transaction with the Nomba API (`verify_order`, `GET {prefix}/transaction`) before doing anything else. The API response is the source of truth, not the webhook payload. It cross-checks the verified amount and currency, stores `_nomba_transaction_id` from the verified `paymentReference`, and completes the order. Re-verification applies to `payment_success` only; `payment_failed` marks an unpaid order failed and `payment_reversal` marks a paid order refunded, both from the signed event without an extra API call.
4. **Refunds** run from the WooCommerce order screen through `POST {prefix}/refund` with the stored transaction id; partial amounts are supported. Refunds use the mode the order was paid in.

## Environments

Test mode (default on) targets `https://sandbox.nomba.com`; live mode targets `https://api.nomba.com`. Both use the same `/v1/checkout` prefix; only the host differs. The base URL can be overridden for local testing with the `NOMBA_WC_API_BASE` constant or the `nomba_wc_api_base_url` filter, which the local mock server uses.

## The Duro gateway

The same plugin ships a second, independent Duro hosted-checkout gateway (`class-wc-gateway-duro.php`) with saved cards and recovery:

1. **`process_payment`** creates a Duro checkout session (`POST /v1/checkout/sessions`, amount in kobo, mandatory `Idempotency-Key` header) and redirects to the hosted checkout URL. The order stores `_duro_session_id`, `_duro_token`, `_duro_mode`.
2. **`verify_on_return`** calls `Duro_Client::get_session` and completes the order only when the session status is `completed` plus a matching amount (kobo).
3. **The webhook** (`POST /wp-json/duro/v1/webhook`) verifies the `t.rawBody` HMAC signature from the `duro-signature` header, enforces a 300-second replay window, dedupes on `duro-event-id`, then re-verifies the session with the Duro API before completing on `checkout_completed` or `subscription_payment_success`.
4. **No refunds.** A one-off Duro checkout (`kind: one_time`) creates no refundable invoice, so the Duro gateway does not declare `refunds` support; use the Nomba gateway when a refund is required.

The Duro base host follows the secret-key prefix: `sk_test_` targets `https://sandbox.useduro.com`, `sk_live_` targets `https://api.useduro.com`. Overridable via the `DURO_WC_API_BASE` constant or the `duro_wc_api_base_url` filter.

## Error states

| Failure                                       | Surface                                                     |
| --------------------------------------------- | ----------------------------------------------------------- |
| Bad credentials (either gateway)              | Admin notice, checkout hidden, log source `nomba` or `duro` |
| Checkout/session create fails                 | Shopper notice, order note, log                             |
| Invalid webhook signature                     | 401, log warning                                            |
| Stale webhook timestamp                       | 401, log warning                                            |
| Nomba/Duro API re-verify call fails           | 502, log error                                              |
| API reports the payment not yet completed     | 200 unverified, order untouched                             |
| Amount or currency mismatch (verified values) | Order on hold, log error                                    |
| Refund without transaction id (Nomba only)    | Explanatory refund error in admin                           |
| Refund rejected by Nomba                      | Refund error plus order note                                |
| Refund attempted on a Duro order              | Not offered; Duro has no refund UI                          |

## Tests and demo

51 unit tests cover the gateway class (`process_payment`, return verify, refund) plus the protocol classes (client, signature, webhook), run from the repo root:

```bash theme={null}
docker run --rm -v "$PWD":/repo -w /repo/tools/tests composer:2.7 composer install
docker run --rm -v "$PWD":/repo -w /repo/tools/tests php:8.3-cli vendor/bin/phpunit
```

A full local checkout, webhook, and refund walkthrough against the mock Nomba server (no credentials) lives in `tools/demo/woocommerce/README.md`: it stands up WordPress + WooCommerce 9.4.3 in Docker, points the gateway at the mock via `NOMBA_WC_API_BASE`, and walks six webhook exercises (valid completion, wrong secret, tampered payload, stale timestamp, amount mismatch, reversal) plus an admin refund. Switching to the real sandbox is a documented volume-reset step.
