Solved. During our migration from Drupal to Wordpress, both with Civi databases, there were a few days when the Drupal server was still live and 5 recurring donations were added. On the new Wordpress site, the first 5 new recurring contributions used the same primary key in the civicrm_contribution_recur table. This didn't cause the new recurring transaction to fail on the AuthorizeNet side. But we now had two sets of 5 users sharing a recur_id in the AuthorizeNet account.
I copied the 5 Civi records (from both contribution and contribution_recur tables) from Civi on the Drupal side to the Wordpress Civi database starting at the next available primary key value. So I effectively left 5 new Civi users overloading the original recur_id of the five old contributions that were new in new positions in the table. I failed to realize that this did not consider how the AuthorizeNet future transactions were going to notify Civi when the next recurring transaction occurs. So when AuthorizeNet sent a notification back to Civi for the new Wordpress user donation, it somehow mapped the transaction info sent through the API to an older contribution from a different user and it wasn't even recurring.
But Civi happily created a new record in civicrm_contribution for that legacy user's name and sent a receipt to his email which indicated that he was charged again for a recurring transaction that he never created. It belonged to the new user who unknowingly created a recurring transaction with the same recur_id as a contribution_recur record on the old site. He was not actually charged. On the payment gateway, the recurring transaction was charged to the user who created it on the new Wordpress site. But the circuits got crossed when the API notification went to Civi and someone else got the receipt. This continued for several months until we identified the issue. Fortunately, there were no actual charges to the people who mistakenly received receipts and this only affected 5 users.
For anyone reading this who's planning a migration, the lesson learned was make sure you disable contributions on the old site (put it in maintenance mode and disable the payment gateway) right before you bring up the new site with the migrated Civi database.