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

fix(stripe): Fix the bug when there's failed payment we charge customer extra amount #8072

Merged
merged 4 commits into from
Apr 25, 2023

Conversation

tianrunhe
Copy link
Contributor

@tianrunhe tianrunhe commented Apr 19, 2023

Description

Fixes #8061. To reproduce the bug, on master branch:

  1. Install Stripe CLI if you haven't
  2. Setup Stripe webhook to forward to your local server: stripe listen --forward-to localhost:3000/stripe, and copy the webhook singing secret to replace the one in your .env
  3. Create a new team/org and go to the org billing page
  4. Upgrade with this Stripe payment failure test card: 4000 0000 0000 3063, expiration date any month in the future, CVC can be anything

Screenshot 2023-04-19 at 12 26 52

  1. Observe the org is upgraded
  2. Wait a moment and then refresh the page, you should see that one invoice is in the state of PENDING (because the payment wasn't successful) but another invoice for next month with 2x of the amount

Screenshot 2023-04-19 at 12 28 00

  1. Now update the credit card number to 4242 4242 4242 4242

  2. Wait a moment and then refresh the page, you should see a new invoice generated and paid with 2x of the amount, indicating the customer has been double charged.

Screenshot 2023-04-19 at 12 29 37

Testing scenarios

With this branch pulled and server started, repeat the above-mentioned steps 3 - 5.

  1. Wait a moment and then refresh the page, you should see one invoice in the state of FAILED:

Screenshot 2023-04-19 at 12 34 58

  1. Now update the credit card number to 4242 4242 4242 4242
  2. Wait a moment and then refresh the page, you should see a new invoice generated and paid the correct amount, and an upcoming invoice also with the correct amount

Screenshot 2023-04-19 at 12 37 15

Final checklist

  • I checked the code review guidelines
  • I have added Metrics Representative as reviewer(s) if my PR invovles metrics/data/analytics related changes
  • I have performed a self-review of my code, the same way I'd do it for any other team member
  • I have tested all cases I listed in the testing scenarios and I haven't found any issues or regressions
  • Whenever I took a non-obvious choice I added a comment explaining why I did it this way
  • I added the label One Review Required if the PR introduces only minor changes, does not contain any architectural changes or does not introduce any new patterns and I think one review is sufficient'
  • PR title is human readable and could be used in changelog

@tianrunhe tianrunhe linked an issue Apr 19, 2023 that may be closed by this pull request
1 task
@tianrunhe tianrunhe marked this pull request as ready for review April 19, 2023 17:41
@tianrunhe tianrunhe requested a review from nickoferrall April 19, 2023 17:42
Copy link
Contributor

@nickoferrall nickoferrall left a comment

Choose a reason for hiding this comment

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

Really appreciate the testing steps with screenshots! It made my life as a reviewer really easy. It works, and LGTM!

@Dschoordsch
Copy link
Contributor

webhook singing secret

I wonder which tune it sings

Copy link
Contributor

@Dschoordsch Dschoordsch left a comment

Choose a reason for hiding this comment

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

Looks good and it's the best type of fix where we just remove broken code 😎
However the changes in terminateSubscription appear wrong, are these covered by the test case?

@@ -18,7 +18,7 @@ const terminateSubscription = async (orgId: string) => {
stripeSubscriptionId: null
},
{returnChanges: true}
)('changes')(0)('old_val')
)('changes')(0)('old_val')('stripeSubscriptionId')
Copy link
Contributor

Choose a reason for hiding this comment

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

-1 this seems to contradict how we're reading the rethinkResult in line l26

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't sure what you meant by contradiction, but I made a few changes to make it more clear & easy to read. Let me know if that's what you were thinking?

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the change, now I understand what you were doing.

@@ -32,7 +32,7 @@ const terminateSubscription = async (orgId: string) => {
try {
await manager.deleteSubscription(stripeSubscriptionId)
} catch (e) {
// noop
console.error(`cannot delete subscription ${stripeSubscriptionId}`, e)
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 Reading the comment in l28 it appears as if we intentionally do not track errors here. Are you sure this is necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would highly suspect that it never did what it was supposed to do... the issue was hidden as we didn't have that many self upgrades in the past, let alone the even rare cases where the payment failed.

As a quick test, I commented out this part as the comment seemed to indicate, i.e., it wouldn't harm if the deletion failed or not even performed, as Stripe is configured to do so automatically. Well, not the case here. If the customer failed to perform the first payment and just did nothing, this is gonna happen:

  1. A failed payment notification was sent
  2. The invoice stays in PENDING state
  3. The org still stays in the Team tier, i.e., the customer is enjoying the Team tier for free

Screenshot 2023-04-25 at 11 47 29

Looking at Stripe side, after 24 hours, this is going to happen:

  1. The invoice is void
  2. The payment was cancelled
  3. The subscription was incomplete
    But again, none of those prevent us from giving the customers Team tier for free, from the app side.

Screenshot 2023-04-25 at 11 48 02

So, all in all, we definitely need that subscription deletion to finish and succeed, in order for the app to cancel that upgrade. Therefore, we should capture the error.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for investigating this and removing the comment!

@tianrunhe tianrunhe requested a review from Dschoordsch April 25, 2023 02:59
Copy link
Contributor

@Dschoordsch Dschoordsch left a comment

Choose a reason for hiding this comment

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

Did not test again, but looks good now!

@@ -32,7 +32,7 @@ const terminateSubscription = async (orgId: string) => {
try {
await manager.deleteSubscription(stripeSubscriptionId)
} catch (e) {
// noop
console.error(`cannot delete subscription ${stripeSubscriptionId}`, e)
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for investigating this and removing the comment!

@@ -18,7 +18,7 @@ const terminateSubscription = async (orgId: string) => {
stripeSubscriptionId: null
},
{returnChanges: true}
)('changes')(0)('old_val')
)('changes')(0)('old_val')('stripeSubscriptionId')
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the change, now I understand what you were doing.

@tianrunhe tianrunhe merged commit 2ba09fb into master Apr 25, 2023
@tianrunhe tianrunhe deleted the bug/8061/duplicateChargesOnFailedPayments branch April 25, 2023 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Multiples charges incurred on Team subscription when customer failed payments
3 participants