# PaidTerms for WooCommerce — developer integration notes (1.5.2)

One page for the web developer installing PaidTerms on a merchant's store. The merchant guide is `INSTALL.md`.

## What the plugin is

An off-site redirect payment gateway (`id` = `paidterms`). It holds no financing logic and never sees card or bank
details. At checkout it asks PaidTerms to create a financing request for the order, redirects the buyer to the
PaidTerms-hosted application, then follows the order by polling PaidTerms. No custom code, template override or theme
change is needed.

## Network

Outbound only, HTTPS to `https://app.paidterms.co.nz` (20 s timeout). PaidTerms never calls the store: no webhook,
inbound firewall rule, REST route or public endpoint is added.

| Call | When |
|---|---|
| `POST /api/integrations/woocommerce/create` | The buyer places an order with PaidTerms (idempotent per order) |
| `GET /api/integrations/woocommerce/status` | Polling, the buyer's return, and the "Check PaidTerms status" order action |
| `POST /api/integrations/woocommerce/cancel` | The order is cancelled, refunded or partially refunded |
| `GET /api/integrations/woocommerce/connection` | "Test connection" on the settings screen |
| `GET /api/integrations/woocommerce/config` | In the background every 15 minutes (Action Scheduler, group `paidterms`), never during a page load |

Every call sends `Authorization: Bearer <store token>`, `X-PaidTerms-Plugin-Version`, and (1.4.0+) the WordPress,
WooCommerce and PHP versions and the checkout page URL (`X-PaidTerms-Store-Url`) for support. Sent to create: order id
and number, total in cents, currency, billing email, name, company and phone, the item names and quantities (as a
short summary) and the store's name — used only to PREFILL the PaidTerms application, which the buyer can edit — and
the order-received return URL. Street addresses, prices, tax and shipping lines are not sent.

**Configuration from PaidTerms (1.4.0+).** The `/config` answer (cached in the `paidterms_wc_config` option) decides
whether PaidTerms is offered at checkout, the amount window and, where the merchant kept the default text, the
approved title/description. If PaidTerms has never answered, the plugin behaves as before; if it stops answering for
6 hours, PaidTerms is hidden at checkout until it answers again. The server re-checks everything on every order.

**Updates (1.4.0+).** A newer version is offered through WordPress's own Updates screen. The package must be on the
PaidTerms host over https, and the plugin refuses it unless its SHA-256 matches the published value. Nothing is
installed without the site owner's click (or their own auto-update setting).

## The token and the store address

- One token per store **origin** (scheme + host + port). The token is bound to it server-side, and every order's
  return URL must be on that origin, so `www.` vs no-`www`, http vs https and staging hosts each need their own token.
  A WordPress install in a subdirectory is fine (only the origin counts).
- The address is taken from `wc_get_checkout_url()`, which follows WordPress → Settings → General → Site Address.
  The connection test reports a mismatch as `store_url_mismatch`.
- Store the token in the settings field, or in `wp-config.php` as `define( 'PAIDTERMS_TOKEN', 'ptwc_live_…' );`,
  which overrides the field. It is never logged. The settings screen shows its first 18 characters (non-secret).

## Requirements

WordPress 6.4+, WooCommerce 8.0+, PHP 7.4+, store currency NZD. Tested on WordPress 7.1, WooCommerce 11.2, PHP 8.3;
syntax-checked on PHP 7.4, 8.1, 8.3, 8.4. Declares compatibility with HPOS (`custom_order_tables`) and the
Cart/Checkout Blocks. Classic (shortcode) checkout and the Checkout Block are both supported.

## What it hooks

| Hook | Purpose |
|---|---|
| `woocommerce_payment_gateways`, `woocommerce_blocks_payment_method_type_registration` | Registers the gateway (classic and Blocks) |
| `woocommerce_cancel_unpaid_order` | Exempts orders PaidTerms holds from hold-stock auto-cancel (a buyer's application can take over an hour) |
| `woocommerce_valid_order_statuses_for_payment_complete` | A PaidTerms order reaches Processing only from Pending or On hold |
| `woocommerce_email_enabled_customer_on_hold_order` | No "on hold until we confirm payment" email for declined or under-review orders |
| `woocommerce_order_status_cancelled`, `…_refunded`, `woocommerce_order_partially_refunded` | Tells PaidTerms |
| `woocommerce_order_actions` | Adds "Check PaidTerms status" |
| `woocommerce_before_add_to_cart_form` | Product-page message only when Product-page messaging is set to Automatic; no per-product price check |
| `[paidterms_financing_message]` | Renders the message only when Product-page messaging is set to Manual placement |

Product-page messaging defaults to Off. Both placements also require an enabled
gateway, configured token, NZD store currency and a currently available cached
PaidTerms configuration. The compact marketing message does not calculate
instalments or decide eligibility; the complete order is assessed at checkout
and in the PaidTerms application.
The setting does not participate in checkout gateway availability. The message
CSS is a separate asset enqueued only for an Automatic message or a detected
Manual shortcode.

Order meta (HPOS-safe, via the order object): `_paidterms_checkout_id`, `_paidterms_reference`, `_paidterms_pay_url`,
`_paidterms_state`, plus `_paidterms_cancel_*` bookkeeping. Do not edit these.

## Scheduled checks

Action Scheduler, group `paidterms` (WooCommerce → Status → Scheduled Actions): checks at 2, 10, 20, 30 and 45 minutes,
1, 2, 6 and 24 hours after the order, then every 6 hours until 14 days after the order. Cancellation retries use the
same group. A low-traffic site needs WP-Cron to run (or a real cron calling `wp-cron.php`).

## Order states

Pending payment (application in progress) → On hold (payment set up; **not approved for fulfilment**) → Processing
(PaidTerms approved). Declined and manual review stay On hold with a note. Only Processing means ship. The plugin never
moves an order backwards, never cancels one, and never re-opens a cancelled or declined order.

## Compatibility notes

- Ordinary themes using WooCommerce's checkout (classic or Block) need nothing. Custom checkout builders, one-page
  checkouts and headless storefronts are not supported without testing.
- Caching/CDN: exclude the checkout and order-received pages, as for any payment gateway.
- Security plugins that block outbound requests must allow `app.paidterms.co.nz`.
- Deactivating changes no order. Deleting removes the plugin's settings and queued checks; orders and notes are kept.
- Upgrade by uploading the new ZIP ("Replace current with uploaded"); open orders are re-checked automatically.
