サブスクリプションモジュール

説明

サブスクリプションモジュールは、ユーザーが無料プランと様々な機能を持つ有料プランを含む、さまざまなサービスパッケージを選択し、登録することを可能にします。このモジュールはStripeと統合されており、決済処理、サブスクリプションサイクルの管理、およびその他の支払い関連タスクを処理します。

各サービスパッケージ(package)は、異なる価格と支払いサイクルを持つ複数のパッケージプラン(package_plan)を持つことができます。ユーザーは特定のパッケージプランを選択して登録し、システムはデータベースとStripe上で対応するサブスクリプションを作成します。

主な機能

  1. 無料プランの登録

    • ユーザーがリソース数の制限付きで無料サービスプランを登録できる
    • 支払い情報は不要で、登録時に自動的に確認される
    • ユースケース:基本機能でシステムを試したい新規ユーザー
  2. 有料プランの登録

    • 様々な価格と特典を持つ複数のサービスプランをサポート
    • 安全な支払い処理のためのStripe統合
    • ユーザーをStripeの支払いページに自動的にリダイレクト
    • ユースケース:製品やメンバーの制限を増やすなどの高度な機能が必要な企業
  3. Stripeからのウェブフックの処理

    • Stripeからのイベント(支払い成功、失敗など)を受信して処理
    • Stripeからの情報に基づいてシステム内のサブスクリプションステータスを更新
    • 支払い履歴とイベントの記録
    • ユースケース:ユーザーが支払いを完了すると、システムはサービスを自動的に有効化する必要がある
  4. サブスクリプション管理

    • ユーザーが現在のプラン情報を確認可能
    • サービスプラン間のアップグレード/ダウングレードをサポート
    • 支払いサイクルの変更を処理
    • ユースケース:顧客が使用制限を増やすために上位プランにアップグレードしたい
  5. サブスクリプションのキャンセル

    • ユーザーがサービスプランをキャンセルできる
    • 即時キャンセルまたは支払いサイクル終了時のキャンセルをサポート
    • 分析のためのキャンセル理由の記録
    • ユースケース:サービス利用の必要がなくなった顧客
  6. サブスクリプション履歴の追跡

    • サブスクリプションの変更と支払いの履歴を保存
    • 新規作成、アップグレード、ダウングレード、キャンセルなどのイベントの詳細を表示
    • ユースケース:管理者が支払い履歴やプラン変更を確認する必要がある
  7. 自動更新

    • 期限切れ時のサブスクリプションの自動更新をサポート
    • Stripeを通じた定期支払いの処理
    • ユースケース:顧客へのサービス中断を防止
  8. リソース制限の管理

    • 各プランはメンバー数、製品数、製品グループなどに独自の制限を持つ
    • サブスクリプションプランに基づく機能へのアクセス制御
    • ユースケース:顧客のニーズと予算に応じたサービスの階層化
  9. 支払い失敗の処理

    • 支払いが失敗した場合のユーザーへの通知
    • 支払いの再試行をサポート
    • サービスキャンセル前の猶予期間の提供
    • ユースケース:顧客のクレジットカードが期限切れまたは残高不足
  10. レポートと統計

    • プラン別のサブスクリプション数に関する情報の提供
    • 収益とコンバージョン率の追跡
    • ユースケース:ビジネスの効果と利用傾向の分析

このモジュールは、登録プロセス中のシームレスなユーザーエクスペリエンス、データベース内のサブスクリプションステータスの管理、およびStripeからのウェブフックイベントの効率的な処理を保証します。

プロセスフロー概要図

---
config:
  theme: base
  layout: dagre
  flowchart:
    curve: linear
    htmlLabels: true
  themeVariables:
    edgeLabelBackground: "transparent"
---
flowchart TD
    %% Main components
    Client[ユーザー]
    
    %% API Controller Layer
    subgraph ApiControllerLayer["API Controller Layer"]
        SubscriptionController[SubscriptionController]
        WebhookController[WebhookController]
        Middleware{Middleware}
    end
    
    %% API Service Layer
    subgraph ApiServiceLayer["API Service Layer"]
        SubscriptionService(SubscriptionService)
    end
    
    %% Business Logic Services
    subgraph BusinessServices["Business Logic Services"]
        StripeService(StripeService)
    end
    
    %% Database Layer
    subgraph DatabaseLayer["Database Layer"]
        SubscriptionDB[(subscriptions)]
        UsersDB[(users)]
        HistoryDB[(subscription_histories)]
        PackageDB[(packages)]
        PackagePlanDB[(package_plans)]
        PackagePlanToProviderDB[(package_plan_to_providers)]
    end
    
    %% External Services
    subgraph ExternalServices["External Services"]
        StripeAPI((Stripe API))
    end
    
    %% Flow Steps
    Client --- Step1[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>1</span>
            <p style='margin-top: 8px'>サービスプラン選択</p>
        </div>
    ]
    Step1 --> SubscriptionController
    
    SubscriptionController --- Step2[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>2</span>
            <p style='margin-top: 8px'>ユーザー&データ認証</p>
        </div>
    ]
    Step2 --> Middleware
    Middleware --> Step2Output[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>2'</span>
            <p style='margin-top: 8px'>認証結果</p>
        </div>
    ]
    Step2Output --> SubscriptionController
    
    SubscriptionController --- Step3[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3</span>
            <p style='margin-top: 8px'>package & package_plan情報取得</p>
        </div>
    ]
    Step3 --> PackagePlanDB
    PackagePlanDB --> PackageDB
    PackagePlanDB --> PackagePlanToProviderDB
    
    SubscriptionController --- Step3b[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3'</span>
            <p style='margin-top: 8px'>Serviceにデータ転送</p>
        </div>
    ]
    Step3b --> SubscriptionService
    
    SubscriptionService --- Step4A[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>4A</span>
            <p style='margin-top: 8px'>無料プラン権限付与</p>
        </div>
    ]
    Step4A --> SubscriptionDB
    Step4A --> HistoryDB
    Step4A --> UsersDB
    
    SubscriptionService --- Step4B[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>4B</span>
            <p style='margin-top: 8px'>有料プラン処理</p>
        </div>
    ]
    Step4B --> StripeService
    
    StripeService --- Step5[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>5</span>
            <p style='margin-top: 8px'>決済セッション作成</p>
        </div>
    ]
    Step5 --> StripeAPI
    
    StripeAPI --- Step6[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>6</span>
            <p style='margin-top: 8px'>webhookイベント送信</p>
        </div>
    ]
    Step6 --> WebhookController
    
    WebhookController --- Step7A[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>7A</span>
            <p style='margin-top: 8px'>webhook認証</p>
        </div>
    ]
    Step7A --> StripeService
    
    WebhookController --- Step7B[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>7B</span>
            <p style='margin-top: 8px'>webhookイベント処理</p>
        </div>
    ]
    Step7B --> SubscriptionService
    
    SubscriptionService --- Step8[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>8</span>
            <p style='margin-top: 8px'>サブスクリプション状態更新</p>
        </div>
    ]
    Step8 --> SubscriptionDB
    Step8 --> HistoryDB
    
    Client --- Step9[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>9</span>
            <p style='margin-top: 8px'>サブスクリプション管理</p>
        </div>
    ]
    Step9 --> SubscriptionController
    
    %% Styling
    style Client fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
    style ApiControllerLayer fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
    style ApiServiceLayer fill:#f0f8e6,stroke:#339933,stroke-width:2px
    style BusinessServices fill:#f5f0ff,stroke:#9966cc,stroke-width:2px
    style DatabaseLayer fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
    style ExternalServices fill:#fcd9d9,stroke:#cc3333,stroke-width:2px
    style Step1 fill:transparent,stroke:transparent,stroke-width:1px
    style Step2 fill:transparent,stroke:transparent,stroke-width:1px
    style Step2Output fill:transparent,stroke:transparent,stroke-width:1px
    style Step3 fill:transparent,stroke:transparent,stroke-width:1px
    style Step3b fill:transparent,stroke:transparent,stroke-width:1px
    style Step4A fill:transparent,stroke:transparent,stroke-width:1px
    style Step4B fill:transparent,stroke:transparent,stroke-width:1px
    style Step5 fill:transparent,stroke:transparent,stroke-width:1px
    style Step6 fill:transparent,stroke:transparent,stroke-width:1px
    style Step7A fill:transparent,stroke:transparent,stroke-width:1px
    style Step7B fill:transparent,stroke:transparent,stroke-width:1px
    style Step8 fill:transparent,stroke:transparent,stroke-width:1px
    style Step9 fill:transparent,stroke:transparent,stroke-width:1px

機能リスト

名前 リンク 説明
サービスプラン登録 サービスプラン登録 無料と有料プランを含む新しいサービスプラン登録プロセス
サブスクリプション管理 サブスクリプション管理 サービスプランの更新、アップグレード、ダウングレード、キャンセル
サブスクリプション履歴 サブスクリプション履歴 サブスクリプション変更の追跡と表示
Stripeウェブフック処理 ウェブフック処理 サブスクリプション状態を更新するためのStripeウェブフックイベントの処理

関連データベース

erDiagram
    users {
        bigint id PK "主キー"
        string name "ユーザー名"
        string email "ユーザーメール (unique)"
        string uid "ユーザーUID (unique)"
        string payment_provider_customer_id "Stripe顧客ID (nullable)"
        integer status "ユーザー状態"
        string remember_token "ログイン記憶トークン (nullable)"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
        timestamp deleted_at "ソフト削除日時 (nullable)"
    }
    
    packages {
        bigint id PK "主キー"
        string name "パッケージ名"
        string slug "パッケージスラッグ (unique)"
        text description "パッケージ説明 (nullable)"
        text image "画像パス (nullable)"
        integer schedule_id "スケジュールID"
        integer schedule_priority "スケジュール優先度"
        integer max_member "最大メンバー数 (nullable)"
        integer max_product_group "最大製品グループ数 (nullable)"
        integer max_product "最大製品数 (nullable)"
        integer max_category "最大カテゴリ数 (nullable)" 
        integer max_search_query "最大検索クエリ数 (nullable)"
        integer max_viewpoint "最大ビューポイント数 (nullable)"
        string data_visible "データ表示可能性"
        tinyint api_available "API利用可能性"
        tinyint status "パッケージ状態"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
    }
    
    package_plans {
        bigint id PK "主キー"
        string name "プラン名"
        string slug "プランスラッグ (unique)"
        bigint package_id FK "packagesテーブルへの関連付け"
        double amount "金額"
        string currency "通貨"
        string type "プランタイプ (recurring, one_time)"
        string billing_plan "支払いサイクル"
        tinyint status "プラン状態"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
    }
    
    package_plan_to_providers {
        bigint id PK "主キー"
        bigint package_plan_id FK "package_plansテーブルへの関連付け"
        bigint provider_id FK "payment_providersテーブルへの関連付け"
        string provider_price_id "プロバイダー価格ID (Stripe)"
        tinyint status "状態"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
    }
    
    subscriptions {
        bigint id PK "主キー"
        string slug "サブスクリプションスラッグ (nullable, unique)"
        bigint package_id FK "packagesテーブルへの関連付け"
        bigint package_plan_id FK "package_plansテーブルへの関連付け"
        bigint group_id FK "groupsテーブルへの関連付け"
        bigint user_id FK "usersテーブルへの関連付け"
        string email "ユーザーメール"
        string payment_provider_customer_id "決済プロバイダー顧客ID"
        string payment_provider_subscription_id "決済プロバイダーサブスクリプションID (nullable, unique)"
        boolean auto_renew "自動更新"
        string status "サブスクリプション状態"
        string scheduled_plan_id "予定プラン変更ID (nullable)"
        timestamp scheduled_plan_change_at "プラン変更予定日時 (nullable)"
        timestamp canceled_at "キャンセル日時 (nullable)"
        string canceled_reason "キャンセル理由 (nullable)"
        timestamp first_register_at "初回登録日時 (nullable)"
        timestamp deadline_at "期限 (nullable)"
        timestamp grace_period_end_at "猶予期間終了日時 (nullable)"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
        timestamp deleted_at "ソフト削除日時 (nullable)"
    }
    
    subscription_histories {
        bigint id PK "主キー"
        bigint subscription_id FK "subscriptionsテーブルへの関連付け"
        bigint package_id FK "packagesテーブルへの関連付け"
        bigint package_plan_id FK "package_plansテーブルへの関連付け"
        bigint group_id FK "groupsテーブルへの関連付け"
        bigint user_id FK "usersテーブルへの関連付け"
        string old_plan_id "旧プランID (nullable)"
        string payment_intent_id "Stripe支払いインテントID (nullable, unique)"
        string invoice_id "Stripe請求書ID (nullable, unique)"
        string billing_plan "支払いサイクル"
        integer payment_attempt "支払い試行回数 (nullable)"
        json payment_method_details "支払い方法詳細 (nullable)"
        string type "サブスクリプションタイプ"
        decimal amount "金額"
        string currency "通貨"
        integer schedule_id "スケジュールID"
        integer schedule_priority "スケジュール優先度"
        integer product_group_usage "使用製品グループ数"
        integer product_usage "使用製品数"
        integer category_usage "使用カテゴリ数"
        integer search_query_usage "使用検索クエリ数"
        integer max_member "最大メンバー数"
        integer max_product_group "最大製品グループ数"
        integer max_product "最大製品数"
        integer max_category "最大カテゴリ数"
        integer max_search_query "最大検索クエリ数"
        integer max_viewpoint "最大ビューポイント数"
        string data_visible "データ表示可能性"
        tinyint api_available "API利用可能性"
        tinyint status "状態"
        string payment_status "支払い状態"
        timestamp started_at "開始日時 (nullable)"
        timestamp expires_at "期限切れ日時 (nullable)"
        timestamp paid_at "支払い日時 (nullable)"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
    }
    
    stripe_webhook_events {
        bigint id PK "主キー"
        string stripe_event_id "Stripeイベント識別子"
        string request_id "リクエストID (nullable)"
        string event_type "イベントタイプ"
        enum status "状態 (pending, processing, completed, failed)"
        text error "エラー (nullable)"
        timestamp processed_at "処理日時 (nullable)"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
    }

    users ||--o{ subscriptions : has
    packages ||--o{ package_plans : has
    package_plans ||--o{ package_plan_to_providers : has
    packages ||--o{ subscriptions : associated_with
    package_plans ||--o{ subscriptions : associated_with
    package_plan_to_providers ||--o{ subscriptions : used_for
    subscriptions ||--o{ subscription_histories : has

関連APIエンドポイント

Method Endpoint Controller 説明
GET /api/general/subscription SubscriptionController@index 現在のユーザーのサブスクリプションリストを取得
POST /api/general/subscription SubscriptionController@store 新しいサービスプランを登録
GET /api/general/subscription/active SubscriptionController@getActiveSubscription 現在のサブスクリプション情報を取得
PUT /api/general/subscription/{id} SubscriptionController@update サブスクリプションプランを更新/変更
POST /api/general/subscription/free-plan SubscriptionController@registerFreePlan 無料プランを登録
GET /api/admin/group/packages PackageController@index 利用可能なサービスパッケージのリストを取得
GET /api/general/package-plan PackagePlanController@index サービスパッケージプランのリストを取得
GET /api/general/checkout CheckoutController@index 支払い情報を取得
POST /api/general/checkout CheckoutController@store 新しい支払いセッションを作成
POST /api/general/stripe/checkout/session StripeController@createCheckoutSession Stripe支払いセッションを作成
POST /api/general/stripe/portal/session StripeController@createPortalSession Stripeポータルセッションを作成
POST /api/admin/stripe/webhook WebhookController@handleWebhook Stripeからのウェブフックイベントを処理