Fixes #3568
Part of the on-going duplicate order notes/emails issue: #3501
Changes proposed in this Pull Request:
During the subscription renewal order payment process, the order payment is locked before initiating the first Stripe create/confirm intent request (code reference).
In the current develop branch, the order is being unlocked prematurely (code reference). Here's the current sequence:
1. process_subscription_payment( $amount, $renewal_order )
2. lock_order_payment( $renewal_order )
3. create_and_confirm_intent_for_off_session( ... )
4. unlock_order_payment( $renewal_order )
5. get_latest_charge_from_intent() // sends a "GET charges/ID" request off to Stripe
6. process_response( $charge, $renewal_order ) // marks the order from pending -> processing/completed status
When there are delays in fetching the latest charge from Stripe or if the payment_intent.succeeded webhook is sent quickly, the current request and incoming webhook may be processed in parallel, leading to both requests calling $renewal_order->payment_complete() and adding the "Stripe charge completed." order note.
This results in duplicate emails being sent to customers and third-party code hooked to status transitions is fired multiple times.
Solution
This PR addresses the issue by moving the unlock step to occur after processing the response and handling any exceptions. This ensures the order remains locked until all necessary actions are complete, preventing parallel handling conflicts.
Testing instructions
To replicate this consistently, I had to add a small sleep( 2 ); between fetching the latest charge from Stripe and calling process_response() (here). While it seems hacky, this change simulates a slow Stripe request to fetch a charge object and assists with giving time for the payment_intent.succeeded webhook to be sent
Fixes #3568 Part of the on-going duplicate order notes/emails issue: #3501
Changes proposed in this Pull Request:
During the subscription renewal order payment process, the order payment is locked before initiating the first Stripe create/confirm intent request (code reference).
In the current develop branch, the order is being unlocked prematurely (code reference). Here's the current sequence:
When there are delays in fetching the latest charge from Stripe or if the
payment_intent.succeeded
webhook is sent quickly, the current request and incoming webhook may be processed in parallel, leading to both requests calling$renewal_order->payment_complete()
and adding the "Stripe charge completed." order note.This results in duplicate emails being sent to customers and third-party code hooked to status transitions is fired multiple times.
Solution
This PR addresses the issue by moving the unlock step to occur after processing the response and handling any exceptions. This ensures the order remains locked until all necessary actions are complete, preventing parallel handling conflicts.
Testing instructions
To replicate this consistently, I had to add a small
sleep( 2 );
between fetching the latest charge from Stripe and callingprocess_response()
(here). While it seems hacky, this change simulates a slow Stripe request to fetch a charge object and assists with giving time for thepayment_intent.succeeded
webhook to be sent4242424242424242
develop
process a renewal order using the "Process renewal" action on the Edit Subscription pagesleep( 2 )
in the code.develop
changelog.txt
andreadme.txt
(or does not apply)Post merge