=== Pay4All Shop - Simple order forms with TWINT & STRIPE Payment ===
Contributors: pay4all
Tags: order form, products, payments, twint, woocommerce twint
Requires at least: 5.9
Tested up to: 7.0
Requires PHP: 7.4
Stable tag: 2.10.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Simple order forms with products, deliveries and Twint payment - no WooCommerce required.

== Description ==

Pay4All Shop is a simple and lightweight order form plugin designed for small shops, wineries, caterers, takeaway restaurants, and associations.

It was intentionally created as a simpler alternative to WooCommerce: create an order form, add your products and delivery methods, assign payment methods to each delivery option, and embed the form anywhere on your website using a shortcode.

Available in French, German, Italian, and English.

All customer-facing text can be customized separately in all four supported languages: French, German, Italian, and English.

== Main Features ==

* Create as many order forms as you need and add your products to each form.
* Default fields such as first name, last name, email address, phone number, and address are automatically translated into every enabled language.
* Add, edit, remove, and organize form fields according to your needs. Forms are fully customizable.
* Products can include an image, regular price, sale price, minimum and maximum quantities, and a short description.
* Product image size can be configured separately for each form.
* Multiple product types are supported:

  * Physical products, enabled by default.
  * Virtual products, with a downloadable file delivered after purchase.
  * Event tickets with QR code validation, available with Pay4All Pro.
* Out-of-stock management: products remain visible, but their quantity selector is disabled.
* Optional stock management for each product, with email notifications when stock becomes low.
* Available delivery methods:

  * Local pickup.
  * Flat-rate delivery.
  * Free delivery.
  * Free delivery above a configurable order amount.
  * Rate table (tiered pricing based on cart weight, order amount or number of items).
  * No delivery required.
* Payment methods can be assigned individually to each delivery method.
* Secure delivery of downloadable virtual products.
* Optional "Remember my information" feature:

  * Customer details are stored only in the customer's browser.
  * No saved information is sent to the server.
  * Customers can prefill the form with one click during a future visit.
  * Disabling the option immediately removes the saved information.
* Complete order management:

  * Custom order statuses.
  * Payment statuses.
  * Internal notes.
* Customizable emails sent to customers and merchants.
* The email subject, title, introduction, and closing text can be customized separately in each supported language.
* Export orders in XLS or CSV format, with filters for date, form, and order status.
* Compatible with the WordPress Privacy API for GDPR data export and erasure requests.
* Anti-spam protection using a honeypot field, WordPress nonces, and server-side validation.

== Included Payment Methods ==

* Payment on pickup.
* Cash on delivery.
* Bank transfer.
* Install Pay4All for TWINT to accept TWINT payments.
* Install Pay4All for Stripe to accept credit and debit card payments.

== Age Verification ==

Install Pay4All Age Verification to add age verification to your products.

Features include:

* Age verification can be enabled individually for each product with a simple checkbox.
* Identity document or selfie verification.
* QR code capture from another device.

== Pay4All Pro — Optional ==

=== Events and Ticketing ===

Sell tickets for events, parties, shows, concerts, and other activities.

A unique QR code is generated for every ticket purchased and sent to the customer by email. Tickets can be scanned and validated at the entrance using any smartphone, with no application to install.

The QR ticketing system also includes:

* A public ticket validation page.
* A self-service check-in screen.
* Ticket and participant search.
* Participant export.

QR ticketing is included with Pay4All Pro, with a free 30-day trial.

=== Quantity Rules ===

Define quantity rules by product category or brand:

* Minimum quantity.
* Maximum quantity.
* Quantity multiples.
* Minimum quantity combined with a required multiple.

Rules are validated both in the customer's browser and on the server to prevent invalid orders.

=== Themes and Customization ===

Quickly customize the appearance of your forms using the Light, Dark, Red, Orange, Yellow, Green, Blue, Purple, and Brown predefined themes, or create a fully customized design using the advanced customization options.

A live preview displays your changes immediately.

=== Abandoned Carts ===

Automatically capture incomplete orders and send a follow-up email after a configurable delay.

Each recovery email contains a unique, single-use link that allows the customer to restore the abandoned cart and continue the order where they left off.

== Usage Examples ==

* Wineries selling products at their cellar door or during events.
* Cheese shops offering local pickup.
* Caterers accepting weekly orders.
* Local shops offering in-store pickup only.
* Associations accepting online registrations and payments.
* Businesses selling digital products such as PDF files, MP3 files, online courses, and documents.

== Installation ==

1. Upload the plugin to `/wp-content/plugins/pay4all-form/` or install via *Plugins › Add New*.
2. Activate the plugin from the *Plugins* menu.
3. Open *Pay4All Shop* in the admin sidebar.
4. Create your products, delivery methods, then a form.
5. Copy the form shortcode into any page.

== Frequently Asked Questions ==

= Do I need WooCommerce? =

No. Pay4All Shop is standalone. It does not depend on WooCommerce in any way.

= Does it support online payments? =

The free version supports manual payments only (pickup, on delivery, bank transfer, TWINT instructions). Automated TWINT payments are available via Pay4All Pro.

= Is the plugin GDPR-friendly? =

Yes. Customer data is included in WordPress's *Tools › Export / Erase Personal Data* workflow.

= Will my data be deleted if I uninstall? =

No, unless you explicitly enable *Réglages › Données › Supprimer les données lors de la désinstallation*.

= Does the plugin use cookies? =

No HTTP cookies are set by the plugin. When the shopper ticks the *« Se souvenir de mes informations pour ma prochaine commande »* checkbox on the customer step, their contact fields are stored in the browser's own `localStorage` (client-side only, never uploaded to the server, one-year retention, scoped per form). Unticking the checkbox and clicking *Suivant* — or the browser's *Clear site data* action — wipes the entry immediately. Nothing is stored when the checkbox stays off.

= How do virtual products work? =

When you create a product of type *Virtual*, you upload one file per product. The file is stored in `wp-content/uploads/p4all-virtual/`, protected by an automatically generated `.htaccess` (deny from all). After purchase, the customer's e-mail contains a one-time download link served by the plugin endpoint (`?p4all_dl={token}`). The link is valid for 60 days.

= How does *Out of stock* work? =

Tick the *Rupture de stock* checkbox on a product. The product stays visible in the form so the customer still sees what you usually offer, but the quantity field is greyed and disabled. A red badge labels the product as out of stock. Server-side validation rejects any tampered submission that tries to order an out-of-stock item.

= How does *Stock management* work? =

Tick *Manage stock* on a product and enter the quantity available. From that point on:

* Each order decrements the stock by the quantity ordered (one decrement per ticket / per unit). The decrement happens as soon as the customer validates the order, regardless of the payment status.
* The order form prevents the customer from ordering more than what is left — the quantity input's *max* attribute is capped at the stock available, and a server-side check rejects any tampered submission.
* When the stock reaches 0, the product is automatically flagged *out of stock*: it stays visible but cannot be ordered.
* When an order is deleted from the admin (definitive delete, not just trashed), the stock is restored for every product still in the order. Status changes (paid, cancelled…) do NOT restore stock; only deletion does.
* If you set a *Notify me when stock reaches* threshold in *Settings › General* (under the shop address), an e-mail alert is sent to the notification address as soon as a tracked product crosses the threshold downwards after a sale.
* A *Stock* column in the products admin list shows the current quantity (or `—` when stock management is off), highlighted in orange below the threshold and red when out of stock.

== Screenshots ==

1. Example of an order form and delivery options.
2. Payment method selection, confirmation and payment via TWINT.
3. Dashboard, order list and language selection.
4. Order form configuration.

== Changelog ==

= 2.10.0 =
* **New « Rate table » delivery type.** Charge shipping based on a merchant-defined tier grid: pick a basis (cart weight, order amount or number of items), then declare as many `from - to - price` rows as needed. The winning tier is the one whose `from` still holds; values past the last tier reuse its price. The frontend previews the exact delivery price live on step 3 as the customer changes quantities or picks options; the server re-computes it at checkout so the amount charged always matches what was shown.
* **Weight support on products, variations and add-ons.** Physical and variable products can now carry a per-unit weight (kg / lb, unit picked under Settings > Currency & weight). Each variation option adds its own weight to the base, and add-on options have their own « Multiply weight by ordered quantity » toggle. The cart aggregates the weight across every line and shows a « Weight » row on the order summary when it's greater than 0.
* **Currency & weight settings tab.** The existing Currency tab was renamed and now hosts a Weight section with a kg / lb radio, used everywhere the shipping weight is displayed (product edit, delivery rate-table thresholds, order summary).
* Full FR / DE / IT translations for every new UI string.

= 2.9.0 =
* Translations validated and shipped for French (fr_FR), German (de_DE) and Italian (it_IT). All customer-facing and admin-facing strings now resolve to the merchant's active locale via the bundled `.mo` files. No configuration change required.

= 2.8.0 =
* **i18n refactor - source strings converted to English.** Every user-facing string in the plugin code is now in English, matching the WordPress i18n convention (`msgid` = English source, `msgstr` = translation). Roughly 1,180 gettext calls were converted from FR msgid to EN msgid. Full translation packs are bundled and validated for French (fr_FR), German (de_DE) and Italian (it_IT) under `/languages/`.
* **Config-like constants cleanup.** The historic per-locale inline defaults inside `Schema::PAYMENT_DESC_DEFAULTS`, `Schema::CONFIRMATION_MESSAGES_DEFAULTS`, `Mailer::email_defaults()` and `Mailer::abandoned_cart_email_defaults()` are replaced by a single English source array feeding through gettext. The merchant per-language overrides in Settings still win over the shipped translation, so custom copy is preserved on upgrade.
* No behaviour change, no configuration change, no manual action required.

= 2.7.3 =
* Classement des paiements.

= 2.7.2 =
* Variable-product bug fix : options (variations + single-mode add-ons) now render as checkboxes with JS-enforced mutual exclusion - the customer can click their picked option again to unselect it. Uncheck is blocked when the group is marked « required » (no way to end up with zero selection). Fixed internal `input[type="radio"]:checked` selectors left stale that were preventing the price + summary recompute after the switch to checkboxes.
* UI : « - » separator between the label and the price in both the variation dropdowns AND the variation buttons (« Grande - CHF 50.- »). Add-on button padding lowered to `0 15px` (was `8px 12px`). Labels forced to 12px inside the plugin wrapper to beat Elementor kits that raise every `<label>` to 16px globally.

= 2.7.1 =
* UX : the running **Total** row and the « Suivant » button on step 1 (products) are now pinned to the bottom of the viewport while the shopper scrolls the product list. Especially useful on catalogues with many products - the shopper can pick a couple of items at the top of the list and validate the step without scrolling to the very bottom first. The pinned bar releases seamlessly (no border / no shadow) once the shopper reaches the natural end of the list, so short forms are unaffected.

= 2.7.0 =
* New **VAT (TVA) support**. A dedicated « TVA » settings tab lets the merchant turn VAT on globally, define a default rate (defaults to 8.1 % - the current Swiss standard rate) and see every custom rate at a glance. When VAT is on, all prices displayed on the order form are treated as tax-inclusive (TTC) and the summary + confirmation e-mails render a « incl. VAT X% » breakdown grouped by rate.
* **Per-item VAT rate overrides**. Products, individual variations, add-on options and delivery methods can each carry their own VAT rate override via a click-to-edit badge next to their price - useful for shops mixing food (2.6 %) and non-food (8.1 %), or a delivery fee taxed differently from the goods.
* **Custom rates table** on the TVA tab. Every custom rate currently in use across products, variations, options and delivery modes is listed with per-category usage counts (e.g. « 3 produits, 1 option ») and an inline « Remplacer » button to bulk-replace all occurrences of a rate with a new one - handy when a statutory rate changes.
* **Dashboard step** for VAT setup added to the onboarding checklist.
* Full FR / EN / DE / IT translations for every new UI string.

= 2.6.1 =
* New **Grid column count** for the product picker. When the merchant picks the « Grille » layout, a new dropdown lets them choose 1 / 2 / 3 / 4 columns (default 2). Smartphones always drop to 1 column regardless of the pick, so the mobile experience stays readable without a per-form tweak.
* Enhancement : **product thumbnails** on the order form now request the WordPress `medium_large` size (768 px wide, aspect ratio preserved) instead of `medium` (300 × 300). Retina and larger cards render crisper ; the standard `srcset` still picks a smaller size on tighter viewports so bandwidth stays under control.

= 2.6.0 =
* New **Multi-variable products**. Products can now expose multiple variation groups (for example « Bottle size » + « Vintage year »), with per-group required / optional flag, per-group stock management, per-variation price delta and per-variation description. Groups carry a customer-facing « Title » AND a short admin-facing « Label » — the latter is what shows up in orders, invoices, admin lists and the variation-stock matrix header.
* New **Product options (add-ons)**. Each product can offer optional extras grouped in named add-on groups (single-choice or multi-choice, with or without an inline text field per option). Each add-on carries its own price, optional per-quantity multiplier and optional required customer message. Add-ons appear on the order form, on the summary, in emails and on the admin order screen.
* New **Delivery hours & locations** management. Global settings screen for per-weekday opening hours (multiple slots per day, per-day « Closed » toggle), an ordering-lead-hours threshold, a slot granularity (15 / 30 / 60 min), a holidays / vacation date range picker with tag UI, and a delivery-postal-codes allow-list. Delivery methods can opt in to slot booking : the order form then shows a per-mode calendar that grays out closed days / holidays / dates before the lead time, plus a time-slot dropdown filtered by the day's opening hours. The customer's NPA (via the new « Postal code / NPA » field type) is checked against the allow-list — non-covered addresses see the delivery mode disabled with a « Nous ne livrons pas à cette adresse » notice.
* Full FR / EN / DE / IT translations for every new UI string introduced by the three features above.

= 2.5.2 =
* The bottom « Publier / Mettre à jour » button on the form editor now shows the correct label depending on the post status (« Publier » while the form is still a draft, « Mettre à jour » once it's live) — matching WordPress core's own publish metabox behaviour.
* Fix : clicking the bottom save button no longer triggers the browser's « Les modifications que vous avez apportées ne seront peut-être pas enregistrées » warning. The button now delegates to WordPress's own `#publish` control, so the full save pipeline (beforeunload cleanup, autosave lock, post-save redirect + notice) runs as expected.

= 2.5.1 =
* Fix : the Age Verification companion plugin is now correctly detected under its current slug (`pay4all-age-verification`). Previously the stale `pay4all-age` reference caused the greyed teaser + « Install from WordPress » button to keep showing on the product editor and the Add-ons page even when the plugin was already installed and active.
* Rename : the Add-ons card labeled « Pay4All Age » is now « Pay4All Age Verification » to match the actual plugin name.

= 2.5.0 =
* New **WordPress dashboard widget** « Pay4All SHOP ». Adds monthly / yearly / lifetime order counts + revenue, plus a backlog block (new orders to process, orders in preparation, active products, published forms) to the main /wp-admin dashboard. Every tile is clickable and jumps to the matching admin screen. Values reuse the same aggregation as the plugin's own dashboard so the two views cannot drift.
* New **Shortcode video tutorial**. A short MP4 walkthrough is now reachable from three places : the Help page (section 2, step 4), the Forms list (next to Copier in the Shortcode column), and the Shortcode metabox on the form edit screen. Opens in a lightweight in-page modal (native <video>, no external service).
* New **Click-to-copy** on the Shortcode metabox input : clicking the field copies the shortcode to the clipboard and flashes a green « Copié ! » badge for confirmation. « Copier » button behaviour on the list is unchanged.
* Fix : the Comments admin menu no longer shows a stray red « 0 » badge when Pay4All Shop is active. The plugin's own menu-badge CSS was leaking onto WordPress core's `.awaiting-mod` element and overriding the `.count-0 { display:none }` rule — badges are now scoped to a dedicated `.p4all-shop-badge` class.
* Fix : Commentaires site-wide counters now correctly subtract our custom `p4all_order_note` comment_type (previously the filter existed but did nothing). Cached via the object cache with automatic invalidation on note create / delete / status change.

= 2.4.0 =
* **Security — CSV formula injection blocked.** Order fields written by anonymous visitors (name, address, comment, product titles) are now prefixed with an apostrophe when they start with `=`, `+`, `-`, `@`, TAB or CR before being written to the CSV export, so Excel / LibreOffice never evaluate them as formulas (CWE-1236). Real XLSX export was already safe (inline strings) and is unchanged.
* **Security — rate-limit on public form submission.** The `p4all_form_submit` endpoint (formerly unmetered beyond a honeypot + 3s minimum-time trap) is now capped at 5 valid POSTs per minute per IP + form via a transient counter. Blocks scripted mail-bombing of the merchant's SMTP without breaking the honest UX (filter `p4all_form_submit_rate_max` to override).
* **Security — anti-spam timestamp is now mandatory.** Previously, a missing / zero `p4all_ts` silently bypassed the minimum-time check ; the field is now required (the same JS that renders the form always sets it) so bots can no longer skip the wait entirely.
* **Security — hardened client IP detection.** `META_CLIENT_IP` now defaults to `REMOTE_ADDR` and only accepts `HTTP_CF_CONNECTING_IP` when Cloudflare's `CF-RAY` signature is present, preventing trivial WAN-side spoofing that used to let a visitor forge the IP recorded on their order. Non-Cloudflare proxies can be trusted via the new `p4all_form_trust_forwarded_for` filter.
* Packaging : leftover `languages/pay4all-form-en_US.po.bak` removed.

= 2.3.0 =
* New **Bank transfer** payment method with a per-language description block. The merchant edits the IBAN / BIC / beneficiary in *Réglages › Paiement* under each language accordion — the input is a multi-line textarea with a fake-data placeholder so an empty field at least shows the expected shape at checkout. The description is rendered with `white-space:pre-wrap` at checkout (so the IBAN / BIC / beneficiary stay on their own lines) AND injected into the customer confirmation e-mail in the language the customer used to place the order (resolved via the `_p4all_lang` meta persisted at submission time).
* Internal : `Mailer::payment_instructions()` now takes an explicit `$lang` argument and delegates to `Schema::payment_description($method, $lang)` for every method — so any future manual method inherits the same per-language override chain (per-lang → primary-lang → localised default → FR default).
* Translations : « Virement bancaire » label already shipped in the 2.1.0 batch — de : « Banküberweisung », it : « Bonifico bancario », en : « Bank transfer ». The multi-line default templates live in `Schema::PAYMENT_DESC_DEFAULTS['bank_transfer']` as per-language constants.

= 2.2.0 =
* New **Per-form photo size picker** on the form editor (*Affichage › Taille*). Choose *Petite (60 px)*, *Moyenne (120 px)*, *Grande (200 px)* or *Personnaliser…* — the last one reveals a pixel input clamped between 20 and 1000. In *List* layout the value constrains the photo's width; in *Grid* layout its height. Implemented via two CSS custom properties (`--p4all-photo-w`, `--p4all-photo-h`) set on the form wrapper, no inline `<style>` block emitted.
* Documentation : previously introduced *« Remember my information »* checkbox on the customer step is now clearly listed under Core features (persisted in `localStorage` for 1 year, per form, no HTTP cookie).

= 2.1.0 =
* New **« Remember my information » opt-in checkbox** on the customer step. When ticked, the customer's contact fields are persisted in the browser's own `localStorage` (scoped per form, retention 1 year, no HTTP cookie set) and pre-filled on the next visit. Unticking + clicking *Suivant* clears the memory. Ships with FR / DE / IT / EN translations.
* Pay4All Age integration : the *« Vérification d'âge »* step now auto-skips when the current cart carries no age-restricted product. The customer no longer has to click *Suivant* on an empty age-check step just because the form's product catalogue happens to include an adult item they didn't add to their order. Implemented via a generic `data-p4all-skip="1"` contract on any `.p4all-step`, so companion plugins can use the same escape hatch.
* Checkbox alignment polish : the standalone *« Remember me »* checkbox uses the same inline-flex layout as the existing checkbox fields (defensive against themes that turn every input into `display:block`, e.g. Elementor / Astra Ecommerce).

= 2.0.0 =
* **Breaking change — five features moved to a separate Pay4All Pro plugin.** The free version keeps the form builder, products, deliveries, virtual downloads, orders and e-mails; the following move out:
  * Product filter widget on the public form (categories / brands / sort dropdowns).
  * *Categories* taxonomy (`p4all_product_cat`) — including per-language names and per-category quantity rules (min / max / multiple / exact).
  * *Brands* taxonomy (`p4all_product_brand`) — same shape as categories.
  * *Abandoned cart* capture, recovery e-mail and admin page.
  * *Ticket* product type (QR codes for events) — including the *Billets* admin page and the public `/p4all-ticket/…` validation URL.
* **Automatic clean-up on upgrade** — activating 2.0.0 once drops the `wp_p4all_tickets` and `wp_p4all_abandoned_carts` tables, deletes every term in `p4all_product_cat` / `p4all_product_brand`, deletes every ticket post, and un-schedules the hourly `p4all_abandoned_cart_tick` cron event. Idempotent, guarded by an option so re-installs skip it.
* **XLSX export removed** on both the orders CSV screen and the products import/export screen. CSV export is unchanged. XLSX moves to Pro.
* Product edit screen : the *Billet (QR code)* option is removed from the product-type dropdown. Existing tickets keep rendering as *Physical* until edited.
* Settings › General : *Délai d'envoi du mail d'abandon de panier* setting removed (no longer used).
* Help page : reworded to reflect the trimmed feature set.
* Internal : new `Domain\Language` helper replaces `Domain\Category::current_language()` at every call site.

= 1.4.3 =
* New **Content picker next to each URL field** — a *« 🔗 Rechercher »* button opens a mini search panel that queries pages, posts, WooCommerce products, every public custom post type, and media attachments (PDFs, images, mp3…). Clicking a result drops the full URL into the input. WPML / Polylang are detected automatically: on the DE tab the search only returns DE pages, on IT only IT, etc. Media stays language-agnostic (a PDF is the same file in every locale). AJAX endpoint requires `edit_posts` + a nonce.
* Full **DE / IT / EN translations** for the eleven new admin strings introduced by the « Learn more » link, the picker, and the photo lightbox (labels, placeholders, help text, status messages).
* **Plugin Check compliance polish** — `phpcs:disable` / `phpcs:enable` block around the `$_FILES` sanitisation in `MetaBoxes::store_virtual_upload()` (nonce is verified upstream in `save()`); documented ignores for WPML's public `wpml_switch_language` hook (must be invoked verbatim to trigger their language switcher) and for the intentional `suppress_filters` on the media query used by the picker (language-agnostic search is the product decision).

= 1.4.2 =
* New **« Learn more » link per product** — two new fields (URL + link label) on each language tab of the product edit screen. When the URL is set, a link is rendered under the short description in the order form and opens in a new tab. The *Link label* field is pre-filled with a language-specific default on every tab (FR *« Découvrez en plus… »*, DE *« Erfahren Sie mehr… »*, IT *« Scopri di più… »*, EN *« Learn more… »*) regardless of the admin's own locale; the merchant is free to overwrite any of them. Same default kicks in on the front-end so a visitor in DE (or IT / EN) always sees the label in their language, even when the merchant left the field blank. URLs are validated at save with `esc_url_raw()` (http / https / mailto / tel only, `javascript:` rejected).
* New **Product photo lightbox** — clicking the product thumbnail on the first step of the form now opens the full-size photo in an overlay. Keyboard-accessible (Enter / Space to open, ESC to close), non-destructive to the form (body scroll is temporarily locked). No dependency added; pure vanilla JS.
* **Security hardening** — three fixes triggered by an internal security review:
  * *Path traversal fix* in the virtual-download endpoint (`?p4all_dl=`). The customer-facing streamer now rejects any filename containing directory separators, `..`, or NULL bytes, and enforces a `realpath()` boundary check that guarantees the resolved path lives inside the private uploads folder. Protects against manipulated `_p4all_virtual_downloads` meta on stores with a compromised admin account or a SQL injection introduced by a third-party plugin.
  * *X-Frame-Options / CSP frame-ancestors* now emitted on every front-end page rendering the `[pay4all_form]` shortcode. Default: `SAMEORIGIN` — blocks brand impersonation via cross-origin iframes and closes the per-domain-licence bypass some agencies used by iframing a licensed shop on many client sites. Configurable via a new *Réglages › Général* option (`sameorigin` / `deny` / `off`).
  * *Virtual-product upload allowlist* — merchant-configurable list of accepted file extensions (default: `pdf, mp3, mp4, m4a, epub, zip, jpg, jpeg, png, gif, svg, doc(x), xls(x), ppt(x), txt, csv, html`). Blocks executable uploads (`.php`, `.phtml`, `.exe`, `.htaccess`…) BEFORE `wp_handle_upload()` runs. Set the option to an empty value to restore the legacy behaviour of allowing anything. The private uploads folder is now also guarded by a `web.config` deny rule for IIS hosts, in addition to the existing `.htaccess`.
* **Admin menu badge polish** — the "Commandes" count badge next to *Pay4All SHOP* now uses the same red (`#d63638`) and compact sizing as WordPress core's *update available* pill, and stays visible on every admin screen (previously the CSS only loaded on Pay4All Form pages, so the badge disappeared on the Dashboard).
* **Admin menu i18n** — sub-entries *Paramètres* and *Licences* under Pay4All AGE, plus the WooCommerce entry-point label, are now localised in DE / IT / EN based on the admin locale.
* **KYC tab visual polish** — the accordion widget on *Réglages › Protection des mineurs* now matches the visual language of the *Paiements* tab (rounded card, `▾` chevron, language pill), and the accordions honour the languages selected in *Réglages › Langue* (so a DE-only shop only shows the DE accordion here).
* Plugin Check clean-up — nonce comments, `sanitize_text_field( wp_unslash() )` on remaining `$_POST` reads, `esc_url()` on the licence page form action, `wp_delete_file()` in the KYC storage purge, and Plugin Check ignore blocks documented for WPML hooks (`wpml_element_trid`, `wpml_get_element_translations`) that intentionally do not carry the plugin prefix.

= 1.4.1 =
* New **Client-side product filtering** — an optional filter bar can be enabled per form (Form editor › *Affichage des produits* › *Afficher les filtres*). Customers can narrow the product list on the first step by category, brand and sub-taxonomies, with an optional item-count next to each option. Fully client-side, no page reload.
* **Multilingual category / brand labels** — every category and brand can carry a customer-facing name in each activated language, plus a *« Tous les… »* label for filter dropdowns. Empty fields fall back to the WordPress canonical term name. Drag-and-drop reorder from the taxonomy list.
* **Minimum order amount is now per-form** — the *Montant minimum de commande* panel has moved from *Réglages › Commandes* to each form's edit screen (right above *Affichage des produits*), so different forms can have different thresholds. Includes the per-language error message (with `%s` for the amount) and live validation on the product step: the *Suivant* button is blocked and a red alert appears as soon as the subtotal drops below the floor. Existing global setting is honoured as fallback for forms that haven't set their own.
* Compatibility with Pay4All Age plugin improved.

= 1.4.0 =
* New Compatible with Pay4All AGE **Protection of minors** (age verification) — enable per product with the *Requires age verification* checkbox on the product edit screen. At checkout the customer scans a QR code, opens a mobile page and captures their ID (recto + verso) and a selfie; the order cannot be validated until every required slot is present.
* **Cross-device flow** — the QR code embeds a short-lived session token (20 min); the desktop cart polls the server and unlocks automatically as soon as the mobile capture is complete, without asking the customer to reload.
* **Encryption at rest** — every photo is encrypted with AES-256-GCM using a per-order subkey derived via HMAC-SHA256 from a master key stored in `wp-content/uploads/p4all-secure/kyc.key` (outside the database backup). Ciphertexts live in a private folder guarded by `.htaccess` + `index.php` + `web.config`.
* **Admin-only decrypted view** — a *Protection des mineurs* section on the order page streams the decrypted photos through a nonce-protected admin endpoint (never served publicly, never written back to disk).
* **Yellow warning banner** in the admin new-order e-mail when the order requires age verification, with a *Voir la commande* button that links straight to the order edit screen.
* **Auto-purge** — deleting an order also deletes its KYC directory. A daily cron purges session buckets older than 24 h.
* All KYC-related strings translated in FR / DE / IT / EN.

= 1.3.1 =
* New **Timezone** setting in *Settings › General* — surfaces the WordPress global timezone selector for admins who don't know where to find it. Defaults to Europe/Paris when not configured. Modifying it updates the standard WP `timezone_string` option.
* Dates in *Pay4All Shop › Abandon panier* admin page now use `get_date_from_gmt()` for accurate conversion of UTC-stored timestamps to the site's configured timezone.
* Renamed menu label from *Abandon de panier* to *Abandon panier* for compactness; sub-menu translated in all four languages.
* Plugin Check / WP.org compliance: escaped `$cart_id` output in the abandoned-cart view dialog, and added `WordPress.DB.PreparedSQL.InterpolatedNotPrepared` + `PluginCheck.Security.DirectDB.UnescapedDBParameter` ignores on the `wp_p4all_abandoned_carts` custom-table queries (table name built from `$wpdb->prefix` + literal suffix).

= 1.3.0 =
* New **Abandoned cart** capture and recovery — when a customer fills the contact-info step (with their e-mail) and clicks *Next* but never validates, the cart is stored. A recovery e-mail with a one-time link is automatically sent after a configurable delay (default 10 days). Clicking the link returns the customer to the form with all their items, contact fields, delivery and payment method already pre-filled.
* New admin page *Pay4All Shop › Abandon panier* (above *Billets*) — lists every captured cart with filters (pending / e-mail sent / recovered / converted), totals, dates and a delete action.
* New setting *Réglages › Général › Délai d'envoi du mail d'abandon de panier (jours)* — 0 disables, default 10. Carts older than 90 days are automatically purged (GDPR).
* New e-mail template *Abandoned cart recovery* in *Réglages › E-mails*, editable per language (subject / heading / intro / outro), with `{site_name}`, `{customer_firstname}`, `{customer_email}` and `{recover_url}` placeholders.
* GDPR exporter and eraser hooks include abandoned-cart rows in WordPress's *Tools › Export / Erase Personal Data*.
* Hourly cron `p4all_abandoned_cart_tick` sends due recovery e-mails and purges old rows.

= 1.2.0 =
* New **Hide a product** checkbox on the product edit screen — the product is omitted from the form entirely (no badge, no greyed-out field). Server-side rejects any tampered order containing a hidden product. Useful for archiving seasonal items without deleting them.
* Stock-related checkboxes reordered for clarity: *Hide this product* → *Out of stock* → *Manage stock* (+ quantity field).
* New **Stock management** (optional, per product) — tick *Manage stock* on a product to track its quantity. Stock is decremented at every order (one unit per ticket / per item), the customer cannot order more than what is left, and the product is automatically flagged *out of stock* when the stock hits 0.
* **Stock restored on order deletion** — deleting an order definitively (from trash or via the delete button) restores the stock of every product still present in that order. Status changes (paid, cancelled, etc.) do not restore stock; only deletion does.
* **Low-stock e-mail alert** — set *Settings › General › Notify me when stock reaches* (under the shop address) to a non-zero threshold; an alert is e-mailed to the notification address each time a tracked product crosses the threshold downwards after a sale.
* New **Stock** column in the products admin list — shows `—` when stock management is off, the current quantity otherwise (orange if at or below threshold, red `0 Out of stock` when empty).

= 1.1.0 =
* New product type **Virtual** — upload one downloadable file per product, customer receives a one-time link valid 60 days, served by the plugin's protected endpoint (files stored in a private folder with deny-all `.htaccess`).
* New product type **Ticket** — one QR code per seat ordered, sent in the order email, scannable from any smartphone, with a public validation page showing green (available) or red (already used).
* New admin page *Pay4All Shop › Billets* — list every QR ticket, search by first/last name / city / e-mail, validate manually, export to CSV.
* **Out of stock** flag on every product — product stays visible, quantity field is greyed and disabled, server-side rejects tampered orders.
* **Default form fields** (first name, last name, e-mail, phone, address, etc.) are now pre-translated in every activated language at form creation time.
* **Per-delivery payment methods** — accepted payment methods are now configured per delivery method (a delivery edit screen has a *Modes de paiement acceptés* section), so each delivery can show its own payment choices to the customer. Forms without any delivery still use the legacy per-form payment picker.

= 1.0.0 =
* Initial release.

== Upgrade Notice ==

= 2.3.0 =
Adds a « Virement bancaire » payment method with a per-language IBAN / BIC / beneficiary block, shown at checkout and injected into the customer confirmation e-mail in the shopper's own language. No schema change, no manual action.

= 2.2.0 =
Adds a per-form photo size picker (Petite / Moyenne / Grande / Personnaliser). No schema change, no manual action — existing forms keep their previous look until the merchant picks a size.

= 2.1.0 =
Adds an opt-in « Remember my information » checkbox (client-side localStorage, no cookie) and auto-skips the Pay4All Age step when the cart carries no age-restricted product. No schema change, no manual action.

= 2.0.0 =
Breaking : Categories, Brands, Tickets, Abandoned Cart and filter widget move to Pay4All Pro. On upgrade the legacy tables `wp_p4all_tickets` + `wp_p4all_abandoned_carts` are dropped, ticket posts deleted, cron cancelled. Back up first. XLSX export replaced by CSV.

= 1.4.3 =
Adds a « 🔗 Search » button next to each per-language URL field on products to pick a page, post, WooCommerce product or media (PDFs, images…) from the site. Detects WPML / Polylang automatically. Full DE/IT/EN translations. Also clears three Plugin Check nits.

= 1.4.2 =
Adds a per-language « Learn more » link on every product (URL + label, defaults FR/DE/IT/EN) and a full-size photo lightbox on the form. Security pass: path-traversal fix on virtual downloads, SAMEORIGIN default on shortcode pages, extension allowlist on virtual uploads.

= 1.4.1 =
Adds client-side product filtering (per form), multilingual category / brand labels, and moves the *Montant minimum de commande* control to each form (previously global). Existing global threshold stays effective as fallback until you configure per-form values. No schema change, no manual action.

= 1.4.0 =
Adds optional *Protection des mineurs* (age verification) with mobile-side ID + selfie capture, AES-256-GCM encryption at rest, admin-only decrypted view and auto-purge on order deletion. No schema change, no manual action; opt-in per product via the new *Requires age verification* checkbox.

= 1.3.1 =
Adds a Timezone selector in *Settings › General*, fixes abandoned-cart date display via `get_date_from_gmt()`, and clears Plugin Check warnings. No schema change, no manual action.

= 1.3.0 =
Adds abandoned-cart capture + automatic recovery e-mail (configurable delay, default 10 days), new admin page, GDPR-aware. Please deactivate / reactivate once so the new table and hourly cron are installed.

= 1.2.0 =
Adds *Hide this product* and optional per-product stock management: auto out-of-stock at 0, restock on order delete, low-stock e-mail alert (threshold in *Settings*) and a *Stock* admin column. No schema change, no manual action.

= 1.1.2 =
Change name plugin and order menu

= 1.1.0 =
Adds ticket QR codes, virtual file downloads, out-of-stock flag, per-delivery payment methods and pre-translated default fields. Please deactivate and reactivate the plugin once after upgrading so the new `wp_p4all_tickets` table and the `/p4all-ticket/...` rewrite rule are installed.

= 1.0.0 =
First public release.
