TrustLens – Fraud Prevention & Chargeback Defense for WooCommerce

Changelog

1.2.6

  • New: Quick Start setup card on the dashboard — three guided choices (block risky customers, email alerts, import past orders) get a new store protected without hunting through settings.
  • New: Block reason picker — when you block a customer you can record why, from a preset or your own note; it’s saved to the customer’s notes and history so you keep an audit trail.
  • New: Plain-language score summary on each customer — see at a glance what’s weighing a score down and what’s in their favour, instead of decoding raw signal bars.
  • New: Segment glossary and inline help — a built-in legend for VIP through Critical with their score bands, plus ? tips that explain terms like “fingerprint” right where you need them.
  • New: Recommended next actions on the dashboard and Card-Testing Defense — each screen now tells you what to do, not just what’s happening.
  • Improvement: Customer actions no longer reload the whole page — block and unblock update in place with a confirmation toast, and allowlist or recalculate keep your scroll position so you never lose your place.
  • Improvement: Panic Freeze remembers your chosen duration instead of resetting to 15 minutes each time, and the misleading “1 hour” option (which the server capped at 30) has been removed.
  • Improvement: Card-Testing Defense is now discoverable as the 8th detection module from the Modules tab in Settings.
  • Improvement: Signal explanations on the customer page are now keyboard-accessible, not hover-only.
  • Fix: The Automation tab now shows consistently in the in-app header, matching the WordPress admin menu.

1.2.5

Dashboard visual refresh — every chart now reads as one product.

Changed

  • All dashboard charts repainted in the TrustLens emerald palette to match the website and the rest of the admin — Trust Score Trends, Segment Distribution, Refund Activity, Hourly Activity, Category Returns, Monthly Protection, the Chargeback Monitor 12-month trend, and the Card-Testing Defense Attack History timeline. Replaces the previous mix of Bootstrap red/orange/green and a stray purple that had drifted in over earlier releases.
  • Chart tooltips switched from black bubbles to light cards with subtle slate borders and softer typography so they no longer overpower the data.
  • Line charts now use thinner 2px strokes with smoother curves and gradient fills that fade fully to transparent; bar charts have rounded 6px caps.
  • Snappier 600ms entry animation (was the Chart.js 1s default).
  • Hourly Activity chart now uses an emerald intensity gradient (low → slate, high → emerald) instead of a purple-to-violet ramp, so quiet hours read as quiet and busy hours pop.
  • New shared chart-theme.js module — every chart in the plugin pulls from one palette, so future charts pick up the same identity automatically.

Fixed

  • Segment doughnut’s “X customers” total now sits on the actual ring center. Previously, the bottom-positioned legend pushed the doughnut up but the label kept dividing the full canvas height in half, landing the text below the ring.
  • Segment-doughnut legend markers (VIP / Trusted / Normal / Caution / Risk / Critical) now render as perfect circles instead of slightly-oval boxes.
  • Empty charts now show a quiet “No data yet” message instead of a blank canvas, so a fresh install doesn’t look broken before any orders have been scored.

1.2.4

Card-Testing Defense — false-positive fix + WordPress 7.0 compatibility.

Fixed

  • Card-Testing Defense self-blocking legitimate shoppers on Blocks checkout. The velocity counter was incrementing on every Store API cart write — add-item, update-item, apply-coupon, select-shipping-rate, update-customer — not only on actual payment submissions. A normal shopper rapidly adjusting quantities or applying a coupon in a Blocks cart could cross the 10-submissions-per-60s threshold and target their own fingerprint for a 90-second lockdown, locking themselves out of checkout. Root cause: the gate context had no way to distinguish cart-write intent from checkout-submit intent, so the velocity recorder treated everything as a payment attempt. The gate now carries an explicit intent field populated from the route; the velocity recorder only counts true checkout submissions, and the card-testing module short-circuits on cart writes (which also removes 2 unnecessary COUNT(*) queries from every cart click). The email blocklist continues to fire on cart writes per the 1.3.0 design — known-bad customers still cannot add items to cart. No configuration change required; existing thresholds keep working but are now fed only real payment attempts.

Compatibility

  • Tested up to WordPress 7.0 (“Armstrong”). Full audit completed: PHPMailer 7.0.2 update, Backbone 1.6.1, CodeMirror v5 / Espree, Interactivity API state.navigation deprecation, Block API version, Block Hooks REST move, REST permission_callback coverage, early-init translation, and PHP-8 deprecation surfaces. No plugin changes were needed for 7.0.

1.2.3

Security and reliability hardening. Closes several issues surfaced during a pre-release audit.

Fixed

  • Card-Testing Defense — VIP bypass too permissive. Previously, any customer with at least one completed order was permanently exempt from card-testing velocity blocks — meaning a fraud actor who completed a single order gained immunity from then on. The threshold now matches the plugin-wide trustlens_min_orders setting (default 3 orders) AND customers in risk or critical segments no longer bypass card-testing defense regardless of order count.
  • Chargeback meta not HPOS-compatible. Manual chargeback writes used update_post_meta() / get_post_meta() directly, which silently target the wrong table on stores with WooCommerce High-Performance Order Storage enabled. Switched to WC_Order::update_meta_data() / WC_Order::get_meta() so the chargeback indicator and Record Manual Chargeback form work correctly on HPOS stores.
  • IP spoofing via forwarding headers. HTTP_X_FORWARDED_FOR (request gate) and HTTP_CLIENT_IP + HTTP_X_FORWARDED_FOR (payment-method controls) were trusted unconditionally, letting an attacker send X-Forwarded-For: 1.2.3.4 to rotate their apparent IP and defeat per-IP velocity rules. Both code paths now default to REMOTE_ADDR. Sites legitimately behind a trusted reverse proxy (Cloudflare, load balancer, Sucuri) can opt in to X-Forwarded-For via the new trustlens/trust_proxy_headers filter — the last entry in the header is used (the IP the closest trusted hop observed).
  • Webhook signing secret exposed in DOM. The “Test” button on the Pro Webhooks settings page rendered the signing secret as a data-secret HTML attribute, making it readable by any browser extension or XSS payload running in the admin panel. The secret is no longer rendered to the page; the AJAX handler now looks up the endpoint server-side from the stored config.
  • Webhook async dispatch could pile up duplicates. Automation rule webhooks used as_enqueue_async_action() without dedup, so a rapid burst of identical triggers (e.g. score_updated firing several times during a batch refund) queued multiple deliveries for the same rule+customer. Now dedup’d via wstl_ensure_single_action; retries from inside the dispatch handler still carry a distinct retry counter and bypass dedup so failed deliveries still get their 60s / 120s / 240s attempts.
  • Score-update queue race. wstl_queue_score_update() used a read-then-write pattern that could double-schedule the score recalculation under concurrent events for the same customer. Replaced with wstl_ensure_single_action, which uses unschedule-then-schedule semantics and is race-free.
  • Chargeback record double-increment under concurrency. The manual chargeback AJAX path ran two separate UPDATE statements (one for total_disputes, one for the outcome counter). Two concurrent calls for the same customer could leave disputes_lost exceeding total_disputes. Now done as a single atomic UPDATE when the outcome is known at record time.
  • Shipping anomaly re-entrancy. The trustlens/shipping_anomaly action fired (and address_anomaly_detected was logged) from inside get_signal(), which runs on every score recalculation. That could spawn re-entrant Action Scheduler jobs through automation rules. Both events now fire once from handle_order_completed, so each detection produces exactly one event per order completion.
  • Guest-order automation actions silently dropped. Customer-level actions (send email, block customer, tag, etc.) on order-bound rules silently returned when the order was a guest checkout. Now a ‘skipped’ row is written to the rule log with the reason “Guest order: no customer email hash” — the inspector can finally answer “why didn’t my rule fire?”.
  • Lockdown targets transient race. Card-Testing Defense stored every targeted device fingerprint in one shared transient map; concurrent attacks from different devices could clobber each other on write. Switched to one transient per fingerprint so concurrent target writes never conflict. Admin listing and “any-target-active” check use indexed wp_options LIKE queries.
  • Automation is_first_order matched 0-order customers. Condition now requires total_orders === 1 (exact), so rules don’t fire against brand-new customer records that exist before any order has been counted.
  • Chargeback signal ignored min-orders threshold. A one-time buyer who filed a legitimate dispute could trigger the -30 chargeback penalty before any other signals existed. The chargeback module now honors trustlens_min_orders like the returns, coupons, and shipping modules.
  • Dispute Report didn’t validate the hash format. $_GET[‘hash’] is now checked against wstl_is_email_hash() before being passed to the lookup, matching the rest of the codebase.
  • Webhook log table escaping. Endpoint URL inside <code> was escaped with esc_url() (an attribute-context function) instead of esc_html(). Switched to the correct function for text content.
  • Automation retention cron not cleared on deactivation. The trustlens/automation/retention_cleanup event survived Deactivate, leaving an orphan WP-Cron entry. Now cleared alongside the other scheduled events.
  • Duplicate score-save logic. process_score_calculation() and TrustLens_Score_Calculator::recalculate_score() each contained their own copy of the save-and-fire(score_updated / segment_changed) flow. The Action Scheduler callback now delegates to the calculator method so the two paths can’t drift.
  • Redundant order re-fetches. class-module-orders and class-module-shipping-anomalies registered woocommerce_order_status_completed with 1 arg and immediately called wc_get_order() on the order ID. Hooks now register with 2 args and use the WC_Order instance WooCommerce passes — with a defensive fallback for third-party callers firing the hook with one argument.
  • Card-testing defense bypass via client fingerprint rotation. Bots that rotated their JavaScript-side fingerprint per request avoided the per-fingerprint velocity threshold (each rotated hash had count 1, never tripping the limit). Declines are now also recorded under the server-fallback fingerprint (IP + User-Agent + Accept-Language) — which stays stable across client-hash rotation — so the velocity detector accumulates and targets even rotating attackers. Lockdown checks test both hashes on every request, so an attacker who got the server hash targeted on attempt 3 stays blocked even if they rotate the client hash on attempt 4.
  • Panic Freeze duration ceiling. The duration the panic-freeze AJAX accepted from the admin form was clamped to 3600s (1 hour). An admin mis-entering the value could accidentally block checkout for an hour. The server-side ceiling is now 30 minutes by default, filterable via trustlens/card_testing/panic_max_duration for sites that genuinely need longer.
  • Cron reconciliation on every page load. ensure_notification_schedules() was hooked to init and ran on every frontend request, writing to wp_options on stores with notifications disabled. The reconciliation now self-throttles to once per hour, with explicit invalidation on notification-setting changes so toggles still take effect immediately.
  • Automation customer_age_days / days_since_last_order timezone drift. Conditions mixed local-time (current_time(‘timestamp’)) with UTC-stored MySQL timestamps, producing up to ±14 hours of drift on non-UTC sites — enough to push a daily-granularity condition off by a full day. Both sides now anchor to UTC.
  • Webhook endpoints option marked autoload=no. The endpoint config (which contains plaintext HMAC signing secrets) was autoloaded on every request. It’s now loaded only when a webhook actually needs to fire.
  • Card-Testing Defense not actually enabled by default. The readme advertised “ships enabled with sensible thresholds” but the activation flow never set the trustlens_module_card_testing_enabled option, so the module sat dormant until merchants found the toggle in Settings. New installs now enable card-testing defense and the VIP customer bypass automatically, matching the documented promise. Existing sites keep whatever value they already have — no surprise behavior changes.
  • Welcome email never sent on default-off installs. The 24-hour-post-activation welcome summary was gated behind the master trustlens_enable_notifications switch, which ships disabled. The handler silently returned and the carefully-built welcome email was dropped on every fresh install. The welcome email is now gated only by its own trustlens_notify_welcome_summary opt-out (which already defaults to on), so the onboarding email actually fires.
  • Plugin row “Dashboard” / “Settings” shortcuts. The Plugins screen now surfaces direct links to the TrustLens dashboard and Settings page in the plugin row, matching standard WordPress plugin UX.
  • Dashboard onboarding card now signals active protection. When a fresh install lands on the empty dashboard, a small pill next to the onboarding steps confirms that the detection modules are already scoring incoming orders, so merchants know protection is live and not deferred until they finish setup.

Changed (potentially breaking for existing webhook receivers)

  • Webhook signature scheme v2. Outgoing webhook signatures now cover timestamp + ‘.’ + body instead of body alone, and a new X-TrustLens-Timestamp header carries the Unix epoch. This lets receivers reject replayed deliveries by checking the timestamp falls within a short window (recommended: ±5 minutes). Verification on the receiver side: compute ‘sha256=’ + hmac_sha256(timestamp + ‘.’ + body, secret) and constant-time-compare against X-TrustLens-Signature. If you have an existing webhook receiver, update its verification code before upgrading.

Internal

  • Centralized client-IP retrieval in wstl_get_client_ip() so future fraud modules don’t have to re-solve the spoofable-header problem.
  • Centralized webhook signature computation in TrustLens_Webhooks::compute_signature() so the three send sites (settings test, classic webhooks, automation webhooks) can’t drift apart.
  • Defensive: replaced ActionScheduler_Store::STATUS_PENDING constant reference with the literal ‘pending’ in wstl_queue_score_update() so the function survives unusual AS bootstrap orderings.

1.2.2

Automation Rules — reliability rewrite + major capability expansion. Plus Card-Testing Defense admin UX consolidation.

Automation

  • Added triggers: Chargeback Filed · Dispute Recorded · Linked Accounts Detected · Card Testing Attack · Shipping Anomaly.
  • Added condition fields: Total Order Value · Total Disputes · Linked Accounts · Coupon Then Refund · Cancelled Orders · Customer Type · Is Blocked · Customer Age · Days Since Last Order · Payment Method · Shipping Country · Billing Country · Country Mismatch · Coupon Total.
  • Added actions: Allowlist Customer · Cancel Order.
  • Added: Save-time validator blocks rules that can never fire — unsatisfiable conditions, schema-bound violations, trigger-state contradictions, invalid operators for the field type, incomplete actions — each with a specific inline reason.
  • Added: Inspector shows SKIP status on evaluations that didn’t execute, with the reason (“Cooldown active” / “Condition not met: trust_score > 50”). Directly answers “why didn’t my rule fire?”.
  • Changed: Webhooks now dispatch async with automatic retry (60s/120s/240s backoff) and are HMAC-SHA256 signed by default.
  • Changed: Rule editor no longer full-page-reloads on save or delete; errors appear inline inside the modal.
  • Fixed: Concurrent rule saves were last-write-wins — now serialized via advisory lock.
  • Fixed: A failed action locked the rule out for an hour via cooldown — now clears on error so the next event retries.
  • Fixed: Rules with an unknown condition field silently matched everything (catastrophic for block_customer rules). Now rejected.
  • Fixed: Timezone drift between log timestamps and inspector counters when MySQL server TZ ≠ site TZ.
  • Fixed: Operators <, <=, <> couldn’t save at all.
  • Fixed: “Send Email” action ignored the recipient field; now honors it as a per-rule override (falls back to site notification email when blank).
  • Fixed: “Refund Processed” trigger silently dropped order context — order-only actions/conditions never fired on refunds.

Admin UX — Card-Testing Defense + Dashboard

  • Changed: Card-Testing Defense page consolidated from four tabs into a single live view — panic controls, live state, and targeted fingerprints visible without clicking.
  • Added: Dashboard alert band for active Panic Freeze, targeted lockdowns, and card-network programs over chargeback threshold.
  • Added: Module-status pill row on the dashboard (on/off + one stat for each subsystem).
  • Added: Persistent plugin-wide admin header with unified nav, live status pill, notifications bell, and ⌘K command palette.
  • Fixed: Unchecking “Enable Card-Testing Defense” or “VIP bypass” didn’t save (Settings API checkbox quirk).
  • Fixed: Slack webhook delivery failures are now logged instead of swallowed.
  • Fixed: Uninstall clears card-testing options and cron hooks; deactivation unschedules card-testing crons.
  • Fixed: Card-testing attacks with an identifiable customer email now fire trustlens/checkout_blocked (once per newly-targeted fingerprint) so Notifications / Automation / Webhooks can react.

Safe additive upgrade — new composite index added idempotently, no data migration.

1.5.0

Card-Testing Defense — Pro tier

  • Added (Pro): Auto-escalation from targeted blocking to global panic freeze when an attack spreads across multiple device fingerprints. Default threshold: 3 distinct devices in a 10-minute window.
  • Added (Pro): Geographic-diversity safeguard. Before auto-escalating, checks whether the decline burst is naturally distributed across ≥10 countries with no single country holding >50% — if so, treats as a legitimate flash-sale or viral-moment burst and holds off.
  • Added (Pro): Fingerprint and IP CIDR allowlists. Devices or IP ranges on the allowlist bypass the card-testing defense entirely — for QA, integration partners, or known-good traffic. Both IPv4 and IPv6 CIDR ranges supported.
  • Added (Pro): Advanced fingerprint signal — enumerates 12 common fonts via baseline-width comparison and adds the detected-fonts list to the fingerprint hash. Harder for botnets to spoof consistently across nodes than canvas + screen alone. Opt-in via script tag data attribute (only injected when Pro is licensed AND card-testing is enabled).
  • Added (Pro): Per-fingerprint threshold overrides. Tighter or looser thresholds for specific known devices.
  • Added (Pro): Attack History tab — 24h decline count, decline-code breakdown, top-10 attacking fingerprints, hourly timeline chart (Chart.js). CSV export of all velocity events in the window.
  • Added (Pro): Slack and email alert dispatcher — subscribes to attack_detected, auto_escalated, and panic_button_activated events. Configure a Slack webhook and/or email address to receive attack notifications.
  • Added (Pro): Documented stable contract on the trustlens/panic_button_activated action — Pro integrators can rely on the signature and timing.
  • Free tier behavior unchanged.

1.4.0

Card-Testing Defense (Free) — blocks stolen-card attack traffic before it reaches the payment gateway

  • Added (Free): Real-time card-testing detection. Watches per-device decline rates in 60-second and 10-minute rolling windows. A device that crosses the decline threshold is blocked from checkout for 90 seconds. No merchant configuration required — sensible defaults ship enabled.
  • Added (Free): Panic Freeze button on the new TrustLens → Card-Testing Defense admin page. One click blocks ALL checkouts for 15 minutes (configurable 5m/30m/1h). Use during active attacks your thresholds haven’t caught.
  • Added (Free): VIP Customer Bypass (enabled by default). Customers with at least one successful past order are never blocked by card-testing velocity — attacks can’t disrupt legitimate repeat buyers.
  • Added (Free): Negative trust-score signal for customers linked to device fingerprints involved in past attacks — keeps bad actors scored correctly even after the 90-second targeted block expires.
  • Added (Free): during_attack_window event logged on orders completed while an attack is active — audit trail of which successful orders slipped through.
  • Added (Free): Dashboard widget shows current defense state (IDLE / TARGETED / PANIC) and 24-hour decline count at a glance.
  • Added (Free): Daily retention cron keeps the velocity-events table trimmed to the configured window (default 48h, configurable 24–168h).
  • Note on velocity systems: This feature’s “velocity” is keyed on device fingerprint and measures gateway declines — unrelated to the existing Payment-Method Controls velocity (email-keyed, completed-order-count-based) and Shipping Anomalies velocity (email-keyed, distinct-address-count-based). Three independent systems, three different threats, three different responses.
  • Coming in 1.5 (Pro): Auto-escalation to global freeze, geographic-diversity flash-sale safeguard, fingerprint allowlists, attack-history analytics, Slack / email alerts.

For the complete changelog of earlier versions, visit the full changelog.

Plugin Website
Visit website

Author
webstepper
Version:
1.2.6
Last Updated
June 17, 2026
Requires
WordPress 6.4
Tested Up To
WordPress 7.0
Requires PHP
7.4

Share Post

Join our newsletter.

Get insights into what’s happening at ChangelogWP right in your inbox. We don’t believe in spam.