Skip to content

[server] Make Stripe usage-based product price IDs configurable #10660

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

Merged
merged 3 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .werft/jobs/build/helm/values.payment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why this whole section is necessary.

Werft installs gitpod using the installer which already configures server's volumes and mounts, so what is this section for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question -- I'm actually not sure 🙈 I just basically copied how chargebee-config is implemented.

Do you think I should try without some or all of the changes in this file?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd try it without the changes to this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting -- still works as intended:

Screenshot 2022-06-17 at 11 29 10

Do you think we should delete this YAML file?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think we should delete this YAML file?

Probably not in this PR. Open a separate one and maybe get the Platform team's view on what it's for.

Copy link
Member

Choose a reason for hiding this comment

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

@andrew-farries Indeed, this is a left-over - and we have been cargo-culting here 🙈 Let's remove (incl. all references)! 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good! I'll send a follow-up PR. 🧹

Copy link
Contributor Author

Choose a reason for hiding this comment

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


paymentEndpoint:
disabled: false
1 change: 1 addition & 0 deletions .werft/jobs/build/installer/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
13 changes: 13 additions & 0 deletions .werft/jobs/build/payment/stripe-configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: stripe-config
namespace: ${NAMESPACE}
data:
config: >
{
"usageProductPriceIds": {
"EUR": "price_1LAyjSGadRXm50o3MO7CNyVU",
"USD": "price_1LAyjSGadRXm50o3tkwZe1N2"
}
}
11 changes: 5 additions & 6 deletions components/server/ee/src/user/stripe-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,15 @@ export class StripeService {
}

async createSubscriptionForCustomer(customerId: string, currency: Currency): Promise<void> {
// 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),
});
}
Expand Down
20 changes: 18 additions & 2 deletions components/server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -152,6 +158,7 @@ export interface ConfigSerialized {
*/
chargebeeProviderOptionsFile?: string;
stripeSecretsFile?: string;
stripeConfigFile?: string;
enablePayment?: boolean;

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -252,6 +267,7 @@ export namespace ConfigFile {
builtinAuthProvidersConfigured,
chargebeeProviderOptions,
stripeSecrets,
stripeConfig,
license,
workspaceGarbageCollection: {
...config.workspaceGarbageCollection,
Expand Down
13 changes: 11 additions & 2 deletions install/installer/pkg/components/server/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion install/installer/pkg/components/server/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
25 changes: 24 additions & 1 deletion install/installer/pkg/components/server/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}
Expand Down
1 change: 1 addition & 0 deletions install/installer/pkg/components/server/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down