Membership Upgrades
Overview
The membership upgrade system allows users to upgrade their membership tier mid-cycle with automatic prorated billing. The system calculates the exact amount to charge based on the time remaining until the next billing cycle and handles payment processing, subscription updates, and error recovery with automatic refunds.
Architecture
Key Components
-
User Service - Orchestrates the upgrade flow, validates requests, and manages membership records
-
Subscription Service - Manages recurring billing and subscription state
-
Payment Service - Processes debit card charges and refunds
-
Growthbook - Provides membership tier configuration and pricing
Data Flow
The upgrade process follows these steps:
-
Frontend requests proration calculation
-
User confirms upgrade with calculated amount
-
Backend validates user, tier, and amount
-
Payment is charged via Payment Service
-
Subscription is updated via Subscription Service
-
Membership record is created
-
If any step fails after payment, automatic refund is issued
API Endpoints
Get Proration Amount
Calculate the exact charge for upgrading to a specific tier.
GET /{user_id}/user/membership/upgrade/proration?upgrade_tier={tier}
Parameters
-
user_id(path) - The user’s unique identifier -
upgrade_tier(query) - The tier to upgrade to (e.g., "plus", "premium")
Response 200 OK
{
"proration_amount": 15.67,
"upgrade_tier": "plus",
"billing_date": "2024-02-15T00:00:00Z",
"days_until_billing": 15.5
}
Response Fields
-
proration_amount- Exact charge in dollars, rounded to nearest cent -
upgrade_tier- The tier being upgraded to -
billing_date- Next billing date in RFC3339 format -
days_until_billing- Number of days until next billing cycle
Error Responses
-
400 Bad Request- Invalid tier or calculation failed-
Error Code:
8, Error String:M8_INVALID_TIERorM10_PRORATION_CALCULATION_FAILED
-
-
404 Not Found- User not found-
Error Code:
8, Error String:M3_USER_NOT_FOUND
-
-
500 Internal Server Error- Config fetch or billing details failed-
Error Code:
8, Error String:M2_CONFIG_FETCH_FAILEDorM7_BILLING_DETAILS_FETCH_FAILED
-
Upgrade Membership Tier
Execute the membership upgrade with payment processing.
POST /{user_id}/user/membership/upgrade
Request Fields
-
upgrade_tier- The tier to upgrade to (must match Growthbook config) -
upgrade_amount- The proration amount (must match server calculation exactly)
Response 201 Created
{
"confirmation_id": "pay_123abc",
"membership": {
"user_id": "user_123",
"tier": "PLUS",
"term": "MONTHLY",
"status": "ACTIVE",
"start_date": "2024-01-15T10:30:00Z",
"tier_version": "v1"
}
}
Error Responses
All upgrade errors return with error_code: 8 and a specific error_string:
-
400 Bad Request-
M1_INVALID_REQUEST_BODY- Malformed JSON request -
M8_INVALID_TIER- Tier not found in configuration -
M9_TIER_VERSION_NOT_FOUND- Version not found in tier config -
M10_PRORATION_CALCULATION_FAILED- Negative proration amount -
M11_PRORATION_AMOUNT_MISMATCH- Client amount doesn’t match server calculation
-
-
402 Payment Required-
M13_PAYMENT_DECLINED- Payment processor declined the charge
-
-
403 Forbidden-
M4_USER_NOT_ACTIVE- User must be in ACTIVE status
-
-
404 Not Found-
M3_USER_NOT_FOUND- User doesn’t exist -
M5_MEMBERSHIP_NOT_FOUND- No membership record found
-
-
500 Internal Server Error-
M2_CONFIG_FETCH_FAILED- Growthbook config fetch failed -
M6_DEBIT_CARD_NOT_FOUND- Failed to retrieve debit card -
M7_BILLING_DETAILS_FETCH_FAILED- Subscription service error -
M12_PAYMENT_SUBMISSION_FAILED- Payment service communication error -
M16_REFUND_FAILED- Critical: upgrade failed and refund failed -
M17_UPGRADE_FAILED_REFUND_ISSUED- Upgrade failed but refund succeeded -
M18_MEMBERSHIP_RECORD_CREATE_FAILED- Database write failed
-
Error Handling
Error Response Format
All membership upgrade errors follow a standard format:
{
"error_code": 8,
"error_string": "M13_PAYMENT_DECLINED",
"message": "Payment declined",
"status_code": 402
}
Error Categories
Validation Errors (M1, M8-M11)
These occur before any payment is attempted:
-
Invalid request format
-
Invalid tier configuration
-
Proration amount mismatch
Action Required: Fix the request and retry
User State Errors (M3-M5)
User or membership state is invalid:
-
User not found or not active
-
No membership record exists
Action Required: Verify user state before retrying
External Service Errors (M2, M6-M7, M12)
Temporary failures communicating with external services:
-
Growthbook configuration unavailable
-
Payment or subscription service errors
Action Required: Safe to retry after brief delay
Payment Errors (M13)
Payment processor declined the charge:
-
Insufficient funds
-
Card declined
-
Invalid card details
Action Required: User must update payment method
Proration Calculation
Formula
The prorated amount is calculated based on the daily rate and remaining days:
daily_rate = monthly_price / 30
days_remaining = (next_billing_date - now) / 24_hours
proration_amount = round(daily_rate * days_remaining, 2)
Frontend Integration
Recommended Flow
-
Display Upgrade Options
// Fetch proration for each tier option GET /user/membership/upgrade/proration?upgrade_tier=plus GET /user/membership/upgrade/proration?upgrade_tier=premium -
Show Confirmation Dialog
Display the exact
proration_amountto the user with billing date and days remaining -
Execute Upgrade
POST /user/membership/upgrade { "upgrade_tier": "plus", "upgrade_amount": 15.67 // Exact value from proration endpoint } -
Handle Errors
if (response.error_code === 8) { switch (response.error_string) { case "M11_PRORATION_AMOUNT_MISMATCH": // Refetch proration amount and show updated price break; case "M13_PAYMENT_DECLINED": // Prompt user to update payment method break; case "M17_UPGRADE_FAILED_REFUND_ISSUED": // Show "Upgrade failed, no charges made" message break; // ... handle other cases } }
Best Practices
-
Always fetch fresh proration amount before upgrade
-
Display exact amount with 2 decimal places
-
Show billing date and days remaining for transparency
-
Handle M11_PRORATION_AMOUNT_MISMATCH by refetching (billing date may have passed)
-
Show clear error messages for payment failures
-
For M16/M17, inform user to contact support
Sequence Diagrams
Successful Upgrade Flow
@startuml
actor User
participant Frontend
participant "User Service" as US
participant "Growthbook" as GB
participant "Subscription Service" as SS
participant "Payment Service" as PS
database "DynamoDB" as DB
User -> Frontend: Click "Upgrade to Plus"
Frontend -> US: GET /user/membership/upgrade/proration?upgrade_tier=plus
US -> GB: Get tier config
GB --> US: Tier config with price
US -> SS: Get billing details
SS --> US: Next billing date
US -> US: Calculate proration
US --> Frontend: {proration_amount: 15.67, billing_date: ...}
User -> Frontend: Confirm upgrade
Frontend -> US: POST /user/membership/upgrade
US -> GB: Get tier config
US -> US: Validate user is ACTIVE
US -> US: Validate tier exists
US -> SS: Get billing details
US -> US: Calculate & verify amount
US -> PS: Submit payment ($15.67)
PS --> US: {confirmation_id: "pay_123"}
US -> SS: Upgrade subscription tier
SS --> US: 201 Created
US -> DB: Create membership record
DB --> US: Success
US --> Frontend: 201 {confirmation_id, membership}
Frontend --> User: "Upgrade successful!"
@enduml
Failed Upgrade with Refund Flow
@startuml
participant Frontend
participant "User Service" as US
participant "Subscription Service" as SS
participant "Payment Service" as PS
Frontend -> US: POST /user/membership/upgrade
US -> PS: Submit payment
PS --> US: {confirmation_id: "pay_123"}
US -> SS: Upgrade subscription
SS --> US: 500 Internal Server Error
US -> PS: Submit refund (pay_123)
PS --> US: Refund successful
US --> Frontend: 500 {error_code: 8, error_string: "M17_UPGRADE_FAILED_REFUND_ISSUED"}
Frontend --> Frontend: Show "Upgrade failed, no charges made"
@enduml
Monitoring and Alerts
Key Metrics
-
Upgrade Success Rate - Track M17 errors vs successful upgrades
-
M16 Critical Errors - Alert immediately on refund failures
-
M11 Mismatch Rate - May indicate timing issues if high
-
Average Proration Amount - Monitor for unexpected spikes
Testing
Unit Tests
The codebase includes comprehensive tests for:
-
Proration calculation with various scenarios
-
Rounding to nearest cent validation
-
Exact amount matching
-
All error code paths
-
Successful upgrade flow
-
Refund scenarios
Test Scenarios
-
Valid upgrade with exact amount match
-
Invalid tier name
-
User not active
-
Amount mismatch (off by $0.01)
-
Payment declined
-
Subscription service failure with successful refund
-
Subscription service failure with failed refund
-
Membership record creation failure
-
Proration calculation with fractional cents
-
Billing date in past (should return $0)
Configuration
Troubleshooting
Common Issues
"Proration amount does not match" (M11)
Cause: Client amount calculated at different time than server
Solution: 1. Refetch proration amount 2. Display new amount to user 3. Retry upgrade with fresh amount
"Payment declined" (M13)
Cause: Insufficient funds, expired card, or fraud detection
Solution: 1. Prompt user to update payment method 2. Verify card details in Payment Service 3. Check for fraud holds
Related Documentation
-
Users and Membership - General membership documentation
-
API Specification - Full OpenAPI specification
-
Architecture - System architecture overview