-
Notifications
You must be signed in to change notification settings - Fork 3
Add new subscription plan: Standard #827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughThe changes add tiered pricing across billing and payments: a new PricingTier type and tier-aware PricingPlan methods; Subscription gains a nullable plan_tier_key and auto_recharge_enabled default flips to False. Billing UI, Stripe/PayPal flows, and webhooks propagate tier_key through product/line-item IDs, metadata, and upgrade/downgrade dialogs. Auto-recharge is gated by Workspace.allow_credit_topups(), a new helper that checks Enterprise subscription or prior top-ups. Several migrations add/alter subscription and transaction plan fields. A script disables auto-recharge for restricted workspaces in batches. Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Areas to review closely:
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
daras_ai_v2/billing.py(3 hunks)payments/auto_recharge.py(1 hunks)payments/migrations/0007_alter_subscription_auto_recharge_enabled.py(1 hunks)payments/models.py(1 hunks)payments/plans.py(1 hunks)scripts/disable_auto_recharge_for_restricted_workspaces.py(1 hunks)workspaces/models.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
payments/auto_recharge.py (1)
workspaces/models.py (1)
allow_credit_topups(223-247)
daras_ai_v2/billing.py (2)
daras_ai_v2/grid_layout_widget.py (1)
grid_layout(25-38)workspaces/models.py (1)
allow_credit_topups(223-247)
workspaces/models.py (2)
app_users/models.py (2)
AppUserTransaction(277-396)TransactionReason(265-274)payments/plans.py (2)
PricingPlan(51-405)from_sub(282-283)
scripts/disable_auto_recharge_for_restricted_workspaces.py (3)
app_users/models.py (2)
AppUserTransaction(277-396)TransactionReason(265-274)payments/models.py (1)
Subscription(37-390)payments/plans.py (1)
PricingPlan(51-405)
🪛 Ruff (0.14.1)
payments/migrations/0007_alter_subscription_auto_recharge_enabled.py
8-10: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
12-18: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Analyze (python)
- GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (8)
payments/models.py (1)
48-48: LGTM - Default change aligns with new permission model.The change to
default=Falseforauto_recharge_enabledaligns with the PR's goal to disable auto-recharge for non-Enterprise users without top-up history. The migration handles existing records appropriately.payments/migrations/0007_alter_subscription_auto_recharge_enabled.py (1)
1-18: LGTM - Migration correctly alters field default.The migration properly changes the
auto_recharge_enabledfield default toFalse. The static analysis hints aboutClassVarare false positives - Django migration attributes (dependenciesandoperations) should not be type-annotated as they are framework-specific class attributes.scripts/disable_auto_recharge_for_restricted_workspaces.py (1)
24-91: LGTM - Script correctly implements batch updates for restricted workspaces.The script logic is sound:
- Uses an efficient
Existssubquery to check for top-up history (lines 29-32)- Correctly filters for non-Enterprise subscriptions with auto-recharge enabled and no top-up history (lines 38-49)
- Batch processing with lazy QuerySet evaluation ensures updated records are excluded from subsequent batches (lines 71-91)
The alignment with
workspace.allow_credit_topups()ensures consistency across the codebase.payments/auto_recharge.py (1)
31-38: LGTM - Auto-recharge prerequisite correctly enforces workspace capability.The addition of
workspace.allow_credit_topups()as a prerequisite for auto-recharge ensures that only Enterprise workspaces or those with prior top-up history can use this feature. The placement as the first condition enables short-circuit evaluation for efficiency.daras_ai_v2/billing.py (3)
187-187: LGTM - Simplified to fixed 3-column layout.The change from dynamic column count to a fixed 3-column grid simplifies the rendering logic while maintaining appropriate display for all plans.
524-527: LGTM - Addon section properly gated by workspace capability.The early return when
workspace.allow_credit_topups()isFalsecorrectly hides the entire addon purchase section for workspaces without top-up permissions. This aligns with the auto-recharge prerequisite changes inpayments/auto_recharge.py.
921-924: LGTM - Auto-recharge section properly gated by workspace capability.The guard correctly hides the auto-recharge configuration UI for restricted workspaces. This ensures UI consistency with the backend logic in
payments/auto_recharge.pythat enforces the same capability check.workspaces/models.py (1)
223-247: LGTM - Workspace capability method correctly implements top-up permissions.The
allow_credit_topups()method properly defines workspace-level permissions for credit top-ups:
- Enterprise workspaces always have access ✓
- Workspaces with prior top-up history (ADDON or AUTO_RECHARGE transactions) retain access ✓
- All others are restricted ✓
The implementation efficiently checks the Enterprise case first before querying transaction history, and uses local imports to avoid circular dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
daras_ai_v2/billing.py(25 hunks)payments/migrations/0008_add_plan_tier_key.py(1 hunks)payments/models.py(3 hunks)payments/plans.py(9 hunks)payments/webhooks.py(3 hunks)recipes/VideoBots.py(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-09T06:59:25.756Z
Learnt from: nikochiko
PR: GooeyAI/gooey-server#757
File: daras_ai_v2/safety_checker.py:68-71
Timestamp: 2025-08-09T06:59:25.756Z
Learning: In the capture_openai_content_policy_violation context manager in daras_ai_v2/safety_checker.py, only openai.BadRequestError exceptions with specific error codes ("content_policy_violation" or "content_filter") should be converted to UserError. Other OpenAI exceptions like APIStatusError should not be converted to UserError and should be allowed to propagate unchanged.
Applied to files:
recipes/VideoBots.py
🧬 Code graph analysis (5)
payments/webhooks.py (1)
payments/plans.py (2)
PricingPlan(86-540)get_by_stripe_product(377-419)
recipes/VideoBots.py (1)
daras_ai_v2/exceptions.py (1)
UserError(58-65)
payments/models.py (1)
payments/plans.py (4)
PricingPlan(86-540)from_sub(366-367)get_active_credits(67-71)get_active_monthly_charge(73-77)
payments/plans.py (1)
payments/utils.py (1)
make_stripe_recurring_plan(4-31)
daras_ai_v2/billing.py (5)
payments/plans.py (11)
get_active_monthly_charge(73-77)get_active_credits(67-71)label(29-30)PricingPlan(86-540)get_default_tier_key(79-83)get_tier(61-65)get_pricing_title(49-53)get_pricing_caption(55-59)from_sub(366-367)get_stripe_line_item(439-460)supports_stripe(433-434)daras_ai_v2/grid_layout_widget.py (1)
grid_layout(25-38)app_users/models.py (1)
PaymentProvider(85-87)payments/models.py (3)
is_paid(145-146)cancel(157-184)Subscription(37-399)workspaces/models.py (2)
cancel(754-757)allow_credit_topups(223-247)
🪛 Ruff (0.14.2)
payments/webhooks.py
173-175: Create your own exception
(TRY002)
173-175: Avoid specifying long messages outside the exception class
(TRY003)
payments/plans.py
441-441: Avoid specifying long messages outside the exception class
(TRY003)
payments/migrations/0008_add_plan_tier_key.py
8-10: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
12-39: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
daras_ai_v2/billing.py
510-510: Unused function argument: session
(ARG001)
511-511: Unused function argument: user
(ARG001)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (19)
payments/models.py (3)
39-44: LGTM!The
plan_tier_keyfield is properly defined with nullable constraints to maintain backward compatibility for existing subscriptions without tiers.
103-110: LGTM!The tier-aware pricing logic is correctly implemented:
- Plan instance is created once and reused efficiently
- Tier key is properly passed to retrieve tier-specific credits and charges
- Falls back gracefully to default values when tier_key is None
54-54: Default change properly addressed through migration and targeted scripts.Verification confirms the implementation is sound. The default change from
TruetoFalseis applied via migration 0007_alter_subscription_auto_recharge_enabled.py, which only affects new subscription records. Existing subscriptions are preserved as-is, with selective updates handled bydisable_auto_recharge_for_restricted_workspaces.py(only non-Enterprise plans without topup history are modified). Code dependencies are defensive—auto_recharge.py safely checks the flag before use—and users can toggle the setting via the billing UI. No code updates or breaking changes identified.payments/migrations/0008_add_plan_tier_key.py (1)
1-39: LGTM!The migration correctly adds the
plan_tier_keyfield and updates plan choices to include the new STANDARD plan. The structure follows Django migration conventions.Note: The static analysis hints about
ClassVarannotations are false positives - Django migrations are expected to definedependenciesandoperationsas class attributes in this exact format.payments/webhooks.py (2)
168-189: LGTM with tier extraction logic correctly implemented.The code properly:
- Retrieves the product with expanded price information
- Handles the new tuple return from
get_by_stripe_product- Prefers tier_key from product match over metadata
- Falls back gracefully to metadata when product doesn't specify a tier
The error handling is appropriate for missing plan mappings.
267-278: LGTM!The
plan_tier_keyparameter is properly added with a default value ofNonefor backward compatibility, and correctly assigned to the subscription instance.payments/plans.py (6)
23-31: LGTM!The
PricingTierNamedTuple is well-designed with appropriate fields and a cleanlabelproperty for display purposes.
47-83: LGTM!The tier-aware methods are correctly implemented with proper fallback behavior:
get_tier()safely handles None and missing keysget_active_credits()andget_active_monthly_charge()use the walrus operator elegantlyget_default_tier_key()provides a sensible default (first tier)
170-204: LGTM!The STARTER plan updates improve clarity by:
- Renaming to "Free" to better communicate the pricing
- Updating descriptions to reflect current features
- Adding a clear pricing caption about starting credits
These are appropriate marketing/content improvements.
377-419: LGTM!The
get_by_stripe_productmethod correctly:
- Returns a tuple
(plan, tier_key)for consistency- Handles legacy products with backward compatibility
- Matches tiered plans by name and price
- Falls back gracefully when price information is unavailable
- Returns
Nonewhen no match is foundThe tier key extraction logic is sound and aligns with product creation in
get_stripe_product_id.
439-460: LGTM!The method correctly:
- Accepts an optional
tier_keyparameter- Uses
get_active_credits()andget_active_monthly_charge()for tier-aware pricing- Propagates the tier_key to
get_stripe_product_id()for product resolution
472-500: LGTM!The tier-aware product ID resolution:
- Builds appropriate product names including tier labels
- Matches existing products by both name and price
- Creates new products when needed with tier-specific pricing
- Maintains consistency with
get_by_stripe_productmatching logicdaras_ai_v2/billing.py (7)
83-87: LGTM!The current plan display correctly retrieves tier-specific pricing using
get_active_monthly_charge()andget_active_credits().
159-165: LGTM!The plan filtering correctly:
- Excludes deprecated plans
- Restricts STANDARD plan to personal workspaces only (business logic requirement)
This ensures team workspaces are guided toward Business/Enterprise plans.
212-273: LGTM with tier selection UI properly implemented.The tier selection logic:
- Only displays tier dropdown for Stripe subscriptions (line 235)
- Uses current subscription's tier as default when available
- Falls back to plan's default tier for new subscriptions
- Updates pricing display based on selected tier
Note: PayPal subscriptions don't support tier selection, which is consistent with PayPal's simpler subscription model.
301-320: LGTM!The tier change detection correctly:
- Identifies when a different tier is selected within the STANDARD plan
- Delegates to
_render_tier_change_buttonfor upgrade/downgrade UI- Shows "Your Plan" button when the selected tier matches current tier
346-369: LGTM!The STANDARD plan button logic:
- Correctly handles both paid and free plan states
- Always uses "Upgrade" label for consistent UX
- Properly propagates
selected_tier_keythrough the flow
733-738: LGTM!The addon section is correctly gated by
workspace.allow_credit_topups(), which aligns with the PR objective to disable addons for new non-enterprise users.
1144-1146: LGTM!The auto-recharge section is consistently gated using the same
workspace.allow_credit_topups()check as the addon section.
| if is_enterprise: | ||
| gui.write( | ||
| f"As a premium customer, please contact us to setup a managed number" | ||
| "As a premium customer, please contact us to setup a managed number" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix grammatical error: "setup" → "set up".
When used as a verb phrase, "set up" should be two words. Line 1980 correctly uses "set up" in a similar context. This change maintains consistency across the file.
Apply this diff to fix the grammar:
- "As a premium customer, please contact us to setup a managed number"
+ "As a premium customer, please contact us to set up a managed number"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "As a premium customer, please contact us to setup a managed number" | |
| "As a premium customer, please contact us to set up a managed number" |
🤖 Prompt for AI Agents
In recipes/VideoBots.py around line 1939, the string uses the incorrect
noun-form "setup" instead of the verb phrase "set up"; change "As a premium
customer, please contact us to setup a managed number" to "As a premium
customer, please contact us to set up a managed number" so it matches correct
grammar and the usage on line 1980.
d073dba to
d1024df
Compare
d1024df to
ac20427
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
daras_ai_v2/billing.py(18 hunks)daras_ai_v2/settings.py(1 hunks)payments/plans.py(11 hunks)payments/webhooks.py(5 hunks)routers/paypal.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
routers/paypal.py (1)
payments/plans.py (3)
PricingPlan(88-542)get_by_key(429-433)supports_paypal(438-439)
daras_ai_v2/billing.py (6)
payments/plans.py (11)
get_active_monthly_charge(76-80)get_active_credits(70-74)get_pricing_title(50-55)label(29-30)PricingPlan(88-542)from_sub(373-376)get_default_tier_key(82-85)get_pricing_caption(57-62)get_tier(64-68)get_stripe_line_item(441-462)supports_stripe(435-436)app_users/models.py (2)
PaymentProvider(85-87)cached_workspaces(242-249)daras_ai_v2/grid_layout_widget.py (1)
grid_layout(25-38)routers/account.py (2)
account_route(99-111)payment_processing_route(38-84)payments/models.py (1)
Subscription(37-399)workspaces/models.py (1)
allow_credit_topups(223-247)
payments/webhooks.py (1)
payments/plans.py (3)
PricingPlan(88-542)get_by_key(429-433)get_by_stripe_product(386-420)
payments/plans.py (1)
payments/utils.py (1)
make_stripe_recurring_plan(4-31)
🪛 Ruff (0.14.2)
payments/plans.py
85-85: Avoid specifying long messages outside the exception class
(TRY003)
403-403: Loop control variable tier_key not used within loop body
Rename unused tier_key to _tier_key
(B007)
433-433: Avoid specifying long messages outside the exception class
(TRY003)
443-443: Avoid specifying long messages outside the exception class
(TRY003)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (10)
routers/paypal.py (1)
123-129: LGTM! Improved error handling for plan validation.The refactored validation logic provides better separation of concerns with distinct error messages for invalid plan keys versus PayPal support issues, making debugging easier for API consumers.
payments/plans.py (2)
23-86: Well-designed tier-aware pricing structure.The
PricingTierclass and tier-aware accessor methods (get_tier,get_active_credits,get_active_monthly_charge) provide a clean abstraction for handling multi-tier plans while maintaining backward compatibility with single-tier plans through sensible defaults.
396-420: Remove unusedtier_keyunpacking; current return signature is sufficient.The
tier_keyvariable on line 403 is genuinely unused—onlytieris referenced in the loop body. This is not a false positive. Both callers (webhooks.py and migrate_existing_subscriptions.py) obtain tier context elsewhere if needed (webhooks.py retrieves it fromstripe_sub.metadata) and don't require tier information fromget_by_stripe_product.Simplify line 403 to
for tier in plan.tiers.values():to eliminate the dead code.Likely an incorrect or invalid review comment.
daras_ai_v2/settings.py (1)
392-392: LGTM! Constant addition for tier metadata.The new constant follows the existing pattern for Stripe metadata fields and enables tier-aware subscription handling.
payments/webhooks.py (1)
187-195: LGTM! Safe tier_key extraction and propagation.The tier_key extraction uses
.get()with appropriate None handling, and the value is correctly passed through toset_workspace_subscription.daras_ai_v2/billing.py (5)
81-142: LGTM! Tier-aware current plan rendering.The tier_key extraction from the subscription and tier-aware pricing display using
get_active_monthly_chargeandget_active_creditscorrectly handles both tiered and non-tiered plans.
154-219: Well-structured tier-aware plan rendering.The filtering of STANDARD plan for non-personal workspaces (lines 157-158) and the separation of grid vs. full-width plans provides good UX differentiation. The tier context is properly propagated through the rendering pipeline.
626-631: LGTM! Appropriate gating of credit topups.The
allow_credit_topups()check correctly hides the addon section for workspaces that shouldn't have access to credit purchases, aligning with the PR objective to disable addons for new non-enterprise users.
768-870: Tier-aware subscription creation logic looks solid.The tier_key is correctly propagated through Stripe metadata and line items, and the pricing calculations use
get_active_monthly_chargeandget_active_creditsto handle tier-specific values.
1033-1039: LGTM! Consistent gating of auto-recharge feature.The
allow_credit_topups()check correctly hides the auto-recharge section, consistent with the addon gating and the PR objective to disable auto-recharge for new non-enterprise users.
| if plan > current_plan or ( | ||
| plan == current_plan | ||
| and plan.tiers | ||
| and ( | ||
| plan.get_tier(selected_tier_key).monthly_charge | ||
| > plan.get_tier(current_tier_key).monthly_charge | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add defensive check for tier lookup in comparison logic.
Lines 400-401 directly access .monthly_charge after get_tier(), which returns PricingTier | None. If the subscription contains an invalid current_tier_key (e.g., from database migration or stale data), this will raise an AttributeError.
Apply this diff to add defensive handling:
if plan > current_plan or (
plan == current_plan
and plan.tiers
+ and (current_tier := plan.get_tier(current_tier_key))
+ and (selected_tier := plan.get_tier(selected_tier_key))
and (
- plan.get_tier(selected_tier_key).monthly_charge
- > plan.get_tier(current_tier_key).monthly_charge
+ selected_tier.monthly_charge > current_tier.monthly_charge
)
):📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if plan > current_plan or ( | |
| plan == current_plan | |
| and plan.tiers | |
| and ( | |
| plan.get_tier(selected_tier_key).monthly_charge | |
| > plan.get_tier(current_tier_key).monthly_charge | |
| ) | |
| if plan > current_plan or ( | |
| plan == current_plan | |
| and plan.tiers | |
| and (current_tier := plan.get_tier(current_tier_key)) | |
| and (selected_tier := plan.get_tier(selected_tier_key)) | |
| and ( | |
| selected_tier.monthly_charge > current_tier.monthly_charge | |
| ) |
🤖 Prompt for AI Agents
In daras_ai_v2/billing.py around lines 396 to 402, the code calls
plan.get_tier(...).monthly_charge directly even though get_tier can return None;
fetch both tiers into local variables (selected_tier =
plan.get_tier(selected_tier_key), current_tier =
plan.get_tier(current_tier_key)), check that they are not None before accessing
.monthly_charge, and if either is None handle defensively (e.g., log a warning
about the missing tier and treat its monthly_charge as 0 or skip the tier-based
comparison) so the comparison never raises an AttributeError.
| try: | ||
| plan = PricingPlan.get_by_key( | ||
| stripe_sub.metadata[settings.STRIPE_USER_SUBSCRIPTION_METADATA_FIELD] | ||
| ) | ||
| except KeyError: | ||
| product = stripe.Product.retrieve( | ||
| stripe_sub.plan.product, expand=["default_price"] | ||
| ) | ||
| plan = PricingPlan.get_by_stripe_product(product) | ||
| assert plan is not None, f"Plan for product {product.id} not found" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify the assertion handles all Stripe subscription scenarios.
The fallback logic asserts that a plan is found when metadata is missing, but this could fail if Stripe contains legacy subscriptions or products that don't match current plan definitions. Consider logging a warning and handling the None case more gracefully.
🏁 Script executed:
#!/bin/bash
# Check if there are other places where missing plan lookups are handled
rg -n -B3 -A3 'get_by_stripe_product|get_by_paypal_plan_id' --type=pyLength of output: 2856
---
🏁 Script executed:
```shell
#!/bin/bash
# Check the webhook handler function and surrounding error handling
rg -n -B10 "def.*webhook" payments/webhooks.py | head -100
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check if there's try-catch wrapping around the get_by_stripe_product call
rg -n -B10 -A5 "stripe_sub.plan.product" payments/webhooks.pyLength of output: 1046
🏁 Script executed:
#!/bin/bash
# Search for any custom exception handling or logging patterns in webhooks
rg -n "except|logger|logging" payments/webhooks.py | head -40Length of output: 444
---
🏁 Script executed:
```shell
#!/bin/bash
# Find where handle_subscription_updated is called and any wrapping error handlers
rg -n "handle_subscription_updated" --type=py
Length of output: 727
🏁 Script executed:
#!/bin/bash
# Check if there's a Flask/Django error handler or middleware
rg -n "error_handler|errorhandler|middleware|@app|@route" payments/webhooks.py | head -20Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look at the start of the file to understand the context (imports, decorators, flask/django setup)
head -50 payments/webhooks.pyLength of output: 1822
---
🏁 Script executed:
```shell
#!/bin/bash
# Check the Stripe webhook endpoint for error handling
rg -n -B5 -A10 "handle_subscription_updated" routers/stripe.py
Length of output: 582
🏁 Script executed:
#!/bin/bash
# Check the PayPal webhook endpoint for error handling
rg -n -B5 -A10 "handle_subscription_updated" routers/paypal.pyLength of output: 705
🏁 Script executed:
#!/bin/bash
# Check if there's a catch-all error handler in the Django app
fd -e py -x rg -l "exception_handler|handle_exception|catch_all|@app" {} \; routers/ | head -20Length of output: 337
---
🏁 Script executed:
```shell
#!/bin/bash
# Get full function context from stripe router
rg -n -B20 "handle_subscription_updated" routers/stripe.py | head -80
Length of output: 932
🏁 Script executed:
#!/bin/bash
# Get full function context from paypal router
rg -n -B20 "handle_subscription_updated" routers/paypal.py | head -80Length of output: 1136
🏁 Script executed:
#!/bin/bash
# Check for exception handlers in main app config
fd . routers/ --name '*.py' -x grep -l "exception_handler\|@app\|def.*webhook" {} \;Length of output: 232
---
🏁 Script executed:
```shell
#!/bin/bash
# Get the full Stripe webhook function definition
rg -n -B40 "handle_subscription_updated" routers/stripe.py | grep -A40 "def webhook"
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check if Stripe webhook has any try-catch wrapping the match statement
rg -n "def webhook" routers/stripe.py -A50Length of output: 1999
🏁 Script executed:
#!/bin/bash
# Look for any global exception handlers or middleware in FastAPI setup
rg -n "exception_handler|ExceptionMiddleware|@app.exception_handler" --type=pyLength of output: 498
---
🏁 Script executed:
```shell
#!/bin/bash
# Check the global exception handler implementation
rg -n -A10 "async def server_error_exception_handler" server.py
Length of output: 460
🏁 Script executed:
#!/bin/bash
# Verify that AssertionError will be caught by the Exception handler
rg -n -B5 -A15 "def server_error_exception_handler" server.pyLength of output: 890
Add graceful error handling for missing plan lookups in Stripe webhook handler.
The assertion at line 179 will crash the webhook on legacy subscriptions or products without matching plan definitions. Unlike the migration script which gracefully logs and skips missing plans (lines 115-117 in scripts/migrate_existing_subscriptions.py), the webhook crashes with a 5xx error that Stripe will retry indefinitely.
Replace the assertion with logging and early return:
plan = PricingPlan.get_by_stripe_product(product)
if plan is None:
logger.warning(f"Plan not found for Stripe product {product.id}")
return # Acknowledge webhook to prevent retriesAlso apply the same fix to PayPal webhook handlers at lines 33-34 and 62-63, which have identical assertion patterns without logging.
🤖 Prompt for AI Agents
In payments/webhooks.py around lines 170 to 179 the code asserts that a
PricingPlan exists for a Stripe product which will raise an exception and crash
the webhook for legacy/missing plans; replace the assertion with a null-check
that logs a warning and returns early to acknowledge the webhook (e.g., if plan
is None: logger.warning(f"Plan not found for Stripe product {product.id}");
return). Also apply the same change to the PayPal webhook handlers at lines
~33-34 and ~62-63: remove the assertions, add a warning log indicating the
missing plan with the product/subscription id, and return early so the webhook
responds 200 and Stripe/PayPal will not retry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
daras_ai_v2/billing.py (1)
384-390: Add defensive checks for tier lookups to prevent AttributeError.Lines 388-390 directly access
.monthly_chargeafterget_tier(), which returnsPricingTier | None. If eitherselected_tier_keyorcurrent_tier_keyis invalid (e.g., from stale data or migration issues), this will raise anAttributeError.Based on the past review comment, this was supposed to be addressed but the defensive handling is still missing.
Apply this diff to add defensive handling:
if plan > current_plan or ( plan == current_plan and plan.tiers + and (current_tier := plan.get_tier(current_tier_key)) + and (selected_tier := plan.get_tier(selected_tier_key)) and ( - plan.get_tier(selected_tier_key).monthly_charge - > plan.get_tier(current_tier_key).monthly_charge + selected_tier.monthly_charge > current_tier.monthly_charge ) ):
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
daras_ai_v2/billing.py(17 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
daras_ai_v2/billing.py (6)
payments/plans.py (10)
get_active_monthly_charge(76-80)get_active_credits(70-74)get_pricing_title(50-55)PricingPlan(88-542)from_sub(373-376)get_default_tier_key(82-85)get_pricing_caption(57-62)get_tier(64-68)get_stripe_line_item(441-462)supports_stripe(435-436)app_users/models.py (1)
PaymentProvider(85-87)daras_ai_v2/grid_layout_widget.py (1)
grid_layout(25-38)routers/account.py (2)
account_route(99-111)payment_processing_route(38-84)payments/models.py (1)
Subscription(37-399)workspaces/models.py (1)
allow_credit_topups(223-247)
🔇 Additional comments (12)
daras_ai_v2/billing.py (12)
81-142: LGTM: Tier-aware pricing display is correctly implemented.The function properly retrieves the tier key from the subscription and consistently uses tier-aware accessors (
get_active_monthly_charge,get_active_credits,get_pricing_title) to display pricing information.
168-168: Verify the default payment provider change.The default payment provider has been changed to
PaymentProvider.STRIPEfor workspaces without an existing subscription. Ensure this aligns with your product requirements, especially if PayPal was previously the default or if this impacts user experience in specific regions.
243-275: LGTM: Tier selector logic is well-structured.The function correctly:
- Displays tier selector only when plan has tiers and Stripe is the payment provider
- Preserves current tier selection for existing subscriptions
- Falls back to plan defaults appropriately
- Returns the selected tier key for downstream use
278-338: LGTM: Action button logic correctly handles tier changes.The function properly distinguishes between plan changes and tier changes within the same plan, routing to appropriate handlers with the selected tier key.
422-487: LGTM: Upgrade flow correctly handles tier-aware pricing and STANDARD plan specifics.The function properly:
- Uses safe tier-aware accessors (
get_active_monthly_charge,get_active_credits) that handle None gracefully- Distinguishes STANDARD plan from other plans for workspace creation flow
- Threads tier context through upgrade and fallback paths
489-523: LGTM: Create subscription flow handles plan-specific behavior correctly.The function appropriately:
- Differentiates UI labels for STANDARD vs other plans
- Routes personal workspace users away from workspace creation for STANDARD plan
- Passes tier context to Stripe (PayPal doesn't support tiers)
525-529: LGTM: Price formatting helper is simple and correct.
532-600: LGTM: Subscription change flow properly integrates tier context.The function correctly:
- Validates that plan or tier has actually changed before proceeding
- Includes tier information in Stripe metadata using the configured metadata field
- Passes tier context through to line item generation
- Appropriately omits tier handling for PayPal (which doesn't support tiers)
614-636: LGTM: Addon section correctly gated by workspace capability.The early return at lines 618-619 appropriately hides the entire addon purchase section for workspaces that don't have credit topup privileges (new non-Enterprise users without topup history).
756-801: LGTM: Stripe subscription button correctly handles tier-aware pricing.The function properly retrieves tier-specific pricing and passes the tier context through all subscription creation paths.
803-858: LGTM: Stripe subscription creation consistently includes tier context.The function properly:
- Conditionally adds tier to metadata when provided
- Uses configured metadata field name for consistency
- Passes tier context to line item generation
- Includes metadata in both direct subscription creation and checkout flows
1021-1103: LGTM: Auto-recharge section correctly gated by workspace capability.The early return at lines 1026-1027 appropriately hides the auto-recharge configuration section for workspaces without topup privileges, consistent with the addon section gating.
| def _render_plan_details(plan: PricingPlan): | ||
| with gui.div(className="flex-grow-1 d-flex flex-column"): | ||
| with gui.div(): | ||
| with gui.tag("h2", className="mb-1"): | ||
| gui.html(plan.title) | ||
| gui.caption( | ||
| plan.description, | ||
| style={ | ||
| "minHeight": "calc(var(--bs-body-line-height) * 2em)", | ||
| "display": "block", | ||
| }, | ||
| """Render plan details and return selected tier key if plan has tiers""" | ||
| with gui.div(): | ||
| gui.write(plan.long_description, unsafe_allow_html=True) | ||
| with gui.div(className="mt-3"): | ||
| gui.write(plan.footer, unsafe_allow_html=True) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix misleading docstring.
The docstring claims the function returns a selected tier key, but the function doesn't return anything. The tier selection logic was moved to _render_plan_pricing.
Apply this diff to correct the docstring:
-def _render_plan_details(plan: PricingPlan):
- """Render plan details and return selected tier key if plan has tiers"""
+def _render_plan_details(plan: PricingPlan):
+ """Render plan description and footer"""📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def _render_plan_details(plan: PricingPlan): | |
| with gui.div(className="flex-grow-1 d-flex flex-column"): | |
| with gui.div(): | |
| with gui.tag("h2", className="mb-1"): | |
| gui.html(plan.title) | |
| gui.caption( | |
| plan.description, | |
| style={ | |
| "minHeight": "calc(var(--bs-body-line-height) * 2em)", | |
| "display": "block", | |
| }, | |
| """Render plan details and return selected tier key if plan has tiers""" | |
| with gui.div(): | |
| gui.write(plan.long_description, unsafe_allow_html=True) | |
| with gui.div(className="mt-3"): | |
| gui.write(plan.footer, unsafe_allow_html=True) | |
| def _render_plan_details(plan: PricingPlan): | |
| """Render plan description and footer""" | |
| with gui.div(): | |
| gui.write(plan.long_description, unsafe_allow_html=True) | |
| with gui.div(className="mt-3"): | |
| gui.write(plan.footer, unsafe_allow_html=True) |
🤖 Prompt for AI Agents
In daras_ai_v2/billing.py around lines 222 to 228, the function
_render_plan_details has a misleading docstring that says it "return[s] selected
tier key if plan has tiers" even though it returns nothing (tier selection was
moved to _render_plan_pricing); update the docstring to accurately describe
behavior (e.g., "Render plan details" or "Render plan details; this does not
return a tier key — tier selection is handled in _render_plan_pricing") so the
docstring matches the function's actual behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
daras_ai_v2/billing.py (2)
222-228: Fix misleading docstring.The docstring claims the function returns a selected tier key, but the function doesn't return anything. The tier selection logic was moved to
_render_plan_pricing.Apply this diff to correct the docstring:
def _render_plan_details(plan: PricingPlan): - """Render plan details and return selected tier key if plan has tiers""" + """Render plan description and footer"""
384-390: Add defensive check for tier lookup in comparison logic.Lines 388-390 directly access
.monthly_chargeafterget_tier(), which returnsPricingTier | None. If the subscription contains an invalidplan_tier_key(e.g., from database migration or stale data), this will raise anAttributeError.Apply this diff to add defensive handling:
if plan > current_plan or ( plan == current_plan and plan.tiers + and (current_tier := plan.get_tier(current_tier_key)) + and (selected_tier := plan.get_tier(selected_tier_key)) and ( - plan.get_tier(selected_tier_key).monthly_charge - > plan.get_tier(current_tier_key).monthly_charge + selected_tier.monthly_charge > current_tier.monthly_charge ) ):
🧹 Nitpick comments (1)
daras_ai_v2/billing.py (1)
433-433: Consider clarifying comment about workspace creation popup logic.The comments state "Standard plan is only for personal workspaces, skip workspace creation popup," but the condition
workspace.is_personal and plan != PricingPlan.STANDARDshows the workspace creation popup for non-Standard plans. The comment could more clearly explain that the popup is shown for personal workspaces upgrading to non-Standard plans (Business, Enterprise).Consider this clearer wording:
- # Standard plan is only for personal workspaces, skip workspace creation popup + # For personal workspaces upgrading to non-Standard plans, show workspace creation popup if workspace.is_personal and plan != PricingPlan.STANDARD:Also applies to: 495-495
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
daras_ai_v2/billing.py(17 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
daras_ai_v2/billing.py (6)
payments/plans.py (10)
get_active_monthly_charge(76-80)get_active_credits(70-74)get_pricing_title(50-55)PricingPlan(88-542)from_sub(373-376)get_default_tier_key(82-85)get_pricing_caption(57-62)get_tier(64-68)get_stripe_line_item(441-462)supports_stripe(435-436)app_users/models.py (1)
PaymentProvider(85-87)daras_ai_v2/grid_layout_widget.py (1)
grid_layout(25-38)routers/account.py (2)
account_route(99-111)payment_processing_route(38-84)payments/models.py (1)
Subscription(37-399)workspaces/models.py (1)
allow_credit_topups(223-247)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: test (3.10.12, 1.8.3)
- GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (9)
daras_ai_v2/billing.py (9)
83-87: LGTM!The tier-aware pricing retrieval is correctly implemented. The
get_active_monthly_chargeandget_active_creditsmethods properly handleNonetier keys and fall back to default values.
230-240: LGTM!Good refactoring to extract the plan heading into a separate function. The consistent minimum height ensures proper grid alignment across plan cards.
243-275: LGTM!The tier selection logic is well-structured:
- Properly defaults to the subscription's current tier when viewing the active plan
- Falls back to the plan's default tier for new plan selections
- Correctly restricts tier selection to Stripe payment provider only
610-612: LGTM!The guard properly restricts addon purchases to Enterprise workspaces or those with existing topup history, aligning with the PR objective to disable addons for new non-enterprise users.
749-793: LGTM!The Stripe subscription button correctly propagates tier information through the payment flow and displays tier-specific pricing in the confirmation modal.
796-851: LGTM!The Stripe subscription creation properly:
- Includes tier information in metadata for webhook processing
- Uses tier-aware line items from
get_stripe_line_item(tier_key)- Propagates metadata through both direct subscription and checkout flows
1018-1020: LGTM!The guard consistently restricts auto-recharge configuration to Enterprise workspaces or those with existing topup history, matching the addon section's behavior and fulfilling the PR objectives.
525-593: LGTM!The subscription change logic properly:
- Checks if both plan and tier are unchanged to prevent no-op updates
- Includes tier information in Stripe metadata
- Uses tier-aware line items
- Correctly handles the Stripe-only tier support (PayPal branch unchanged as expected)
518-522: All callers offmt_priceare properly updated.The verification found two call sites (lines 407 and 455), both passing integer arguments (
current_monthly_chargeandnew_monthly_charge) that match the new signaturefmt_price(monthly_charge: int) -> str. No incompatible calls were found.
a31142d to
a6a26f1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
daras_ai_v2/billing.py (2)
282-288: Fix misleading docstring.The docstring still incorrectly states the function returns a selected tier key, but the function returns
None. Tier selection was moved to_render_plan_pricing.Apply this diff to correct the docstring:
def _render_plan_details(plan: PricingPlan): - """Render plan details and return selected tier key if plan has tiers""" + """Render plan description and footer"""
444-451: Add defensive check for tier lookup in comparison logic.Lines 448-449 directly access
.monthly_chargeafterget_tier(), which returnsPricingTier | None. If the subscription contains an invalidcurrent_tier_keyorselected_tier_key, this will raise anAttributeError.Apply this diff to add defensive handling:
if plan > current_plan or ( plan == current_plan and plan.tiers + and (current_tier := plan.get_tier(current_tier_key)) + and (selected_tier := plan.get_tier(selected_tier_key)) and ( - plan.get_tier(selected_tier_key).monthly_charge - > plan.get_tier(current_tier_key).monthly_charge + selected_tier.monthly_charge > current_tier.monthly_charge ) ):
🧹 Nitpick comments (1)
payments/plans.py (1)
382-416: Consider minor loop optimization.The loop variable
tier_keyat line 399 is not used in the loop body. Since only thetiervalues are needed, consider using.values()instead of.items()for clarity.Apply this diff if you'd like to simplify:
- for tier_key, tier in plan.tiers.items(): + for tier in plan.tiers.values(): expected_name = f"{plan.title} - {tier.label}"Otherwise, the tier-matching logic correctly handles both tiered and non-tiered plans.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app_users/migrations/0029_alter_appusertransaction_plan.py(1 hunks)daras_ai_v2/billing.py(17 hunks)payments/migrations/0009_alter_subscription_plan.py(1 hunks)payments/plans.py(10 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
app_users/migrations/0029_alter_appusertransaction_plan.py (2)
payments/migrations/0009_alter_subscription_plan.py (1)
Migration(6-29)app_users/migrations/0026_alter_appusertransaction_plan.py (1)
Migration(6-18)
payments/plans.py (1)
payments/utils.py (1)
make_stripe_recurring_plan(4-31)
daras_ai_v2/billing.py (5)
payments/plans.py (10)
get_active_monthly_charge(76-80)get_active_credits(70-74)get_pricing_title(50-55)PricingPlan(88-538)from_sub(369-372)get_default_tier_key(82-85)get_pricing_caption(57-62)get_tier(64-68)get_stripe_line_item(437-458)supports_stripe(431-432)app_users/models.py (1)
PaymentProvider(85-87)daras_ai_v2/grid_layout_widget.py (1)
grid_layout(25-38)payments/models.py (1)
Subscription(37-399)workspaces/models.py (1)
allow_credit_topups(223-247)
payments/migrations/0009_alter_subscription_plan.py (3)
app_users/migrations/0029_alter_appusertransaction_plan.py (1)
Migration(6-33)payments/migrations/0005_alter_subscription_plan.py (1)
Migration(6-18)payments/migrations/0006_alter_subscription_plan.py (1)
Migration(6-18)
🪛 Ruff (0.14.2)
app_users/migrations/0029_alter_appusertransaction_plan.py
8-10: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
12-33: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
payments/plans.py
85-85: Avoid specifying long messages outside the exception class
(TRY003)
399-399: Loop control variable tier_key not used within loop body
Rename unused tier_key to _tier_key
(B007)
429-429: Avoid specifying long messages outside the exception class
(TRY003)
439-439: Avoid specifying long messages outside the exception class
(TRY003)
payments/migrations/0009_alter_subscription_plan.py
8-10: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
12-29: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: test (3.10.12, 1.8.3)
- GitHub Check: Analyze (python)
- GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (13)
app_users/migrations/0029_alter_appusertransaction_plan.py (1)
1-33: LGTM! Migration correctly adds Standard plan.The migration properly updates the
AppUserTransaction.planfield choices to include the new Standard plan (8) and renames "Pay as you go" to "Free" (3), aligning with the updatedPricingPlanenum.payments/migrations/0009_alter_subscription_plan.py (1)
1-29: LGTM! Migration correctly updates Subscription plan choices.The migration properly adds the Standard plan (8) to the
Subscription.planfield choices and depends on the correct prior migration (0008_add_plan_tier_key), maintaining proper migration sequencing for the tier-aware pricing feature.daras_ai_v2/billing.py (5)
82-143: LGTM! Tier-aware current plan rendering.The function correctly retrieves the
tier_keyfrom the subscription and uses tier-aware pricing methods to display the appropriate monthly charge and credits. The tier-aware getters handleNonevalues safely.
152-192: LGTM! Correct STANDARD plan filtering for workspace types.The filtering logic correctly hides the STANDARD plan from non-personal workspaces (except when already subscribed to it), aligning with the business requirement that STANDARD is designed for personal workspaces. The new compact/full-width rendering structure with tier-aware pricing is well-implemented.
667-688: LGTM! Proper guard for addon section.The early return correctly prevents non-eligible workspaces from accessing the credit top-up functionality, aligning with the PR objective to disable auto-recharge/addons for new non-enterprise users. The check using
workspace.allow_credit_topups()ensures only Enterprise plans or workspaces with prior top-up history can access this section.
1074-1157: LGTM! Proper guard for auto-recharge section.The early return at lines 1079-1080 correctly prevents non-eligible workspaces from accessing auto-recharge functionality, consistent with the addon section guard and aligned with the PR objectives.
809-911: LGTM! Comprehensive tier_key propagation in Stripe subscription flow.The tier_key is correctly threaded through the Stripe subscription creation flow:
- Metadata properly includes tier information via
STRIPE_USER_SUBSCRIPTION_TIER_METADATA_FIELD- Line items use tier-aware pricing via
get_stripe_line_item(tier_key)- Tier-specific credits and monthly charges are displayed in the UI
payments/plans.py (6)
23-31: LGTM! Clean PricingTier type definition.The
PricingTierNamedTuple provides a clear structure for tier pricing with a convenientlabelproperty for consistent formatting.
47-86: LGTM! Robust tier-aware pricing methods.The tier-aware methods correctly handle
Nonetier keys and missing tiers by falling back to default values. Theget_tier()method properly returnsNonewhen tiers don't exist or aren't found, and downstream methods handle this safely.
208-267: LGTM! Well-structured STANDARD plan with comprehensive tiers.The STANDARD plan provides 6 pricing tiers ranging from $25 to $400/month, with credits scaling appropriately (2,000 to 44,000). The plan structure, content, and tier definitions align well with the "power users" target audience.
352-356: LGTM! Comparison logic correctly uses tier-aware pricing.The comparison correctly uses
get_active_monthly_charge()for ordering plans, which defaults to base tier pricing when notier_keyis provided. The ENTERPRISE exception is appropriately maintained.
437-458: LGTM! Stripe line item generation correctly uses tier-aware pricing.The method properly retrieves tier-specific credits and monthly charges, and correctly passes
tier_keytoget_stripe_product_id()for tiered plans.
470-498: LGTM! Tier-aware Stripe product ID generation.The method correctly builds tier-specific product names (e.g., "Standard - 2,000 Credits for $25/month") and searches/creates Stripe products with the appropriate pricing. The logic properly handles both tiered and non-tiered plans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
daras_ai_v2/billing.py (2)
282-288: Misleading docstring already flagged.The docstring claims this function returns a selected tier key, but it returns nothing. This issue was previously identified.
444-451: Defensive check for tier lookup already flagged.Lines 448-450 access
.monthly_chargedirectly afterget_tier(), which can returnNone. This AttributeError risk was previously identified.
🧹 Nitpick comments (3)
payments/plans.py (3)
82-85: Consider custom exception for better error handling.Static analysis suggests avoiding long messages directly in exception constructors. While the current implementation is functional, you could define a custom exception class if this pattern appears frequently.
Example refactor (if desired):
class NoTiersAvailableError(ValueError): def __init__(self, plan_key: str): super().__init__(f"No tiers available for plan {plan_key}") def get_default_tier_key(self) -> str: if self.tiers: return next(iter(self.tiers.keys())) raise NoTiersAvailableError(self.key)
382-416: Consider using_for unused loop variable.Static analysis notes that
tier_keyat line 399 is not used in the loop body. While this doesn't affect functionality, you could use_to signal the unused variable.Apply this diff if desired:
if plan.tiers: # Check each tier by matching product name - for tier_key, tier in plan.tiers.items(): + for _, tier in plan.tiers.items(): expected_name = f"{plan.title} - {tier.label}" expected_price = tier.monthly_charge * 100 # convert to centsOtherwise, the tier-aware product matching logic is well-structured and handles both tiered and non-tiered plans correctly.
425-429: Consider custom exception for consistency.Similar to line 85, static analysis suggests avoiding long messages in exception constructors. However, for this key lookup failure, the current implementation is clear and functional.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
daras_ai_v2/billing.py(18 hunks)payments/plans.py(10 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
payments/plans.py (1)
payments/utils.py (1)
make_stripe_recurring_plan(4-31)
daras_ai_v2/billing.py (4)
payments/plans.py (10)
get_active_monthly_charge(76-80)get_active_credits(70-74)get_pricing_title(50-55)PricingPlan(88-538)from_sub(369-372)get_default_tier_key(82-85)get_pricing_caption(57-62)get_tier(64-68)get_stripe_line_item(437-458)supports_stripe(431-432)daras_ai_v2/user_date_widgets.py (1)
render_local_date_attrs(30-38)payments/models.py (1)
Subscription(37-399)workspaces/models.py (1)
allow_credit_topups(223-247)
🪛 Ruff (0.14.2)
payments/plans.py
85-85: Avoid specifying long messages outside the exception class
(TRY003)
399-399: Loop control variable tier_key not used within loop body
Rename unused tier_key to _tier_key
(B007)
429-429: Avoid specifying long messages outside the exception class
(TRY003)
439-439: Avoid specifying long messages outside the exception class
(TRY003)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: test (3.10.12, 1.8.3)
- GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (21)
daras_ai_v2/billing.py (13)
82-143: LGTM! Tier-aware rendering in current plan display.The function correctly retrieves the tier key from the subscription and uses tier-aware pricing methods throughout. The next invoice date rendering and pricing display logic are well-structured.
152-192: LGTM! Plan filtering and rendering logic.The filtering correctly restricts STANDARD to personal workspaces (unless already subscribed), and the grid/full-width separation aligns with the new plan structure.
303-335: LGTM! Tier selection UI implementation.The tier selection logic correctly renders a selectbox for tiered plans when using Stripe, uses the current subscription's tier as default, and returns the selected tier key for downstream usage.
338-398: LGTM! Action button orchestration with tier support.The logic correctly handles tier changes within the same plan and delegates to appropriate subscription creation or change flows with tier context.
458-476: LGTM! Downgrade flow with tier-aware pricing.The downgrade confirmation dialog correctly displays tier-aware monthly charges using
get_active_monthly_chargeand passestier_keyto the subscription change function.
478-543: LGTM! Upgrade flow with tier-aware pricing.The upgrade confirmation dialog correctly retrieves and displays tier-aware monthly charges and credits. The tier_key is properly propagated through the subscription change flow. Error handling for payment failures was previously addressed.
545-576: LGTM! Subscription creation with tier support.The function correctly handles STANDARD as personal-only, passes tier_key through Stripe flows, and documents PayPal's lack of tier support.
578-582: LGTM! Simplified price formatting.The refactored signature accepting a numeric
monthly_chargeparameter supports tier-aware pricing nicely.
585-653: LGTM! Subscription change flow with tier support.The function correctly propagates tier_key through Stripe metadata and line items. The early return when both plan and tier are unchanged is a good optimization.
667-688: LGTM! Addon section gated by workspace allowance.The early return based on
workspace.allow_credit_topups()correctly implements the feature restriction for new non-enterprise users as per PR objectives.
809-854: LGTM! Stripe subscription button with tier support.The function correctly accepts tier_key, retrieves tier-aware pricing, and propagates the tier context through the subscription creation flow.
856-911: LGTM! Stripe subscription creation with tier support.The function properly accepts tier_key, includes it in Stripe metadata using the configured metadata field, and passes it through line item generation for both direct subscription and checkout flows.
1074-1157: LGTM! Auto-recharge section gated by workspace allowance.The early return based on
workspace.allow_credit_topups()correctly restricts auto-recharge access as per the PR objectives.payments/plans.py (8)
23-31: LGTM! Clean PricingTier definition.The NamedTuple structure is straightforward, and the label property provides a clear user-facing representation.
50-80: LGTM! Tier-aware pricing accessors.The accessor methods correctly handle tier lookups with appropriate fallbacks. The walrus operator usage in
get_active_creditsandget_active_monthly_chargeis clean and idiomatic.
208-267: LGTM! Comprehensive STANDARD plan definition.The plan is well-structured with six tiers offering a reasonable pricing progression. The description and footer accurately reflect the capabilities, and the tier naming convention is consistent.
172-206: LGTM! Improved STARTER plan messaging.The updated title "Free" and expanded feature list provide clearer value communication. The pricing_title and pricing_caption overrides work well for the free tier.
269-347: LGTM! BUSINESS and ENTERPRISE plan updates.The hierarchy is clear with BUSINESS referencing "Everything in Standard +", and ENTERPRISE's full_width flag correctly supports the UI rendering requirements.
349-362: LGTM! Plan-level comparison operators.The
__gt__operator correctly usesget_active_monthly_charge()without tier_key, which provides plan-level comparison. Tier-specific comparisons are handled separately in the billing logic where needed.
437-458: LGTM! Tier-aware Stripe line item generation.The function correctly retrieves tier-specific pricing and passes tier_key through to product ID generation. The ValueError for unsupported plans is appropriate, though static analysis flags the long message as a minor style issue.
470-498: LGTM! Tier-aware Stripe product ID resolution.The function correctly builds tier-specific product names that match the format expected by
get_by_stripe_product. The search and create logic properly handles both existing and new products.
Q/A checklist
How to check import time?
You can visualize this using tuna:
To measure import time for a specific library:
To reduce import times, import libraries that take a long time inside the functions that use them instead of at the top of the file:
Legal Boilerplate
Look, I get it. The entity doing business as “Gooey.AI” and/or “Dara.network” was incorporated in the State of Delaware in 2020 as Dara Network Inc. and is gonna need some rights from me in order to utilize my contributions in this PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Dara Network Inc can use, modify, copy, and redistribute my contributions, under its choice of terms.