From 44b926485004cb4aff512e9cf49b3dcb335a75fd Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Wed, 15 Jun 2022 15:21:15 +0000 Subject: [PATCH 1/3] [server] Make Stripe usage-based product price IDs configurable --- .werft/jobs/build/helm/values.payment.yaml | 10 ++++++-- .werft/jobs/build/installer/installer.ts | 1 + .../jobs/build/payment/stripe-configmap.yaml | 13 ++++++++++ .../server/ee/src/user/stripe-service.ts | 11 ++++---- components/server/src/config.ts | 20 +++++++++++++-- .../pkg/components/server/configmap.go | 13 ++++++++-- .../pkg/components/server/constants.go | 3 ++- .../pkg/components/server/deployment.go | 25 ++++++++++++++++++- .../installer/pkg/components/server/types.go | 1 + .../config/v1/experimental/experimental.go | 1 + 10 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 .werft/jobs/build/payment/stripe-configmap.yaml diff --git a/.werft/jobs/build/helm/values.payment.yaml b/.werft/jobs/build/helm/values.payment.yaml index 9ef4746ca98190..19626f96e934a2 100644 --- a/.werft/jobs/build/helm/values.payment.yaml +++ b/.werft/jobs/build/helm/values.payment.yaml @@ -6,16 +6,22 @@ components: - name: chargebee-config mountPath: "/chargebee" readOnly: true + - name: stripe-secret + mountPath: "/stripe-secret" + readOnly: true - name: stripe-config - mountPath: "/stripe" + mountPath: "/stripe-config" readOnly: true volumes: - name: chargebee-config secret: secretName: chargebee-config - - name: stripe-config + - name: stripe-secret secret: secretName: stripe-api-keys + - name: stripe-config + configMap: + name: stripe-config paymentEndpoint: disabled: false diff --git a/.werft/jobs/build/installer/installer.ts b/.werft/jobs/build/installer/installer.ts index b5af4bde1a03f1..4d1cbcdd10e855 100644 --- a/.werft/jobs/build/installer/installer.ts +++ b/.werft/jobs/build/installer/installer.ts @@ -84,6 +84,7 @@ export class Installer { // let installer know that there is a stripe config exec(`yq w -i ${this.options.installerConfigPath} experimental.webapp.server.stripeSecret stripe-api-keys`, { slice: slice }); + exec(`yq w -i ${this.options.installerConfigPath} experimental.webapp.server.stripeConfig stripe-config`, { slice: slice }); } } catch (err) { diff --git a/.werft/jobs/build/payment/stripe-configmap.yaml b/.werft/jobs/build/payment/stripe-configmap.yaml new file mode 100644 index 00000000000000..aa40b2d4e94597 --- /dev/null +++ b/.werft/jobs/build/payment/stripe-configmap.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: stripe-config + namespace: ${NAMESPACE} +data: + config: > + { + "usageProductPriceIds": { + "EUR": "price_1LAE0AGadRXm50o3xjegX0Kd", + "USD": "price_1LAE0AGadRXm50o3rKoktPiJ" + } + } diff --git a/components/server/ee/src/user/stripe-service.ts b/components/server/ee/src/user/stripe-service.ts index c99e7f2eb74ec0..ee7491c8b24757 100644 --- a/components/server/ee/src/user/stripe-service.ts +++ b/components/server/ee/src/user/stripe-service.ts @@ -116,16 +116,15 @@ export class StripeService { } async createSubscriptionForCustomer(customerId: string, currency: Currency): Promise { - // FIXME(janx): Use configmap. - const prices = { - EUR: "price_1LAE0AGadRXm50o3xjegX0Kd", - USD: "price_1LAE0AGadRXm50o3rKoktPiJ", - }; + const priceId = this.config?.stripeConfig?.usageProductPriceIds[currency]; + if (!priceId) { + throw new Error(`No Stripe Price ID configured for currency '${currency}'`); + } const startOfNextMonth = new Date(new Date().toISOString().slice(0, 7) + "-01"); // First day of this month (YYYY-MM-01) startOfNextMonth.setMonth(startOfNextMonth.getMonth() + 1); // Add one month await this.getStripe().subscriptions.create({ customer: customerId, - items: [{ price: prices[currency] }], + items: [{ price: priceId }], billing_cycle_anchor: Math.round(startOfNextMonth.getTime() / 1000), }); } diff --git a/components/server/src/config.ts b/components/server/src/config.ts index bcb69f86b3ad22..990ac524d8f3d7 100644 --- a/components/server/src/config.ts +++ b/components/server/src/config.ts @@ -20,12 +20,18 @@ import { filePathTelepresenceAware } from "@gitpod/gitpod-protocol/lib/env"; export const Config = Symbol("Config"); export type Config = Omit< ConfigSerialized, - "blockedRepositories" | "hostUrl" | "chargebeeProviderOptionsFile" | "stripeSecretsFile" | "licenseFile" + | "blockedRepositories" + | "hostUrl" + | "chargebeeProviderOptionsFile" + | "stripeSecretsFile" + | "stripeConfigFile" + | "licenseFile" > & { hostUrl: GitpodHostUrl; workspaceDefaults: WorkspaceDefaults; chargebeeProviderOptions?: ChargebeeProviderOptions; stripeSecrets?: { publishableKey: string; secretKey: string }; + stripeConfig?: { usageProductPriceIds: { EUR: string; USD: string } }; builtinAuthProvidersConfigured: boolean; blockedRepositories: { urlRegExp: RegExp; blockUser: boolean }[]; inactivityPeriodForRepos?: number; @@ -152,6 +158,7 @@ export interface ConfigSerialized { */ chargebeeProviderOptionsFile?: string; stripeSecretsFile?: string; + stripeConfigFile?: string; enablePayment?: boolean; /** @@ -222,7 +229,15 @@ export namespace ConfigFile { fs.readFileSync(filePathTelepresenceAware(config.stripeSecretsFile), "utf-8"), ); } catch (error) { - console.error("Could not load Stripe secrets", error); + log.error("Could not load Stripe secrets", error); + } + } + let stripeConfig: { usageProductPriceIds: { EUR: string; USD: string } } | undefined; + if (config.enablePayment && config.stripeConfigFile) { + try { + stripeConfig = JSON.parse(fs.readFileSync(filePathTelepresenceAware(config.stripeConfigFile), "utf-8")); + } catch (error) { + log.error("Could not load Stripe config", error); } } let license = config.license; @@ -252,6 +267,7 @@ export namespace ConfigFile { builtinAuthProvidersConfigured, chargebeeProviderOptions, stripeSecrets, + stripeConfig, license, workspaceGarbageCollection: { ...config.workspaceGarbageCollection, diff --git a/install/installer/pkg/components/server/configmap.go b/install/installer/pkg/components/server/configmap.go index c27b32e08a07d6..f5dce6c905f555 100644 --- a/install/installer/pkg/components/server/configmap.go +++ b/install/installer/pkg/components/server/configmap.go @@ -99,6 +99,14 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { return nil }) + stripeConfig := "" + _ = ctx.WithExperimental(func(cfg *experimental.Config) error { + if cfg.WebApp != nil && cfg.WebApp.Server != nil { + stripeConfig = cfg.WebApp.Server.StripeConfig + } + return nil + }) + disableWsGarbageCollection := false _ = ctx.WithExperimental(func(cfg *experimental.Config) error { if cfg.WebApp != nil && cfg.WebApp.Server != nil { @@ -216,9 +224,10 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { ImageBuilderAddr: "image-builder-mk3:8080", CodeSync: CodeSync{}, VSXRegistryUrl: fmt.Sprintf("https://open-vsx.%s", ctx.Config.Domain), // todo(sje): or "https://{{ .Values.vsxRegistry.host | default "open-vsx.org" }}" if not using OpenVSX proxy - EnablePayment: chargebeeSecret != "" || stripeSecret != "", + EnablePayment: chargebeeSecret != "" || stripeSecret != "" || stripeConfig != "", ChargebeeProviderOptionsFile: fmt.Sprintf("%s/providerOptions", chargebeeMountPath), - StripeSecretsFile: fmt.Sprintf("%s/apikeys", stripeMountPath), + StripeSecretsFile: fmt.Sprintf("%s/apikeys", stripeSecretMountPath), + StripeConfigFile: fmt.Sprintf("%s/config", stripeConfigMountPath), InsecureNoDomain: false, PrebuildLimiter: map[string]int{ // default limit for all cloneURLs diff --git a/install/installer/pkg/components/server/constants.go b/install/installer/pkg/components/server/constants.go index 4419bc71b9e7a3..7a4ed4bf097c9a 100644 --- a/install/installer/pkg/components/server/constants.go +++ b/install/installer/pkg/components/server/constants.go @@ -15,7 +15,8 @@ const ( authProviderFilePath = "/gitpod/auth-providers" licenseFilePath = "/gitpod/license" chargebeeMountPath = "/chargebee" - stripeMountPath = "/stripe" + stripeSecretMountPath = "/stripe-secret" + stripeConfigMountPath = "/stripe-config" githubAppCertSecret = "github-app-cert-secret" PrometheusPort = 9500 PrometheusPortName = "metrics" diff --git a/install/installer/pkg/components/server/deployment.go b/install/installer/pkg/components/server/deployment.go index 1e8bb59675987c..d468ce842fd699 100644 --- a/install/installer/pkg/components/server/deployment.go +++ b/install/installer/pkg/components/server/deployment.go @@ -204,7 +204,30 @@ func deployment(ctx *common.RenderContext) ([]runtime.Object, error) { volumeMounts = append(volumeMounts, corev1.VolumeMount{ Name: "stripe-secret", - MountPath: stripeMountPath, + MountPath: stripeSecretMountPath, + ReadOnly: true, + }) + } + return nil + }) + + _ = ctx.WithExperimental(func(cfg *experimental.Config) error { + if cfg.WebApp != nil && cfg.WebApp.Server != nil && cfg.WebApp.Server.StripeConfig != "" { + stripeConfig := cfg.WebApp.Server.StripeConfig + + volumes = append(volumes, + corev1.Volume{ + Name: "stripe-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: stripeConfig}, + }, + }, + }) + + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: "stripe-config", + MountPath: stripeConfigMountPath, ReadOnly: true, }) } diff --git a/install/installer/pkg/components/server/types.go b/install/installer/pkg/components/server/types.go index 80709b6d5d7768..27089eca63c285 100644 --- a/install/installer/pkg/components/server/types.go +++ b/install/installer/pkg/components/server/types.go @@ -33,6 +33,7 @@ type ConfigSerialized struct { VSXRegistryUrl string `json:"vsxRegistryUrl"` ChargebeeProviderOptionsFile string `json:"chargebeeProviderOptionsFile"` StripeSecretsFile string `json:"stripeSecretsFile"` + StripeConfigFile string `json:"stripeConfigFile"` EnablePayment bool `json:"enablePayment"` WorkspaceHeartbeat WorkspaceHeartbeat `json:"workspaceHeartbeat"` diff --git a/install/installer/pkg/config/v1/experimental/experimental.go b/install/installer/pkg/config/v1/experimental/experimental.go index 38d87c766d7514..f70324e76f87e1 100644 --- a/install/installer/pkg/config/v1/experimental/experimental.go +++ b/install/installer/pkg/config/v1/experimental/experimental.go @@ -140,6 +140,7 @@ type ServerConfig struct { GithubApp *GithubApp `json:"githubApp"` ChargebeeSecret string `json:"chargebeeSecret"` StripeSecret string `json:"stripeSecret"` + StripeConfig string `json:"stripeConfig"` DisableDynamicAuthProviderLogin bool `json:"disableDynamicAuthProviderLogin"` EnableLocalApp *bool `json:"enableLocalApp"` RunDbDeleter *bool `json:"runDbDeleter"` From ccc95e63b54c612ecefa337083e4be3bb468a6cd Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Thu, 16 Jun 2022 12:08:08 +0000 Subject: [PATCH 2/3] [werft] Update Stripe product price IDs in preview environments --- .werft/jobs/build/payment/stripe-configmap.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.werft/jobs/build/payment/stripe-configmap.yaml b/.werft/jobs/build/payment/stripe-configmap.yaml index aa40b2d4e94597..878c42581580ad 100644 --- a/.werft/jobs/build/payment/stripe-configmap.yaml +++ b/.werft/jobs/build/payment/stripe-configmap.yaml @@ -7,7 +7,7 @@ data: config: > { "usageProductPriceIds": { - "EUR": "price_1LAE0AGadRXm50o3xjegX0Kd", - "USD": "price_1LAE0AGadRXm50o3rKoktPiJ" + "EUR": "price_1LAyjSGadRXm50o3MO7CNyVU", + "USD": "price_1LAyjSGadRXm50o3tkwZe1N2" } } From c0075fb2c544ae1ab3d5c454a38937f8353bc4e5 Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Fri, 17 Jun 2022 09:08:28 +0000 Subject: [PATCH 3/3] Revert changes to .werft/jobs/build/helm/values.payment.yaml --- .werft/jobs/build/helm/values.payment.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.werft/jobs/build/helm/values.payment.yaml b/.werft/jobs/build/helm/values.payment.yaml index 19626f96e934a2..9ef4746ca98190 100644 --- a/.werft/jobs/build/helm/values.payment.yaml +++ b/.werft/jobs/build/helm/values.payment.yaml @@ -6,22 +6,16 @@ components: - name: chargebee-config mountPath: "/chargebee" readOnly: true - - name: stripe-secret - mountPath: "/stripe-secret" - readOnly: true - name: stripe-config - mountPath: "/stripe-config" + mountPath: "/stripe" readOnly: true volumes: - name: chargebee-config secret: secretName: chargebee-config - - name: stripe-secret + - name: stripe-config secret: secretName: stripe-api-keys - - name: stripe-config - configMap: - name: stripe-config paymentEndpoint: disabled: false