feat: Add command for adding schedules to subscriptions#707
feat: Add command for adding schedules to subscriptions#707calvin-codecov wants to merge 6 commits intomainfrom
Conversation
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Outdated
Show resolved
Hide resolved
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Outdated
Show resolved
Hide resolved
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Show resolved
Hide resolved
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Show resolved
Hide resolved
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Show resolved
Hide resolved
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Outdated
Show resolved
Hide resolved
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Show resolved
Hide resolved
a1aafd4 to
7e2f900
Compare
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Outdated
Show resolved
Hide resolved
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Show resolved
Hide resolved
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Outdated
Show resolved
Hide resolved
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Outdated
Show resolved
Hide resolved
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Outdated
Show resolved
Hide resolved
| # If the user is not in a schedule, update immediately | ||
| # If the user is in a schedule, update the existing schedule |
There was a problem hiding this comment.
This comment was actually already no longer correct and had not been updated. This handler has been releasing the existing schedule for a long time.
apps/codecov-api/billing/management/commands/apply_subscription_schedules.py
Show resolved
Hide resolved
938002c to
f36f356
Compare
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (78.78%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #707 +/- ##
==========================================
- Coverage 92.25% 92.20% -0.06%
==========================================
Files 1302 1303 +1
Lines 47854 48050 +196
Branches 1628 1628
==========================================
+ Hits 44149 44303 +154
- Misses 3396 3438 +42
Partials 309 309
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (78.78%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| log.info( | ||
| f"Restored end-date schedule for owner {owner.ownerid} after upgrade failure" | ||
| ) | ||
| raise |
There was a problem hiding this comment.
Error recovery can mask original StripeError exception
Medium Severity
The schedule recovery code inside the except stripe.StripeError block (lines 486–502) calls stripe.Subscription.retrieve and _create_end_date_schedule (which itself makes two Stripe API calls) without its own try/except. If any of those calls fail, the new exception replaces the original upgrade failure, and the raise on line 503 is never reached. This masks the root cause of the upgrade failure. The post-success path at lines 512–533 correctly wraps _create_end_date_schedule in try/except to handle exactly this scenario — the error recovery path needs the same treatment.
Additional Locations (1)
thomasrockhu-codecov
left a comment
There was a problem hiding this comment.
Probably need Ajay to really review the logic
| If subscription is not provided, it is retrieved from Stripe. | ||
| """ | ||
| if subscription is None: | ||
| subscription = stripe.Subscription.retrieve(owner.stripe_subscription_id) |
There was a problem hiding this comment.
what happens if this fails?
| if phase2_plan_id is None: | ||
| phase2_plan_id = phase1_plan_id | ||
| if phase2_quantity is None: | ||
| phase2_quantity = phase1_quantity |
There was a problem hiding this comment.
nit: can this be done before subscription call? or change the argument ordering. helps to read the code more smoothly
| "task_signature": task_signature, | ||
| "end_date": end_date.strftime("%Y-%m-%d"), | ||
| "script_version": "1.0", |
There was a problem hiding this comment.
nit: alphabetize
| # An increase in seats and/or plan implies the user is upgrading, hence 'is_upgrading' is a consequence | ||
| # of proration_behavior providing an invoice, in this case, != "none" | ||
| # TODO: change this to "self._is_upgrading_seats(owner, desired_plan) or self._is_extending_term(owner, desired_plan)" | ||
| is_upgrading = ( |
There was a problem hiding this comment.
does this also handle downgrading and the comment is off?
| # payment_behavior="pending_if_incomplete", | ||
| ) | ||
| except stripe.StripeError: | ||
| # Upgrade payment failed but we already released the schedule so add back an end-date schedule so the user doesn't lose it |
There was a problem hiding this comment.
does the schedule release have to happen after the above step then? would that prevent the need to rollback?
| self.style.WARNING("DRY RUN MODE - No changes will be made to Stripe") | ||
| ) | ||
|
|
||
| # Build queryset of owners to process |
| owners = list(owners[:limit]) | ||
|
|
||
| # Output summary | ||
| self.stdout.write(f"Total matching owners: {total_matching}") |
There was a problem hiding this comment.
we probably want to write to a file here as well


Adds general command for the team to run to bulk add subscription schedules for Owners. It will procure the matching owners based on the arguments and process each.
Takes these as user provided arguments:
Handles multiple scenarios for each owner
The command will return a summary of what happened and the owner id for the last acted upon owner.
PR also adds to the modify_subscription webhook handler logic to reapply the schedule if the subscription encounters an upgrade after the command has been run as upgrades will release schedules.
Note
Medium Risk
Touches Stripe billing/scheduling flows and adds a bulk command that can modify many subscriptions; correctness around schedule metadata/end dates and error recovery is important but changes are localized and covered by tests.
Overview
Adds a new
apply_subscription_schedulesDjango management command to bulk create/update Stripe subscription schedules that transition (optionally) to a target plan and then cancel at a specified end date, with filtering, batching, idempotent skip behavior, and--dry-runsupport.Introduces shared schedule
task_signatureconstants and a reusable_create_end_date_schedulehelper, and updatesStripeService.modify_subscriptionso upgrades that release schedules will detect prior cancellation schedules and recreate/restore the end-date schedule (including recovery when the upgrade Stripe call fails). Includes comprehensive unit tests for the command and the new upgrade/schedule-recreation behaviors.Written by Cursor Bugbot for commit a526ac1. This will update automatically on new commits. Configure here.