Package Sync with Stripe
Description
This document describes how package and package plan data are synchronized from Stripe into the system via webhooks. The sync covers two artifact types:
- Stripe Product →
packagesandpackage_to_providers - Stripe Price →
package_plansandpackage_plan_to_providers
The logic ensures local records are created or updated from Stripe events, maintaining provider linkage for checkout and subscription flows.
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"]
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
Use Cases
Case 1: Sync Product (Stripe → Package)
Description
Handle product.created / product.updated from Stripe to create or update local packages and package_to_providers.
Sequence Diagram
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
Steps
Step 1: Receive Product Event
- Description: Stripe sends
product.createdorproduct.updated. - Validation: Ensure payload has
id. If missing, reject as invalid.
Step 2: Map Package Fields
- Mapping (from
PackageService::mapPackageParams):name,description,status: activeslugfrommetadata.slug- Limits:
max_member,max_product_group,max_product,max_category,max_search_query,max_viewpoint data_visible,api_available,schedule_id,schedule_priority
Step 3: Upsert Package
- Find by slug; create or update accordingly.
Step 4: Upsert Provider Mapping
- Insert or update
package_to_providerswith:provider_id,provider_product_id,status,created_at,updated_at(from Stripe timestamps)
Step 5: Response
- On success: 200 OK; on error: log, notify, and return error status.
Case 3: Product Deleted
Description
When a product.deleted event is received, the system can mark the corresponding package as inactive or deprecated depending on active subscriptions (implementation detail may vary).
Related Database Structure
erDiagram
packages {
bigint id PK "Primary key"
string name "Package name"
string slug "Package slug (unique)"
text description "Package description (nullable)"
text image "Image path (nullable)"
integer schedule_id "Schedule ID"
integer schedule_priority "Schedule priority"
integer max_member "Max members (nullable)"
integer max_product_group "Max product groups (nullable)"
integer max_product "Max products (nullable)"
integer max_category "Max categories (nullable)"
integer max_search_query "Max search queries (nullable)"
integer max_viewpoint "Max viewpoints (nullable)"
string data_visible "Data visibility"
tinyint api_available "API available"
tinyint status "Package status"
timestamp created_at "Created at"
timestamp updated_at "Updated at"
}
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_to_providers {
bigint id PK "Primary key"
bigint package_id FK "Linked to packages table"
bigint provider_id FK "Linked to payment_providers table"
string provider_product_id "Product ID from provider (Stripe)"
tinyint status "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"
}
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
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
Related API Endpoints
| Method | Endpoint | Controller | Description |
|---|---|---|---|
| POST | /api/v1/admin/stripe/webhook | WebhookController@handleWebhook | Receive Stripe product/price events |
| GET | /api/v1/admin/groups/packages | PackageController@index | Get available service packages list |
| 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 | "Product created without slug" | Stripe Product metadata missing slug |
| 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
- Package slugs should be stable and unique; used to correlate Stripe Product metadata with local records.
- Price
lookup_keymust be set and should include the package context to avoid collisions. - On monthly plan creation, the Product's
default_pricemay be set to the monthly price for convenience. - All upsert logic is idempotent; repeated events will not create duplicate records.