Stripe とのパッケージ同期
説明
このドキュメントでは、Stripe からの Webhook により、パッケージおよびパッケージプランのデータをシステムへ同期する方法を説明します。同期対象のアーティファクトは以下のとおりです。
- Stripe Product →
packagesとpackage_to_providers - Stripe Price →
package_plansとpackage_plan_to_providers
このロジックにより、Stripe のイベントからローカルレコードが作成または更新され、Checkout/Subscription フローで利用するプロバイダ連携が維持されます。
前提条件:
- 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"]
PackageService(PackageService)
PackagePlanService(PackagePlanService)
end
%% Repository Layer
subgraph RepositoryLayer["Repository Layer"]
PackageRepository(PackageRepository)
PackagePlanRepository(PackagePlanRepository)
PackageToProviderRepository(PackageToProviderRepository)
PackagePlanToProviderRepository(PackagePlanToProviderRepository)
end
%% Database Layer
subgraph DatabaseLayer["Database Layer"]
PackagesDB[(packages)]
PackagePlansDB[(package_plans)]
PackageToProvidersDB[(package_to_providers)]
PlanToProvidersDB[(package_plan_to_providers)]
ProvidersDB[(payment_providers)]
end
%% Flow
StripeAPI --- E1["Send webhook (product/price events)"]
E1 --> WebhookController
WebhookController --- E2["Verify & dispatch by event type"]
E2 -->|product.created / product.updated / product.deleted| PackageService
E2 -->|price.created / price.updated| PackagePlanService
PackageService --> PackageRepository
PackageService --> PackageToProviderRepository
PackageRepository --> PackagesDB
PackageToProviderRepository --> PackageToProvidersDB
PackagePlanService --> PackagePlanRepository
PackagePlanService --> PackagePlanToProviderRepository
PackagePlanService --> PackageRepository
PackagePlanRepository --> PackagePlansDB
PackagePlanToProviderRepository --> PlanToProvidersDB
PackageRepository --> PackagesDB
ProvidersDB -. reference .- PackageToProvidersDB
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
ユースケース
ケース 1: プロダクト同期 (Stripe → Package)
説明
product.created / product.updated を受信し、ローカルの packages および package_to_providers を作成または更新します。
シーケンス図
sequenceDiagram
participant StripeAPI as Stripe API
participant Webhook as WebhookController
participant PkgSvc as PackageService
participant Repo as Repositories
participant DB as Database
Note over StripeAPI,DB: Product Sync
StripeAPI->>Webhook: POST /api/v1/admin/stripe/webhook (product.*)
Webhook->>Webhook: Verify signature & construct event
Webhook->>PkgSvc: syncFromStripe(product)
rect rgb(230, 255, 255)
Note right of PkgSvc: Map fields
PkgSvc->>Repo: findBySlug(product.metadata.slug)
alt Package exists
PkgSvc->>Repo: update package fields (name, description, status, limits, visibility)
else Not found
PkgSvc->>Repo: create package
end
PkgSvc->>Repo: upsert package_to_providers (provider_product_id)
Repo->>DB: persist changes
end
Webhook-->>StripeAPI: 200 OK
手順
手順 1: Product イベントを受信
- 説明: Stripe から
product.createdまたはproduct.updatedが送信されます。 - 検証: ペイロードに
idがあることを確認。欠落している場合は無効として拒否。
手順 2: パッケージ項目のマッピング
- マッピング(
PackageService::mapPackageParamsより):name,description,status: activeslugはmetadata.slugから設定- 上限:
max_member,max_product_group,max_product,max_category,max_search_query,max_viewpoint data_visible,api_available,schedule_id,schedule_priority
手順 3: パッケージの Upsert
- slug で検索し、存在すれば更新、なければ作成。
手順 4: プロバイダマッピングの Upsert
package_to_providersに以下を挿入または更新:provider_id,provider_product_id,status,created_at,updated_at(Stripe のタイムスタンプから)
手順 5: レスポンス
- 成功時: 200 OK。エラー時: ログ、通知、エラーステータスを返却。
ケース 3: プロダクト削除
説明
product.deleted イベントを受信した場合、アクティブなサブスクリプションの有無に応じて、該当パッケージを非アクティブまたは非推奨としてマークできます(実装詳細は状況により異なります)。
関連データベース構成
erDiagram
packages {
bigint id PK "主キー"
string name "パッケージ名"
string slug "パッケージスラッグ(一意)"
text description "説明(null 可)"
text image "画像パス(null 可)"
integer schedule_id "スケジュール ID"
integer schedule_priority "スケジュール優先度"
integer max_member "メンバー上限(null 可)"
integer max_product_group "商品グループ上限(null 可)"
integer max_product "商品上限(null 可)"
integer max_category "カテゴリ上限(null 可)"
integer max_search_query "検索クエリ上限(null 可)"
integer max_viewpoint "ビューポイント上限(null 可)"
string data_visible "データ可視性"
tinyint api_available "API 利用可否"
tinyint status "パッケージステータス"
timestamp created_at "作成日時"
timestamp updated_at "更新日時"
}
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_to_providers {
bigint id PK "主キー"
bigint package_id FK "packages テーブルへの外部キー"
bigint provider_id FK "payment_providers テーブルへの外部キー"
string provider_product_id "プロバイダのプロダクト ID(Stripe)"
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 "更新日時"
}
payment_providers {
bigint id PK "主キー"
string name "プロバイダ名"
string slug "プロバイダスラッグ(一意)"
tinyint status "ステータス"
timestamp created_at "作成日時"
timestamp updated_at "更新日時"
}
packages ||--o{ package_plans : has
packages ||--o{ package_to_providers : uses
package_plans ||--o{ package_plan_to_providers : uses
payment_providers ||--o{ package_to_providers : linked
payment_providers ||--o{ package_plan_to_providers : linked
関連 API エンドポイント
| メソッド | エンドポイント | コントローラ | 説明 |
|---|---|---|---|
| POST | /api/v1/admin/stripe/webhook | WebhookController@handleWebhook | Stripe の product/price イベントを受信 |
| GET | /api/v1/admin/groups/packages | PackageController@index | 利用可能なサービスパッケージ一覧を取得 |
| GET | /api/v1/general/package-plan | PackagePlanController@index | サービスパッケージプラン一覧を取得 |
エラーハンドリング
-
ログと通知:
- すべての同期失敗はログに記録。重大なケースは Slack 等へ通知される場合があります。
-
エラー詳細:
| ステータスコード | エラーメッセージ | 説明 |
|---|---|---|
| 400 | "Invalid request" | Stripe のペイロードに必須識別子が欠落 |
| 400 | "Product created without slug" | Stripe Product の metadata.slug が存在しない |
| 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 作成に失敗 |
追加メモ
- パッケージの slug は安定かつ一意であるべきで、Stripe Product のメタデータとローカルレコードの関連付けに使用します。
- Price の
lookup_keyは必須で、衝突を避けるためにパッケージの文脈を含めることが望ましいです。 - 月次プラン作成時、利便性のため Product の
default_priceを月次価格へ設定する場合があります。 - すべての upsert ロジックは冪等であり、同じイベントが繰り返されても重複は作成されません。