Self-hosted in-product purchase (#21804)
* Self-hosted admins can purchase licenses in-app when `ServiceSettings,SelfHostedPurchase` is true (the default) * Content Security Policy enables loading assets from `js.stripe.com/v3` when `ServiceSettings.SelfHostedPurchase` is true (the default). * Add `hosted_customer` API subpath * Add status of SelfHostedPurchase to telemetry config report. * Support showing admins self-hosted invoices when `ServiceSettings.SelfHostedPurchase` is true (the default) Co-authored-by: Conor Macpherson <116016004+ConorMacpherson@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
6fd174a95f
Коммит
a8fa3f29e9
@@ -8558,6 +8558,72 @@ func (c *Client4) GetNewTeamMembersSince(teamID string, timeRange string, page i
|
||||
return newTeamMembersList, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) SelfHostedSignupAvailable() (*Response, error) {
|
||||
r, err := c.DoAPIGet(c.hostedCustomerRoute()+"/signup_available", "")
|
||||
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) SelfHostedSignupCustomer(form *SelfHostedCustomerForm) (*Response, *SelfHostedSignupCustomerResponse, error) {
|
||||
payloadBytes, err := json.Marshal(form)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("SelfHostedSignupCustomer", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPost(c.hostedCustomerRoute()+"/customer", string(payloadBytes))
|
||||
|
||||
if err != nil {
|
||||
return BuildResponse(r), nil, err
|
||||
}
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return BuildResponse(r), nil, err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
response := SelfHostedSignupCustomerResponse{}
|
||||
err = json.Unmarshal(data, &response)
|
||||
if err != nil {
|
||||
return BuildResponse(r), nil, err
|
||||
}
|
||||
|
||||
return BuildResponse(r), &response, nil
|
||||
}
|
||||
|
||||
func (c *Client4) SelfHostedSignupConfirm(form *SelfHostedConfirmPaymentMethodRequest) (*Response, *SelfHostedSignupConfirmClientResponse, error) {
|
||||
payloadBytes, err := json.Marshal(form)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("SelfHostedSignupConfirm", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPost(c.hostedCustomerRoute()+"/confirm", string(payloadBytes))
|
||||
|
||||
if err != nil {
|
||||
return BuildResponse(r), nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return BuildResponse(r), nil, err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
response := SelfHostedSignupConfirmClientResponse{}
|
||||
err = json.Unmarshal(data, &response)
|
||||
if err != nil {
|
||||
return BuildResponse(r), nil, err
|
||||
}
|
||||
|
||||
defer closeBody(r)
|
||||
|
||||
return BuildResponse(r), &response, nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetPostInfo(postId string) (*PostInfo, *Response, error) {
|
||||
r, err := c.DoAPIGet(c.postRoute(postId)+"/info", "")
|
||||
if err != nil {
|
||||
|
||||
@@ -290,17 +290,14 @@ type ProductLimits struct {
|
||||
Teams *TeamsLimits `json:"teams,omitempty"`
|
||||
}
|
||||
|
||||
type BootstrapSelfHostedSignupRequest struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type BootstrapSelfHostedSignupResponse struct {
|
||||
Progress string `json:"progress"`
|
||||
}
|
||||
|
||||
type BootstrapSelfHostedSignupResponseInternal struct {
|
||||
Progress string `json:"progress"`
|
||||
License string `json:"license"`
|
||||
// CreateSubscriptionRequest is the parameters for the API request to create a subscription.
|
||||
type CreateSubscriptionRequest struct {
|
||||
ProductID string `json:"product_id"`
|
||||
AddOns []string `json:"add_ons"`
|
||||
Seats int `json:"seats"`
|
||||
Total float64 `json:"total"`
|
||||
InternalPurchaseOrder string `json:"internal_purchase_order"`
|
||||
DiscountID string `json:"discount_id"`
|
||||
}
|
||||
|
||||
func (p *Product) IsYearly() bool {
|
||||
|
||||
@@ -383,7 +383,7 @@ type ServiceSettings struct {
|
||||
CollapsedThreads *string `access:"experimental_features"`
|
||||
ManagedResourcePaths *string `access:"environment_web_server,write_restrictable,cloud_restrictable"`
|
||||
EnableCustomGroups *bool `access:"site_users_and_teams"`
|
||||
SelfHostedFirstTimePurchase *bool `access:"write_restrictable,cloud_restrictable"`
|
||||
SelfHostedPurchase *bool `access:"write_restrictable,cloud_restrictable"`
|
||||
AllowSyncedDrafts *bool `access:"site_posts"`
|
||||
}
|
||||
|
||||
@@ -854,8 +854,8 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) {
|
||||
s.AllowSyncedDrafts = NewBool(true)
|
||||
}
|
||||
|
||||
if s.SelfHostedFirstTimePurchase == nil {
|
||||
s.SelfHostedFirstTimePurchase = NewBool(false)
|
||||
if s.SelfHostedPurchase == nil {
|
||||
s.SelfHostedPurchase = NewBool(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
58
model/hosted_customer.go
Обычный файл
58
model/hosted_customer.go
Обычный файл
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
type BootstrapSelfHostedSignupRequest struct {
|
||||
Email string `json:"email"`
|
||||
Reset bool `json:"reset"`
|
||||
}
|
||||
|
||||
type BootstrapSelfHostedSignupResponse struct {
|
||||
Progress string `json:"progress"`
|
||||
}
|
||||
|
||||
type BootstrapSelfHostedSignupResponseInternal struct {
|
||||
Progress string `json:"progress"`
|
||||
License string `json:"license"`
|
||||
}
|
||||
|
||||
// email contained in token, so not in the request body.
|
||||
type SelfHostedCustomerForm struct {
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
BillingAddress *Address `json:"billing_address"`
|
||||
Organization string `json:"organization"`
|
||||
}
|
||||
|
||||
type SelfHostedConfirmPaymentMethodRequest struct {
|
||||
StripeSetupIntentID string `json:"stripe_setup_intent_id"`
|
||||
Subscription CreateSubscriptionRequest `json:"subscription"`
|
||||
}
|
||||
|
||||
// SelfHostedSignupPaymentResponse contains feels needed for self hosted signup to confirm payment and receive license.
|
||||
type SelfHostedSignupCustomerResponse struct {
|
||||
CustomerId string `json:"customer_id"`
|
||||
SetupIntentId string `json:"setup_intent_id"`
|
||||
SetupIntentSecret string `json:"setup_intent_secret"`
|
||||
Progress string `json:"progress"`
|
||||
}
|
||||
|
||||
// SelfHostedSignupConfirmResponse contains data received on successful self hosted signup
|
||||
type SelfHostedSignupConfirmResponse struct {
|
||||
License string `json:"license"`
|
||||
Progress string `json:"progress"`
|
||||
}
|
||||
|
||||
type SelfHostedSignupConfirmClientResponse struct {
|
||||
License map[string]string `json:"license"`
|
||||
Progress string `json:"progress"`
|
||||
}
|
||||
|
||||
type SelfHostedBillingAccessRequest struct {
|
||||
LicenseId string `json:"license_id"`
|
||||
}
|
||||
|
||||
type SelfHostedBillingAccessResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
@@ -81,6 +81,7 @@ const (
|
||||
WebsocketEventDraftDeleted = "draft_deleted"
|
||||
WebsocketEventAcknowledgementAdded = "post_acknowledgement_added"
|
||||
WebsocketEventAcknowledgementRemoved = "post_acknowledgement_removed"
|
||||
WebsocketEventHostedCustomerSignupProgressUpdated = "hosted_customer_signup_progress_updated"
|
||||
)
|
||||
|
||||
type WebSocketMessage interface {
|
||||
|
||||
Ссылка в новой задаче
Block a user