=== CartProof Connector ===
Contributors: hexcorp
Tags: woocommerce, updates, checkout, testing, staging
Requires at least: 6.4
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 1.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Connects WooCommerce to CartProof, which tests pending updates against a real checkout in an isolated sandbox before they reach your store.

== Description ==

CartProof Connector links your WooCommerce store to the CartProof service (https://hexcorp.io/products/cartproof/). **The plugin does nothing on its own until you pair it with a CartProof account.** Unpaired, it only adds a settings screen under WooCommerce → CartProof. Every feature below needs a paired account, including the free tier.

What each CartProof plan does, once paired:

* **Checkout Monitor (free CartProof account).** Once a day, at a fixed time CartProof assigns to your store, the connector checks your checkout from your own server: the checkout page with an item in a temporary cart, the WooCommerce Store API cart, and PHP fatal errors since the last check. It sends the result and the list of pending updates to CartProof, which emails you when checkout stops working and sends a daily digest when updates are waiting. No order is placed, nothing runs in a sandbox and nothing is changed on your site. When updates are waiting on a free account, one notice on the CartProof, Plugins and Updates screens points to the paid plans; it can be dismissed for 30 days and appears nowhere else.
* **Store / Store Pro / Agency (paid plans).** CartProof takes a PII-anonymized copy of your store into an isolated sandbox. It places test orders before and after applying your pending plugin, theme, core and translation updates, compares the results, and tells you whether it is safe to update and which update broke checkout. On plans that include it, CartProof then applies the tested updates to your live store for you, but only after the connector has created and verified a restore point here. Store Pro and Agency can also use optional, opt-in **Canary Orders**: one hidden $0 test product that CartProof uses for a final live check after an update. Canary orders are cancelled and deleted automatically.

The owner decides everything in the CartProof dashboard: which stores are connected, when tests run, and whether updates are applied automatically, after approval, or never. The only switches in wp-admin are local ones: polling-only delivery, allowing Canary Orders on this store, and deleting restore points.

Customer data is anonymized **on your server, while it is exported**. Names, emails, addresses, phone numbers, IP addresses, payment tokens and similar fields are replaced or removed before any byte is uploaded. See "External services" for exactly what is sent and when.

Requires WooCommerce 8.0 or newer. Multisite is not supported in this version.

== Installation ==

1. Install and activate WooCommerce, then this plugin.
2. Create a CartProof account at https://cartproof.hexcorp.io (the free Checkout Monitor needs an account too) and add your store in the dashboard. The dashboard shows an 8-word pairing code, valid for 90 seconds.
3. In wp-admin, go to WooCommerce → CartProof, paste the code and click **Pair with CartProof**. Pairing means you accept the CartProof Terms of Service and Privacy Policy, which are linked under the button.
4. Your `wp-config.php` must define unique `AUTH_KEY` and `SECURE_AUTH_KEY` values. CartProof encrypts its keys with them and refuses to pair while they are missing or still the sample placeholder.
5. If a firewall (Cloudflare, Wordfence, Sucuri …) protects your REST API, the settings screen shows the allowlist entry to add. Without it, the connector works in polling mode, with up to 5 minutes of latency.

To disconnect, click **Disconnect** on the same screen. The keys and schedules are removed and CartProof is told to revoke them.

== External services ==

This plugin connects to the CartProof service and to the services listed below. **Before pairing, the plugin makes no request to any external server.** The only request it makes unpaired is a loopback request to your own site (see the last entry).

CartProof Terms of Service: https://hexcorp.io/terms/
CartProof Privacy Policy: https://hexcorp.io/privacy/

= 1. CartProof API (https://api.cartproof.hexcorp.io/v1) =

The CartProof service itself. All requests are HMAC-signed, and CartProof's requests to your site are verified the same way.

* **When you pair** (you click "Pair with CartProof"): the pairing code, site URL, home URL, REST URL, whether pretty permalinks are on, and the connector, PHP, WordPress and WooCommerce versions.
* **Every 5 minutes while paired** (heartbeat, through WP-Cron or Action Scheduler; every 15 minutes in the WP-Cron fallback): store id, connector/WordPress/WooCommerce/PHP/database versions, a hash of the plugin list, the number of pending core/plugin/theme/translation updates, checkout health (the last checkout HTTP status, whether a PHP fatal error occurred in the last 15 minutes, whether the maintenance gate is on), and environment hints: how CartProof reaches the store (Action Scheduler present, loopback works, a firewall or Cloudflare detected, REST restricted), whether restore points sit in the web root on a server that ignores `.htaccess`, and whether `wp-config.php` defines real security keys. The heartbeat also picks up queued work in polling mode.
* **With the heartbeat, when there is something new:** the connector's own audit log (action names such as `apply.start`, the result, the REST path and small metadata such as counts and versions; never request bodies or secrets), and store events: a PHP fatal error (its message, file and line), a plugin activated or deactivated, a theme switched, a WordPress automatic update, WooCommerce deactivated (plugin and theme names and versions only). At most a few hundred entries per heartbeat; anything not delivered is retried on the next one.
* **Once a day while paired** (Checkout Monitor, at a time CartProof assigns): a request for that time, then the check result (the same health fields as below, including the last PHP fatal error message) and the pending core, plugin and theme updates (names, installed and new versions, whether they come from wordpress.org).
* **When CartProof requests an inventory** (before a test run, or when the heartbeat hash changes): the software inventory, meaning active and inactive plugins, must-use plugins and drop-ins (names, versions, update info), the active theme, the database table prefix, server software, OS family, locale, timezone, and the checkout-related WooCommerce settings (gateway ids, titles and test-mode state, shipping zones and methods, tax and coupon switches, currency, guest checkout, HPOS, block or classic cart/checkout and their page ids, product counts by type). It contains no customer data and no secret values.
* **During a test run, apply, rollback or canary check:** job status and results (counts, versions, checksums, error codes).
* **When you disconnect:** one signed notice so CartProof revokes the keys.

= 2. CartProof object storage (Cloudflare R2, pre-signed HTTPS URLs) =

CartProof stores test material in Cloudflare R2. The connector never holds storage credentials. It uploads to and downloads from single-use, time-limited, pre-signed URLs that the CartProof API issues for one object. Cloudflare terms: https://www.cloudflare.com/terms/ · Cloudflare privacy policy: https://www.cloudflare.com/privacypolicy/

* **When a test run you scheduled (or your plan's schedule) starts:** an **anonymized** export of your database, plus the plugin, theme and referenced media files needed to rebuild the store in the sandbox, uploaded in parts. Anonymization happens inside the connector before upload. A signed manifest describes the parts.
* **When CartProof tests an update whose package is not public** (for example a premium plugin licensed to your site): the connector downloads that package on your server, using your site's own license through the plugin's normal update mechanism, and **uploads the plugin or theme zip to CartProof storage** (in one request up to 8 MB, in 8 MB parts above that) so the exact same package can be tested. CartProof never asks for or stores your license keys.
* **When tested updates are applied to your store:** the connector downloads each package from CartProof storage and installs it only if its SHA-256 matches the package that passed the tests.

= 3. Plugin and theme update servers =

When CartProof asks the connector to fetch a pending update, the connector uses WordPress's own updater download for that plugin or theme. That means wordpress.org for directory plugins, or the vendor's update server for premium ones. These are the same requests WordPress makes when you click "Update now", including any license headers the vendor's plugin adds. The connector does not send CartProof data to these servers.

= 4. Loopback requests to your own site =

The connector sends requests to your own site's URL to check that background processing works. This happens on the CartProof settings screen at most once every 6 hours, even unpaired. It also uses them to run long exports in small steps when Action Scheduler is unavailable, and for the health check (daily, and after installing each update): it reads the Store API cart, puts one unit of your cheapest in-stock product into a new, empty guest cart, loads the checkout page with that cart, then empties the cart and deletes that guest session. No order is created, no payment is attempted, and these requests are marked so analytics plugins can ignore them (the `cartproof_health_cart_probe` filter turns the cart step off). These requests never leave your server's own address.

== Frequently Asked Questions ==

= Does the free plan work without a CartProof account? =

No. The free Checkout Monitor runs in the CartProof service and needs a free CartProof account paired with this plugin. Unpaired, the plugin only shows its settings screen.

= What customer data leaves my store? =

None in readable form. Test-run exports are anonymized on your server while they are written. Personal fields are replaced with consistent fake values or removed, and the export is checked for leaks before upload. The heartbeat and inventory contain software and configuration information only.

= Does the plugin install or change anything on my live store? =

Only on paid plans that include applying updates, and only when you approved that set of updates in the CartProof dashboard or your own auto-apply policy allows it. CartProof is a management service: you control it from your CartProof account, not from wp-admin. Before changing anything, the connector creates a restore point and verifies it. It then installs the exact packages that passed the sandbox tests, each checked by SHA-256, from CartProof storage. It reactivates only plugins that were active before, and it rolls back automatically if a health check fails.

For a WordPress core update, the connector installs the tested core release zip with WordPress's own Core_Upgrader. During that one upgrade call it skips core's MD5 pre-check, because the package was already verified by SHA-256 against the tested artifact. It also blocks calls to api.wordpress.org and downloads.wordpress.org, so that no untested translation packs are installed mid-update. Both settings are restored when the call returns, and WordPress's own updater is otherwise left alone.

= Why does a file appear in wp-content/mu-plugins during an update? =

While an update is being applied or rolled back, the connector writes a temporary `000-cartproof-maintenance.php` must-use plugin. It briefly answers checkout and cart requests with "back in a minute" (HTTP 503) so no shopper pays against a half-installed plugin. It expires after at most 15 minutes and removes itself, and deactivating or uninstalling the plugin removes it immediately.

= What is a restore point and where is it stored? =

Before an update, the connector zips the plugins and themes being updated and saves their settings. When an update can change the database (WooCommerce core, Subscriptions, or plugins with their own database version), it also makes a full database backup. Restore points stay on your server in `wp-content/uploads/cartproof-restore/` and are never uploaded. The database backup is **not anonymized**, because it is your own rollback copy. It is therefore **encrypted** with a key derived from your `wp-config.php` security keys, and the directory is blocked from web access by `.htaccess`. If you ask CartProof to restore the database, the orders placed since the restore point are first exported, encrypted the same way; download them as CSV or JSON from WooCommerce → CartProof.

On nginx, `.htaccess` is ignored. The settings screen then shows the `location` block to add. Better still, move restore points outside the web root by adding `define( 'CARTPROOF_RESTORE_DIR', '/path/outside/webroot' );` to `wp-config.php`.

The connector keeps the newest 3 restore points, removes others after 72 hours, and removes anything older than 14 days.

= How do I remove restore points? =

Click **Delete restore points now** on WooCommerce → CartProof (refused while an update is being applied). Uninstalling the plugin (Plugins → Delete) removes every restore point. Without the plugin they cannot be restored, and the database backups contain un-anonymized data, so they are not left behind. Take a host backup first if you want to keep a copy. Deactivating the plugin keeps them.

= What are Canary Orders? =

An optional, owner-enabled feature on paid plans. Turn it on with **Allow CartProof to place $0 Canary Orders on this store** on WooCommerce → CartProof (or `define( 'CARTPROOF_CANARY_ENABLED', true );`), and also in your CartProof policy. The connector creates one hidden, $0, virtual product and a single-use coupon. CartProof places one test order with them after an update to confirm the live checkout works. Canary orders send no emails, do not touch stock, are excluded from WooCommerce Analytics and webhooks, and are cancelled and deleted automatically. Some third-party tracking tools (for example PixelYourSite, Klaviyo, Hotjar or your host's analytics) cannot be told to ignore the test visit. Turning the setting off removes the canary product and coupon. Deactivating the plugin moves the canary product to draft. Uninstalling deletes the product, the coupon and any leftover canary order.

= What does uninstalling remove? =

All CartProof options (including the event queue and the upgrade-notice dismissal), keys, tables and schedules (heartbeat, daily check, export steps), the maintenance file, the canary product, coupon, orders and gateway settings, restore points and any unfinished export parts.

== Screenshots ==

1. A run report in the CartProof dashboard: the update set failed checkout in the sandbox, so nothing was applied to the live store. Store Pro and Agency name the update that broke it and the safe subset that can still go out.
2. The visual comparison for a failing run: drag the slider between the before and after captures. Here an update put a pop-up over the Place order button, which CartProof flags as a functional risk, not a cosmetic change.
3. The store list for an agency account: every connected store with its last verdict (pass, fail, flaky or baseline broken), pending updates and next check.
4. A store's overview: the software inventory the connector reports, the pending updates with what the policy will do with each, and recent runs.
5. The per-store policy editor: maintenance window, apply automatically or after approval, apply the safe subset, hold a specific update until a fixed version, and flaky or soft-warning handling.
6. The CartProof screen in wp-admin (WooCommerce → CartProof) on a paired store: connection status and heartbeat, the last and next daily checkout check, delivery mode, Disconnect, the Canary Orders opt-in and the restore points kept on the server.

== Changelog ==

= 1.0.0 =
* Checkout Monitor runs on the connector: a daily check at a time CartProof assigns (checkout page with an item in a temporary cart, Store API cart, PHP fatals since the last check) plus the pending update list, reported to CartProof, which emails you.
* Health checks load the checkout page with one item in a throwaway guest cart, then empty it and delete the session, so "checkout has a form" is meaningful. No order is ever created.
* The heartbeat mirrors the connector's audit log and sends store events (PHP fatal, WordPress auto-update, plugin activated/deactivated, theme switched, WooCommerce deactivated), in bounded batches with retries.
* Premium packages larger than 8 MB are uploaded in 8 MB parts instead of being skipped.
* The orders-at-risk export is encrypted like the database backup and downloadable from the CartProof screen.
* New on the CartProof screen: Delete restore points now, the Canary Orders opt-in with plain-language notes, and the last and next daily check.
* A free-plan notice (only when updates are waiting, only on the CartProof, Plugins and Updates screens, dismissible for 30 days).
* The heartbeat reports whether restore points are unprotected on nginx and whether wp-config.php security keys are set.

= 0.1.0 =
* First public release.
* Pairing with CartProof (HMAC-signed requests both ways, encrypted key storage, replay protection).
* Heartbeat, inventory and checkout health reporting for the free Checkout Monitor.
* PII-anonymized, resumable snapshot export for sandbox testing (paid plans).
* Verified restore points (with an encrypted database backup when needed), tested-package apply and automatic rollback (paid plans).
* Opt-in Canary Orders (paid plans).
* wordpress.org review fixes: Requires Plugins header, direct-access guards, a complete readme with external services, uninstall cleanup of canary data and restore points, encrypted database backups, nginx notice, a working Disconnect confirmation, no version on the public ping, translatable gateway and maintenance strings.

== Upgrade Notice ==

= 1.0.0 =
Daily Checkout Monitor, a real-cart checkout check, encrypted order exports, and restore-point and Canary Orders controls on the CartProof screen.

= 0.1.0 =
First public release.
