Commit Graph
4 Commits
Author SHA1 Message Date
sean-brydonGitHubClaude Sonnet 4.5Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
c150828988 feat: add scheduled trigger.dev task for monthly proration (#26991)
* feat: add seat tracking infrastructure for monthly proration

Add seat change logging infrastructure with operationId for idempotency.
This PR adds the foundation for monthly proration billing by tracking
seat additions and removals, gated behind the monthly-proration feature flag.

- Add operationId field to SeatChangeLog for idempotency
- Update SeatChangeLogRepository to support upsert with operationId
- Add feature flag guard in SeatChangeTrackingService
- Integrate seat tracking in team member invites
- Integrate seat tracking in bulk user deletions
- Integrate seat tracking in team service operations
- Integrate seat tracking in DSYNC user creation

When monthly-proration feature flag is disabled, seat logging is skipped
and behavior remains unchanged.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: add monthly proration processing

Add monthly proration billing processing that works on top of the seat
tracking infrastructure. This PR implements the core proration logic,
webhook handlers, and integration with Stripe billing.

- Enhance MonthlyProrationService to process seat change logs
- Add payment webhook handlers (invoice.payment_succeeded, invoice.payment_failed)
- Update subscription webhook to sync billing period on renewals
- Update TeamBillingService to skip real-time updates when proration enabled
- Enhance StripeBillingService with proration capabilities
- Add Tasker enhancements for processing queues
- Update team creation/upgrade routes

Depends on: feat/monthly-proration-seat-tracking

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: remove unused logger from SeatChangeTrackingService

* fix: description for calculation

* fix null check on trial

* feat: add scheduled trigger.dev task for monthly proration

* feat: add custom month key support and use batchTrigger

* feat: add isValidMonthKey and return result from batch task

* fix merge artifact

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-23 09:17:21 +00:00
sean-brydonGitHubClaude Sonnet 4.5Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
3af533e2c8 feat: add monthly proration processing (#27002)
* feat: add seat tracking infrastructure for monthly proration

Add seat change logging infrastructure with operationId for idempotency.
This PR adds the foundation for monthly proration billing by tracking
seat additions and removals, gated behind the monthly-proration feature flag.

- Add operationId field to SeatChangeLog for idempotency
- Update SeatChangeLogRepository to support upsert with operationId
- Add feature flag guard in SeatChangeTrackingService
- Integrate seat tracking in team member invites
- Integrate seat tracking in bulk user deletions
- Integrate seat tracking in team service operations
- Integrate seat tracking in DSYNC user creation

When monthly-proration feature flag is disabled, seat logging is skipped
and behavior remains unchanged.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: add monthly proration processing

Add monthly proration billing processing that works on top of the seat
tracking infrastructure. This PR implements the core proration logic,
webhook handlers, and integration with Stripe billing.

- Enhance MonthlyProrationService to process seat change logs
- Add payment webhook handlers (invoice.payment_succeeded, invoice.payment_failed)
- Update subscription webhook to sync billing period on renewals
- Update TeamBillingService to skip real-time updates when proration enabled
- Enhance StripeBillingService with proration capabilities
- Add Tasker enhancements for processing queues
- Update team creation/upgrade routes

Depends on: feat/monthly-proration-seat-tracking

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: remove unused logger from SeatChangeTrackingService

* fix: description for calculation

* fix null check on trial

* chore: no more prisma calls

* add feature flag check

* fix stub

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-22 11:23:38 +00:00
sean-brydonGitHubsean@cal.com <Sean@brydon.io>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
442779d08d feat: monthly-proration-tasker (#26870)
* feat: monthly-proration-taskerh

* remove cronjob from tasker implementation

* feat: add DI for MonthlyProrationService and TriggerDevLoggerServiceModule

- Create TriggerDevLoggerServiceModule for DI injection of TriggerDevLogger
- Add tokens for TriggerDevLogger in shared.tokens.ts
- Create MonthlyProrationService DI module and container
- Update processMonthlyProrationBatch.ts to use DI container
- Use redactError in Tasker.ts to avoid logging sensitive information

Co-Authored-By: sean@cal.com <Sean@brydon.io>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-16 20:58:51 +00:00
sean-brydonGitHubunknown <>cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>Anik Dhabal BabuDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Keith Williams
ac90caa81b feat: add DAL for monthly proration tracking (#26588)
* feat(billing): add database schema for monthly proration tracking

add new database models and migration for tracking seat changes and monthly proration on annual subscriptions

- new models: SeatChangeLog, MonthlyProration
- new enums: SeatChangeType, ProrationStatus
- extend TeamBilling and OrganizationBilling with billingPeriod, pricePerSeat, paidSeats fields
- add feature flag 'monthly-proration' (disabled by default)
- squashed migration: 20260106093811_add_monthly_proration_tracking

the schema supports:
- tracking seat additions/removals per month with metadata
- storing proration calculations and stripe invoice details
- high-water mark billing via paidSeats field
- proper foreign key relationships and indexes for query performance

* feat(billing): add repository layer for monthly proration

add repository pattern for proration data access

- MonthlyProrationRepository: crud operations for proration records
  - create, update, and query proration records
  - status management (pending, invoice_created, charged, failed)
  - retry count tracking

- MonthlyProrationTeamRepository: team-specific queries with billing context
  - fetch teams with billing information
  - query annual teams with unprocessed seat changes
  - update billing info and paidSeats
  - metadata fallback for legacy teams without billing records

- update IBillingRepository interface to include proration fields

these repositories provide clean data access layer without business logic

* feat(billing): add seat change tracking service

add service for logging and querying seat changes per month

- SeatChangeTrackingService: track seat additions and removals
  - log seat additions with user and metadata context
  - log seat removals with optional triggered-by info
  - calculate monthly changes (additions, removals, net change)
  - retrieve unprocessed changes for a given month
  - mark changes as processed after proration calculation
  - auto-detect billing entity (team vs organization)

- stripe-subscription-utils: extract billing data from stripe subscriptions
  - determine billing period (monthly vs annually)
  - extract price per seat and quantity

includes comprehensive unit tests

* feat(billing): add billing period service

add service to determine billing period and proration eligibility

- BillingPeriodService: query and manage billing period information
  - check if team is on annual vs monthly plan
  - check if team is in trial period
  - determine if monthly proration should apply (annual + not in trial + feature flag)
  - lazy load billing data from stripe if missing in database
  - backfill missing billing data automatically

supports both TeamBilling and OrganizationBilling models
includes comprehensive unit tests with stripe mocking

* feat(billing): add monthly proration service

add core service for calculating and processing monthly seat prorations

- MonthlyProrationService: orchestrate monthly proration workflow
  - process monthly prorations for teams with seat changes
  - calculate prorated amounts based on remaining subscription days
  - create stripe invoice items for seat additions
  - handle payment success and failure webhooks
  - track high-water mark (paidSeats) for billing
  - update stripe subscription quantities after payment
  - lazy load and backfill missing billing data from stripe
  - support retry logic for failed payments

calculation logic:
- only charge for net seat increases (additions - removals, capped at 0)
- prorate based on remaining days in annual subscription
- track separately from stripe's built-in proration to aggregate monthly

includes unit tests and integration tests with stripe mocking

* refactor(billing): extract prisma logic from services to repositories

move database access logic from services to repository layer for better separation of concerns

- add SeatChangeLogRepository for seat change log data access
- add BillingPeriodRepository for billing period queries and updates
- refactor SeatChangeTrackingService to use SeatChangeLogRepository
- refactor BillingPeriodService to use BillingPeriodRepository
- add create methods to MonthlyProrationTeamRepository for billing records
- refactor MonthlyProrationService to use repository for billing creation
- remove direct prisma calls from services (except FeaturesRepository dependency)

services now focus on business logic while repositories handle data access

* refactor(billing): inject prisma via constructor in repositories

make repositories more testable by accepting prisma client via constructor

- SeatChangeLogRepository: accept optional prisma in constructor
- BillingPeriodRepository: accept optional prisma in constructor
- MonthlyProrationRepository: accept optional prisma in constructor
- MonthlyProrationTeamRepository: accept optional prisma in constructor

all repositories default to global prisma instance when not provided
enables easier mocking in tests by injecting test prisma instances

* refactor: remove verbose comments and unnecessary try-catch

* refactor: change pricePerSeat and proratedAmount to cents (Int)

store monetary values as integers in cents instead of floats for precision
remove unnecessary schema comments

* fix tests and add back try catch error handlign

* Update packages/features/ee/billing/service/proration/__tests__/MonthlyProrationService.integration-test.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* add faeture flag to config.ts

* Update packages/features/ee/billing/service/proration/__tests__/MonthlyProrationService.integration-test.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* restore schema

* add hooks types for flag

* fix: correct pricePerSeat calculations for cents-based storage

MonthlyProrationService now correctly handles pricePerSeat stored as
cents (INTEGER) instead of dollars (DOUBLE PRECISION). Removed duplicate
conversions to cents that were causing 100x price inflation.

* Use cents for money values

* fix type error

* Move integration to test to use cents value

* fix integration import

* mock subscription

* include paid seats tracking in proration service

* add mocks for invoice creation

* feat: use stripebilling service instead of using stripe directly

* fix validation erorr handler in proration service

* fix: fix sub

* use correct seat value

* fix(billing): mark seat changes as processed when netChange is zero

When a team has additions and removals that net to zero, the function
was returning null without calling markAsProcessed(). This left seat
change logs in an unprocessed state indefinitely, causing subsequent
proration runs to re-query the same changes repeatedly.

Changed the early return condition from checking netChange === 0 to
checking if there are no changes at all (additions === 0 && removals === 0).
This ensures that when there are actual seat changes that net to zero,
a proration record is still created and seat changes are marked as processed.

Co-Authored-By: unknown <>

* update test to reflect new return type of proration

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2026-01-12 10:58:21 +00:00