Skip to content

Comments

feat: monthly proration service and seat tracking#26986

Closed
sean-brydon wants to merge 8 commits intomainfrom
feat/monthly-proration-service
Closed

feat: monthly proration service and seat tracking#26986
sean-brydon wants to merge 8 commits intomainfrom
feat/monthly-proration-service

Conversation

@sean-brydon
Copy link
Member

@sean-brydon sean-brydon commented Jan 19, 2026

What does this PR do?

This PR adds the monthly proration feature (gated behind feature flag):

  • Add operationId field to SeatChangeLog for idempotency
  • Add seat change tracking on member invites and deletions
  • Add MonthlyProrationService for processing prorated billing
  • Add payment webhooks for invoice success/failure tracking
  • Update subscription webhook to sync billing period on renewals
  • Update TeamBillingService to skip real-time updates when proration enabled

The feature is gated by the 'monthly-proration' feature flag in BillingPeriodService.shouldApplyMonthlyProration() - when disabled, all behavior remains unchanged.

Updates since last revision

Addressed Cubic AI review feedback:

  • Added select clause to TeamBilling query to fetch only required fields (id, teamId) instead of pulling the full row
  • Removed sensitive billing identifiers (subscriptionId, customerId) from webhook warning logs to comply with security guidelines

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A - internal billing feature
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  1. Ensure the monthly-proration feature flag is disabled - all existing behavior should remain unchanged
  2. Enable the feature flag and verify:
    • Seat changes are logged when members are invited/removed
    • Subscription renewals sync billing period data correctly
    • Invoice webhooks track payment success/failure

Human Review Checklist

  • Verify the subscription webhook correctly handles renewal events and updates billing records
  • Confirm seat tracking logic in inviteMember/utils.ts correctly tracks additions across teams and organizations
  • Note: Seat change logging runs unconditionally (not gated by feature flag) - this is intentional for data collection before enabling proration

Link to Devin run: https://app.devin.ai/sessions/795ceda9b16f4d649139e628c130064a
Requested by: unknown ()

@sean-brydon sean-brydon requested a review from a team as a code owner January 19, 2026 09:02
@graphite-app graphite-app bot added the core area: core, team members only label Jan 19, 2026
@graphite-app graphite-app bot requested a review from a team January 19, 2026 09:02
@github-actions github-actions bot added ❗️ migrations contains migration files and removed core area: core, team members only consumer labels Jan 19, 2026
@sean-brydon sean-brydon force-pushed the feat/monthly-proration-service branch from 395d549 to 7bca3da Compare January 19, 2026 09:04
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 111 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts">

<violation number="1" location="packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts:88">
P2: Select only the fields you need from TeamBilling to avoid pulling the full row.

(Based on your team's feedback about selecting only required fields for Prisma queries.) [FEEDBACK_USED]</violation>

<violation number="2" location="packages/features/ee/billing/api/webhook/_customer.subscription.updated.ts:130">
P1: Rule violated: **Avoid Logging Sensitive Information**

Avoid logging sensitive billing identifiers (subscription/customer IDs) in webhook logs. This violates the rule against logging sensitive information.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts:420">
P2: Seat change logging now runs unconditionally, so seat logs are created even when the monthly-proration flag is off. This changes behavior despite the feature being gated; consider guarding these logSeatAddition calls with BillingPeriodService.shouldApplyMonthlyProration (or an equivalent flag check) before writing seat logs.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/organizations/utils.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/organizations/utils.ts:128">
P2: This nested filter makes seat counting O(T * N) for bulk invites. Consider precomputing counts once to keep the hot path O(N + T).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

if (createdUsers.length > 0) {
const seatTracker = new SeatChangeTrackingService();
const trackingTeamId = parentId ?? teamId;
await seatTracker.logSeatAddition({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 19, 2026

Choose a reason for hiding this comment

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

P2: Seat change logging now runs unconditionally, so seat logs are created even when the monthly-proration flag is off. This changes behavior despite the feature being gated; consider guarding these logSeatAddition calls with BillingPeriodService.shouldApplyMonthlyProration (or an equivalent flag check) before writing seat logs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts, line 420:

<comment>Seat change logging now runs unconditionally, so seat logs are created even when the monthly-proration flag is off. This changes behavior despite the feature being gated; consider guarding these logSeatAddition calls with BillingPeriodService.shouldApplyMonthlyProration (or an equivalent flag check) before writing seat logs.</comment>

<file context>
@@ -415,6 +413,16 @@ export async function createNewUsersConnectToOrgIfExists({
+  if (createdUsers.length > 0) {
+    const seatTracker = new SeatChangeTrackingService();
+    const trackingTeamId = parentId ?? teamId;
+    await seatTracker.logSeatAddition({
+      teamId: trackingTeamId,
+      seatCount: createdUsers.length,
</file context>
Fix with Cubic

@github-actions
Copy link
Contributor

Devin AI is addressing Cubic AI's review feedback

A Devin session has been created to address the issues identified by Cubic AI.

View Devin Session

This PR adds the monthly proration feature (gated behind feature flag):

- Add operationId field to SeatChangeLog for idempotency
- Add seat change tracking on member invites and deletions
- Add MonthlyProrationService for processing prorated billing
- Add payment webhooks for invoice success/failure tracking
- Update subscription webhook to sync billing period on renewals
- Update TeamBillingService to skip real-time updates when proration enabled

The feature is gated by the 'monthly-proration' feature flag in
BillingPeriodService.shouldApplyMonthlyProration() - when disabled,
all behavior remains unchanged.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sean-brydon sean-brydon force-pushed the feat/monthly-proration-service branch from 5d4de7e to 3f4680c Compare January 19, 2026 09:17
sean-brydon and others added 3 commits January 19, 2026 09:24
Only log seat changes when monthly-proration feature flag is enabled
for the team. This prevents behavior changes when the feature is disabled.

The check is performed in SeatChangeTrackingService.logSeatAddition/logSeatRemoval
methods, so callers don't need to know about the feature flag.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/features/ee/billing/service/seatTracking/SeatChangeTrackingService.ts">

<violation number="1" location="packages/features/ee/billing/service/seatTracking/SeatChangeTrackingService.ts:50">
P2: This feature-flag gate prevents seat change logging when monthly proration is disabled, but the PR description says logging must run unconditionally for data collection. This will stop collecting baseline logs until the flag is enabled.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

operationId,
} = params;

// Only log seat changes if monthly proration is enabled
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 19, 2026

Choose a reason for hiding this comment

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

P2: This feature-flag gate prevents seat change logging when monthly proration is disabled, but the PR description says logging must run unconditionally for data collection. This will stop collecting baseline logs until the flag is enabled.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/features/ee/billing/service/seatTracking/SeatChangeTrackingService.ts, line 50:

<comment>This feature-flag gate prevents seat change logging when monthly proration is disabled, but the PR description says logging must run unconditionally for data collection. This will stop collecting baseline logs until the flag is enabled.</comment>

<file context>
@@ -44,6 +46,14 @@ export class SeatChangeTrackingService {
       operationId,
     } = params;
+
+    // Only log seat changes if monthly proration is enabled
+    const billingPeriodService = new BillingPeriodService();
+    const shouldLog = await billingPeriodService.shouldApplyMonthlyProration(teamId);
</file context>
Fix with Cubic

Add operationId field to SeatChangeLog table to prevent duplicate
seat change logs from race conditions.

- Add nullable operationId column
- Add unique constraint on (teamId, operationId)
@github-actions
Copy link
Contributor

Devin AI is addressing Cubic AI's review feedback

A Devin session has been created to address the issues identified by Cubic AI.

View Devin Session

Mock shouldApplyMonthlyProration to return true so tests can verify
seat logging behavior.
@sean-brydon
Copy link
Member Author

Closing this PR as it has been split into two smaller PRs for easier review:

Both PRs together contain the same functionality as this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

❗️ migrations contains migration files size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant