初回無料プラン登録

説明

初回無料プラン登録機能は、アクティブなサブスクリプションプランを持たないユーザーまたはグループが無料サービスプランに登録できるようにします。自動登録とは異なり、システムはユーザーがログインした際に無料プランへの登録を提案するモーダルを表示します。このプロセスはStripeと統合され、顧客情報とサブスクリプションを管理し、内部データベースで正確な状態を維持します。

この機能は、新規ユーザーがスムーズにシステムの利用を開始できるようにするために特に重要であり、支払い情報を必要とせず、システムの基本機能へのアクセスをすぐに提供します。

前提条件:

  • ユーザーがシステムに正常にログインしている。
  • ユーザーに関連するグループにアクティブなサブスクリプションがない。
  • ユーザーがグループのcreator(所有者)である。

プロセスフロー図

---
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"]
        LoginController[LoginController]
        SubscriptionController[SubscriptionController]
        WebhookController[WebhookController]
    end
    
    %% API Service Layer
    subgraph ApiServiceLayer["API Service Layer"]
        AuthService(AuthService)
        SubscriptionService(SubscriptionService)
    end
    
    %% Business Logic Services
    subgraph BusinessServices["Business Logic Services"]
        StripeService(StripeService)
    end
    
    %% Database Layer
    subgraph DatabaseLayer["Database Layer"]
        UsersDB[(users)]
        GroupsDB[(groups)]
        SubscriptionDB[(subscriptions)]
        HistoryDB[(subscription_histories)]
        PackageDB[(packages)]
        PackagePlanDB[(package_plans)]
        WebhookEventsDB[(stripe_webhook_events)]
    end
    
    %% External Services
    subgraph ExternalServices["External Services"]
        StripeAPI((Stripe API))
    end
    
    %% Login Flow
    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 --> LoginController
    
    LoginController --- 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 --> AuthService
    
    AuthService --- 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'>グループとサブスクリプション確認</p>
        </div>
    ]
    Step3 --> UsersDB
    Step3 --> GroupsDB
    Step3 --> SubscriptionDB
    
    AuthService --- Step4[
        <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'>4</span>
            <p style='margin-top: 8px'>無料プランモーダル表示フラグ設定</p>
        </div>
    ]
    Step4 --> LoginController
    
    LoginController --- Step5[
        <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'>5</span>
            <p style='margin-top: 8px'>ログイン結果を返す</p>
        </div>
    ]
    Step5 --> Client
    
    %% Registration Flow
    Client --- Step6[
        <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'>6</span>
            <p style='margin-top: 8px'>無料プラン登録</p>
        </div>
    ]
    Step6 --> SubscriptionController
    
    SubscriptionController --- Step7[
        <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'>7</span>
            <p style='margin-top: 8px'>ユーザー権限確認</p>
        </div>
    ]
    Step7 --> UsersDB
    Step7 --> GroupsDB
    
    SubscriptionController --- Step8A[
        <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'>8A</span>
            <p style='margin-top: 8px'>Stripe顧客確認/作成</p>
        </div>
    ]
    Step8A --> StripeService
    StripeService --> StripeAPI
    
    SubscriptionController --- Step8B[
        <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'>8B</span>
            <p style='margin-top: 8px'>無料プラン情報取得</p>
        </div>
    ]
    Step8B --> PackageDB
    Step8B --> PackagePlanDB
    
    SubscriptionController --- Step9[
        <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'>9</span>
            <p style='margin-top: 8px'>サブスクリプションレコード作成</p>
        </div>
    ]
    Step9 --> SubscriptionService
    
    SubscriptionService --- Step10[
        <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'>10</span>
            <p style='margin-top: 8px'>サブスクリプション情報保存</p>
        </div>
    ]
    Step10 --> SubscriptionDB
    Step10 --> HistoryDB
    
    SubscriptionService --- Step11[
        <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'>11</span>
            <p style='margin-top: 8px'>Stripeでサブスクリプション作成</p>
        </div>
    ]
    Step11 --> StripeService
    StripeService --> StripeAPI
    
    SubscriptionController --- Step12[
        <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'>12</span>
            <p style='margin-top: 8px'>登録結果を返す</p>
        </div>
    ]
    Step12 --> Client
    
    %% Webhook Flow
    StripeAPI --- StepW1[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #ff9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>W1</span>
            <p style='margin-top: 8px'>webhookイベント送信</p>
        </div>
    ]
    StepW1 --> WebhookController
    
    WebhookController --- StepW2[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #ff9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>W2</span>
            <p style='margin-top: 8px'>webhook認証</p>
        </div>
    ]
    StepW2 --> StripeService
    
    WebhookController --- StepW3[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #ff9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>W3</span>
            <p style='margin-top: 8px'>webhookイベント保存</p>
        </div>
    ]
    StepW3 --> WebhookEventsDB
    
    WebhookController --- StepW4[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #ff9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>W4</span>
            <p style='margin-top: 8px'>イベント処理</p>
        </div>
    ]
    StepW4 --> SubscriptionService
    
    SubscriptionService --- StepW5[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #ff9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>W5</span>
            <p style='margin-top: 8px'>サブスクリプション状態更新</p>
        </div>
    ]
    StepW5 --> SubscriptionDB
    StepW5 --> HistoryDB
    
    %% 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 Step3 fill:transparent,stroke:transparent,stroke-width:1px
    style Step4 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 Step7 fill:transparent,stroke:transparent,stroke-width:1px
    style Step8A fill:transparent,stroke:transparent,stroke-width:1px
    style Step8B fill:transparent,stroke:transparent,stroke-width:1px
    style Step9 fill:transparent,stroke:transparent,stroke-width:1px
    style Step10 fill:transparent,stroke:transparent,stroke-width:1px
    style Step11 fill:transparent,stroke:transparent,stroke-width:1px
    style Step12 fill:transparent,stroke:transparent,stroke-width:1px
    style StepW1 fill:transparent,stroke:transparent,stroke-width:1px
    style StepW2 fill:transparent,stroke:transparent,stroke-width:1px
    style StepW3 fill:transparent,stroke:transparent,stroke-width:1px
    style StepW4 fill:transparent,stroke:transparent,stroke-width:1px
    style StepW5 fill:transparent,stroke:transparent,stroke-width:1px

ユースケース

ケース1: ログインと無料プランモーダル表示

説明

ユーザーがログインすると、システムはユーザーのグループにアクティブなサブスクリプションがなく、ユーザーがcreatorである場合、無料プランへの登録を提案するモーダルを表示します。

シーケンス図

sequenceDiagram
    participant User as ユーザー
    participant LC as LoginController
    participant AS as AuthService
    participant DB as データベース
    
    Note over User,DB: ログインと無料プランモーダル表示
    
    rect rgb(200, 255, 200)
    Note right of User: Happy Case Flow
    
    User->>LC: ログイン情報送信
    
    rect rgb(200, 230, 255)
    Note right of LC: ユーザー認証
    LC->>AS: ユーザー認証処理
    AS->>DB: ユーザー情報照会
    DB-->>AS: ユーザーデータ返却
    end
    
    rect rgb(200, 255, 255)
    Note right of AS: グループとサブスクリプション確認
    AS->>DB: グループとサブスクリプション照会
    DB-->>AS: グループ・サブスクリプションデータ
    
    Note over AS: ユーザーがcreator かつ<br/>アクティブなサブスクリプションなし
    
    AS->>AS: show_free_plan_modal = true
    end
    
    AS-->>LC: 認証結果返却
    LC-->>User: 認証成功レスポンス<br/>(show_free_plan_modal=true)
    end
    
    rect rgb(255, 200, 200)
    Note right of User: エラー処理
    rect rgb(255, 230, 230)
    alt ログイン情報が無効
        AS-->>LC: 認証失敗
        LC-->>User: 401 Unauthorized
    else ユーザーが存在しない
        DB-->>AS: ユーザーが見つからない
        AS-->>LC: ユーザーが見つからない
        LC-->>User: 401 Unauthorized
    end
    end
    end

ステップ

ステップ1: ログイン

  • 説明: ユーザーがログイン情報を送信
  • 認証: データベースでログイン情報を確認
  • 可能性のあるエラー: ログイン情報が正しくない

ステップ2: グループとサブスクリプション状態の確認

  • 説明: システムがユーザーのグループとサブスクリプション状態を確認
  • アクション:
    • ユーザーのmembershipsからグループ情報を取得
    • グループにアクティブなサブスクリプションがあるか確認
    • ユーザーがcreatorであるか確認

ステップ3: モーダル表示フラグの設定

  • 説明: アクティブなサブスクリプションがなく、ユーザーがcreatorの場合
  • アクション: ログイン応答にshow_free_plan_modal = trueフラグを設定

ステップ4: ログイン結果の返却

  • 説明: モーダル表示フラグ付きの成功ログイン結果を返す
  • レスポンス:
    • 成功: ユーザー情報とshow_free_plan_modalフラグを含む200 OK
    • エラー: 適切なエラーコードとメッセージ

ケース2: 無料プラン登録

説明

ユーザーがモーダルまたはサービスリストページから無料プランを登録します。

シーケンス図

sequenceDiagram
    participant User as ユーザー
    participant SC as SubscriptionController
    participant SS as SubscriptionService
    participant StS as StripeService
    participant DB as データベース
    participant StripeAPI as Stripe API
    
    Note over User,StripeAPI: 無料プラン登録プロセス
    
    rect rgb(200, 255, 200)
    Note right of User: Happy Case Flow
    
    User->>SC: POST /api/v1/general/subscription/free-plan
    
    rect rgb(255, 255, 200)
    Note right of SC: ユーザー権限確認
    SC->>DB: ユーザー権限確認
    DB-->>SC: ユーザー/グループデータ
    end
    
    alt ユーザーがcreatorでない
        SC-->>User: 403エラー (権限なし)
    else creatorである
        rect rgb(255, 230, 200)
        Note right of SC: Stripe顧客処理
        SC->>SS: firstRegisterFreePlanSubscription()
        
        alt ユーザーにStripe顧客IDがない
            SS->>StS: Stripe顧客ID同期
            StS->>StripeAPI: 顧客作成
            StripeAPI-->>StS: 顧客データ
            StS-->>SS: 更新されたユーザー
        end
        
        SS->>StripeAPI: アクティブなサブスクリプション確認
        StripeAPI-->>SS: サブスクリプションリスト
        end
        
        alt Stripeにアクティブなサブスクリプションが存在
            SS-->>SC: エラーレスポンス
            SC-->>User: 409エラー (競合)
        else サブスクリプションなし
            rect rgb(230, 200, 255)
            Note right of SS: プラン情報取得
            SS->>DB: 無料プラン情報取得
            DB-->>SS: パッケージ/プランデータ
            end
            
            rect rgb(200, 255, 255)
            Note right of SS: サブスクリプション作成
            SS->>DB: トランザクション開始
            SS->>DB: サブスクリプションレコード作成
            SS->>DB: サブスクリプション履歴作成
            DB-->>SS: 作成結果
            
            SS->>StS: Stripeサブスクリプション作成
            StS->>StripeAPI: サブスクリプションAPI呼び出し
            StripeAPI-->>StS: サブスクリプションデータ
            StS-->>SS: Stripe処理結果
            
            alt Stripe処理失敗
                SS->>DB: トランザクションロールバック
                SS-->>SC: エラーレスポンス
                SC-->>User: 500エラー
            else 成功
                SS->>DB: トランザクションコミット
                SS-->>SC: 成功レスポンス
                SC-->>User: 200 OK (サブスクリプションデータ)
            end
            end
        end
    end
    end
    
    rect rgb(255, 200, 200)
    Note right of User: エラー処理
    rect rgb(255, 230, 230)
    alt 無料プランが見つからない
        DB-->>SS: プラン情報なし
        SS-->>SC: プラン情報なし
        SC-->>User: 404 Not Found
    else 既にアクティブなサブスクリプションあり
        DB-->>SS: アクティブなサブスクリプション
        SS-->>SC: サブスクリプション競合
        SC-->>User: 409 Conflict
    else Stripe APIエラー
        StripeAPI-->>StS: APIエラー
        StS-->>SS: Stripeエラー
        SS-->>SC: APIエラー
        SC-->>User: 500 Internal Server Error
    end
    end
    end

ステップ

ステップ1: 登録リクエスト送信

  • 説明: ユーザーが無料プラン登録リクエストを送信
  • リクエスト: POST /api/v1/general/subscription/free-plan
  • 認証:
    • ユーザーがログインしていることを確認
    • ユーザーがグループのcreatorであることを確認

ステップ2: Stripe顧客の確認/作成

  • 説明: システムがStripe顧客を確認または新規作成
  • アクション:
    • ユーザーがすでにStripe顧客IDを持っている場合、アクティブなサブスクリプションを確認
    • 持っていない場合、新しいStripe顧客を作成し、ユーザープロフィールを更新
    • Stripeにアクティブなサブスクリプションがある場合、システムにログを記録しSlackに通知を送信する可能性がある

ステップ3: 無料プラン情報の取得

  • 説明: システムがデータベースから無料プラン情報を取得
  • アクション:
    • slugで無料プランを検索
    • 関連するpackage_planとprovider情報を取得

ステップ4: サブスクリプションレコードの作成

  • 説明: データベースに新しいサブスクリプションレコードを作成
  • アクション:
    • データの一貫性を確保するためのトランザクション開始
    • subscriptionsテーブルにレコードを作成
    • subscription_historiesテーブルにレコードを作成
    • ユーザーとグループ情報を更新

ステップ5: Stripeでのサブスクリプション作成

  • 説明: Stripeで無料プランサブスクリプションを作成
  • アクション:
    • 必要なパラメータでStripe APIを呼び出して無料サブスクリプションを作成
    • Stripeからのレスポンスを確認し、返されたステータスに応じて処理
    • 作成に失敗した場合、エラーをログに記録しSlackに通知を送信する可能性がある

ステップ6: 結果の返却

  • 説明: システムが登録成功結果を返す
  • レスポンス:
    • 成功: サブスクリプション情報を含む200 OK
    • エラー: 適切なエラーコードとメッセージ

ケース3: Stripeからのウェブフック処理

説明

無料プラン登録後、Stripeはサブスクリプション状態についてのウェブフックイベントを送信します。システムはこれらのイベントを処理して、データベース内のサブスクリプション状態を更新する必要があります。

シーケンス図

sequenceDiagram
    participant StripeAPI as Stripe API
    participant WC as WebhookController
    participant StS as StripeService
    participant SS as SubscriptionService
    participant DB as データベース
    
    Note over StripeAPI,DB: Stripeウェブフック処理
    
    rect rgb(200, 255, 200)
    Note right of StripeAPI: Happy Case Flow
    
    StripeAPI->>WC: POST /api/v1/admin/stripe/webhook
    Note over StripeAPI,WC: イベントデータ (customer.subscription.created など)
    
    rect rgb(255, 255, 200)
    Note right of WC: ウェブフック認証
    WC->>StS: webhook署名検証
    StS-->>WC: 検証結果
    end
    
    alt 署名無効
        WC-->>StripeAPI: 403エラー
    else 署名有効
        rect rgb(230, 200, 255)
        Note right of WC: イベント保存
        WC->>DB: webhookイベント保存 (pending状態)
        DB-->>WC: 保存結果
        
        WC->>WC: イベントタイプ判別
        end
        
        rect rgb(200, 255, 255)
        Note right of WC: イベント処理
        alt subscription.created / subscription.updated
            WC->>SS: サブスクリプション処理
            SS->>DB: サブスクリプション照会
            DB-->>SS: サブスクリプションデータ
            
            SS->>DB: サブスクリプション状態更新
            SS->>DB: 履歴レコード追加
            DB-->>SS: 更新結果
            SS-->>WC: 処理結果
        else invoice.paid
            WC->>SS: インボイス処理
            SS->>DB: サブスクリプション照会
            DB-->>SS: サブスクリプションデータ
            
            SS->>DB: サブスクリプション支払い状態更新
            SS->>DB: 履歴レコード更新
            DB-->>SS: 更新結果
            SS-->>WC: 処理結果
        end
        end
        
        rect rgb(255, 230, 200)
        Note right of WC: 完了処理
        WC->>DB: webhookイベント状態更新 (completed)
        DB-->>WC: 更新結果
        WC-->>StripeAPI: 200 OK
        end
    end
    end
    
    rect rgb(255, 200, 200)
    Note right of StripeAPI: エラー処理
    rect rgb(255, 230, 230)
    alt 既に処理済みイベント
        DB-->>WC: イベント既存
        WC-->>StripeAPI: 409 Conflict
    else サブスクリプションが見つからない
        DB-->>SS: サブスクリプションなし
        SS-->>WC: 検索エラー
        WC->>DB: webhookイベント状態更新 (failed)
        WC-->>StripeAPI: 404 Not Found
    else データベースエラー
        DB-->>SS: データベースエラー
        SS-->>WC: エラー結果
        WC->>DB: webhookイベント状態更新 (failed, エラーメッセージ)
        WC-->>StripeAPI: 500 Internal Server Error
    end
    end
    end

ステップ

ステップ1: ウェブフックイベントの受信

  • 説明: Stripeが設定されたエンドポイントにウェブフックイベントを送信
  • リクエスト: POST /api/v1/admin/stripe/webhook
  • データ: Stripeからのイベント情報(event_type, data)

ステップ2: ウェブフック認証

  • 説明: ウェブフックの有効性を確認
  • アクション:
    • Stripeシークレットでウェブフック署名を検証
    • イベントタイプ(event_type)を確認
    • 重複処理を防止

ステップ3: ウェブフックイベントの保存

  • 説明: イベント情報をデータベースに保存
  • アクション:
    • stripe_webhook_eventsテーブルにレコードを作成
    • 初期状態を'pending'に設定

ステップ4: サブスクリプションイベントの処理

  • 説明: イベントタイプに基づいて処理
  • アクション:
    • customer.subscription.createdイベント: 新規サブスクリプションを確認
    • customer.subscription.updatedイベント: サブスクリプション情報を更新
    • customer.subscription.deletedイベント: サブスクリプションをキャンセル済みとマーク

ステップ5: サブスクリプション状態の更新

  • 説明: データベース内のサブスクリプション情報を更新
  • アクション:
    • subscriptionsテーブルでサブスクリプション状態を更新
    • subscription_historiesテーブルに新しいレコードを作成
    • ウェブフックイベントを正常に処理済みとマーク

関連データベース

erDiagram
    users {
        bigint id PK "主キー"
        string name "ユーザー名"
        string email "ユーザーメール (unique)"
        string uid "ユーザーUID"
        string payment_provider_customer_id "Stripe顧客ID (nullable)"
        integer status "ユーザー状態"
        boolean is_first_login "初回ログインフラグ"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
        timestamp deleted_at "ソフト削除日時 (nullable)"
    }
    
    groups {
        bigint id PK "主キー"
        string name "グループ名"
        bigint created_by FK "作成者ID (users)"
        integer status "グループ状態"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
    }
    
    group_roles {
        bigint id PK "主キー"
        string name "ロール名"
        string slug "ロールスラッグ (unique)"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
    }
    
    group_members {
        bigint id PK "主キー"
        bigint user_id FK "usersテーブルへの関連付け"
        bigint group_id FK "groupsテーブルへの関連付け"
        bigint group_role_id FK "group_rolesテーブルへの関連付け"
        boolean is_creator "グループ作成者フラグ"
        timestamp joined_at "参加日時"
        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{ group_members : has
    groups ||--o{ group_members : has
    group_roles ||--o{ group_members : has
    users ||--o{ subscriptions : registers
    groups ||--o{ subscriptions : has
    subscriptions ||--o{ subscription_histories : has

関連APIエンドポイント

Method Endpoint Controller 説明
POST /api/v1/general/auth/login AuthController@login ログインとサブスクリプション状態の確認
POST /api/v1/general/subscription/free-plan SubscriptionController@registerFreePlan 無料プランの登録
GET /api/v1/general/subscription/active SubscriptionController@getActiveSubscription アクティブなサブスクリプションの確認
POST /api/v1/admin/stripe/webhook WebhookController@handleWebhook Stripeからのウェブフックイベントを処理
GET /api/v1/general/packages/free-plan PackageController@getFreePlan 無料プラン情報の取得
GET /api/v1/general/subscription/status SubscriptionController@getCurrentStatus 現在のサブスクリプション状態の取得

エラー処理

  • ログ:

    • すべての登録エラーはアプリケーションログに記録される
    • Stripe APIエラーは詳細情報と共にログに記録される
    • Webhookエラーはstripe_webhook_eventsテーブルのerrorフィールドに保存される
    • 重大なケースでは、Slackに通知が送信される
  • エラー情報:

エラーコード エラーメッセージ 説明
400 "グループには既にアクティブなサブスクリプションがあります。" 無料プラン登録の必要なし
403 "ユーザーはグループのcreatorではありません。" プラン登録権限なし
404 "無料プランが見つかりません。" システムに無料プランが存在しない
409 "Stripeにアクティブなサブスクリプションが既に存在します。" Stripeで既存のサブスクリプションを検出
409 "Webhookイベントは既に処理されています。" 重複Webhookイベント
422 "無効なデータ: ..." 入力データ検証エラー
500 "Stripe APIエラー: ..." Stripe API呼び出し時のエラー
500 "データベースエラー: ..." データベース操作時のエラー

追加注意事項

  • 無料プラン登録はグループにアクティブなサブスクリプションがない場合にのみ実行できます。
  • ユーザーは無料プランを登録するためにグループのcreatorである必要があります(group_membersテーブルのis_creator = true)。
  • すべてのStripeおよびデータベース操作は、一貫性を確保するためにトランザクションとして実行されます。
  • 無料プラン登録プロセスでは、ユーザーからの支払い情報は不要です。
  • 無料プランには通常、メンバー数、製品数、利用可能な機能に制限があります。
  • システムは内部データベースとStripeの両方をチェックすることで、重複登録がないことを確認します。
  • Stripeからのwebhookはstripe_webhook_eventsテーブルを通じて重複処理を防止するメカニズムで処理されます。
  • 無料プランの自動更新はデフォルトで有効になっていますが、無料プランのため実際の影響はありません。