📋 Changelog

Historique des mises à jour

Suivez l'évolution de nos plugins et découvrez les dernières améliorations.

← Tous les plugins
🔄

Subscription for Listeo

Dernière version: v1.9.0
v1.9.0 ✨ Dernière 📅 03 juin 2026
[1.9.0] - 2026-06-03

### 🚀 Version majeure — Virement bancaire (complet), SEPA, rapprochement, multi-forfaits & correctifs / Major release — Bank transfer (full), SEPA, reconciliation, multi-package & fixes

---

#### 🇫🇷 Français

##### 🐛 Correctif — Changement de forfait multi-catégories
- Le changement de forfait (`ajax_change_plan` / `ajax_preview_plan_change`) ciblait toujours l'abonnement « annonces » au lieu de l'abonnement de la **catégorie** du nouveau forfait. Désormais, la cible est déterminée via `get_active_subscription_by_category()` selon la catégorie du forfait choisi, ce qui rétablit le fonctionnement multi-forfaits par catégorie.

##### 🏦 Phase 1 — Méthode « Virement bancaire » (passerelle manuelle)
- Nouvelle passerelle `bank_transfer` activable dans les réglages : titulaire, banque, **IBAN/BIC**, instructions et texte personnalisable.
- Au checkout, bouton **« Payer par virement »** disponible pour tout forfait payant → crée l'abonnement en statut **`pending`** et affiche les coordonnées bancaires + une **référence normée (`SFL-{ID}`)**.
- **Page de validation distincte** (shortcode `[sfl_bank_transfer]`), créée automatiquement, où le client confirme le virement et **joint une preuve** (rendue obligatoire ou facultative par l'admin).
- Action admin **« Valider le paiement »** → active l'abonnement (début = date de validation, renouvellement à l'anniversaire).

##### 🔁 Phase 2 — Renouvellement manuel & relances
- Pas d'auto-renouvellement (comme WiPay) : bouton **« Renouveler par virement »** sur la page d'abonnement du client.
- **E-mails de rappel** spécifiques virement avec texte configurable + **délai de grâce** paramétrable (jours après échéance) pour ne pas couper l'accès pendant le traitement.
- Référence normée stockée sur l'abonnement pour le rapprochement.

##### 📊 Phase 3 — Rapprochement & comptabilité
- **Import CSV du relevé bancaire** : matching montant + référence (`SFL-{ID}`), classement des lignes (rapproché / montant différent / déjà traité / anomalie) et **validation en masse**.
- **Journal des ventes enrichi** : export CSV avec mode de paiement, date de règlement, référence/n° facture, entreprise, statut et statut de rapprochement (OK/KO), en plus de l'export existant.
- La validation d'un virement se comporte comme une vraie passerelle : elle **remplace l'abonnement actif de la même catégorie** (pas de doublon).

##### 💳 Prélèvement SEPA via Stripe
- Nouvelle option (`Réglages → Prélèvement SEPA`) qui ajoute le **prélèvement SEPA** au checkout Stripe (devise EUR requise), en plus de la carte.
- Gestion **asynchrone** : l'abonnement reste en attente et n'est **activé qu'à réception du paiement** (`invoice.paid`) ; un rejet (`invoice.payment_failed`) le repasse en impayé. Aucun nouvel événement webhook à ajouter.

##### 🏦 Virement bancaire — Page admin dédiée, formats multi-pays & RIB
- **Nouvelle page admin « Virements à valider »** (sous-menu dédié, avec compteur des virements en attente) : liste centralisée des virements en attente (souscriptions **et** renouvellements) avec référence, client, forfait, montant, type, date, confirmation client et preuve. Bouton **Valider** par ligne qui active l'abonnement à la date du jour et annule, si besoin, l'abonnement actif de la même catégorie.
- **Formats bancaires multi-pays** : nouveau sélecteur « Pays / format » (Zone SEPA, France, États-Unis ACH/Wire, Royaume-Uni Sort code, Canada, Suisse, Maroc, Autre) qui adapte automatiquement les libellés des champs (IBAN/BIC → routing/account → sort code…), côté réglages **et** côté page client. Ajout d'un champ **« Coordonnées complémentaires »** libre pour les comptes hors zone SEPA.
- **RIB / RID téléchargeable** : possibilité de joindre un fichier RIB/RID (PDF/JPG/PNG) via la médiathèque dans les réglages ; un **bouton de téléchargement** s'affiche pour le client sur la page de paiement par virement.

##### 🔁 Virement bancaire — Reprendre/confirmer plus tard & anti-doublon
- **« Confirmer plus tard »** : sur la page de paiement par virement, ajout d'un lien « Je confirmerai plus tard » + message indiquant comment revenir confirmer (la demande et la référence restent enregistrées).
- **Reprendre le paiement** : dans le tableau de bord client (« Mon abonnement » → Historique), les virements en attente affichent désormais un bouton **« Reprendre le paiement par virement »** qui ramène à la page de paiement avec la bonne référence.
- **Anti-doublon renforcé** : si un virement est déjà en attente pour une catégorie et que le client choisit un **autre forfait/périodicité de la même catégorie**, la demande existante est **mise à jour** vers le nouveau forfait au lieu d'en créer une seconde (impossible d'accumuler plusieurs virements en attente pour la même catégorie). Le changement de forfait réinitialise la confirmation/preuve précédente.
- Précision : un abonnement en attente **ne donne aucun accès** ; le forfait n'est assigné qu'à la **validation** du paiement (statut actif). Les comptes administrateur conservent un accès complet indépendamment de l'abonnement.

##### 🐛 Correctif CRITIQUE — Formulaire d'ajout d'annonce (types d'annonces + galerie)
- **Problème** : avec le multi-forfaits, l'**affichage des types d'annonces** et l'**ajout de visuels dans la galerie** pouvaient être cassés lors de l'ajout d'une annonce.
- **Cause** : les hooks SFL du formulaire d'annonce se basaient sur **n'importe quel abonnement actif** au lieu de l'abonnement de **catégorie « Annonces »**. Pour un utilisateur E-commerce (ou une meta périmée), un `chosen_package_id` pointant vers une ligne `listeo_core_user_packages` **inexistante** était injecté, l'étape « package » était supprimée et des règles de modules E-commerce étaient appliquées au formulaire d'annonce.
- **Correctif (sans toucher au cœur de Listeo)** : nouveau helper `get_listings_user_package_id()` (package renvoyé uniquement pour un abonnement **Annonces actif** dont la ligne existe réellement) ; tous les hooks du formulaire d'annonce sont désormais **strictement limités à la catégorie « Annonces »**. Sans abonnement Annonces valide, SFL **se met en retrait** et laisse Listeo fonctionner nativement ; tout `chosen_package_id` résiduel/mort est nettoyé.

##### Traductions
- Toutes les nouvelles chaînes sont traduites en anglais (`en_US`) et le `.mo` est recompilé.

---

#### 🇬🇧 English

##### 🐛 Fix — Multi-category plan change
- Plan change (`ajax_change_plan` / `ajax_preview_plan_change`) always targeted the "listings" subscription instead of the subscription matching the **category** of the new package. The target is now resolved via `get_active_subscription_by_category()` based on the chosen package's category, restoring multi-package behaviour per category.

##### 🏦 Phase 1 — "Bank transfer" method (manual gateway)
- New `bank_transfer` gateway, enabled in settings: holder, bank, **IBAN/BIC**, instructions and custom text.
- At checkout, a **"Pay by bank transfer"** button is available for any paid package → creates the subscription as **`pending`** and shows the bank details + a **standard reference (`SFL-{ID}`)**.
- **Dedicated validation page** (shortcode `[sfl_bank_transfer]`), created automatically, where the customer confirms the transfer and **attaches a proof** (made mandatory or optional by the admin).
- Admin action **"Validate payment"** → activates the subscription (start = validation date, renewal on anniversary).

##### 🔁 Phase 2 — Manual renewal & reminders
- No auto-renewal (like WiPay): **"Renew by bank transfer"** button on the customer's subscription page.
- Bank-transfer-specific **reminder e-mails** with configurable text + a configurable **grace period** (days after expiry) so access is not cut while the transfer is processed.
- Standard reference stored on the subscription for reconciliation.

##### 📊 Phase 3 — Reconciliation & accounting
- **Bank statement CSV import**: amount + reference (`SFL-{ID}`) matching, row classification (reconciled / amount mismatch / already processed / anomaly) and **bulk validation**.
- **Enriched sales journal**: CSV export with payment method, settlement date, reference/invoice no., company, status and reconciliation status (OK/KO), in addition to the existing export.
- Validating a transfer behaves like a real gateway: it **replaces the active subscription of the same category** (no duplicate).

##### 💳 SEPA Direct Debit via Stripe
- New option (`Settings → SEPA Direct Debit`) adding **SEPA Direct Debit** to the Stripe checkout (EUR currency required), in addition to card.
- **Asynchronous** handling: the subscription stays pending and is **only activated once the payment is received** (`invoice.paid`); a failure (`invoice.payment_failed`) sets it back to unpaid. No new webhook event to add.

##### 🏦 Bank transfer — Dedicated admin page, multi-country formats & bank details file
- **New "Bank transfers to validate" admin page** (dedicated submenu with a pending counter): centralized list of pending bank transfers (new subscriptions **and** renewals) with reference, customer, package, amount, type, date, customer confirmation and proof. A per-row **Validate** button activates the subscription as of today and, if needed, cancels the active subscription in the same category.
- **Multi-country bank formats**: new "Country / format" selector (SEPA zone, France, United States ACH/Wire, United Kingdom Sort code, Canada, Switzerland, Morocco, Other) that automatically adapts the field labels (IBAN/BIC → routing/account → sort code…), in both settings **and** the customer page. Added a free **"Additional bank details"** field for accounts outside the SEPA zone.
- **Downloadable bank details (RIB/RID)**: ability to attach a RIB/RID file (PDF/JPG/PNG) via the media library in settings; a **download button** is shown to the customer on the bank transfer payment page.

##### 🔁 Bank transfer — Resume/confirm later & duplicate prevention
- **"Confirm later"**: on the bank transfer payment page, added an "I'll confirm later" link + a note explaining how to come back to confirm (the request and reference stay saved).
- **Resume payment**: in the customer dashboard ("My subscription" → History), pending bank transfers now show a **"Resume bank transfer payment"** button that returns to the payment page with the correct reference.
- **Stronger duplicate prevention**: if a bank transfer is already pending for a category and the customer picks a **different plan/billing interval in the same category**, the existing request is **updated** to the new plan instead of creating a second one. Changing the plan resets the previous confirmation/proof.
- Clarification: a pending subscription **grants no access**; the package is only assigned upon payment **validation** (active status). Administrator accounts keep full access regardless of subscription.

##### 🐛 CRITICAL fix — Add-listing form (listing types + gallery)
- **Problem**: with multi-package, the **listing types display** and **gallery image upload** could break on the add-listing form.
- **Cause**: the SFL add-listing hooks relied on **any active subscription** instead of the **Listings-category** one. For an E-commerce user (or a stale meta), a dead `chosen_package_id` (pointing to a non-existent `listeo_core_user_packages` row) was injected, the package step was removed, and E-commerce module rules were applied to the listing form.
- **Fix (without touching Listeo core)**: new `get_listings_user_package_id()` helper (package returned only for an **active Listings** subscription whose row actually exists); all add-listing hooks are now **strictly scoped to the Listings category**. With no valid Listings subscription, SFL **stands down** and lets Listeo run natively; any stale/dead `chosen_package_id` is cleared.

##### Translations
- All new strings are translated to English (`en_US`) and the `.mo` is recompiled.
v1.8.1 📅 03 juin 2026
[1.8.1] - 2026-06-03

### 🐛 Correction — Annulation et rétrogradation à l'expiration (option « downgrade to free ») / Cancellation & downgrade-on-expiry ("downgrade to free" option)

---

#### 🇫🇷 Français

##### Problème
1. À l'annulation d'un abonnement, le client recevait une alerte « Erreur de connexion » (« Connection error ») alors que l'abonnement passait quand même au statut « annulé ».
2. Lorsqu'un abonnement premium expirait (ou via une date d'expiration manuelle), le vendeur n'était pas rétrogradé vers le forfait gratuit et restait premium.

##### Cause
- La méthode `SFL_Subscription_Manager::get_user_active_subscription()` était **appelée** (dans `auto_downgrade_to_free()`, `SFL_Package_Manager::get_user_active_package()` et `can_user_subscribe()`) mais **n'était jamais définie** — seule `get_active_subscription()` existait. Avec l'option « cancellation action = downgrade to free », l'annulation et l'expiration déclenchaient un **« Fatal error: Call to undefined method »** (HTTP 500). Le statut était mis à jour avant le crash (d'où l'historique « annulé »), mais la rétrogradation/suspension ne s'exécutait jamais, et le client voyait l'erreur de connexion.
- De plus, `get_active_subscription()` ne vérifiait que le `status` en base et **ignorait `current_period_end`**, donc une date d'expiration manuelle n'était appliquée qu'au prochain passage du cron quotidien (jusqu'à 24 h de délai).

##### Corrections
- Ajout de la méthode `get_user_active_subscription()` (alias de `get_active_subscription()`), ce qui supprime le fatal et rétablit l'annulation **et** la rétrogradation automatique vers le forfait gratuit.
- Ajout d'une **vérification temps réel de `current_period_end`** dans `get_active_subscription()` et `get_active_subscription_by_category()` : un abonnement dont la période est dépassée n'est plus considéré comme actif, sans attendre le cron. Les abonnements avec `cancelled_at` (annulation en fin de période payée) conservent leur accès jusqu'à `current_period_end`.

##### Impact
L'annulation n'affiche plus d'erreur de connexion, et la rétrogradation vers le forfait gratuit (ou la suspension) s'applique immédiatement à l'expiration. Prérequis pour « downgrade to free » : un forfait gratuit de repli doit être configuré (`sfl_free_fallback_package_id`).

---

#### 🇬🇧 English

##### Problem
1. When cancelling a subscription, the customer got a "Connection error" alert even though the subscription still moved to the "cancelled" status.
2. When a premium subscription expired (or via a manual expiry date), the vendor was not downgraded to the free plan and stayed premium.

##### Cause
- `SFL_Subscription_Manager::get_user_active_subscription()` was **called** (in `auto_downgrade_to_free()`, `SFL_Package_Manager::get_user_active_package()` and `can_user_subscribe()`) but was **never defined** — only `get_active_subscription()` existed. With the "cancellation action = downgrade to free" option, cancellation and expiry triggered a **"Fatal error: Call to undefined method"** (HTTP 500). The status was updated before the crash (hence the "cancelled" history), but the downgrade/suspension never ran and the customer saw the connection error.
- Additionally, `get_active_subscription()` only checked the DB `status` and **ignored `current_period_end`**, so a manual expiry date only applied on the next daily cron run (up to 24h delay).

##### Fixes
- Added the `get_user_active_subscription()` method (alias of `get_active_subscription()`), removing the fatal and restoring both cancellation **and** automatic downgrade to the free plan.
- Added a **real-time `current_period_end` check** in `get_active_subscription()` and `get_active_subscription_by_category()`: a subscription past its period is no longer considered active, without waiting for the cron. Subscriptions with `cancelled_at` (cancel-at-period-end) keep access until `current_period_end`.

##### Impact
Cancellation no longer shows a connection error, and downgrade to the free plan (or suspension) applies immediately on expiry. Prerequisite for "downgrade to free": a free fallback package must be configured (`sfl_free_fallback_package_id`).

---
v1.8.0 📅 29 mai 2026
[1.8.0] - 2026-05-29

### 🐛 Correction — Date de renouvellement « le jour même » (API Stripe basil) / Wrong "same day" renewal date (Stripe basil API)

---

#### 🇫🇷 Français

##### Problème
Lors de la création d'un abonnement ou d'un changement de forfait, la date de prochain renouvellement affichée restait celle du jour même au lieu du mois suivant.

##### Cause
- Depuis la version d'API Stripe **`2025-03-31.basil`**, les champs `current_period_start` et `current_period_end` ne sont **plus exposés à la racine de l'objet Subscription** mais uniquement sur ses **items** (`items.data[0].current_period_*`). Le code lisait ces champs à la racine → valeur vide → la date enregistrée puis affichée retombait sur « aujourd'hui ».
- En complément, `get_subscription()` / `get_checkout_session()` renvoyaient un **tableau associatif** alors que la quasi-totalité du code les lisait en **notation objet** (`$sub->current_period_end`), ce qui produisait des dates vides sur les chemins de retour de paiement et de webhook.

##### Corrections
- Nouveau helper `SFL_Stripe_Handler::normalize_subscription()` : convertit systématiquement la réponse Stripe en objet **et** rétablit `current_period_start`/`current_period_end` à la racine depuis le premier item lorsqu'ils sont absents (compatibilité API pré-basil **et** basil+).
- `get_subscription()`, `get_checkout_session()` et `change_plan()` renvoient désormais des objets normalisés ; les accès internes (`change_plan`, `preview_plan_change`, `change_subscription_plan`) ont été alignés en notation objet.
- Le webhook `customer.subscription.updated` normalise l'abonnement reçu avant d'enregistrer les dates.
- La synchronisation et l'import Stripe (SDK) effectuent le même repli `items` → racine pour les dates de période.

##### Impact
La date de prochain renouvellement affiche à nouveau correctement le mois (ou l'année) suivant, quelle que soit la version d'API Stripe du compte.

---

#### 🇬🇧 English

##### Problem
When creating a subscription or changing a plan, the next renewal date stayed on today's date instead of next month.

##### Cause
- Since Stripe API version **`2025-03-31.basil`**, `current_period_start` and `current_period_end` are **no longer exposed at the root of the Subscription object** but only on its **items** (`items.data[0].current_period_*`). The code read those fields at the root → empty value → the stored/displayed date fell back to "today".
- Additionally, `get_subscription()` / `get_checkout_session()` returned an **associative array** while almost all consumers read them using **object syntax** (`$sub->current_period_end`), producing empty dates on the payment-return and webhook paths.

##### Fixes
- New helper `SFL_Stripe_Handler::normalize_subscription()`: always converts the Stripe response to an object **and** restores root-level `current_period_start`/`current_period_end` from the first item when missing (pre-basil **and** basil+ compatible).
- `get_subscription()`, `get_checkout_session()` and `change_plan()` now return normalized objects; internal accesses (`change_plan`, `preview_plan_change`, `change_subscription_plan`) were aligned to object syntax.
- The `customer.subscription.updated` webhook normalizes the incoming subscription before saving dates.
- Stripe sync and (SDK) import apply the same `items` → root fallback for period dates.

##### Impact
The next renewal date is once again correctly displayed as next month (or next year), regardless of the account's Stripe API version.

---

### 🧩 Nouveauté — Multi-forfaits par catégorie + ciblage des forfaits par rôle

---

#### 🇫🇷 Français

Ajout du **multi-forfaits** : un même utilisateur peut désormais détenir **plusieurs abonnements actifs en parallèle, un par catégorie de forfait**. Cela restaure le comportement multi-forfaits natif de Listeo qui était annulé par le modèle « un seul abonnement actif » des versions précédentes.

**Nouveautés :**
- **Catégorie de forfait** (champ sur chaque forfait SFL) : `Annonces` ou `E-commerce`. Un abonné peut cumuler **un forfait Annonces + un forfait E-commerce** simultanément (ex. PREMIUM Annonces + STARTER E-commerce). Plus besoin de dupliquer chaque forfait pour intégrer l'e-commerce.
- **Ciblage par rôle** : sur chaque forfait, possibilité de choisir **à quels rôles le forfait est affiché** (vide = tous les rôles). Les forfaits E-commerce, n'étant pas filtrés par rôle, restent communs à tous les rôles → on ne multiplie plus les forfaits par rôle.

**Détails techniques :**
- `create_subscription()` n'annule désormais **que** l'abonnement actif de la **même catégorie** (vrai changement de forfait intra-catégorie), au lieu d'annuler n'importe quel abonnement actif.
- Nouvelles API : `get_active_subscription_by_category()`, `get_active_subscriptions_by_category()` ; `get_active_subscription()` privilégie la catégorie *Annonces* (rétro-compatibilité totale du tableau de bord et des limites).
- `assign_package_to_user()` est désormais conscient de la catégorie : un forfait E-commerce n'écrase plus les permissions d'annonces de l'utilisateur (limites/modules) et n'active que l'accès e-commerce/Dokan via sa propre ligne `listeo_core_user_packages`.
- Expiration/annulation gérées par catégorie : un forfait E-commerce qui expire **ne touche pas aux annonces** (`revoke_ecommerce_package()`), et inversement.
- La publication d'annonces requiert explicitement un abonnement de catégorie *Annonces* (un abonnement E-commerce seul ne donne pas ce droit).
- Affichage de la page des forfaits filtré selon le rôle de l'utilisateur connecté.
- **Page « Mon abonnement » enrichie** : affichage **côte à côte** du forfait *Annonces* (carte principale) et du forfait *E-commerce* (carte dédiée), chacun avec son statut, sa facturation, son renouvellement et son propre bouton **Annuler** / **Changer de forfait**.
- **Badge de catégorie** : visible dans l'admin (colonne « Catégorie » de la liste des forfaits, bleu = Annonces / violet = E-commerce) **et côté client** sur les cartes de la page des forfaits.
- **Documentation** mise à jour (`admin-documentation.php`) avec une section dédiée à la nouveauté (catégories, fonctionnalités E-commerce, badge, ciblage par rôle).
- Nouvelles chaînes traduites (FR/EN), y compris la documentation, et `.mo` recompilés.

**Compatibilité :** les abonnements et forfaits existants sont traités comme catégorie *Annonces* par défaut → aucun changement de comportement pour l'existant.

---

#### 🇬🇧 English

Added **multi-package support**: a single user can now hold **several active subscriptions in parallel, one per package category**. This restores Listeo's native multi-package behaviour, which was cancelled by the previous "single active subscription" model.

**What's new:**
- **Package category** (field on each SFL package): `Listings` or `E-commerce`. A subscriber can hold a **Listings package + an E-commerce package** at the same time (e.g. PREMIUM Listings + STARTER E-commerce). No more duplicating every package just to add e-commerce.
- **Per-role targeting**: each package can choose **which roles can see it** (empty = all roles). E-commerce packages, not filtered by role, stay shared across all roles → no more multiplying packages per role.

**Technical details:**
- `create_subscription()` now cancels **only** the active subscription of the **same category** (a true in-category plan change) instead of cancelling any active subscription.
- New APIs: `get_active_subscription_by_category()`, `get_active_subscriptions_by_category()`; `get_active_subscription()` favours the *Listings* category (full backward compatibility for dashboard and limits).
- `assign_package_to_user()` is now category-aware: an E-commerce package no longer overwrites the user's listing permissions (limits/modules) and only enables e-commerce/Dokan access via its own `listeo_core_user_packages` row.
- Expiry/cancellation handled per category: an expiring E-commerce package **does not affect listings** (`revoke_ecommerce_package()`), and vice versa.
- Listing submission explicitly requires a *Listings* category subscription (an E-commerce subscription alone does not grant it).
- Packages page display filtered by the logged-in user's role.
- **Enhanced "My Subscription" page**: shows the *Listings* package (main card) and the *E-commerce* package (dedicated card) **side by side**, each with its status, billing, renewal and its own **Cancel** / **Change package** button.
- **Category badge**: shown in the admin (the packages list "Category" column, blue = Listings / purple = E-commerce) **and on the client side** on the packages page cards.
- **Documentation** updated (`admin-documentation.php`) with a dedicated section about the new feature (categories, E-commerce features, badge, per-role targeting).
- New translatable strings (FR/EN), including the documentation, and recompiled `.mo` files.

**Compatibility:** existing subscriptions and packages are treated as the *Listings* category by default → no behaviour change for existing data.

---
v1.7.8 📅 29 mai 2026
[1.7.8] - 2026-05-29

### 🌐 Correction — Fichiers de langue (sujets d'emails en français + français résiduel)

---

#### 🇫🇷 Français

Réparation complète des fichiers de traduction. En anglais (site réglé sur English), les **sujets des emails envoyés aux utilisateurs** restaient en français et plusieurs textes du back-office s'affichaient encore en français.

**Causes identifiées :**
- **Corruption des fichiers `.po`/`.mo`** : toutes les chaînes contenant une apostrophe (`'`) avaient été tronquées (ex. `d'essai` → `d"`), cassant ~180 entrées en anglais et ~240 en français. Un `msgid` corrompu ne correspond plus au code → repli automatique sur le texte source (français).
- **Sujets d'emails non/mal traduits** : ex. le sujet « Bienvenue » restait en français, « Échec de paiement » traduit en « Failed of paiement ».
- **~270 chaînes jamais traduites** en anglais (français résiduel sur le backend).
- Rappel important : WordPress lit le `.mo` **compilé**, pas le `.po` — éditer le `.po` sans recompiler n'avait aucun effet.

**Corrections :**
- Régénération d'un modèle `.pot` **propre** directement depuis le code source (1755 chaînes).
- Reconstruction de `subscription-for-listeo-en_US.po` : récupération des traductions valides existantes + traduction des chaînes manquantes/corrompues → **100% traduit (1755/1755)**, dont tous les sujets d'emails.
- Reconstruction de `subscription-for-listeo-fr_FR.po` (aligné sur la source française, sans corruption).
- Recompilation des fichiers `.mo` (`subscription-for-listeo-en_US.mo` / `fr_FR.mo`) et vérification binaire via gettext.

**Impact :** sur un site en anglais, les sujets d'emails et l'interface d'administration s'affichent désormais entièrement en anglais.

---

#### 🇬🇧 English

Full repair of the translation files. In English (site set to English), the **subject lines of emails sent to users** stayed in French and several back-office texts were still displayed in French.

**Root causes:**
- **Corrupted `.po`/`.mo` files**: every string containing an apostrophe (`'`) had been truncated (e.g. `d'essai` → `d"`), breaking ~180 English and ~240 French entries. A corrupted `msgid` no longer matches the code → automatic fallback to the source text (French).
- **Untranslated/mistranslated email subjects**: e.g. the "Welcome" subject stayed in French, "Payment failed" was translated as "Failed of paiement".
- **~270 strings never translated** to English (residual French in the backend).
- Important reminder: WordPress reads the **compiled** `.mo`, not the `.po` — editing the `.po` without recompiling had no effect.

**Fixes:**
- Regenerated a **clean** `.pot` template directly from source code (1755 strings).
- Rebuilt `subscription-for-listeo-en_US.po`: salvaged existing valid translations + translated the missing/corrupted strings → **100% translated (1755/1755)**, including all email subjects.
- Rebuilt `subscription-for-listeo-fr_FR.po` (aligned to the French source, no corruption).
- Recompiled the `.mo` files (`subscription-for-listeo-en_US.mo` / `fr_FR.mo`) and verified the binaries via gettext.

**Impact:** on an English site, email subjects and the admin interface are now fully displayed in English.

---
v1.7.7 📅 29 mai 2026
[1.7.7] - 2026-05-29

### 🐛 Correction — Bascule automatique vers le forfait gratuit à l'expiration

---

#### 🇫🇷 Français

Correction de l'option **« Basculer automatiquement vers le forfait gratuit »** (`sfl_cancellation_action = downgrade_to_free`) qui ne s'appliquait pas : à l'expiration, l'abonnement passait simplement en statut « expiré » au lieu de transférer le vendeur vers le forfait gratuit configuré.

**Cause :**
- Dans les handlers **Stripe** et **PayPal**, la méthode `suspend_user_listings()` était appelée avec `$subscription->user_id` (un entier) alors qu'elle attend l'objet `$subscription` complet (elle lit `$subscription->user_id` en interne). Résultat : l'`user_id` était évalué à `0`, la bascule vers le forfait gratuit ne s'exécutait jamais (et PHP 8 émettait l'avertissement *« Attempt to read property 'user_id' on int »*).
- Dans la synchronisation des passerelles (`class-sfl-subscription-sync.php`), à la détection d'un abonnement annulé/expiré, le code appelait une méthode **inexistante** `revoke_package_from_user()`, provoquant une erreur fatale *« Call to undefined method »* qui interrompait le traitement.

**Corrections :**
- `class-sfl-stripe-handler.php` — les 2 appels passent désormais l'objet `$subscription`.
- `class-sfl-paypal-handler.php` — les 3 appels (annulation, suspension, expiration) passent désormais l'objet `$subscription`.
- `class-sfl-subscription-sync.php` — les 2 appels (sync Stripe + PayPal) utilisent désormais `suspend_user_listings( $subscription )`, qui respecte le réglage `downgrade_to_free`.

**Impact :** quel que soit le chemin d'expiration (webhook passerelle, synchronisation horaire ou cron quotidien), l'abonné est désormais correctement basculé vers le forfait gratuit choisi au lieu de rester « expiré ».

---

#### 🇬🇧 English

Fixed the **"Automatically switch to the free package"** option (`sfl_cancellation_action = downgrade_to_free`) which was not being applied: on expiry, the subscription simply turned to "expired" instead of moving the vendor to the configured free package.

**Cause:**
- In the **Stripe** and **PayPal** handlers, `suspend_user_listings()` was called with `$subscription->user_id` (an integer) while it expects the full `$subscription` object (it reads `$subscription->user_id` internally). As a result, the `user_id` evaluated to `0`, the downgrade to the free package never ran (and PHP 8 emitted the *"Attempt to read property 'user_id' on int"* warning).
- In the gateway sync (`class-sfl-subscription-sync.php`), when a cancelled/expired subscription was detected, the code called a **non-existent** method `revoke_package_from_user()`, causing a fatal *"Call to undefined method"* error that aborted processing.

**Fixes:**
- `class-sfl-stripe-handler.php` — both calls now pass the `$subscription` object.
- `class-sfl-paypal-handler.php` — all 3 calls (cancellation, suspension, expiration) now pass the `$subscription` object.
- `class-sfl-subscription-sync.php` — both calls (Stripe + PayPal sync) now use `suspend_user_listings( $subscription )`, which honors the `downgrade_to_free` setting.

**Impact:** regardless of the expiry path (gateway webhook, hourly sync or daily cron), the subscriber is now correctly switched to the chosen free package instead of staying "expired".
v1.7.6 📅 19 mai 2026
[1.7.6] - 2026-05-19

### 🔗 Intégration — Recurring Bookings for Listeo (LRB)

---

#### 🇫🇷 Français

Préparation et intégration complète du plugin **Recurring Bookings for Listeo** (LRB) dans l'écosystème SFL.

##### Ce qui a été ajouté dans SFL pour supporter LRB

**Nouveau champ dans les forfaits (`class-sfl-package-commission.php`)** :
- Ajout d'une option `_sfl_allow_recurring_bookings` sur chaque produit WooCommerce lié à un forfait SFL.
- Visible dans l'éditeur de produit sous la section *Recurring Bookings for Listeo*, uniquement si le plugin LRB est actif.
- Permet à l'admin de choisir, forfait par forfait, si les vendeurs abonnés peuvent proposer des réservations récurrentes à leurs clients.

**Capture dans le snapshot d'abonnement (`class-sfl-subscription-manager.php`)** :
- La valeur `_sfl_allow_recurring_bookings` est désormais capturée dans le snapshot de l'abonnement au moment de l'achat.
- Cela garantit que les modifications ultérieures du forfait n'affectent pas les abonnements existants.
- Structure dans le snapshot :
```json
"recurring_bookings": { "allowed": true|false }
```

**Nouvelles méthodes dans `SFL_Subscription_Manager`** :
- `get_recurring_bookings_option( $user_id )` — vérifie si l'abonnement actif d'un vendeur autorise les réservations récurrentes (lecture depuis le snapshot en priorité, sinon depuis le forfait courant).
- `owner_allows_recurring_bookings( $owner_id )` (static) — méthode publique utilisée par LRB pour vérifier si un propriétaire d'annonce peut utiliser les fonctionnalités de réservation récurrente. Les admins sont toujours autorisés.

**Traductions** :
- Nouvelles chaînes ajoutées dans `subscription-for-listeo-fr_FR.po` et `subscription-for-listeo-en_US.po`.
- Fichiers `.mo` recompilés.

##### Impact sur LRB
Grâce à cette intégration, le plugin LRB peut désormais :
- Masquer ses menus dashboard (client et vendeur) si le vendeur n'a pas de forfait SFL actif autorisant les récurrences.
- Masquer le widget de réservation récurrente sur les annonces des vendeurs non éligibles.
- S'appuyer sur le snapshot pour des vérifications fiables même après modification du forfait.

---

#### 🇬🇧 English

Full preparation and integration of the **Recurring Bookings for Listeo** (LRB) plugin into the SFL ecosystem.

##### What was added to SFL to support LRB

**New field on packages (`class-sfl-package-commission.php`)** :
- Added a `_sfl_allow_recurring_bookings` option on each WooCommerce product linked to an SFL package.
- Visible in the product editor under the *Recurring Bookings for Listeo* section, only when the LRB plugin is active.
- Allows the admin to decide, package by package, whether subscribed vendors can offer recurring bookings to their clients.

**Snapshot capture (`class-sfl-subscription-manager.php`)** :
- The `_sfl_allow_recurring_bookings` value is now captured in the subscription snapshot at the time of purchase.
- This ensures that future package modifications do not affect existing subscriptions.
- Snapshot structure:
```json
"recurring_bookings": { "allowed": true|false }
```

**New methods in `SFL_Subscription_Manager`** :
- `get_recurring_bookings_option( $user_id )` — checks whether a vendor's active subscription allows recurring bookings (reads from snapshot first, then from the current package).
- `owner_allows_recurring_bookings( $owner_id )` (static) — public method used by LRB to check whether a listing owner can use recurring booking features. Admins are always allowed.

**Translations** :
- New strings added to `subscription-for-listeo-fr_FR.po` and `subscription-for-listeo-en_US.po` for recurring bookings option labels.
- `.mo` files recompiled.

##### Impact on LRB
Thanks to this integration, the LRB plugin can now:
- Hide its dashboard menus (client and vendor) if the vendor does not have an active SFL package that enables recurring bookings.
- Hide the recurring booking widget on listings owned by ineligible vendors.
- Rely on the snapshot for reliable checks even after the package has been modified.

---

### 🐛 Correction — Date de renouvellement incorrecte (même jour) / Wrong renewal date (same day)

---

#### 🇫🇷 Français

##### Problème
Lors de la création d'un abonnement PayPal ou d'un changement de forfait (Stripe ou PayPal), la date de prochain renouvellement affichée était la date du jour au lieu du mois suivant.

##### Causes identifiées et corrections

**1. Stripe – changement de forfait (`class-sfl-stripe-handler.php`)**
- **Cause** : Lors d'un changement mensuel↔annuel, Stripe réinitialisait automatiquement l'ancre du cycle de facturation à aujourd'hui si `billing_cycle_anchor` n'était pas spécifié.
- **Correction** : Ajout de `billing_cycle_anchor: 'unchanged'` dans la requête de mise à jour Stripe, garantissant que la date de renouvellement reste inchangée.
- **Bonus** : `process_stripe_plan_change` met maintenant à jour `current_period_end` localement depuis la réponse Stripe directe (sans attendre le webhook).

**2. PayPal – handler de retour (`handle_paypal_subscription_return`)**
- **Cause** : La création de l'abonnement local se faisait sans `billing_interval`, `current_period_start` ni `current_period_end` — tous restaient à `null`.
- **Correction** : Les données de facturation sont maintenant extraites depuis la réponse de l'API PayPal (`start_time`, `billing_info.next_billing_time`, `custom_id` JSON) avant d'insérer l'abonnement.

**3. PayPal – webhook `BILLING.SUBSCRIPTION.ACTIVATED`**
- **Cause** : Quand l'abonnement local existait déjà (créé par le handler de retour), le webhook ne mettait à jour que `status = 'active'`, laissant les dates erronées.
- **Correction** : Le webhook met désormais également à jour `current_period_start`, `current_period_end` et `billing_interval` pour les abonnements existants.

---

#### 🇬🇧 English

##### Problem
When creating a PayPal subscription or changing a plan (Stripe or PayPal), the next renewal date displayed was showing today's date instead of next month.

##### Root causes and fixes

**1. Stripe – plan change (`class-sfl-stripe-handler.php`)**
- **Cause**: When switching monthly↔yearly, Stripe automatically reset the billing cycle anchor to today if `billing_cycle_anchor` was not specified.
- **Fix**: Added `billing_cycle_anchor: 'unchanged'` to the Stripe update request, ensuring the renewal date is preserved.
- **Bonus**: `process_stripe_plan_change` now immediately updates `current_period_end` locally from the Stripe API response (without waiting for the webhook).

**2. PayPal – return handler (`handle_paypal_subscription_return`)**
- **Cause**: The local subscription was created without `billing_interval`, `current_period_start`, or `current_period_end` — all defaulting to `null`.
- **Fix**: Billing data is now extracted from the PayPal API response (`start_time`, `billing_info.next_billing_time`, `custom_id` JSON) before inserting the local subscription.

**3. PayPal – webhook `BILLING.SUBSCRIPTION.ACTIVATED`**
- **Cause**: When the local subscription already existed (created by the return handler), the webhook only updated `status = 'active'`, leaving the dates incorrect.
- **Fix**: The webhook now also updates `current_period_start`, `current_period_end`, and `billing_interval` for existing subscriptions.
v1.7.5 📅 19 mai 2026
[1.7.5] - 2026-05-19

### ✨ Nouvelle fonctionnalité — Basculement automatique vers le forfait gratuit à l'expiration

---

#### 🇫🇷 Français

##### Contexte
Lorsqu'un abonnement premium expire et que l'abonné ne renouvelle pas, le plugin pouvait uniquement suspendre les annonces, retirer les features premium ou ne rien faire. Il n'était pas possible de réinserrer automatiquement l'utilisateur dans un forfait gratuit.

##### Nouvelle option : "Basculer automatiquement vers le forfait gratuit"

- **Emplacement** : Subscriptions → Paramètres → Options avancées → *Action à l'annulation / expiration*
- **Nouvelle valeur** : `downgrade_to_free` — ⬇️ Basculer automatiquement vers le forfait gratuit
- **Sélecteur de forfait de repli** : un second champ apparaît (visible uniquement quand cette option est choisie) permettant de sélectionner quel forfait gratuit (prix = 0) est utilisé comme repli.

##### Ce qui se passe à l'expiration (avec cette option activée)

1. **Les features premium sont retirées** : `_featured`, `_listing_featured` supprimés sur toutes les annonces.
2. **Un nouvel abonnement gratuit/lifetime** est créé en base de données pour l'utilisateur.
3. **Le package Listeo gratuit est assigné** : la table `listeo_core_user_packages` est mise à jour avec les nouvelles limites du forfait gratuit.
4. **Les annonces excédentaires sont suspendues** : si l'utilisateur avait 100 annonces et que le forfait gratuit autorise 5, les **5 plus récentes** restent actives, les 95 autres passent en `sfl_suspended` (raison : `downgrade_excess`).
5. **Les metas WC legacy** (`_listeo_package_id`, `_listeo_listing_limit`, etc.) sont mises à jour.
6. **Hooks déclenchés** : `sfl_subscription_created` et `sfl_subscription_auto_downgraded_to_free` (pour intégrations tierces et emails).

##### Comportement de repli
Si aucun package gratuit n'est configuré ou si le package est introuvable, le plugin replie sur la suspension normale (`suspend`) avec un log d'erreur.

##### Fichiers modifiés
- `subscription-for-listeo.php` — `handle_settings()`, `on_auto_downgrade_assign_wc_package()`, hook `sfl_auto_downgrade_assign_wc_package`
- `includes/class-sfl-subscription-manager.php` — `suspend_user_listings()` (nouveau cas `downgrade_to_free`), nouvelle méthode `auto_downgrade_to_free()`
- `templates/admin-settings.php` — nouvelle option dans le sélecteur + sélecteur de forfait conditionnel

---

#### 🇬🇧 English

##### Context
When a premium subscription expired and the subscriber did not renew, the plugin could only suspend listings, strip premium features, or do nothing. There was no way to automatically re-enroll the user in a free package.

##### New option: "Automatically switch to free package"

- **Location**: Subscriptions → Settings → Advanced Options → *Action on cancellation / expiry*
- **New value**: `downgrade_to_free` — ⬇️ Automatically switch to the free package
- **Fallback package selector**: a second field appears (visible only when this option is selected) to choose which free package (price = 0) is used as the fallback.

##### What happens on expiry (with this option enabled)

1. **Premium features are removed**: `_featured`, `_listing_featured` deleted from all listings.
2. **A new free/lifetime subscription** is created in the database for the user.
3. **The free Listeo package is assigned**: the `listeo_core_user_packages` table is updated with the free package's limits.
4. **Excess listings are suspended**: if the user had 100 listings and the free package allows 5, the **5 most recent** stay active; the other 95 become `sfl_suspended` (reason: `downgrade_excess`).
5. **WC legacy metas** (`_listeo_package_id`, `_listeo_listing_limit`, etc.) are updated.
6. **Hooks fired**: `sfl_subscription_created` and `sfl_subscription_auto_downgraded_to_free` (for third-party integrations and emails).

##### Fallback behavior
If no free package is configured or the package cannot be found, the plugin falls back to normal suspension (`suspend`) with an error log entry.

##### Modified files
- `subscription-for-listeo.php` — `handle_settings()`, `on_auto_downgrade_assign_wc_package()`, hook `sfl_auto_downgrade_assign_wc_package`
- `includes/class-sfl-subscription-manager.php` — `suspend_user_listings()` (new `downgrade_to_free` case), new method `auto_downgrade_to_free()`
- `templates/admin-settings.php` — new option in selector + conditional free package selector

---
v1.7.4 📅 04 avril 2026
[1.7.4] - 2026-04-04

### ✨ Nouvelle fonctionnalité: modules "premium verrouillés" pour forfait gratuit

#### 🇫🇷 Français

Ajout d'un mode **visible mais verrouillé** pour les modules désactivés d'un forfait gratuit.

- Nouvelle metabox sur l'édition produit WooCommerce (`listing_package` / `listing_package_subscription`) pour choisir, module par module, lesquels restent visibles en mode premium.
- Comportement frontend: module affiché en grisé avec message **"premium feature - upgrade"** + lien vers la page des forfaits.
- L'option n'est cochable que si le produit est gratuit **et** que le module est désactivé.

#### 🇬🇧 English

Added a **visible but locked** mode for disabled modules on free plans.

- New metabox on WooCommerce product edit (`listing_package` / `listing_package_subscription`) to choose which disabled modules stay visible as premium-locked.
- Frontend behavior: module remains visible in grey mode with **"premium feature - upgrade"** message and upgrade link.
- The option is clickable only when the product is free **and** the module is disabled.

---
v1.7.3 📅 01 avril 2026
[1.7.3] - 2026-04-01

### 🐛 Fix: Fatal Error `debug_coupons_page_setup`

#### 🇫🇷 Français

Correction d'une erreur fatale PHP qui crashait le dashboard Listeo.

- **Cause** : un hook `add_action( 'wp_footer', 'debug_coupons_page_setup' )` restait dans le constructeur de `class-sfl-frontend.php` alors que la méthode correspondante avait été supprimée.
- **Impact** : Fatal Error sur toutes les pages du dashboard, empêchant l'exécution du footer (et donc tous les scripts injectés via `wp_footer`).
- **Fix** : suppression du hook orphelin.

**Fichier modifié :** `includes/class-sfl-frontend.php`

---

#### 🇬🇧 English

Fixed a PHP fatal error crashing the Listeo dashboard.

- **Cause**: an `add_action( 'wp_footer', 'debug_coupons_page_setup' )` hook remained in the `class-sfl-frontend.php` constructor while the method had been removed.
- **Impact**: Fatal Error on all dashboard pages, preventing footer execution (and all scripts injected via `wp_footer`).
- **Fix**: removed the orphan hook.

**File changed:** `includes/class-sfl-frontend.php`

---
v1.7.2 📅 01 avril 2026
[1.7.2] - 2026-04-01

### 🐛 Fix: Exclusion de suspension — réactivation des annonces + alertes + vérifications globales

#### 🇫🇷 Français

Correction de plusieurs bugs liés au système d'exclusion de suspension par rôles et emails.

##### Bug 1 — `reactivate_excluded_users_suspended_listings()` ne réactivait rien

- Cause : `get_users( array( 'fields' => array('ID','roles','user_email') ) )` retourne des `stdClass`, pas des `WP_User`.
- La vérification `$user instanceof WP_User` dans `is_user_excluded_from_suspension_by_settings()` échouait systématiquement.
- Fix : passage à `'fields' => 'all'` pour obtenir des objets `WP_User` complets.

##### Bug 2 — Vérification per-user ne réactivait pas les annonces exclues

- `check_user_listings_status()` ne vérifiait pas si l'utilisateur était exclu avant la logique de suspension.
- Un utilisateur exclu avec des annonces déjà suspendues restait suspendu indéfiniment.
- Fix : ajout d'un check `should_exclude_user_from_suspension()` en amont + appel `reactivate_excluded_user_listings()`.

##### Bug 3 — Vérification globale ignorait les annonces suspendues des exclus

- `check_all_listings_globally()` ne requêtait que `publish` et `pending`, excluant les annonces `sfl_suspended`.
- Les annonces déjà suspendues pour des utilisateurs exclus n'étaient jamais trouvées pour réactivation.
- Fix : ajout de `sfl_suspended` dans la requête SQL + appel `reactivate_excluded_user_listings()` pour les utilisateurs exclus.

##### Bug 4 — Alerte "Pas d'abonnement" affichée pour les utilisateurs exclus

- `inject_subscription_section_js()` affichait l'alerte même pour les utilisateurs exclus sans abonnement.
- Fix : si l'utilisateur est exclu et n'a pas d'abonnement, la méthode retourne sans afficher l'alerte.

##### Nouvelle méthode `reactivate_excluded_user_listings()`

- Réactive toutes les annonces `sfl_suspended` d'un utilisateur exclu vers leur statut précédent (`_sfl_previous_status`).
- Nettoie les metas de suspension (`_sfl_suspended`, `_sfl_suspended_date`, `_sfl_suspended_no_subscription`, `_sfl_suspended_reason`, `_sfl_previous_status`).

##### Coupon Widget dans le formulaire d'édition d'annonce

- La section "Coupon Widget Settings" disparaissait lors de l'édition d'une annonce approuvée (hook `submit_listing_form_fields_get_user_data` ne se déclenchait pas à l'édition).
- Fix : hook supplémentaire `listeo_listing_type_fields` pour injecter la section coupon dans les champs de base du formulaire (création ET édition).
- Fix : hook `listeo_core_listing_submitted` pour définir `_user_package_id` et `_package_id` sur les nouvelles annonces SFL, permettant à Listeo de résoudre le package lors de l'édition.

**Fichiers modifiés :**
- `subscription-for-listeo.php`
- `includes/class-sfl-frontend.php`
- `includes/class-sfl-subscription-manager.php`

---

#### 🇬🇧 English

Fixed multiple bugs in the suspension exclusion system (by roles and emails).

##### Bug 1 — `reactivate_excluded_users_suspended_listings()` never reactivated anything

- Cause: `get_users( array( 'fields' => array('ID','roles','user_email') ) )` returns `stdClass` objects, not `WP_User`.
- The `$user instanceof WP_User` check in `is_user_excluded_from_suspension_by_settings()` always failed.
- Fix: changed to `'fields' => 'all'` to get full `WP_User` objects.

##### Bug 2 — Per-user check did not reactivate excluded users' listings

- `check_user_listings_status()` did not check exclusion before running suspension logic.
- An excluded user with already-suspended listings remained suspended indefinitely.
- Fix: added `should_exclude_user_from_suspension()` early check + call to `reactivate_excluded_user_listings()`.

##### Bug 3 — Global check ignored suspended listings of excluded users

- `check_all_listings_globally()` only queried `publish` and `pending`, missing `sfl_suspended` listings.
- Already-suspended listings for excluded users were never found for reactivation.
- Fix: added `sfl_suspended` to the SQL query + call to `reactivate_excluded_user_listings()` for excluded users.

##### Bug 4 — "No subscription" alert shown for excluded users

- `inject_subscription_section_js()` displayed the alert even for excluded users without a subscription.
- Fix: if the user is excluded and has no subscription, the method returns without displaying the alert.

##### New method `reactivate_excluded_user_listings()`

- Reactivates all `sfl_suspended` listings for an excluded user back to their previous status (`_sfl_previous_status`).
- Cleans up suspension metas (`_sfl_suspended`, `_sfl_suspended_date`, `_sfl_suspended_no_subscription`, `_sfl_suspended_reason`, `_sfl_previous_status`).

##### Coupon Widget in listing edit form

- The "Coupon Widget Settings" section disappeared when editing an approved listing (`submit_listing_form_fields_get_user_data` hook did not fire during editing).
- Fix: additional `listeo_listing_type_fields` hook to inject the coupon section into base form fields (both creation AND editing).
- Fix: `listeo_core_listing_submitted` hook to set `_user_package_id` and `_package_id` on new SFL listings, allowing Listeo to resolve the package during editing.

**Files changed:**
- `subscription-for-listeo.php`
- `includes/class-sfl-frontend.php`
- `includes/class-sfl-subscription-manager.php`

---
v1.7.1 📅 26 mars 2026
[1.7.1] - 2026-03-26

### 🐛 Fix: Menu Coupons masqué à tort pour abonnements SFL actifs

#### 🇫🇷 Français

Correction d'un bug où le menu **Coupons** du dashboard pouvait rester caché même avec un abonnement SFL actif autorisant les coupons.

- Cause: la logique frontend lisait uniquement `get_user_meta( _sfl_option_coupons )`.
- En cas de méta vide (abonnement manuel/ancien flux), le menu était masqué malgré `package_option_coupons = 1` dans `listeo_core_user_packages`.
- Ajout d'un **fallback robuste** côté frontend:
- source 1: `_sfl_option_*` (user meta),
- source 2: package actif utilisateur (`_sfl_current_user_package_id` -> `listeo_core_user_packages`).
- Renforcement de `assign_package_to_user()` pour synchroniser systématiquement les metas `_sfl_option_*` après assignation.
- Même logique de fallback appliquée au module Dokan (`_sfl_option_dokan_store`).

**Fichiers modifiés :**
- `includes/class-sfl-frontend.php`
- `includes/class-sfl-subscription-manager.php`

---

#### 🇬🇧 English

Fixed an issue where the dashboard **Coupons** menu could remain hidden even when the user had an active SFL subscription that allows coupons.

- Root cause: frontend visibility relied only on `get_user_meta( _sfl_option_coupons )`.
- If that meta was empty (manual subscription / legacy flow), the menu was hidden even when `package_option_coupons = 1` in `listeo_core_user_packages`.
- Added a **reliable frontend fallback**:
- source 1: `_sfl_option_*` user meta,
- source 2: active user package (`_sfl_current_user_package_id` -> `listeo_core_user_packages`).
- Hardened `assign_package_to_user()` to always sync `_sfl_option_*` metas after package assignment.
- Applied the same fallback approach to Dokan module visibility (`_sfl_option_dokan_store`).

**Files changed:**
- `includes/class-sfl-frontend.php`
- `includes/class-sfl-subscription-manager.php`

---

### 🛠 Patch: Masquer Coupons si aucun abonnement actif

#### 🇫🇷 Français

Correctif de comportement du menu dashboard: quand un utilisateur n'a **pas d'abonnement SFL actif**, l'entrée **Coupons** est maintenant masquée.

- Avant: sans abonnement actif, le plugin laissait Listeo afficher le lien natif Coupons.
- Maintenant: sans abonnement actif => `Coupons` caché.
- Avec abonnement actif: le menu reste conditionné par le module coupons du package (avec fallback robuste sur `listeo_core_user_packages`).

**Fichier modifié :**
- `includes/class-sfl-frontend.php`

---

#### 🇬🇧 English

Dashboard menu behavior fix: when a user has **no active SFL subscription**, the **Coupons** menu entry is now hidden.

- Before: without an active subscription, the plugin let Listeo render the native Coupons link.
- Now: no active subscription => `Coupons` hidden.
- With an active subscription: menu visibility still depends on package coupon module access (with robust `listeo_core_user_packages` fallback).

**File changed:**
- `includes/class-sfl-frontend.php`

---

### 🔄 Patch: Auto-sync complet des metas modules pour abonnés actifs

#### 🇫🇷 Français

Ajout d'une auto-réparation des metas `_sfl_option_*` pour tous les abonnés `active` / `trialing`, afin de corriger les anciens comptes créés via des flux historiques (ex: manuel) où certaines metas pouvaient rester vides.

- Nouvelle routine `backfill_user_option_metas_from_current_package()`:
- source de vérité: `listeo_core_user_packages` (package utilisateur actif),
- resynchronise toutes les metas modules (`booking`, `reviews`, `gallery`, `pricing_menu`, `coupons`, `faq`, `social_links`, `opening_hours`, `video`, `dokan_store`),
- resynchronise aussi `_listing_limit`, `_listing_duration`, `_listing_featured`,
- exécution périodique (max 1 fois / 6h) avec option de forçage.

**Fichier modifié :**
- `includes/class-sfl-subscription-manager.php`

---

#### 🇬🇧 English

Added an automatic self-heal routine for `_sfl_option_*` metas across all `active` / `trialing` subscribers, to fix legacy accounts created through older flows (e.g. manual creation) where some metas could remain empty.

- New `backfill_user_option_metas_from_current_package()` routine:
- source of truth: active user package row in `listeo_core_user_packages`,
- resyncs all module metas (`booking`, `reviews`, `gallery`, `pricing_menu`, `coupons`, `faq`, `social_links`, `opening_hours`, `video`, `dokan_store`),
- also resyncs `_listing_limit`, `_listing_duration`, `_listing_featured`,
- periodic execution (max once every 6h) with force mode support.

**File changed:**
- `includes/class-sfl-subscription-manager.php`

---

### 🎯 Patch: Package Details — masquer l'item placeholder par défaut

#### 🇫🇷 Français

Correction de l'affichage des "Package Details" sur les cartes plans: l'item par défaut de Listeo **"Edit packages in WP Admin → Products"** est désormais filtré et n'est plus affiché au frontend.

- Affiche uniquement les items réellement saisis par l'admin dans le produit.
- Filtrage robuste avec variantes de séparateurs (`→`, `->`, etc.) et normalisation du texte.

**Fichier modifié :**
- `includes/class-sfl-frontend.php`

---

#### 🇬🇧 English

Fixed "Package Details" rendering on plan cards: the default Listeo placeholder item **"Edit packages in WP Admin → Products"** is now filtered out and no longer shown on the frontend.

- Only truly user-entered product detail items are displayed.
- Robust filtering handles separator variants (`→`, `->`, etc.) via normalized text matching.

**File changed:**
- `includes/class-sfl-frontend.php`

---

### 🔐 Patch critique: verrouillage tarif + sync auto obligatoire

#### 🇫🇷 Français

Renforcement de sécurité sur l'édition des tarifs package (`sfl_package`) :

- Lorsqu'un prix mensuel/annuel est modifié puis sauvegardé, le plugin déclenche maintenant une **synchronisation automatique immédiate** des passerelles configurées (Stripe, PayPal, 2Checkout).
- Si la synchronisation échoue, le plugin applique un **rollback strict** des prix (`_sfl_price_monthly`, `_sfl_price_yearly`) vers les anciennes valeurs.
- Une **alerte admin explicite** est affichée avec la cause de l'échec, afin d'éviter toute désynchronisation prix frontend / passerelles.
- Si aucun gateway auto n'est configuré pour un package payant, la modification tarifaire est refusée (rollback) avec message clair.
- Ajout d'un texte d'avertissement explicite dans l'écran d'édition package.

**Fichier modifié :**
- `includes/class-sfl-package-cpt.php`

---

#### 🇬🇧 English

Critical safety hardening for package price edits (`sfl_package`):

- When monthly/yearly price changes are saved, the plugin now runs an **immediate automatic gateway sync** (Stripe, PayPal, 2Checkout when configured).
- If sync fails, a **strict rollback** is applied for prices (`_sfl_price_monthly`, `_sfl_price_yearly`) back to previous values.
- A clear **admin notice** is shown with failure details to prevent frontend/gateway price drift.
- If no automatic gateway is configured for a paid package, price updates are rejected (rollback) with an explicit message.
- Added explicit warning text in the package edit screen.

**File changed:**
- `includes/class-sfl-package-cpt.php`

---

### 🧯 Hotfix: 2Checkout non-bloquant sur update tarif

#### 🇫🇷 Français

Correction urgente du mode strict:

- L'échec 2Checkout ne provoque plus un rollback global des nouveaux tarifs.
- Stripe/PayPal restent les passerelles bloquantes pour valider le changement de prix.
- Si 2Checkout échoue, le prix est conservé et une alerte admin de type warning est affichée.

**Fichier modifié :**
- `includes/class-sfl-package-cpt.php`

---

#### 🇬🇧 English

Urgent strict-mode hotfix:

- A 2Checkout failure no longer triggers a global rollback of newly saved prices.
- Stripe/PayPal remain the blocking gateways for paid price-change validation.
- If 2Checkout fails, prices are kept and an admin warning notice is shown.

**File changed:**
- `includes/class-sfl-package-cpt.php`

---


### 🧹 UI Patch: suppression bloc info webhook

#### 🇫🇷 Français

- Suppression du bloc d'information webhook en haut de la section "URLs des Webhooks" (bannière statut + 4 indicateurs).
- Conservation du reste de la section (URLs, secrets, guide, état global) sans ce panneau.

**Fichier modifié :**
- `templates/admin-settings.php`

---

#### 🇬🇧 English

- Removed the top webhook information block in the "Webhook URLs" section (status banner + 4 indicators).
- Kept the rest of the section (URLs, secrets, guide, global status) without this panel.

**File changed:**
- `templates/admin-settings.php`

---

### 🎯 UI Patch: devise alignée + taille adaptative des montants élevés

#### 🇫🇷 Français

- Correction de l'affichage prix dans les cartes plans: la devise n'est plus rendue "en haut" du montant.
- Ajustement dynamique de la taille du montant selon le nombre de chiffres:
- 5 chiffres: réduction légère,
- 6+ chiffres: réduction plus forte,
- pour garder une seule ligne et améliorer la lisibilité.

**Fichier modifié :**
- `includes/class-sfl-frontend.php`

---

#### 🇬🇧 English

- Fixed plan card price rendering so the currency symbol is no longer displayed above the amount.
- Added adaptive amount sizing based on digit count:
- 5 digits: slight size reduction,
- 6+ digits: stronger reduction,
- to preserve a single-line, readable layout.

**File changed:**
- `includes/class-sfl-frontend.php`

---

### 🛡️ Patch: exclusions de suspension par rôles et emails

#### 🇫🇷 Français

- Extension de la logique "Exclure les administrateurs" avec deux nouvelles exclusions:
- rôles WordPress sélectionnés (y compris rôles personnalisés),
- utilisateurs ciblés via leurs emails.
- Le comportement est identique à l'exclusion admin: les annonces de ces utilisateurs ne sont plus suspendues en absence d'abonnement actif.
- Ajout des champs dans Settings:
- multi-sélection de tous les rôles disponibles,
- textarea d'emails (un par ligne ou séparés par virgule).
- Lors d'une mise à jour des exclusions, les annonces déjà en `sfl_suspended` pour ces utilisateurs sont automatiquement réactivées.

**Fichiers modifiés :**
- `templates/admin-settings.php`
- `subscription-for-listeo.php`
- `includes/class-sfl-subscription-manager.php`
- `includes/class-sfl-frontend.php`

---

#### 🇬🇧 English

- Extended the "Exclude administrators" suspension rule with two new exclusion sources:
- selected WordPress roles (including custom roles),
- specific users by email.
- Behavior matches admin exclusion: listings from these users are no longer suspended when no active subscription exists.
- Added Settings inputs:
- multi-select with all available roles,
- email textarea (one per line or comma-separated).
- When exclusions are updated, already `sfl_suspended` listings for matching users are automatically reactivated.

**Files changed:**
- `templates/admin-settings.php`
- `subscription-for-listeo.php`
- `includes/class-sfl-subscription-manager.php`
- `includes/class-sfl-frontend.php`

---

### 🔧 Hotfix: réinjection section Coupon submit-listing

#### 🇫🇷 Français

- Ajout d'un filet de sécurité sur le formulaire de publication:
- si l'utilisateur SFL est éligible au module coupons, la section **Coupon Widget Settings** est réinjectée même si Listeo l'a retirée.
- Couvre les cas client où la section disparaît malgré package coupons actif (package non résolu au runtime, règles core trop strictes, etc.).
- En complément, si l'utilisateur n'a encore aucun coupon publié, la section reste visible avec une option neutre.

**Fichier modifié :**
- `includes/class-sfl-subscription-manager.php`

---

#### 🇬🇧 English

- Added a safety fallback on listing submission form:
- when an SFL user is eligible for coupons, **Coupon Widget Settings** is re-injected even if Listeo removed it.
- Covers client-side cases where coupons section disappears despite coupon-enabled package (runtime package resolution edge cases, strict core checks, etc.).
- Additionally, when the user has no published coupons yet, the section remains visible with a neutral option.

**File changed:**
- `includes/class-sfl-subscription-manager.php`

---

### 🎟️ Hotfix: section Coupons absente chez certains clients

#### 🇫🇷 Français

- Correctif ciblé pour le formulaire de publication d'annonce:
- force l'affichage de la section Coupons pour les utilisateurs SFL éligibles (module coupons actif),
- même si un réglage global Listeo masquait les coupons (`listeo_remove_coupons`).
- Durcissement de lecture des metas modules (compatibilité valeurs `yes` / `on` / `1`) pour éviter les faux négatifs sur l'option coupons selon les environnements client.

**Fichiers modifiés :**
- `includes/class-sfl-subscription-manager.php`
- `includes/class-sfl-package-cpt.php`

---

#### 🇬🇧 English

- Targeted fix for listing submission form:
- forces Coupons section visibility for eligible SFL users (coupon module enabled),
- even when a global Listeo setting hides coupons (`listeo_remove_coupons`).
- Hardened module meta parsing (`yes` / `on` / `1`) to prevent false negatives on coupon-module detection across client environments.

**Files changed:**
- `includes/class-sfl-subscription-manager.php`
- `includes/class-sfl-package-cpt.php`

---

### 🎛️ UI Patch: position du champ texte de durée

#### 🇫🇷 Français

- Ajustement de l'interface d'édition package: le champ **Texte personnalisé de durée (plans)** est maintenant repositionné juste en dessous du champ **Listing duration**.
- Aucun changement fonctionnel sur la logique d'affichage frontend, uniquement le placement du champ en admin.

**Fichier modifié :**
- `includes/class-sfl-package-commission.php`

---

#### 🇬🇧 English

- Package edit UI adjustment: **Custom duration text (plans)** field is now positioned directly below **Listing duration**.
- No frontend behavior change, admin field placement only.

**File changed:**
- `includes/class-sfl-package-commission.php`

---

### ✍️ Patch: texte de durée personnalisé sur plans/shortcode

#### 🇫🇷 Français

- Ajout d'un champ admin package (édition produit listing package):
- **Texte personnalisé de durée (plans)**.
- Si ce champ est rempli, il remplace l'affichage **"Durée illimitée"** sur:
- les cartes plans,
- le shortcode des plans,
- la vue "Mon abonnement".
- L'icône de durée reste inchangée comme demandé.

**Fichiers modifiés :**
- `includes/class-sfl-package-commission.php`
- `includes/class-sfl-package-cpt.php`
- `includes/class-sfl-frontend.php`
- `templates/frontend-my-subscription.php`
- `languages/subscription-for-listeo-en_US.po`

---

#### 🇬🇧 English

- Added a new package admin field (listing package product edit):
- **Custom duration text (plans)**.
- When this field is filled, it replaces **"Unlimited duration"** on:
- plan cards,
- plans shortcode output,
- "My subscription" view.
- Duration icon remains unchanged as requested.

**Files changed:**
- `includes/class-sfl-package-commission.php`
- `includes/class-sfl-package-cpt.php`
- `includes/class-sfl-frontend.php`
- `templates/frontend-my-subscription.php`
- `languages/subscription-for-listeo-en_US.po`

---

### 🚑 Hotfix: contournement total publication pour utilisateurs exclus

#### 🇫🇷 Français

- Correction d'un blocage restant: un utilisateur exclu pouvait encore être limité par son abonnement actif (ex: limite 3 annonces).
- Le mode "Autoriser la publication sans abonnement pour les utilisateurs exclus" contourne désormais totalement les vérifications de limite:
- accès à la soumission sans redirection,
- bouton "Publier une annonce" non bloqué,
- activation/désactivation d'annonces sans contrainte de limite package,
- pas d'injection de package/cookie Listeo pour ces utilisateurs (évite le re-blocage par limite package).

**Fichiers modifiés :**
- `includes/class-sfl-frontend.php`
- `includes/class-sfl-subscription-manager.php`

---

#### 🇬🇧 English

- Fixed a remaining blocker where excluded users could still be constrained by their active package limit (e.g. 3 listings).
- "Allow publishing without subscription for excluded users" now fully bypasses listing-limit enforcement:
- submit access without redirect,
- "Publish Listing" button no longer blocked,
- activate/deactivate actions no longer constrained by package limit,
- no Listeo package/cookie injection for these users (prevents package-limit reblocking).

**Files changed:**
- `includes/class-sfl-frontend.php`
- `includes/class-sfl-subscription-manager.php`

---

### 🔓 Patch: exclusions indépendantes + publication sans abonnement

#### 🇫🇷 Français

- Correction logique: les exclusions par rôles et par emails fonctionnent désormais indépendamment de la case "Exclure les administrateurs".
- Nouvelle option de réglage:
- **Autoriser la publication sans abonnement pour les utilisateurs exclus**.
- Quand cette option est activée, les administrateurs et utilisateurs exclus (rôles/emails):
- peuvent publier sans abonnement actif,
- conservent un bouton "Publier une annonce" actif,
- conservent les metas/champs du formulaire de soumission disponibles.

**Fichiers modifiés :**
- `templates/admin-settings.php`
- `subscription-for-listeo.php`
- `includes/class-sfl-subscription-manager.php`
- `includes/class-sfl-frontend.php`
- `languages/subscription-for-listeo-en_US.po`

---

#### 🇬🇧 English

- Logic fix: role-based and email-based exclusions now work independently from the "Exclude administrators" checkbox.
- New setting added:
- **Allow publishing without subscription for excluded users**.
- When enabled, administrators and excluded users (roles/emails):
- can publish without an active subscription,
- keep the "Publish Listing" button active,
- keep listing form metas/fields available.

**Files changed:**
- `templates/admin-settings.php`
- `subscription-for-listeo.php`
- `includes/class-sfl-subscription-manager.php`
- `includes/class-sfl-frontend.php`
- `languages/subscription-for-listeo-en_US.po`

---

### 🌐 i18n Patch: nouvelles chaînes exclusions rôles/emails

#### 🇫🇷 Français

- Ajout des traductions anglaises pour les nouvelles options d'exclusion de suspension:
- exclusion par rôles,
- exclusion par emails.
- Mise à jour du template `POT` puis recompilation des fichiers `MO` existants.

**Fichiers modifiés :**
- `languages/subscription-for-listeo-en_US.po`
- `languages/subscription-for-listeo-en_US.mo`
- `languages/subscription-for-listeo-fr_FR.mo`
- `languages/subscription-for-listeo.pot`

---

#### 🇬🇧 English

- Added English translations for the new suspension exclusion settings:
- role-based exclusions,
- email-based exclusions.
- Updated the `POT` template and recompiled existing `MO` files.

**Files changed:**
- `languages/subscription-for-listeo-en_US.po`
- `languages/subscription-for-listeo-en_US.mo`
- `languages/subscription-for-listeo-fr_FR.mo`
- `languages/subscription-for-listeo.pot`

---
v1.7.0 📅 17 mars 2026
[1.7.0] - 2026-03-17

### 🚀 Checkout & Registration Flow Overhaul (Owner + Custom Fields + Post-payment mode)

#### 🇫🇷 Français

Cette version introduit une refonte complète du flux d'abonnement/inscription pour harmoniser le comportement entre les pages plans, checkout et l'écosystème Listeo (popup + separate page), avec support des champs personnalisés Owner.

##### 1) Modes de facturation persistants (mensuel / annuel / les deux)
- Nouvelle logique globale `both`, `year_only`, `month_only`.
- Respect du mode sur:
- cartes plans frontend,
- shortcodes,
- checkout,
- modal de changement de plan.
- Correctifs d'affichage:
- format prix (`9.90` conservé),
- badge économies non négatif et conditionnel,
- rendu devise/symbole cohérent avec Listeo.

##### 1-bis) Réductions visibles sur la page plans (et shortcodes)
- Ajout d'un affichage dynamique du prix barré (ancien prix) mensuel et annuel.
- Calcul et affichage du pourcentage de réduction par package.
- Compatibilité avec les modes de facturation:
- pas d'affichage incohérent en `year_only` / `month_only`,
- affichage pertinent en mode `both`.
- Harmonisation du rendu visuel (positionnement, style, hiérarchie prix actuel / ancien prix / badge).

##### 2) Inscription + souscription (non connecté) renforcée
- Option admin d'autorisation d'inscription pendant souscription maintenue et stabilisée.
- Messages clairs si inscription WordPress désactivée.
- Cas forfait gratuit aligné:
- affichage dédié,
- activation sans passerelles de paiement inutiles,
- détails package visibles dans checkout.

##### 3) Formulaire natif Listeo directement dans checkout
- Option admin pour afficher le formulaire natif Listeo directement dans checkout.
- Affichage du vrai formulaire registration (Owner) avec champs personnalisés.
- Forçage du rôle Owner pour afficher immédiatement les champs attendus.

##### 4) Nouveau mode: création/confirmation du compte uniquement après paiement validé
- Nouvelle option admin:
- **Créer/confirmer le compte uniquement après paiement validé**
- En mode non connecté:
- clic sur payer ne redirige plus vers `my-profile`,
- collecte des données d'inscription en checkout,
- création différée du compte après confirmation paiement.
- Implémentation technique:
- token d'inscription pending sécurisé (transient),
- résolution compte côté webhooks/callbacks,
- support Stripe, PayPal, WiPay et 2Checkout,
- flow gratuit aligné.

##### 5) Compatibilité Separate page / Login redirect
- Injection `redirect_to` dans formulaire login Listeo en mode separate page.
- Retour automatique vers checkout après login/inscription.
- Stabilisation des redirections pour éviter les boucles.

**Fichiers principaux modifiés :**
- `subscription-for-listeo.php`
- `includes/class-sfl-frontend.php`
- `assets/js/frontend.js`
- `includes/class-sfl-stripe-handler.php`
- `includes/class-sfl-paypal-handler.php`
- `includes/class-sfl-wipay-handler.php`
- `includes/class-sfl-twocheckout-handler.php`
- `includes/class-sfl-webhook-handler.php`
- `templates/admin-settings.php`
- `languages/subscription-for-listeo-en_US.po`
- `languages/subscription-for-listeo-fr_FR.po`
- `languages/subscription-for-listeo.pot`

---

#### 🇬🇧 English

This release delivers a full subscription/registration flow overhaul to align plans, checkout, and Listeo behavior (popup + separate page), including Owner custom fields support.

##### 1) Persistent billing modes (monthly / yearly / both)
- New global mode handling: `both`, `year_only`, `month_only`.
- Mode is now enforced across:
- frontend plan cards,
- shortcodes,
- checkout,
- change-plan modal.
- Display fixes:
- stable price formatting (`9.90` preserved),
- non-negative conditional savings badges,
- consistent Listeo currency symbol rendering.

##### 1-bis) Discount display on plans page (and shortcodes)
- Added dynamic strikethrough old price display for both monthly and yearly pricing.
- Added per-package discount percentage calculation and rendering.
- Full billing-mode compatibility:
- no inconsistent display in `year_only` / `month_only`,
- proper discount display in `both` mode.
- Improved visual hierarchy and positioning (current price / old price / discount badge).

##### 2) Improved subscribe + register flow for guests
- Admin option to allow registration during subscribe kept and hardened.
- Clear messaging when WordPress registration is disabled.
- Free package flow aligned:
- dedicated display,
- no unnecessary payment gateways,
- package details visible in checkout.

##### 3) Native Listeo form directly inside checkout
- New admin option to display the native Listeo form directly in checkout.
- Uses the actual registration form (Owner + custom fields).
- Owner role is forced for immediate field visibility.

##### 4) New mode: create/confirm account only after payment confirmation
- New admin option:
- **Create/confirm account only after payment is confirmed**
- For guests:
- clicking pay no longer redirects to `my-profile`,
- registration data is collected on checkout,
- account creation is deferred until payment confirmation.
- Technical implementation:
- secure pending registration token (transient),
- account resolution in webhooks/callbacks,
- support for Stripe, PayPal, WiPay, and 2Checkout,
- free-plan flow aligned.

##### 5) Separate page / login redirect compatibility
- `redirect_to` injection in Listeo login form for separate page mode.
- Automatic redirect back to checkout after login/register.
- Redirect behavior hardened to avoid loops.

**Main files changed:**
- `subscription-for-listeo.php`
- `includes/class-sfl-frontend.php`
- `assets/js/frontend.js`
- `includes/class-sfl-stripe-handler.php`
- `includes/class-sfl-paypal-handler.php`
- `includes/class-sfl-wipay-handler.php`
- `includes/class-sfl-twocheckout-handler.php`
- `includes/class-sfl-webhook-handler.php`
- `templates/admin-settings.php`
- `languages/subscription-for-listeo-en_US.po`
- `languages/subscription-for-listeo-fr_FR.po`
- `languages/subscription-for-listeo.pot`

---
v1.6.4 📅 16 mars 2026
[1.6.4] - 2026-03-16

### ✨ Affichage des "Package Details" dans la grille des plans

#### 🇫🇷 Français

Ajout d'une section dédiée **"Détails du package"** dans les cartes de forfaits (page plans + shortcode), alimentée automatiquement depuis le bloc produit WooCommerce **Listeo Package Details** (`package_items_group`).

- Lecture des items CMB2 depuis le produit WooCommerce lié au package.
- Affichage dans les 2 rendus de carte (`render_package_card_pro` et `render_package_card`).
- Filtrage des items vides et du texte placeholder par défaut pour éviter le bruit.
- Mise à jour des traductions pour la nouvelle chaîne.

**Fichiers modifiés :**
- `includes/class-sfl-frontend.php`
- `languages/subscription-for-listeo-en_US.po`
- `languages/subscription-for-listeo-fr_FR.po`
- `languages/subscription-for-listeo.pot`

---

#### 🇬🇧 English

Added a dedicated **"Package details"** section in plan cards (plans page + shortcode), automatically populated from the WooCommerce product block **Listeo Package Details** (`package_items_group`).

- Reads CMB2 group items from the WooCommerce product linked to the package.
- Rendered in both card variants (`render_package_card_pro` and `render_package_card`).
- Filters empty rows and default placeholder text to keep cards clean.
- Updated translations for the new string.

**Files modified:**
- `includes/class-sfl-frontend.php`
- `languages/subscription-for-listeo-en_US.po`
- `languages/subscription-for-listeo-fr_FR.po`
- `languages/subscription-for-listeo.pot`

---
v1.6.3 📅 16 mars 2026
[1.6.3] - 2026-03-16

### 🐛 Fix: Création manuelle d'abonnement (admin) — erreur "Package invalide."

#### 🇫🇷 Français

Le flux AJAX `sfl_create_manual_subscription` rejetait certains packages valides avec le message **"Package invalide."** lors de la création manuelle d'abonnement depuis l'admin.

- Correction de la validation des packages pour accepter :
- les `sfl_package`
- les produits WooCommerce `listing_package` et `listing_package_subscription`
- Validation alignée sur `SFL_Package_CPT::get_package()` pour garder une logique unique.
- Correction de l'appel d'assignation package redondant/mal typé après création (déjà géré dans `create_subscription()`).
- Récupération du prix et de l'intervalle depuis les données package normalisées.

**Fichier modifié :**
- `subscription-for-listeo.php` — `ajax_create_manual_subscription`

---

#### 🇬🇧 English

The `sfl_create_manual_subscription` AJAX flow was incorrectly rejecting valid packages with **"Invalid package."** when creating manual subscriptions from admin.

- Fixed package validation to accept:
- `sfl_package`
- WooCommerce `listing_package` and `listing_package_subscription` products
- Validation now uses `SFL_Package_CPT::get_package()` for consistent package resolution.
- Removed a redundant/mistyped package assignment call after creation (already handled by `create_subscription()`).
- Price and billing interval now come from normalized package data.

**File modified:**
- `subscription-for-listeo.php` — `ajax_create_manual_subscription`

---
v1.6.2 📅 09 mars 2026
[1.6.2] - 2026-03-09

### ✨ Nouvelles fonctionnalités / New Features

#### 🇫🇷 Français

##### 1. Réorganisation des packages par glisser-déposer (admin)

La page **Admin → Packages** dispose désormais d'une fonctionnalité de glisser-déposer pour réorganiser l'ordre d'affichage des forfaits. L'ordre défini ici est immédiatement reflété sur la **page des plans frontend** et dans le **shortcode `[sfl_packages]`**.

- Une barre d'information bleue explique la fonctionnalité.
- Une poignée de glissement (☰) apparaît en haut à gauche de chaque carte au survol.
- La sauvegarde est automatique (AJAX) dès qu'un forfait est relâché, avec message de confirmation.
- Le `menu_order` est mis à jour à la fois sur le produit WooCommerce et sur le `sfl_package` lié.

**Fichiers modifiés :**
- `includes/class-sfl-package-manager.php` — nouveau handler AJAX `sfl_reorder_packages`
- `subscription-for-listeo.php` — chargement de `jquery-ui-sortable` sur la page packages
- `templates/admin-packages.php` — UI drag-and-drop, `data-wc-id`, JS Sortable + sauvegarde AJAX

##### 2. Option "Masquer la durée" sur les cartes frontend

Un nouveau champ checkbox **"Masquer la durée sur le frontend"** (`_sfl_hide_listing_duration`) a été ajouté dans la section *Subscription for Listeo – Options avancées* de chaque produit WooCommerce de type listing package.

- Si coché, la ligne *"Durée illimitée"* (ou *"Durée : X jours"*) **ne s'affiche plus** sur la carte de ce forfait dans la page des plans et dans le shortcode.
- Affecte les 3 variantes de rendu : checkout, vue compacte, et vue principale shortcode.

**Fichiers modifiés :**
- `includes/class-sfl-package-commission.php` — nouveau champ + sauvegarde
- `includes/class-sfl-package-cpt.php` — champ `hide_listing_duration` ajouté dans `get_listeo_features_from_wc()`, `get_package()`, `format_wc_product_as_package()`
- `includes/class-sfl-frontend.php` — conditions `if ( empty( $package['hide_listing_duration'] ) )` sur les 3 affichages

##### 3. Économies individuelles par carte de forfait

Le badge **"Économisez X%"** est maintenant affiché **sur chaque carte** individuellement lors du basculement en tarification annuelle, au lieu d'un seul pourcentage moyen global sur le toggle.

- Le calcul est : `(mensuel × 12 − annuel) / (mensuel × 12) × 100`
- Affiché uniquement si l'économie est > 0 et que le forfait a un prix annuel.
- La modal "Changer de plan" utilise également le pourcentage réel du forfait concerné (plus de `-17%` hardcodé).

**Fichiers modifiés :**
- `includes/class-sfl-frontend.php` — élément `.sfl-price-save` ajouté dans `render_package_card_pro()` + CSS
- `subscription-for-listeo.php` — champ `savings_percent` ajouté dans la réponse AJAX `ajax_preview_plan_change`
- `assets/js/frontend.js` — badge de la modal dynamique via `data.savings_percent`

---

#### 🇬🇧 English

##### 1. Drag-and-drop package reordering (admin)

The **Admin → Packages** page now supports drag-and-drop reordering of subscription plans. The order set here is immediately reflected on the **frontend plans page** and the **`[sfl_packages]` shortcode**.

- A blue information bar explains the feature.
- A drag handle (☰) appears in the top-left corner of each card on hover.
- Saving is automatic (AJAX) as soon as a card is dropped, with a confirmation message.
- The `menu_order` is updated on both the WooCommerce product and the linked `sfl_package` post.

**Files modified:**
- `includes/class-sfl-package-manager.php` — new AJAX handler `sfl_reorder_packages`
- `subscription-for-listeo.php` — enqueue `jquery-ui-sortable` on packages page
- `templates/admin-packages.php` — drag-and-drop UI, `data-wc-id`, Sortable JS + AJAX save

##### 2. "Hide duration" option on frontend plan cards

A new checkbox **"Hide duration on frontend"** (`_sfl_hide_listing_duration`) has been added in the *Subscription for Listeo – Advanced Options* section of each WooCommerce listing package product.

- When checked, the *"Unlimited duration"* (or *"Duration: X days"*) row **no longer appears** on that plan's card on the plans page and in the shortcode.
- Affects all 3 render variants: checkout, compact view, and main shortcode view.

**Files modified:**
- `includes/class-sfl-package-commission.php` — new field + save
- `includes/class-sfl-package-cpt.php` — `hide_listing_duration` field added in `get_listeo_features_from_wc()`, `get_package()`, `format_wc_product_as_package()`
- `includes/class-sfl-frontend.php` — `if ( empty( $package['hide_listing_duration'] ) )` conditions on all 3 display locations

##### 3. Individual per-card savings badge

The **"Save X%"** badge is now shown **on each individual plan card** when switching to annual pricing, instead of a single global average percentage on the toggle button.

- Calculation: `(monthly × 12 − yearly) / (monthly × 12) × 100`
- Shown only when savings > 0 and the plan has an annual price set.
- The "Change Plan" modal also uses the actual per-package percentage (no more hardcoded `-17%`).

**Files modified:**
- `includes/class-sfl-frontend.php` — `.sfl-price-save` element added in `render_package_card_pro()` + CSS
- `subscription-for-listeo.php` — `savings_percent` field added to `ajax_preview_plan_change` AJAX response
- `assets/js/frontend.js` — modal badge now dynamic via `data.savings_percent`

---
v1.6.1 📅 06 mars 2026
[1.6.1] - 2026-03-06

### 🐛 Fix: Menus "Coupons" et "Store (Dokan)" toujours visibles dans le dashboard selon le package

#### 🇫🇷 Français

##### Problème 1 — Menu "Coupons" visible même sans le module

Le package gratuit a le module Coupons désactivé. Le plugin masquait correctement la section coupon dans le formulaire de création d'annonce, mais le lien **"Coupons"** restait visible dans le menu du tableau de bord Listeo.

##### Problème 2 — Menu "Store" (Dokan) visible même sans le module

La section **"Store"** (Dokan) s'affichait dans le menu du tableau de bord pour tous les vendeurs ayant Dokan installé, quelle que soit leur formule d'abonnement.

##### Problème 3 — `option_dokan_store` absent de la liste des modules contrôlés

Le module `option_dokan_store` n'était pas déclaré dans `sfl_enable_all_package_module_controls()`, empêchant SFL de signaler à Listeo Core que ce module devait être contrôlé par les packages.

##### Problème 4 — `dokan_store_expires` toujours null (bug de transmission)

`_package_dokan_store_duration` était lue depuis WC mais jamais transmise dans le tableau retourné par `get_package()`. Par conception, l'accès Dokan doit être **illimité tant que l'abonnement est actif** (comme `package_duration = 0` pour les annonces) — l'expiration est gérée par le cycle de vie de l'abonnement SFL.

##### Cause racine (commun aux 2 menus)

Listeo Core hardcode ces sections dans `template-dashboard.php` sans aucun filtre basé sur les packages. C'est une limitation Listeo : aucun hook par package n'existe pour ces menus.

##### Fix — `includes/class-sfl-subscription-manager.php`

- `option_dokan_store` ajouté à la liste de `sfl_enable_all_package_module_controls()`.
- `dokan_store_expires` forcé à `null` — l'accès est géré par le cycle de vie de l'abonnement.

##### Fix — `includes/class-sfl-frontend.php`

- Nouvelle méthode `hide_coupon_menu_if_disabled()` : lit `_sfl_option_coupons` et masque via jQuery le `` du lien Coupons dans `.dashboard-nav`.
- Nouvelle méthode `hide_dokan_menu_if_disabled()` : vérifie `_sfl_option_dokan_store` et masque/affiche le `` dans `.dashboard-nav`.
- Les deux méthodes sont hookées sur `wp_footer` (priorité 20) et vérifient l'abonnement SFL actif avant d'agir.

---

#### 🇬🇧 English

##### Problem 1 — "Coupons" menu visible even without the module

The free package has the Coupons module disabled. The plugin was correctly hiding the coupon section in the listing creation form, but the **"Coupons"** link remained visible in the Listeo dashboard sidebar.

##### Problem 2 — "Store" (Dokan) menu visible even without the module

The **"Store"** (Dokan) section was showing in the dashboard sidebar for all vendors who had Dokan installed, regardless of their subscription package.

##### Problem 3 — `option_dokan_store` missing from the controlled modules list

The `option_dokan_store` module was not declared in `sfl_enable_all_package_module_controls()`, preventing SFL from informing Listeo Core that this module should be package-controlled.

##### Problem 4 — `dokan_store_expires` always null (data propagation bug)

`_package_dokan_store_duration` was read from WC but never forwarded in the array returned by `get_package()`. By design, Dokan access should be **unlimited as long as the subscription is active** (same as `package_duration = 0` for listings) — expiry is managed by the SFL subscription lifecycle.

##### Root cause (common to both menus)

Listeo Core hardcodes these sections in `template-dashboard.php` with no package-based filter. This is a Listeo Core limitation: no per-package hook exists for these menu items.

##### Fix — `includes/class-sfl-subscription-manager.php`

- `option_dokan_store` added to the list returned by `sfl_enable_all_package_module_controls()`.
- `dokan_store_expires` forced to `null` — access is managed by the subscription lifecycle.

##### Fix — `includes/class-sfl-frontend.php`

- New method `hide_coupon_menu_if_disabled()`: reads `_sfl_option_coupons` and hides the Coupons `` in `.dashboard-nav` via jQuery.
- New method `hide_dokan_menu_if_disabled()`: checks `_sfl_option_dokan_store` and hides/shows the `` in `.dashboard-nav`.
- Both methods are hooked on `wp_footer` (priority 20) and verify an active SFL subscription before acting.

---
v1.6.0 📅 04 mars 2026
[1.6.0] - 2026-03-04

### ✨ New: Package sync now covers legacy free subscriptions (pre-SFL-CPT)

#### Problem
Free subscriptions created before the SFL package CPT was implemented stored the WooCommerce product ID directly in `wp_sfl_subscriptions.package_id` (instead of the SFL CPT post ID). They also had no row in `wp_listeo_core_user_packages` and no `_sfl_package_product_id` user meta — the two data points required by the sync to identify active subscribers. As a result, clicking "Push to subscribers" never updated these old subscribers; they had to cancel and re-subscribe manually to receive package changes.

#### Fix — `includes/class-sfl-subscription-manager.php`
- **Chemin C** added to `_get_active_subscriber_user_ids_for_product()`: queries `wp_sfl_subscriptions WHERE package_id = $wc_product_id AND status IN ('active','trialing')` — catches all subscriptions where `package_id` equals the WC product ID directly.
- **UPSERT in `ajax_push_package_to_subscribers()`**: when `$wpdb->update()` returns 0 (no existing row in `listeo_core_user_packages`), the code now checks whether the row is truly absent and, if so, inserts it with the current package settings. It also sets the missing `_sfl_package_product_id` and `_sfl_current_user_package_id` user metas so future syncs find the subscriber via the standard Chemin B (no Chemin C needed again).

---

### ✨ New: Gallery photo limit shown on frontend package card

#### Problem
The gallery module was displayed as a generic "🖼️ Galerie photos" label with no indication of the photo limit configured on the package.

#### Fix — `includes/class-sfl-frontend.php`
The package card rendering loop now detects the `gallery` module and appends the limit dynamically: **"🖼️ Galerie photos (3 images)"** or **"🖼️ Galerie photos (illimité)"** based on `$package['gallery_limit']`. Uses `_n()` for correct singular/plural.

---

### 🐛 Fix: Translation "Viofo" → "Video"

#### Problem
The English translation file `en_US.po` contained 8 occurrences of `"Viofo"` (typo) instead of `"Video"`, affecting every location where the video module label was displayed on an English-locale site.

#### Fix — `languages/subscription-for-listeo-en_US.po`
All 8 occurrences corrected: `"Viofo"` → `"Video"`, `"Presentation viofo"` → `"Presentation video"`.

---

### ✨ New: Allowed Listing Types — display on package card + enforcement on submission form

#### Problem
The `_allowed_listing_types` restriction configured on WooCommerce packages (Listeo's native feature) was: (a) never displayed on the SFL frontend package cards, and (b) never enforced on the listing submission form when accessed through SFL. Custom listing types from the Listeo DB were ignored. The meta key used internally was also inconsistent (`_package_listing_types` vs `_package_option_listings_types` vs `_allowed_listing_types`).

#### Fix
- **Meta key unified** across `class-sfl-package-cpt.php`, `class-sfl-subscription-manager.php` (snapshot), and `class-sfl-package-manager.php` to `_allowed_listing_types` — the key actually written by Listeo's paid-listings admin.
- **Frontend display** (`includes/class-sfl-frontend.php`): if the package has restricted types, a `🏷️` feature row is shown on the pricing card. Labels are fetched dynamically from `listeo_core_get_listing_types()` — includes all custom types stored in `wp_listeo_listing_types`.
- **PHP enforcement** (`filter_allowed_listing_types_php()`): hooks on `listeo_core_get_listing_types` filter to remove non-allowed slugs from the types array for the current user's package.
- **JS enforcement** (`inject_allowed_listing_types_js()`): on the submit page, injects a script that hides non-allowed type tiles (``) using the exact Listeo markup from `listing-submit-type.php`. Two timeouts (500 ms + 1500 ms) cover AJAX multi-step navigation.
v1.5.9 📅 03 mars 2026
[1.5.9] - 2026-03-03

### 🐛 Bugfix: Emails de notification admin en texte brut (sans design)
### 🐛 Bugfix: Aucun email envoyé lors d'une annulation d'abonnement

#### ✨ Fix: Admin notification emails now use the HTML template (EN)

##### Problem

When a user subscribed or a payment failed, **two emails were sent to the admin**:
- One with the proper HTML design (WooCommerce template) ✅
- One as **plain text** with no formatting ❌

The second plain-text email came from `notify_admin_new_subscription()` and `notify_admin_payment_failed()`, which called `send_email(..., false)` — bypassing the `wrap_html_email()` wrapper entirely.

##### Fix

Both admin notification methods now build a full **HTML email** using `wrap_html_email()`, consistent with all other SFL emails:
- `notify_admin_new_subscription()`: displays a styled table with subscriber name/email, package, and payment method, plus a "View subscriptions" button linking to the admin panel.
- `notify_admin_payment_failed()`: displays the same table in a red/alert style, with a "Manage subscriptions" button.

##### New i18n strings (5 new, duplicates removed)

| French | English |
|---|---|
| `🎉 Nouvel abonnement souscrit` | `🎉 New subscription created` |
| `Un nouvel abonnement a été souscrit sur votre site.` | `A new subscription has been created on your site.` |
| `Voir les abonnements` | `View subscriptions` |
| `Un paiement a échoué sur votre site.` | `A payment has failed on your site.` |
| `Gérer les abonnements` | `Manage subscriptions` |

---

#### 🐛 Fix: Cancellation emails not sent — admin and user now both notified (EN)

##### Problem

When an admin cancelled a subscription (any package — free or paid), **no email was sent** to either the user or the admin. The same issue affected cancellations triggered from the user's front-end account page.

##### Root cause

- `cancel_subscription()` in the subscription manager **never fired** `do_action('sfl_subscription_cancelled', ...)` for immediate cancellations (`$at_period_end = false`), only the cron job did (for end-of-period transitions).
- `admin-subscriptions.php` updated the DB status directly without firing the action at all.
- `send_cancellation_notice()` **only sent to the user** — no admin copy existed.

##### Fix

1. **`cancel_subscription()`**: fires `do_action('sfl_subscription_cancelled', $subscription, $at_period_end)` after both immediate and end-of-period cancellations.
2. **`admin-subscriptions.php`**: fires `do_action('sfl_subscription_cancelled', ...)` after updating the local subscription status.
3. **`notify_admin_cancellation()`** (new private method): sends a styled HTML email to the admin with subscriber name/email, package name, and cancellation type (immediate vs. end of period), using `wrap_html_email()` consistently with other admin notifications.

##### New i18n strings (EN)

| French | English |
|---|---|
| `[%1$s] Abonnement annulé : %2$s` | `[%1$s] Subscription cancelled: %2$s` |
| `❌ Abonnement annulé` | `❌ Subscription cancelled` |
| `Un abonnement a été annulé sur votre site.` | `A subscription has been cancelled on your site.` |
| `Type d'annulation` | `Cancellation type` |
| `À la fin de la période en cours (accès conservé)` | `At the end of the current period (access retained)` |
| `Immédiate` | `Immediate` |
v1.5.8 📅 03 mars 2026
[1.5.8] - 2026-03-03

### 🐛 Bugfix: Limite galerie et restrictions de modules non appliquées dans le formulaire de création d'annonce

### ✨ Feature: Synchronisation des paramètres de package vers les abonnés actifs

---

#### 🇫🇷 Français

##### Problème

Lors de la création d'une annonce par un utilisateur ayant un abonnement SFL actif, la **limite d'images galerie** définie dans le package (ex. : 1 image pour le package Basic) n'était **pas respectée** dans le formulaire — l'utilisateur pouvait uploader autant d'images que la limite globale de l'installation (`listeo_max_files`).

De même, les **modules non inclus dans le package** (ex. : vidéo, coupons, FAQ, liens sociaux) continuaient d'apparaître dans le formulaire de soumission.

##### Cause racine

Deux problèmes distincts dans `class-sfl-subscription-manager.php` :

**1. `self::$package_id = 0` au moment du rendu du formulaire**

`Listeo_Core_Submit::__construct()` s'exécute au hook `init` priorité 13. Il détermine le package courant en lisant `$_COOKIE['chosen_package_id']`. Or, ce cookie n'était jamais défini pour les utilisateurs SFL — le plugin ne définissait que la session WooCommerce (`WC()->session->set(...)`) et `$_POST['listeo_core_listing_package']`, deux mécanismes que Listeo ne lit pas dans son constructeur.

Résultat : `self::$package_id = 0` → la galerie utilisait le fallback global (`listeo_max_files` = 15), sans aucune restriction de package.

**2. `listeo_listing_packages_options` vide → aucune restriction de module**

Listeo Core conditionne toute restriction de module (booking, vidéo, coupons, FAQ, liens sociaux, galerie, menu prix, horaires) à la présence de l'option `listeo_listing_packages_options`. Cette option était vide → les blocs `if(in_array('option_booking', $packages_disabled_modules))`, `if(in_array('option_gallery', ...))`, etc. ne s'exécutaient jamais, laissant tous les modules visibles dans le formulaire.

##### Correction : 3 nouveaux éléments dans `init_hooks()`

**`sfl_inject_package_cookie_for_listeo()`** — hook `init` priorité 5 (avant Listeo)

Injecte `$_COOKIE['chosen_package_id']` et `$_COOKIE['chosen_package_is_user_package']` dans la superglobale pour la requête courante, et appelle `wc_setcookie()` pour la persistance. Listeo lit ces valeurs dans son `__construct()` et fixe correctement `self::$package_id`.

**`sfl_enable_all_package_module_controls()`** — filtre `pre_option_listeo_listing_packages_options`

Retourne dynamiquement la liste complète des modules contrôlables par packages pour tout utilisateur ayant un abonnement SFL actif : `option_booking`, `option_social_links`, `option_opening_hours`, `option_pricing_menu`, `option_gallery`, `option_video`, `option_coupons`, `option_faq`. Listeo applique alors les vérifications `$package->has_listing_*()` pour chaque module.

**`skip_listeo_packages_if_sfl_active()` — simplifiée**

Supprimait le handler de l'étape `process-package` et le remplaçait par `sfl_auto_select_package_handler` — un handler incomplet qui n'appelait pas `next_step()` et ne définissait pas le cookie. L'étape ne se terminait donc pas correctement. Désormais, seule l'étape de sélection du package (`package`) est supprimée ; Listeo's `choose_package_handler` gère correctement la validation et la progression (puisque `self::$package_id` est déjà fixé via le cookie).

##### Impact

- ✅ Limite galerie respectée pour tous les packages (gratuit et payant)
- ✅ Modules non inclus dans le package masqués du formulaire (vidéo, coupons, FAQ, liens sociaux, etc.)
- ✅ Modules inclus dans le package correctement affichés
- ✅ Fonctionne au re-abonnement : le snapshot du nouveau package est immédiatement pris en compte
- ✅ Aucun impact sur les utilisateurs sans abonnement SFL (Listeo fonctionne normalement)

##### Fichiers modifiés

- `includes/class-sfl-subscription-manager.php` — ajout de `sfl_inject_package_cookie_for_listeo()`, `sfl_enable_all_package_module_controls()`, et simplification de `skip_listeo_packages_if_sfl_active()`

---

#### 🇬🇧 English

##### Problem

When a user with an active SFL subscription created a listing, the **gallery image limit** defined in the package (e.g., 1 image for the Basic package) was **not enforced** in the submission form — users could upload as many images as the global installation limit (`listeo_max_files`).

Similarly, **modules not included in the package** (video, coupons, FAQ, social links, etc.) were still visible in the submission form.

##### Root cause

Two distinct issues in `class-sfl-subscription-manager.php`:

1. **`self::$package_id = 0`** — `Listeo_Core_Submit::__construct()` runs at `init` priority 13 and reads `$_COOKIE['chosen_package_id']` to determine the current package. This cookie was never set for SFL users (only WC session and a wrong `$_POST` key were set), so `self::$package_id` stayed 0 → gallery used the global fallback.

2. **Empty `listeo_listing_packages_options`** — Listeo Core gates all per-package module restrictions on this option. Since it was empty, none of the `if(in_array('option_gallery', $packages_disabled_modules))` etc. blocks ever ran.

##### Fix

- **`sfl_inject_package_cookie_for_listeo()`** at `init` priority 5: injects `$_COOKIE['chosen_package_id']` for the current request so Listeo's constructor reads the correct package ID.
- **`sfl_enable_all_package_module_controls()`** on `pre_option_listeo_listing_packages_options`: dynamically returns all controllable module keys for SFL subscribers, enabling per-package module visibility enforcement.
- **`skip_listeo_packages_if_sfl_active()` simplified**: no longer replaces the `process-package` handler; Listeo's native `choose_package_handler` now works correctly since the package ID is properly set.

---

#### ✨ Feature: Sync package settings to active subscribers (EN)

##### Overview

A new **"Apply to all subscribers"** button has been added to the Packages admin page. When an admin modifies a package's parameters (e.g. gallery limit, modules, listing limit), this button appears automatically on the relevant package card, allowing the admin to push those changes to all currently active subscribers without requiring them to re-subscribe.

##### How it works

- **Detection**: On each page load of the Packages admin, the plugin compares the current WooCommerce product meta (the authoritative source) with every active subscriber's row in `listeo_core_user_packages`. If any field differs, an orange warning banner is shown on the package card.
- **Subscriber count**: The banner displays how many subscribers have outdated settings.
- **Button**: Clicking "Apply to all subscribers" triggers an AJAX call (`sfl_push_package_to_subscribers`). The handler updates every affected subscriber's `listeo_core_user_packages` row **and** refreshes their existing listing permissions via `update_user_listings_permissions()`.
- **Snapshot system preserved**: The sync only happens when the admin explicitly clicks the button. New subscribers continue to receive a snapshot at subscription time. The button disappears automatically after a successful sync.
- **Free packages supported**: Works for both paid (with an `sfl_package` CPT entry) and free packages (detected via `_sfl_package_product_id` user meta).

##### New code

- `SFL_Subscription_Manager::get_package_sync_status( $wc_product_id )` — compares current WC product meta with every active subscriber's stored data and returns `['has_changes', 'count', 'user_ids']`.
- `SFL_Subscription_Manager::ajax_push_package_to_subscribers()` — AJAX handler (admin-only, nonce-protected) that updates the database and refreshes listing permissions.
- `SFL_Subscription_Manager::_get_wc_package_snapshot( $wc_product_id )` — private helper building the canonical settings array from WC product meta.
- `SFL_Subscription_Manager::_get_active_subscriber_user_ids_for_product( $wc_product_id )` — private helper returning all user IDs with an active/trialing subscription (or free package assignment) for a given WC product.
- Template `admin-packages.php` updated: orange sync banner + AJAX button rendered per package card when `has_changes === true`.

---
v1.5.7 📅 02 mars 2026
[1.5.7] - 2026-03-02

### ✨ New Feature: Claim Listing → SFL Subscription Integration

---

#### 🇫🇷 Français

##### Remplacement du système de packages Listeo Core dans les réclamations d'annonces

**Problème**
Lorsqu'un vendeur réclamait une annonce (système "Claim Listing" de Listeo Core) et que l'admin validait la réclamation (`status = completed`) :
- L'annonce était transférée au nouveau propriétaire via Listeo Core ✅
- Mais aucun abonnement SFL n'était créé pour le vendeur ❌
- Résultat : dans les 2 minutes suivant la validation, SFL suspendait automatiquement l'annonce (`sfl_suspended`) car le vendeur n'avait aucun abonnement actif

Le champ "Selected Package" dans l'admin Listeo Core (CPT `claim`) ne montrait que des packages WooCommerce natifs — un système totalement séparé et incompatible avec SFL.

**Solution : `class-sfl-claim-integration.php`**

Nouveau fichier `includes/class-sfl-claim-integration.php` — classe `SFL_Claim_Integration`.

1. **Masquage du champ "Selected Package" de Listeo Core**
- Le champ CMB2 `#listeo_core_claim_metaboxes .cmb-row.cmb2-id--package` est masqué via CSS admin sur l'écran du CPT `claim`
- Le champ **Status** de Listeo Core reste visible et fonctionnel

2. **Nouvelle meta box "Abonnement SFL — Attribution automatique"**
- Positionnée en priorité `high` (avant les autres meta boxes)
- **Dropdown** : tous les packages `sfl_package` avec prix affiché (Gratuit/illimité, prix mensuel/annuel)
- **Date de fin** : champ date visible uniquement pour les packages payants
- **Résumé** : affiche le `payment_method` qui sera créé (`free` ou `manual`) et la date de fin
- **Bannière verte** : si l'abonnement est déjà créé → affiche le lien vers l'abonnement SFL
- **Avertissement jaune** : si le vendeur a déjà un abonnement actif → informe qu'aucun doublon ne sera créé

3. **Création automatique de l'abonnement SFL à la complétion du claim**
- Hook `updated_post_meta` à priorité 20 (après Listeo Core à priorité 2 qui transfère le `post_author`)
- Déclenchement quand `meta_key = 'status'` et `meta_value = 'completed'` sur un post de type `claim`
- Logique :
- Récupère l'`user_id` depuis le `post_author` de l'annonce (après transfert)
- Vérifie anti-doublon (`_sfl_subscription_created`)
- Si vendeur a déjà un abonnement actif → enregistre l'info, ne crée pas de doublon
- Package gratuit → `payment_method = 'free'`, `billing_interval = 'lifetime'`, `current_period_end = null`
- Package payant → `payment_method = 'manual'`, date de fin optionnelle
- Appelle `SFL_Subscription_Manager::assign_package_to_user()` → inscrit dans `listeo_core_user_packages`
- Déclenche `sfl_subscription_created` et `sfl_subscription_created_from_claim`
- Marque le claim avec `_sfl_subscription_created = 1` et `_sfl_subscription_id`

4. **Traductions**
- 25 nouvelles chaînes ajoutées dans `.pot` et `en_US.po`
- Fichiers `.mo` recompilés

- **Fichiers créés** : `includes/class-sfl-claim-integration.php`
- **Fichiers modifiés** : `subscription-for-listeo.php` (require + instanciation), `languages/subscription-for-listeo.pot`, `languages/subscription-for-listeo-en_US.po`, `languages/subscription-for-listeo-en_US.mo`, `languages/subscription-for-listeo-fr_FR.mo`

---

#### 🇬🇧 English

##### Replacing Listeo Core's package system in listing claims

**Problem**
When a vendor claimed a listing (Listeo Core "Claim Listing" system) and the admin validated the claim (`status = completed`):
- The listing was transferred to the new owner via Listeo Core ✅
- But no SFL subscription was created for the vendor ❌
- Result: within 2 minutes of validation, SFL automatically suspended the listing (`sfl_suspended`) because the vendor had no active subscription

The "Selected Package" field in the Listeo Core admin (CPT `claim`) only showed native WooCommerce packages — a completely separate system incompatible with SFL.

**Solution: `class-sfl-claim-integration.php`**

1. **Hiding Listeo Core's "Selected Package" field** via admin CSS — Status field remains functional
2. **New "SFL Subscription — Automatic Assignment" meta box** with SFL package dropdown, optional end date, summary, and status indicators
3. **Automatic SFL subscription creation on claim completion** — hooked on `updated_post_meta` at priority 20, handles free (lifetime) and paid (manual) packages, anti-duplicate protection, fires standard SFL hooks
4. **25 new translation strings** added to `.pot` and `en_US.po`
v1.5.6 📅 23 février 2026
[1.5.6] - 2026-02-23

### ✨ Enhancement: Dynamic "Save X%" badge on the plans page

#### Problem solved
The savings badge displayed in the Monthly/Annual toggle on the plans page was hardcoded to `17%`, with no connection to the actual prices configured in the admin.

#### Changes made
- **`includes/class-sfl-frontend.php`**: Removed the hardcoded string `'Save 17%'`
- Added a dynamic calculation of the average savings percentage based on the real monthly and yearly prices of each package: `(monthly × 12 − yearly) / (monthly × 12) × 100`
- The badge now displays the **rounded average** savings across all packages that have a yearly price configured

#### Impact
- ✅ The badge automatically reflects the actual prices configured in the admin
- ✅ No need to edit code when prices change
- ✅ Compatible with any number of packages

--- French

### ✨ Amélioration : Badge "Économisez X%" dynamique sur la page des plans

#### Problème résolu
Le badge d'économie affiché dans le toggle Mensuel/Annuel de la page des plans était codé en dur à `17%`, sans aucun lien avec les prix réels configurés dans l'administration.

#### Modifications apportées
- **`includes/class-sfl-frontend.php`** : Suppression de la chaîne fixe `'Économisez 17%'`
- Ajout d'un calcul dynamique du pourcentage moyen d'économie basé sur les prix mensuels et annuels réels de chaque package : `(mensuel × 12 − annuel) / (mensuel × 12) × 100`
- Le badge affiche désormais la **moyenne arrondie** de l'économie sur l'ensemble des packages ayant un tarif annuel configuré

#### Impact
- ✅ Le badge reflète automatiquement les vrais prix configurés en administration
- ✅ Plus besoin de modifier le code si les tarifs changent
- ✅ Compatible avec un nombre illimité de packages

---
v1.5.5 📅 26 janvier 2026
[1.5.5] - 2026-01-26

### 🐛 Correction critique : Bouton "Edit" bloqué par la limite d'annonces

#### Problème résolu
Le bouton "Edit" de Listeo était incorrectement bloqué lorsque l'utilisateur avait atteint sa limite d'annonces. Le système confondait l'édition d'annonces existantes avec la création de nouvelles annonces.

#### Corrections apportées
- **Exclusion des actions de modification** : Les actions `edit`, `continue` et `renew` sont maintenant exclues de la vérification de limite d'annonces
- **Redirection PHP** : La fonction `redirect_submit_page_without_subscription()` ne bloque plus les éditions
- **JavaScript** : Le sélecteur jQuery exclut maintenant les URLs contenant `action=edit`, `action=continue` ou `action=renew`

#### Impact
- ✅ Les utilisateurs peuvent maintenant éditer leurs annonces même si leur limite est atteinte
- ✅ La création de nouvelles annonces reste correctement bloquée quand la limite est atteinte
- ✅ Les fonctions Continue et Renew ne sont plus affectées par la limite
v1.5.4 📅 21 janvier 2026
[1.5.4] - 2026-01-21

### 💰 Intégration des paramètres de devise Listeo

#### Nouvelle fonctionnalité : Affichage dynamique de la devise
Le plugin utilise maintenant les paramètres de devise configurés dans **Listeo > Settings** pour afficher tous les prix, au lieu de l'Euro (€) codé en dur.

#### Paramètres Listeo supportés
- **Currency** : Code de devise sélectionné (USD, EUR, GBP, XOF, etc.)
- **Custom Currency** : Symbole personnalisé si votre devise n'est pas dans la liste
- **Currency Position** : Affichage avant ou après le montant (ex: `$29` vs `29€`)

#### Fichiers modifiés
- `includes/class-sfl-frontend.php` :
- Nouvelle méthode `get_listeo_currency_settings()` : Récupère symbole, position et code depuis Listeo
- Nouvelle méthode `format_listeo_price()` : Formate un prix avec la devise Listeo
- Page checkout, cartes packages, affichages commission mis à jour
- Paramètres de devise transmis au JavaScript via `sfl_frontend.currency`
- `assets/js/frontend.js` :
- Nouvelles fonctions `formatPrice()` et `formatNumber()` pour le formatage côté client
- Toggle mensuel/annuel respecte maintenant la position de la devise
- `templates/frontend-my-subscription.php` : Prix et commissions formatés avec Listeo
- `templates/admin-reports.php` : Statistiques MRR, ARR, LTV, ARPU utilisent la devise Listeo

#### Traductions i18n ajoutées
- `save` : "Économisez"
- `per_month` : "mois"
- `per_year` : "an"

#### Exemple
Si Listeo est configuré avec **USD** et position **Before** :
- Avant : `29€`
- Après : `$29`
v1.5.3 📅 20 janvier 2026
[1.5.3] - 2026-01-20

### ✨ Nouvelle fonctionnalité

#### Exclusion des administrateurs de la désactivation des annonces
- **Nouvelle option** : "Exclure les administrateurs" dans Paramètres → Annonces & Abonnements
- **Fonctionnement** : Si activé, les annonces des utilisateurs ayant le rôle Administrateur ne seront jamais désactivées, même sans abonnement actif
- **Cas d'usage** : Utile pour les comptes de test, de démonstration, ou pour les administrateurs qui gèrent le site
- **Impact** : S'applique à la suspension pour non-paiement ET au downgrade (réduction de limite d'annonces)
v1.5.2 📅 20 janvier 2026
[1.5.2] - 2026-01-20

### 🐛 Corrections de bugs

#### JavaScript & Traductions
- **Correction erreur JavaScript** - Résolution de l'erreur `Uncaught SyntaxError: missing ) after argument list` qui se produisait quand le site était configuré dans une langue autre que l'anglais
- **Échappement des traductions** - Toutes les traductions PHP dans le JavaScript inline sont maintenant correctement échappées avec `esc_js()` pour éviter les conflits avec les apostrophes
- **Fichiers corrigés** :
- `templates/admin-subscriptions.php` - 22 corrections
- `templates/frontend-my-subscription.php` - 12 corrections

#### Loco Translate - Compatibilité améliorée
- **Support du chemin personnalisé Loco** - Ajout du scan du dossier `wp-content/languages/loco/plugins/` pour détecter les traductions créées avec un chemin personnalisé dans Loco Translate
- **Chargement des traductions** - La fonction `load_textdomain()` cherche maintenant dans 5 emplacements différents :
1. `wp-content/languages/loco/plugins/` (Loco personnalisé)
2. `wp-content/languages/plugins/` (Loco standard)
3. Dossier `languages/` du plugin
4. Sous-dossier avec nom du plugin dans languages/plugins/
5. Chemin alternatif Loco avec sous-dossier
- **Détection automatique des langues** - Les langues créées dans tous les emplacements Loco apparaissent maintenant dans le sélecteur de langue des paramètres

#### Impact
- Les popups (comme "Changer de plan") fonctionnent maintenant correctement dans toutes les langues
- Les administrateurs peuvent utiliser n'importe quel chemin dans Loco Translate pour leurs traductions
v1.5.1 📅 17 janvier 2026
[1.5.1] - 2026-01-17

### 🌍 Compatibilité Loco Translate & Système de Traduction

#### ✨ Nouvelles fonctionnalités
- **Fichier POT (Template)** - Création du fichier `subscription-for-listeo.pot` avec 2618 chaînes pour Loco Translate
- **Détection dynamique des langues** - Les langues créées avec Loco apparaissent automatiquement dans les paramètres
- **Support multi-emplacements** - Recherche des traductions dans `wp-content/languages/plugins/` (Loco) et dossier plugin

#### 🔧 Améliorations techniques
- **Fonction `load_textdomain()` améliorée** - Compatible avec Loco Translate et le système WordPress standard
- **Scanning automatique** - Détection des fichiers `.mo` dans le dossier global et le dossier plugin
- **35+ langues supportées** - Noms natifs et drapeaux pour les langues courantes

#### 📖 Documentation
- **Nouvelle section** - "Traductions & Loco Translate" (Section 12) dans la documentation admin
- **Guide complet** - Instructions pas à pas pour ajouter une nouvelle langue
- **Structure des fichiers** - Explication détaillée des fichiers PO/MO/POT

#### 🌐 Traductions incluses
- **Français (fr_FR)** - Langue source du plugin
- **Anglais (en_US)** - 2618 chaînes complètement traduites
v1.5.0 📅 15 janvier 2026
[1.5.0] - 2026-01-15

### 🆕 Création Manuelle d'Abonnements & Import WC Subscriptions

#### ✨ Création manuelle d'abonnements (Admin)
- **Nouveau bouton** "Créer un abonnement" dans la page Abonnements admin
- **Modes de création** :
- Un seul vendeur (sélection individuelle)
- Plusieurs vendeurs (sélection multiple)
- Tous les vendeurs (création en masse)
- **Options disponibles** :
- Sélection du package
- Date de fin personnalisée (ou illimité)
- Statut (Actif ou Période d'essai)
- **Nouveau payment_method** : `manual` pour les abonnements créés manuellement

#### 🔄 Import WooCommerce Subscriptions
- **Nouveau bouton** "Import WC Subscriptions" (visible si WC Subscriptions est installé)
- **Prévisualisation** des abonnements WC avant import
- **Import sélectif** : choisir quels abonnements importer
- **Mapping des packages** : Associer chaque produit WC à un package SFL existant
- **Création automatique de packages** : Si aucun package SFL correspondant, création automatique avec les mêmes infos (prix, options, modules)
- **Migration automatique** des données :
- User ID, Package, Montant, Devise
- Intervalle de facturation
- Dates (début, fin, prochain paiement)
- Statut (active, on-hold, pending-cancel)
- **Attribution automatique** du package Listeo après import
- **Nouveau payment_method** : `wc_subscriptions` pour les imports

#### 📧 Notification d'expiration pour abonnements importés
- **Cron quotidien** : Vérifie les abonnements importés/manuels qui vont expirer
- **Email de rappel** : Envoyé 7 jours avant l'expiration (configurable via filtre `sfl_imported_expiration_notice_days`)
- **Contenu de l'email** :
- Nom du package et date d'expiration
- Explication que le renouvellement automatique n'est pas possible
- Lien vers la page d'abonnement pour souscrire à nouveau
- Avertissement sur la suspension des annonces
- **Anti-doublon** : Marque l'abonnement comme notifié pour éviter les emails répétés
- **Hook d'extension** : `sfl_imported_subscription_expiring_notice_sent` pour intégration avec Email Template for Listeo

#### ⚠️ Important : Limitations des abonnements importés
Les abonnements importés depuis WooCommerce Subscriptions :
- ✅ Conservent toutes les données (utilisateur, package, dates, statut)
- ❌ Ne peuvent PAS être renouvelés automatiquement (les infos de paiement ne sont pas transférables)
- 📧 Les vendeurs sont notifiés 7 jours avant l'expiration
- 🔄 Les vendeurs doivent souscrire à un nouvel abonnement SFL pour continuer

#### 🎨 Améliorations Interface
- **Affichage Nom + Prénom** : Les vendeurs sont maintenant affichés avec leur prénom et nom
- **Nouveaux badges Gateway** :
- Badge violet "Manuel" pour les abonnements manuels
- Badge violet "WC Subs" pour les imports WooCommerce Subscriptions
- **Interface de mapping** : Tableau pour associer produits WC → packages SFL

#### 🔧 Technique
- Nouveaux handlers AJAX : `sfl_create_manual_subscription`, `sfl_get_wc_subscriptions_to_import`, `sfl_import_wc_subscriptions`
- Nouvelle fonction : `create_sfl_package_from_wc_product()` pour création automatique de packages
- Nouveau cron : `sfl_notify_imported_expiring` pour les notifications d'expiration
- Intégration avec la table `wp_sfl_subscriptions`
- Attribution automatique des packages Listeo via `listeo_core_user_packages`
v1.4.0 📅 04 janvier 2026
[1.4.0] - 2026-01-04

### 🏪 Intégration Passerelles de Paiement Vendeur (LPP v1.7.0)

#### Nouvelle fonctionnalité
Intégration complète avec le système de passerelles vendeur de Prepaid Booking for Listeo v1.7.0, permettant de contrôler l'accès à cette fonctionnalité via les packages d'abonnement.

### ✨ Fonctionnalités

#### 📦 Contrôle par Package
- **Nouvelle option package** : "Allow custom gateways" dans les produits WooCommerce (Listing Package)
- **Snapshot intégré** : L'option est capturée dans le snapshot au moment de l'achat
- **Affichage admin** : Badge "Passerelles vendeur : Activé" dans la liste des packages
- **Affichage frontend** : Option visible sur checkout, liste packages et "Mon Abonnement"

#### 🔐 Vérification d'Accès
- **`owner_allows_vendor_gateways($owner_id)`** : Nouvelle méthode pour vérifier si le package du vendeur autorise les passerelles personnalisées
- **`get_vendor_gateway_option()`** : Récupère l'option depuis le snapshot
- **Intégration LPP** : LPP vérifie automatiquement via `SFL_Subscription_Manager::owner_allows_vendor_gateways()`

#### 🎨 Interface
- **Dashboard** : Nouvelle carte "Vendor Gateways" dans la section "Pourquoi choisir SFL"
- **Documentation** : Section mise à jour avec les nouvelles fonctionnalités
- **Traductions** : Français et anglais complets

### 🔧 Fichiers modifiés
- `includes/class-sfl-package-commission.php` : Option `_sfl_allow_vendor_gateways`
- `includes/class-sfl-subscription-manager.php` : Snapshot et méthodes de vérification
- `templates/admin-packages.php` : Affichage de l'option
- `templates/admin-dashboard.php` : Nouvelle carte avantage
- `templates/frontend-my-subscription.php` : Affichage sur Mon Abonnement

### 📝 Comment ça fonctionne

1. **Prérequis** : LPP v1.7.0+ avec "Enable Vendor Payment Gateways" activé
2. **Configuration admin** : Activer "Subscription control" dans LPP
3. **Configuration package** : Cocher "Allow custom gateways" dans le produit Listing Package
4. **Vendeur** : Le vendeur avec ce package verra le menu "Mes Paiements" dans son dashboard

### 🌐 Traductions
- **Français** : Toutes les nouvelles chaînes traduites
- **Anglais** : Traductions complètes en_US

---

## [1.3.0] - 2026-01-04

### ✨ Nouvelles fonctionnalités

#### 🔐 Contrôle Acompte Complet par Package
- **Désactivation complète** : Si le package du vendeur n'autorise pas les acomptes, le système d'acompte est DÉSACTIVÉ pour toutes ses réservations
- **Logique intelligente** : Vérifie le package via `SFL_Subscription_Manager::owner_allows_deposit()` lors de chaque réservation
- **Intégration LPP** : Modification de `should_use_deposit()` pour respecter le package du vendeur

#### 🌍 Intégration WiPay & 2Checkout Améliorée
- **Période d'essai** : Support complet des périodes d'essai (trial_days) pour WiPay et 2Checkout
- **Annulation 2Checkout** : Support du paramètre `at_period_end` via l'API 2Checkout (RecurringEnabled=false)
- **Méthodes correctes** : Correction des appels de méthodes (`create_subscription`, `update_subscription`, `get_by_external_id`)
- **Champs standardisés** : `billing_interval`, `next_billing_date`, `trial_end` pour toutes les passerelles

#### 📊 Dashboard Amélioré
- **Nouvelles cartes** : WiPay + 2Checkout et Contrôle Acompte dans la section "Pourquoi choisir SFL"
- **4 passerelles** : Affichage des 4 méthodes de paiement disponibles

#### 🌐 Affichage Admin Packages
- **Méthodes actives** : WiPay et 2Checkout affichés dans "Méthodes de souscription actives"
- **Badges colorés** : Stripe (violet), PayPal (bleu), 2Checkout (bleu foncé), WiPay (vert)

### 🐛 Corrections
- **suspend_user_listings** : Correction du paramètre (objet subscription au lieu de user_id)
- **Traductions** : Nouvelles chaînes pour toutes les fonctionnalités

---

## [1.2.0] - 2026-01-03

### ✨ Nouvelle fonctionnalité : Contrôle d'Acompte par Package

#### Système d'Acompte (Dépôt)
- **Nouvelle option package** : Checkbox "Autoriser la personnalisation d'acompte" dans les produits WooCommerce (Listing Package)
- **Snapshot intégré** : L'option d'acompte est capturée dans le snapshot au moment de l'achat de l'abonnement
- **Méthode de vérification** : Nouvelle fonction `owner_allows_deposit($owner_id)` pour vérifier si le vendeur a accès aux acomptes
- **Affichage package admin** : L'option "Config. acompte : Activé" s'affiche dans la liste des packages
- **Affichage frontend** : Option visible sur les pages checkout, liste packages, page succès et "Mon Abonnement"

#### Intégration avec Prepaid Booking
- **Contrôle via package** : Quand l'option "Contrôle via packages de souscription" est activée dans Prealable, seuls les vendeurs dont le package autorise les acomptes verront le bouton de configuration sur "Mes Annonces"
- **Vérification runtime** : La vérification du package se fait à chaque affichage du bouton (pas au chargement du plugin)

#### Fichiers modifiés
- `includes/class-sfl-package-commission.php` : Ajout section et sauvegarde option `_sfl_allow_deposit`
- `includes/class-sfl-subscription-manager.php` : Snapshot et méthodes `get_deposit_option()`, `owner_allows_deposit()`
- `templates/admin-packages.php` : Affichage de l'option d'acompte
- `templates/frontend-my-subscription.php` : Affichage sur la page Mon Abonnement
- `includes/class-sfl-frontend.php` : Affichage sur checkout et liste packages

---

## [1.1.1] - 2025-01-03

### ✅ Corrections et améliorations

#### Annulation d'abonnement
- **WiPay** - Même logique d'annulation que Stripe/PayPal (options de fin de période)
- **2Checkout** - Annulation via l'API avec gestion des erreurs

#### Interface Admin
- **Affichage passerelles** - 2Checkout et WiPay visibles dans la liste des packages
- **Metabox synchronisation** - Affiche l'état de synchronisation 2Checkout et WiPay
- **Badges gateway** - Couleurs distinctes (2Checkout = bleu, WiPay = vert)
- **Liens rapides** - Accès direct aux dashboards 2Checkout et WiPay
- **Vue abonnement** - Section références pour toutes les passerelles

#### Interface Vendeur
- **Bouton modifier paiement** - Support 2Checkout (redirection portail client)
- **Affichage méthode** - "2Checkout" et "WiPay (Caraïbes)" correctement affichés

#### Documentation
- **Réorganisation sections** - Changelog déplacé après WiPay & 2Checkout
- **Nouvelles traductions** - Chaînes anglaises pour toutes les modifications

---

## [1.1.0] - 2025-01-03

### 🌍 Nouvelles passerelles de paiement

#### WiPay (Caraïbes) 🌴
- **Intégration complète** - Support des paiements par carte bancaire via WiPay
- **Mode direct uniquement** - Pas de plugin WooCommerce requis
- **Renouvellement MANUEL** - Bouton de renouvellement affiché 7 jours avant expiration
- **Pays supportés** - Trinidad & Tobago (TT), Jamaïque (JM), Barbade (BB)
- **Callback sécurisé** - Retour automatique après paiement avec création d'abonnement

#### 2Checkout / Verifone (International) 🌐
- **Subscription API complète** - Renouvellements automatiques comme Stripe/PayPal
- **Synchronisation des produits** - Création automatique des produits et prix dans 2Checkout
- **Webhooks IPN** - Réception des événements (renouvellement, échec, annulation, remboursement)
- **Support mondial** - 200+ pays et multiples devises
- **Mode test et production** - Environnement sandbox disponible

### ✨ Améliorations

#### Système de paiement
- **Nouveaux boutons checkout** - WiPay et 2Checkout affichés sur la page de paiement
- **Vérification étendue** - `is_paid_subscription` inclut maintenant wipay et 2checkout
- **Gestionnaire de packages** - Synchronisation 2Checkout ajoutée au bouton "Sync"

#### Interface Admin
- **Statut des passerelles** - WiPay et 2Checkout affichés dans les paramètres
- **URLs des webhooks** - URL REST API pour 2Checkout avec bouton copier
- **Statut de synchronisation** - Indicateurs pour 2Checkout dans la liste des packages

#### Interface Vendeur
- **Bouton de renouvellement WiPay** - Affiché dans "Mon Abonnement" avant expiration
- **Message d'avertissement** - Indication du renouvellement manuel pour WiPay
- **Redirection automatique** - Vers la page de paiement pour le renouvellement

### 🔧 Fichiers ajoutés
- `includes/class-sfl-wipay-handler.php` - Gestionnaire complet WiPay
- `includes/class-sfl-twocheckout-handler.php` - Gestionnaire complet 2Checkout

### 📝 Traductions
- Ajout des traductions françaises pour toutes les nouvelles chaînes
- Messages d'erreur et de succès pour WiPay et 2Checkout
- Labels et descriptions dans l'interface admin

### ⚙️ Configuration requise
Pour activer ces passerelles dans Prepaid Booking for Listeo :
```php
// WiPay
update_option('lpp_subscription_wipay_enabled', 'yes');

// 2Checkout
update_option('lpp_subscription_2checkout_enabled', 'yes');
```

---

## [1.0.0] - 2025-12-26

### 🎉 Version initiale

Première version publique du plugin Subscription for Listeo.

### ✨ Fonctionnalités principales

#### Système d'abonnement
- **Abonnements récurrents** - Mensuel et annuel avec renouvellement automatique
- **Stripe Billing natif** - Intégration directe sans WooCommerce Subscriptions
- **PayPal Subscriptions natif** - Support complet des abonnements PayPal
- **Synchronisation automatique** - Création des produits et prix Stripe/PayPal
- **Webhooks sécurisés** - Gestion automatique des renouvellements et annulations

#### Options par forfait
- **Commission personnalisée** - Taux de commission spécifique par forfait (%, fixe, ou 0%)
- **Paiement en espèces** - Option permettant le paiement cash pour les réservations
- **Période d'essai** - Jours gratuits avant la première facturation
- **Modules Listeo** - Contrôle des modules (Booking, Reviews, Gallery, etc.) par forfait
- **Limite d'annonces** - Nombre maximum d'annonces par forfait
- **Annonces en vedette** - Nombre d'annonces pouvant être mises en avant

#### Snapshot forfait
- **Protection des abonnés** - Les paramètres du forfait sont figés au moment de l'achat
- **Modifications futures** - Les changements de forfait n'affectent pas les abonnés existants

#### Interface Admin
- **Tableau de bord** - Vue d'ensemble des abonnements, MRR, ARR, statistiques
- **Gestion des packages** - Liste, synchronisation, configuration des forfaits
- **Liste des abonnés** - Historique complet avec détails et actions
- **Rapports** - Analyse des revenus, taux de rétention, churn rate
- **Gestion des emails** - Configuration des notifications automatiques
- **Documentation** - Guide d'utilisation intégré

#### Interface Vendeur
- **Page "Mon Abonnement"** - Visualisation du forfait actuel
- **Changement de forfait** - Upgrade/downgrade depuis le dashboard
- **Modification du paiement** - Mise à jour de la carte via Stripe Customer Portal
- **Annulation** - Possibilité d'annuler l'abonnement

#### Gestion des annonces
- **Durée illimitée** - Les annonces restent actives tant que l'abonnement est actif
- **Suspension automatique** - En cas de downgrade, les annonces excédentaires sont suspendues
- **Réactivation** - Les annonces suspendues peuvent être réactivées lors d'un upgrade

#### Intégration
- **Prepaid Booking for Listeo** - Dépendance requise pour les passerelles de paiement
- **Listeo Core** - Compatibilité complète avec le système de packages Listeo
- **WooCommerce** - Utilisation des produits Listing Package

#### Sécurité
- **Activation conditionnelle** - Le plugin ne s'active que si les prérequis sont remplis
- **Vérification de licence** - Compatible avec les versions obfusquées et originales

---

## Notes de mise à jour

### Prérequis
- WordPress 5.8+
- WooCommerce 5.0+
- Listeo Core (thème Listeo)
- Prepaid Booking for Listeo (avec licence valide)
- WooCommerce Stripe Gateway

### Installation
1. Installer et configurer Prepaid Booking for Listeo
2. Activer les abonnements Stripe et/ou PayPal dans Préalable
3. Installer Subscription for Listeo
4. Activer la licence
5. Activer le plugin dans Paramètres

---

## Liens

- [Documentation](https://costaplugin.com/documentation/subscription-for-listeo/)
- [Support](mailto:[email protected])
- [Site officiel](https://costaplugin.com/)

📖 Légende des changements

✨ Nouvelles fonctionnalités 🐛 Corrections de bugs 🚀 Améliorations 🔐 Sécurité 🗑️ Suppressions