0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

License System Day 14: 階層型プランの設計

Last updated at Posted at 2025-12-13

🎄 科学と神々株式会社アドベントカレンダー 2025

License System Day 14: 階層型プランの設計


📖 今日のテーマ

今日は、階層型プラン設計を学びます。

Freemiumビジネスモデルを実現するための3段階プラン(Free / Premium / Enterprise)の設計と実装方法を解説します。


🎯 プラン設計の目的

1. ビジネスモデルの実現

┌─────────────────────────────────────┐
│  Freemiumモデル                      │
├─────────────────────────────────────┤
│                                     │
│  Free Tier (フリーミアム)           │
│  └─ ユーザー獲得の入口               │
│                                     │
│  ↓ アップグレード誘導                │
│                                     │
│  Premium Tier (収益の柱)            │
│  └─ メイン顧客層                    │
│                                     │
│  ↓ エンタープライズ提案              │
│                                     │
│  Enterprise Tier (高収益)           │
│  └─ 大口顧客                        │
│                                     │
└─────────────────────────────────────┘

2. ユーザー動線の設計

新規ユーザー
  │
  ├─ Free プラン登録(無料)
  │    └─ 基本機能を体験
  │         │
  │         ├─ 満足して継続利用
  │         │
  │         └─ 制限に到達
  │              └─ Premium検討
  │
  ├─ Premium 契約($9.99/月)
  │    └─ 全機能利用
  │         │
  │         └─ 事業成長
  │              └─ Enterprise検討
  │
  └─ Enterprise 契約($99.99/月)
       └─ カスタマイズ対応

📊 3段階プラン詳細設計

Free プラン(無料)

目的: ユーザー獲得・製品体験

# shared/types.nim
const FreePlan* = PlanConfig(
  name: "Free",
  price: 0.0,

  # レート制限
  maxRequestsPerHour: 10,
  maxRequestsPerDay: 50,

  # データ制限
  maxMessageLength: 100,         # 100文字まで
  maxDataStorageMB: 10,          # 10MBまで

  # 機能制限
  features: {
    fBasicEcho,                  # 基本エコーのみ
  },

  # サポート
  support: "Community forum only",
  responseTimeSLA: "No SLA",

  # その他
  ads: true,                     # 広告表示あり
  watermark: true,               # ウォーターマーク表示
  priorityQueue: false           # 優先処理なし
)

制限の理由:

  • システムリソース保護
  • Premium誘導
  • 悪用防止

Premium プラン($9.99/月)

目的: メイン収益源・一般ユーザー

const PremiumPlan* = PlanConfig(
  name: "Premium",
  price: 9.99,
  billingCycle: bcMonthly,

  # レート制限
  maxRequestsPerHour: 1000,
  maxRequestsPerDay: 10_000,

  # データ制限
  maxMessageLength: 10_000,      # 10,000文字まで
  maxDataStorageMB: 1000,        # 1GBまで

  # 全機能利用可能
  features: {
    fBasicEcho,
    fAdvancedEcho,               # 大文字変換など
    fBulkOperations,             # 一括処理
    fCustomFilters,              # カスタムフィルタ
    fAPIAccess,                  # API直接利用
  },

  # サポート
  support: "Email support",
  responseTimeSLA: "24 hours",

  # その他
  ads: false,                    # 広告なし
  watermark: false,              # ウォーターマークなし
  priorityQueue: true,           # 優先処理あり

  # トライアル
  trialDays: 14                  # 14日間無料トライアル
)

ターゲット:

  • 個人ユーザー
  • フリーランス
  • 小規模チーム

Enterprise プラン($99.99/月)

目的: 高収益・大口顧客

const EnterprisePlan* = PlanConfig(
  name: "Enterprise",
  price: 99.99,
  billingCycle: bcMonthly,
  customPricing: true,           # カスタム価格対応

  # レート制限(実質無制限)
  maxRequestsPerHour: int.high,
  maxRequestsPerDay: int.high,

  # データ制限(実質無制限)
  maxMessageLength: 1_000_000,   # 1MB
  maxDataStorageMB: 100_000,     # 100GB

  # 全機能 + Enterprise限定機能
  features: {
    fBasicEcho,
    fAdvancedEcho,
    fBulkOperations,
    fCustomFilters,
    fAPIAccess,
    fPrioritySupport,            # Enterprise限定
    fSLA,                        # SLA保証
    fDedicatedAccount,           # 専任担当者
    fCustomIntegration,          # カスタム統合
    fOnPremiseDeployment,        # オンプレミス対応
  },

  # サポート
  support: "24/7 phone & email",
  responseTimeSLA: "1 hour",
  dedicatedAccountManager: true,

  # その他
  ads: false,
  watermark: false,
  priorityQueue: true,
  customBranding: true,          # カスタムブランディング
  whiteLabel: true               # ホワイトラベル
)

ターゲット:

  • 大企業
  • 政府機関
  • 大規模プロジェクト

💻 Nim実装: プラン管理システム

型定義

# shared/types.nim
import std/[sets, times]

type
  PlanType* = enum
    ptFree = "free"
    ptPremium = "premium_monthly"
    ptEnterprise = "enterprise_monthly"

  BillingCycle* = enum
    bcMonthly = "monthly"
    bcYearly = "yearly"
    bcCustom = "custom"

  Feature* = enum
    # 基本機能
    fBasicEcho
    fAdvancedEcho
    fBulkOperations
    fCustomFilters
    fAPIAccess

    # Enterprise限定
    fPrioritySupport
    fSLA
    fDedicatedAccount
    fCustomIntegration
    fOnPremiseDeployment

  PlanConfig* = object
    name*: string
    price*: float
    billingCycle*: BillingCycle
    customPricing*: bool

    # レート制限
    maxRequestsPerHour*: int
    maxRequestsPerDay*: int

    # データ制限
    maxMessageLength*: int
    maxDataStorageMB*: int

    # 機能セット
    features*: set[Feature]

    # サポート
    support*: string
    responseTimeSLA*: string
    dedicatedAccountManager*: bool

    # その他
    ads*: bool
    watermark*: bool
    priorityQueue*: bool
    customBranding*: bool
    whiteLabel*: bool
    trialDays*: int

proc getPlanConfig*(planType: PlanType): PlanConfig =
  case planType
  of ptFree: FreePlan
  of ptPremium: PremiumPlan
  of ptEnterprise: EnterprisePlan

proc hasFeature*(plan: PlanConfig, feature: Feature): bool =
  feature in plan.features

プラン比較機能

# server/src/plan_manager.nim
import ../../shared/types
import std/[json, tables]

proc comparePlans*(): JsonNode =
  ## プラン比較表をJSON形式で返す
  result = %*{
    "plans": []
  }

  for planType in [ptFree, ptPremium, ptEnterprise]:
    let config = getPlanConfig(planType)

    result["plans"].add(%*{
      "name": config.name,
      "price": config.price,
      "billing": $config.billingCycle,
      "limits": {
        "requests_per_hour": config.maxRequestsPerHour,
        "requests_per_day": config.maxRequestsPerDay,
        "message_length": config.maxMessageLength,
        "storage_mb": config.maxDataStorageMB
      },
      "features": {
        "basic_echo": config.hasFeature(fBasicEcho),
        "advanced_echo": config.hasFeature(fAdvancedEcho),
        "bulk_operations": config.hasFeature(fBulkOperations),
        "custom_filters": config.hasFeature(fCustomFilters),
        "api_access": config.hasFeature(fAPIAccess),
        "priority_support": config.hasFeature(fPrioritySupport),
        "sla": config.hasFeature(fSLA),
        "dedicated_account": config.hasFeature(fDedicatedAccount),
        "custom_integration": config.hasFeature(fCustomIntegration),
        "on_premise": config.hasFeature(fOnPremiseDeployment)
      },
      "support": {
        "type": config.support,
        "sla": config.responseTimeSLA,
        "dedicated_manager": config.dedicatedAccountManager
      },
      "extras": {
        "ads": config.ads,
        "watermark": config.watermark,
        "priority_queue": config.priorityQueue,
        "custom_branding": config.customBranding,
        "white_label": config.whiteLabel
      },
      "trial_days": config.trialDays
    })

proc canUpgrade*(currentPlan, targetPlan: PlanType): bool =
  ## アップグレード可能かチェック
  let currentLevel = ord(currentPlan)
  let targetLevel = ord(targetPlan)
  result = targetLevel > currentLevel

proc getUpgradePath*(currentPlan: PlanType): seq[PlanType] =
  ## 可能なアップグレードパスを取得
  result = @[]
  case currentPlan
  of ptFree:
    result = @[ptPremium, ptEnterprise]
  of ptPremium:
    result = @[ptEnterprise]
  of ptEnterprise:
    result = @[]  # すでに最上位

プラン選択API

# server/src/main.nim
import jester

routes:
  get "/api/v1/plans":
    ## プラン一覧取得
    let plans = comparePlans()
    resp Http200, plans

  get "/api/v1/plans/compare":
    ## プラン比較表
    let comparison = %*{
      "comparison": [
        {
          "feature": "Requests per hour",
          "free": 10,
          "premium": 1000,
          "enterprise": "Unlimited"
        },
        {
          "feature": "Message length",
          "free": "100 chars",
          "premium": "10K chars",
          "enterprise": "1M chars"
        },
        {
          "feature": "Support",
          "free": "Community",
          "premium": "Email (24h)",
          "enterprise": "24/7 Phone (1h)"
        },
        {
          "feature": "Ads",
          "free": true,
          "premium": false,
          "enterprise": false
        }
      ]
    }
    resp Http200, comparison

  post "/api/v1/subscription/upgrade":
    ## プランアップグレード
    let body = parseJson(request.body)
    let token = request.headers["X-Activation-Key"]
    let targetPlan = parseEnum[PlanType](body["target_plan"].getStr())

    # JWT検証
    let claims = cryptoService.verifyJWT(token)
    if claims.isNone:
      resp Http401, %*{"error": "Invalid token"}

    let userId = claims.get["user_id"].getStr()
    let currentPlan = parseEnum[PlanType](claims.get["plan_type"].getStr())

    # アップグレード可能かチェック
    if not canUpgrade(currentPlan, targetPlan):
      resp Http400, %*{"error": "Invalid upgrade path"}

    # サブスクリプション更新
    let success = db.upgradeSubscription(userId, targetPlan)
    if not success:
      resp Http500, %*{"error": "Upgrade failed"}

    # 新しいJWT発行
    let newToken = cryptoService.generateJWT(
      userId,
      claims.get["email"].getStr(),
      $targetPlan
    )

    resp Http200, %*{
      "message": "Upgrade successful",
      "new_plan": $targetPlan,
      "activation_key": newToken
    }

🎨 フィーチャーフラグ実装

フィーチャーゲート

# server/src/feature_gate.nim
import ../../shared/types

proc checkFeatureAccess*(plan: PlanConfig, feature: Feature): Result[void, string] =
  ## フィーチャーアクセスチェック
  if plan.hasFeature(feature):
    return ok()
  else:
    return err("Feature not available in " & plan.name & " plan. Please upgrade.")

proc requireFeature*(plan: PlanConfig, feature: Feature) =
  ## フィーチャー要求(例外を投げる)
  let check = plan.checkFeatureAccess(feature)
  if check.isErr:
    raise newException(FeatureNotAvailableError, check.error)

# 使用例
proc processBulkOperation(plan: PlanConfig, data: seq[string]) =
  requireFeature(plan, fBulkOperations)  # Freeプランなら例外

  # 一括処理実行
  for item in data:
    processItem(item)

💰 価格戦略

1. 価格設定の考え方

Free:      $0/月    → ユーザー獲得コスト削減
Premium:   $9.99/月  → 競合他社比較で設定
Enterprise: $99.99/月 → 価値ベース価格設定

2. 年間契約割引

const
  PremiumMonthly = 9.99
  PremiumYearly = 99.99  # 月額換算 $8.33 (16%割引)

  EnterpriseMonthly = 99.99
  EnterpriseYearly = 999.99  # 月額換算 $83.33 (16%割引)

3. ボリュームディスカウント(Enterprise)

proc calculateEnterprisePrice(users: int): float =
  ## ユーザー数に応じた価格計算
  let basePrice = 99.99

  if users <= 10:
    return basePrice
  elif users <= 50:
    return basePrice * users * 0.9  # 10%割引
  elif users <= 100:
    return basePrice * users * 0.8  # 20%割引
  else:
    return basePrice * users * 0.7  # 30%割引

🌟 まとめ

階層型プラン設計の要点:

  1. 3段階プラン

    • Free: ユーザー獲得
    • Premium: メイン収益
    • Enterprise: 高収益
  2. 段階的制限

    • レート制限
    • データ制限
    • 機能制限
  3. Nim実装

    • フィーチャーフラグ
    • プラン比較API
    • アップグレードフロー
  4. 価格戦略

    • Freemiumモデル
    • 年間契約割引
    • ボリュームディスカウント

前回: Day 13: レートリミットの実装
次回: Day 15: サーバーサイドの実装(Nim/Jester)

Happy Learning! 🎉

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?