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 = true in the login response

Step 4: Return Login Result

  • Description: Return successful login result with modal display flag
  • Response:
    • Success: 200 OK with user information and show_free_plan_modal flag
    • Error: Appropriate error code with message

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 subscriptions table. Its initial subscription.status is set to Unpaid.
    • A corresponding history record is created in the subscription_histories table. For this initial record, the subscription_histories.type is set to new, and the subscription_histories.payment_status is set to unpaid.

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 OK with subscription information
    • Error: Appropriate error code with message

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 WebhookController uses the Stripe SDK to validate the Stripe-Signature header. An invalid signature results in a 403 Forbidden error.
    • It queries the stripe_webhook_events table using the Stripe event ID, request ID, and event type.
    • If an event record exists:
      • with status Completed: The process stops, and a 200 OK is returned with the message "Event already processed."
      • with status Processing: The process stops, and a 200 OK is returned with the message "Event is being processed."
      • with status Failed: The system will re-attempt processing.

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 updateOrCreate operation on the stripe_webhook_events table sets the event status to Pending.

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.updated and invoice.paid events.
  • Action:
    • customer.subscription.updated event: The SubscriptionService handles this event to activate the plan. It finds the local subscription and updates its subscription.status from Unpaid to Active.
    • invoice.paid event: Since a free plan generates a $0 invoice, Stripe sends an invoice.paid event. The SubscriptionService processes this to finalize the billing record. It finds the corresponding history record (created in Case 2, Step 4) and updates its subscription_histories.payment_status from unpaid to paid. 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_events record status is updated to Completed.
    • 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 OK response is sent to Stripe to acknowledge receipt and prevent retries for successfully handled events.

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.