subscription.plan_change ​
Handles subscription upgrades and downgrades with Stripe proration
Subscription plan changes (upgrades / downgrades) with Stripe proration.
Uses context.session (SQLAlchemy session injected by the engine, ADR-011) to load subscription/plan records and to sync the plan change to the database after Stripe is updated.
States: initiated → validating → calculating_proration → awaiting_confirmation → updating_stripe → syncing → completed OR → updating_stripe (skip confirmation) canceled | failed (terminal)
Overview ​
| Property | Value |
|---|---|
| Workflow type | Linear |
| Library | App-subscription |
| Version | 2.0 |
Triggers ​
| Source | Endpoint / Event | Description |
|---|---|---|
| API | POST /api/subscriptions/{id}/change-plan | User requests plan change |
Input Schema ​
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
subscription_uuid | uuid | Yes | — | OrganizationSubscription UUID |
organization_uuid | uuid | Yes | — | Organization UUID |
current_plan_uuid | uuid | Yes | — | Current plan UUID |
new_plan_uuid | uuid | Yes | — | New plan UUID |
user_uuid | uuid | No | — | User requesting the change |
proration_behavior | string | No | — | Stripe proration behavior (create_prorations |
require_confirmation | boolean | No | — | Whether to require user confirmation before updating Stripe (default: True) |
Output Schema ​
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
subscription_uuid | uuid | No | — | OrganizationSubscription UUID |
organization_uuid | uuid | No | — | — |
new_plan_uuid | uuid | No | — | New plan UUID applied to the subscription |
current_plan_uuid | uuid | No | — | Plan UUID at workflow start (pre-change) |
user_uuid | uuid | No | — | — |
proration_behavior | string | No | — | — |
require_confirmation | boolean | No | — | — |
stripe_subscription_ref | string | No | — | — |
current_plan_name | string | No | — | — |
current_stripe_product_ref | string | No | — | — |
new_plan_name | string | No | — | — |
new_stripe_product_ref | string | No | — | — |
app_uuid | uuid | No | — | — |
initiated_at | string | No | — | — |
initiated_by | string | No | — | — |
validated_at | string | No | — | — |
new_stripe_price_ref | string | No | — | — |
current_stripe_item_ref | string | No | — | — |
proration_amount | integer | No | — | Stripe-computed proration amount in minor units |
proration_details | json | No | — | Stripe upcoming-invoice preview |
proration_calculated_at | string | No | — | — |
confirmation_requested_at | string | No | — | — |
confirmation_skipped | boolean | No | — | — |
confirmed | boolean | No | — | — |
confirmed_at | string | No | — | — |
confirmed_by | string | No | — | — |
stripe_updated_at | string | No | — | — |
new_stripe_status | string | No | — | Stripe subscription status after the modify call |
completed_at | string | No | — | — |
canceled_at | string | No | — | — |
cancel_reason | string | No | — | — |
failed_at | string | No | — | — |
failure_reason | string | No | — | — |
failure_type | string | No | — | — |
failed_action | string | No | — | — |
failed_at_state | string | No | — | — |
failed_step | string | No | — | — |
failed_layer | string | No | — | — |
error | string | No | — | — |
error_type | string | No | — | — |
States ​
| State | Initial | Terminal | Success | Auto-advance | Description |
|---|---|---|---|---|---|
initiated | Yes | No | — | validate_change | Plan change request received |
awaiting_confirmation | No | No | — | — | Waiting for user to confirm the proration amount |
calculating_proration | No | No | — | — | Calculating proration amount from Stripe |
syncing | No | No | — | complete | Syncing updated plan to the database |
updating_stripe | No | No | — | update_stripe | Updating subscription in Stripe |
validating | No | No | — | calculate_proration | Validating subscription and plan |
canceled | No | Yes | No | — | User canceled plan change |
completed | No | Yes | Yes | — | Plan change completed |
failed | No | Yes | No | — | Plan change failed |
State Diagram ​
Transitions ​
| From | Action | To | Description |
|---|---|---|---|
initiated | validate_change | validating | Validate inputs and load subscription/plan data |
validating | calculate_proration | calculating_proration | Calculate proration via Stripe |
calculating_proration | request_confirmation | awaiting_confirmation | Request user confirmation of proration |
calculating_proration | skip_confirmation | updating_stripe | Skip confirmation, go directly to Stripe update |
awaiting_confirmation | confirm | updating_stripe | User confirmed — proceed with Stripe update |
awaiting_confirmation | cancel | canceled | User canceled the plan change |
updating_stripe | update_stripe | syncing | Update Stripe subscription, then sync DB |
syncing | complete | completed | Sync plan to DB and mark complete |
* (any state) | fail | failed | Catch-all failure handler |
Outcomes ​
| Outcome | Type | Description | State Data Keys |
|---|---|---|---|
completed | SUCCESS | Plan change completed successfully | new_plan_uuid, proration_amount, completed_at |
canceled | FAILURE | User canceled the plan change | canceled_at, cancel_reason |
failed | FAILURE | Plan change failed — see failure_reason | failed_at, failure_reason |
Business Errors ​
| Code | Message Template |
|---|---|
ORGANIZATION_NOT_FOUND | Organization {organization_uuid} not found |
SUBSCRIPTION_NOT_FOUND | Subscription {subscription_uuid} not found |
SUBSCRIPTION_NOT_ACTIVE | Subscription is not active (status={status}) |
NO_STRIPE_SUBSCRIPTION | Subscription has no Stripe subscription reference |
PLAN_NOT_FOUND | Plan {plan_uuid} not found or inactive |
SAME_PLAN | New plan is the same as current plan |
DIFFERENT_APP | Cannot change to a plan from a different app |
NO_PRICE_FOUND | No active price found for product reference |
BILLING_AUTHORIZATION_REQUIRED |
State Timeouts ​
| State | Timeout | Action |
|---|---|---|
awaiting_confirmation | 1440 min | fail |
Alarms ​
| Name | Type | State | Severity | Description |
|---|---|---|---|---|
plan_change_failed | state_entered | failed | warning | Subscription plan change failed |
confirmation_pending | state_timeout | awaiting_confirmation | info | Plan change awaiting confirmation for over 1 hour |
API Usage ​
bash
POST /api/workflows/start
Content-Type: application/json
{
"workflow_type": "subscription.plan_change",
"initial_data": {
"subscription_uuid": "value",
"organization_uuid": "value",
"current_plan_uuid": "value",
"new_plan_uuid": "value"
}
}