Implement proration#1
Conversation
- skip sub updates monthy proration - seat change logs for new user invites - cron month key params
- Add operationId field to SeatChangeLog for idempotency (prevents duplicate seat change logs from race conditions) - Add voidInvoice method to billing service (prevents double charging on retry) - Add days_until_due for send_invoice collection method (ensures invoices have proper due dates) - Fix type error in stripe-subscription-utils (null to undefined conversion) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…#26824) Addresses Cubic AI feedback: silent error suppression could lead to double charging if the Stripe API call fails. Now the error is logged and re-thrown so callers can handle failures appropriately. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: sean@cal.com <Sean@brydon.io>
…sitory Co-Authored-By: sean@cal.com <Sean@brydon.io>
| import { getBillingProviderService } from "@calcom/ee/billing/di/containers/Billing"; | ||
| import { getTeamBillingServiceFactory } from "@calcom/ee/billing/di/containers/Billing"; | ||
| import { extractBillingDataFromStripeSubscription } from "@calcom/features/ee/billing/lib/stripe-subscription-utils"; | ||
| import { Plan, SubscriptionStatus } from "@calcom/features/ee/billing/repository/billing/IBillingRepository"; |
| @@ -0,0 +1,131 @@ | |||
| import process from "node:process"; | |||
| import { getMonthlyProrationTasker } from "@calcom/features/ee/billing/di/tasker/MonthlyProrationTasker.container"; | |||
| import { formatMonthKey } from "@calcom/features/ee/billing/lib/month-key"; | |||
| const monthKey = requestedMonthKey || defaultMonthKey; | ||
|
|
||
| // Validate monthKey is not in the future and is within a reasonable range (12 months) | ||
| if (requestedMonthKey) { |
There was a problem hiding this comment.
what's going on here?
|
|
||
| log.info(`Scheduling monthly proration tasks for ${monthKey}`); | ||
|
|
||
| const teamRepository = new MonthlyProrationTeamRepository(prisma); |
| @@ -124,7 +125,11 @@ const handler = async (data: SWHMap["invoice.paid"]["data"]) => { | |||
| // Get the Stripe subscription object | |||
| const stripeSubscription = await stripe.subscriptions.retrieve(paymentSubscriptionId); | |||
| const billingService = getBillingProviderService(); | |||
| quantity: number; | ||
| prorationBehavior?: ProrationBehavior; | ||
| logger?: ISimpleLogger; | ||
| }): Promise<void> { |
| subscriptionEnd, | ||
| subscriptionTrialEnd, | ||
| billingPeriod, | ||
| pricePerSeat, | ||
| paidSeats, |
| linkType: hard | ||
|
|
||
| "@opentelemetry/api-logs@npm:0.209.0": | ||
| version: 0.209.0 |
| dependencies: | ||
| "@opentelemetry/api": "npm:^1.3.0" | ||
| checksum: 10/bd270d8b60b2fb8124174f0ec0babe7b57eca69f58feb6f4e1394714591324939dd4049a29499f435e948011cc8e5bdc9d0da2a2a96bd8be8ee9e5a3d297dfa0 | ||
| checksum: 10/fc9bf8693105f73162d8e6999443d5f15b2b3226f36a88815d5c5cc5a9771ecec92a808356087da062c24a4ad467891b33d8ae884fbe515cb1711bb7b192e371 |
There was a problem hiding this comment.
pending comment to existing
| "@opentelemetry/api-logs@npm:0.209.0": | ||
| version: 0.209.0 | ||
| resolution: "@opentelemetry/api-logs@npm:0.209.0" | ||
| "@opentelemetry/api-logs@npm:0.210.0": |
dastratakos
left a comment
There was a problem hiding this comment.
review comment no inlines
| @@ -0,0 +1,131 @@ | |||
| import process from "node:process"; | |||
| import { getMonthlyProrationTasker } from "@calcom/features/ee/billing/di/tasker/MonthlyProrationTasker.container"; | |||
| import { formatMonthKey } from "@calcom/features/ee/billing/lib/month-key"; | |||
| const isEnabled = await featuresRepository.checkIfFeatureIsEnabledGlobally( | ||
| "monthly-proration" | ||
| ); | ||
|
|
| @@ -3000,6 +3003,7 @@ model SeatChangeLog { | |||
| organizationBillingId String? | |||
| organizationBilling OrganizationBilling? @relation(fields: [organizationBillingId], references: [id]) | |||
|
|
|||
| organizationBilling: { | ||
| findUnique: vi.fn(), | ||
| update: vi.fn(), | ||
| }, |
| } = params; | ||
| const monthKey = providedMonthKey || formatMonthKey(new Date()); | ||
|
|
||
| const { teamBillingId, organizationBillingId } = await this.repository.getTeamBillingIds(teamId); |
| @@ -3000,6 +3003,7 @@ model SeatChangeLog { | |||
| organizationBillingId String? | |||
| organizationBilling OrganizationBilling? @relation(fields: [organizationBillingId], references: [id]) | |||
|
|
|||
|
LaTeX This sentence uses The Cauchy-Schwarz Inequality The Cauchy-Schwarz Inequality The Cauchy-Schwarz Inequality This expression uses To split $100 in half, we calculate |
|
test |
|
Ready to review this PR? Stage has broken it down into 12 individual chapters for you: Chapters generated by Stage for commit c9b4c77 on May 11, 2026 1:48am UTC. |
What does this PR do?
Visual Demo (For contributors especially)
A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).
Video Demo (if applicable):
Image Demo (if applicable):
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
Checklist