=== Chat App Brasil ===
Contributors: lupeedesign
Tags: whatsapp, woocommerce, whatsapp notifications, abandoned cart, whatsapp button
Requires at least: 6.0
Tested up to: 6.9
Requires PHP: 7.4
Stable tag: 3.5.1
WC requires at least: 7.0
WC tested up to: 9.8
License: GPLv2 or later

Send automatic WhatsApp messages to WooCommerce customers. Order notifications, abandoned cart, PIX, tracking and WhatsApp button.

== Description ==

**Chat App Brasil** is the most complete WhatsApp plugin for WooCommerce. Connect your store to WhatsApp and send automatic messages to your customers at every step of their journey — from new order to post-sale follow-up.

Official integration with the **ChatAppBrasil** platform — no direct WhatsApp Business API setup required.

= Why Chat App Brasil? =

* ✅ **WhatsApp + WooCommerce native** — no complex setup
* ✅ **Floating WhatsApp button** with click tracking and full customization
* ✅ **Abandoned cart recovery** via WhatsApp (CartBounty Free & Pro)
* ✅ **PIX via WhatsApp** — automatic QR code (Mercado Pago & Asaas)
* ✅ **Order tracking** — Correios and Melhor Envio
* ✅ **WooCommerce Subscriptions** — renewal and cancellation notifications
* ✅ **Back In Stock** — notify customers when a product is available again
* ✅ **HPOS compatible** — WooCommerce 7.1+

= Main Features =

**Automatic Order Notifications**
Each WooCommerce status triggers a personalized message to the customer and
optionally to the administrator. Individual toggle per notification — message
is preserved even when deactivated. Supported statuses: new order, pending,
on-hold, processing, completed, cancelled, refunded, failed, order note,
custom statuses.

**Payments Tab — Mercado Pago PIX**
Automatic QR code and copy-and-paste code delivery. Two modes:
* Immediate: sends in real time
* Reminder: sends only if not paid after X minutes (cancels if paid before)
* PIX expired notification: alerts the customer when time expires

**Payments Tab — Asaas PIX**
Full integration with the Asaas gateway for PIX payments.
Same immediate/reminder options. QR code + copy-and-paste code.

**Payments Tab — Asaas Boleto**
Automatic delivery of the boleto link when an Asaas Boleto order is created.
Use %boleto_url% in the message to include the link.

**Payments Tab — PIX Expired**
Unified notification for Mercado Pago and Asaas when PIX expires without payment.
Configurable delay in minutes after PIX generation.

**Tracking Tab — Correios and Melhor Envio**
Automatic notification when an order is completed and a tracking code is available.
Detects Correios (WooCommerce Correios) and Melhor Envio automatically.
Tags: %tracking_code%, %tracking_url%

**Abandoned Cart Recovery**
Up to 3 follow-ups via CartBounty Free or Pro, configurable interval per step.
Independent toggle per follow-up.

**Aftersales**
Automatic message X hours after order completion.

**Back In Stock**
WhatsApp notification when a product returns to stock.

**Easy Digital Downloads (EDD)**
New order and completed order notifications for digital products.

**Contact Form 7 / Caldera Forms**
Messages triggered via contact forms with tag substitution, line breaks preserved.

**Floating WhatsApp Button**
Configurable SVG button: position, size (40-200px), edge offset (0-300px),
pulse effect, tooltip, pre-filled message, hide by device.

**Click Tracking**
Every button click is recorded via AJAX for visitors and logged-in users.

**Bulk Messaging**
Share posts/products with a contact list, personalized messages, customer import.

**Message Logs**
Statistics cards (sent, errors, dedup), expandable rows with order link,
resend button, separate tabs by type, pagination, direct order link.

**Private Order Notes**
Automatic note on every WhatsApp send with status, number, and resend link.

**Security**
Configurable deduplication, rate limiting, send hours filter, error monitoring
(auto-pause after 1h of failures), API errors in friendly language.

**HPOS Compatibility**
Fully compatible with WooCommerce HPOS (7.1+).

== Installation ==

1. Plugins > Add New > Upload Plugin > select the .zip
2. Activate the plugin
3. Chat App Brasil > Settings > enter your Access Token
4. Configure messages in Notifications, Payments, Tracking, and other tabs

== Available Tags ==

= Order =
%id%, %order_key%, %order_date%, %order_time%, %order_link%, %order_status%,
%product%, %product_name%, %order_subtotal%, %order_shipping%, %order_total%,
%order_discount%, %currency%, %cust_note%, %shipping_method%,
%payment_method_title%, %payment_url%

= Customer =
%billing_first_name%, %billing_last_name%, %billing_email%, %billing_phone%,
%billing_city%, %billing_state%, %billing_address_1%

= Shipping =
%shipping_first_name%, %shipping_address_1%, %shipping_city%, %shipping_state%

= Tracking =
%tracking_code% — tracking code (Correios or Melhor Envio)
%tracking_url%  — tracking link

= Payments =
%pix_code%    — PIX copy-and-paste code (Mercado Pago or Asaas)
%boleto_url%  — boleto link (Asaas)
%payment_url% — pending payment link

= Other =
%shop_name%, %note%
Spintax: {Hello|Hi|Hey} — random variation per send

== Frequently Asked Questions ==

= How do I send automatic WhatsApp messages in WooCommerce? =
Configure your token in Chat App Brasil > Settings and set your messages in Chat App Brasil > Notifications. WhatsApp messages are sent automatically on every order status change.

= Can I send the PIX QR code via WhatsApp? =
Yes. The plugin integrates with Mercado Pago PIX and Asaas PIX. The QR code is automatically sent as an image with the copy-and-paste code when the order is created.

= Does it work with WhatsApp Business? =
Yes. The ChatAppBrasil platform uses the WhatsApp Business API. You need an active account at chatappbrasil.com.

= How does abandoned cart recovery via WhatsApp work? =
Install CartBounty Free or Pro, configure your follow-ups in the Abandoned Cart tab, and the plugin automatically sends WhatsApp messages to customers who abandoned their cart.

= Can I send tracking codes via WhatsApp? =
Yes. Install WooCommerce Correios or Melhor Envio. When an order is completed with a tracking code, the plugin sends it automatically via the Tracking tab.

= The button does not appear =
Fill in the phone number in Chat App Brasil > Button.

= Messages not sending =
Confirm the Access Token, check the Logs, ensure the message is not empty.
Check for a "Sends paused" alert in the admin panel.

= Asaas PIX not sending =
Ensure the WooCommerce Asaas plugin is active and the order payment method
is "asaas-pix". The __ASAAS_ORDER meta must be saved.

= Tracking notification not sending =
The tracking code must be available when the order is marked as Completed.
Verify WooCommerce Correios or Melhor Envio is active with a code on the order.

= "Sends paused" alert showing =
WhatsApp disconnected. Go to dashboard.chatappbrasil.com, reconnect, and click
"Reactivate sends" in the admin alert.

== Changelog ==

= 3.5.1 =
* FIX: a tracking notification that failed to send still marked the order as notified. The order was stamped "tracking sent" without looking at the result, and that stamp is exactly what makes the retry and the Melhor Envio poller skip the order — one platform error and the customer never got the code. Only what actually went out is stamped now
* NEW: tracking that did not go out is retried on its own — 5 minutes later, then 15, 1 hour and 12 hours. Once the attempts run out the plugin gives up (instead of hammering the order hourly for 30 days) and writes an order note with the last error and how to resend
* FIX: when the send went through the anti-ban queue, the failure happened after the plugin had already called the tracking sent, and nobody looked at that order again. The queue now reports back, the stamp is undone and the tracking goes back into the retry line
* FIX: saving the tracking code that was already stored sent nothing and did not say why — anyone generating the label in the Melhor Envio plugin opens the order with the field already filled, clicks save, reads "Code saved" and the customer gets nothing. The button now notifies the customer whenever the order has not been notified yet, and the screen says what actually happened: sent, queued, already notified, or the send error
* NEW: you choose which order statuses trigger the tracking lookup and message (Tracking tab → "When to look for tracking"). It used to be "Completed" and nothing else: a store that ships on "Processing" or on a status of its own never saw the notice go out on its own. Nothing ticked still means Completed, and the Melhor Envio poller now scans exactly the same statuses
* FIX: an internal error from the sending platform (ERR_INTERNAL_ERROR) on a message carrying a link is now retried once without the link preview. The preview is built by the sending server, which has to open the URL in the message: a link it cannot open was taking the whole message down. Without the preview you lose the link card, not the notice
* IMPROVED: ERR_INTERNAL_ERROR showed up raw in the log and in the order note. It now reads as plain Portuguese, like the other errors

= 3.5.0 =
* FIX: messages refused with "identical message already sent today" that were never sent. The duplicate mark was written BEFORE the send and survived even when the API refused it — the customer got no notice and every retry hit the same refusal. The mark now only covers what actually went out: a failed send clears its own mark
* FIX: the Resend button (order note, order list and log) and the Test send were blocked as duplicates. These are deliberate actions — sending the same text to the same number again is exactly what was asked — and they now always go out
* FIX: different orders carrying the same text fought over the same mark. The key is now per event (order + number + message), so two orders from the same customer, or two customers, never block each other
* FIX: number and text were glued together with no separator to build the key, so two customers whose number and message fitted together produced the same key — the second one never received anything
* FIX: a blocking window saved blank or as zero lasted FOREVER (a 0 expiry never expires). Blank now means 24h, and the ceiling is one week
* IMPROVED: a duplicate refusal now says what happened to the twin — "already delivered X ago (source: Y)" or "queued, waiting to go out" — instead of announcing a send that may never have happened
* IMPROVED: the order note for a duplicate no longer says "failed ✗" about a message that was delivered, and the log line now carries the order and the source, so the twin can be found
* NEW: Order lookup by the AI — the store can now answer "where is my order?" for the WhatsApp AI, with status, items, tracking and payment link. Turn it on under Chat App Brasil → Configuration, in the "Order lookup by the AI" card: one button generates the token and the screen shows the address and token ready to copy
* The lookup ships TURNED OFF. With no token generated, the address refuses every question — updating the plugin never opens anything by itself
* SECURITY: the AI only sees orders belonging to the phone number of the ongoing conversation. Someone who bought using another phone can get through with the pair order number + CPF/CNPJ, never with the document alone
* SECURITY: "order does not exist" and "document does not match" answer exactly the same, so nobody can discover an order exists by guessing numbers
* SECURITY: the response never includes tax ID, e-mail or address. Only status, date, total, items, tracking and payment link — and the payment link only on orders still awaiting payment
* Phone numbers are matched by area code + the last 8 digits, so the ninth digit and the checkout mask never separate a customer from their own orders
* Tracking in the lookup comes from the same resolver the messages use (Correios → Melhor Envio → Melhor Envio API), so the lookup and WhatsApp never disagree about the same order
* Buttons to generate another token and to switch the lookup off at any time
* IMPROVED: One click turns the lookup on — the plugin registers this store with XMetric Whats by itself, using the sending key it already has, and confirms the gateway managed to query it. Nothing to copy between panels
* The "Order lookup by the AI" card only shows for stores using XMetric Whats as the sending server. With one exception: if the lookup is already on, the card stays visible even with the server back on ChatApp Brasil, because hiding a switch that is on would leave the store unable to turn it off
* Turning the lookup off notifies XMetric Whats, so it stops asking instead of piling up errors on a screen the store owner already switched off

= 3.4.0 =
* NEW: Sending server option — besides ChatApp Brasil (the default), the plugin can now send through XMetric Whats. The option is discreet: it sits at the bottom of the Access Token card under Configuration, and shows as a single line of text until it is switched on
* ChatApp Brasil remains the default: anyone who never opens the option sees no change in behaviour, and upgrading from 3.3.1 keeps sending exactly as before
* NEW: Switching XMetric Whats on swaps the fields — server address (pre-filled with https://whats.xmetric.com.br) and the token label becomes "Client key". The "Save messages to the panel ticket" option disappears, because it does not apply to that server
* IMPROVED: Test Connection now answers two questions on XMetric Whats — whether the key is valid and whether a number is paired. Before, a good token on a store with no connected number was announced as ready to send
* IMPROVED: Changing the server or the address invalidates the previous token validation, as changing the token already did — a key approved by one platform says nothing about the other
* IMPROVED: The XMetric address accepts being pasted in any shape (no https, trailing slash, or the whole panel URL copied from the browser) and is normalised before saving
* INTERNAL: The three call sites that built the API address on their own (text send, media send and token test) now use a single resolver. The wlp_api_url filter still applies, and applies last

= 3.3.1 =
* NEW: Direct integration with the Melhor Envio API — set the access token on the Tracking tab and the plugin fetches the tracking code automatically, without depending on the ME plugin to store the data
* NEW: Tracking card on the order screen — add or edit the tracking code by hand and the WhatsApp message goes out to the customer right away
* NEW: Automatic hourly poller that checks Melhor Envio for new codes on completed orders that have no tracking sent yet
* NEW: Automatic retry via Action Scheduler when the tracking code is not available yet at the time the order is completed
* FIX: Duplicate expired PIX message — idempotency through the _wlp_pix_expired_sent meta stops Action Scheduler and the cancellation hook from sending two consecutive messages for the same order (MP and Asaas)
* FIX: Payment method check in wlp_mp_pix_expired switched to strpos, compatible with Mercado Pago PIX gateway variations
* FIX: The %rt_total% tag in abandoned cart now works as an alias of %cart_total%

= 3.3.0 =
* FIX: Compatibility with the new platform (Whaticket) — send success is now determined by the HTTP response code instead of the legacy "status" field. Delivered messages no longer show as "Unknown"/error in the log and order notes, and the false "Sends paused" warning is no longer triggered
* FIX: Send endpoint updated to the platform's documented address (dashboard.chatappbrasil.com/backend/api/messages/send), customizable via the wlp_api_url filter
* NEW: Anti-ban system — configurable minimum spacing between sends (default 20s) plus random jitter (default up to 15s). Burst sends are queued and dispatched one at a time, mimicking human pace; the queue is processed on every request and by a 1-minute cron
* NEW: Messages that exceed the per-minute rate limit now join the anti-ban queue instead of being dropped
* NEW: "Save messages on ticket" option (saveOnTicket) — records store messages in the contact's conversation on the platform; linkPreview enabled for text sends
* FIX: Token test compatible with the new platform's error format (HTTP 401/403 and ERR_* codes)
* FIX: The abandoned cart frequency chosen on the settings screen is now persisted on save (it previously reverted to 5 min)
* FIX: The "Block identical messages" and "Sending hours" toggles can now be turned off (the off state was never saved)
* IMPROVEMENT: New platform error codes translated in the log (ERR_SENDING_WAPP_MSG, ERR_SESSION_EXPIRED, ERR_WAPP_INVALID_CONTACT)
* SECURITY: Log directory now uses a secret suffix in its name (uploads/woosend-logs-xxxx) — on Nginx servers .htaccess is ignored and logs (containing customer phone numbers and messages) were reachable at a predictable URL; the legacy directory is migrated automatically
* FIX: Sending-hours window now supports ranges crossing midnight (e.g. 22h–8h) — that configuration used to block all sends
* FIX: Local numbers from area code 55 (Santa Maria-RS region) now get the country code 55 correctly — they were previously sent without it and never delivered
* FIX: Abandoned cart (CartBounty Pro): follow-ups 2 and 3 never fired — the 24h eligibility window was shorter than their delays (48h/72h); the window now covers the full follow-up horizon
* FIX: Abandoned cart (CartBounty Free): added an eligibility window — activating the plugin with historical carts could blast messages to months-old carts
* FIX: Abandoned cart (Free): carts no longer get permanently stuck after 3 attempts blocked by sending hours/temporary errors
* FIX: Asaas boleto and new-order messages are no longer re-sent when the customer reloads the thank-you page (order meta guard)
* FIX: Back In Stock: subscribers notified via the direct hook are now marked as processed — the cron no longer re-sends the same notification after 5 minutes
* FIX: After-sales: elapsed-time calculation no longer mixes timezones (±3h drift) and the sending-hours window now applies to after-sales messages
* FIX: Media sending no longer requires the cURL extension (uses the WordPress HTTP API)
* IMPROVEMENT: After-sales, order-note and PIX reminder sends now record an order note and are categorized correctly in the log
* IMPROVEMENT: Floating button click tracking rewritten in vanilla JavaScript with sendBeacon — works on themes without jQuery and never delays the visitor's navigation
* CLEANUP: Removed the instance actions panel (status/reconnect/reboot/reset/webhook/QR) — the /api/instance/* endpoints belonged to the legacy platform and do not exist on Whaticket; the connection is managed on the dashboard.chatappbrasil.com panel
* IMPROVEMENT: Abandoned-cart step tracking (Pro) migrated from post meta to dedicated columns (wlp_step/wlp_last_sent) on the CartBounty table, with automatic migration of legacy values
* FIX: Button click counting no longer depends on a nonce — cached pages (WP Rocket etc.) served expired nonces and clicks stopped being counted after ~12h; protection kept via origin check + per-IP rate limit
* FIX: Resending from the orders list now redirects after firing (PRG pattern) — reloading the page no longer re-sends the message; added a confirmation notice in the admin
* FIX: Fixed a latent syntax error in the admin JS (orphan handler left over from the old autoresponders block)

= 3.2.1 =
* RELIABILITY: PIX reminders (Mercado Pago and Asaas) migrated from WP-Cron to Action Scheduler — works even with DISABLE_WP_CRON=true and WP Rocket, because AS has its own runner triggered by AJAX, REST and admin requests
* RELIABILITY: Dual guarantee for PIX reminders — in addition to AS, a queue in wp_options is processed on every WordPress request (frontend, admin, AJAX), ensuring the reminder fires within seconds regardless of cron or traffic
* RELIABILITY: Idempotency lock (transient 2h) prevents double sending when Action Scheduler and the request queue fire the same reminder nearly simultaneously
* RELIABILITY: Internal helpers wlp_schedule_single and wlp_unschedule_all with automatic fallback to WP-Cron when Action Scheduler is unavailable
* FIX: When a PIX order is cancelled by the gateway (PIX expired without payment), the "PIX expired" message is now sent IMMEDIATELY via the woocommerce_order_status_cancelled hook — previously it depended on the AS executing a scheduled action, which could take minutes and leave the customer without any notification
* FIX: New order notification (wlp_wa_order_receive) now correctly passes order_id to the log and order notes
* CLEANUP: Removed dead method wlp_mp_pix_delayed_send that had a registered hook but was never called
* IMPROVEMENT: Every WhatsApp message related to orders (status, PIX, reminder, expired, tracking, new order) now automatically records a private order note in WooCommerce with: source, masked phone, send status, message preview, and ↺ Resend link
* LOGGING: wlp_debug_log records AS or WP-Cron scheduling, warning if AS unavailable, and each request-queue dispatch with delay in seconds
* DIAGNOSTICS: Debug report now shows Action Scheduler status (version, availability, pending plugin actions)

= 3.1.0 =
* NEW: Admin notifications and CF7/Caldera forms now accept multiple phone numbers separated by commas — sends to each recipient individually
* SECURITY: Content-Length header is checked before downloading external images — files over 2 MB are rejected without wasting bandwidth
* SECURITY: IP-based rate limiting added to the button click-tracking endpoint (max 10 clicks/hour per IP) to prevent database flooding
* SECURITY: Deduplication hash upgraded from MD5 to SHA-256
* SECURITY: WhatsApp button URL construction fixed (esc_url_raw instead of esc_attr) and phone number sanitized with regex before URL composition
* RELIABILITY: file_put_contents for PIX QR code now checks its return value and logs an error to the debug log if the write fails
* RELIABILITY: HTTP 4xx/5xx responses from the ChatAppBrasil API are now treated as errors (previously logged as success when the JSON body was valid)
* RELIABILITY: Abandoned cart cron lock now uses an atomic transient (in addition to the conditional UPDATE) to prevent duplicate sends when parallel cron runs overlap
* RELIABILITY: normalize_phone_number validates minimum length (country code + 7 digits) and maximum length (15 digits, E.164) — invalid numbers are discarded before reaching the API
* FLEXIBILITY: API timeout (60s) and image download timeout (30s) are now configurable via the wlp_api_timeout and wlp_download_timeout filters
* CLEANUP: Leftover empty comment blocks from the trait refactoring removed from wlp-main.php

= 3.0.7 =
* FIX: Added the missing callback for the Asaas PIX retry (wlp_asaas_pix_retry). The event was scheduled when the __ASAAS_ORDER meta was not yet saved, but never handled, so the message could never be sent
* FIX: Deduplication no longer blocks reminders, expired PIX and manual resends (sends with the resend parameter), which now always go through
* IMPROVEMENT: Unified the Back In Stock message logging (removed the duplicate manual write); now there is a single record per send, identified as Back in Stock and with the customer name
* IMPROVEMENT: The Debug tab now shows a fixed panel with the supported integrations and their installed versions (Melhor Envio, Mercado Pago, Asaas, Correios, CartBounty, Back In Stock Notifier, WooCommerce, etc), indicating active/not installed, plus the plugin, WordPress and PHP versions

= 3.0.6 =
* FIX: Back In Stock once again triggers WhatsApp when the back-in-stock email is sent. The handler resolved the subscriber product via a wrong meta key (cwginstock_product_id) and aborted; it now uses cwginstock_pid/cwginstock_bypass_pid. It also listens to the automatic and manual send events (cwginstock_auto_email_sent and cwginstock_manual_email_sent)
* IMPROVEMENT: New Back In Stock tab in the log, in the same style as Abandoned carts, listing eligible subscribers (with phone) with statuses "Waiting for stock" (hourglass), "Email sent" and "WhatsApp sent", plus the product current stock status

= 3.0.5 =
* FIX: Expired PIX was not scheduled when send mode was set to "Reminder" (the function returned before scheduling). Expired PIX is now scheduled in any mode, for Mercado Pago and Asaas
* FIX: The expired PIX message never fired because it required the order to be pending/on-hold, but the gateway already cancels the order on expiry. It is now sent unless the customer has paid (processing/completed/refunded)
* IMPROVEMENT: Back In Stock is more robust when reading the subscriber phone (tries multiple meta keys, including subscriber_phone) and the cron no longer excludes subscribers due to the phone meta key name

= 3.0.4 =
* IMPROVEMENT: Message log now supports search by name, phone or order number, plus a filter by message type (order completed, processing, failed, on-hold, PIX generated, tracking, abandoned cart, subscription, Back in Stock, etc). Search and filter respect pagination
* IMPROVEMENT: Each send now records the customer name in the log and abandoned cart messages are properly identified as such (previously logged as "Order")

= 3.0.3 =
* FIX: Melhor Envio detection on the Tracking tab. The plugin checked for melhor-envio-cotacao.php (nonexistent) and the MelhorEnvio\MelhorEnvio class (nonexistent), showing "not installed or active" even when Melhor Envio was active. It now detects via the MELHORENVIO_VERSION constant, the Melhor_Envio_Plugin class and the correct melhor-envio-beta.php file

= 3.0.2 =
* FIX: Abandoned cart reminder is no longer sent when the customer has already purchased. Before sending, the plugin checks via wc_get_orders (HPOS compatible) whether a paid order (processing or completed) already exists for the cart email or phone, and if so marks the cart as done to prevent resending
* UX: Abandoned carts screen now hides carts without a phone number and only shows carts created after plugin activation (wlp_install_date). Count and pagination adjusted to the same filters
* UX: Carts that converted into a sale now show a "(recovered)" badge in the Status WA column and a notice in the detail; the manual send button is hidden in those cases
* FILTER: New wlp_converted_order_statuses hook to customize which order statuses count as "already purchased" (default: processing and completed)

= 3.0.1 =
* ARCHITECTURE: wlp-main.php refactored from 3,300 to 1,942 lines using 7 PHP Traits
* ARCHITECTURE: Added includes/trait-wlp-api.php — HTTP client, send_msg, process_variables, QR image
* ARCHITECTURE: Added includes/trait-wlp-orders.php — process_states, order_receive, resend
* ARCHITECTURE: Added includes/trait-wlp-payments.php — MP PIX, Asaas PIX, Asaas Boleto, expired
* ARCHITECTURE: Added includes/trait-wlp-cart.php — CartBounty, abandoned_order, aftersales
* ARCHITECTURE: Added includes/trait-wlp-subscriptions.php — 7 WCS subscription events
* ARCHITECTURE: Added includes/trait-wlp-tracking.php — Correios and Melhor Envio
* ARCHITECTURE: Added includes/trait-wlp-bis.php — Back In Stock + watchers + cron
* PERFORMANCE: Admin panel CSS extracted to assets/css/wlp-admin.css (browser caching)
* PERFORMANCE: Public AJAX endpoint get_base_url removed; URL injected via wp_localize_script
* PERFORMANCE: Click retention cleanup (90 days) moved from public AJAX to daily cron
* SECURITY: Size limit (max 2 MB) and MIME allowlist (JPEG/PNG/WebP/GIF) enforced before media upload
* FIX: Button position normalization now correctly applies the legacy variant map
* CLEANUP: Dead variable $curl_err removed
* CLEANUP: wlp_autoresponders UI without backend removed from admin JS
* RELIABILITY: BIS watchers (save_post, meta, transition_post_status) registered as formal hooks
* UX: "📚 Tags" tab added to Notifications panel with full variable reference
* UX: Abandoned cart cron frequency configurable in Security settings (1/2/5/10 min)
* DEPRECATED: wlp_bis_legacy_hook(), remove_emoji(), wlp_wa_encoding() marked @deprecated

= 2.9.8 =
* PERFORMANCE: Abandoned cart cron frequency configurable (1, 2, 5 or 10 min), default 5 min
* RELIABILITY: BIS watchers registered as formal hooks (save_post, updated_post_meta, transition_post_status)
* DEPRECATED: wlp_bis_legacy_hook(), remove_emoji(), wlp_wa_encoding() marked @deprecated
* UX: Tags reference tab exposed in Notifications panel

= 2.9.7 =
* SECURITY: Public AJAX endpoint get_base_url removed; URL injected via wp_localize_script
* PERFORMANCE: Click retention DELETE moved from public AJAX to daily cron wlp_button_cleanup_clicks
* SECURITY: File size (max 2 MB) and MIME validation before media send
* CLEANUP: Dead variable $curl_err removed
* FIX: Button position normalization correctly applies $pos_map[$val]
* CLEANUP: wlp_autoresponders UI removed from JS (backend never implemented)

= 2.9.6 =
* SECURITY: MD5 replaced by wp_hash() for Access Token validation
* SECURITY: @unserialize replaced by is_serialized() + allowed_classes false
* SECURITY: Manual cURL replaced by WP_Http for media uploads
* PERFORMANCE: admin_init migration hook guarded (only registers when needed)
* CLEANUP: Lottie Player removed (hook + enqueue_lottie method)
* FIX: Button positions normalized (left-bottom → bottom-left)
* CLEANUP: uninstall.php now removes JSONL logs, migration flags and transients

= 2.9.0 =
* NEW: Asaas PIX integration — QR code + copy-and-paste code, immediate/reminder modes
* NEW: Asaas Boleto integration — boleto link via %boleto_url%
* NEW: PIX expired notification — shared between Mercado Pago and Asaas
* NEW: Payments tab — centralizes Mercado Pago PIX, Asaas PIX, Asaas Boleto, PIX Expired
* NEW: Tracking tab — Correios and Melhor Envio with automatic detection
* NEW: Tags %tracking_code%, %tracking_url% — tracking code and link
* NEW: Tags %order_status%, %order_time%, %payment_url% — new message tags
* FIXED: "Sends paused" notification appearing twice in admin panel
* UPDATED: readme.txt and readme-en_US.txt fully updated for v2.9.0

= 2.8.0 =
* NEW: PIX reminder, private order notes, per-notification toggle
* NEW: Expandable log rows, error monitoring, friendly API errors
* FIXED: Log statistics, CF7 line breaks, Clear Logs button

= 2.7.5 =
* Rate limiting, send hours filter, configurable dedup, Back In Stock, debug mode

= 2.7.0 =
* Full HPOS compatibility, wc_get_order(), WC_Order API

= 2.0.0 =
* SVG floating button with live preview, click tracking

= 1.0.0 =
* Initial release
