{"id":263209,"date":"2025-12-03T15:33:39","date_gmt":"2025-12-03T15:33:39","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/doctor-subs\/"},"modified":"2026-05-18T11:21:14","modified_gmt":"2026-05-18T11:21:14","slug":"doctor-subs","status":"publish","type":"plugin","link":"https:\/\/kin.wordpress.org\/plugins\/doctor-subs\/","author":23189207,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"2.1.1","stable_tag":"2.1.1","tested":"6.9.4","requires":"6.4","requires_php":"7.4","requires_plugins":null,"header_name":"Doctor Subs","header_author":"DavidR","header_description":"An intuitive WooCommerce Subscriptions troubleshooting tool that implements a simple 3-step diagnostic process.","assets_banners_color":"c7cacc","last_updated":"2026-05-18 11:21:14","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/davidrukahu\/doctor-subs","header_author_uri":"https:\/\/github.com\/davidrukahu","rating":0,"author_block_rating":0,"active_installs":0,"downloads":336,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.2.4":{"tag":"1.2.4","author":"davidrukahu","date":"2025-12-03 16:21:42"},"2.1.0":{"tag":"2.1.0","author":"davidrukahu","date":"2026-04-27 16:19:40"},"2.1.1":{"tag":"2.1.1","author":"davidrukahu","date":"2026-05-18 11:21:14"}},"upgrade_notice":{"2.1.1":"<p>Docs-only release. Adds an FAQ comparing Doctor Subs to WooCommerce&#039;s new built-in Subscriptions Health Check tool. No code changes.<\/p>","2.1.0":"<p>Adds three new detection rules (manual-renewal drift, mass on-hold cascade, total drift), bulk-fix for any visible row set, and a styled revert confirm with executed-payment warnings. Schema migrates automatically on first admin page load. No breaking API changes for third-party rules.<\/p>","2.0.0-alpha.1":"<p>Major rewrite. Breaking class rename: WCST_* to DR_Subs_*. class_alias shims keep WCST_Plugin \/ WCST_Admin \/ WCST_Ajax_Handler working. Backup before upgrading if you have custom integrations.<\/p>","1.2.4":"<p>Important security fixes and compatibility improvements. All users should upgrade.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3516753,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3516753,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3516743,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3516753,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3516753,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.2.4","2.1.0","2.1.1"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3516668,"resolution":"1","location":"assets","locale":"","width":2644,"height":2048},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3516668,"resolution":"2","location":"assets","locale":"","width":2692,"height":1604},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3516668,"resolution":"3","location":"assets","locale":"","width":2710,"height":1708},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3516668,"resolution":"4","location":"assets","locale":"","width":1904,"height":2026},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3516668,"resolution":"5","location":"assets","locale":"","width":1584,"height":2426}},"screenshots":{"1":"Dashboard: X-of-Y healthy stat, At risk + Broken counters, search, rule chips, and the Needs attention table with the \"Fix all\" CTA next to the active filter","2":"Fix preview modal: plain-English narrative, named diff (before -&gt; after), and the \"you can undo this\" reassurance line","3":"Fix history: per-rule filter chips along the top, plain-English summary per row, individual Revert buttons","4":"Settings: alerts toggle + email recipient, fix-history retention selector, anonymous telemetry opt-in","5":"Detection rules: six rule cards with on\/off toggles plus Detects + Fix descriptions for every rule"}},"plugin_section":[],"plugin_tags":[23519,251617,4079,8541,286],"plugin_category":[41,45],"plugin_contributors":[250233],"plugin_business_model":[],"class_list":["post-263209","plugin","type-plugin","status-publish","hentry","plugin_tags-diagnostics","plugin_tags-payment-issues","plugin_tags-subscriptions","plugin_tags-troubleshooting","plugin_tags-woocommerce","plugin_category-communication","plugin_category-ecommerce","plugin_contributors-davidrukahu","plugin_committers-davidrukahu"],"banners":{"banner":"https:\/\/ps.w.org\/doctor-subs\/assets\/banner-772x250.png?rev=3516753","banner_2x":"https:\/\/ps.w.org\/doctor-subs\/assets\/banner-1544x500.png?rev=3516753","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/doctor-subs\/assets\/icon.svg?rev=3516743","icon":"https:\/\/ps.w.org\/doctor-subs\/assets\/icon.svg?rev=3516743","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/doctor-subs\/assets\/screenshot-1.png?rev=3516668","caption":"Dashboard: X-of-Y healthy stat, At risk + Broken counters, search, rule chips, and the Needs attention table with the \"Fix all\" CTA next to the active filter"},{"src":"https:\/\/ps.w.org\/doctor-subs\/assets\/screenshot-2.png?rev=3516668","caption":"Fix preview modal: plain-English narrative, named diff (before -&gt; after), and the \"you can undo this\" reassurance line"},{"src":"https:\/\/ps.w.org\/doctor-subs\/assets\/screenshot-3.png?rev=3516668","caption":"Fix history: per-rule filter chips along the top, plain-English summary per row, individual Revert buttons"},{"src":"https:\/\/ps.w.org\/doctor-subs\/assets\/screenshot-4.png?rev=3516668","caption":"Settings: alerts toggle + email recipient, fix-history retention selector, anonymous telemetry opt-in"},{"src":"https:\/\/ps.w.org\/doctor-subs\/assets\/screenshot-5.png?rev=3516668","caption":"Detection rules: six rule cards with on\/off toggles plus Detects + Fix descriptions for every rule"}],"raw_content":"<!--section=description-->\n<p>You don't always find out a renewal is broken until a customer emails you. Doctor Subs watches your subscriptions in the background and tells you when something is silently wrong, before you hear about it.<\/p>\n\n<p>For each problem, you get a plain-English explanation, a preview of exactly what the fix will change, and one button to apply it. Every fix is logged and can be undone. If a fix already triggered a real payment, the undo screen says so explicitly.<\/p>\n\n<p>Built for store owners who run between 20 and 500 active subscriptions and don't want to read logs or write code.<\/p>\n\n<h4>What it spots<\/h4>\n\n<ul>\n<li>Subscriptions silently flipped to \"manual renewal\" by recent WooCommerce bugs, so auto-billing has quietly stopped.<\/li>\n<li>Active subscriptions with no next payment scheduled - the next charge will never happen.<\/li>\n<li>A wave of subscriptions all going on hold at once after a product change.<\/li>\n<li>Customers who paid in Stripe but whose subscription is still showing on hold.<\/li>\n<li>Subscriptions with a couple of recent failed payments - usually a gateway blip a single retry fixes.<\/li>\n<li>Subscription totals that no longer match their line items.<\/li>\n<\/ul>\n\n<h4>What it doesn't do<\/h4>\n\n<ul>\n<li>It doesn't change anything without you clicking. There's no auto-fix.<\/li>\n<li>It doesn't replace dunning, retries, or refunds. For card declines and refunds, use your gateway.<\/li>\n<li>It doesn't send your customer data anywhere.<\/li>\n<\/ul>\n\n<p>If you've ever found out about a broken renewal from a customer instead of from your store, this is for you.<\/p>\n\n<!--section=installation-->\n<h4>Automatic<\/h4>\n\n<ol>\n<li>Plugins &gt; Add New &gt; search \"Doctor Subs\"<\/li>\n<li>Install &gt; Activate<\/li>\n<\/ol>\n\n<h4>Manual<\/h4>\n\n<ol>\n<li>Download the plugin zip from WordPress.org or GitHub Releases<\/li>\n<li>Plugins &gt; Add New &gt; Upload Plugin<\/li>\n<li>Install &gt; Activate<\/li>\n<\/ol>\n\n<h4>After activation<\/h4>\n\n<ol>\n<li>WooCommerce &gt; Doctor Subs<\/li>\n<li>Click <strong>Scan my subscriptions<\/strong><\/li>\n<li>Review the X-of-Y healthy stat and the two action counters, click into the broken bucket, preview a fix, commit<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20doctor%20subs%20fix%20issues%20automatically%3F\"><h3>Does Doctor Subs fix issues automatically?<\/h3><\/dt>\n<dd><p>No. The plugin never mutates your data without an explicit Fix click. Every change shows a preview first and lands in the Fix history with a Revert button. The scanner only detects; the merchant decides.<\/p><\/dd>\n<dt id=\"what%20happens%20if%20i%20revert%20a%20fix%20after%20the%20payment%20already%20ran%3F\"><h3>What happens if I revert a fix after the payment already ran?<\/h3><\/dt>\n<dd><p>The revert confirm explicitly tells you: the re-scheduled payment has already charged the customer, reverting will undo the status change but will NOT refund. If a refund is needed, handle it in the related WooCommerce order.<\/p><\/dd>\n<dt id=\"does%20it%20really%20detect%20the%20silent%20%22manual%20renewal%22%20bug%3F\"><h3>Does it really detect the silent \"manual renewal\" bug?<\/h3><\/dt>\n<dd><p>Yes. The Manual-renewal drift rule looks for active subs whose <code>_requires_manual_renewal<\/code> flag is set despite a working Stripe customer\/source meta on file. It clears the flag in both the orders table and postmeta (belt-and-braces against the HPOS sync gap), re-stamps <code>next_payment<\/code> if past-due, and schedules a fresh renewal so WCS bills automatically again.<\/p><\/dd>\n<dt id=\"is%20this%20a%20replacement%20for%20dunning%20management%3F\"><h3>Is this a replacement for dunning management?<\/h3><\/dt>\n<dd><p>No. Doctor Subs fixes structural issues (missing AS events, stuck-on-hold statuses, manual-renewal flag drift, line-item total drift). It does not handle card declines, SCA prompts, or retry strategy. For those, use your gateway's built-in dunning or a dedicated plugin.<\/p><\/dd>\n<dt id=\"which%20gateways%20does%20it%20support%3F\"><h3>Which gateways does it support?<\/h3><\/dt>\n<dd><p>v2.1: Stripe fully supported across all rules that need a gateway signal (stuck on-hold, manual-renewal drift). The remaining rules are gateway-agnostic. PayPal, Authorize.net, Square, and WooPayments variants land in a future release.<\/p><\/dd>\n<dt id=\"will%20it%20work%20on%20stores%20with%2010%2C000%2B%20subscriptions%3F\"><h3>Will it work on stores with 10,000+ subscriptions?<\/h3><\/dt>\n<dd><p>Yes. The scanner uses a shared pre-built index so every rule is O(1) per sub (no N+1 queries). The <code>DR_SUBS_SCAN_BATCH_SIZE<\/code> constant lets you tune the batch size in wp-config.php for very large stores.<\/p><\/dd>\n<dt id=\"can%20i%20extend%20it%20with%20my%20own%20rules%3F\"><h3>Can I extend it with my own rules?<\/h3><\/dt>\n<dd><p>Yes. Implement <code>DR_Subs_Rule_Interface<\/code> and register on the <code>dr_subs_register_rules<\/code> action. See the six built-in rules under <code>includes\/rules\/<\/code> for examples.<\/p><\/dd>\n<dt id=\"is%20hpos%20supported%3F\"><h3>Is HPOS supported?<\/h3><\/dt>\n<dd><p>Required. Doctor Subs declares High-Performance Order Storage compatibility and requires WooCommerce 9.0 or higher.<\/p><\/dd>\n<dt id=\"how%20is%20this%20different%20from%20woocommerce%27s%20built-in%20subscriptions%20health%20check%20tool%3F\"><h3>How is this different from WooCommerce's built-in Subscriptions Health Check tool?<\/h3><\/dt>\n<dd><p>WooCommerce ships a <a href=\"https:\/\/woocommerce.com\/document\/woocommerce-subscriptions-health-check\/\">Subscriptions Health Check tool<\/a> that flags two conditions: subs on manual renewal that have a valid saved payment token, and subs with a missing or overdue next-payment date. It surfaces them as a list and points you at the relevant order so you can act manually.<\/p>\n\n<p>Doctor Subs overlaps on those two patterns (Manual-renewal drift, Ghost subscription) and adds four more the built-in tool doesn't cover: mass on-hold cascade after a product edit, stuck on-hold despite a captured Stripe renewal, repeated payment failures within 30 days, and total drift between the stored total and line items.<\/p>\n\n<p>It also wraps every detection in a preview-before-apply modal, one-click fixes, a per-entry revert journal, bulk-fix across N matches, state-guarded apply (aborts if the sub changed between detection and apply), and an optional email digest when something new breaks between scans.<\/p>\n\n<p>Short version: WC's tool is a flagger. Doctor Subs is a flagger plus a reversible repair surface. Running both is fine - they don't conflict.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>2.1.1<\/h4>\n\n<p>Docs only. New FAQ entry comparing Doctor Subs to WooCommerce's built-in Subscriptions Health Check tool, with link to the official WooCommerce documentation. No code changes.<\/p>\n\n<h4>2.1.0<\/h4>\n\n<p>Major detection + UX expansion. Six rules now ship; design language tightened.<\/p>\n\n<p><strong>New rules (3):<\/strong><\/p>\n\n<ul>\n<li><strong>Manual-renewal drift<\/strong> (Stripe-only): detects active subs silently flipped to manual renewal despite a Stripe customer\/source on file. Direct response to the four April 2026 subscriptions-core bug disclosures. Fix clears the flag in HPOS + postmeta, re-stamps next_payment if past-due, and schedules a fresh renewal.<\/li>\n<li><strong>Mass on-hold cascade<\/strong>: detects 20 or more on-hold transitions for the same product within a 1-hour window. Backed by a new <code>dr_subs_status_transitions<\/code> log written by an observer on every subscription status change. Fix reactivates each cascade member; bulk-fix recovers the whole cascade in one click.<\/li>\n<li><strong>Total drift<\/strong> (flag-only): detects subs whose stored total no longer matches the sum of line items + tax + shipping + fees by more than $0.50, ignoring subs modified in the last 7 days. Surfaces the discrepancy and links to the sub for manual review.<\/li>\n<\/ul>\n\n<p><strong>Dashboard:<\/strong><\/p>\n\n<ul>\n<li>Healthy counter relocated as an \"X of Y healthy\" stat above the action counters; can no longer be mistaken for a clickable filter.<\/li>\n<li>Search bar matches sub number, customer name, and billing email. Debounced, ESC clears.<\/li>\n<li>Rule chip filters above the table; bucket counter and rule chip are now mutually exclusive (clicking one clears the other).<\/li>\n<li>Reason column strips inline HTML so emphasis tags never render literally.<\/li>\n<li>Issue column header moved to screen-reader-only; the rule pill carries the label.<\/li>\n<\/ul>\n\n<p><strong>Bulk + revert:<\/strong><\/p>\n\n<ul>\n<li>Bulk-fix button beside the active rule chip OR in \"All rules\" mode: groups visible rows by rule, posts one batch per rule, surfaces a styled confirm modal listing the per-rule counts and a renewal-payment warning. Total Drift opts out (manual-only).<\/li>\n<li>Revert confirm now opens a styled in-plugin modal instead of <code>window.confirm()<\/code>. When the journal entry's AS action has already executed, the modal escalates to a danger-styled button and the explicit \"this will NOT refund\" warning.<\/li>\n<\/ul>\n\n<p><strong>Settings:<\/strong><\/p>\n\n<ul>\n<li>New \"Detection rules\" section listing all six rules with toggle, plain-English Detects\/Fix descriptions, and bucket tag. Disabled rules skip detection on every scan.<\/li>\n<li>Plain-English summaries on dashboard rule chips and table pills via <code>DR_Subs_Rule_Catalog<\/code>.<\/li>\n<\/ul>\n\n<p><strong>Fix history:<\/strong><\/p>\n\n<ul>\n<li>Plain-English summary per row (\"Rescheduled the missed renewal payment\", \"Reactivated as part of a mass-hold cascade recovery\", etc.) instead of the raw <code>key: value<\/code> after-state dump.<\/li>\n<li>All canonical rule ids now render correct labels (legacy short ids fall back).<\/li>\n<\/ul>\n\n<p><strong>Schema + scanner:<\/strong><\/p>\n\n<ul>\n<li>Schema bumped to 2.1.0; new <code>dr_subs_status_transitions<\/code> table with <code>sub_id<\/code>, <code>from_status<\/code>, <code>to_status<\/code>, <code>product_id<\/code>, <code>variation_id<\/code>, <code>transitioned_at<\/code>. Pruned daily on a 30-day TTL.<\/li>\n<li>Scanner now walks <code>active<\/code>, <code>on-hold<\/code>, and <code>pending-cancel<\/code> statuses (was <code>active<\/code> only). Mass-hold + Stuck-on-hold rules now reach their target subs.<\/li>\n<li>Manual_renewal_drift registered before Ghost_sub so it claims primary-rule on the same broken state with the right fix. Ghost_sub now skips manual-renewal subs entirely.<\/li>\n<\/ul>\n\n<p><strong>Design + accessibility:<\/strong><\/p>\n\n<ul>\n<li>Display typeface swapped from Instrument Serif to Source Serif 4 (less editorial-romantic, calmer italic).<\/li>\n<li>Counter numerals dropped from 62px display serif to 32px Switzer 500: a 28-broken count no longer reads like a panic-amplifier.<\/li>\n<li>Dashboard hint copy de-imperative-d (\"needs you now\" -&gt; \"since last scan\").<\/li>\n<li>All em dashes \/ en dashes \/ minus signs replaced with plain hyphens across PHP, JS, CSS, and views.<\/li>\n<li>Modal focus on open lands on the dialog itself (tabindex=-1) so the sub-id link no longer reads as \"selected\".<\/li>\n<li>New <code>.btn-danger<\/code> token using the existing terracotta <code>--broken<\/code> for genuinely destructive actions.<\/li>\n<\/ul>\n\n<p><strong>Internal:<\/strong><\/p>\n\n<ul>\n<li>New <code>DR_Subs_Rule_Catalog<\/code> central source-of-truth for per-rule label, summary, detect, fix, bucket, and journal_summary copy.<\/li>\n<li>New <code>dev\/<\/code> directory with <code>seed-test-data.php<\/code> and <code>wipe-test-data.php<\/code> (excluded from the production zip via build script + CI workflow).<\/li>\n<\/ul>\n\n<h4>2.0.0-alpha.1<\/h4>\n\n<p>Major rewrite. Single breaking change moment: every PHP class renamed from WCST_* to DR_Subs_*; legacy class_alias shims ship for the three most-likely-extended public classes.<\/p>\n\n<ul>\n<li>Traffic-light dashboard with per-bucket drill-down<\/li>\n<li>Three deterministic detection rules: Ghost Sub, On-Hold with Paid Renewal (Stripe), Repeated Payment Failures<\/li>\n<li>Daily background scanner via Action Scheduler + WP-Cron watchdog<\/li>\n<li>One-click fixes with state-guarded apply and reversible Fix journal<\/li>\n<li>Fix preview modal with named diff and executed-payment warning<\/li>\n<li>Email digest alerts (off by default; configurable recipient)<\/li>\n<li>Settings page with retention controls and anonymous telemetry opt-in<\/li>\n<li>20 language translations via Potomatic<\/li>\n<li>Self-hosted typography, no external asset fetches<\/li>\n<li>HPOS baseline (WC 9.0+)<\/li>\n<\/ul>\n\n<h4>1.2.4<\/h4>\n\n<ul>\n<li>Fixed all security issues (sanitization, validation, escaping)<\/li>\n<li>Fixed Action Scheduler compatibility (scheduled_date_gmt column)<\/li>\n<li>Improved error handling and debugging<\/li>\n<li>Enhanced analyzer stability<\/li>\n<li>Added PHPCS configuration<\/li>\n<\/ul>\n\n<h4>1.2.3<\/h4>\n\n<ul>\n<li>Initial release<\/li>\n<\/ul>","raw_excerpt":"Spot the broken WooCommerce subscriptions you didn&#039;t know about, and fix them with one click you can undo.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/263209","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=263209"}],"author":[{"embeddable":true,"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/davidrukahu"}],"wp:attachment":[{"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=263209"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=263209"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=263209"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=263209"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=263209"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/kin.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=263209"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}