プラン変更の実装
サンプルアプリのプラン設定変更機能を題材に、SaaSus Auth API と Pricing API を組み合わせて、テナントの料金プラン変更機能を実装する方法を解説します。
以下はプラン設定画面のスクリーンショットです。

プラン設定変更機能では以下の機能を提供します:
- 現在のプラン情報の表示
- プラン変更予約情報の表示
- プラン変更の実行
フロントエンド実装
実装例リンク
以下のリンク先に、フロントエンドの実装が含まれています。
- React:
PlanSettings.tsx
バックエンド実装
エンドポイント一覧
| 種別 | メソッド & パス | 説明 |
|---|---|---|
| 現在のプラン情報 | GET /tenants/{tenantId}/plan | テナントの現在のプラン情報と履歴を取得します。 |
| プラン一覧 | GET /pricing_plans | 全料金プランの一覧を取得します。 |
| 税率一覧 | GET /tax_rates | 利用可能な税率の一覧を取得します。 |
| プラン変更実行 | PUT /tenants/{tenantId}/plan | テナントの料金プランを変更します。 |
備考
以下のコードサンプルはバックエンドが Go を前提としています。
現在のプラン情報取得エンドポイント
実装例(履歴から現在の税率設定を取得)
- 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"})
}
// 管理者権限チェック
if !hasBillingAccess(userInfo, tenantId) {
return c.JSON(http.StatusForbidden, echo.Map{"error": "Insufficient permissions"})
}
// テナント詳細情報を取得
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"})
}
// 現在の税率設定を取得(プラン履歴の最新エントリから)
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
}
}
// レスポンスを構築
response := echo.Map{
"id": tenantDetailResp.JSON200.Id,
"name": tenantDetailResp.JSON200.Name,
"plan_id": tenantDetailResp.JSON200.PlanId,
"tax_rate_id": currentTaxRateId,
"plan_reservation": nil,
}
// 予約情報がある場合は追加
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)
}
実装例リンク
以下のリンク先に、本エンドポイントの実装が含まれています。
関数名で検索して該当箇所をご確認ください。
- Go (Echo):
getTenantPlanInfo - Python (FastAPI):
get_tenant_plan_info - Java (Spring):
getTenantPlanInfo - C# (.NET 8):
GetTenantPlanInfo - C# (.NET Framework 4.8):
GetTenantPlanInfo
プラン一覧取得エンドポイント
プラン変更セレクトボックスの実装
ユーザーが選択可能なプランをセレクトボックスで表示します。
このエンドポイントで取得したプラン一覧を使用してUIを構築します。
実装例リンク
以下のリンク先に、本エンドポイントの実装が含まれています。
関数名で検索して該当箇所をご確認ください。
- Go (Echo):
getPricingPlans - Python (FastAPI):
get_pricing_plans - Java (Spring):
getPricingPlans - C# (.NET 8):
GetPricingPlans - C# (.NET Framework 4.8):
GetPricingPlans
税率一覧取得エンドポイント
税率選択セレクトボックスの実装
プラン変更時に適用する税率をユーザーが選択できるよう、利用可能な税率一覧をセレクトボックスで表示します。
このエンドポイントで取得した税率一覧を使用してUIを構築します。
実装例リンク
以下のリンク先に、本エンドポイントの実装が含まれています。
関数 名で検索して該当箇所をご確認ください。
- Go (Echo):
getTaxRates - Python (FastAPI):
get_tax_rates - Java (Spring):
getTaxRates - C# (.NET 8):
GetTaxRates - C# (.NET Framework 4.8):
GetTaxRates
プラン変更実行エンドポイント
リクエストパラメータによる操作
同一 エンドポイント PUT /tenants/{tenantId}/plan で以下の操作が可能です:
プラン変更予約
{
"nextPlanId": "plan-id",
"taxRateId": "tax-rate-id",
"usingNextPlanFrom": 1640995200
}
プラン解約
{
"nextPlanId": "",
"usingNextPlanFrom": 1640995200
}
予約取り消し
{}
バックエンドでの処理ロジック
- 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")
}
// 管理者権限チェック
if !hasBillingAccess(userInfo, tenantId) {
return c.String(http.StatusForbidden, "Insufficient permissions")
}
// テナントプランを更新
updateTenantPlanParam := authapi.UpdateTenantPlanParam{
NextPlanId: (*authapi.Uuid)(&nextPlanId),
}
// 税率IDが指定されている場合のみ設定
if taxRateId != nil && *taxRateId != "" {
updateTenantPlanParam.NextPlanTaxRateId = (*authapi.Uuid)(taxRateId)
}
// using_next_plan_fromが指定されている場合のみ設定
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"})
}
// レスポンスのステータスコードをチェック
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"})
}
実装例リンク
以下のリンク先に、本エンドポイントの実装が含まれています。
関数名で検索して該当箇所をご確認ください。
- Go (Echo):
updateTenantPlan - Python (FastAPI):
update_tenant_plan - Java (Spring):
updateTenantPlan - C# (.NET 8):
UpdateTenantPlan - C# (.NET Framework 4.8):
UpdateTenantPlan