feat(billing): add subscriptions and plans support - #303
Conversation
Extended `schema.ts` with `stripeSubscriptionId`, `plan`, and `planExpiresAt` fields for enhanced subscription management. Added `subscriptions` route in `api`. Updated API type definitions to reflect these changes. Improves flexibility in handling user plans and statuses.
Introduced `resolveOrganizationFromStripeEvent` to streamline Stripe event handling and ensure accurate organization identification. Updated event handlers across various Stripe events to utilize this helper, improving code reuse and maintainability. Additionally, updated UI toast messages in plan management to clarify users may need to refresh the page after
Added PostHog tracking for subscription creation and cancellation in Stripe event handlers. Captures relevant details such as organization ID, plan changes, and source. Improves analytics on subscription activity.
Included `plan` and `planExpiresAt` in organization test data and `mode` in project test data for better simulation of real-world scenarios in tests.
Included `plan` field in organization test data to align with real-world scenarios and improve test coverage.
Included `plan` in organization setup and `mode` in project setup within e2e tests. Enhances test fidelity by aligning test data with real-world configurations.
Implemented API support for resuming canceled Pro subscriptions. Updated `/subscriptions/resume-pro-subscription` route and added related type definitions. Integrated functionality into UI plan management with confirmation dialog and status update. Enhances user experience for re-activating subscriptions.
Replaced `any` with specific Stripe event type annotations in event handlers. Updated functions to destructure `event.data.object` for relevant fields. Improves type safety and readability in Stripe webhook processing.
Ensured safe handling of optional fields like `metadata` and `subscription` in Stripe event data. Converted `payment_method` and `customer` to consistent string identifiers. Improves robustness and error handling in webhook processing.
Added checks for `current_period_end` to avoid accessing undefined fields in subscription objects. Improved robustness in Stripe event processing. Updated UI to use `toDateString` for date display in plan management.
Added `subscriptionCancelled` field to the organization schema. Updated Stripe event handlers to track subscription status changes, ensuring accurate updates for cancellations and reactivations. Integrated PostHog tracking for subscription reactivation events.
Replaced `cancelAtPeriodEnd` with `subscriptionCancelled` in the schema. Updated subscription cancellation logic, UI components, and database migration. Refactored API routes for accurate subscription status management.
WalkthroughThis update introduces full support for Pro plan subscriptions, including backend schema changes, Stripe integration, new API endpoints for subscription management, and UI components for plan upgrades and billing. It enforces Pro plan restrictions on provider keys and project modes, adds plan information to organization data, and updates tests and documentation accordingly. Changes
Sequence Diagram(s)Pro Subscription Lifecycle (Create, Cancel, Resume, Status)sequenceDiagram
participant User
participant UI (PlanManagement)
participant API (/subscriptions)
participant DB
participant Stripe
User->>UI (PlanManagement): Click "Upgrade to Pro"
UI (PlanManagement)->>API (/subscriptions): POST /subscriptions/create-pro-subscription
API (/subscriptions)->>DB: Fetch user & organization, check plan/payment method
API (/subscriptions)->>Stripe: Create subscription
Stripe-->>API (/subscriptions): Subscription created, client secret (if needed)
API (/subscriptions)->>DB: Update organization with subscription details
API (/subscriptions)-->>UI (PlanManagement): Return client secret, subscriptionId
UI (PlanManagement)->>Stripe: (If needed) Confirm payment with client secret
Stripe-->>UI (PlanManagement): Payment confirmation result
UI (PlanManagement)-->>User: Show success/error
User->>UI (PlanManagement): Click "Cancel Pro"
UI (PlanManagement)->>API (/subscriptions): POST /subscriptions/cancel-pro-subscription
API (/subscriptions)->>Stripe: Set subscription to cancel at period end
API (/subscriptions)->>DB: Update org cancellation status
API (/subscriptions)-->>UI (PlanManagement): { success: true }
UI (PlanManagement)-->>User: Show cancellation confirmation
User->>UI (PlanManagement): Click "Resume Pro"
UI (PlanManagement)->>API (/subscriptions): POST /subscriptions/resume-pro-subscription
API (/subscriptions)->>Stripe: Resume subscription
API (/subscriptions)->>DB: Update org cancellation status
API (/subscriptions)-->>UI (PlanManagement): { success: true }
UI (PlanManagement)-->>User: Show resume confirmation
UI (PlanManagement)->>API (/subscriptions): GET /subscriptions/status
API (/subscriptions)->>DB: Fetch org subscription details
API (/subscriptions)-->>UI (PlanManagement): Return plan, subscriptionId, planExpiresAt, cancelled
Provider Key Creation with Pro Plan EnforcementsequenceDiagram
participant User
participant UI (ProviderKeyDialog)
participant API (/keys-provider)
participant DB
User->>UI (ProviderKeyDialog): Attempt to create provider key
UI (ProviderKeyDialog)->>API (/keys-provider): POST /keys-provider
API (/keys-provider)->>DB: Fetch organization, check plan
alt Plan is "pro"
API (/keys-provider)->>DB: Proceed with provider key creation
API (/keys-provider)-->>UI (ProviderKeyDialog): Success
else Plan is "free"
API (/keys-provider)-->>UI (ProviderKeyDialog): 403 error, "Pro required"
UI (ProviderKeyDialog)-->>User: Show upgrade prompt
end
Project Mode Selection with Pro Plan RestrictionsequenceDiagram
participant User
participant UI (ProjectModeSettings)
participant API (/projects)
participant DB
User->>UI (ProjectModeSettings): Select project mode
alt Mode requires Pro & user is not Pro
UI (ProjectModeSettings)-->>User: Show "Pro Only" badge, disable selection
else
UI (ProjectModeSettings)->>API (/projects): PATCH /projects/:id
API (/projects)->>DB: Update project mode
API (/projects)-->>UI (ProjectModeSettings): Success
end
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (7)
✅ Files skipped from review due to trivial changes (5)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms (2)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 8
🔭 Outside diff range comments (2)
apps/api/src/routes/keys-provider.e2e.ts (1)
92-132:⚠️ Potential issueRemove duplicate skipped test for custom baseUrl.
The
test.skip("POST /keys/provider with custom baseUrl", ...)block appears twice; remove the redundant copy to avoid confusion.Also applies to: 134-174
🧰 Tools
🪛 GitHub Check: lint / run
[warning] 94-94:
Unexpected console statement🪛 GitHub Check: generate / run
[warning] 94-94:
Unexpected console statementapps/api/src/stripe.ts (1)
229-238: 🛠️ Refactor suggestionUse
amount_received& currency for accurate credit calculation
paymentIntent.amountis the intended amount, not the captured one.
amount_received(in the succeeded event) guarantees correct value and protects against partial captures or currency adjustments.
Also, credits should be adjusted based on the actual currency.-const { metadata, amount } = paymentIntent; -// Convert amount from cents to dollars -const amountInDollars = amount / 100; +const { metadata, amount_received: amountReceived, currency } = paymentIntent; + +// TODO: handle non-USD currencies – for USD cents ➜ dollars: +const amountInDollars = currency.toLowerCase() === "usd" + ? amountReceived / 100 + : amountReceived; // fallback 1:1 for now
🧹 Nitpick comments (6)
apps/api/.env.example (1)
3-3: IncludeSTRIPE_PRO_PRICE_IDfor Pro plan pricing.Consider updating documentation (e.g., README or deployment guides) to include guidance on obtaining and configuring this price ID.
.env.example (1)
97-97: Add Pro plan Stripe price ID placeholder.The placeholder
STRIPE_PRO_PRICE_IDis correctly added under payment configuration.Consider clarifying in comments that this value must be obtained from Stripe and kept in sync with
apps/api/.env.example.apps/ui/src/components/settings/project-mode-settings.tsx (1)
118-142: Comprehensive UI restrictions for Pro-only features.The implementation provides excellent user experience with:
- Disabled radio buttons for restricted modes
- Visual indicators (muted text, "Pro Only" badges)
- Clear labeling of plan requirements
However, there's a minor styling redundancy in the description text className.
- <p - className={`text-sm ${requiresPro && !isProPlan ? "text-muted-foreground" : "text-muted-foreground"}`} - > + <p className="text-sm text-muted-foreground">apps/ui/src/components/billing/plan-management.tsx (1)
316-359:<Elements>wrapper is unused – remove it or render a PaymentElement
<Elements>is only needed when you embed Stripe Elements (e.g.<PaymentElement/>,<CardElement/>).
In this dialog you just callstripe.confirmCardPayment, so the wrapper adds overhead and a dev-warning (“You must pass either options or stripe”). Either:- <Elements stripe={stripe}> + <> ... - </Elements> + </>or actually collect card details with a
PaymentElementwhen no default PM exists.
Cleaning this up simplifies the DOM and avoids Stripe console noise.apps/api/src/routes/subscriptions.ts (1)
99-108: Organizationplanfield is never updated on successful purchaseAfter creating the subscription you set
stripeSubscriptionId&subscriptionCancelled, but leaveplanandplanExpiresAtunchanged.
Until the webhook fires, the UI thinks the org is still on the Free plan.Consider an immediate optimistic update:
- stripeSubscriptionId: subscription.id, - subscriptionCancelled: false, + stripeSubscriptionId: subscription.id, + plan: "pro", + subscriptionCancelled: false,(or document that the webhook will back-fill and make the UI poll).
apps/api/src/stripe.ts (1)
180-187: Handle thepayment_intent.payment_failedbranch or explicitly ignoreThe
case "payment_intent.payment_failed":is empty – Stripe will still retry, but logging the failure (and maybe notifying the user) helps troubleshooting.case "payment_intent.payment_failed": console.warn("Payment failed for PI:", event.data.object.id); break;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (24)
.env.example(1 hunks)apps/api/.env.example(1 hunks)apps/api/src/routes/index.ts(2 hunks)apps/api/src/routes/keys-provider.e2e.ts(2 hunks)apps/api/src/routes/keys-provider.spec.ts(1 hunks)apps/api/src/routes/keys-provider.ts(1 hunks)apps/api/src/routes/organization.ts(1 hunks)apps/api/src/routes/subscriptions.ts(1 hunks)apps/api/src/stripe.ts(7 hunks)apps/gateway/src/api.e2e.ts(2 hunks)apps/gateway/src/api.spec.ts(2 hunks)apps/ui/src/components/billing/plan-management.tsx(1 hunks)apps/ui/src/components/credits/payment-methods-management.tsx(2 hunks)apps/ui/src/components/provider-keys/create-provider-key-dialog.tsx(5 hunks)apps/ui/src/components/settings/project-mode-settings.tsx(5 hunks)apps/ui/src/hooks/useOrganization.ts(1 hunks)apps/ui/src/lib/api/v1.d.ts(4 hunks)apps/ui/src/routes/dashboard/_layout/settings.tsx(2 hunks)packages/db/migrations/1749230483_spooky_ultimatum.sql(1 hunks)packages/db/migrations/1749243276_parallel_ultron.sql(1 hunks)packages/db/migrations/meta/1749230483_snapshot.json(1 hunks)packages/db/migrations/meta/1749243276_snapshot.json(1 hunks)packages/db/migrations/meta/_journal.json(1 hunks)packages/db/src/schema.ts(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
apps/api/src/routes/index.ts (1)
apps/api/src/routes/subscriptions.ts (1)
subscriptions(11-11)
apps/ui/src/components/credits/payment-methods-management.tsx (1)
apps/ui/src/lib/fetch-client.ts (1)
$api(13-13)
apps/ui/src/components/billing/plan-management.tsx (9)
packages/db/src/schema.ts (1)
organization(94-115)apps/ui/src/hooks/useOrganization.ts (1)
useDefaultOrganization(17-29)apps/ui/src/lib/components/use-toast.ts (2)
useToast(192-192)toast(192-192)apps/ui/src/lib/fetch-client.ts (1)
$api(13-13)apps/ui/src/lib/components/card.tsx (6)
Card(85-85)CardHeader(86-86)CardTitle(88-88)CardDescription(90-90)CardContent(91-91)CardFooter(87-87)apps/ui/src/lib/components/badge.tsx (1)
Badge(46-46)apps/ui/src/lib/components/dialog.tsx (7)
Dialog(123-123)DialogTrigger(132-132)DialogContent(125-125)DialogHeader(128-128)DialogTitle(131-131)DialogDescription(126-126)DialogFooter(127-127)apps/ui/src/lib/components/button.tsx (1)
Button(59-59)apps/ui/src/lib/stripe.ts (1)
useStripe(19-37)
apps/ui/src/components/settings/project-mode-settings.tsx (6)
packages/db/src/schema.ts (1)
organization(94-115)apps/ui/src/hooks/useOrganization.ts (1)
useDefaultOrganization(17-29)apps/ui/src/lib/components/use-toast.ts (1)
toast(192-192)apps/ui/src/lib/components/radio-group.tsx (1)
RadioGroupItem(41-41)apps/ui/src/lib/components/label.tsx (1)
Label(24-24)apps/ui/src/lib/components/badge.tsx (1)
Badge(46-46)
🪛 Biome (1.9.4)
apps/api/src/stripe.ts
[error] 340-341: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build / run
🔇 Additional comments (33)
packages/db/migrations/1749243276_parallel_ultron.sql (1)
1-1: Addsubscription_cancelledcolumn toorganizationtable.Looks correct; this column will track cancellation state.
Verify that the ORM schema definition (e.g.,
packages/db/src/schema.ts) is updated to include this new column to prevent schema mismatch.apps/api/src/routes/keys-provider.e2e.ts (1)
23-23: Align e2e test setup with subscription and project mode.The test fixtures correctly include
plan: "pro"andmode: "api-keys"to match the new gating logic.Also applies to: 37-37
apps/api/src/routes/keys-provider.spec.ts (1)
21-21: Ensure spec test organization uses Pro plan.Adding
plan: "pro"aligns with the new subscription requirement for provider keys.apps/gateway/src/api.e2e.ts (2)
82-82: Ensure default values for new organization fields
The test now insertsplan: "pro", but migrations also introduceplan_expires_at,stripe_subscription_id, andsubscription_cancelled. Confirm that those columns are nullable or have defaults so this insert won’t fail.
95-95: Override updated project mode default
Explicitly settingmode: "api-keys"preserves the previous behavior despite the default changing tocredits. This is correct to maintain test isolation.packages/db/migrations/meta/_journal.json (2)
96-102: Validate journal entry for migration1749230483_spooky_ultimatum
The new entry is correctly appended with the matching version and breakpoint flag.
103-109: Validate journal entry for migration1749243276_parallel_ultron
This entry appears properly ordered and formatted in the migration journal.apps/ui/src/hooks/useOrganization.ts (1)
7-8: Confirm API response includes new plan fields
The interface now requiresplanandplanExpiresAt. Ensure the/orgsendpoint returns these properties (or they are defaulted) so the hook’sdataremains type-safe at runtime.apps/gateway/src/api.spec.ts (2)
66-66: Ensure defaults for new organization columns
Addingplan: "pro"aligns with the schema change, but verify thatplan_expires_at,stripe_subscription_id, andsubscription_cancelledare nullable or have defaults to avoid insertion errors.
79-79: Override updated project mode default
Explicitly settingmode: "api-keys"maintains the legacy behavior now that the default has shifted tocredits.apps/api/src/routes/index.ts (1)
11-11: Register new subscriptions route
Thesubscriptionshandler is correctly imported and mounted on/subscriptions.apps/api/src/routes/organization.ts (1)
17-18: LGTM: Clean schema extension for subscription plans.The addition of
planandplanExpiresAtfields follows proper Zod validation patterns with appropriate type constraints. The enum restriction to "free" and "pro" plans and nullable expiration date are well-designed for the subscription model.apps/ui/src/routes/dashboard/_layout/settings.tsx (3)
4-4: LGTM: Proper import of PlanManagement component.Clean import statement following existing patterns.
305-305: LGTM: Logical placement of plan management component.The PlanManagement component is appropriately placed in the billing tab, above the payment methods card.
308-312: LGTM: Clear payment methods card description.The updated description better clarifies that payment methods are used for both credits and subscriptions, improving user understanding.
apps/api/src/routes/keys-provider.ts (1)
119-127: LGTM: Proper plan-based authorization for provider keys.The pro plan check is well-implemented:
- Correctly placed after organization access verification
- Clear error message with actionable guidance
- Prevents unnecessary downstream processing for unauthorized requests
The authorization logic aligns with the subscription model introduced in this PR.
apps/ui/src/components/credits/payment-methods-management.tsx (2)
192-192: LGTM: Appropriate hook import for cache management.Adding
useQueryClienthook import to support query invalidation functionality.
227-230: LGTM: Consistent cache invalidation pattern.The query invalidation after successful card setup follows the same pattern used elsewhere in the component (lines 43-48, 82-87). This ensures the payment methods list refreshes immediately after adding a new payment method, providing a better user experience.
packages/db/migrations/1749230483_spooky_ultimatum.sql (1)
1-4: Database migration looks good with minor observations.The migration correctly adds subscription-related fields to the organization table and updates the project mode default. The changes align well with the subscription feature implementation.
Consider adding an index on
stripe_subscription_idfor performance if subscription lookups will be frequent, though this can be done in a future migration if needed.apps/ui/src/components/provider-keys/create-provider-key-dialog.tsx (5)
15-16: Good addition of required UI components.The Alert and Badge imports are appropriately added to support the Pro plan gating UI elements.
72-72: Correct implementation of plan detection.The
isProPlanboolean correctly checks the organization's plan status for Pro plan detection.
94-103: Excellent user feedback for plan restrictions.The form submission guard provides clear feedback to users about the upgrade requirement. The toast message is informative and actionable.
202-209: Good visual indication of feature restrictions.The alert component effectively communicates the Pro plan requirement with a clear message and "Pro Only" badge. This provides immediate visual feedback to users.
280-284: Proper submit button state management.The submit button is correctly disabled for non-Pro users, preventing interaction while maintaining visual consistency with other disabled states.
packages/db/src/schema.ts (2)
103-103: Appropriate field addition for Stripe integration.The
stripeSubscriptionIdtext field correctly supports Stripe subscription tracking without unnecessary constraints.
105-111: Well-designed subscription plan schema.The plan enum with "free" and "pro" values, NOT NULL constraint, and "free" default value provides a solid foundation for subscription management. The
planExpiresAtandsubscriptionCancelledfields complete the subscription lifecycle tracking.apps/ui/src/components/settings/project-mode-settings.tsx (5)
6-7: Appropriate imports for subscription plan features.The addition of
useDefaultOrganizationhook andBadgecomponent correctly supports the Pro plan restrictions functionality.
18-18: Correct organization data fetching.The organization data is properly fetched to access the subscription plan information.
34-34: Accurate plan detection logic.The
isProPlanboolean correctly determines Pro plan status from the organization's plan field.
48-57: Excellent validation and user feedback.The save handler properly validates Pro plan requirements and provides clear, actionable feedback through the toast message. This prevents users from inadvertently losing functionality.
102-115: Well-structured mode configuration with plan requirements.The addition of
requiresProflags clearly identifies which modes require Pro plan access. The configuration is clean and maintainable.apps/ui/src/lib/api/v1.d.ts (1)
1-5: Auto-generated file – safe to omit from manual review
openapi-typescriptwill overwrite any edits here on the next generation cycle, so I’m intentionally skipping detailed comments.packages/db/migrations/meta/1749243276_snapshot.json (1)
528-533: Store timestamps with timezone
plan_expires_atis declared astimestamp(no time zone). Webhook code converts Unix seconds vianew Date(...), producing UTC times, but the DB will silently assume the session’s timezone.
Usetimestamptzto avoid subtle off-by-hours issues during DST changes or in multi-region setups.-"plan_expires_at": { "type": "timestamp", … } +"plan_expires_at": { "type": "timestamptz", … }
Added validation to ensure the `STRIPE_PRO_PRICE_ID` environment variable is set. Throws an HTTPException if not defined, improving error handling for subscription creation.
Ensures `subscriptionCancelled` is set to `false` when a subscription is reactivated. Updates database to reflect accurate subscription status.
Eliminated unnecessary `createdAt` and `updatedAt` fields from database update operations to streamline queries and improve consistency. No changes to functional behavior.
Added detailed status handling for payments, including additional toast messages for `requires_action`, `processing`, and `requires_capture` statuses. Improved user feedback for various payment scenarios.
Removed unnecessary default values for mode and usedMode fields in the schema. Ensures cleaner schema definitions without changing functionality.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
apps/api/src/stripe.ts (1)
324-340: Guard optional properties with optional chaining to prevent crashes
firstLineItem.parent.subscription_item_detailsmay beundefined. A single malformed invoice would currently throw and fail the entire webhook delivery.🧰 Tools
🪛 Biome (1.9.4)
[error] 334-335: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🧹 Nitpick comments (1)
apps/api/src/stripe.ts (1)
271-282: Consider using the centralized helper for consistency.While the direct metadata extraction works, using
resolveOrganizationFromStripeEventwould provide better consistency and validation across all handlers.- const { metadata, payment_method } = setupIntent; - const organizationId = metadata?.organizationId; - - if (!organizationId || !payment_method) { - console.error("Missing organizationId or payment_method in setupIntent"); - return; - } + const { metadata, payment_method } = setupIntent; + + if (!payment_method) { + console.error("Missing payment_method in setupIntent"); + return; + } + + const result = await resolveOrganizationFromStripeEvent({ metadata }); + if (!result) { + console.error("Could not resolve organization from setup intent"); + return; + } + + const { organizationId } = result;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/api/src/routes/payments.ts(0 hunks)apps/api/src/routes/subscriptions.ts(1 hunks)apps/api/src/stripe.ts(7 hunks)apps/gateway/src/api.e2e.ts(2 hunks)apps/gateway/src/worker.ts(0 hunks)apps/ui/src/components/billing/plan-management.tsx(1 hunks)
💤 Files with no reviewable changes (2)
- apps/api/src/routes/payments.ts
- apps/gateway/src/worker.ts
✅ Files skipped from review due to trivial changes (1)
- apps/gateway/src/api.e2e.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/ui/src/components/billing/plan-management.tsx
- apps/api/src/routes/subscriptions.ts
🧰 Additional context used
🪛 Biome (1.9.4)
apps/api/src/stripe.ts
[error] 334-335: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: build / run
- GitHub Check: e2e / run
🔇 Additional comments (8)
apps/api/src/stripe.ts (8)
10-10: Good addition of proper typing.Adding the Stripe type import improves type safety for the webhook handlers.
45-140: Excellent centralization of organization resolution logic.The
resolveOrganizationFromStripeEventhelper function is well-designed with multiple fallback strategies and comprehensive error handling. This improves maintainability and reduces code duplication across webhook handlers.
181-196: Proper subscription event handling added.The webhook handler now correctly processes all necessary Stripe subscription lifecycle events. The integration looks complete and follows the established pattern.
210-225: Good refactoring to use centralized helper.The updated function properly leverages the new organization resolution helper, improving consistency across the codebase.
291-292: Good handling of payment method type variation.Properly handles both string and object forms of the payment method, improving robustness.
316-426: Solid subscription invoice processing logic.The function correctly handles subscription invoice payments, upgrades organizations to pro plan, and includes proper error handling and analytics tracking. The verification query provides good debugging information.
🧰 Tools
🪛 Biome (1.9.4)
[error] 334-335: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
428-496: Comprehensive subscription update handling.The function properly manages subscription expiration dates, cancellation status, and tracks reactivation events. The logic correctly identifies when a previously cancelled subscription becomes active again.
498-550: Proper subscription cancellation and cleanup.The function correctly downgrades organizations to the free plan, clears all subscription-related fields, and tracks the cancellation event. This ensures a clean state after subscription deletion.
| ) { | ||
| const invoice = event.data.object; | ||
| const { customer, metadata } = invoice; | ||
| const subscription = (invoice as any).subscription; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Improve type safety for subscription access.
The type assertion (invoice as any).subscription bypasses TypeScript's type checking and could lead to runtime errors.
- const subscription = (invoice as any).subscription;
+ const subscription = invoice.subscription;If this causes type errors, consider updating the Stripe type definitions or using proper type guards.
📝 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.
| const subscription = (invoice as any).subscription; | |
| const subscription = invoice.subscription; |
🤖 Prompt for AI Agents
In apps/api/src/stripe.ts at line 321, replace the unsafe type assertion
(invoice as any).subscription with a safer approach by either updating the
Stripe type definitions to include the subscription property or implementing a
type guard to check for the subscription property before accessing it. This will
improve type safety and prevent potential runtime errors.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/db/migrations/1749245675_cuddly_krista_starr.sql (2)
4-4: Add index and uniqueness constraint onstripe_subscription_id
Since you’ll frequently look up organizations by their Stripe subscription, consider adding a unique index to enforce one-to-one mapping and speed up queries.Example diff:
ALTER TABLE "organization" ADD COLUMN "stripe_subscription_id" text; +CREATE UNIQUE INDEX idx_organization_stripe_sub_id ON "organization" ("stripe_subscription_id");
6-6: Prefer timezone-aware expiry timestamps
Storing expiry dates without time zone can cause ambiguity across regions. Consider usingTIMESTAMPTZ:-ALTER TABLE "organization" ADD COLUMN "plan_expires_at" timestamp; +ALTER TABLE "organization" ADD COLUMN "plan_expires_at" timestamptz;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
packages/db/migrations/1749245675_cuddly_krista_starr.sql(1 hunks)packages/db/migrations/meta/1749245675_snapshot.json(1 hunks)packages/db/migrations/meta/_journal.json(1 hunks)packages/db/src/schema.ts(3 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/db/migrations/meta/1749245675_snapshot.json
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/db/migrations/meta/_journal.json
- packages/db/src/schema.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build / run
🔇 Additional comments (3)
packages/db/migrations/1749245675_cuddly_krista_starr.sql (3)
1-2: Confirm downstream impact of dropping defaults onlog
Removing the default values fromlog.modeandlog.used_modewill require all INSERTs to explicitly set these columns. Verify that no existing queries or ORM mappings rely on the old defaults, or you may introduce failures.
3-3: Approve setting default forproject.mode
Defaultingproject.modeto'credits'aligns with the new billing model and should work as intended.
7-7: Approve addingsubscription_cancelledflag
A boolean defaulting tofalseis a clear, safe initial state for cancellations.
| ALTER TABLE "log" ALTER COLUMN "used_mode" DROP DEFAULT;--> statement-breakpoint | ||
| ALTER TABLE "project" ALTER COLUMN "mode" SET DEFAULT 'credits';--> statement-breakpoint | ||
| ALTER TABLE "organization" ADD COLUMN "stripe_subscription_id" text;--> statement-breakpoint | ||
| ALTER TABLE "organization" ADD COLUMN "plan" text DEFAULT 'free' NOT NULL;--> statement-breakpoint |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Split migration and enforce valid plan values
Adding a non-nullable column with a default rewrites the entire table. For large datasets, break it into steps and enforce allowed values via an ENUM or CHECK constraint:
-- 1. Add nullable column
-ALTER TABLE "organization" ADD COLUMN "plan" text DEFAULT 'free' NOT NULL;
+ALTER TABLE "organization" ADD COLUMN "plan" text;
-- 2. Backfill existing rows
+UPDATE "organization" SET "plan" = 'free';
-- 3. Lock in constraints
+ALTER TABLE "organization" ALTER COLUMN "plan" SET NOT NULL;
+ALTER TABLE "organization" ALTER COLUMN "plan" SET DEFAULT 'free';
-- 4. (Optional) Enforce allowed plans
+CREATE TYPE org_plan AS ENUM ('free', 'pro', 'enterprise');
+ALTER TABLE "organization" ALTER COLUMN "plan" TYPE org_plan USING plan::org_plan;🤖 Prompt for AI Agents
In packages/db/migrations/1749245675_cuddly_krista_starr.sql at line 5, the
migration adds a non-nullable column with a default value in one step, which
rewrites the entire table and can be costly for large datasets. To fix this,
split the migration into multiple steps: first add the column as nullable
without a default, then backfill existing rows with the default value, and
finally alter the column to be non-nullable with the default. Additionally,
enforce valid plan values by defining an ENUM type or adding a CHECK constraint
to restrict the allowed values.
There was a problem hiding this comment.
ok but this still works right? this table is very small
There was a problem hiding this comment.
@steebchen Yes, absolutely! The migration will work perfectly fine for a small organization table. My suggestion was more of a scalability best practice for when tables have millions of rows. For small tables (which most organization tables are), the single-step approach you have is completely appropriate and will execute quickly without any issues.
The migration will add the column, set the default value, and make it non-nullable all in one go - no problems at all for your use case! 👍
🐰
♪ ♫ ♪
(◕‿◕)
_/
Added `mode` and `usedMode` fields to test data in logs and activity specs. Ensures comprehensive testing of mode-related functionalities.
Added unique constraints for `stripeCustomerId` and `stripeSubscriptionId` in the `organization` schema. Ensures constraints are reflected in migrations for data integrity.
Summary by CodeRabbit
New Features
Improvements
Database & API