Remove unused cloud invoice endpoints (#39523)

* Remove unused cloud invoice endpoints

Remove GET /api/v4/cloud/subscription/invoices and
/invoices/{invoice_id}/pdf, their CloudInterface methods and the
Client4.GetInvoicesForSubscription method. Mark the invoice model types
as deprecated since server/public is a published module.

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

* Clarify deprecation notes on cloud invoice model types

Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
authored and GitHub committed 2026-10-30 12:20:38 -04:00
1 parent a29df1bb0f
commit 4df6bbc764
10 files changed
+19 -298

No files matched your search

-63
View File
@@ -215,69 +215,6 @@
$ref: "#/components/responses/Forbidden"
"501":
$ref: "#/components/responses/NotImplemented"
/api/v4/cloud/subscription/invoices:
get:
tags:
- cloud
summary: Get cloud subscription invoices
description: >
Retrieves the invoices for the subscription bound to this installation.
##### Permissions
Must have `manage_system` permission and be licensed for Cloud.
__Minimum server version__: 5.30
__Note:__ This is intended for internal use and is subject to change.
operationId: GetInvoicesForSubscription
responses:
"200":
description: Subscription invoices returned successfully
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Invoice"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"501":
$ref: "#/components/responses/NotImplemented"
/api/v4/cloud/subscription/invoices/{invoice_id}/pdf:
get:
tags:
- cloud
summary: Get cloud invoice PDF
description: >
Retrieves the PDF for the invoice passed as parameter
##### Permissions
Must have `manage_system` permission and be licensed for Cloud.
__Minimum server version__: 5.30
__Note:__ This is intended for internal use and is subject to change.
operationId: GetInvoiceForSubscriptionAsPdf
parameters:
- name: invoice_id
in: path
description: Invoice ID
required: true
schema:
type: string
responses:
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"501":
$ref: "#/components/responses/NotImplemented"
/api/v4/hosted_customer/signup_available:
get:
tags:
-50
View File
@@ -3676,56 +3676,6 @@ components:
type: integer
is_paid_tier:
type: string
Invoice:
type: object
properties:
id:
type: string
number:
type: string
create_at:
type: integer
format: int64
total:
type: integer
format: int64
tax:
type: integer
format: int64
status:
type: string
period_start:
type: integer
format: int64
period_end:
type: integer
format: int64
subscription_id:
type: string
item:
type: array
items:
$ref: "#/components/schemas/InvoiceLineItem"
InvoiceLineItem:
type: object
properties:
price_id:
type: string
total:
type: integer
format: int64
quantity:
type: integer
format: int64
price_per_unit:
type: integer
format: int64
description:
type: string
metadata:
type: array
items:
type: string
Group:
type: object
properties:
-79
View File
@@ -4,16 +4,12 @@
package api4
import (
"bytes"
"encoding/binary"
"encoding/json"
"io"
"net/http"
"time"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/v8/platform/shared/web"
)
func (api *API) InitCloud() {
@@ -27,8 +23,6 @@ func (api *API) InitCloud() {
// GET /api/v4/cloud/subscription
api.BaseRoutes.Cloud.Handle("/subscription", api.APISessionRequired(getSubscription)).Methods(http.MethodGet)
api.BaseRoutes.Cloud.Handle("/subscription/invoices", api.APISessionRequired(getInvoicesForSubscription)).Methods(http.MethodGet)
api.BaseRoutes.Cloud.Handle("/subscription/invoices/{invoice_id:[_A-Za-z0-9]+}/pdf", api.APISessionRequiredGzip(getSubscriptionInvoicePDF)).Methods(http.MethodGet)
// GET /api/v4/cloud/validate-business-email
api.BaseRoutes.Cloud.Handle("/validate-business-email", api.APISessionRequired(validateBusinessEmail)).Methods(http.MethodPost)
@@ -372,79 +366,6 @@ func getInstallation(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
func getInvoicesForSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
ensured := ensureCloudInterface(c, "Api4.getInvoicesForSubscription")
if !ensured {
return
}
if !c.App.Channels().License().IsCloud() {
c.Err = model.NewAppError("Api4.getInvoicesForSubscription", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleReadBilling) {
c.SetPermissionError(model.PermissionSysconsoleReadBilling)
return
}
invoices, appErr := c.App.Cloud().GetInvoicesForSubscription(c.AppContext.Session().UserId)
if appErr != nil {
c.Err = model.NewAppError("Api4.getInvoicesForSubscription", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
return
}
json, err := json.Marshal(invoices)
if err != nil {
c.Err = model.NewAppError("Api4.getInvoicesForSubscription", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
return
}
if _, err := w.Write(json); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
}
func getSubscriptionInvoicePDF(c *Context, w http.ResponseWriter, r *http.Request) {
ensured := ensureCloudInterface(c, "Api4.getSubscriptionInvoicePDF")
if !ensured {
return
}
if !c.App.Channels().License().IsCloud() {
c.Err = model.NewAppError("Api4.getSubscriptionInvoicePDF", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
c.RequireInvoiceId()
if c.Err != nil {
return
}
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleReadBilling) {
c.SetPermissionError(model.PermissionSysconsoleReadBilling)
return
}
pdfData, filename, appErr := c.App.Cloud().GetInvoicePDF(c.AppContext.Session().UserId, c.Params.InvoiceId)
if appErr != nil {
c.Err = model.NewAppError("Api4.getSubscriptionInvoicePDF", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
return
}
web.WriteFileResponse(
filename,
"application/pdf",
int64(binary.Size(pdfData)),
time.Now(),
*c.App.Config().ServiceSettings.WebserverMode,
bytes.NewReader(pdfData),
false,
w,
r,
)
}
func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
ensured := ensureCloudInterface(c, "Api4.handleCWSWebhook")
if !ensured {
+1 -1
View File
@@ -79,7 +79,7 @@ func (api *API) APISessionRequired(h handlerFunc, opts ...APIHandlerOption) http
}
// APISessionRequiredGzip is APISessionRequired without brotli: large file downloads
// (e.g. log files, invoice PDFs) compress about as well under gzip as under brotli,
// (e.g. log files) compress about as well under gzip as under brotli,
// and brotli's memory footprint scales with response size, so these stay on gzip
// rather than paying that cost.
func (api *API) APISessionRequiredGzip(h handlerFunc, opts ...APIHandlerOption) http.Handler {
-12
View File
@@ -790,18 +790,6 @@ 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.Params.InvoiceId != model.UpcomingInvoice {
c.SetInvalidURLParam("invoice_id")
}
return c
}
func (c *Context) RequireContentReviewerId() *Context {
if c.Err != nil {
return c
-4
View File
@@ -121,9 +121,6 @@ type Params struct {
// Views
ViewId string
// Cloud
InvoiceId string
// Custom Profile Attributes
FieldId string
@@ -191,7 +188,6 @@ func ParamsFromRequest(r *http.Request) *Params {
params.SchemeId = props["scheme_id"]
params.GroupId = props["group_id"]
params.RemoteId = props["remote_id"]
params.InvoiceId = props["invoice_id"]
params.OutgoingOAuthConnectionID = props["outgoing_oauth_connection_id"]
params.ExcludeOffline, _ = strconv.ParseBool(query.Get("exclude_offline"))
params.InChannel = query.Get("in_channel")
-2
View File
@@ -18,8 +18,6 @@ type CloudInterface interface {
GetCloudCustomer(userID string) (*model.CloudCustomer, error)
GetSubscription(userID string) (*model.Subscription, error)
GetInvoicesForSubscription(userID string) ([]*model.Invoice, error)
GetInvoicePDF(userID, invoiceID string) ([]byte, string, error)
ChangeSubscription(userID, subscriptionID string, subscriptionChange *model.SubscriptionChange) (*model.Subscription, error)
@@ -322,73 +322,6 @@ func (_m *CloudInterface) GetInstallation(userID string) (*model.Installation, e
return r0, r1
}
// GetInvoicePDF provides a mock function with given fields: userID, invoiceID
func (_m *CloudInterface) GetInvoicePDF(userID string, invoiceID string) ([]byte, string, error) {
ret := _m.Called(userID, invoiceID)
if len(ret) == 0 {
panic("no return value specified for GetInvoicePDF")
}
var r0 []byte
var r1 string
var r2 error
if rf, ok := ret.Get(0).(func(string, string) ([]byte, string, error)); ok {
return rf(userID, invoiceID)
}
if rf, ok := ret.Get(0).(func(string, string) []byte); ok {
r0 = rf(userID, invoiceID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(string, string) string); ok {
r1 = rf(userID, invoiceID)
} else {
r1 = ret.Get(1).(string)
}
if rf, ok := ret.Get(2).(func(string, string) error); ok {
r2 = rf(userID, invoiceID)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// GetInvoicesForSubscription provides a mock function with given fields: userID
func (_m *CloudInterface) GetInvoicesForSubscription(userID string) ([]*model.Invoice, error) {
ret := _m.Called(userID)
if len(ret) == 0 {
panic("no return value specified for GetInvoicesForSubscription")
}
var r0 []*model.Invoice
var r1 error
if rf, ok := ret.Get(0).(func(string) ([]*model.Invoice, error)); ok {
return rf(userID)
}
if rf, ok := ret.Get(0).(func(string) []*model.Invoice); ok {
r0 = rf(userID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Invoice)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(userID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetSelfHostedProducts provides a mock function with given fields: userID
func (_m *CloudInterface) GetSelfHostedProducts(userID string) ([]*model.Product, error) {
ret := _m.Called(userID)
-9
View File
@@ -7478,15 +7478,6 @@ func (c *Client4) GetSubscription(ctx context.Context) (*Subscription, *Response
return DecodeJSONFromResponse[*Subscription](r)
}
func (c *Client4) GetInvoicesForSubscription(ctx context.Context) ([]*Invoice, *Response, error) {
r, err := c.doAPIGet(ctx, c.cloudRoute().Join("subscription", "invoices"), "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
return DecodeJSONFromResponse[[]*Invoice](r)
}
func (c *Client4) ListImports(ctx context.Context) ([]string, *Response, error) {
r, err := c.doAPIGet(ctx, c.importsRoute(), "")
if err != nil {
+18 -11
View File
@@ -18,6 +18,7 @@ const (
EventTypeTriggerDelinquencyEmail = "trigger-delinquency-email"
)
// Deprecated: The cloud subscription invoice endpoints have been removed.
const UpcomingInvoice = "upcoming"
var MockCWS string
@@ -170,17 +171,19 @@ type PaymentMethod struct {
// Subscription model represents a subscription on the system.
type Subscription struct {
ID string `json:"id"`
CustomerID string `json:"customer_id"`
ProductID string `json:"product_id"`
AddOns []string `json:"add_ons"`
StartAt int64 `json:"start_at"`
EndAt int64 `json:"end_at"`
CreateAt int64 `json:"create_at"`
Seats int `json:"seats"`
Status string `json:"status"`
DNS string `json:"dns"`
LastInvoice *Invoice `json:"last_invoice"`
ID string `json:"id"`
CustomerID string `json:"customer_id"`
ProductID string `json:"product_id"`
AddOns []string `json:"add_ons"`
StartAt int64 `json:"start_at"`
EndAt int64 `json:"end_at"`
CreateAt int64 `json:"create_at"`
Seats int `json:"seats"`
Status string `json:"status"`
DNS string `json:"dns"`
// Deprecated: Retained for compatibility; the cloud subscription invoice endpoints have been removed.
LastInvoice *Invoice `json:"last_invoice"`
// Deprecated: Retained for compatibility; the cloud subscription invoice endpoints have been removed.
UpcomingInvoice *Invoice `json:"upcoming_invoice"`
IsFreeTrial string `json:"is_free_trial"`
TrialEndAt int64 `json:"trial_end_at"`
@@ -227,6 +230,8 @@ func (s *Subscription) GetWorkSpaceNameFromDNS() string {
}
// Invoice model represents a cloud invoice
//
// Deprecated: The cloud subscription invoice endpoints have been removed.
type Invoice struct {
ID string `json:"id"`
Number string `json:"number"`
@@ -243,6 +248,8 @@ type Invoice struct {
}
// InvoiceLineItem model represents a cloud invoice lineitem tied to an invoice.
//
// Deprecated: The cloud subscription invoice endpoints have been removed.
type InvoiceLineItem struct {
PriceID string `json:"price_id"`
Total int64 `json:"total"`