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

Allow teams to cancel and renew their usage-based subscription in Stripe #10890

Merged
merged 1 commit into from
Jun 24, 2022

Conversation

jankeromnes
Copy link
Contributor

@jankeromnes jankeromnes commented Jun 24, 2022

Description

Allow teams to cancel and renew their usage-based subscription in Stripe.

Before:

  • We show subscription status based on whether the Stripe customer exists
  • If the Stripe customer has a payment issue, or cancels their subscription, they are "stuck" in "Active Billing" status (but without a subscription and no way to renew it)

After:

  • We show subscription status based on whether a valid Stripe subscription exists
  • If the Stripe customer has a payment issue, or cancels their subscription, the UI goes back to the "Inactive Billing" (allowing an existing customer to create a new subscription)

Related Issue(s)

Fixes #

How to test

  1. Create a team with a name that contains "Gitpod" (to enable the usage-based feature flag)
  2. Go to Team Billing & wait for the usage-based UI to show up
  3. Subscribe to usage-based billing by adding a credit card (e.g. 4242 4242 4242 4242, 4/24, 424)
  4. When the "Manage Billing" link appears, click it
  5. In the Stripe Portal, click on "Cancel" next to your subscription, and go back to Gitpod
  6. Here, the UI should now go back to "Inactive Billing" state, thus allowing you to create a new subscription

Release Notes

NONE

Documentation

Werft options:

  • /werft with-preview
  • /werft with-payment

@jankeromnes
Copy link
Contributor Author

jankeromnes commented Jun 24, 2022

/werft run

👍 started the job as gitpod-build-jx-allow-cancel.1
(with .werft/ from main)

@jankeromnes jankeromnes marked this pull request as ready for review June 24, 2022 08:40
@jankeromnes jankeromnes requested a review from a team June 24, 2022 08:40
@github-actions github-actions bot added the team: webapp Issue belongs to the WebApp team label Jun 24, 2022
Copy link
Member

@easyCZ easyCZ left a comment

Choose a reason for hiding this comment

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

How is the cancellation triggered by the user? I'm assuming it's using the customer portal.

await this.ensureIsUsageBasedFeatureFlagEnabled(user);
await this.guardTeamOperation(teamId, "update");
try {
const customer = await this.stripeService.findCustomerByTeamId(teamId);
return customer?.id || undefined;
if (!customer?.id) {
return undefined;
Copy link
Member

Choose a reason for hiding this comment

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

As in another PR, I think this should be returning NotFound and the client handling not found as No subscription exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Until now, I think we've had our API return something | undefined for such findSomething methods.

We could change this pattern to introduce 404 errors instead, but then maybe it would make sense to change all these methods?

Copy link
Member

Choose a reason for hiding this comment

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

Up to you. I'd mostly suggest we do this opportunistically on apis we work on as part of features.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Follow-up issue: #10892

Comment on lines +1938 to +1941
let customer = await this.stripeService.findCustomerByTeamId(team!.id);
if (!customer) {
customer = await this.stripeService.createCustomerForTeam(user, team!, setupIntentId);
}
Copy link
Member

Choose a reason for hiding this comment

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

It seems it is possible for 2 parallel requests to subscribeTeamToStripe to succeed and create 2 different subscriptions. Is this somehow transactional on the Stripe side?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for identifying this pre-existing problem! I don't think it can realistically happen, but I'm happy to open a follow-up issue about this if it seems important. 💡

Copy link
Member

Choose a reason for hiding this comment

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

No need to solve in this PR. I would like an issue for this however. In the least, the Billing Controller (when listing subscriptions for a team) need to tell us there were more than 1 for a given TeamID so we can fix it.

Copy link
Member

Choose a reason for hiding this comment

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

The controller already handles this case - https://github.com/gitpod-io/gitpod/pull/10854/files#diff-1f1047abea4ed15decb31dfdf94d84646a601fbacd5462dcef20f7f8b4de33dbR54 it will ignore any with multiple subscriptions and logs an error.

@jankeromnes
Copy link
Contributor Author

Thanks @easyCZ for the initial feedback!

How is the cancellation triggered by the user? I'm assuming it's using the customer portal.

Please read the provided test instructions:

  1. In the Stripe Portal, click on "Cancel" next to your subscription, and go back to Gitpod

@easyCZ
Copy link
Member

easyCZ commented Jun 24, 2022

Just ran through the steps:

  • After I added a payment method, for 2-3seconds, the view still continued to say Inactive and only then a loading screen indicator showed.
  • I've also managed to create 2 subscriptions for my team. This then results in the following error (as implemented)
TeamUsageBasedBilling.tsx:43 Error: Failed to get Stripe Subscription ID for team 'TEAM_ID_FOO'
    at main.js:449:48
    at main.js:276:17
    at main.js:260:13
    at setImmediate.js:40:13
    at l (setImmediate.js:69:21)
    at n (setImmediate.js:109:17)

Here, the problem is the plan remains Inactive and I can still click Update payment info, add my credit card and create yet another subscription.

@easyCZ
Copy link
Member

easyCZ commented Jun 24, 2022

I think we should return a Typed error from the Subscription lookup by Team when there are more than 1, which prevents you from creating another subscription (on the server side) but also tell you that there are multiple subscriptions and to please reach out to support.

@jankeromnes
Copy link
Contributor Author

jankeromnes commented Jun 24, 2022

  • After I added a payment method, for 2-3seconds, the view still continued to say Inactive and only then a loading screen indicator showed.

Good catch! This is #10656, which I'm planning to fix soon. 📝

  • I've also managed to create 2 subscriptions for my team. This then results in the following error (as implemented)

Did you achieve this by quickly completing the billing setup again, as allowed by #10656? If so, when we fix the UI state, this bug will become much harder to reproduce.

However, I do see your point about the race condition / transaction, so I'll open a follow-up issue for this as well.

@easyCZ
Copy link
Member

easyCZ commented Jun 24, 2022

Did you achieve this by quickly completing the billing setup again, as allowed by #10656? If so, when we fix the UI state, this bug will become much harder to reproduce.

Yes. 2 windows. But with the delay for the subscription to be created (on Stripe), this didn't really need special timing either. We don't need to fix the create, just need to communicate the issue better and ask people to reach out. Thanks for creating the issue!

Copy link
Member

@easyCZ easyCZ left a comment

Choose a reason for hiding this comment

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

Works as advertised.v User can unsubscribe and then re-subscribe modulo other related issues discussed.

Thanks for the change!

@roboquat roboquat merged commit 26b4f55 into main Jun 24, 2022
@roboquat roboquat deleted the jx/allow-cancel branch June 24, 2022 09:25
@roboquat roboquat added deployed: webapp Meta team change is running in production deployed Change is completely running in production labels Jun 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
deployed: webapp Meta team change is running in production deployed Change is completely running in production release-note-none size/L team: webapp Issue belongs to the WebApp team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants