Plan Change Implementation
Overview
This page explains how to implement tenant pricing plan change functionality by combining SaaSus Auth API and Pricing API, using the plan change feature of the sample application as an example.
Below is a screenshot of the plan settings screen.

The plan change functionality provides the following features:
- Display current plan information
- Display plan change reservation information
- Execute plan changes
Frontend Implementation
Example Implementations
The following links point to repositories that include implementations of the frontend.
- React:
PlanSettings.tsx
Backend Implementation
Endpoint Summary
| Type | Method & Path | Description |
|---|---|---|
| Current Plan Information | GET /tenants/{tenantId}/plan | Retrieves the tenant's current plan information and history. |
| Plan List | GET /pricing_plans | Retrieves a list of all pricing plans. |
| Tax Rate List | GET /tax_rates | Retrieves a list of available tax rates. |
| Plan Change Execution | PUT /tenants/{tenantId}/plan | Changes the tenant's pricing plan. |
The following code samples assume Go for the backend.
Current Plan Information Retrieval Endpoint
Implementation Example (Retrieving Current Tax Rate Settings from History)
- Go
func getTenantPlanInfo(c echo.Context) error {
tenantId := c.Param("tenant_id")
if tenantId == "" {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "tenant_id is required"})
}
userInfo, ok := c.Get(string(ctxlib.UserInfoKey)).(*authapi.UserInfo)
if !ok {
c.Logger().Error("failed to get user info")
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "Internal server error"})
}
// Check admin permissions
if !hasBillingAccess(userInfo, tenantId) {
return c.JSON(http.StatusForbidden, echo.Map{"error": "Insufficient permissions"})
}
// Retrieve tenant detail information
tenantDetailResp, err := authClient.GetTenantWithResponse(context.Background(), authapi.TenantId(tenantId))
if err != nil {
c.Logger().Errorf("Failed to retrieve tenant detail: %v", err)
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "Failed to retrieve tenant detail"})
}
if tenantDetailResp.StatusCode() != http.StatusOK {
c.Logger().Errorf("Failed to retrieve tenant detail: status %d", tenantDetailResp.StatusCode())
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "Failed to retrieve tenant detail"})
}
if tenantDetailResp.JSON200 == nil {
return c.JSON(http.StatusNotFound, echo.Map{"error": "Tenant not found"})
}
// Get current tax rate settings (from the latest entry in plan history)
var currentTaxRateId *string
if len(tenantDetailResp.JSON200.PlanHistories) > 0 {
latestPlanHistory := tenantDetailResp.JSON200.PlanHistories[len(tenantDetailResp.JSON200.PlanHistories)-1]
if latestPlanHistory.TaxRateId != nil {
taxRateIdStr := string(*latestPlanHistory.TaxRateId)
currentTaxRateId = &taxRateIdStr
}
}
// Build response
response := echo.Map{
"id": tenantDetailResp.JSON200.Id,
"name": tenantDetailResp.JSON200.Name,
"plan_id": tenantDetailResp.JSON200.PlanId,
"tax_rate_id": currentTaxRateId,
"plan_reservation": nil,
}
// Add reservation information if available
if tenantDetailResp.JSON200.NextPlanId != nil {
planReservation := echo.Map{
"next_plan_id": *tenantDetailResp.JSON200.NextPlanId,
"using_next_plan_from": tenantDetailResp.JSON200.UsingNextPlanFrom,
"next_plan_tax_rate_id": tenantDetailResp.JSON200.NextPlanTaxRateId,
}
response["plan_reservation"] = planReservation
}
return c.JSON(http.StatusOK, response)
}
Example Implementations
The following links point to repositories that include implementations of this endpoint. Search by function name to locate the relevant code.
- Go (Echo):
getTenantPlanInfo - Python (FastAPI):
get_tenant_plan_info - Java (Spring):
getTenantPlanInfo - C# (.NET 8):
GetTenantPlanInfo - C# (.NET Framework 4.8):
GetTenantPlanInfo
Plan List Retrieval Endpoint
Plan Change Select Box Implementation
Display plans that users can select in a select box.
Use the plan list retrieved from this endpoint to build the UI.
Example Implementations
The following links point to repositories that include implementations of this endpoint. Search by function name to locate the relevant code.
- Go (Echo):
getPricingPlans - Python (FastAPI):
get_pricing_plans - Java (Spring):
getPricingPlans - C# (.NET 8):
GetPricingPlans - C# (.NET Framework 4.8):
GetPricingPlans
Tax Rate List Retrieval Endpoint
Tax Rate Selection Select Box Implementation
Display available tax rates in a select box so users can select the tax rate to apply when changing plans.
Use the tax rate list retrieved from this endpoint to build the UI.
Example Implementations
The following links point to repositories that include implementations of this endpoint. Search by function name to locate the relevant code.
- Go (Echo):
getTaxRates - Python (FastAPI):
get_tax_rates - Java (Spring):
getTaxRates - C# (.NET 8):
GetTaxRates - C# (.NET Framework 4.8):
GetTaxRates
Plan Change Execution Endpoint
Operations by Request Parameters
The following operations are possible with the same endpoint PUT /tenants/{tenantId}/plan:
Plan Change Reservation
{
"nextPlanId": "plan-id",
"taxRateId": "tax-rate-id",
"usingNextPlanFrom": 1640995200
}
Plan Cancellation
{
"nextPlanId": "",
"usingNextPlanFrom": 1640995200
}
Reservation Cancellation
{}
Backend Processing Logic
- Go
func updateTenantPlan(c echo.Context) error {
tenantId := c.Param("tenant_id")
if tenantId == "" {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "tenant_id is required"})
}
var request UpdateTenantPlanRequest
if err := c.Bind(&request); err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "Invalid request"})
}
nextPlanId := request.NextPlanId
taxRateId := request.TaxRateId
usingNextPlanFrom := request.UsingNextPlanFrom
userInfo, ok := c.Get(string(ctxlib.UserInfoKey)).(*authapi.UserInfo)
if !ok {
c.Logger().Error("failed to get user info")
return c.String(http.StatusInternalServerError, "internal server error")
}
// Check admin permissions
if !hasBillingAccess(userInfo, tenantId) {
return c.String(http.StatusForbidden, "Insufficient permissions")
}
// Update tenant plan
updateTenantPlanParam := authapi.UpdateTenantPlanParam{
NextPlanId: (*authapi.Uuid)(&nextPlanId),
}
// Set tax rate ID only if specified
if taxRateId != nil && *taxRateId != "" {
updateTenantPlanParam.NextPlanTaxRateId = (*authapi.Uuid)(taxRateId)
}
// Set using_next_plan_from only if specified
if usingNextPlanFrom != nil && *usingNextPlanFrom > 0 {
usingNextPlanFromInt := int(*usingNextPlanFrom)
updateTenantPlanParam.UsingNextPlanFrom = &usingNextPlanFromInt
}
resp, err := authClient.UpdateTenantPlanWithResponse(context.Background(), tenantId, updateTenantPlanParam)
if err != nil {
c.Logger().Errorf("failed to update tenant plan: %v", err)
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "Failed to update tenant plan"})
}
// Check response status code
if resp.StatusCode() != http.StatusOK {
c.Logger().Errorf("tenant plan update failed with status %d: %s", resp.StatusCode(), string(resp.Body))
return c.JSON(resp.StatusCode(), echo.Map{"error": "Failed to update tenant plan"})
}
return c.JSON(http.StatusOK, echo.Map{"message": "Tenant plan updated successfully"})
}
Example Implementations
The following links point to repositories that include implementations of this endpoint. Search by function name to locate the relevant code.
- Go (Echo):
updateTenantPlan - Python (FastAPI):
update_tenant_plan - Java (Spring):
updateTenantPlan - C# (.NET 8):
UpdateTenantPlan - C# (.NET Framework 4.8):
UpdateTenantPlan