Stripe とのパッケージプラン同期

説明

このドキュメントでは、Stripe の Price イベントから Webhook を通じて、パッケージプランのデータをシステムへ同期する方法を説明します。同期は以下のローカルテーブルへのマッピングを対象とします。

  • Stripe Price → package_planspackage_plan_to_providers

このロジックにより、Stripe のイベントからローカルレコードが作成または更新され、関連する Stripe Product から導出される正しい Package とリンクされます。

前提条件:

  • Stripe が設定済みで、Webhook エンドポイントがシステムに設定されていること。
  • Stripe の決済プロバイダレコードが存在し、アプリケーションコンテナに app('stripe.payment.provider') として登録されていること。

プロセスフローダイアグラム

---
config:
  theme: base
  layout: dagre
  flowchart:
    curve: linear
    htmlLabels: false
  themeVariables:
    edgeLabelBackground: "transparent"
---
flowchart TD
    %% External Services
    subgraph ExternalServices["External Services"]
        StripeAPI((Stripe API))
    end

    %% API Controller Layer
    subgraph ApiControllerLayer["API Controller Layer"]
        WebhookController[WebhookController]
    end

    %% Business Services
    subgraph BusinessServices["Business Logic Services"]
        PackagePlanService(PackagePlanService)
    end

    %% Repository Layer
    subgraph RepositoryLayer["Repository Layer"]
        PackagePlanRepository(PackagePlanRepository)
        PackagePlanToProviderRepository(PackagePlanToProviderRepository)
        PackageRepository(PackageRepository)
    end

    %% Database Layer
    subgraph DatabaseLayer["Database Layer"]
        PackagePlansDB[(package_plans)]
        PlanToProvidersDB[(package_plan_to_providers)]
        PackagesDB[(packages)]
        ProvidersDB[(payment_providers)]
    end

    %% Flow
    StripeAPI --- E1["Send webhook (price events)"]
    E1 --> WebhookController
    WebhookController --- E2["Verify & dispatch by event type"]
    E2 -->|price.created / price.updated| PackagePlanService

    PackagePlanService --> PackageRepository
    PackagePlanService --> PackagePlanRepository
    PackagePlanService --> PackagePlanToProviderRepository
    PackageRepository --> PackagesDB
    PackagePlanRepository --> PackagePlansDB
    PackagePlanToProviderRepository --> PlanToProvidersDB
    ProvidersDB -. reference .- PlanToProvidersDB

    %% Styling
    style ExternalServices fill:#fcd9d9,stroke:#cc3333,stroke-width:2px
    style ApiControllerLayer fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
    style BusinessServices fill:#f5f0ff,stroke:#9966cc,stroke-width:2px
    style RepositoryLayer fill:#f0f8e6,stroke:#339933,stroke-width:2px
    style DatabaseLayer fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
    style E1 fill:transparent,stroke:transparent,stroke-width:1px
    style E2 fill:transparent,stroke:transparent,stroke-width:1px

ユースケース

ケース: 価格同期 (Stripe → パッケージプラン)

説明

Stripe の price.created / price.updated を処理し、ローカルの package_planspackage_plan_to_providers を作成または更新します。関連する Stripe Product を取得して親 Package の存在を保証します。

シーケンス図

sequenceDiagram
    participant StripeAPI as Stripe API
    participant Webhook as WebhookController
    participant PlanSvc as PackagePlanService
    participant Repo as Repositories
    participant DB as Database

    Note over StripeAPI,DB: Price Sync

    StripeAPI->>Webhook: POST /api/v1/admin/stripe/webhook (price.*)
    Webhook->>Webhook: Verify signature & construct event
    Webhook->>PlanSvc: syncFromStripe(price)

    rect rgb(230, 255, 255)
    Note right of PlanSvc: Validate & map
    alt lookup_key missing
        PlanSvc-->>Webhook: Log & skip (no slug)
    else proceed
        PlanSvc->>StripeAPI: retrieve product(price.product)
        StripeAPI-->>PlanSvc: product
        PlanSvc->>Repo: getOrCreatePackageFromStripe(product)
        PlanSvc->>Repo: findBySlug(lookup_key)
        PlanSvc->>Repo: create/update package_plan
        PlanSvc->>Repo: upsert package_plan_to_providers(provider_price_id)
        Repo->>DB: persist changes
    end
    end

    Webhook-->>StripeAPI: 200 OK

手順

手順 1: Price イベントを受信

  • 説明: Stripe から price.created または price.updated が送信されます。
  • 検証: id の存在と、プランの slug として用いる lookup_key の存在を確認。欠落している場合はログに記録してスキップ。

手順 2: Package の存在を保証

  • 関連する Product を Stripe から取得し、そのメタデータに基づいて packages を作成または更新します。

手順 3: パッケージプラン項目のマッピング

  • マッピング(PackagePlanService::buildPackagePlanParams より):
    • name: nickname, slug: lookup_key, package_id
    • amount: unit_amount, currency
    • type: type, billing_plan: recurring.interval

手順 4: パッケージプランとプロバイダマッピングの Upsert

  • slug をキーに package_plans を作成/更新。
  • package_plan_to_providersprovider_price_id とステータスを upsert。

手順 5: レスポンス

  • 成功時: 200 OK。エラー時: ログ、通知、エラーステータスを返却。

関連データベース構造

erDiagram
    package_plans {
        bigint id PK "主キー"
        string name "プラン名"
        string slug "プランスラッグ(一意)"
        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 "更新日時"
    }

    packages {
        bigint id PK "主キー"
        string name "パッケージ名"
        string slug "パッケージスラッグ(一意)"
    }

    payment_providers {
        bigint id PK "主キー"
        string name "プロバイダ名"
        string slug "プロバイダスラッグ(一意)"
        tinyint status "ステータス"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
    }

    packages ||--o{ package_plans : has
    package_plans ||--o{ package_plan_to_providers : uses
    payment_providers ||--o{ package_plan_to_providers : linked

関連 API エンドポイント

メソッド エンドポイント コントローラ 説明
POST /api/v1/admin/stripe/webhook WebhookController@handleWebhook Stripe の price イベントを受信
GET /api/v1/general/package-plan PackagePlanController@index サービスパッケージプラン一覧を取得

エラーハンドリング

  • ログと通知:

    • すべての同期失敗はログに記録。重大なケースは Slack 等へ通知される場合があります。
  • エラー詳細:

ステータスコード エラーメッセージ 説明
400 "Invalid request" Stripe のペイロードに必須識別子が欠落
400 "Price created without slug" Stripe Price の lookup_key(プラン slug)が存在しない
404 "Package not found" 参照された Product/Package を解決できない
500 "Failed to create price for plan ..." Stripe 側での Price 作成に失敗

追加メモ

  • Price の lookup_key は必須で、衝突を避けるためパッケージの文脈を含めることが望ましいです。
  • すべての upsert ロジックは冪等であり、同じイベントが繰り返されても重複は作成されません。