Stripe とのパッケージプラン同期
説明
このドキュメントでは、Stripe の Price イベントから Webhook を通じて、パッケージプランのデータをシステムへ同期する方法を説明します。同期は以下のローカルテーブルへのマッピングを対象とします。
- Stripe Price →
package_plansとpackage_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_plans と package_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_idamount: unit_amount,currencytype: type,billing_plan: recurring.interval
手順 4: パッケージプランとプロバイダマッピングの Upsert
slugをキーにpackage_plansを作成/更新。package_plan_to_providersにprovider_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 ロジックは冪等であり、同じイベントが繰り返されても重複は作成されません。