=== FluentCart Customer Rights ===
Contributors: fluentcart
Tags: ecommerce, fluentcart, withdrawal, widerruf, gdpr, consumer rights, germany, eu
Requires at least: 5.6
Tested up to: 6.8
Requires PHP: 7.4
Stable tag: 0.1.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

EU/German digital withdrawal function (Widerrufsbutton, § 356a BGB) add-on for FluentCart — mandatory for German B2C stores from June 19, 2026.

== Description ==

FluentCart Customer Rights is a free add-on for [FluentCart](https://fluentcart.com) that adds the legally required **digital withdrawal function** ("Widerrufsbutton") to your store. From **June 19, 2026**, German law (§ 356a BGB, transposing Art. 11a of the EU Consumer Rights Directive — Directive (EU) 2023/2673) requires every B2C merchant selling through an online interface to provide a permanently available, prominently placed way for consumers to withdraw from a contract.

It applies to physical goods, digital products, memberships, subscriptions, and service bookings alike. It ships as a separate free add-on rather than in core because only German/EU stores need it.

**Customer side**

* Public withdrawal page via the `[fluent_cart_withdrawal]` shortcode — no login required, mobile-friendly, with the statutory labels ("Vertrag widerrufen" → "Widerruf bestätigen") built in.
* Two-step flow: identify (name + order number + email, with an optional "which part of the contract?" note) → review with the exact statutory confirm control → success screen showing a reference code and the date and time of receipt.
* **Verified vs. unverified**: if the order number and billing email match an order, the customer sees a minimal order summary; if not, the declaration is still accepted and queued for manual review. Nobody is ever turned away — the law forbids refusing a declaration just because it cannot be matched.
* Duplicate protection on both paths: an open request for the same order (or the same email + reference) is reported back instead of duplicated.
* Deep links from the customer profile: a "Withdraw from contract here" entry point on orders and subscriptions opens the form prefilled.
* A timestamped **acknowledgment email** sent immediately — including for unverified declarations — and impossible to switch off while the module is enabled, because the law requires it.

**Merchant side**

* "Withdrawal (EU)" module card in FluentCart settings (enable, customer-profile entry point, and an explicitly-warned opt-in to hard-reject unmatched declarations — off by default).
* Admin notification email on every new request, plus automatic accepted/declined decision emails to the customer — all manageable in the standard Email Notifications screen.
* A status-colored widget on the single-order admin page (light + dark theme) with one-click Accept / Decline; actor and timestamp are recorded.
* A full **Withdrawal Requests page** in the admin SPA (`#/withdrawal-requests`) on FluentCart's standard table stack — tabs, search, column toggles, sorting, and pagination — plus inline "Link order" to attach unverified requests to the right order.

**Abuse protection** (because the form must accept strangers): honeypot field, per-IP rate limiting (10 attempts / 15 min), HMAC-signed 15-minute token between the two steps, enumeration-safe responses, and full output escaping of consumer-supplied text in admin.

== Installation ==

1. Upload the `fluent-cart-customer-rights` folder to the `/wp-content/plugins/` directory, or install the plugin through the WordPress plugins screen.
2. Make sure **FluentCart** is installed and activated.
3. Activate **FluentCart Customer Rights** through the 'Plugins' screen in WordPress.
4. Go to FluentCart → Settings → Features & addon → **Withdrawal (EU)** and enable it. Activating creates a draft "Withdraw from contract" page containing the `[fluent_cart_withdrawal]` shortcode.

== Frequently Asked Questions ==

= Does this plugin require FluentCart? =

Yes. FluentCart Customer Rights requires FluentCart to be installed and activated. A notice will be shown in the admin if the dependency is missing.

= Who needs this plugin? =

Any store that sells to consumers (B2C) in Germany through an online interface. From June 19, 2026, German law (§ 356a BGB) requires a digital withdrawal function. Non-compliance carries Abmahnung (cease-and-desist) exposure and an extended withdrawal period of up to 12 months + 14 days.

= Does the customer need an account to withdraw? =

No. The withdrawal page is public and requires no login — a login wall does not satisfy the statutory requirement.

= What happens if a declaration cannot be matched to an order? =

It is still accepted. Unmatched declarations land in an "unverified" queue for manual linking by the merchant. The law does not allow refusing a valid declaration just because it cannot be auto-matched.

= Can I turn off the acknowledgment email? =

No. While the module is enabled, the timestamped acknowledgment of receipt is always sent, because the law requires the consumer to receive confirmation on a durable medium with the date and time of receipt.

= Where is the data stored? =

In a dedicated table, `{prefix}fctcr_requests` — one row per declaration (order link, verified flag, status, name/email/reference/message, and GMT receipt timestamp). It is created on activation and retained on deactivation as a legal record.

== Changelog ==

= 0.1.1 =
* Fix: Estimated refund was inflated on discounted orders — `unit_price` is the pre-discount list price; per-unit `discount_total / qty` is now subtracted before computing the tax-aware refund amount (`effectiveUnitPrice()` helper added; applies to both the identify response and the submit-time snapshot)
* Fix: Excluded-categories, blocked-order-statuses, and blocked-shipping-statuses remote-select pickers in the Withdrawal settings page were not loading options — stale `fctres` hook prefix corrected to `fctcr` in the three `add_filter` registrations in `Settings.php`
* Fix: Withdrawal button is now hidden in the customer portal (subscription, order, and dashboard) when every item in the order is Art. 16 CRD-excluded; the same all-excluded check is enforced as a hard 422 block at `fctcr_identify` and `fctcr_submit` time

= 0.1.0 =
* Initial release
* Public two-step withdrawal form via the `[fluent_cart_withdrawal]` shortcode with statutory § 356a BGB labels
* Verified / unverified request handling — every declaration is accepted, unmatched ones queued for manual linking
* Timestamped acknowledgment email through FluentCart's email pipeline
* Admin order-page widget (Accept/Decline) with decision emails
* Withdrawal Requests admin page with search, sorting, column toggles, and order linking
* Customer profile entry points on orders and subscriptions
* Abuse protection: honeypot, per-IP rate limiting, HMAC-signed step token, enumeration-safe responses
