Duplicate renewal charges are one of the nastiest bugs in a subscription store. The customer sees it on a bank statement before you see it in a log. This is the order in which I investigate.
1. Count orders and payments separately
The first question is whether WooCommerce created one renewal order or two. One order with two gateway payments means the request was sent twice to the gateway. Two orders means the renewal logic ran twice. Everything after this depends on that answer.
2. Read the timestamps
Seconds apart usually means a double request: a double-click, a preloaded link, or a duplicate webhook. Hours apart usually means a retry or two scheduler runs. A WooCommerce Subscriptions release note describes an early renewal "Pay now" link charging a saved payment method twice after a double-click or browser preload, and a related fix for a renewal paid after logging in at checkout. Check your version against the changelog.
3. Isolate on staging with a test gateway
Clone, deactivate suspects one at a time, trigger a renewal, count the payments. Forum reports have tied some duplicates to a multi-currency or multilingual plugin used with a third-party Stripe gateway plugin, where rolling back or deactivating the plugin stopped it. I treat those as leads to test, not as conclusions.
4. Look at the scheduler
Open Scheduled Actions and search by subscription ID. Two actions for the same date, or a stuck in-progress action that gets retried, points to cron overlap or a timeout. WP-Cron and a system cron both running is a classic cause.
5. Make the charge idempotent
If a renewal can be triggered twice by a job or a webhook, the code needs to check "is this billing period already paid?" before it charges. That is a design property, not a plugin setting.
6. Protect the next run first
While debugging, switch affected subscriptions to manual renewal for one cycle and enable duplicate-payment alerts at the gateway.
Full walkthrough, including refund steps and typical fix cost ranges: https://amanurrahman.com/blog-post/woocommerce-subscriptions-double-charge-fix
What is the strangest cause of a duplicate charge you have found in a payment integration?