diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index b1dfbae2b916bd..aeb56b328bb2b1 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -294,6 +294,7 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, findStripeSubscriptionIdForTeam(teamId: string): Promise; createOrUpdateStripeCustomerForTeam(teamId: string, currency: string): Promise; createOrUpdateStripeCustomerForUser(currency: string): Promise; + subscribeToStripe(attributionId: string, setupIntentId: string): Promise; subscribeTeamToStripe(teamId: string, setupIntentId: string): Promise; getStripePortalUrl(attributionId: string): Promise; getStripePortalUrlForTeam(teamId: string): Promise; diff --git a/components/server/ee/src/workspace/gitpod-server-impl.ts b/components/server/ee/src/workspace/gitpod-server-impl.ts index 4ba3642bef7f25..578a49a365b793 100644 --- a/components/server/ee/src/workspace/gitpod-server-impl.ts +++ b/components/server/ee/src/workspace/gitpod-server-impl.ts @@ -2124,32 +2124,50 @@ export class GitpodServerEEImpl extends GitpodServerImpl { } protected defaultSpendingLimit = 100; - async subscribeTeamToStripe(ctx: TraceContext, teamId: string, setupIntentId: string): Promise { - this.checkAndBlockUser("subscribeTeamToStripe"); - const team = await this.guardTeamOperation(teamId, "update"); - await this.ensureStripeApiIsAllowed({ team }); + async subscribeToStripe(ctx: TraceContext, attributionId: string, setupIntentId: string): Promise { + const attrId = AttributionId.parse(attributionId); + if (attrId === undefined) { + log.error(`Invalid attribution id: ${attributionId}`); + throw new ResponseError(ErrorCodes.BAD_REQUEST, `Invalid attibution id: ${attributionId}`); + } + + const user = this.checkAndBlockUser("subscribeToStripe"); + + let customer: Stripe.Customer | undefined; try { - let customer = await this.stripeService.findCustomerByTeamId(team!.id); + if (attrId.kind === "team") { + const team = await this.guardTeamOperation(attrId.teamId, "update"); + await this.ensureStripeApiIsAllowed({ team }); + customer = await this.stripeService.findCustomerByTeamId(team!.id); + } else { + await this.ensureStripeApiIsAllowed({ user }); + customer = await this.stripeService.findCustomerByUserId(user.id); + } if (!customer) { - throw new Error(`No Stripe customer profile for team '${team.id}'`); + throw new Error(`No Stripe customer profile for '${attributionId}'`); } + await this.stripeService.setDefaultPaymentMethodForCustomer(customer, setupIntentId); await this.stripeService.createSubscriptionForCustomer(customer); - - const attributionId: AttributionId = { kind: "team", teamId }; - - // Creating a cost center for this team await this.usageServiceClientProvider.getDefault().setCostCenter({ - id: attributionId, + id: attrId, spendingLimit: this.defaultSpendingLimit, billingStrategy: "stripe", }); } catch (error) { - log.error(`Failed to subscribe team '${teamId}' to Stripe`, error); - throw new ResponseError(ErrorCodes.INTERNAL_SERVER_ERROR, `Failed to subscribe team '${teamId}' to Stripe`); + log.error(`Failed to subscribe '${attributionId}' to Stripe`, error); + throw new ResponseError( + ErrorCodes.INTERNAL_SERVER_ERROR, + `Failed to subscribe '${attributionId}' to Stripe`, + ); } } + async subscribeTeamToStripe(ctx: TraceContext, teamId: string, setupIntentId: string): Promise { + const attributionId: AttributionId = { kind: "team", teamId: teamId }; + return this.subscribeToStripe(ctx, AttributionId.render(attributionId), setupIntentId); + } + async getStripePortalUrl(ctx: TraceContext, attributionId: string): Promise { const attrId = AttributionId.parse(attributionId); if (attrId === undefined) { diff --git a/components/server/src/auth/rate-limiter.ts b/components/server/src/auth/rate-limiter.ts index 554c9901fd5f2e..85db9da0af3ba6 100644 --- a/components/server/src/auth/rate-limiter.ts +++ b/components/server/src/auth/rate-limiter.ts @@ -210,6 +210,7 @@ const defaultFunctions: FunctionsConfig = { findStripeSubscriptionIdForTeam: { group: "default", points: 1 }, createOrUpdateStripeCustomerForTeam: { group: "default", points: 1 }, createOrUpdateStripeCustomerForUser: { group: "default", points: 1 }, + subscribeToStripe: { group: "default", points: 1 }, subscribeTeamToStripe: { group: "default", points: 1 }, getStripePortalUrl: { group: "default", points: 1 }, getStripePortalUrlForTeam: { group: "default", points: 1 }, diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index a905a7266ae4f6..361e66359fe7c7 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -3233,6 +3233,9 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { async subscribeTeamToStripe(ctx: TraceContext, teamId: string, setupIntentId: string): Promise { throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`); } + async subscribeToStripe(ctx: TraceContext, attributionId: string, setupIntentId: string): Promise { + throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`); + } async getStripePortalUrlForTeam(ctx: TraceContext, teamId: string): Promise { throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`); }