Skip to content

feat: switch subscription plan#185

Merged
think-in-universe merged 10 commits intomainfrom
fix/downgrade-plan
Feb 23, 2026
Merged

feat: switch subscription plan#185
think-in-universe merged 10 commits intomainfrom
fix/downgrade-plan

Conversation

@think-in-universe
Copy link
Contributor

@think-in-universe think-in-universe commented Feb 22, 2026

Copilot AI review requested due to automatic review settings February 22, 2026 18:22
@gemini-code-assist
Copy link

Summary of Changes

Hello @think-in-universe, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces the capability for users to change their subscription plans. It adds a new API endpoint and implements the underlying service logic to handle plan transitions, including crucial validation for agent instance limits when downgrading. The changes ensure that subscription updates are correctly processed through Stripe and reflected in the application's database, enhancing the flexibility of user subscriptions.

Highlights

  • New API Endpoint for Plan Changes: A new API endpoint, /v1/subscriptions/change-plan, has been introduced to allow users to modify their subscription plans.
  • Subscription Plan Change Logic: The core change_plan functionality has been implemented within the SubscriptionService, handling plan validation, instance limit checks, and interaction with the Stripe API.
  • Instance Limit Validation: When a user attempts to downgrade their plan, the system now validates that their current number of active agent instances does not exceed the limits of the target plan, preventing downgrades that would violate plan constraints.
  • Stripe Integration for Plan Updates: The change_plan process integrates with Stripe to update the user's subscription item with the new price ID and subsequently updates the local database record.
  • Agent Repository Dependency: The SubscriptionService now depends on the AgentRepository to retrieve user instance counts for plan downgrade validation.
Changelog
  • crates/api/src/main.rs
    • Added agent_repo to the AppState configuration for dependency injection.
  • crates/api/src/openapi.rs
    • Registered the new /v1/subscriptions/change-plan endpoint in the OpenAPI specification.
    • Included ChangePlanRequest and ChangePlanResponse schemas for API documentation.
  • crates/api/src/routes/subscriptions.rs
    • Defined ChangePlanRequest and ChangePlanResponse data structures for the new endpoint.
    • Implemented the change_plan API handler, including detailed error handling for various subscription-related issues.
    • Added the change_plan route to the subscriptions router.
  • crates/api/tests/common.rs
    • Initialized agent_repo in the test server setup.
    • Passed agent_repo to the SubscriptionServiceConfig for proper testing of subscription functionalities.
  • crates/services/src/subscription/ports.rs
    • Introduced a new InstanceLimitExceeded error variant to handle plan downgrade restrictions.
    • Added the change_plan method to the SubscriptionService trait, defining its interface.
  • crates/services/src/subscription/service.rs
    • Imported AgentRepository for use within the subscription service.
    • Added agent_repo to SubscriptionServiceConfig and SubscriptionServiceImpl to provide access to agent data.
    • Implemented the change_plan method, which includes fetching provider plans, validating instance limits against target plan configurations, interacting with the Stripe API to update the subscription, and persisting changes to the database within a transaction.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@claude
Copy link

claude bot commented Feb 22, 2026

PR Review: feat: switch plan

Critical issues found before merge:


1. Privacy Violation (CLAUDE.md) — Logging Request Body at INFO Level

Per CLAUDE.md: "Never log request/response bodies". The plan name originates from user-submitted request body and is logged at INFO (production) level in two places:

In crates/api/src/routes/subscriptions.rs:

tracing::info!(
    "Changing plan for user_id={} to plan={}",
    user.user_id,
    req.plan  // request body field, logged at production level
);

crates/services/src/subscription/service.rs also has three similar info! calls logging target_plan.

Remove these or downgrade to tracing::debug!. Logging subscription IDs or internal event labels is fine; logging user-submitted field values is not.


2. Stripe/DB Inconsistency on Partial Failure

The Stripe subscription update happens outside the DB transaction. If Stripe succeeds but the DB upsert or commit fails, Stripe has the new plan but the DB still reflects the old one. The user could be billed at the new rate while the system enforces the old plan's limits (or vice versa):

// Stripe updated (external, non-transactional)
let updated_sub = StripeSubscription::update(&client, &subscription_id, params).await?;

// Only DB is wrapped in txn — Stripe is NOT rolled back on commit failure
let txn = db_client.transaction().await?;
self.subscription_repo.upsert_subscription(&txn, updated_model).await?;
txn.commit().await?;  // if this fails, Stripe is already changed

Consider reversing the operation order: update DB first (inside the transaction), then call Stripe, and if Stripe fails, roll back the DB transaction. Alternatively, rely on Stripe webhooks to reconcile state — but that path needs explicit documentation.


3. Missing proration_behavior in Stripe Update

The UpdateSubscription params omit proration_behavior, leaving Stripe to use its default (create_prorations). This means downgrades generate an unexpected credit/charge immediately rather than taking effect at period end. This should be explicit:

let params = stripe::UpdateSubscription {
    items: Some(vec![update_item]),
    proration_behavior: Some(stripe::ProrationBehavior::None),  // or AlwaysInvoice for upgrades
    ..Default::default()
};

The expected billing behavior (immediate vs. end-of-period) should be a conscious decision, not a default.


4. TOCTOU Race Condition on Instance Limit Check

The instance count is validated before the Stripe/DB update. A user could create additional agent instances between the check and when the plan change commits, bypassing the downgrade guard:

check count → [user creates instance] → stripe update → db commit

At minimum, re-check the count inside the DB transaction (with a lock or re-query) to tighten the window.


⚠️ Issues found — address #1 (logging) and #3 (proration) before merge; #2 and #4 are important but may be acceptable with documented tradeoffs.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a new subscription plan switching feature that allows users to change their subscription plan through the API.

Changes:

  • Added change_plan method to subscription service that validates instance limits, updates Stripe subscription, and persists changes to database
  • Introduced InstanceLimitExceeded error variant to prevent plan switches when user's instance count exceeds target plan's limit
  • Wired AgentRepository dependency into subscription service for instance count validation

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/services/src/subscription/service.rs Implements change_plan method with instance validation, Stripe API integration, and database transaction handling
crates/services/src/subscription/ports.rs Adds InstanceLimitExceeded error variant and change_plan trait method signature
crates/api/src/routes/subscriptions.rs Adds change_plan endpoint with request/response types and comprehensive error mapping
crates/api/src/openapi.rs Registers change_plan endpoint and related schemas in OpenAPI documentation
crates/api/src/main.rs Wires agent_repo into subscription service configuration
crates/api/tests/common.rs Moves agent_repo initialization before subscription service (dependency requirement)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@think-in-universe
Copy link
Contributor Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new change_plan functionality for subscriptions, enabling users to switch between different subscription plans. It integrates an agent_repo dependency into SubscriptionServiceConfig and SubscriptionServiceImpl for instance limit validation, and updates OpenAPI documentation and subscription routes. The review comments suggest improvements for code readability, organization, error handling, and consistency, including a positive note on the use of std::fmt::Display for enum string representation, which aligns with good practices. A comprehensive security audit found no vulnerabilities meeting medium, high, or critical severity criteria, and the implementation adheres to security best practices.

@think-in-universe think-in-universe changed the title feat: switch plan feat: switch subscription plan Feb 23, 2026
@think-in-universe think-in-universe changed the title feat: switch plan feat: switch subscription plan Feb 23, 2026
@think-in-universe think-in-universe merged commit df9d942 into main Feb 23, 2026
1 check passed
@think-in-universe think-in-universe deleted the fix/downgrade-plan branch February 23, 2026 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants