-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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 sign up for Usage-Based Billing via Stripe #10378
Conversation
2436fd3
to
82c88b2
Compare
82c88b2
to
2467150
Compare
1442173
to
bb5c9c2
Compare
bb5c9c2
to
c2575b2
Compare
c2575b2
to
dc04864
Compare
TODO:
Known issue:
|
dc04864
to
3f90eb3
Compare
return this.config.stripeSettings?.publishableKey; | ||
} | ||
|
||
async getStripeSetupIntentClientSecret(ctx: TraceContext): Promise<string | undefined> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When interactions with 3rd party services fail, we shouldn't be propagating the errors directly to the client. We don't entirely control what is in the errors and may contain sensitive data. How is that handled here? I'd expect that we check for success/error and return appropriate obfuscated error messages upstream to the client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree -- we could avoid propagating to the user/client with a try/catch:
// Pseudo-code
try {
await stripeThing();
} catch (error) {
log.error("the full error for our logs", { error });
throw new Error("Stripe thing failed for some reason ¯\_(ツ)_/¯");
}
Behaviour wise: works well and tested. The popup to add credit-card logs a number of errors when making a If you wanted to, this PR could've been split into the following:
That way, you'd be able to keep the PRs smaller, and individual steps smaller. For example, in this case you'd get the review feedback on needing the feature flag guard server-side on the API setup, not on the full PR. |
3f90eb3
to
49b0a9f
Compare
@gtsiolis Had a quick look into how we can style Stripe's PaymentElement. Here is how they suggest we do it:
The 4 themes are:
I think a good Minimum-Viable-Change could be:
We could do this to further align the look & feel of Stripe Elements with our dashboard in broad strokes. 💡
With this, we can even drill down into specific styling issues that might remain after 1. and 2. 🔍 |
Works for me as well - Is there a way to navigate manually to the Stripe portal to manage billing/payment details (as a Gitpod user - not as the GitPod Stripe admin) ? |
@easyCZ do you know why the feature flag feels slow ? |
Thanks! I'm glad this works perfectly fine for everyone. 😊
I don't think so, but I'm planning to implement the Stripe portal in the next PR once this is merged. |
To me it looks like react is not being told the values have changed so it doesn't propagate as soon as it should. I'm no expert on react either so not entirely sure where this delay lies. |
@@ -1827,6 +1830,54 @@ export class GitpodServerEEImpl extends GitpodServerImpl { | |||
return subscription; | |||
} | |||
|
|||
protected async ensureIsUsageBasedFeatureFlagEnabled(user: User): Promise<void> { | |||
const teams = await this.teamDB.findTeamsByUser(user.id); | |||
const isUsageBasedBillingEnabled = await getExperimentsClient().getValueAsync( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this work on the server? The implementation of getExperimentsClient()
is
export function getExperimentsClient(): Client {
// We have already instantiated a client, we can just re-use it.
if (client !== undefined) {
return client;
}
const host = window.location.hostname;
if (host === "gitpod.io") {
client = newProductionConfigCatClient();
} else if (host === "gitpod-staging.com" || host.endsWith("gitpod-dev.com") || host.endsWith("gitpod-io-dev.com")) {
client = newNonProductionConfigCatClient();
} else {
// We're gonna use a client which always returns the default value.
client = newAlwaysReturningDefaultValueClient();
}
return client;
}
This uses the window.location.hostname
which doesn't exist in the server context. How is this initialised?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If feature flags don't work as expected in the server
, I'd vote for not using them right now (a client-side feature flag is enough to hide this WIP feature), and open a separate issue about fixing the server-side feature flags so that we can use them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jankeromnes How about adding a pre-pr to make it work on server properly and then using it here? I'm happy to pair if that makes it easier. Boy/girl scout rules apply.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@easyCZ That's the dashboard implementation, right?
The server-side implementation seems to be here: https://github.com/gitpod-io/gitpod/blob/main/components/server/src/experiments.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, wasn't aware this landed (and my IDE resolved the method to the dashboard one). This must be stray from another PR. It also lives under the root of server/src which it should not.
This will in fact work for server. However, there's a second problem with it as it hard-codes a single Client ID which is for the preview/staging environment, but not production.
Let's land as is, and fix it up in a follow-up PR. We'll need to move the file, but also inject the Client ID based on environment we're running in. The current version would also break self-hosted environments as it reaches out to an external source.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow-up issue: #10524
This is not the case for me, works rather snappy now. 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, tested and works.
I leave it up to you @jankeromnes to address the "error leak" in a follow-up.
Awesome, many thanks @geropl. ❤️ Adding a brief hold for the try/catches: /hold |
@jankeromnes Let me know when API layer error handling is in and I'll re-review. |
f9d98f9
to
d31c9f0
Compare
await this.guardTeamOperation(teamId, "update"); | ||
try { | ||
const customer = await this.stripeService.findCustomerByTeamId(teamId); | ||
return customer?.id || undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, IMO you should return a 404 error when customer.id does not exist. Returning undefined prevents you classifying your errors and actually having reasonable metrics on what worked and didn't. This goes for setupIntent
also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, interesting point. I agree on the 404 semantics. However, returning undefined
currently means that the customer doesn't exist yet, and is handled that way in the client (different from "the Stripe API failed"). If we switch this around to a 404, we'd need to update the client as well to handle 404 as a valid case (i.e. "the team is not yet a Stripe customer").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's correct. If it doesn't exist, then it wasn't found and we should create one (if needed). In general, we should try to avoid OK responses with No data as much as possible, especially when we know why there is no data - in this case it doesn't exist in stripe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Follow-up PR/issue is fine)
…tripe customer ID
…thout the feature flag
d31c9f0
to
f31407b
Compare
/werft run with-clean-slate-deployment 👍 started the job as gitpod-build-jx-stripe-service.15 |
Thanks again for the helpful reviews 🙏 and sorry for the bumpy ride. Will try even harder to keep PRs small going forward. Build finally went through, and everything still works. 🎉 Pushing this over the finish line. 🏁 Many improvements to come as follow-ups. 🚀 /unhold |
Description
Allow teams to sign up for Usage-Based Billing via Stripe.
Related Issue(s)
Fixes #10326
How to test
Release Notes
Documentation