Membership Upgrades
Overview
The membership upgrade flow allows a user to move to a higher tier mid-billing-cycle with a prorated charge. The proration is calculated based on the days remaining until the next billing date and the price difference between the new tier and the base tier price of $4.99/month.
Architecture
| Component | Role |
|---|---|
User Service |
Orchestrates the upgrade: validates the request, charges the user, updates the subscription, and writes the membership record |
Growthbook |
Provides tier configuration and pricing at runtime |
Subscription Service |
Returns the next billing date and processes the tier change |
Payment Service |
Charges the prorated amount and processes refunds on failure |
Proration Calculation
Formula
daily_rate = (new_tier_monthly_price - 4.99) / 30 days_remaining = days from today (midnight UTC) to next billing date (midnight UTC) proration_amount = round(daily_rate * days_remaining, 2)
Key Rules
-
Both timestamps are truncated to midnight UTC before computing days remaining. This keeps the proration amount stable throughout the day.
-
Returns
0if the billing date is today or in the past. -
Returns
-1(error sentinel) if either input is nil, unparseable, or if the billing date is more than 65 days away. Billing dates beyond 65 days indicate MX has extended the user’s subscription indefinitely; these users cannot upgrade via the standard flow. -
The client must submit the exact proration amount returned by the proration endpoint. An off-by-one-cent mismatch returns
M11_PRORATION_AMOUNT_MISMATCH.
API
Upgrade Flow
POST /{user_id}/user/membership/upgrade
│
▼
Decode request
Fetch tier config from Growthbook ──fail──► 500 M2_CONFIG_FETCH_FAILED
│
▼
Validate tier exists ──fail──► 400 M8_INVALID_TIER / M9_TIER_VERSION_NOT_FOUND
│
▼
User exists and is ACTIVE? ──No──► 404 M3_USER_NOT_FOUND / 403 M4_USER_NOT_ACTIVE
│ Yes
▼
Membership record exists? ──No──► save default BASE-tier record and continue
│ Yes
▼
Get debit card (Payment Service) ──fail──► 500 M6_DEBIT_CARD_NOT_FOUND
│
▼
Get next billing date (Subscription Service) ──fail──► 500 M7_BILLING_DETAILS_FETCH_FAILED
│
▼
Calculate proration amount
Negative result? ──► 400 M10_PRORATION_CALCULATION_FAILED
Mismatch with request? ──► 400 M11_PRORATION_AMOUNT_MISMATCH
│
▼
Charge prorated amount (Payment Service)
Declined? ──► 402 M13_PAYMENT_DECLINED
Error? ──► 500 M12_PAYMENT_SUBMISSION_FAILED
│ Charged (confirmation_id)
▼
Upgrade subscription tier (Subscription Service)
Error? ──► attempt refund
Refund ok? ──► 500 M17_UPGRADE_FAILED_REFUND_ISSUED
Refund fail? ──► 500 M16_REFUND_FAILED (critical)
│ Success
▼
Write membership record (DynamoDB)
Error? ──► 500 M18_MEMBERSHIP_RECORD_CREATE_FAILED (manual reconciliation needed)
│ Success
▼
Return 201 Created
Error Reference
All upgrade errors use error_code: 8. Downgrade errors use error_code: 9. Finalize downgrade errors use error_code: 10.
| Error String | Status | Description |
|---|---|---|
|
400 |
Malformed JSON in the request body |
|
500 |
Growthbook tier configuration unavailable — safe to retry |
|
404 |
User does not exist |
|
403 |
User must be in |
|
500 |
Failed to read or create the user’s membership record (the upgrade flow will create a default BASE record if none exists, so this should never fire from a missing record alone) |
|
500 |
Failed to retrieve the user’s debit card |
|
500 |
Subscription service error fetching next billing date |
|
400 |
Requested tier not found in Growthbook configuration |
|
400 |
Current version not found within the tier config |
|
400 |
Proration returned a negative result |
|
400 |
Client amount does not match server calculation — refetch proration and retry |
|
500 |
Payment service communication error — no charge was made |
|
402 |
Card declined by the payment processor |
|
500 |
Critical — user was charged, subscription not updated, and refund failed. Requires immediate manual intervention. |
|
500 |
Subscription update failed but refund succeeded — user was not charged. Safe to retry. |
|
500 |
Payment and subscription succeeded but DynamoDB write failed. Requires manual reconciliation. |
Related Pages
-
Membership Downgrades — Downgrade flow and finalize
-
Membership Pause — Pausing billing mid-cycle
-
Users & Data Model — Membership event types and DynamoDB schema
-
Event-Driven Flows —
MEMBERSHIP_UPGRADEevent produced on the outbound Kinesis stream