=== Oliver POS – WooCommerce POS for iPhone, iPad & Android ===
Contributors: oliverpos
Tags: woocommerce, point of sale, pos, retail, inventory
Requires at least: 6.2
Tested up to: 7.0
Requires PHP: 8.1
Requires Plugins: woocommerce
WC requires at least: 8.0
WC tested up to: 9.8
Stable tag: 4.5.8
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

WooCommerce POS for iPhone, iPad & Android. Tap to Pay, Stripe Terminal, offline mode, multi-outlet stock — every WooCommerce payment gateway.

== Description ==

Oliver POS is the state-of-the-art point of sale for WooCommerce. More than 45,000 retailers around the world use Oliver POS to sell in-store, manage inventory across multiple outlets, and accept every payment method WooCommerce supports — all without ever leaving their existing WooCommerce shop.

https://www.youtube.com/watch?v=bUvbb8plbjw

There is only one database, one product catalogue and one source of truth: your WooCommerce store. Oliver POS adds a beautiful, touch-first register on top of it — for iPad, Mac, Android tablets and PC — and keeps everything in sync in real time. No middleware, no double entry, no exported CSVs.

= Real WooCommerce Integration, Not a Bolt-On =

Oliver POS speaks the official WooCommerce REST API. When you pair a device, the plugin mints a real WooCommerce `consumer_key` / `consumer_secret` for that station — the same API contract every other WooCommerce integration uses. Orders, refunds, products, inventory, customers and taxes all flow through `wp-json/wc/v3/*`. Your data stays portable, auditable and 100% inside your own WordPress install.

Full compatibility with WooCommerce HPOS (High-Performance Order Storage), the new Product Block Editor, and Cart & Checkout Blocks is declared and tested.

= Offline-First Sales =

When the internet drops, the line at your counter doesn't. Oliver POS keeps selling — every order, every line item, every payment captured by your cashier is queued locally on the device. The moment connectivity returns, the queue drains into WooCommerce in the exact order it was rung up. No lost sales, no manual reconciliation, no panic.

Refunds, customer lookups and live stock checks still require an online connection (because they touch live WooCommerce data), but the core "make the sale" flow is fully offline-capable.

= Multi-Outlet & Multi-Station Stock =

Run one store or fifty. Oliver POS gives each outlet its own stock level — synced back to WooCommerce as the global truth — and lets each station ring up sales independently with its own register number, receipt sequence and shift. Move stock between outlets, audit movements per location, and see live inventory across every store from your WooCommerce admin.

= Real-Time Sync Across Every Device =

Every sale, stock movement, refund and customer update fans out across every Oliver POS device — iPhone, iPad, Android tablet, countertop terminal and web dashboard — in real time, and lands as a standard WooCommerce record on your WordPress store within the same second. Your WooCommerce shop stays the single source of truth: there are no proprietary tables, no exported CSVs and no scheduled syncs to babysit. When the internet drops, every station keeps selling locally and the queue drains into WooCommerce in order the moment connectivity returns.

= Every WooCommerce Payment Gateway, In-Store =

If WooCommerce supports it, Oliver POS supports it. Cash, card, store credit, gift cards, integrated terminals — plus any WooCommerce payment gateway you've already configured: WooPayments, Stripe, PayPal, Klarna, Square, Mollie, Amazon Pay, Authorize.net and hundreds more. The cashier picks a gateway, the gateway's own checkout opens in a WebView on the POS device, and the customer pays through the exact same flow they'd use online. Apple Pay, Google Pay and other wallets work automatically through whichever wallet-enabled gateway you've already turned on.

For card-present payments, Oliver POS integrates directly with **Stripe Terminal**: pair a reader to an outlet and the amount due is pushed to the terminal at checkout, processed by Stripe, and recorded on the WooCommerce order — no double entry, no reconciliation drift.

= WooCommerce POS for iPhone, iPad, Android, Mac & PC =

Oliver POS ships native apps for iPhone, iPad and Android, plus a web register that runs in any modern browser on Mac, PC and Chromebook. Tap to Pay works on every modern iPhone (iOS 16.4+) and on supported Android phones — no extra card reader required. Touch, mouse, keyboard, camera and Bluetooth/USB barcode scanners are all first-class inputs. Use a Mac as your back-office register, an iPad on the counter, an iPhone for pop-ups and street markets, and the Oliver POS countertop terminal for high-volume lanes — all selling from the same WooCommerce shop.

= Works With the WooCommerce Plugins You Already Run =

Because Oliver POS reads and writes through the official WooCommerce REST API, your existing WooCommerce extensions keep working at the counter — including WooCommerce Subscriptions, Memberships, Bookings, Product Bundles, Points & Rewards, Gift Cards and WooPayments. No bespoke integration per plugin, no broken add-ons, no workflow change for your online customers. The themes, taxes, shipping rules, coupons and product types you already use on your WooCommerce shop apply in-store too.

= Real-Time Inventory, Reports & Staff Insights =

Sales, stock movements, payment summaries, tax reports and staff performance — all live, all sliced by outlet, register and shift. Oliver POS includes 15+ unique reports out of the box in Oliver Hub, plus staff permissions backed by real WordPress capabilities so each user only sees what they're entitled to.

= Free to Start, Paid to Scale =

Oliver POS offers a genuine Free plan — no credit card, no trial timer — so you can install, pair a device and ring up a real sale before you ever pay us. Paid tiers unlock multi-outlet, advanced reporting, integrated payments and priority support. Current pricing lives at [oliverpos.com/pricing](https://oliverpos.com/pricing?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=readme&utm_content=pricing).

= Hardware Built for Retail =

Bring your own iPad, Mac or PC, or buy the purpose-built [Oliver POS terminals](https://oliverpos.com/hardware?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=readme&utm_content=hardware) — integrated receipt printer, barcode scanner, cash drawer and card terminal in one box, running the Oliver POS Android app. Third-party thermal printers, barcode scanners and cash drawers also work out of the box.

= Support You Can Reach =

Email support@oliverpos.com and a human responds within one business day. We also run live chat from inside Oliver Hub and maintain a public help centre at [help.oliverpos.com](https://help.oliverpos.com/?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=readme&utm_content=help). Bug reports, feature requests and security disclosures are all welcome — see the Privacy & Security section below for how to reach our security team.

== Installation ==

1. Install Oliver POS from the WordPress plugin directory and activate it.
2. From the WordPress admin sidebar, open **Oliver POS** and click **Connect**.
3. Sign in or create your free Oliver POS account at app.oliverpos.com.
4. Your products, customers, orders and tax settings sync automatically.
5. Open the web register at sell.oliverpos.com, or pair the Oliver POS iPad / Android app using the on-screen pairing code. You're ready to sell.

The whole process takes less than three minutes on a typical shop.

== Frequently Asked Questions ==

= What do I need to run a WooCommerce POS in my store? =

A WordPress site with WooCommerce installed and configured — that's it. Your WooCommerce products, prices, inventory, customers, tax rates and currency are the single source of truth for Oliver POS, so set those up in WooCommerce before pairing your first device.

For hardware, any modern browser will run the Oliver POS web register. We recommend Chrome on Mac, PC or Android, and Safari on iPad. Receipt printing uses the device's default printer.

= Can a WooCommerce POS keep selling when the internet goes down? =

Yes. The "Allow offline orders" setting is enabled by default. When your device loses internet connectivity, Oliver POS keeps accepting sales — every order is queued on the device and syncs into WooCommerce in order the moment the connection comes back. Refunds, live stock checks and customer lookups require an online connection because they touch live WooCommerce data.

= Which payment gateways does Oliver POS support in-store? =

All of them. Any payment gateway you've enabled in **WooCommerce → Settings → Payments** can be turned on for in-store use in **Oliver POS → Payment Methods**. When the cashier picks that gateway at checkout, the gateway's own payment form opens in a WebView on the POS device. We've tested with WooPayments, Stripe, PayPal, Klarna, Square, Mollie, Amazon Pay and Authorize.net, but any properly-built WooCommerce gateway will work.

Oliver POS also has a first-class integration with **Stripe Terminal** for card-present payments — pair a reader to an outlet and the amount due is pushed automatically at checkout.

= Does Oliver POS support Apple Pay, Google Pay and Tap to Pay? =

Yes — through whichever wallet-enabled gateway you've already configured in WooCommerce. If your WooPayments or Stripe gateway has Apple Pay and Google Pay turned on, those wallets will appear on the POS WebView checkout exactly as they do on your online store.

= Can I run Oliver POS on iPhone, iPad and Android? =

Yes. Oliver POS ships native apps for iPhone, iPad and Android phones / tablets, and the web register also runs in Safari on iPad and in Chrome on Android. Tap to Pay on iPhone (iOS 16.4+) and Tap to Pay on supported Android phones let any modern phone accept contactless cards and wallets with no extra hardware. We also support iPad / iPhone-friendly Bluetooth barcode scanners, AirPrint receipt printers and the Stripe Terminal BBPOS WisePad 3.

= Does Oliver POS work for multi-outlet retailers and chains? =

Yes. Oliver POS is built around outlets — each physical store or pop-up gets its own stock levels, register sequence, payment methods, tax setup and timezone, while all rolling up to the same WooCommerce shop. Stock can be transferred between outlets, and reports can be filtered per outlet, per register or globally.

= How long does it take to get up and running? =

We've shipped Oliver POS to over 45,000 retailers, and the median setup is under three minutes:

1. Install and activate the plugin.
2. Click **Connect** and create your free Oliver POS account.
3. Open the web register or pair an iPad / Android device, and ring up your first sale.

= How much does a WooCommerce POS cost? Is there a free plan? =

Oliver POS offers a free plan with no credit card required. Paid tiers add multi-outlet, advanced reporting, integrated payments and priority support. See [current pricing](https://oliverpos.com/pricing?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=readme&utm_content=pricing-faq) on oliverpos.com.

= Does Oliver POS use the official WooCommerce REST API (no vendor lock-in)? =

Yes. When you pair a device, Oliver POS mints a real WooCommerce REST API key for that station and the device speaks `wp-json/wc/v3/*` directly. Your orders, products, refunds and customers are stored as standard WooCommerce records — no proprietary tables, no vendor lock-in. If you ever stop using Oliver POS, your data stays exactly where it is.

= Is Oliver POS GDPR and PCI compliant? =

Card data never touches the Oliver POS plugin or our servers. For integrated card payments via Stripe Terminal or any WooCommerce gateway, card information flows directly between the customer's card / terminal / browser and the gateway, keeping your shop's PCI scope to the SAQ-A or SAQ-A-EP minimum.

For GDPR, the plugin transmits only the data documented in the **External services** section below, never sells data, never runs third-party analytics, and removes all stored Oliver POS data cleanly on uninstall.

= Can I print receipts with Oliver POS? =

Yes — thermal, inkjet, laser and Bluetooth receipt printers all work. The Oliver POS terminals ship with an integrated thermal printer that auto-prints after every checkout. Other setups print to whatever printer is connected as the device default. Email and SMS receipts are also supported.

= All my products have barcodes — will Oliver POS scan them? =

Yes. Any USB or Bluetooth barcode scanner that emulates a keyboard works out of the box. Add the barcode as the SKU or to a custom field in WooCommerce, and Oliver POS will look it up at the speed of the scanner. You can also scan barcodes with the device's built-in camera on iPhone, iPad and Android — no extra hardware required.

= Can I accept card payments on my iPhone without a card reader? =

Yes. Oliver POS supports **Tap to Pay on iPhone** (iOS 16.4+) and **Tap to Pay on Android** through our integration with Stripe Terminal. Tap a contactless card, Apple Pay or Google Pay against the back of the phone, the charge runs through your own Stripe account, and the completed payment is recorded on the WooCommerce order — no extra reader, no double entry, no reconciliation drift.

= Is Oliver POS compatible with WooCommerce Subscriptions, Bookings, Gift Cards and Memberships? =

Yes. Because Oliver POS reads and writes through the official WooCommerce REST API, your existing WooCommerce extensions keep working at the counter — including WooCommerce Subscriptions, Memberships, Bookings, Product Bundles, Points & Rewards, Gift Cards and WooPayments. There is no bespoke integration to install per plugin, and the customer experience on your online store stays unchanged.

== Screenshots ==

1. Oliver POS register — touch-first WooCommerce POS for iPhone, iPad, Android and web.
2. Adding a WooCommerce variable product to the cart in Oliver POS.
3. Customer view — every WooCommerce customer, full order history and store credit, in one place.
4. Payments view — Tap to Pay, Stripe Terminal, cash, store credit and every WooCommerce payment gateway.
5. Activity view — search, filter and refund WooCommerce orders from any Oliver POS outlet or station.
6. Sales summary — 15+ live reports for in-store revenue, payments, taxes and staff performance.
7. Staff and shifts — PIN overrides, permissions and per-employee sales insights in Oliver Hub.
8. Payment methods — turn on any WooCommerce gateway for in-store use, per outlet.

== Privacy & Security ==

Oliver POS is designed so that the merchant — not Oliver POS — is the data controller, and card data never enters our systems.

* **Card data path.** For Stripe Terminal and every WooCommerce payment gateway, card information flows directly between the customer's terminal / browser and the gateway. The Oliver POS plugin and our backend never see card numbers, CVVs or full PANs.
* **Device pairing uses WordPress Application Passwords.** The 4.3.0 "Connect with site URL" flow asks the merchant to approve a single Application Password prompt in wp-admin. Devices authenticate with that password against the standard WordPress REST API — no shared secrets, no master credentials.
* **No analytics, advertising, or telemetry.** The plugin does not transmit shop data to any third party for analytics, advertising or telemetry. The only outbound calls are documented in **External services** below.
* **Clean uninstall.** Removing the plugin deletes every option, custom table and meta key Oliver POS created. Your WooCommerce orders, products and customers are untouched.
* **Idempotent device pairing.** The bootstrap pairing endpoint is keyed on the WordPress user and the device's UUID, so a re-run of the pairing flow from the same device (for example, recovering from a network drop mid-pair) returns the same `consumer_key` / `consumer_secret` rather than orphaning the previous WooCommerce REST API key. The key is gated by `manage_woocommerce`, remains revocable by the merchant in **WooCommerce → Settings → Advanced → REST API**, and the plugin explicitly rejects pairings whose key was revoked between attempts.
* **Security disclosure.** Report vulnerabilities to security@oliverpos.com. We acknowledge within one business day.

== External services ==

This plugin connects to a small number of external services so that the in-store register, the merchant subscription and integrated card payments can work. The plugin does not transmit shop data to any third party for analytics, advertising or telemetry.

= Oliver POS backend (phoenix.oliverpos.com) =

The plugin contacts the Oliver POS backend to register the merchant's site, manage their Oliver POS subscription, mint short-lived authentication tokens, link a Stripe Connect account for integrated card payments, and synchronise Stripe Terminal locations when outlets are created or deleted.

What is sent: the WordPress site URL, the merchant's WordPress user ID and email when they sign in to Oliver POS, the outlet IDs and addresses configured in this plugin, and the Stripe account ID once the merchant connects one. Card data is never handled by this plugin or by `phoenix.oliverpos.com`; it goes directly between the merchant's browser / terminal and Stripe.

When it is sent: only after the merchant explicitly opts in by clicking **"Create your free Oliver POS account"** on the Billing page or on the first-run Dashboard onboarding panel (or **"Connect with Stripe"** on the Oliver Pay page), after which the page also fetches the current subscription status, and on subsequent merchant actions: clicking "Manage your current plan" on Billing, pasting an OPAY-XXXX-XXXX-XXXX activation code into the Billing page (or following the marketing-site deep link `?claim=OPAY-…`), adding / editing / deleting an outlet, running the "Sync Terminal Locations" / "Refresh status" / "Test connection" / "Refresh plan from Stripe" actions, or when a paired Oliver POS device requests a Phoenix pairing code via `POST /oliver-pos/v1/devices/phoenix-pair-code` (which the plugin proxies through to `/api/devices/pairing-codes`). Plan selection itself happens on [oliverpos.com/pricing](https://oliverpos.com/pricing), not inside wp-admin. No outbound call is made on plugin activation, on `plugins_loaded`, on `init`, on the Oliver POS Dashboard / Settings / Reports / Staff / Outlets / Receipts admin pages (the Dashboard onboarding panel only transmits when the explicit "Create your free Oliver POS account" button is clicked — its disclosure expander lists exactly what gets sent), or on any frontend request.

Endpoints used: `/api/auth/token`, `/api/auth/register`, `/api/auth/claim`, `/api/subscriptions/current`, `/api/subscriptions/manage-link`, `/api/subscriptions/resync`, `/api/connect/status`, `/api/connect/accounts`, `/api/connect/sessions`, `/api/terminal/locations`, `/api/terminal/locations/sync`, `/api/devices/pairing-codes`.

Service: [Oliver POS](https://oliverpos.com) — see [Terms of Service](https://oliverpos.com/terms) and [Privacy Policy](https://oliverpos.com/privacy).

**Switching backends (developers / QA).** The active backend is selected by the `PHOENIX_ENV` constant in `wp-config.php` and defaults to production. Setting `define( 'PHOENIX_ENV', 'staging' );` routes every request to `phoenix-api-git-staging-oliver-pos.vercel.app` (Stripe test mode, separate Neon database). The staging deployment sits behind Vercel Deployment Protection, so the matching `define( 'PHOENIX_STAGING_BYPASS_TOKEN', '<token>' );` constant is also required — without it the plugin returns an inline diagnostic instead of attempting any call. Both constants are intentionally `wp-config.php`-only (never exposed in wp-admin) so a misconfigured site cannot route real Stripe traffic to the staging environment. The staging deployment is intended for Oliver POS internal QA only and is not used by production sites.

**Local-dev forwarding (advanced).** Setting `define( 'PHOENIX_BASE_URL_OVERRIDE', 'https://your-tunnel.example' );` overrides the resolved base URL for both production and staging, which is useful when developing Phoenix locally behind ngrok or a similar reverse proxy. Leave this constant undefined on every other install.

**End-to-end smoke tests (Oliver POS developers / QA only).** The plugin's public source repository ships a `dev/` folder with `wp eval-file`-style smoke scripts that exercise the Phoenix code paths end-to-end against the active environment — a destructive `staging-smoke.php` for staging, and a read-only `prod-smoke.php` for diagnosing real merchants. These scripts are intentionally **not** included in the version of the plugin distributed through WordPress.org. To run them, check out the source from the [public GitHub repository](https://github.com/oliverpos/oliver-pos) and follow `dev/README.md`. They are not needed (and not loadable) on a normal merchant install.

**Stripe webhook URL gotcha (staging only).** Stripe sends webhooks server-to-server, so the test-mode webhook endpoint configured in dashboard.stripe.com must include the Vercel Deployment Protection bypass token as a query parameter — `https://phoenix-api-git-staging-oliver-pos.vercel.app/api/webhooks/stripe?x-vercel-protection-bypass=<token>` — otherwise every delivery returns Vercel's SSO login HTML and Phoenix's handler never runs. This is a Phoenix / DevOps configuration concern (the plugin doesn't receive webhooks itself), but a missing bypass on the webhook URL is the most common reason staging subscriptions appear stuck after Checkout. The plugin's "Refresh plan from Stripe" button on the Billing admin page is the per-merchant recovery path when this happens — it forces a pull-through reconcile from Stripe via `/api/subscriptions/resync`.

= Stripe Connect embedded UI (connect-js.stripe.com) =

The plugin loads `https://connect-js.stripe.com/connect-js.umd.js` on the Oliver Pay admin page so the merchant can complete Stripe Connect onboarding and review their payouts inside wp-admin without leaving the site. This script is hosted by Stripe and cannot be self-hosted (Stripe's terms require the live URL).

What is sent: the Stripe-issued client secret minted by `phoenix.oliverpos.com` for the current admin session. No shop data is sent directly from this plugin to Stripe; everything flows through the Stripe-hosted UI under the merchant's own Stripe account.

When it is sent: only when the merchant opens the **Oliver POS → Oliver Pay** admin page.

Service: [Stripe](https://stripe.com) — see [Stripe Services Agreement](https://stripe.com/legal/ssa) and [Stripe Privacy Policy](https://stripe.com/privacy).

== Changelog ==

= 4.5.8 - 2026-05-22 =
* Billing dashboard iteration on top of 4.5.7 — `Billing` core rework, `Billing_Admin` polish, JS/CSS refinements, and additional `Pay_API_Client` resilience. New `BillingClaimTest` unit coverage.
* First-run UX: promoted the **"Create your free Oliver POS account"** CTA from the Billing page into the existing Dashboard onboarding panel so it's the first thing a merchant sees after activation, with conversion copy ("Free forever · No credit card · Instant · Start selling") and a one-click flow. The Billing-page CTA stays as a fallback surface.
* Still a single explicit, disclosed click — WordPress.org Plugin Directory guideline 7 (no "phoning home" without informed consent) preserved. Activation continues to make zero outbound calls; the new card carries the same expandable "What gets sent when you click" disclosure as the Billing CTA, listing site URL, site name, and admin email before any request goes out.
* No new endpoints. The new Dashboard button reuses the existing `wp_ajax_oliver_pos_billing_register` handler (nonce `oliver_pos_billing`), so the throttle, error mapping, and Phoenix register path are a single source of truth across both surfaces.
* Hero subhead on the Dashboard panel updated to reflect the new step. When `Pay_API_Client::is_registered()` is already true (e.g. the merchant registered from Billing first), the card renders in a compact "Account created" state in place so returning merchants see continuity rather than a missing step.

= 4.5.7 - 2026-05-21 =
* Phoenix device pairing bridge, plan-tier gating, `/meta` wire-shape fix. Staff admin form values preserved on validation errors and the user dropdown is broadened.

= 4.5.6 - 2026-05-20 =
* Fresh-install UX fix: outlet stock is now seeded from WooCommerce's existing `_stock` on activation, so a single-outlet store sees real inventory the moment a device pairs. Previously every product showed "0 in stock" until the merchant manually entered per-outlet quantities — busy-work that defeated the "install → pair → start ringing" promise.
* Implemented as a single bulk `INSERT … SELECT` in the new `Activator::maybe_seed_outlet_stock_from_woo()` so it stays fast on 50k+ SKU catalogs. Only products / variations with `_manage_stock = 'yes'` and `post_status = 'publish'` are seeded; unmanaged products (which rely on `_stock_status` alone) are deliberately skipped so they don't suddenly look out-of-stock on the device.
* Four belt-and-braces guards: the seed is a no-op when already run (one-shot `oliver_pos_outlet_stock_seeded` option), when the install has more than one active outlet (multi-outlet stores must decide the split themselves), when the `wp_oliver_outlet_stock` table already has any rows, or when legacy `_oliver_stock_{outlet_id}` postmeta exists. The seeder will never overwrite a value the merchant has typed in.
* Self-heal for existing installs: the same routine runs from `Activator::maybe_upgrade()` after the outlet has been verified, so a merchant who upgraded from 4.5.5 (or earlier) without ever pairing a device picks up the mirror automatically on the next admin page load.
* Eight new PHPUnit cases in `tests/test-outlet-stock-seed.php` cover the mirror path (simple products, variations, negative stock, empty catalog) and every guard branch, plus a re-run idempotency check.

= 4.5.5 - 2026-05-20 =
* Security: removed `wp_set_current_user( $customer_id )` from `Coupon_Rest::init_cart_context()` (POST `/oliver-pos/v1/coupons/validate`). The endpoint no longer impersonates the request's `customer_id` while running the coupon validation pipeline, closing an authorization-bypass primitive flagged by the WordPress.org plugin review team. `WC()->customer = new WC_Customer( $customer_id )` is still set, so per-user usage limits, `customer_email` restrictions, billing address, and tax location continue to evaluate against the right customer; role / capability-restricted coupons (e.g. "wholesale-only") will now correctly require the customer's own session.
* Security: removed `wp_set_current_user( $order->get_customer_id() )` from `POS_Payment_Page::set_customer_context()` on the POS pay-for-order page (`?oliver_pos_pay=1&...&key=ORDER_KEY`) as the same defensive sweep. `WC()->customer` is still scoped to the order's customer so billing and tax context are unchanged.
* New `oliver_pos_payment_customer_id` filter and `POS_Payment_Page::resolve_payment_customer_id()` static helper give third-party balance-based gateways (store credit, gift card, wallet) a stable hook for resolving the in-flight POS customer without calling `get_current_user_id()`. See `docs/balance-gateway-migration.md` in the development repository for the worked migration example and POS-app test checklist.
* The only remaining `wp_set_current_user()` call sites in the codebase are now inside `tests/` (PHPUnit auth setup) — nothing in shipping code.

= 4.5.4 - 2026-05-20 =
* WordPress.org submission pass — privacy, security, and Plugin Check cleanup before the directory listing goes live.
* Privacy / "phone home" hardening (Plugin Directory guideline 7): the silent `current_screen` auto-register on the **Billing** and **Oliver Pay** admin pages is gone. Opening either screen on a fresh install now makes ZERO outbound calls to `phoenix.oliverpos.com`; the merchant has to click the new **"Create your free Oliver POS account"** CTA on the Billing screen (or the existing **"Connect with Stripe"** CTA on the Oliver Pay screen) for the site URL + admin email to be transmitted, and the Billing CTA lists exactly what gets sent before the click.
* Removed the `Plan_Badge` live-Phoenix injection — tier badges on the Dashboard / Settings / Reports / Staff / Outlets / Receipts admin pages now read the persisted `oliver_pos_subscription_plan` option only and never trigger a Phoenix call. The persisted option is still refreshed by the Billing screen on every successful read.
* Security: `GET /oliver-pos/v1/staff` now omits the legacy `pin_hash` field **by default** (the `oliver_pos_emit_pin_hash` option flips to `0`). The `Deprecation` / `Sunset` headers stay; the field is removed entirely in 4.6.0. Site owners running an older paired Oliver POS app build that hasn't been updated yet can re-enable it temporarily with `wp option update oliver_pos_emit_pin_hash 1`.
* Security: receipt templates posted to `oliver_pos_save_template` are now fully sanitized per-field in `Receipt_Templates::sanitize_template()` — section type, alignment, paper width, every section-config scalar, and the styling block — before being persisted to `wp_options`. Unknown section types and non-scalar config values are dropped.
* Plugin Check / PCP cleanups: `wp_unslash()` added around `$_POST` integers in product-fields meta saves, admin image preview now builds the `<img>` via DOM rather than HTML string concatenation, and the receipt-config / receipt-template AJAX handlers `wp_unslash` the `$_POST` fallback path.
* Translation template regenerated against the 4.5.3 plugin-name / description rebrand; SVN `/assets/` screenshot set trimmed to match the 8 captions in the readme (wp.org displays at most 10).
* External-services disclosure refreshed to reflect the explicit-CTA flow and to list the previously-undocumented `/api/subscriptions/pricing-table-config` and `/api/subscriptions/plans` endpoints.
* Verified against WordPress 7.0 (released May 20, 2026). No blocks, no iframed-editor surfaces, no AI Client / Connectors / Abilities API consumers in the plugin, and `Requires PHP: 8.1` is already above WP 7.0's new floor (7.4). `Tested up to: 7.0` in readme.txt.
* No app-team contract changes other than the `pin_hash` default flip; see `docs/handover-2026-05-staff-pin-online-verify.md` for the rollout plan.

= 4.5.3 - 2026-05-19 =
* Readme / SEO refresh — title, short description, plugin-header description and tags realigned with the new oliverpos.com positioning (iPhone, iPad, Android, Tap to Pay, Stripe Terminal). Swapped the `ipad` tag for the higher-traffic `pos` slug; iPad coverage stays in the title and short description.
* Two new readme sections — "Real-Time Sync Across Every Device" and "Works With the WooCommerce Plugins You Already Run" (named compatibility for Subscriptions, Memberships, Bookings, Product Bundles, Points & Rewards, Gift Cards and WooPayments).
* Devices section reworked to lead with native iPhone / iPad / Android apps and Tap to Pay; web register for Mac / PC / Chromebook called out explicitly.
* Two new FAQs covering Tap to Pay on iPhone / Android (no extra reader required) and compatibility with the most-used WooCommerce extensions.
* Rewrote all eight screenshot captions for keyword density and image-search clarity. No code changes in this release.

= 4.5.2 - 2026-05-19 =
* WordPress.org pre-submission pass — bundles unminified upstream sources for the vendored Chart.js / qrcode-generator builds, ships the full GPL-2.0 license text, regenerates the translation template, and refreshes the readme for the 6.9 release window.
* External-service disclosure for `js.stripe.com/v3/pricing-table.js` added, and the script is now enqueued from PHP on the Billing admin screen instead of being injected at runtime by JavaScript.
* Security hardening: `POST /stations/{id}/activate` rejects station-key requests whose `{id}` does not match the authenticating key's bound station (returns `403 oliver_pos_station_mismatch`).
* PIN-hash deprecation begins: `GET /staff` adds a `pin_hash_deprecated: true` marker plus `Deprecation` / `Warning` response headers. A new `oliver_pos_emit_pin_hash` option (default `true`) lets sites flip to the v4.6 behaviour early, in which case the per-row `pin_hash` field is omitted entirely.
* New transient-backed rate limiter on `POST /bootstrap`, `GET /bootstrap/preview`, `POST /staff/verify-pin`, and `POST /staff/me/pin` (5 failures per 15 minutes per IP+user). Throttled requests return `429 oliver_pos_rate_limited` with a `Retry-After` header.
* HPOS-aware money-cents backfill — the migration now finds POS orders regardless of whether WooCommerce is using HPOS, fixing a stuck `oliver_pos_money_cents_migrated` flag on HPOS-only stores.
* Dashboard "View All" link routes to `admin.php?page=wc-orders` when HPOS is active instead of the legacy `edit.php?post_type=shop_order` URL (which is empty under HPOS).
* Removed two unconditional `console.error` calls from the Billing admin JS; diagnostics now gate behind `?oliver_debug=1`.

= 4.5.1 - 2026-05-18 =
* Iteration on the 4.5.0 staging readiness — refinements to `Pay_API_Client` (auth retry path), billing dashboard JS/CSS polish, `Billing_Service` rework, `Activator` cleanup, and a full rewrite of the Billing service unit-test suite.

= 4.5.0 - 2026-05-18 =
* Phoenix staging readiness — Oliver Pay admin now shows an env pill (yellow "Phoenix: STAGING (test mode)" on staging, neutral on production) and a "Test connection" button that round-trips through `/api/subscriptions/plans` against the active Phoenix environment. Staging mode is enabled by adding `define( 'PHOENIX_ENV', 'staging' )` and `define( 'PHOENIX_STAGING_BYPASS_TOKEN', '...' )` to wp-config.php; production is the default.
* New `dev/staging-smoke.php` script (`wp eval-file`) runs 13 assertions end-to-end against staging Phoenix including Connect account creation, Terminal location sync, and the §9.11 cross-env credential isolation check.

= 4.4.2 - 2026-05-16 =
* Maintenance redeploy — re-runs the version-aware upgrade routine and refreshes plugin files on disk

= 4.4.1 - 2026-05-15 =
* Maintenance redeploy — picks up the latest plugin name / description refresh used in the WordPress.org listing alongside the 4.4.0 billing dashboard overhaul.

= 4.4.0 - 2026-05-15 =
* Billing dashboard overhaul — full plan cards with tier accents, dedicated lifetime card, non-blocking status banners for `past_due` / `canceled` / `unpaid`, feature chips, and an embedded Phoenix-hosted pricing table section.
* New helpers in `Billing_Admin` for plan/tier/state resolution, currency-aware price formatting, billing-period suffixes, and feature-chip humanisation.
* `Billing_Service` and `Pay_API_Client` refinements; Phoenix call retry path tightened.
* `Activator::maybe_upgrade()` extended for the new billing data shape.
* Order feed and sync generator hardening; uninstall cleanup expanded.
* Updated meta-endpoint test coverage.

= 4.3.0 - 2026-05-14 =
* New `GET /wp-json/oliver-pos/v1/bootstrap/preview` and `POST /wp-json/oliver-pos/v1/bootstrap` REST endpoints power the "Connect with site URL" reverse-pairing flow shipped in the Oliver POS app. The merchant types only their WordPress URL on the device, approves a one-page wp-admin Application Password prompt, and the app self-configures.
* Both routes use HTTP Basic Auth (Application Password preferred, real WP password as fallback) and require `manage_woocommerce`. The mutating `POST /bootstrap` mints a WooCommerce REST API key, optionally creates a new outlet, registers a station bound to the device, and returns a `store_connection` payload.
* Bootstrap is fully idempotent on `(user_id, device_uuid)` — a re-run after a mid-pair crash returns the same key / secret instead of orphaning a duplicate WooCommerce API key.
* Stations table gains `device_uuid`, `platform`, `user_id`, and `consumer_key` columns plus a `UNIQUE KEY (user_id, device_uuid)` for idempotency lookups.
* One-shot wp-admin success notice is shown to the bootstrapping user on the next admin page load, then auto-clears.
* Readme rewrite — title, tags, short description and body all refreshed; every external link points directly at oliverpos.com (no redirect shorteners); new Privacy & Security section added.

= 4.2.0 - 2026-05-14 =
* Billing admin rewrite — replaced the legacy 3-state Billing page with a single Phoenix-driven dashboard that shows the current plan badge, a "Manage Subscription" button (Stripe Customer Portal) and a "View Plans" button (Phoenix-hosted Stripe pricing table). Lifetime customers see a "Lifetime" tag and the View Plans button is hidden.
* New WP REST routes under `/wp-json/oliver-pos/v1/billing/` (`current`, `manage-link`, `pricing-link`) proxy Phoenix from the server. The Phoenix API key and JWT never leave the server.
* Five-minute transient cache on `/billing/current`. The cache is invalidated automatically when the merchant returns from the Stripe portal so the badge picks up the new plan immediately.
* New status banner on the dashboard for `past_due` / `canceled` / `unpaid` subscriptions. Non-blocking.
* Removed the legacy `wp_ajax_oliver_pos_billing_*` AJAX handlers. The Stripe Pricing Table now lives on Phoenix.

= 4.1.0 - 2026-05-14 =
* Added `GET /wp-json/oliver-pos/v1/meta` REST endpoint for the Oliver POS app's tax / cash / gateway configuration. Replaces the static `wp-content/uploads/oliver-pos/sync/meta.json` file.
* Tax `rate` is now serialised as a 4-decimal string (e.g. `"15.0000"`).
* Tax classes with no resolved rates for an outlet are omitted from `outlet_rates` instead of being emitted as empty arrays.
* New `tax.cart_discount_taxes_subtotal` field mirrors `prices_include_tax` so the device can apply the correct cart-level discount semantics.
* The cached meta payload now refreshes synchronously when a WC tax rate, outlet address, or `woocommerce_calc_taxes` toggle changes.

For the full release history including the 4.0.x rewrite and the legacy 2.x changelog, see [oliverpos.com/changelog](https://oliverpos.com/changelog?utm_source=wordpress.org&utm_medium=plugin&utm_campaign=readme&utm_content=changelog) or the `CHANGELOG.md` in the public development repository.

== Upgrade Notice ==

= 4.5.8 =
Billing dashboard iteration on top of 4.5.7 — billing core rework, admin polish, additional Pay client resilience, plus a first-run "Create your free Oliver POS account" CTA promoted into the Dashboard onboarding panel (single explicit click, same disclosure as the Billing CTA — no silent phone-home).

= 4.5.7 =
Phoenix device pairing bridge, plan-tier gating, `/meta` wire-shape fix.

= 4.5.6 =
Fresh-install UX fix: outlet stock is now seeded from WooCommerce's existing `_stock` on activation, so single-outlet stores see real inventory the moment they pair a device — no more "0 in stock everywhere" until manual per-outlet entry. Self-heals on existing single-outlet installs that never paired. Multi-outlet shops and merchants who have already typed in per-outlet stock are unaffected.

= 4.5.5 =
Security: removes wp_set_current_user impersonation from POST /oliver-pos/v1/coupons/validate and the POS pay-for-order page. Cashier session can no longer impersonate other users during coupon evaluation. WC()->customer still scoped to customer for billing/tax.

= 4.5.4 =
Submission pass + WP 7.0 ready. Explicit "Create your free Oliver POS account" CTA replaces silent auto-register on Billing and Oliver Pay (no phoenix.oliverpos.com calls on fresh install). Receipt templates fully sanitized per-field on save. GET /staff omits deprecated pin_hash by default.

= 4.5.3 =
Readme / SEO refresh — title, short description and tags realigned with the new oliverpos.com positioning (iPhone, iPad, Android, Tap to Pay, Stripe Terminal), plus two new sections (Real-Time Sync, WooCommerce extension compatibility) and two new FAQs. No code changes.

= 4.5.2 =
WordPress.org pre-submission pass — full GPL-2.0 text, regenerated translation template, External-services disclosure for the Stripe pricing table, HPOS-aware money backfill / dashboard link, rate-limited pairing and PIN endpoints, and a tightened station-activation check. The PIN-hash field exposed by `GET /staff` is deprecated and will be removed in 4.6.0; the field is still emitted in 4.5.2 for app compatibility.

= 4.5.1 =
Iteration on 4.5.0 — billing dashboard polish, `Pay_API_Client` auth retry refinements, and `Billing_Service` test rework. Staging Phoenix wiring unchanged.

= 4.5.0 =
Phoenix staging readiness — adds an env pill and Test connection button on the Oliver Pay admin, plus a `dev/staging-smoke.php` script for the full 13-assertion staging round-trip. Staging is opt-in via `PHOENIX_ENV` in wp-config.php; production remains the default.

= 4.4.2 =
Maintenance redeploy — re-runs the version-aware upgrade routine.

= 4.4.1 =
Maintenance redeploy — picks up the WordPress.org listing's refreshed plugin name and description on top of the 4.4.0 billing dashboard overhaul.

= 4.4.0 =
Billing dashboard overhaul — full plan cards with tier accents, lifetime support, non-blocking status banners (`past_due` / `canceled` / `unpaid`), feature chips, embedded pricing table section, and refined Phoenix-proxied REST flow. Activator and order-feed/sync hardening included.

= 4.3.0 =
Adds the `/wp-json/oliver-pos/v1/bootstrap` and `/bootstrap/preview` endpoints used by the app's "Connect with site URL" flow. Existing QR / paste-key pairings continue to work unchanged. The stations table is upgraded automatically on the first admin page load after activation.

= 4.1.0 =
Adds a dedicated `/wp-json/oliver-pos/v1/meta` REST endpoint. Tax `rate` becomes a 4-decimal string and a new `cart_discount_taxes_subtotal` field is included. Requires Oliver POS app build 2026.05+ — older builds parse `rate` as a number and will misread the response.
