Skip to main content

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.

Backend Implementation

Endpoint Summary

TypeMethod & PathDescription
Current Plan InformationGET /tenants/{tenantId}/planRetrieves the tenant's current plan information and history.
Plan ListGET /pricing_plansRetrieves a list of all pricing plans.
Tax Rate ListGET /tax_ratesRetrieves a list of available tax rates.
Plan Change ExecutionPUT /tenants/{tenantId}/planChanges the tenant's pricing plan.
info

The following code samples assume Go for the backend.

Current Plan Information Retrieval Endpoint

Implementation Example (Retrieving Current Tax Rate Settings from History)

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.

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.

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.

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

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.