Subscription Plan Change Comparison
Overview Description
The Subscription Plan Change Comparison feature allows group owners to preview the impact of changing to a new subscription plan before confirming the change. When a user schedules a plan change (upgrade or downgrade), they need to understand how the new plan's resource limits will affect their existing group members and wishlists.
This feature provides a detailed comparison showing:
- Current vs target plan resource limits
- Members that exceed the new member limit (if downgrading)
- Wishlists that violate resource constraints
- Wishlists that exceed the new product group limit
The process requires the group owner to review potential impacts and make informed decisions about which members to deactivate and which wishlists to set to manual training status before the plan change can be confirmed.
Pre-conditions:
- User must be authenticated
- User must be the group creator (owner)
- Group must have an active subscription
- Must have a pending plan change (scheduled plan change)
API: Subscription Compare Change API
Activity Diagram
---
config:
theme: base
layout: dagre
flowchart:
curve: linear
htmlLabels: true
themeVariables:
edgeLabelBackground: "transparent"
---
flowchart TB
%% Main components
SubscriptionController[SubscriptionController]
SubscriptionService(SubscriptionService)
UserService(UserService)
WishlistToGroupService(WishlistToGroupService)
SubscriptionModel[[Subscription]]
SubscriptionHistoryModel[[SubscriptionHistory]]
GroupModel[[Group]]
GroupMemberModel[[GroupMember]]
WishlistToGroupModel[[WishlistToGroup]]
SubDB[(subscriptions)]
SubHistDB[(subscription_histories)]
GroupDB[(groups)]
GroupMemberDB[(group_members)]
WishlistDB[(wishlist_to_groups)]
subgraph Controllers
GetCompareChangeController["GET /subscription/compare-change"]
ConfirmChangeController["POST /subscription/confirm-change"]
end
subgraph Services
SubscriptionService
UserService
WishlistToGroupService
end
subgraph Models
SubscriptionModel
SubscriptionHistoryModel
GroupModel
GroupMemberModel
WishlistToGroupModel
end
subgraph Databases
SubDB
SubHistDB
GroupDB
GroupMemberDB
WishlistDB
end
%% GET compare-change flow
GetCompareChangeController --- Step1A[
<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'>1A</span>
<p style='margin-top: 8px'>Validate User Authorization</p>
</div>
]
Step1A --> GetCompareChangeController
GetCompareChangeController --- Step2A[
<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'>2A</span>
<p style='margin-top: 8px'>Get Active Subscription</p>
</div>
]
Step2A --> SubscriptionModel
GetCompareChangeController --- Step3A[
<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'>3A</span>
<p style='margin-top: 8px'>Get Plan Comparison Preview</p>
</div>
]
Step3A --> SubscriptionService
GetCompareChangeController --- Step4A[
<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'>4A</span>
<p style='margin-top: 8px'>Calculate Member Differences</p>
</div>
]
Step4A --> GroupMemberModel
GetCompareChangeController --- Step5A[
<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'>5A</span>
<p style='margin-top: 8px'>Calculate Wishlist Differences</p>
</div>
]
Step5A --> WishlistToGroupModel
GetCompareChangeController --- Step6A[
<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'>6A</span>
<p style='margin-top: 8px'>Return Preview Response</p>
</div>
]
Step6A --> GetCompareChangeController
%% POST confirm-change flow
ConfirmChangeController --- Step1B[
<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'>1B</span>
<p style='margin-top: 8px'>Validate User Authorization</p>
</div>
]
Step1B --> ConfirmChangeController
ConfirmChangeController --- Step2B[
<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'>2B</span>
<p style='margin-top: 8px'>Validate Request Data</p>
</div>
]
Step2B --> ConfirmChangeController
ConfirmChangeController --- Step3B[
<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'>3B</span>
<p style='margin-top: 8px'>Begin Transaction</p>
</div>
]
Step3B --> SubscriptionService
ConfirmChangeController --- Step4B[
<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'>4B</span>
<p style='margin-top: 8px'>Deactivate Members</p>
</div>
]
Step4B --> UserService
ConfirmChangeController --- Step5B[
<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'>5B</span>
<p style='margin-top: 8px'>Set Wishlists to Manual</p>
</div>
]
Step5B --> WishlistToGroupService
ConfirmChangeController --- Step6B[
<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'>6B</span>
<p style='margin-top: 8px'>Commit Transaction</p>
</div>
]
Step6B --> SubscriptionService
%% Service to Model relationships
SubscriptionService -.-> SubscriptionModel
SubscriptionService -.-> SubscriptionHistoryModel
SubscriptionService -.-> GroupModel
UserService -.-> GroupMemberModel
WishlistToGroupService -.-> WishlistToGroupModel
%% Model to Database relationships
SubscriptionModel -.-> SubDB
SubscriptionHistoryModel -.-> SubHistDB
GroupModel -.-> GroupDB
GroupMemberModel -.-> GroupMemberDB
WishlistToGroupModel -.-> WishlistDB
%% Styling
style SubDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style SubHistDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style GroupDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style GroupMemberDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style WishlistDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
style Controllers fill:#e6f3ff
style Services fill:#f0f8e6
style Models fill:#fff0f5
style Databases fill:#ffe6cc
style Step1A fill:transparent,stroke:transparent,stroke-width:1px
style Step2A fill:transparent,stroke:transparent,stroke-width:1px
style Step3A fill:transparent,stroke:transparent,stroke-width:1px
style Step4A fill:transparent,stroke:transparent,stroke-width:1px
style Step5A fill:transparent,stroke:transparent,stroke-width:1px
style Step6A fill:transparent,stroke:transparent,stroke-width:1px
style Step1B fill:transparent,stroke:transparent,stroke-width:1px
style Step2B fill:transparent,stroke:transparent,stroke-width:1px
style Step3B fill:transparent,stroke:transparent,stroke-width:1px
style Step4B fill:transparent,stroke:transparent,stroke-width:1px
style Step5B fill:transparent,stroke:transparent,stroke-width:1px
style Step6B fill:transparent,stroke:transparent,stroke-width:1px
Case 1: Get Compare Change Preview - Success
Description
Group owner successfully retrieves a preview of the changes between the current subscription plan and the target plan. The system analyzes member limits and wishlist constraints to identify potential impacts.
Sequence Diagram
sequenceDiagram
participant Client
participant Controller as SubscriptionController
participant Service as SubscriptionService
participant SubModel as Subscription
participant HistModel as SubscriptionHistory
participant GroupModel as Group
participant MemberModel as GroupMember
participant WishlistModel as WishlistToGroup
participant Database
Note over Client,Database: GET /subscription/compare-change
rect rgb(200, 255, 200)
Note right of Client: Happy Case Flow
Client->>Controller: GET /subscription/compare-change
rect rgb(255, 255, 200)
Note right of Controller: Authorization Check
Controller->>Controller: Check user is group owner
end
rect rgb(200, 230, 255)
Note right of Controller: Fetch Subscription Data
Controller->>SubModel: Get active subscription
SubModel->>Database: SELECT * FROM subscriptions WHERE group_id AND status = 'active'
Database-->>SubModel: Return active subscription
SubModel-->>Controller: Return subscription object
Controller->>HistModel: Get current plan (latest paid)
HistModel->>Database: SELECT * FROM subscription_histories WHERE payment_status = 'paid' ORDER BY paid_at DESC
Database-->>HistModel: Return current plan
Controller->>HistModel: Get target plan (latest pending change)
HistModel->>Database: SELECT * FROM subscription_histories WHERE type = 'change' AND payment_status = 'pending'
Database-->>HistModel: Return target plan
end
rect rgb(200, 255, 255)
Note right of Controller: Calculate Member Differences
Controller->>Service: calculateMemberDifferences(group, currentPlan, targetPlan)
Service->>MemberModel: Get current member count
MemberModel->>Database: SELECT COUNT(*) FROM group_members WHERE group_id
Database-->>MemberModel: Return count
Service->>Service: Compare with target plan max_member limit
Service->>MemberModel: Get non-creator members if over limit
MemberModel->>Database: SELECT * FROM group_members WHERE group_id AND is_creator = false
Database-->>MemberModel: Return members list
Service-->>Controller: Return member differences
end
rect rgb(230, 200, 255)
Note right of Controller: Calculate Wishlist Differences
Controller->>Service: calculateWishlistDifferences(group, currentPlan, targetPlan)
Service->>WishlistModel: Get all wishlists with related data
WishlistModel->>Database: SELECT * FROM wishlist_to_groups WHERE group_id
Database-->>WishlistModel: Return wishlists
Service->>Service: Check each wishlist against target limits
Service->>Service: Identify force deactivation (violates resource limits)
Service->>Service: Identify optional deactivation (exceeds max_product_group)
Service-->>Controller: Return wishlist differences
end
Controller-->>Client: 200 OK (comparison data)
end
rect rgb(255, 200, 200)
Note right of Client: Error Scenarios
rect rgb(255, 230, 230)
alt Not Group Owner
Controller-->>Client: 403 Forbidden
else No Active Subscription
SubModel-->>Controller: No active subscription found
Controller-->>Client: 400 Bad Request
else No Target Plan
HistModel-->>Controller: No pending change found
Controller-->>Client: 400 Bad Request
end
end
end
Steps
Step 1: Authorization Check
- Description: Verify that the user making the request is the group owner
- Validation:
- User must be authenticated (Bearer token)
- User's
groupMember.is_creatormust betrue
- Error condition: If not owner, return 403 Forbidden with message "アクセスが拒否されました。"
Step 2: Fetch Active Subscription
- Description: Retrieve the group's current active subscription
- Action: Query
subscriptionstable for subscription withstatus = 'active'and matchinggroup_id - Error condition: If no active subscription exists, return 400 with message "アクティブなサブスクリプションがありません。"
Step 3: Get Current and Target Plans
- Description: Retrieve subscription history records for comparison
- Current Plan: Latest
subscription_historyrecord withpayment_status = 'paid'(ordered bypaid_at DESC) - Target Plan: Latest
subscription_historyrecord withtype = 'change'andpayment_status = 'pending' - Error condition: If no target plan exists, return 400 with message "変更予定のプランがありません。"
Step 4: Calculate Member Differences
- Description: Analyze how the target plan's member limit affects current members
- Actions:
- Get current member count from
group_memberstable - Compare with
target_plan.max_member - If over limit, calculate
excess_member_count = current_count - max_member - Fetch list of non-creator members (
is_creator = false) that can be deactivated
- Get current member count from
- Response fields:
is_over_limit: booleancurrent_member_count: integercurrent_member_limit: from current plannew_member_limit: from target planexcess_member_count: integermembers_to_choose: array of member objects (user_id, name, role)
Step 5: Calculate Wishlist Differences
- Description: Analyze how the target plan's resource limits affect wishlists
- Actions:
- Fetch all wishlists for the group with related data:
wishlistProductswishlistCategorieswishlistSearchQuerieswishlistCategoryViewpointReference(with nested viewpoint data)
- For each wishlist, count resources:
- Product count
- Category count
- Search query count
- Viewpoint count
- Classify wishlists:
- Force Deactivation: Violates ANY resource limit
products > max_productcategories > max_categorysearch_queries > max_search_queryviewpoints > max_viewpoint
- Optional Deactivation: Valid wishlists but
total_valid_wishlist > max_product_group
- Force Deactivation: Violates ANY resource limit
- Fetch all wishlists for the group with related data:
- Response fields:
is_over_limit: booleantotal_wishlist: total counttotal_valid_wishlist: count of wishlists meeting resource requirementstotal_excess: excess valid wishlistsforce_deactivation: array with reasonsoptional_deactivation: array of valid wishlists
Step 6: Return Comparison Response
- Description: Format and return the complete comparison data
- Response structure:
{ "status": true, "message": "プラン変更のプレビューを取得しました。", "data": { "current_plan": { "slug": "plan_slug", "name": "Plan Name", "limits": { "max_member": 5, "max_product_group": 10, "max_product": 50, "max_category": 20, "max_search_query": 100, "max_viewpoint": 10 } }, "target_plan": { /* same structure */ }, "differences": { "members": { /* member diff data */ }, "wishlists": { /* wishlist diff data */ } } } }
Database Related Tables & Fields
erDiagram
subscriptions {
bigint id PK
bigint package_id FK
bigint package_plan_id FK
bigint group_id FK "Reference to groups table"
bigint user_id FK
string status "Active, Canceled, Pending Cancellation, Past Due, Unpaid"
timestamp created_at
timestamp updated_at
}
subscription_histories {
bigint id PK
bigint subscription_id FK "Reference to subscriptions table"
bigint package_id FK
bigint package_plan_id FK
bigint group_id FK
bigint user_id FK
string old_plan_id "Old package plan data, has data when plan is changed"
string type "The type of the subscription"
integer max_member
integer max_product_group
integer max_product
integer max_category
integer max_search_query
integer max_viewpoint
string payment_status
tinyint status "0:Inactive, 1: Active"
timestamp paid_at
timestamp created_at
}
groups {
bigint id PK
string name
bigint created_by FK "Reference to users table"
integer status "0: Inactive, 1: Active"
timestamp created_at
}
group_members {
bigint id PK
bigint group_id FK "Reference to groups table"
bigint user_id FK "Reference to users table"
bigint group_role_id FK
boolean is_creator "Flag indicating if member is the group creator"
timestamp joined_at
timestamp created_at
}
wishlist_to_groups {
bigint id PK
bigint group_id FK "Reference to groups table"
bigint created_by FK "Reference to users table"
bigint subscription_id FK "Reference to subscriptions table"
string name "Name of the wishlist"
string slug "Slug of the wishlist"
integer status "0: Inactive, 1: Active, 3: Canceled"
timestamp created_at
}
subscriptions ||--o{ subscription_histories : has
groups ||--|| subscriptions : has
groups ||--o{ group_members : has
groups ||--o{ wishlist_to_groups : has
Error Handling
Log:
- All errors logged to application log with
logThrow($th) - Critical errors may trigger Slack notifications
Error Detail:
| Status Code | Error Message | Description |
|---|---|---|
| 401 | "未認証です。" | User not authenticated or invalid token |
| 403 | "アクセスが拒否されました。" | User is not the group owner |
| 400 | "アクティブなサブスクリプションがありません。" | No active subscription found for group |
| 400 | "変更予定のプランがありません。" | No pending plan change found |
| 400 | "プラン変更のプレビューに失敗しました。" | General error during preview generation |
Case 2: Confirm Compare Change - Success
Description
Group owner confirms the plan change and applies the specified member deactivations and wishlist status changes. All operations are wrapped in a database transaction to ensure data consistency.
Sequence Diagram
sequenceDiagram
participant Client
participant Controller as SubscriptionController
participant Request as ConfirmCompareChangeRequest
participant Service as SubscriptionService
participant UserService as UserService
participant WishlistService as WishlistToGroupService
participant SubModel as Subscription
participant HistModel as SubscriptionHistory
participant GroupMemberModel as GroupMember
participant WishlistModel as WishlistToGroup
participant Database
Note over Client,Database: POST /subscription/confirm-change
rect rgb(200, 255, 200)
Note right of Client: Happy Case Flow
Client->>Controller: POST /subscription/confirm-change (members_to_inactive, wishlists_to_manual)
rect rgb(255, 255, 200)
Note right of Controller: Authorization Check
Controller->>Controller: Check user is group owner
end
rect rgb(200, 230, 255)
Note right of Controller: Request Validation
Controller->>Request: validate(data)
Request->>Request: Validate members_to_inactive format
Request->>GroupMemberModel: Check member IDs belong to group
GroupMemberModel->>Database: SELECT user_id FROM group_members WHERE group_id AND user_id IN (...)
Database-->>GroupMemberModel: Return matching members
Request->>Request: Validate wishlists_to_manual format
Request->>WishlistModel: Check wishlist slugs belong to group
WishlistModel->>Database: SELECT slug FROM wishlist_to_groups WHERE group_id AND slug IN (...)
Database-->>WishlistModel: Return matching wishlists
Request-->>Controller: Validation passed
end
rect rgb(200, 255, 255)
Note right of Controller: Verify Subscription State
Controller->>SubModel: Get active subscription
SubModel->>Database: SELECT * FROM subscriptions WHERE group_id AND status = 'active'
Database-->>SubModel: Return active subscription
Controller->>HistModel: Get current plan (latest paid)
HistModel->>Database: SELECT * FROM subscription_histories WHERE subscription_id AND payment_status = 'paid'
Database-->>HistModel: Return current plan
Controller->>HistModel: Get target plan (latest change pending)
HistModel->>Database: SELECT * FROM subscription_histories WHERE subscription_id AND type = 'change'
Database-->>HistModel: Return target plan
end
rect rgb(230, 200, 255)
Note right of Controller: Execute Change with Transaction
Controller->>Service: executeCompareChange(members, wishlists)
Service->>Database: BEGIN TRANSACTION
alt Members to Deactivate
Service->>UserService: updateStatusByIds(memberIds, 'inactive')
UserService->>GroupMemberModel: Update user status
GroupMemberModel->>Database: UPDATE users SET status = 'inactive' WHERE id IN (...)
Database-->>GroupMemberModel: Confirm update
end
alt Wishlists to Manual
Service->>WishlistService: changeTranningStatusBySlugs(wishlistSlugs, 'manual')
WishlistService->>WishlistModel: Update training status
WishlistModel->>Database: UPDATE wishlist_to_groups SET training_status = 'manual' WHERE slug IN (...)
Database-->>WishlistModel: Confirm update
end
Service->>Database: COMMIT TRANSACTION
Service-->>Controller: Return success result
end
Controller-->>Client: 200 OK (success message)
end
rect rgb(255, 200, 200)
Note right of Client: Error Scenarios
rect rgb(255, 230, 230)
alt Not Group Owner
Controller-->>Client: 403 Forbidden
else Validation Failed
Request-->>Controller: Validation errors
Controller-->>Client: 400 Bad Request (validation errors)
else No Active Subscription
SubModel-->>Controller: No subscription found
Controller-->>Client: 400 Bad Request
else No Target Plan
HistModel-->>Controller: No pending change
Controller-->>Client: 400 Bad Request
else Transaction Error
Database-->>Service: Database error
Service->>Database: ROLLBACK TRANSACTION
Service-->>Controller: Error result
Controller-->>Client: 400 Bad Request
end
end
end
Steps
Step 1: Authorization Check
- Description: Verify that the user making the request is the group owner
- Same as Case 1, Step 1
Step 2: Request Validation
- Description: Validate the request body parameters
- Validation rules:
members_to_inactive(optional, string):- Must be comma-separated user IDs
- All user IDs must belong to the current group
- Cannot include the group creator
wishlists_to_manual(optional, string):- Must be comma-separated wishlist slugs
- All slugs must belong to the current group
- Validation queries:
- For members:
SELECT user_id FROM group_members WHERE group_id = ? AND user_id IN (?) - For wishlists:
SELECT slug FROM wishlist_to_groups WHERE group_id = ? AND slug IN (?)
- For members:
- Error conditions:
- Invalid member IDs: Return 400 with message "The following members do not belong to your group: {ids}"
- Invalid wishlist slugs: Return 400 with message "The following wishlists do not belong to your group: {slugs}"
Step 3: Verify Subscription State
- Description: Ensure subscription and target plan exist
- Same validation as Case 1, Steps 2-3
Step 4: Begin Database Transaction
- Description: Start a database transaction for atomicity
- Action: Call
$this->subscriptionRepository->transactionBegin() - Purpose: Ensure all changes are applied together or rolled back on error
Step 5: Deactivate Members (if provided)
- Description: Update status of specified members to inactive
- Action:
- Parse comma-separated member IDs:
explode(',', $request->input('members_to_inactive')) - Call
UserService::updateStatusByIds($memberIds, ActiveOrNotStatus::Inactive) - Execute:
UPDATE users SET status = 'inactive' WHERE id IN (?)
- Parse comma-separated member IDs:
- Note: Only affects users who are members of the current group (validated in Step 2)
Step 6: Set Wishlists to Manual (if provided)
- Description: Update training_status of specified wishlists to manual
- Action:
- Parse comma-separated wishlist slugs:
explode(',', $request->input('wishlists_to_manual')) - Call
WishlistToGroupService::changeTranningStatusBySlugs($wishlistSlugs, WishlistToGroupTraningStatus::Manual) - Execute:
UPDATE wishlist_to_groups SET training_status = 'manual' WHERE slug IN (?)
- Parse comma-separated wishlist slugs:
- Purpose: Prevent automatic training for these wishlists
Step 7: Commit Transaction
- Description: Commit all database changes
- Action: Call
$this->subscriptionRepository->transactionCommit() - Success condition: All updates completed without errors
Step 8: Return Success Response
- Description: Return confirmation message to client
- Response structure:
{ "status": true, "message": "プラン変更を確認しました。", "data": [] }
Database Related Tables & Fields
erDiagram
users {
bigint id PK
string name
string email
string status "Account status: active, inactive, suspended"
timestamp created_at
timestamp updated_at
}
group_members {
bigint id PK
bigint group_id FK
bigint user_id FK "Reference to users table"
boolean is_creator "Flag indicating if member is the group creator"
timestamp created_at
}
wishlist_to_groups {
bigint id PK
bigint group_id FK
string slug "Slug of the wishlist"
string training_status "Training status: auto, manual"
integer status "0: Inactive, 1: Active, 3: Canceled"
timestamp created_at
timestamp updated_at
}
subscriptions {
bigint id PK
bigint group_id FK
string status "Active, Canceled, Pending Cancellation, Past Due, Unpaid"
timestamp created_at
}
subscription_histories {
bigint id PK
bigint subscription_id FK
string type "The type of the subscription"
string payment_status
timestamp created_at
}
users ||--o{ group_members : has
group_members }o--|| wishlist_to_groups : manages
subscriptions ||--o{ subscription_histories : tracks
Error Handling
Log:
- All errors logged with
logThrow($th) - Transaction rollback logged for debugging
Error Detail:
| Status Code | Error Message | Description |
|---|---|---|
| 401 | "未認証です。" | User not authenticated |
| 403 | "アクセスが拒否されました。" | User is not the group owner |
| 400 | "リクエストが正しくありません。" | Validation errors (invalid member IDs or wishlist slugs) |
| 400 | "アクティブなサブスクリプションがありません。" | No active subscription found |
| 400 | "変更予定のプランがありません。" | No pending plan change found |
| 400 | "プラン変更の確認に失敗しました。" | Transaction error or other failure |
Additional Notes
Business Rules:
- Only group creators (owners) can access these endpoints
- Member deactivation only affects non-creator members
- Wishlist force deactivation is required when resource limits are violated
- Wishlist optional deactivation is for exceeding max_product_group limit
- All database operations use transactions for data consistency
Performance Considerations:
- Eager load relationships to avoid N+1 queries
- Use
getLimits()helper method from SubscriptionHistory model - Index on
statuscolumns for faster queries
Security:
- Authorization check on every request
- Input validation prevents SQL injection
- Only group-owned resources can be modified
Future Enhancements:
- Add preview of cost differences between plans
- Support bulk operations for large member lists
- Add rollback capability if user changes mind