diff --git a/api4/cloud.go b/api4/cloud.go index 86001d2232..cc8c977dbb 100644 --- a/api4/cloud.go +++ b/api4/cloud.go @@ -4,9 +4,13 @@ package api4 import ( + "bytes" + "encoding/binary" "encoding/json" + "fmt" "io/ioutil" "net/http" + "time" "github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/model" @@ -30,6 +34,8 @@ func (api *API) InitCloud() { // GET /api/v4/cloud/subscription api.BaseRoutes.Cloud.Handle("/subscription", api.ApiSessionRequired(getSubscription)).Methods("GET") + api.BaseRoutes.Cloud.Handle("/subscription/invoices", api.ApiSessionRequired(getInvoicesForSubscription)).Methods("GET") + api.BaseRoutes.Cloud.Handle("/subscription/invoices/{invoice_id:in_[A-Za-z0-9]+}/pdf", api.ApiSessionRequired(getSubscriptionInvoicePDF)).Methods("GET") } func getSubscription(c *Context, w http.ResponseWriter, r *http.Request) { @@ -254,3 +260,68 @@ func confirmCustomerPayment(c *Context, w http.ResponseWriter, r *http.Request) ReturnStatusOK(w) } + +func getInvoicesForSubscription(c *Context, w http.ResponseWriter, r *http.Request) { + if c.App.Srv().License() == nil || !*c.App.Srv().License().Features.Cloud { + c.Err = model.NewAppError("Api4.getInvoicesForSubscription", "api.cloud.license_error", nil, "", http.StatusNotImplemented) + return + } + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + invoices, appErr := c.App.Cloud().GetInvoicesForSubscription() + if appErr != nil { + c.Err = model.NewAppError("Api4.getInvoicesForSubscription", "api.cloud.request_error", nil, appErr.Error(), http.StatusInternalServerError) + return + } + + json, err := json.Marshal(invoices) + if err != nil { + c.Err = model.NewAppError("Api4.getInvoicesForSubscription", "api.cloud.app_error", nil, err.Error(), http.StatusInternalServerError) + return + } + + w.Write(json) +} + +func getSubscriptionInvoicePDF(c *Context, w http.ResponseWriter, r *http.Request) { + if c.App.Srv().License() == nil || !*c.App.Srv().License().Features.Cloud { + c.Err = model.NewAppError("Api4.getSuscriptionInvoicePDF", "api.cloud.license_error", nil, "", http.StatusNotImplemented) + return + } + + c.RequireInvoiceId() + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + pdfData, appErr := c.App.Cloud().GetInvoicePDF(c.Params.InvoiceId) + if appErr != nil { + c.Err = model.NewAppError("Api4.getSuscriptionInvoicePDF", "api.cloud.request_error", nil, appErr.Error(), http.StatusInternalServerError) + return + } + + err := writeFileResponse( + fmt.Sprintf("%s.pdf", c.Params.InvoiceId), + "application/pdf", + int64(binary.Size(pdfData)), + time.Now(), + *c.App.Config().ServiceSettings.WebserverMode, + bytes.NewReader(pdfData), + false, + w, + r, + ) + if err != nil { + c.Err = err + return + } +} diff --git a/einterfaces/cloud.go b/einterfaces/cloud.go index ed4563b25e..bf0b9f4847 100644 --- a/einterfaces/cloud.go +++ b/einterfaces/cloud.go @@ -18,4 +18,6 @@ type CloudInterface interface { UpdateCloudCustomerAddress(address *model.Address) (*model.CloudCustomer, *model.AppError) GetSubscription() (*model.Subscription, *model.AppError) + GetInvoicesForSubscription() ([]*model.Invoice, *model.AppError) + GetInvoicePDF(invoiceID string) ([]byte, *model.AppError) } diff --git a/model/client4.go b/model/client4.go index d280622d91..be0ea2b8a6 100644 --- a/model/client4.go +++ b/model/client4.go @@ -5702,6 +5702,19 @@ func (c *Client4) GetSubscription() (*Subscription, *Response) { return subscription, BuildResponse(r) } +func (c *Client4) GetInvoicesForSubscription() ([]*Invoice, *Response) { + r, appErr := c.DoApiGet(c.GetCloudRoute()+"/subscription/invoices", "") + if appErr != nil { + return nil, BuildErrorResponse(r, appErr) + } + defer closeBody(r) + + var invoices []*Invoice + json.NewDecoder(r.Body).Decode(&invoices) + + return invoices, BuildResponse(r) +} + func (c *Client4) UpdateCloudCustomer(customerInfo *CloudCustomerInfo) (*CloudCustomer, *Response) { customerBytes, _ := json.Marshal(customerInfo) diff --git a/model/cloud.go b/model/cloud.go index a137e5bbc0..0347aaa9d9 100644 --- a/model/cloud.go +++ b/model/cloud.go @@ -84,3 +84,29 @@ type Subscription struct { DNS string `json:"dns"` IsPaidTier string `json:"is_paid_tier"` } + +// Invoice model represents a cloud invoice +type Invoice struct { + ID string `json:"id"` + Number string `json:"number"` + CreateAt int64 `json:"create_at"` + Total int64 `json:"total"` + Tax int64 `json:"tax"` + Status string `json:"status"` + Description string `json:"description"` + PeriodStart int64 `json:"period_start"` + PeriodEnd int64 `json:"period_end"` + SubscriptionID string `json:"subscription_id"` + Items []*InvoiceLineItem `json:"line_items"` +} + +// InvoiceLineItem model represents a cloud invoice lineitem tied to an invoice. +type InvoiceLineItem struct { + PriceID string `json:"price_id"` + Total int64 `json:"total"` + Quantity int64 `json:"quantity"` + PricePerUnit int64 `json:"price_per_unit"` + Description string `json:"description"` + Type string `json:"type"` + Metadata map[string]interface{} `json:"metadata"` +} diff --git a/web/context.go b/web/context.go index 381e1a28f0..0927baec60 100644 --- a/web/context.go +++ b/web/context.go @@ -663,3 +663,15 @@ func (c *Context) RequireBotUserId() *Context { } return c } + +func (c *Context) RequireInvoiceId() *Context { + if c.Err != nil { + return c + } + + if len(c.Params.InvoiceId) != 27 { + c.SetInvalidUrlParam("invoice_id") + } + + return c +} diff --git a/web/params.go b/web/params.go index 7675909166..53043495bd 100644 --- a/web/params.go +++ b/web/params.go @@ -80,6 +80,9 @@ type Params struct { FilterParentTeamPermitted bool CategoryId string WarnMetricId string + + // Cloud + InvoiceId string } func ParamsFromRequest(r *http.Request) *Params { @@ -216,6 +219,10 @@ func ParamsFromRequest(r *http.Request) *Params { params.RemoteId = val } + if val, ok := props["invoice_id"]; ok { + params.InvoiceId = val + } + params.Scope = query.Get("scope") if val, err := strconv.Atoi(query.Get("page")); err != nil || val < 0 {