=== UKTO Invoice Sync for Pennylane ===
Contributors: ukto
Tags: woocommerce, pennylane, invoice, accounting, factur-x
Requires at least: 5.8
Tested up to: 7.1
Requires PHP: 7.4
Requires Plugins: woocommerce
Stable tag: 1.8.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Send your WooCommerce orders to Pennylane as legally compliant invoices, with one click from the order screen.

== Description ==

This plugin connects your WooCommerce store to the Pennylane accounting platform. Invoices are
issued **by Pennylane**, which keeps legal numbering and archiving where they belong — the plugin
never generates an invoice itself.

Create a compliant, legally numbered invoice for any order with a single click: the customer, the
invoice lines, the VAT treatment and the legal mentions are all built for you.

Features:

* **One-click invoicing** — a "Create invoice" button on every WooCommerce order. The invoice is
  created as a draft in Pennylane, then finalized (legal numbering) automatically, and the
  Factur-X PDF is retrievable from the order screen.
* **Customer sync** — companies and individuals are created in Pennylane with their billing
  details, and reused on the next order (no duplicates).
* **Full VAT and OSS mapping** — French rates (20 %, 10 %, 5.5 %, 2.1 %), intra-EU B2B reverse
  charge, intra-EU B2C (OSS), exports outside the EU. The right legal mention is written on the
  invoice automatically.
* **EU VAT number at checkout** — an optional VAT number field for business customers (classic
  checkout and Checkout Blocks), format-checked, with **VIES validation** (the official European
  Commission service) to secure the exemption.
* **B2B VAT exemption at checkout (optional)** — charge no VAT to EU businesses with a validated
  VAT number: the customer pays the net amount and the invoice carries the legal intra-Community
  exemption mention. Off by default (it changes what customers pay, and intra-Community supplies
  require filing the EC Sales List — check with your accountant). Whatever you choose, the
  invoice always follows the money: if VAT was collected, the invoice is issued with that VAT.
* **OSS awareness** — tell the plugin whether the store is registered for the EU One-Stop Shop:
  under the €10,000 threshold, EU consumers are invoiced with French VAT codes; OSS-registered
  stores get destination-country VAT codes.
* **SIRET at checkout with auto-fill** — French business customers type their SIRET
  and the company name and address fill in automatically (public data from data.gouv.fr).
* **Invoice PDF in My Account** — customers can download their invoice from their account,
  through a secure endpoint.
* **Test mode (dry run)** — validate your whole configuration without sending anything to
  Pennylane: payloads are built and written to the logs, nothing else.
* **Diagnostics tab** — token check, live API permission check, decimals check, logs shortcut:
  see at a glance why something would not work.
* **HPOS and Checkout Blocks compatible** — works with the classic checkout and the block-based
  checkout, with or without High-Performance Order Storage.

The free plugin is fully functional on its own and is not time-limited.

Need automation? The [Pro version](https://ukto.fr/pennylane.php?lang=en#editions) adds automatic invoicing on
status change, credit notes on refunds, payment reconciliation, invoice e-mailing to the customer
(your template or Pennylane's), product mapping, bulk actions with ZIP export, backfill of past
orders, integrity checks, weekly summary, failure alerts and WP-CLI commands.

This plugin is not affiliated with, nor endorsed by, Pennylane. "Pennylane" is a trademark of its
respective owner and is used here only to describe compatibility.

== External services ==

This plugin communicates with the following third-party services. Nothing is sent without your
configuration or the customer's own input, and none of these calls carry marketing or tracking
data:

* **Pennylane API** (https://app.pennylane.com) — the purpose of the plugin. When you create an
  invoice, the order data (customer billing details, invoice lines, totals, VAT) is sent to your
  own Pennylane account using the API token you configured. Nothing is sent before a token is
  configured and an invoice is triggered.
  Terms of service: https://www.pennylane.com/fr/conditions-generales
  Privacy policy: https://www.pennylane.com/fr/legal/privacy
* **VIES** (European Commission, https://ec.europa.eu) — optional, off by default. When enabled,
  the VAT number typed by a business customer at checkout (and only that number) is checked
  against the European Commission's official VAT validation service. Disabled by default: it
  only runs after you switch it on in WooCommerce > Pennylane > Invoicing.
  Terms of use: https://ec.europa.eu/taxation_customs/vies/
  Privacy policy: https://commission.europa.eu/privacy-policy_en
* **Recherche d'entreprises** (https://recherche-entreprises.api.gouv.fr) — optional, off by
  default. When enabled, the SIRET typed by the customer (and only that) is sent to
  the French government's public company directory to retrieve the company name and address.
  Disabled by default: it only runs after you switch it on in WooCommerce > Pennylane > Invoicing.
  Terms of use: https://www.data.gouv.fr/pages/legal/cgu/
  Service documentation: https://api.gouv.fr/les-api/api-recherche-entreprises

== Installation ==

1. Install and activate the plugin (WooCommerce must be active).
2. Go to WooCommerce → Pennylane → Connection and paste your Pennylane API token
   (in Pennylane: Settings → Connectivity → Developers). Required permissions: customers
   read/write, customer invoices read/write, products read.
3. Click "Test connection": all three permissions must be green.
4. Open the Invoicing tab and review the settings. Enable test mode and create a test invoice
   from any order: the full payload is written to the logs (WooCommerce → Status → Logs, source
   "pennylane") without anything being sent to your accounting.
5. Happy with the payloads? Turn test mode off and invoice for real.

== Frequently Asked Questions ==

= Is this an official Pennylane plugin? =

No. It is an independent integration built on the public Pennylane API (v2). It is not
affiliated with, endorsed by or sponsored by Pennylane.

= Do I need a Pennylane account? =

Yes. The plugin sends invoices to *your* Pennylane account through the official API. Any
Pennylane plan that includes API access works.

= Does the plugin generate the invoices itself? =

No. Invoices are created and numbered by Pennylane through its API. The plugin sends the order
data and retrieves the resulting document, so legal numbering stays in your accounting software.

= Which VAT cases are handled? =

Domestic French sales at any legal rate, intra-EU B2B with a validated VAT number
(intra-Community supply exemption, Article 138 of Directive 2006/112/EC), intra-EU B2C (OSS —
the customer country's rate as configured in WooCommerce), and exports outside the EU
(article 262 I of the French tax code). The matching legal mention is written on the invoice.

= What happens if VIES is down? =

Nothing blocks. The order goes through and the VAT number is re-checked at invoicing time; a
note is added to the order so you can verify manually. If the B2B exemption option is on and
VIES cannot confirm the number at checkout, VAT is charged — and the invoice then follows what
was actually collected.

= Can EU business customers buy without VAT? =

Yes, as an option (off by default): enable "B2B checkout — VAT exemption" in the Invoicing tab.
With a validated EU VAT number, the customer pays the net amount and the invoice carries the
intra-Community exemption mention (Article 138 of Directive 2006/112/EC). Selling VAT-free
intra-Community supplies requires filing the EC Sales List — check with your accountant first.

= Is test mode safe on a live store? =

Yes. With test mode enabled, no write request reaches Pennylane: the API client blocks them
centrally and payloads are only written to the WooCommerce logs so you can review them before
going live — with customer names, addresses, phone numbers and e-mail addresses masked.

= Where are the logs? =

WooCommerce → Status → Logs, source "pennylane". Every API error is logged with the full
response, and every order keeps its own note trail. Customers' personal data is masked in the
logs; to log it in full while debugging, return true from the `isfp_log_personal_data` filter.

= Can invoices be created automatically? =

Automatic invoicing on status change — plus credit notes, reconciliation, customer e-mailing,
bulk actions and backfill — is part of the [Pro version](https://ukto.fr/pennylane.php?lang=en#editions).

== Screenshots ==

1. The Pennylane box on a WooCommerce order — invoice finalized with its legal number, PDF one click away.
2. Connection tab — your Pennylane API token and the connection test.
3. Invoicing tab — test mode, VIES validation, SIRET auto-fill, B2B exemption, OSS, invoice language.
4. Checkout — SIRET and EU VAT number fields for business customers.
5. Diagnostics tab — everything checked at a glance.
6. The invoice available in the customer's My Account.

== Changelog ==

= 1.8.0 =
* **Customers buying first as individuals, then as businesses, now get a business invoice on a
  business record.** The Pennylane customer remembered on a WordPress account used to be reused
  for every later order: a B2B invoice could land on the buyer's individual record, without their
  VAT number — a mandatory mention on a VAT-exempt intra-Community invoice. The remembered record
  is now only reused for the same identity (individual, or the same company, VAT and SIRET
  numbers); otherwise the right record is looked up or created.
* Customer lookup in Pennylane is now a single filtered request (e-mail and customer type) instead
  of scanning the whole account page by page — up to 50 calls, and a duplicate customer beyond
  5,000. If Pennylane rejects or ignores the filter, the previous full scan is used. Two companies
  sharing one e-mail address (an accounting firm, for instance) are told apart by their VAT and
  SIRET numbers.
* **Personal data is no longer written to the logs.** Test mode — on by default — logged each
  customer's name, address, phone number and e-mail address. They are now masked (amounts, VAT
  rates and references stay readable). The `isfp_log_personal_data` filter restores full values
  for one-off debugging.
* SIRET lookup at checkout: the setting is now checked on the server side (the endpoint kept
  answering for up to 24 hours after the option was turned off); cached answers no longer count
  towards the rate limit; a store-wide ceiling is added, and the `isfp_siret_client_ip` filter lets
  stores behind a CDN or reverse proxy rate-limit per visitor. Its messages can now be translated
  and the server's explanation is shown to the customer.
* Order actions: each action link is now signed for that action only (one nonce used to cover
  every action of an order).
* Customer invoice download (My account): fetched with `wp_safe_remote_get`, size-capped, and only
  served when the file really is a PDF — a hosting error page is no longer sent as "invoice.pdf".
* Uninstalling the plugin no longer deletes the Pro extension's settings, and now removes all of
  its own options (B2B exemption, OSS, the pre-1.7 API token, caches and locks).

= 1.7.9 =
* **The B2B VAT exemption now requires VIES validation.** Until now, with VIES turned off, only the
  format of the number was checked: anyone could type a correctly formatted EU VAT number —
  including a private individual — and pay no VAT, while the invoice went out as a VAT-exempt
  intra-Community supply. That VAT is then owed by the merchant. The exemption is now only granted
  when VIES confirms the number. **If you have the exemption enabled without VIES, enable VIES
  validation in the Invoicing tab**: until you do, VAT is charged to every customer, and a warning
  says so on the settings page.

= 1.7.8 =
* **Fixed: on a fresh install the API token could not be used.** WordPress runs the settings
  sanitizer twice the first time an option is saved, and the token was encrypted on each pass:
  the plugin then read back ciphertext instead of the token, and every call to Pennylane was
  rejected until the token was pasted a second time. The token is now stored in a format the
  sanitizer recognises, and tokens already stored twice-encrypted are repaired automatically.
* **Fixed: invoices for customers outside the EU or in French overseas territories, when VAT had
  been collected.** Many stores charge French VAT to everyone. Such a customer paid 120 but
  received a VAT-exempt invoice for 100 with an export mention. When VAT was collected, the
  invoice now carries it at the rate actually applied, and the order gets a note. Exports on
  which no VAT was collected are unchanged: still exempt, with their legal mention.

= 1.7.7 =
* **E-invoicing status is now surfaced.** Since March 2026 the Pennylane API returns an
  `e_invoicing` object on invoice creation and finalization, telling you what the approved
  platform did with the invoice. The plugin ignored it: an invoice refused or rejected by the
  platform still showed up as simply "finalized" and nobody was told. The status is now stored,
  shown on the order screen, and a refusal, rejection or dispute adds an order note and a log
  entry. French e-invoicing has been mandatory since 1 September 2026, so a silent rejection is
  not a theoretical problem.
* The invoice file is now checked against the API's `factur_x` flag instead of being assumed to
  be Factur-X, and the result is shown on the order screen.
* Orders are sent to Pennylane with `sales_order_reference` (e-invoice field BT-14), so the
  WooCommerce order number is visible from Pennylane. Until now the link between an invoice and
  its order only existed inside WordPress.

= 1.7.6 =
* The bundled French translation was out of sync with the one reviewed on translate.wordpress.org:
  the shipped file still carried glossary terms that had already been corrected there. Both are now
  identical, including the non-breaking spaces.
* French translation switched to inclusive forms for "customer", following the fr_FR glossary and a
  review request from the WordPress French translation team.

= 1.7.5 =
* The checkout field is now called SIRET everywhere, which is what it actually is: a French
  company identifier, 14 digits, checked against the French public company directory. Calling it
  a generic "company ID" was misleading.
* Further French glossary corrections after a second review by the WordPress French team.

= 1.7.4 =
* French translation reworked after review by the WordPress French translation team: glossary terms
  and non-breaking spaces now follow the official style guide.
* Fixed the readme pointing to a "Checkout" settings tab that does not exist — VIES and SIRET
  settings are in the Invoicing tab.

= 1.7.3 =
* The plugin header is translated too, so the Plugins screen no longer shows a French interface
  next to an English description.

= 1.7.2 =
* French translation now ships with the plugin, so French stores get a French admin right away.
  It steps aside automatically as soon as the community language pack from translate.wordpress.org
  becomes available.

= 1.7.1 =
* Links to the plugin site now open in the language of your WordPress admin, and land on the
  edition comparison rather than the top of the page.

= 1.7.0 =
* First release on WordPress.org.
* Renamed to "UKTO Invoice Sync for Pennylane"; option and hook prefixes moved to `isfp_`, with
  a one-time migration so existing settings are preserved.
* VIES validation and company-ID auto-fill are now off by default: nothing is sent to a third
  party until you enable it.
* Invoice locking now relies on a database uniqueness constraint, so two simultaneous requests
  can no longer create two numbered invoices.
* VAT numbers are format-checked before any cache write or network call.

= 1.6.0 =
* Submitted for review, never publicly released.
* One-click invoicing (draft + finalization by Pennylane) from the order screen.
* Customer sync (companies and individuals) without duplicates.
* Full VAT mapping: FR rates, intra-EU B2B reverse charge, OSS, exports — with automatic legal mentions.
* EU VAT number checkout field with VIES validation.
* Optional B2B VAT exemption at checkout (validated EU VAT number → net price + legal mention).
* OSS setting: French VAT codes under the €10,000 threshold, destination codes when OSS-registered.
* SIRET checkout field with company auto-fill (data.gouv.fr).
* Invoice PDF download in My Account.
* Test mode (dry run), diagnostics tab, detailed logging.
* HPOS and Checkout Blocks compatibility.
* Before this public release, the plugin went through six private iterations (1.0 → 1.5) on a
  production pilot store, including a full code review with every confirmed finding fixed.

== Upgrade Notice ==

= 1.8.0 =
Business orders from customers who previously bought as individuals now go to a business record in Pennylane. Personal data is no longer written to the logs.

= 1.7.9 =
The B2B VAT exemption now requires VIES validation. If you use the exemption without VIES, enable VIES in the Invoicing tab after updating.

= 1.7.8 =
Fixes a token storage bug that broke the connection on fresh installs, and wrong export invoices when VAT had been collected. Recommended for everyone.

= 1.7.7 =
E-invoicing refusals are no longer silent: the status from the approved platform is shown on the order.

= 1.7.6 =
French translation realigned with the reviewed version on translate.wordpress.org.

= 1.7.5 =
Clearer wording: the checkout field is a French SIRET, and says so.

= 1.7.4 =
French translation corrected after review by the WordPress French team.

= 1.7.3 =
Completes the French translation of the Plugins screen.

= 1.7.2 =
Adds the French translation to the package.

= 1.7.1 =
Cosmetic: the plugin site opens in your admin language.

= 1.7.0 =
First public release.
