Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move charge related code to separate try-catch to prevent renewal failure #3444

Merged
merged 5 commits into from
Sep 30, 2024

Conversation

Mayisha
Copy link
Contributor

@Mayisha Mayisha commented Sep 19, 2024

Reported in 8657329-zd-a8c
Discussion p1726467962851179-slack-C7U3Y3VMY

In process_subscription_payment function under one try-catch block, we make 2 API calls during the payment flow.

  1. The first API call is to create and confirm the intent which mainly processes the subscription payment. In this step, the card is already charged if the request is successful.
  2. We make an API call to get the charge object to find the transaction ID. (here and here)

The merchant reported that their customers are charged multiple times for the same subscription renewal. They have the retry mechanism enabled for failed payments.
We have noticed from their logs that they encountered some timeouts during requests to Stripe. In the above function, when they encountered the timeouts in step 2, it marked their order as failed. As a result the retry mechanism was triggered and customer was charged again.

Changes proposed in this Pull Request:

  • Moved the API requests under two separate try-catch blocks.
  • If the first request (create and confirm intent) fails, the card won't be charged. In the catch block of this section, we change the order status to failed.
  • In the catch block of second API request (get charge), we only log the error but won't change the order status to failed as the card should be already charged.

Testing instructions

  • Checkout to develop branch.
  • From WooCommerce > Settings > Subscriptions, enable Retry Failed Payments.
  • As a shopper, purchase a subscription.
  • In this line add the following code to intentionally fail the get charges request.
// tweak charge id to fail intentionally
if ( ! empty( $response->latest_charge ) ) {
    $response->latest_charge = $response->latest_charge . 'm';
}
  • As an admin, go to the subscription edit page and renew the subscription from Order actions dropdown.
  • Go to the renewal order edit page.
  • Notice that the order has failed but in the order notes you will see a payment intent was created.
  • Go to your Stripe dashboard and you will find the corresponding transaction.
  • Now checkout to this branch.
  • Follow the above steps to reproduce the issue.
  • This time the order should not fail but you should see the charges related error in your logs.

@Mayisha Mayisha marked this pull request as draft September 19, 2024 20:47
@Mayisha Mayisha marked this pull request as ready for review September 20, 2024 15:01
}

try {

Copy link
Contributor Author

@Mayisha Mayisha Sep 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff makes it look like some lines are added or removed. But the only change I made here is, I have moved this block to another try-catch below, where catch block logs the error only.

Copy link
Contributor

@mattallan mattallan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Mayisha thanks for the details description and nice approach to this issue!

After performing code review I have a quick question, should we be returning at the end of the first catch if the payment fails?

Copy link
Contributor

@mattallan mattallan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Mayisha for the latest commits!

I've tested these changes and confirm the order is not marked as failed if the request to get the latest charge and process response fails. 💯 The order was left pending payment and then later was automatically marked as processing once the intent success webhook was sent (i'm assuming this is correct).

@Mayisha Mayisha merged commit 60328fb into develop Sep 30, 2024
34 of 35 checks passed
@Mayisha Mayisha deleted the fix/prevent-renewal-failure branch September 30, 2024 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants