From 08a7bf51fab550f5dd84d55ae266ca5fc39662c4 Mon Sep 17 00:00:00 2001 From: jSasaki Date: Mon, 29 Sep 2025 14:04:20 +0900 Subject: [PATCH 1/2] add-plan-setting-page --- src/routes/billingRoutes.ts | 149 ++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/src/routes/billingRoutes.ts b/src/routes/billingRoutes.ts index 50ac8bb..8cf9fd7 100644 --- a/src/routes/billingRoutes.ts +++ b/src/routes/billingRoutes.ts @@ -400,4 +400,153 @@ router.post( } } ); + +/** + * GET /pricing_plans + * 料金プラン一覧を取得 + */ +router.get("/pricing_plans", async (req: Request, res: Response) => { + try { + const userInfo = req.userInfo; + if (!userInfo) { + return res.status(401).json({ detail: "No user" }); + } + + const pricingCli = new PricingClient(); + const plans = (await pricingCli.pricingPlansApi.getPricingPlans()).data; + res.json(plans.pricing_plans); + } catch (error) { + console.error(error); + res.status(500).json({ detail: "Internal server error" }); + } +}); + +/** + * GET /tax_rates + * 税率一覧を取得 + */ +router.get("/tax_rates", async (req: Request, res: Response) => { + try { + const userInfo = req.userInfo; + if (!userInfo) { + return res.status(401).json({ detail: "No user" }); + } + + const pricingCli = new PricingClient(); + const taxRates = (await pricingCli.taxRateApi.getTaxRates()).data; + res.json(taxRates.tax_rates); + } catch (error) { + console.error(error); + res.status(500).json({ detail: "Internal server error" }); + } +}); + +/** + * GET /tenants/:tenant_id/plan + * テナントプラン情報を取得 + */ +router.get("/tenants/:tenant_id/plan", async (req: Request, res: Response) => { + try { + const tenantId = req.params.tenant_id; + if (!tenantId) { + return res.status(400).json({ error: "tenant_id is required" }); + } + + const userInfo = req.userInfo; + if (!userInfo) { + return res.status(401).json({ error: "Internal server error" }); + } + + // 管理者権限チェック + if (!hasBillingAccess(userInfo, tenantId)) { + return res.status(403).json({ error: "Insufficient permissions" }); + } + + const authCli = new AuthClient(); + const tenant = (await authCli.tenantApi.getTenant(tenantId)).data; + + // 現在のプランの税率情報を取得(プラン履歴の最新エントリから) + let currentTaxRateId: string | null = null; + if (tenant.plan_histories && tenant.plan_histories.length > 0) { + const latestPlanHistory = tenant.plan_histories[tenant.plan_histories.length - 1]; + if (latestPlanHistory.tax_rate_id) { + currentTaxRateId = latestPlanHistory.tax_rate_id; + } + } + + // レスポンスを構築 + const response: any = { + id: tenant.id, + name: tenant.name, + plan_id: tenant.plan_id, + tax_rate_id: currentTaxRateId, + plan_reservation: null, + }; + + // 予約情報がある場合は追加(通常の予約または解除予約) + if (tenant.using_next_plan_from) { + const planReservation = { + next_plan_id: tenant.next_plan_id, + using_next_plan_from: tenant.using_next_plan_from, + next_plan_tax_rate_id: tenant.next_plan_tax_rate_id, + }; + response.plan_reservation = planReservation; + } + + res.json(response); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to retrieve tenant detail" }); + } +}); + +/** + * PUT /tenants/:tenant_id/plan + * テナントプランを更新 + */ +router.put("/tenants/:tenant_id/plan", async (req: Request, res: Response) => { + try { + const tenantId = req.params.tenant_id; + if (!tenantId) { + return res.status(400).json({ error: "tenant_id is required" }); + } + + const { next_plan_id, tax_rate_id, using_next_plan_from } = req.body; + + const userInfo = req.userInfo; + if (!userInfo) { + return res.status(401).json({ error: "Internal server error" }); + } + + // 管理者権限チェック + if (!hasBillingAccess(userInfo, tenantId)) { + return res.status(403).json({ error: "Insufficient permissions" }); + } + + const authCli = new AuthClient(); + + // テナントプランを更新 + const updateTenantPlanParam: any = { + next_plan_id: next_plan_id, + }; + + // 税率IDが指定されている場合のみ設定 + if (tax_rate_id && tax_rate_id !== "") { + updateTenantPlanParam.next_plan_tax_rate_id = tax_rate_id; + } + + // using_next_plan_fromが指定されている場合のみ設定 + if (using_next_plan_from && using_next_plan_from > 0) { + updateTenantPlanParam.using_next_plan_from = using_next_plan_from; + } + + await authCli.tenantApi.updateTenantPlan(tenantId, updateTenantPlanParam); + + res.json({ message: "Tenant plan updated successfully" }); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to update tenant plan" }); + } +}); + export default router; From 43d15992f188aa15a5a8dbf256147f40090c89cb Mon Sep 17 00:00:00 2001 From: jSasaki Date: Thu, 2 Oct 2025 18:23:22 +0900 Subject: [PATCH 2/2] replace any types with proper interfaces in billing routes --- src/routes/billingRoutes.ts | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/routes/billingRoutes.ts b/src/routes/billingRoutes.ts index 8cf9fd7..0e0a8a4 100644 --- a/src/routes/billingRoutes.ts +++ b/src/routes/billingRoutes.ts @@ -2,6 +2,25 @@ import { Router, Request, Response, NextFunction } from "express"; import { AuthClient, AuthMiddleware, PricingClient } from "saasus-sdk"; import type { PricingPlan } from "saasus-sdk/dist/generated/Pricing"; +// --- 型定義 ----------------------------------------------------------- +interface TenantPlanResponse { + id: string; + name: string; + plan_id: string | undefined; + tax_rate_id: string | null; + plan_reservation: { + next_plan_id: string | undefined; + using_next_plan_from: number; + next_plan_tax_rate_id: string | undefined; + } | null; +} + +interface UpdateTenantPlanParam { + next_plan_id: string; + next_plan_tax_rate_id?: string; + using_next_plan_from?: number; +} + const router = Router(); router.use(AuthMiddleware); @@ -454,7 +473,7 @@ router.get("/tenants/:tenant_id/plan", async (req: Request, res: Response) => { const userInfo = req.userInfo; if (!userInfo) { - return res.status(401).json({ error: "Internal server error" }); + return res.status(401).json({ error: "Unauthorized" }); } // 管理者権限チェック @@ -475,7 +494,7 @@ router.get("/tenants/:tenant_id/plan", async (req: Request, res: Response) => { } // レスポンスを構築 - const response: any = { + const response: TenantPlanResponse = { id: tenant.id, name: tenant.name, plan_id: tenant.plan_id, @@ -515,7 +534,7 @@ router.put("/tenants/:tenant_id/plan", async (req: Request, res: Response) => { const userInfo = req.userInfo; if (!userInfo) { - return res.status(401).json({ error: "Internal server error" }); + return res.status(401).json({ error: "Unauthorized" }); } // 管理者権限チェック @@ -526,7 +545,7 @@ router.put("/tenants/:tenant_id/plan", async (req: Request, res: Response) => { const authCli = new AuthClient(); // テナントプランを更新 - const updateTenantPlanParam: any = { + const updateTenantPlanParam: UpdateTenantPlanParam = { next_plan_id: next_plan_id, };