Package Plan Sync with Stripe
Description
This document describes how package plan data are synchronized from Stripe Price events into the system via webhooks. The sync covers mapping to local tables:
- Stripe Price →
package_plansandpackage_plan_to_providers
The logic ensures local records are created or updated from Stripe events and linked to the correct Package derived from the related Stripe Product.
Prerequisites:
- Stripe is configured and webhook endpoint is set to the system.
- A Stripe Payment Provider record exists and is registered in the app container as
app('stripe.payment.provider').
Process Flow Diagram
---
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
Use Cases
Case: Sync Price (Stripe → Package Plan)
Description
Handle price.created / price.updated from Stripe to create or update local package_plans and package_plan_to_providers. Ensures parent Package exists by retrieving related Product from Stripe.
Sequence Diagram
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
Steps
Step 1: Receive Price Event
- Description: Stripe sends
price.createdorprice.updated. - Validation: Ensure
idis present; ensurelookup_key(used as planslug). If missing, log and skip.
Step 2: Ensure Package Exists
- Retrieve related Product from Stripe, then create or update
packagesbased on product metadata.
Step 3: Map Package Plan Fields
- Mapping (from
PackagePlanService::buildPackagePlanParams):name: nickname,slug: lookup_key,package_idamount: unit_amount,currencytype: type,billing_plan: recurring.interval
Step 4: Upsert Package Plan and Provider Mapping
- Create/update
package_plansbyslug. - Upsert
package_plan_to_providerswithprovider_price_idand status.
Step 5: Response
- On success: 200 OK; on error: log, notify, and return error status.
Related Database Structure
erDiagram
package_plans {
bigint id PK "Primary key"
string name "Plan name"
string slug "Plan slug (unique)"
bigint package_id FK "Linked to packages table"
double amount "Amount"
string currency "Currency"
string type "Plan type (recurring, one_time)"
string billing_plan "Billing cycle"
tinyint status "Plan status"
timestamp created_at "Created at"
timestamp updated_at "Updated at"
}
package_plan_to_providers {
bigint id PK "Primary key"
bigint package_plan_id FK "Linked to package_plans table"
bigint provider_id FK "Linked to payment_providers table"
string provider_price_id "Price ID from provider (Stripe)"
tinyint status "Status"
timestamp created_at "Created at"
timestamp updated_at "Updated at"
}
packages {
bigint id PK "Primary key"
string name "Package name"
string slug "Package slug (unique)"
}
payment_providers {
bigint id PK "Primary key"
string name "Provider name"
string slug "Provider slug (unique)"
tinyint status "Status"
timestamp created_at "Created at"
timestamp updated_at "Updated at"
}
packages ||--o{ package_plans : has
package_plans ||--o{ package_plan_to_providers : uses
payment_providers ||--o{ package_plan_to_providers : linked
Related API Endpoints
| Method | Endpoint | Controller | Description |
|---|---|---|---|
| POST | /api/v1/admin/stripe/webhook | WebhookController@handleWebhook | Receive Stripe price events |
| GET | /api/v1/general/package-plan | PackagePlanController@index | Get service package plan list |
Error Handling
-
Log & Notifications:
- All sync failures are logged; critical cases may notify Slack.
-
Error Detail:
| Status Code | Error Message | Description |
|---|---|---|
| 400 | "Invalid request" | Missing required identifiers from Stripe payload |
| 400 | "Price created without slug" | Stripe Price missing lookup_key used as plan slug |
| 404 | "Package not found" | Referenced product/package cannot be resolved |
| 500 | "Failed to create price for plan ..." | Price creation failed on Stripe side |
Additional Notes
- Price
lookup_keymust be set and should include the package context to avoid collisions. - All upsert logic is idempotent; repeated events will not create duplicate records.