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

Fix porter cloud ingest aliases #4580

Merged
merged 6 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 11 additions & 2 deletions api/server/handlers/billing/ingest.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package billing

import (
"fmt"
"net/http"

"github.com/porter-dev/porter/api/server/handlers"
Expand Down Expand Up @@ -35,12 +36,13 @@ func (c *IngestEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

proj, _ := ctx.Value(types.ProjectScope).(*models.Project)

if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
c.WriteResult(w, r, "")

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeEnabled},
telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeConfigLoaded},
telemetry.AttributeKV{Key: "metronome-enabled", Value: proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient)},
telemetry.AttributeKV{Key: "porter-cloud-enabled", Value: proj.EnableSandbox},
)
return
}
Expand All @@ -64,6 +66,13 @@ func (c *IngestEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
telemetry.AttributeKV{Key: "usage-events-count", Value: len(ingestEventsRequest.Events)},
)

// For Porter Cloud events, we apend a prefix to avoid collisions before sending to Metronome
if proj.EnableSandbox {
for i := range ingestEventsRequest.Events {
ingestEventsRequest.Events[i].CustomerID = fmt.Sprintf("porter-cloud-%s", ingestEventsRequest.Events[i].CustomerID)
}
}

err := c.Config().BillingManager.MetronomeClient.IngestEvents(ctx, ingestEventsRequest.Events)
if err != nil {
err := telemetry.Error(ctx, span, err, "error ingesting events")
Expand Down
49 changes: 24 additions & 25 deletions api/server/handlers/billing/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,25 +115,16 @@ func (c *CheckPaymentEnabledHandler) ensureBillingSetup(ctx context.Context, pro
}

// Create billing customer for project and set the billing ID if it doesn't exist
shouldUpdateBilling, err := c.ensureStripeCustomerExists(ctx, adminUser.Email, proj)
err = c.ensureStripeCustomerExists(ctx, adminUser.Email, proj)
if err != nil {
return telemetry.Error(ctx, span, err, "error ensuring Stripe customer exists")
}

// Create usage customer for project and set the usage ID if it doesn't exist
shouldUpdateUsage, err := c.ensureMetronomeCustomerExists(ctx, adminUser.Email, proj)
err = c.ensureMetronomeCustomerExists(ctx, adminUser.Email, proj)
if err != nil {
return telemetry.Error(ctx, span, err, "error ensuring Metronome customer exists")
}

if !shouldUpdateBilling && !shouldUpdateUsage {
return nil
}

_, err = c.Repo().Project().UpdateProject(proj)
if err != nil {
return telemetry.Error(ctx, span, err, "error updating project")
}
}

return nil
Expand Down Expand Up @@ -171,42 +162,44 @@ func (c *CheckPaymentEnabledHandler) getAdminUser(ctx context.Context, projectID
return adminUser, nil
}

func (c *CheckPaymentEnabledHandler) ensureStripeCustomerExists(ctx context.Context, adminUserEmail string, proj *models.Project) (shouldUpdate bool, err error) {
func (c *CheckPaymentEnabledHandler) ensureStripeCustomerExists(ctx context.Context, adminUserEmail string, proj *models.Project) (err error) {
ctx, span := telemetry.NewSpan(ctx, "ensure-stripe-customer-exists")
defer span.End()

if proj.BillingID != "" {
return false, nil
if !c.Config().BillingManager.StripeConfigLoaded || !proj.GetFeatureFlag(models.BillingEnabled, c.Config().LaunchDarklyClient) || proj.BillingID != "" {
return nil
}

billingID, err := c.Config().BillingManager.StripeClient.CreateCustomer(ctx, adminUserEmail, proj.ID, proj.Name)
if err != nil {
return false, telemetry.Error(ctx, span, err, "error creating billing customer")
return telemetry.Error(ctx, span, err, "error creating billing customer")
}

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "billing-id", Value: proj.BillingID},
)

proj.BillingID = billingID
return true, nil

_, err = c.Repo().Project().UpdateProject(proj)
if err != nil {
return telemetry.Error(ctx, span, err, "error updating project")
}

return nil
}

func (c *CheckPaymentEnabledHandler) ensureMetronomeCustomerExists(ctx context.Context, adminUserEmail string, proj *models.Project) (shouldUpdate bool, err error) {
func (c *CheckPaymentEnabledHandler) ensureMetronomeCustomerExists(ctx context.Context, adminUserEmail string, proj *models.Project) (err error) {
ctx, span := telemetry.NewSpan(ctx, "ensure-metronome-customer-exists")
defer span.End()

if proj.UsageID != uuid.Nil {
return false, nil
}

if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) || proj.UsageID != uuid.Nil {
return false, nil
if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) || proj.UsageID != uuid.Nil {
return nil
}

customerID, customerPlanID, err := c.Config().BillingManager.MetronomeClient.CreateCustomerWithPlan(ctx, adminUserEmail, proj.Name, proj.ID, proj.BillingID, proj.EnableSandbox)
if err != nil {
return false, telemetry.Error(ctx, span, err, "error creating Metronome customer")
return telemetry.Error(ctx, span, err, "error creating Metronome customer")
}

telemetry.WithAttributes(span,
Expand All @@ -216,5 +209,11 @@ func (c *CheckPaymentEnabledHandler) ensureMetronomeCustomerExists(ctx context.C

proj.UsageID = customerID
proj.UsagePlanID = customerPlanID
return true, nil

_, err = c.Repo().Project().UpdateProject(proj)
if err != nil {
return telemetry.Error(ctx, span, err, "error updating project")
}

return nil
}
12 changes: 6 additions & 6 deletions api/server/handlers/billing/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ func (c *ListPlansHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

proj, _ := ctx.Value(types.ProjectScope).(*models.Project)

if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
c.WriteResult(w, r, "")

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeEnabled},
telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeConfigLoaded},
telemetry.AttributeKV{Key: "metronome-enabled", Value: proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient)},
)
return
Expand Down Expand Up @@ -79,11 +79,11 @@ func (c *ListCreditsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

proj, _ := ctx.Value(types.ProjectScope).(*models.Project)

if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
c.WriteResult(w, r, "")

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeEnabled},
telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeConfigLoaded},
telemetry.AttributeKV{Key: "metronome-enabled", Value: proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient)},
)
return
Expand Down Expand Up @@ -127,12 +127,12 @@ func (c *ListCustomerUsageHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
proj, _ := ctx.Value(types.ProjectScope).(*models.Project)

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeEnabled},
telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeConfigLoaded},
telemetry.AttributeKV{Key: "metronome-enabled", Value: proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient)},
telemetry.AttributeKV{Key: "usage-id", Value: proj.UsageID},
)

if !c.Config().BillingManager.MetronomeEnabled || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
c.WriteResult(w, r, "")
return
}
Expand Down
4 changes: 2 additions & 2 deletions api/server/handlers/project/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
}

// Create Stripe Customer
if p.Config().ServerConf.StripeSecretKey != "" && p.Config().ServerConf.StripePublishableKey != "" {
if p.Config().BillingManager.StripeConfigLoaded && proj.GetFeatureFlag(models.BillingEnabled, p.Config().LaunchDarklyClient) {
// Create billing customer for project and set the billing ID
billingID, err := p.Config().BillingManager.StripeClient.CreateCustomer(ctx, user.Email, proj.ID, proj.Name)
if err != nil {
Expand All @@ -98,7 +98,7 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
}

// Create Metronome customer and add to starter plan
if p.Config().BillingManager.MetronomeEnabled && proj.GetFeatureFlag(models.MetronomeEnabled, p.Config().LaunchDarklyClient) {
if p.Config().BillingManager.MetronomeConfigLoaded && proj.GetFeatureFlag(models.MetronomeEnabled, p.Config().LaunchDarklyClient) {
customerID, customerPlanID, err := p.Config().BillingManager.MetronomeClient.CreateCustomerWithPlan(ctx, user.Email, proj.Name, proj.ID, proj.BillingID, proj.EnableSandbox)
if err != nil {
err = telemetry.Error(ctx, span, err, "error creating Metronome customer")
Expand Down
2 changes: 1 addition & 1 deletion api/server/handlers/project/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (p *ProjectDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
return
}

if p.Config().BillingManager.MetronomeEnabled && proj.GetFeatureFlag(models.MetronomeEnabled, p.Config().LaunchDarklyClient) {
if p.Config().BillingManager.MetronomeConfigLoaded && proj.GetFeatureFlag(models.MetronomeEnabled, p.Config().LaunchDarklyClient) {
err = p.Config().BillingManager.MetronomeClient.EndCustomerPlan(ctx, proj.UsageID, proj.UsagePlanID)
if err != nil {
e := "error ending billing plan"
Expand Down
9 changes: 6 additions & 3 deletions api/server/shared/config/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,13 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {

var (
stripeClient billing.StripeClient
stripeEnabled bool
metronomeClient billing.MetronomeClient
metronomeEnabled bool
)
if sc.StripeSecretKey != "" {
stripeClient = billing.NewStripeClient(InstanceEnvConf.ServerConf.StripeSecretKey, InstanceEnvConf.ServerConf.StripePublishableKey)
stripeEnabled = true
res.Logger.Info().Msg("Stripe configuration loaded")
} else {
res.Logger.Info().Msg("STRIPE_SECRET_KEY not set, all Stripe functionality will be disabled")
Expand All @@ -358,9 +360,10 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {

res.Logger.Info().Msg("Creating billing manager")
res.BillingManager = billing.Manager{
StripeClient: stripeClient,
MetronomeClient: metronomeClient,
MetronomeEnabled: metronomeEnabled,
StripeClient: stripeClient,
StripeConfigLoaded: stripeEnabled,
MetronomeClient: metronomeClient,
MetronomeConfigLoaded: metronomeEnabled,
}
res.Logger.Info().Msg("Created billing manager")

Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/main/home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ const Home: React.FC<Props> = (props) => {
} else {
setHasFinishedOnboarding(true);
}
} catch (error) {}
} catch (error) { }
};

useEffect(() => {
Expand Down Expand Up @@ -423,7 +423,7 @@ const Home: React.FC<Props> = (props) => {
>
connect a valid payment method
</Link>
. Your free trial is ending in{" "}
. Your free trial is ending {" "}
{relativeDate(plan.trial_info.ending_before, true)}.
</GlobalBanner>
)}
Expand Down
7 changes: 4 additions & 3 deletions internal/billing/billing.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package billing

// Manager contains methods for managing billing for a project
type Manager struct {
StripeClient StripeClient
MetronomeClient MetronomeClient
MetronomeEnabled bool
StripeClient StripeClient
StripeConfigLoaded bool
MetronomeClient MetronomeClient
MetronomeConfigLoaded bool
}
13 changes: 8 additions & 5 deletions internal/billing/metronome.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,15 @@ func (m MetronomeClient) CreateCustomerWithPlan(ctx context.Context, userEmail s
defer span.End()

planID := m.PorterStandardPlanID
projID := strconv.FormatUint(uint64(projectID), 10)
if sandboxEnabled {
planID = m.PorterCloudPlanID

// This is necessary to avoid conflicts with Porter standard projects
projID = fmt.Sprintf("porter-cloud-%s", projID)
}

customerID, err = m.createCustomer(ctx, userEmail, projectName, projectID, billingID)
customerID, err = m.createCustomer(ctx, userEmail, projectName, projID, billingID)
if err != nil {
return customerID, customerPlanID, telemetry.Error(ctx, span, err, fmt.Sprintf("error while creating customer with plan %s", planID))
}
Expand All @@ -69,25 +73,24 @@ func (m MetronomeClient) CreateCustomerWithPlan(ctx context.Context, userEmail s
}

// createCustomer will create the customer in Metronome
func (m MetronomeClient) createCustomer(ctx context.Context, userEmail string, projectName string, projectID uint, billingID string) (customerID uuid.UUID, err error) {
func (m MetronomeClient) createCustomer(ctx context.Context, userEmail string, projectName string, projectID string, billingID string) (customerID uuid.UUID, err error) {
ctx, span := telemetry.NewSpan(ctx, "create-metronome-customer")
defer span.End()

path := "customers"
projIDStr := strconv.FormatUint(uint64(projectID), 10)

customer := types.Customer{
Name: projectName,
Aliases: []string{
projIDStr,
projectID,
},
BillingConfig: types.BillingConfig{
BillingProviderType: "stripe",
BillingProviderCustomerID: billingID,
StripeCollectionMethod: defaultCollectionMethod,
},
CustomFields: map[string]string{
"project_id": projIDStr,
"project_id": projectID,
"user_email": userEmail,
},
}
Expand Down
Loading