=== CatCode Order Sync with SalesDrive for WooCommerce ===
Contributors: catcodestudio
Tags: woocommerce, crm, orders, sync, ukraine
Requires at least: 6.2
Tested up to: 7.0
Requires PHP: 7.4
Stable tag: 0.2.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Sends every WooCommerce order to SalesDrive as a lead: at checkout or on a status change, with de-duplication, retries and an event log.

== Description ==

SalesDrive is a third-party product; this is an independent integration, not affiliated with or endorsed by SalesDrive.

The plugin connects WooCommerce to your SalesDrive account through its order handler and JSON API. Every new order in your shop is created in SalesDrive as a lead with the buyer, the line items, the shipping cost and a comment carrying the delivery address, the payment method and the paid/unpaid mark.

= Features =

* Sends the order right after checkout (classic checkout and Checkout Blocks) or when it enters one of the statuses you choose
* De-duplication: the SalesDrive order id is stored in the order meta, and a database-level lock keeps two parallel hooks from creating the lead twice
* After a timeout the plugin asks SalesDrive whether the lead is already there (by `externalId`) before repeating the attempt — the order handler itself does not deduplicate
* Line items are sent with name, SKU, price and quantity; the shipping cost goes to `shipping_costs` (optional)
* Payment mark: for orders paid online a "Paid online" line is added to the lead comment (optional)
* Orders in a currency other than UAH carry the currency code in the comment — the order handler has no currency field
* Automatic retries on failure: 3 attempts with 5 min / 30 min / 2 h pauses (WP-Cron)
* Metabox on the order screen: sync status, SalesDrive ID, "Resend" button (an order already in the CRM is updated instead of duplicated)
* "Test connection" button on the settings page
* Event log with the last sync attempts, their HTTP status and error text
* The API key is encrypted at rest (libsodium, with an HMAC fallback)
* Buyer phone numbers are normalised to E.164 (+380…)
* HPOS-compatible (custom order tables) — order data is read and written through the WooCommerce CRUD only
* `cc_salesdrive_order_payload` filter and `cc_salesdrive_order_sent` action for site-specific tweaks

= Requirements =

* WooCommerce 6.0 or newer
* PHP 7.4 or newer
* A SalesDrive account domain (e.g. yourshop.salesdrive.me) and an API key on the order-base level with the "Orders: add" right ("Orders: read" is optional but recommended, see the FAQ)

= How it works =

1. A customer places an order in your shop.
2. The plugin builds the payload (buyer, line items, shipping, comment) and sends `POST /handler/` to your SalesDrive domain with the key in the `X-Api-Key` header.
3. SalesDrive answers `{"success":true,"data":{"orderId":N}}` — the id is stored in the order meta as `_cc_salesdrive_order_id`.
4. If SalesDrive is unreachable the attempt is repeated automatically (up to 3 times).
5. Your manager sees the status and the id in the order metabox and in the log.

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/`, or install the zip from Plugins → Add New → Upload.
2. Activate the plugin.
3. Go to WooCommerce → SalesDrive Sync, enter your account domain and API key.
4. Press "Test connection", pick the trigger statuses and save.

== Frequently Asked Questions ==

= Where do I get the API key? =

In your SalesDrive account: Settings → General settings and integrations → Other services → API → API keys. Create a key on the order-base level with the "Orders: add" right. The key is kept in the WordPress database in encrypted form.

= Why should the key also have the "Orders: read" right? =

Because the SalesDrive order handler does not deduplicate: posting the same `externalId` twice creates two leads. If a send times out, the plugin cannot know whether the lead was created. With read access it looks the order up by `externalId` before retrying and adopts the existing id instead of sending again. Without it the order is flagged in the metabox so you can check the CRM yourself.

= Can the same order end up in the CRM twice? =

The plugin stores the SalesDrive id in the order meta and takes a database lock before sending, so parallel hooks (checkout, status change, payment callback) cannot both create it. The remaining case — a request that never gets an answer — is handled by the `externalId` lookup described above, or flagged as `no_response` when the key may not read orders.

= What does the "Resend" button do? =

If the order is not in SalesDrive yet, it creates the lead (`POST /handler/`). If it is already there, it updates it (`POST /api/order/update/`) with the current buyer, items, shipping and comment.

= In which currency are the amounts sent? =

In the order currency, as plain numbers: the SalesDrive order handler has no currency field, so the amounts land in whatever currency your SalesDrive account uses. For orders in a currency other than UAH the plugin adds the currency code to the lead comment so the manager is not misled.

= What happens when I delete the plugin? =

Deleting (not just deactivating) removes the plugin options and the log table. The SalesDrive ids stored on the orders are kept on purpose: they are what stops a reinstall from creating a second copy of an old order in the CRM.

== External services ==

This plugin connects to the SalesDrive API on your own account domain (`https://<your-account>.salesdrive.me`), a third-party CRM service. The connection is the whole purpose of the plugin: it creates and updates your WooCommerce orders inside your own SalesDrive account.

What is sent and when:

* `POST /handler/` — when a customer places an order, or when the order enters one of the statuses you selected as a trigger. Retried up to 3 times if SalesDrive is unavailable.
* `POST /api/order/update/` — when you press "Resend" in the order metabox for an order that already exists in SalesDrive.
* `GET /api/order/list/` — only after an attempt that got no response, to check by `externalId` whether the lead already exists before retrying.
* `GET /api/statuses/` — only when you press "Test connection" on the settings page.

Every request carries your SalesDrive API key in the `X-Api-Key` header. The order data sent is: the WooCommerce order id as `externalId`, the shop host, the line items (name, SKU, price, quantity), the shipping method and its cost, the delivery city and address, the payment method title and whether the order is paid, the customer note, and the buyer's first and last name, e-mail address and phone number as entered at checkout.

No data is sent to any other service, and nothing about your visitors, site or administrators is sent beyond the order data listed above. Nothing is sent until you enter your SalesDrive domain and API key: without them the plugin makes no external requests.

This service is provided by SalesDrive: [terms of service](https://salesdrive.ua/oferta/), [privacy policy](https://salesdrive.ua/privacy-policy/).

== Screenshots ==

1. Settings: account domain, API key, trigger statuses
2. Sync log with the last attempts
3. SalesDrive metabox on the WooCommerce order with the resend button

== Changelog ==

= 0.2.0 =
* Renamed the plugin to "CatCode Order Sync with SalesDrive for WooCommerce" (new slug and text domain). Options, order meta and the log table keep their names, so nothing has to be migrated.
* Fixed: the "Resend" button in the order metabox never worked. The metabox drew its own `<form>` inside the WooCommerce order form, and browsers drop a nested form — the click submitted the order form instead. It is a nonced link now, and the result is shown as an admin notice.
* Fixed: the guard against a double send was a transient, i.e. a read-then-write — two parallel hooks could both pass it and create the lead in SalesDrive twice. It is now a database-level lock, and the order is re-read past the HPOS order cache before the final check.
* Fixed: uninstalling the plugin left the sync log table in the database forever. It is dropped now; the per-order SalesDrive ids are kept on purpose as the anti-duplicate guard.
* Fixed: the paid-order mark reached the CRM with an HTML entity instead of the currency sign.
* Added: after an attempt that gets no HTTP response the plugin looks the order up by `externalId` (`GET /api/order/list/`) and adopts the existing lead instead of creating a second one; if the key may not read orders, the order is flagged in the metabox instead.
* Added: orders in a currency other than UAH carry the currency code in the lead comment — the order handler has no currency field.
* Admin styles are now enqueued through a registered handle instead of raw `<style>` output.
* Interface strings are in English with a Ukrainian translation shipped in `/languages`.
* Documentation fixes: the readme no longer claims that SalesDrive deduplicates by `externalId` (it does not), the resend button is documented as `POST /api/order/update/`, and the requirements say WooCommerce 6.0 (the plugin never needed 7.0).

= 0.1.0 =
* First release: orders exported to the SalesDrive order handler, retries, metabox, log, connection test.

== Upgrade Notice ==

= 0.2.0 =
Makes the "Resend" button work at all, fixes a race that could create the same order in SalesDrive twice, and stops leaving the log table behind on uninstall.
