Skip to content

Commit 62a94f1

Browse files
committed
fix(billing): recommit the in-flight value, include the plan in seat convergence, bound the reconcile's outbox reads
1 parent c79bc16 commit 62a94f1

9 files changed

Lines changed: 340 additions & 162 deletions

File tree

‎apps/sim/lib/admin/subscription-lifecycle.ts‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { OUTBOX_EVENT_TYPES } from '@/lib/billing/webhooks/outbox-events'
1111
import {
1212
enqueueCancelAtPeriodEndSync,
1313
lockSubscriptionForSyncRetry,
14-
recommitSubscriptionSync,
14+
recordCancelAtPeriodEnd,
1515
} from '@/lib/billing/webhooks/subscription-sync'
1616
import { enqueueOutboxEvent } from '@/lib/core/outbox/service'
1717

@@ -338,11 +338,7 @@ export async function requestDashboardSubscriptionCancellation({
338338
and(eq(outboxEvent.id, existingOperation.id), eq(outboxEvent.status, 'dead_letter'))
339339
)
340340
if (existingOperation.eventType === OUTBOX_EVENT_TYPES.STRIPE_SYNC_CANCEL_AT_PERIOD_END) {
341-
await recommitSubscriptionSync(
342-
tx,
343-
OUTBOX_EVENT_TYPES.STRIPE_SYNC_CANCEL_AT_PERIOD_END,
344-
existingOperation.subscriptionId
345-
)
341+
await recordCancelAtPeriodEnd(tx, existingOperation.subscriptionId, true)
346342
}
347343
return {
348344
operationId,

‎apps/sim/lib/billing/organizations/provision-seat.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const mockGetHighestPriorityPersonalSubscription =
5555

5656
/** The subscription row as the activation re-reads it under its lock. */
5757
function testExecutor(onSubscriptionLock: () => void = () => {}) {
58-
const lockedRow = { cancelAtPeriodEnd: false, seats: 1 }
58+
const lockedRow = { cancelAtPeriodEnd: false, seats: 1, stripeSubscriptionId: 'stripe_sub' }
5959
return {
6060
select: () => ({
6161
from: () => ({

‎apps/sim/lib/billing/organizations/provision-seat.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ async function convertPersonalSubscriptionToTeam(
243243
* the caller's earlier read is still cleared and recorded.
244244
*/
245245
async function activateTeamSubscription(
246-
sub: { id: string; stripeSubscriptionId: string | null },
246+
sub: { id: string },
247247
targetPlan: string,
248248
{ planChanged }: { planChanged: boolean },
249249
tx: DbOrTx
@@ -252,6 +252,7 @@ async function activateTeamSubscription(
252252
.select({
253253
cancelAtPeriodEnd: subscriptionTable.cancelAtPeriodEnd,
254254
seats: subscriptionTable.seats,
255+
stripeSubscriptionId: subscriptionTable.stripeSubscriptionId,
255256
})
256257
.from(subscriptionTable)
257258
.where(eq(subscriptionTable.id, sub.id))
@@ -271,10 +272,10 @@ async function activateTeamSubscription(
271272
})
272273
}
273274

274-
if (!sub.stripeSubscriptionId) return
275-
if (locked?.cancelAtPeriodEnd) {
275+
if (!locked?.stripeSubscriptionId) return
276+
if (locked.cancelAtPeriodEnd) {
276277
await enqueueCancelAtPeriodEndSync(tx, {
277-
stripeSubscriptionId: sub.stripeSubscriptionId,
278+
stripeSubscriptionId: locked.stripeSubscriptionId,
278279
subscriptionId: sub.id,
279280
cancelAtPeriodEnd: false,
280281
reason: 'pro-to-team-conversion',

‎apps/sim/lib/billing/webhooks/outbox-handlers.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,11 +299,13 @@ const stripeSyncSubscriptionSeats: OutboxHandler<SubscriptionSeatsSyncPayload> =
299299

300300
const latest = await getSubscriptionSeatSyncState(payload.subscriptionId)
301301
const latestSeats = (await readRecordedSyncValue(ctx.eventId))?.seats ?? (latest?.seats || 1)
302-
if (latestSeats !== desiredSeats) {
303-
logger.info('Subscription seats changed during Stripe sync; retrying latest value', {
302+
if (latestSeats !== desiredSeats || latest?.plan !== row.plan) {
303+
logger.info('Subscription plan or seats changed during Stripe sync; retrying latest', {
304304
eventId: ctx.eventId,
305305
subscriptionId: payload.subscriptionId,
306306
stripeSubscriptionId: row.stripeSubscriptionId,
307+
attemptedPlan: row.plan,
308+
latestPlan: latest?.plan,
307309
attemptedSeats: desiredSeats,
308310
latestSeats,
309311
attempt,
@@ -323,7 +325,7 @@ const stripeSyncSubscriptionSeats: OutboxHandler<SubscriptionSeatsSyncPayload> =
323325
return
324326
}
325327

326-
throw new Error(`Subscription seats changed while syncing ${payload.subscriptionId}`)
328+
throw new Error(`Subscription plan or seats changed while syncing ${payload.subscriptionId}`)
327329
}
328330

329331
const stripeThresholdOverageInvoice: OutboxHandler<StripeThresholdOverageInvoicePayload> = async (

0 commit comments

Comments
 (0)