Skip to main content

Roadmap

Seven milestones, built in order. Each one is useful on its own — the ledger is testable before any POS exists, the webhook API is usable before any vendor driver ships, and the Virtual Terminal makes the whole thing demonstrable without hardware.

All seven are complete. The expansion drivers in Milestone 7 ship marked experimental — written against documented APIs with no live install to verify against, and built to refuse to activate rather than risk writing wrong stock. See what that means.

29 of 29 tasks completeextension v1.1.0

Verified against the extension source on 2026-09-01.

Foundation — the ledgerDone

5/5

A provider-agnostic core that can record, queue and replay commerce events safely. Nothing here talks to a POS or to a cart; it is the neutral middle that makes the whole system N+M instead of N×M.

  • Extension scaffold Done

    manifest.php, the extension class, and a Settings screen under Unyson+ → POS Sync. Ships inactive by default, like the Animation Engine.

  • Ledger schema + migrations Done

    Three custom tables — pos_items (canonical item + external ids), pos_events (immutable sale / refund / stock-movement log), pos_map (external id ↔ local id, per connection). Versioned migrations run on activation and upgrade.

  • Idempotency ledger Done

    Every inbound event carries an external transaction id, stored behind a UNIQUE index. A replayed webhook is recorded as a duplicate and dropped — never applied twice. This is the single most important correctness guarantee in the extension.

  • Job queue on Action Scheduler Done

    Inbound events are acknowledged fast and applied asynchronously with retry + exponential backoff, so a slow cart write never times out a POS webhook. Uses Action Scheduler when present (every WooCommerce install has it), with a WP-Cron fallback.

  • Audit log + viewer Done

    A readable, filterable record of every event: what arrived, what was applied, what was skipped and why. The first thing anyone opens when a stock number looks wrong.

Store driver seamDone

4/4

One interface between the ledger and whatever e-commerce plugin is installed — designed against two implementations from day one so it cannot leak WooCommerce assumptions.

  • FW_POS_Store interface Done

    The abstract contract: find_by_sku(), set_stock(), adjust_stock(), create_order(), refund_order(), get_capabilities(). Deliberately written while sketching both the Woo and FluentCart implementations.

  • WooCommerce store driver Done

    The shipping implementation — stock writes through wc_update_product_stock(), order creation with the POS sale recorded as the payment method, refund restocking, variation-aware SKU lookup.

  • SKU / GTIN matcher + unmatched queue Done

    Matching is by SKU first, GTIN second, never by title. Items that match nothing land in an Unmatched queue for one-click mapping or product creation, instead of silently vanishing.

  • Capability negotiation Done

    Carts differ — not all support partial refunds or per-location stock. Drivers declare what they can do, and the ledger degrades gracefully instead of throwing.

Generic webhook APIDone

4/4

The headline feature. A documented, signed, normalized endpoint any POS — or any middleware, or a shop's own till software — can push to. No vendor SDK, nothing to break when someone else's API changes.

  • REST namespace unysonplus-pos/v1 Done

    POST /sale, POST /refund, POST /inventory, GET /ping. Fast acknowledgement (202 Accepted) with the event handed to the queue.

  • HMAC-SHA256 signing + replay window Done

    Every request carries X-UPOS-Signature and X-UPOS-Timestamp. Signatures are compared with hash_equals(), timestamps outside a ±5-minute window are rejected, and seen nonces are cached for the window length.

  • Normalized payload schema v1 Done

    A published, versioned JSON Schema for sale / refund / inventory payloads, validated on ingest so a malformed till can never corrupt the ledger.

  • Connections: keys, scopes, rotation Done

    Each till or integration is a named connection with its own key/secret pair, scopes, location mapping and last-seen timestamp. Rotate or revoke one without touching the others.

Virtual TerminalDone

3/3

The answer to “I don't own a POS machine.” A shipping feature that fires realistic synthetic events at your own endpoint — the development test rig, the customer's pre-launch check, and the demo for these docs, all at once.

  • Virtual Terminal screen Done

    Pick products, quantities and a location; fire a signed sale, refund or stock adjustment and watch it land in the log and move real stock.

  • Adversarial scenario presets Done

    One click each for the cases that break naive integrations: duplicate webhook, out-of-order offline batch, partial refund, unknown SKU, stock below zero, expired signature. These double as the automated test suite.

  • Signed request / cURL export Done

    Copy a correctly-signed example request for any scenario, so an integrator can reproduce it from their own till software or Postman.

Square driver (first-party)Done

5/5

One polished vendor driver, chosen for the best free sandbox and the largest small-retail footprint. Proves the provider seam without committing to five vendor APIs.

  • FW_POS_Provider interface Done

    The mirror of the store seam, on the POS side: connect, subscribe, normalize, backfill.

  • Square OAuth connect + token refresh Done

    Connect a Square account without pasting tokens; refresh before expiry and surface a clear re-connect prompt when a grant is revoked.

  • Webhook subscription + signature verification Done

    Subscribes to payment.created, order.updated, refund.created and inventory.count.updated, verifying Square's own signature before normalizing into the ledger.

  • Catalog import + mapping Done

    Pull the Square catalog, match to products by SKU, and present the unmatched remainder for mapping or creation.

  • Location → stock source mapping Done

    Map each Square location to a stock source, or run in explicit single-store mode. Never left implicit.

Reconciliation & operationsDone

4/4

The difference between a demo and something a shop can open the doors with. Assume events will be missed, and make drift visible and fixable.

  • Authority policy engine Done

    Per-field source of truth — the POS owns stock, the store owns content (title, description, images, SEO) — with a per-product override. Symmetric two-way sync with no declared authority is exactly what produces drift and oversells.

  • Nightly reconciliation sweep Done

    Compares POS counts against store stock, reports every divergence, and offers a one-click resync. Catches whatever the event stream dropped.

  • Timestamp-ordered application Done

    Offline tills dump batches late and out of order. Events are applied by event timestamp, not arrival order, so a late batch cannot rewind newer stock.

  • Health dashboard + alerts Done

    Queue depth, last event per connection, failure rate, and an email when a connection stalls or the queue backs up past a threshold.

Expansion (post-1.0)Done

4/4

Only once the seams have proven themselves against real installs. Deliberately not launch scope.

  • FluentCart store driver Done

    The genuine second target — WP-native, custom tables, no first-party POS story of its own.

  • SureCart / EDD drivers Done

    Cheap to add once the seam has been proven by a second real implementation.

  • Clover / Zettle / Lightspeed drivers Done

    Added on demonstrated demand, in that order. Each is bespoke and maintenance-heavy — the generic webhook already covers them adequately.

  • Offline CSV / batch importer Done

    For tills with no network integration at all: a scheduled CSV drop reconciled through the same ledger.

How this page stays current

This page is not hand-maintained, because hand-maintained roadmaps drift from reality within a month. Status is derived from the extension source tree.

src/data/pos-roadmap.json holds the milestones and, for each task, a detect block naming the files and code symbols that constitute "done":

{
"id": "m3-hmac",
"title": "HMAC-SHA256 signing + replay window",
"detail": "Every request carries `X-UPOS-Signature` …",
"detect": { "files": ["includes/rest/class-fw-pos-signature.php"] }
}

A detect block may name files (paths relative to the extension root) or symbols (a regex that must be found inside a given file — useful when a task adds a method to a file that already exists):

"detect": {
"symbols": [
{ "file": "includes/class-fw-pos-queue.php", "pattern": "occurred_at|order_by_timestamp" }
]
}

scripts/gen-pos-roadmap.mjs scans the real extension and rewrites each task's status:

ResultStatus
Every file and symbol foundDone
Some found, some missingIn progress
None foundPlanned

Run it after any work on the extension:

npm run roadmap:pos # scan and rewrite the JSON
npm run roadmap:pos -- --dry # report only, write nothing
npm run roadmap:pos -- --check # exit 1 if stale — for CI or a pre-push hook

It finds the extension automatically, trying POS_EXT_DIR, then the plugin working copy at unysonplus/framework/extensions/pos-sync, then the push clone. Point it somewhere else with:

POS_EXT_DIR=/path/to/pos-sync npm run roadmap:pos

:::tip Why detection rather than a checkbox A checkbox records an intention; a file on disk records a fact. Writing the detect block before the code also forces the file layout to be decided up front — so the roadmap doubles as the implementation's structural contract. When a task genuinely has no file footprint (research, a docs pass), set "pin": true on it and the script leaves its status alone. :::

Not on the roadmap

Worth stating explicitly, so nobody waits for them:

  • Card payment processing. POS Sync records what a till already sold. It never touches payment capture — that stays with the POS.
  • A WordPress-based till UI. Running the register itself is a different product with hardware, offline and PCI concerns of its own.
  • Accounting integration. Xero/QuickBooks sync belongs downstream of the ledger, not inside it.
  • A card-terminal UI in WordPress. Running the register itself is a different product.
  • Ecwid as a store driver. Ecwid is SaaS — its products and inventory live on Ecwid's servers, not in WordPress, and it ships its own POS integrations. Connecting a till straight to Ecwid is the better answer for that merchant; routing through WordPress adds a hop for nothing. (more)