|
|
@@ -25,14 +25,48 @@ const (
|
|
|
|
|
|
// MetronomeClient is the client used to call the Metronome API
|
|
|
type MetronomeClient struct {
|
|
|
- ApiKey string
|
|
|
+ ApiKey string
|
|
|
+ PorterCloudPlanID uuid.UUID
|
|
|
+ PorterStandardPlanID uuid.UUID
|
|
|
}
|
|
|
|
|
|
// NewMetronomeClient returns a new Metronome client
|
|
|
-func NewMetronomeClient(metronomeApiKey string) MetronomeClient {
|
|
|
+func NewMetronomeClient(metronomeApiKey string, porterCloudPlanID string, porterStandardPlanID string) (client MetronomeClient, err error) {
|
|
|
+ porterCloudPlanUUID, err := uuid.Parse(porterCloudPlanID)
|
|
|
+ if err != nil {
|
|
|
+ return client, err
|
|
|
+ }
|
|
|
+
|
|
|
+ porterStandardPlanUUID, err := uuid.Parse(porterStandardPlanID)
|
|
|
+ if err != nil {
|
|
|
+ return client, err
|
|
|
+ }
|
|
|
+
|
|
|
return MetronomeClient{
|
|
|
- ApiKey: metronomeApiKey,
|
|
|
+ ApiKey: metronomeApiKey,
|
|
|
+ PorterCloudPlanID: porterCloudPlanUUID,
|
|
|
+ PorterStandardPlanID: porterStandardPlanUUID,
|
|
|
+ }, nil
|
|
|
+}
|
|
|
+
|
|
|
+// CreateCustomerWithPlan will create the customer in Metronome and immediately add it to the plan
|
|
|
+func (m MetronomeClient) CreateCustomerWithPlan(ctx context.Context, userEmail string, projectName string, projectID uint, billingID string, sandboxEnabled bool) (customerID uuid.UUID, customerPlanID uuid.UUID, err error) {
|
|
|
+ ctx, span := telemetry.NewSpan(ctx, "add-metronome-customer-plan")
|
|
|
+ defer span.End()
|
|
|
+
|
|
|
+ planID := m.PorterStandardPlanID
|
|
|
+ if sandboxEnabled {
|
|
|
+ planID = m.PorterCloudPlanID
|
|
|
+ }
|
|
|
+
|
|
|
+ customerID, err = m.createCustomer(ctx, userEmail, projectName, projectID, billingID)
|
|
|
+ if err != nil {
|
|
|
+ return customerID, customerPlanID, telemetry.Error(ctx, span, err, fmt.Sprintf("error while creating customer with plan %s", planID))
|
|
|
}
|
|
|
+
|
|
|
+ customerPlanID, err = m.addCustomerPlan(ctx, customerID, planID)
|
|
|
+
|
|
|
+ return customerID, customerPlanID, err
|
|
|
}
|
|
|
|
|
|
// createCustomer will create the customer in Metronome
|
|
|
@@ -63,7 +97,7 @@ func (m MetronomeClient) createCustomer(ctx context.Context, userEmail string, p
|
|
|
Data types.Customer `json:"data"`
|
|
|
}
|
|
|
|
|
|
- err = post(path, m.ApiKey, customer, &result)
|
|
|
+ err = do(http.MethodPost, path, m.ApiKey, customer, &result)
|
|
|
if err != nil {
|
|
|
return customerID, telemetry.Error(ctx, span, err, "error creating customer")
|
|
|
}
|
|
|
@@ -97,7 +131,7 @@ func (m MetronomeClient) addCustomerPlan(ctx context.Context, customerID uuid.UU
|
|
|
} `json:"data"`
|
|
|
}
|
|
|
|
|
|
- err = post(path, m.ApiKey, req, &result)
|
|
|
+ err = do(http.MethodPost, path, m.ApiKey, req, &result)
|
|
|
if err != nil {
|
|
|
return customerPlanID, telemetry.Error(ctx, span, err, "failed to add customer to plan")
|
|
|
}
|
|
|
@@ -105,24 +139,31 @@ func (m MetronomeClient) addCustomerPlan(ctx context.Context, customerID uuid.UU
|
|
|
return result.Data.CustomerPlanID, nil
|
|
|
}
|
|
|
|
|
|
-// CreateCustomerWithPlan will create the customer in Metronome and immediately add it to the plan
|
|
|
-func (m MetronomeClient) CreateCustomerWithPlan(ctx context.Context, userEmail string, projectName string, projectID uint, billingID string, planID string) (customerID uuid.UUID, customerPlanID uuid.UUID, err error) {
|
|
|
- ctx, span := telemetry.NewSpan(ctx, "add-metronome-customer-plan")
|
|
|
+// ListCustomerPlan will return the current active plan to which the user is subscribed
|
|
|
+func (m MetronomeClient) ListCustomerPlan(ctx context.Context, customerID uuid.UUID) (plan types.Plan, err error) {
|
|
|
+ ctx, span := telemetry.NewSpan(ctx, "list-customer-plans")
|
|
|
defer span.End()
|
|
|
|
|
|
- porterCloudPlanID, err := uuid.Parse(planID)
|
|
|
- if err != nil {
|
|
|
- return customerID, customerPlanID, telemetry.Error(ctx, span, err, "error parsing starter plan id")
|
|
|
+ if customerID == uuid.Nil {
|
|
|
+ return plan, telemetry.Error(ctx, span, err, "customer id empty")
|
|
|
}
|
|
|
|
|
|
- customerID, err = m.createCustomer(ctx, userEmail, projectName, projectID, billingID)
|
|
|
+ path := fmt.Sprintf("/customers/%s/plans", customerID)
|
|
|
+
|
|
|
+ var result struct {
|
|
|
+ Data []types.Plan `json:"data"`
|
|
|
+ }
|
|
|
+
|
|
|
+ err = do(http.MethodGet, path, m.ApiKey, nil, &result)
|
|
|
if err != nil {
|
|
|
- return customerID, customerPlanID, telemetry.Error(ctx, span, err, "error while creatinc customer with plan")
|
|
|
+ return plan, telemetry.Error(ctx, span, err, "failed to list customer plans")
|
|
|
}
|
|
|
|
|
|
- customerPlanID, err = m.addCustomerPlan(ctx, customerID, porterCloudPlanID)
|
|
|
+ if len(result.Data) > 0 {
|
|
|
+ plan = result.Data[0]
|
|
|
+ }
|
|
|
|
|
|
- return customerID, customerPlanID, err
|
|
|
+ return plan, nil
|
|
|
}
|
|
|
|
|
|
// EndCustomerPlan will immediately end the plan for the given customer
|
|
|
@@ -145,7 +186,7 @@ func (m MetronomeClient) EndCustomerPlan(ctx context.Context, customerID uuid.UU
|
|
|
EndingBeforeUTC: endBefore,
|
|
|
}
|
|
|
|
|
|
- err = post(path, m.ApiKey, req, nil)
|
|
|
+ err = do(http.MethodPost, path, m.ApiKey, req, nil)
|
|
|
if err != nil {
|
|
|
return telemetry.Error(ctx, span, err, "failed to end customer plan")
|
|
|
}
|
|
|
@@ -153,9 +194,9 @@ func (m MetronomeClient) EndCustomerPlan(ctx context.Context, customerID uuid.UU
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// GetCustomerCredits will return the first credit grant for the customer
|
|
|
-func (m MetronomeClient) GetCustomerCredits(ctx context.Context, customerID uuid.UUID) (credits int64, err error) {
|
|
|
- ctx, span := telemetry.NewSpan(ctx, "get-customer-credits")
|
|
|
+// ListCustomerCredits will return the total number of credits for the customer
|
|
|
+func (m MetronomeClient) ListCustomerCredits(ctx context.Context, customerID uuid.UUID) (credits types.ListCreditGrantsResponse, err error) {
|
|
|
+ ctx, span := telemetry.NewSpan(ctx, "list-customer-credits")
|
|
|
defer span.End()
|
|
|
|
|
|
if customerID == uuid.Nil {
|
|
|
@@ -174,15 +215,51 @@ func (m MetronomeClient) GetCustomerCredits(ctx context.Context, customerID uuid
|
|
|
Data []types.CreditGrant `json:"data"`
|
|
|
}
|
|
|
|
|
|
- err = post(path, m.ApiKey, req, &result)
|
|
|
+ err = do(http.MethodPost, path, m.ApiKey, req, &result)
|
|
|
if err != nil {
|
|
|
- return credits, telemetry.Error(ctx, span, err, "failed to get customer credits")
|
|
|
+ return credits, telemetry.Error(ctx, span, err, "failed to list customer credits")
|
|
|
}
|
|
|
|
|
|
- return result.Data[0].Balance.IncludingPending, nil
|
|
|
+ var response types.ListCreditGrantsResponse
|
|
|
+ for _, grant := range result.Data {
|
|
|
+ response.GrantedCredits += grant.GrantAmount.Amount
|
|
|
+ response.RemainingCredits += grant.Balance.IncludingPending
|
|
|
+ }
|
|
|
+
|
|
|
+ return response, nil
|
|
|
}
|
|
|
|
|
|
-func post(path string, apiKey string, body interface{}, data interface{}) (err error) {
|
|
|
+// GetCustomerDashboard will return an embeddable Metronome dashboard
|
|
|
+func (m MetronomeClient) GetCustomerDashboard(ctx context.Context, customerID uuid.UUID, dashboardType string, options []types.DashboardOption, colorOverrides []types.ColorOverride) (url string, err error) {
|
|
|
+ ctx, span := telemetry.NewSpan(ctx, "get-customer-usage-dashboard")
|
|
|
+ defer span.End()
|
|
|
+
|
|
|
+ if customerID == uuid.Nil {
|
|
|
+ return url, telemetry.Error(ctx, span, err, "customer id empty")
|
|
|
+ }
|
|
|
+
|
|
|
+ path := "dashboards/getEmbeddableUrl"
|
|
|
+
|
|
|
+ req := types.EmbeddableDashboardRequest{
|
|
|
+ CustomerID: customerID,
|
|
|
+ Options: options,
|
|
|
+ DashboardType: dashboardType,
|
|
|
+ ColorOverrides: colorOverrides,
|
|
|
+ }
|
|
|
+
|
|
|
+ var result struct {
|
|
|
+ Data map[string]string `json:"data"`
|
|
|
+ }
|
|
|
+
|
|
|
+ err = do(http.MethodPost, path, m.ApiKey, req, &result)
|
|
|
+ if err != nil {
|
|
|
+ return url, telemetry.Error(ctx, span, err, "failed to get embeddable dashboard")
|
|
|
+ }
|
|
|
+
|
|
|
+ return result.Data["url"], nil
|
|
|
+}
|
|
|
+
|
|
|
+func do(method string, path string, apiKey string, body interface{}, data interface{}) (err error) {
|
|
|
client := http.Client{}
|
|
|
endpoint, err := url.JoinPath(metronomeBaseUrl, path)
|
|
|
if err != nil {
|
|
|
@@ -197,7 +274,7 @@ func post(path string, apiKey string, body interface{}, data interface{}) (err e
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(bodyJson))
|
|
|
+ req, err := http.NewRequest(method, endpoint, bytes.NewBuffer(bodyJson))
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
@@ -211,7 +288,15 @@ func post(path string, apiKey string, body interface{}, data interface{}) (err e
|
|
|
}
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
- return fmt.Errorf("non 200 status code returned: %d", resp.StatusCode)
|
|
|
+ // If there is an error, try to decode the message
|
|
|
+ var message map[string]string
|
|
|
+ err = json.NewDecoder(resp.Body).Decode(&message)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("status code %d received, couldn't process response message", resp.StatusCode)
|
|
|
+ }
|
|
|
+ _ = resp.Body.Close()
|
|
|
+
|
|
|
+ return fmt.Errorf("status code %d received, response message: %v", resp.StatusCode, message)
|
|
|
}
|
|
|
|
|
|
if data != nil {
|