Mauricio Araujo 2 лет назад
Родитель
Сommit
b16d6e5025

+ 4 - 17
api/server/handlers/billing/invoices.go

@@ -37,17 +37,10 @@ func (c *ListCustomerInvoicesHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 
 	telemetry.WithAttributes(span,
 		telemetry.AttributeKV{Key: "billing-config-exists", Value: c.Config().BillingManager.StripeConfigLoaded},
-		telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeConfigLoaded},
 		telemetry.AttributeKV{Key: "billing-enabled", Value: proj.GetFeatureFlag(models.BillingEnabled, c.Config().LaunchDarklyClient)},
-		telemetry.AttributeKV{Key: "metronome-enabled", Value: proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient)},
 		telemetry.AttributeKV{Key: "porter-cloud-enabled", Value: proj.EnableSandbox},
 	)
 
-	if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
-		c.WriteResult(w, r, "")
-		return
-	}
-
 	if !c.Config().BillingManager.StripeConfigLoaded || !proj.GetFeatureFlag(models.BillingEnabled, c.Config().LaunchDarklyClient) {
 		c.WriteResult(w, r, "")
 		return
@@ -61,19 +54,13 @@ func (c *ListCustomerInvoicesHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 		return
 	}
 
-	invoices, err := c.Config().BillingManager.MetronomeClient.ListCustomerInvoices(ctx, proj.UsageID, req.Status)
+	invoices, err := c.Config().BillingManager.StripeClient.ListCustomerInvoices(ctx, proj.BillingID, req.Status)
 	if err != nil {
-		err := telemetry.Error(ctx, span, err, "error listing customer invoices")
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error listing customer invoices: %w", err)))
-		return
-	}
-
-	invoices, err = c.Config().BillingManager.StripeClient.PopulateInvoiceURLs(ctx, invoices)
-	if err != nil {
-		err := telemetry.Error(ctx, span, err, "error populating invoice urls")
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error populating invoice urls: %w", err)))
+		err = telemetry.Error(ctx, span, err, fmt.Sprintf("error listing invoices for customer %s", proj.BillingID))
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
 		return
 	}
 
+	// Write the response to the frontend
 	c.WriteResult(w, r, invoices)
 }

+ 0 - 24
api/types/billing_metronome.go

@@ -196,27 +196,3 @@ type BillingEvent struct {
 	TransactionID string                 `json:"transaction_id"`
 	Timestamp     string                 `json:"timestamp"`
 }
-
-// ListCustomerInvoicesRequest is the request to list invoices for a customer
-type ListCustomerInvoicesRequest struct {
-	Status       string `schema:"status,omitempty"`
-	StartingOn   string `schema:"starting_on,omitempty"`
-	EndingBefore string `schema:"ending_before,omitempty"`
-}
-
-// Invoice represents a Metronome invoice.
-type Invoice struct {
-	ID              uuid.UUID       `json:"id"`
-	Status          string          `json:"status"`
-	IssuedAt        string          `json:"issued_at"`
-	ExternalInvoice ExternalInvoice `json:"external_invoice"`
-}
-
-// ExternalInvoice represents an external invoice in the billing provider (e.g. Stripe)
-type ExternalInvoice struct {
-	BillingProviderType string `json:"billing_provider_type"`
-	InvoiceID           string `json:"invoice_id"`
-	IssuedAt            string `json:"issued_at_timestamp"`
-	ExternalStatus      string `json:"external_status"`
-	ExternalURL         string `json:"external_url"`
-}

+ 16 - 1
api/types/billing_stripe.go

@@ -2,7 +2,7 @@ package types
 
 // PaymentMethod is a subset of the Stripe PaymentMethod type,
 // with only the fields used in the dashboard
-type PaymentMethod = struct {
+type PaymentMethod struct {
 	ID           string `json:"id"`
 	DisplayBrand string `json:"display_brand"`
 	Last4        string `json:"last4"`
@@ -10,3 +10,18 @@ type PaymentMethod = struct {
 	ExpYear      int64  `json:"exp_year"`
 	Default      bool   `json:"is_default"`
 }
+
+// Invoice represents an invoice in the billing system.
+type Invoice struct {
+	// The URL to view the hosted invoice.
+	HostedInvoiceURL string `json:"hosted_invoice_url"`
+	// The status of the invoice.
+	Status string `json:"status"`
+	// RFC 3339 timestamp for when the invoice was created.
+	Created string `json:"created"`
+}
+
+// ListCustomerInvoicesRequest is the request to list invoices for a customer
+type ListCustomerInvoicesRequest struct {
+	Status string `schema:"status"`
+}

+ 2 - 7
dashboard/src/lib/billing/types.tsx

@@ -54,14 +54,9 @@ export const CreditGrantsValidator = z.object({
 export type InvoiceList = Invoice[];
 export type Invoice = z.infer<typeof InvoiceValidator>;
 export const InvoiceValidator = z.object({
-  id: z.string(),
-  customer_id: z.string(),
-  credit_type: z.string(),
-  start_timestamp: z.string(),
-  end_timestamp: z.string(),
+  hosted_invoice_url: z.string(),
   status: z.string(),
-  total: z.number(),
-  type: z.string(),
+  created: z.string(),
 });
 
 export const ClientSecretResponse = z.string();

+ 1 - 1
dashboard/src/lib/hooks/useStripe.tsx

@@ -437,7 +437,7 @@ export const useCustomerInvoices = (): TGetInvoices => {
         const res = await api.getCustomerInvoices(
           "<token>",
           {
-            status: "FINALIZED",
+            status: "paid",
           },
           { project_id: currentProject.id }
         );

+ 0 - 22
internal/billing/metronome.go

@@ -355,28 +355,6 @@ func (m MetronomeClient) ListCustomerUsage(ctx context.Context, customerID uuid.
 	return usage, nil
 }
 
-// ListCustomerInvoices will return the invoices for a customer for the given status and time range
-func (m MetronomeClient) ListCustomerInvoices(ctx context.Context, customerID uuid.UUID, status string) (invoices []types.Invoice, err error) {
-	ctx, span := telemetry.NewSpan(ctx, "list-customer-invoices")
-	defer span.End()
-
-	if customerID == uuid.Nil {
-		return invoices, telemetry.Error(ctx, span, err, "customer id empty")
-	}
-
-	path := fmt.Sprintf("customers/%s/invoices", customerID)
-	var result struct {
-		Data []types.Invoice `json:"data"`
-	}
-
-	_, err = m.do(http.MethodGet, path, nil, &result)
-	if err != nil {
-		return invoices, telemetry.Error(ctx, span, err, "failed to list customer invoices")
-	}
-
-	return result.Data, nil
-}
-
 // IngestEvents sends a list of billing events to Metronome's ingest endpoint
 func (m MetronomeClient) IngestEvents(ctx context.Context, events []types.BillingEvent) (err error) {
 	ctx, span := telemetry.NewSpan(ctx, "ingets-billing-events")

+ 20 - 15
internal/billing/stripe.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"strconv"
+	"time"
 
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/telemetry"
@@ -244,33 +245,37 @@ func (s StripeClient) GetPublishableKey(ctx context.Context) (key string) {
 	return s.PublishableKey
 }
 
-// PopulateInvoiceURLs will populate the invoice hosted URL for each invoice
-func (s StripeClient) PopulateInvoiceURLs(ctx context.Context, invoices []types.Invoice) (invoiceList []types.Invoice, err error) {
+func (s StripeClient) ListCustomerInvoices(ctx context.Context, customerID string, status string) (invoiceList []types.Invoice, err error) {
 	ctx, span := telemetry.NewSpan(ctx, "populate-invoice-urls")
 	defer span.End()
 
-	if len(invoices) == 0 {
-		return invoiceList, nil
+	if customerID == "" {
+		return invoiceList, telemetry.Error(ctx, span, err, "customer id cannot be empty")
 	}
 
 	stripe.Key = s.SecretKey
 
-	for _, usageInvoice := range invoices {
-		if usageInvoice.ExternalInvoice.InvoiceID == "" {
-			continue
-		}
+	params := &stripe.InvoiceListParams{
+		Customer: stripe.String(customerID),
+		Status:   stripe.String(status),
+	}
+
+	result := invoice.List(params)
 
-		if usageInvoice.ExternalInvoice.ExternalStatus == "SKIPPED" {
+	for result.Next() {
+		invoice := result.Current().(*stripe.Invoice)
+
+		if invoice == nil {
 			continue
 		}
 
-		stripeInvoice, err := invoice.Get(usageInvoice.ExternalInvoice.InvoiceID, &stripe.InvoiceParams{})
-		if err != nil {
-			return invoiceList, telemetry.Error(ctx, span, err, "failed to get Stripe invoice")
-		}
+		createdTimestamp := time.Unix(invoice.Created, 0)
 
-		usageInvoice.ExternalInvoice.ExternalURL = stripeInvoice.HostedInvoiceURL
-		invoiceList = append(invoiceList, usageInvoice)
+		invoiceList = append(invoiceList, types.Invoice{
+			HostedInvoiceURL: invoice.HostedInvoiceURL,
+			Status:           string(invoice.Status),
+			Created:          createdTimestamp.Format(time.RFC3339),
+		})
 	}
 
 	return invoiceList, nil