First Free Plan Registration
Description
The First Free Plan Registration feature allows users or groups without an active subscription to register for a free service plan. Unlike automatic registration, the system displays a modal suggesting users register for the free plan when they log in. This process integrates with Stripe to manage customer and subscription information, while maintaining accurate status in the internal database.
This feature is particularly important to help new users start using the system seamlessly, requiring no payment information and providing immediate access to the system's basic features.
Prerequisites:
- User has successfully logged into the system.
- The group associated with the user does not have an active subscription.
- User is the creator (owner) of the group.
Process Flow Diagram
---
config:
theme: base
layout: dagre
flowchart:
curve: linear
htmlLabels: true
themeVariables:
edgeLabelBackground: "transparent"
---
flowchart TD
%% Main components
Client[User]
%% API Controller Layer
subgraph ApiControllerLayer["API Controller Layer"]
LoginController[LoginController]
SubscriptionController[SubscriptionController]
WebhookController[WebhookController]
end
%% API Service Layer
subgraph ApiServiceLayer["API Service Layer"]
AuthService(AuthService)
SubscriptionService(SubscriptionService)
end
%% Business Logic Services
subgraph BusinessServices["Business Logic Services"]
StripeService(StripeService)
end
%% Database Layer
subgraph DatabaseLayer["Database Layer"]
UsersDB[(users)]
GroupsDB[(groups)]
SubscriptionDB[(subscriptions)]
HistoryDB[(subscription_histories)]
PackageDB[(packages)]
PackagePlanDB[(package_plans)]
WebhookEventsDB[(stripe_webhook_events)]
end
%% External Services
subgraph ExternalServices["External Services"]
StripeAPI((Stripe API))
end
%% Login Flow
Client --- Step1[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>1</span>
<p style='margin-top: 8px'>Login</p>
</div>
]
Step1 --> LoginController
LoginController --- Step2[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>2</span>
<p style='margin-top: 8px'>Authenticate user</p>
</div>
]
Step2 --> AuthService
AuthService --- Step3[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3</span>
<p style='margin-top: 8px'>Check group and subscription</p>
</div>
]
Step3 --> UsersDB
Step3 --> GroupsDB
Step3 --> SubscriptionDB
AuthService --- Step4[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>4</span>
<p style='margin-top: 8px'>Set free plan modal display flag</p>
</div>
]
Step4 --> LoginController
LoginController --- Step5[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>5</span>
<p style='margin-top: 8px'>Return login result</p>
</div>
]
Step5 --> Client
%% Registration Flow
Client --- Step6[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>6</span>
<p style='margin-top: 8px'>Register free plan</p>
</div>
]
Step6 --> SubscriptionController
SubscriptionController --- Step7[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>7</span>
<p style='margin-top: 8px'>Check user permissions</p>
</div>
]
Step7 --> UsersDB
Step7 --> GroupsDB
SubscriptionController --- Step8A[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>8A</span>
<p style='margin-top: 8px'>Check/Create Stripe Customer</p>
</div>
]
Step8A --> StripeService
StripeService --> StripeAPI
SubscriptionController --- Step8B[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>8B</span>
<p style='margin-top: 8px'>Get free plan information</p>
</div>
]
Step8B --> PackageDB
Step8B --> PackagePlanDB
SubscriptionController --- Step9[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>9</span>
<p style='margin-top: 8px'>Create subscription record</p>
</div>
]
Step9 --> SubscriptionService
SubscriptionService --- Step10[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>10</span>
<p style='margin-top: 8px'>Save subscription information</p>
</div>
]
Step10 --> SubscriptionDB
Step10 --> HistoryDB
SubscriptionService --- Step11[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>11</span>
<p style='margin-top: 8px'>Create subscription on Stripe</p>
</div>
]
Step11 --> StripeService
StripeService --> StripeAPI
SubscriptionController --- Step12[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>12</span>
<p style='margin-top: 8px'>Return registration result</p>
</div>
]
Step12 --> Client
%% Webhook Flow
StripeAPI --- StepW1[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #ff9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>W1</span>
<p style='margin-top: 8px'>Send webhook event</p>
</div>
]
StepW1 --> WebhookController
WebhookController --- StepW2[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #ff9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>W2</span>
<p style='margin-top: 8px'>Verify webhook</p>
</div>
]
StepW2 --> StripeService
WebhookController --- StepW3[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #ff9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>W3</span>
<p style='margin-top: 8px'>Save webhook event</p>
</div>
]
StepW3 --> WebhookEventsDB
WebhookController --- StepW4[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #ff9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>W4</span>
<p style='margin-top: 8px'>Process event</p>
</div>
]
StepW4 --> SubscriptionService
SubscriptionService --- StepW5[
<div style='text-align: center'>
<span style='display: inline-block; background-color: #ff9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>W5</span>
<p style='margin-top: 8px'>Update subscription status</p>
</div>
]
StepW5 --> SubscriptionDB
StepW5 --> HistoryDB
%% Styling
style Client fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
style ApiControllerLayer fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
style ApiServiceLayer fill:#f0f8e6,stroke:#339933,stroke-width:2px
style BusinessServices fill:#f5f0ff,stroke:#9966cc,stroke-width:2px
style DatabaseLayer fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style ExternalServices fill:#fcd9d9,stroke:#cc3333,stroke-width:2px
style Step1 fill:transparent,stroke:transparent,stroke-width:1px
style Step2 fill:transparent,stroke:transparent,stroke-width:1px
style Step3 fill:transparent,stroke:transparent,stroke-width:1px
style Step4 fill:transparent,stroke:transparent,stroke-width:1px
style Step5 fill:transparent,stroke:transparent,stroke-width:1px
style Step6 fill:transparent,stroke:transparent,stroke-width:1px
style Step7 fill:transparent,stroke:transparent,stroke-width:1px
style Step8A fill:transparent,stroke:transparent,stroke-width:1px
style Step8B fill:transparent,stroke:transparent,stroke-width:1px
style Step9 fill:transparent,stroke:transparent,stroke-width:1px
style Step10 fill:transparent,stroke:transparent,stroke-width:1px
style Step11 fill:transparent,stroke:transparent,stroke-width:1px
style Step12 fill:transparent,stroke:transparent,stroke-width:1px
style StepW1 fill:transparent,stroke:transparent,stroke-width:1px
style StepW2 fill:transparent,stroke:transparent,stroke-width:1px
style StepW3 fill:transparent,stroke:transparent,stroke-width:1px
style StepW4 fill:transparent,stroke:transparent,stroke-width:1px
style StepW5 fill:transparent,stroke:transparent,stroke-width:1px
Use Cases
Case 1: Login and Display Free Plan Modal
Description
When a user logs in, the system checks if their group has no active subscription and the user is a creator, a modal suggesting free plan registration will be displayed.
Sequence Diagram
sequenceDiagram
participant User
participant Auth as AuthController
participant AuthSvc as AuthService
participant DB as Database
Note over User,DB: Login and Check Free Plan
rect rgb(200, 255, 200)
Note right of User: Happy Case Flow
User->>Auth: POST /api/v1/general/auth/login
Note over User,Auth: Send login information (email, password)
rect rgb(200, 230, 255)
Note right of Auth: Authenticate Login
Auth->>AuthSvc: login(credentials)
AuthSvc->>DB: validateCredentials(email, password)
DB-->>AuthSvc: User information
end
rect rgb(200, 255, 255)
Note right of AuthSvc: Check Group and Subscription
AuthSvc->>DB: getUserGroups(user_id)
DB-->>AuthSvc: List of groups and roles
AuthSvc->>DB: getActiveSubscription(group_id)
DB-->>AuthSvc: Subscription information (if any)
Note over AuthSvc: Check if user is creator and has no subscription
end
Alt User is creator and has no subscription
AuthSvc-->>Auth: Set flag show_free_plan_modal = true
Else Not creator or already has subscription
AuthSvc-->>Auth: Set flag show_free_plan_modal = false
End
Auth-->>User: 200 OK (user_data, tokens, show_free_plan_modal)
end
rect rgb(255, 200, 200)
Note right of User: Error Handling
rect rgb(255, 230, 230)
alt Invalid login credentials
AuthSvc-->>Auth: Authentication failed
Auth-->>User: 401 Unauthorized
else User not found
DB-->>AuthSvc: User not found
AuthSvc-->>Auth: User not found
Auth-->>User: 401 Unauthorized
end
end
end
Steps
Step 1: Login
- Description: User submits login information
- Validation: Check login information in the database
- Possible errors: Invalid login credentials
Step 2: Check Group and Subscription Status
- Description: System checks the user's group and subscription status
- Action:
- Get group information from group_members for the user
- Check if the group has an active subscription
- Check if the user is a creator (is_creator = true)
Step 3: Set Modal Display Flag
- Description: If no active subscription and user is creator
- Action: Set flag
show_free_plan_modal = truein the login response
Step 4: Return Login Result
- Description: Return successful login result with modal display flag
- Response:
- Success:
200 OKwith user information andshow_free_plan_modalflag - Error: Appropriate error code with message
- Success:
Case 2: Free Plan Registration
Description
User chooses to register for the free plan through the modal or service listing page.
Sequence Diagram
sequenceDiagram
participant User
participant Sub as SubscriptionController
participant SubSvc as SubscriptionService
participant Stripe as StripeService
participant StripeAPI as Stripe API
participant DB as Database
Note over User,DB: Free Plan Registration
rect rgb(200, 255, 200)
Note right of User: Happy Case Flow
User->>Sub: POST /api/v1/general/subscription/free-plan
Note over User,Sub: Send free plan registration request
rect rgb(255, 255, 200)
Note right of Sub: Check Permissions
Sub->>DB: checkUserPermission(user_id, group_id)
DB-->>Sub: User role
end
Alt User is not creator
Sub-->>User: 403 Forbidden
Else Continue processing
rect rgb(255, 230, 200)
Note right of Sub: Stripe Integration
Sub->>Stripe: getOrCreateCustomer(user_id, email)
Alt User has no Stripe customer
Stripe->>StripeAPI: customers.create(email, name)
StripeAPI-->>Stripe: Customer object
Stripe->>DB: updateUserStripeCustomerId(user_id, customer_id)
Else User already has Stripe customer
Stripe->>StripeAPI: customers.retrieve(customer_id)
StripeAPI-->>Stripe: Customer object
End
end
rect rgb(230, 200, 255)
Note right of Sub: Query Plan Information
Sub->>DB: getFreePlanPackage()
DB-->>Sub: Free plan information
end
rect rgb(200, 255, 255)
Note right of Sub: Process Registration
Sub->>SubSvc: createSubscription(group_id, user_id, package_id, plan_id)
SubSvc->>DB: Begin Transaction
SubSvc->>DB: insertSubscription(data)
SubSvc->>DB: insertSubscriptionHistory(data)
SubSvc->>Stripe: createFreeSubscription(customer_id, price_id)
Stripe->>StripeAPI: subscriptions.create(customer, items, trial_end: 'now')
StripeAPI-->>Stripe: Subscription object
Alt Subscription creation successful
SubSvc->>DB: updateSubscriptionStripeId(id, stripe_subscription_id)
SubSvc->>DB: Commit Transaction
SubSvc-->>Sub: Success response
Sub-->>User: 200 OK (subscription_data)
Else Error creating subscription
SubSvc->>DB: Rollback Transaction
SubSvc-->>Sub: Error response
Sub-->>User: 500 Internal Server Error
End
end
End
end
rect rgb(255, 200, 200)
Note right of User: Error Handling
rect rgb(255, 230, 230)
alt Free Plan Not Found
DB-->>Sub: Plan not found
Sub-->>User: 404 Not Found
else Active Subscription Exists
DB-->>Sub: Active subscription exists
Sub-->>User: 409 Conflict
else Stripe API Error
StripeAPI-->>Stripe: API Error
Stripe-->>SubSvc: Stripe Error
SubSvc-->>Sub: API Error
Sub-->>User: 500 Internal Server Error
end
end
end
Steps
Step 1: Send Registration Request
- Description: User sends free plan registration request
- Request:
POST /api/v1/general/subscription/free-plan - Validation:
- Check if user is logged in
- Check if user is creator of the group through group_members table (is_creator = true)
Step 2: Check/Create Stripe Customer
- Description: System checks or creates new Stripe customer
- Action:
- If user already has Stripe customer ID (payment_provider_customer_id), check for active subscriptions
- If not, create new Stripe customer and update to user profile
- If there are active subscriptions on Stripe, log to system and potentially send notification via Slack
Step 3: Get Free Plan Information
- Description: System retrieves information about the free plan from database
- Action:
- Find free plan by slug
- Get related package_plan and provider information
Step 4: Create Subscription Record
- Description: Create new subscription record in database. This is performed within a database transaction to ensure data integrity before any calls to the Stripe API are made.
- Action:
- A new record is created in the
subscriptionstable. Its initialsubscription.statusis set toUnpaid. - A corresponding history record is created in the
subscription_historiestable. For this initial record, thesubscription_histories.typeis set tonew, and thesubscription_histories.payment_statusis set tounpaid.
- A new record is created in the
Step 5: Create Stripe Subscription
- Description: Create free plan subscription on Stripe
- Action:
- Call Stripe API to create free subscription with necessary parameters
- Check response from Stripe and process according to returned status
- If creation fails, log error and potentially send notification via Slack
Step 6: Return Result
- Description: System returns successful registration result
- Response:
- Success:
200 OKwith subscription information - Error: Appropriate error code with message
- Success:
Case 3: Processing Webhook from Stripe
Description
After a subscription action on Stripe (creation, update, cancellation), Stripe sends webhook events to notify the application. The system must process these events securely and reliably to keep the internal database synchronized with Stripe's data. This includes handling subscription statuses, plan changes, and billing cycles. The process is designed to be idempotent, meaning processing the same event multiple times will not result in duplicate data or errors.
Sequence Diagram
sequenceDiagram
participant StripeAPI as Stripe API
participant Webhook as WebhookController
participant SubSvc as SubscriptionService
participant DB as Database
Note over StripeAPI,DB: Process Webhook from Stripe
StripeAPI->>Webhook: POST /api/v1/admin/stripe/webhook (event)
rect rgb(255, 255, 200)
Note right of Webhook: 1. Verify & Construct Event
Webhook->>Webhook: Stripe SDK verifies signature and constructs event
end
rect rgb(200, 230, 255)
Note right of Webhook: 2. Idempotency Check
Webhook->>DB: findExistingEvent(event_id, request_id, type)
DB-->>Webhook: Existing event record (if any)
end
alt Event Already Processed (Status: Completed or Processing)
Webhook-->>StripeAPI: 200 OK ("Event already processed/is processing")
else New or Failed Event
rect rgb(230, 200, 255)
Note right of Webhook: 3. Store Event
Webhook->>DB: updateOrCreate(event_id, ..., status: 'Pending')
end
rect rgb(200, 255, 200)
Note right of Webhook: 4. Process Event
Webhook->>SubSvc: handleSubscriptionEvent(data)
SubSvc->>DB: Update business data (subscriptions, histories, etc.)
DB-->>SubSvc: Success
Note right of Webhook: 5. Mark as Completed
SubSvc-->>Webhook: Success
Webhook->>DB: updateWebhookStatus(event_id, 'Completed')
Webhook-->>StripeAPI: 200 OK ("Event handled successfully")
end
end
rect rgb(255, 200, 200)
Note right of StripeAPI: Error Handling
alt Invalid Signature
Webhook-->>StripeAPI: 403 Forbidden ("Invalid signature")
else Processing Error
Webhook->>SubSvc: handleSubscriptionEvent(data)
SubSvc-->>Webhook: throws Exception
Webhook->>DB: updateWebhookStatus(event_id, 'Failed', error_message)
Webhook-->>StripeAPI: 500 Internal Server Error
end
end
Steps
Step 1: Receive Webhook Event
- Description: Stripe sends a webhook event to the configured endpoint.
- Request:
POST /api/v1/admin/stripe/webhook - Data: Event information from Stripe (event_type, data).
Step 2: Verify Signature & Check Idempotency
- Description: The system first verifies the incoming request's signature to ensure it's a genuine event from Stripe. It then performs an idempotency check to prevent processing the same event multiple times.
- Action:
- The
WebhookControlleruses the Stripe SDK to validate theStripe-Signatureheader. An invalid signature results in a403 Forbiddenerror. - It queries the
stripe_webhook_eventstable using the Stripe event ID, request ID, and event type. - If an event record exists:
- with status
Completed: The process stops, and a200 OKis returned with the message "Event already processed." - with status
Processing: The process stops, and a200 OKis returned with the message "Event is being processed." - with status
Failed: The system will re-attempt processing.
- with status
- The
Step 3: Store Webhook Event
- Description: For new or failed events, a record is created or updated in the database to track its state.
- Action:
- An
updateOrCreateoperation on thestripe_webhook_eventstable sets the event status toPending.
- An
Step 4: Process Subscription Event
- Description: The controller processes events based on their type to update the local database. For a successful free plan registration, Stripe typically sends
customer.subscription.updatedandinvoice.paidevents. - Action:
customer.subscription.updatedevent: TheSubscriptionServicehandles this event to activate the plan. It finds the local subscription and updates itssubscription.statusfromUnpaidtoActive.invoice.paidevent: Since a free plan generates a $0 invoice, Stripe sends aninvoice.paidevent. TheSubscriptionServiceprocesses this to finalize the billing record. It finds the corresponding history record (created in Case 2, Step 4) and updates itssubscription_histories.payment_statusfromunpaidtopaid. At this point, no new history record is created; the existing one is updated.
Step 5: Finalize Event Status
- Description: After processing, the webhook event's status is finalized in the database.
- Action:
- On success: The
stripe_webhook_eventsrecord status is updated toCompleted. - On failure: The status is updated to
Failed, and the error message is logged in the record. This allows for later inspection or retries. - A
200 OKresponse is sent to Stripe to acknowledge receipt and prevent retries for successfully handled events.
- On success: The
Related Database Structure
erDiagram
users {
bigint id PK "Primary key"
string name "User name"
string email "User email (unique)"
string uid "User UID"
string payment_provider_customer_id "Customer ID from Stripe (nullable)"
integer status "User status"
boolean is_first_login "First login flag"
timestamp created_at "Creation timestamp"
timestamp updated_at "Update timestamp"
timestamp deleted_at "Soft delete timestamp (nullable)"
}
groups {
bigint id PK "Primary key"
string name "Group name"
bigint created_by FK "Creator ID (users)"
integer status "Group status"
timestamp created_at "Creation timestamp"
timestamp updated_at "Update timestamp"
}
group_roles {
bigint id PK "Primary key"
string name "Role name"
string slug "Role slug (unique)"
timestamp created_at "Creation timestamp"
timestamp updated_at "Update timestamp"
}
group_members {
bigint id PK "Primary key"
bigint user_id FK "Links to users table"
bigint group_id FK "Links to groups table"
bigint group_role_id FK "Links to group_roles table"
boolean is_creator "Is group creator"
timestamp joined_at "Join timestamp"
timestamp created_at "Creation timestamp"
timestamp updated_at "Update timestamp"
}
subscriptions {
bigint id PK "Primary key"
string slug "Subscription slug (nullable, unique)"
bigint package_id FK "Links to packages table"
bigint package_plan_id FK "Links to package_plans table"
bigint group_id FK "Links to groups table"
bigint user_id FK "Links to users table"
string email "User email"
string payment_provider_customer_id "Customer ID from payment provider"
string payment_provider_subscription_id "Subscription ID from provider (nullable, unique)"
boolean auto_renew "Auto renewal"
string status "Subscription status"
string scheduled_plan_id "ID of plan to change to (nullable)"
timestamp scheduled_plan_change_at "Plan change timestamp (nullable)"
timestamp canceled_at "Cancellation timestamp (nullable)"
string canceled_reason "Cancellation reason (nullable)"
timestamp first_register_at "First registration timestamp (nullable)"
timestamp deadline_at "Deadline timestamp (nullable)"
timestamp grace_period_end_at "Grace period end timestamp (nullable)"
timestamp created_at "Creation timestamp"
timestamp updated_at "Update timestamp"
timestamp deleted_at "Soft delete timestamp (nullable)"
}
subscription_histories {
bigint id PK "Primary key"
bigint subscription_id FK "Links to subscriptions table"
bigint package_id FK "Links to packages table"
bigint package_plan_id FK "Links to package_plans table"
bigint group_id FK "Links to groups table"
bigint user_id FK "Links to users table"
string old_plan_id "Old plan ID (nullable)"
string payment_intent_id "Stripe payment intent ID (nullable, unique)"
string invoice_id "Stripe invoice ID (nullable, unique)"
string billing_plan "Billing cycle"
integer payment_attempt "Payment attempt count (nullable)"
json payment_method_details "Payment method details (nullable)"
string type "Subscription type"
decimal amount "Amount"
string currency "Currency"
integer schedule_id "Schedule ID"
integer schedule_priority "Schedule priority"
integer product_group_usage "Product group usage count"
integer product_usage "Product usage count"
integer category_usage "Category usage count"
integer search_query_usage "Search query usage count"
integer max_member "Maximum members"
integer max_product_group "Maximum product groups"
integer max_product "Maximum products"
integer max_category "Maximum categories"
integer max_search_query "Maximum search queries"
integer max_viewpoint "Maximum viewpoints"
string data_visible "Data visibility"
tinyint api_available "API availability"
tinyint status "Status"
string payment_status "Payment status"
timestamp started_at "Start timestamp (nullable)"
timestamp expires_at "Expiration timestamp (nullable)"
timestamp paid_at "Payment timestamp (nullable)"
timestamp created_at "Creation timestamp"
timestamp updated_at "Update timestamp"
}
stripe_webhook_events {
bigint id PK "Primary key"
string stripe_event_id "Stripe event ID"
string request_id "Request ID (nullable)"
string event_type "Event type"
enum status "Status (pending, processing, completed, failed)"
text error "Error (nullable)"
timestamp processed_at "Processing timestamp (nullable)"
timestamp created_at "Creation timestamp"
timestamp updated_at "Update timestamp"
}
users ||--o{ group_members : has
groups ||--o{ group_members : has
group_roles ||--o{ group_members : has
users ||--o{ subscriptions : registers
groups ||--o{ subscriptions : has
subscriptions ||--o{ subscription_histories : has
Related API Endpoints
| Method | Endpoint | Controller | Description |
|---|---|---|---|
| POST | /api/v1/general/auth/login | AuthController@login | Login and check subscription status |
| POST | /api/v1/general/subscription/free-plan | SubscriptionController@registerFreePlan | Register for free plan |
| GET | /api/v1/general/subscription/active | SubscriptionController@getActiveSubscription | Check active subscription |
| POST | /api/v1/admin/stripe/webhook | WebhookController@handleWebhook | Process webhook events from Stripe |
| GET | /api/v1/general/packages/free-plan | PackageController@getFreePlan | Get free plan information |
| GET | /api/v1/general/subscription/status | SubscriptionController@getCurrentStatus | Get current subscription status |
Error Handling
-
Log:
- All registration failures are logged to the application log
- Stripe API errors are logged with full details
- Webhook errors are saved to the error field in stripe_webhook_events table
- In critical cases, notifications are sent via Slack
-
Error Detail:
| Status Code | Error Message | Description |
|---|---|---|
| 400 | "Group already has an active subscription." | No need for free plan registration |
| 400 | "Invalid payload" | The webhook payload from Stripe is malformed or could not be parsed. |
| 403 | "User is not the creator of the group." | No permission to register a plan |
| 403 | "Invalid signature" | The webhook signature verification failed. |
| 404 | "Free plan not found." | Free plan doesn't exist in the system |
| 409 | "Active subscription exists on Stripe." | Existing subscription detected on Stripe |
| 422 | "Invalid data: ..." | Input data validation error |
| 500 | "Stripe API error: ..." | Error calling Stripe API |
| 500 | "Database error: ..." | Error interacting with database |
Additional Notes
- Free plan registration is only performed when there is no active subscription for the group.
- User must be the creator of the group (is_creator = true in group_members) to register for the free plan.
- All Stripe and database operations are performed as transactions to ensure consistency.
- The free plan registration process doesn't require payment information from the user.
- Free plans typically have limitations on the number of members, products, and features that can be used.
- The system ensures there are no duplicate registrations by checking both the internal database and Stripe.
- Webhooks from Stripe are processed with a duplicate prevention mechanism through the stripe_webhook_events table.
- Auto-renew is enabled by default for free plan registrations, but it has no practical effect since it's a free plan.
All sections above were newly added or updated to align with the documentation guide.