mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
fix(billing): change reset user stats func to invoice payment succeeded (#1116)
* fix(billing): change reset user stats func to invoice payment succeeded * remove nonexistent billing reason
This commit is contained in:
committed by
GitHub
parent
780870c48e
commit
25b2c45ec0
@@ -18,27 +18,75 @@ export async function handleInvoicePaymentSucceeded(event: Stripe.Event) {
|
||||
try {
|
||||
const invoice = event.data.object as Stripe.Invoice
|
||||
|
||||
// Check if this is an overage billing invoice
|
||||
if (invoice.metadata?.type !== 'overage_billing') {
|
||||
logger.info('Ignoring non-overage billing invoice', { invoiceId: invoice.id })
|
||||
// Case 1: Overage invoices (metadata.type === 'overage_billing')
|
||||
if (invoice.metadata?.type === 'overage_billing') {
|
||||
const customerId = invoice.customer as string
|
||||
const chargedAmount = invoice.amount_paid / 100
|
||||
const billingPeriod = invoice.metadata?.billingPeriod || 'unknown'
|
||||
|
||||
logger.info('Overage billing invoice payment succeeded', {
|
||||
invoiceId: invoice.id,
|
||||
customerId,
|
||||
chargedAmount,
|
||||
billingPeriod,
|
||||
customerEmail: invoice.customer_email,
|
||||
hostedInvoiceUrl: invoice.hosted_invoice_url,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const customerId = invoice.customer as string
|
||||
const chargedAmount = invoice.amount_paid / 100 // Convert from cents to dollars
|
||||
const billingPeriod = invoice.metadata?.billingPeriod || 'unknown'
|
||||
// Case 2: Subscription renewal invoice paid (primary period rollover)
|
||||
// Only reset on successful payment to avoid granting a new period while in dunning
|
||||
if (invoice.subscription) {
|
||||
// Filter to subscription-cycle renewals; ignore updates/off-cycle charges
|
||||
const reason = invoice.billing_reason
|
||||
const isCycle = reason === 'subscription_cycle'
|
||||
if (!isCycle) {
|
||||
logger.info('Ignoring non-cycle subscription invoice on payment_succeeded', {
|
||||
invoiceId: invoice.id,
|
||||
billingReason: reason,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('Overage billing invoice payment succeeded', {
|
||||
invoiceId: invoice.id,
|
||||
customerId,
|
||||
chargedAmount,
|
||||
billingPeriod,
|
||||
customerEmail: invoice.customer_email,
|
||||
hostedInvoiceUrl: invoice.hosted_invoice_url,
|
||||
})
|
||||
const stripeSubscriptionId = String(invoice.subscription)
|
||||
const records = await db
|
||||
.select()
|
||||
.from(subscriptionTable)
|
||||
.where(eq(subscriptionTable.stripeSubscriptionId, stripeSubscriptionId))
|
||||
.limit(1)
|
||||
|
||||
// Additional payment success logic can be added here
|
||||
// For example: update internal billing status, trigger analytics events, etc.
|
||||
if (records.length === 0) {
|
||||
logger.warn('No matching internal subscription for paid Stripe invoice', {
|
||||
invoiceId: invoice.id,
|
||||
stripeSubscriptionId,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const sub = records[0]
|
||||
|
||||
if (sub.plan === 'team' || sub.plan === 'enterprise') {
|
||||
await resetOrganizationBillingPeriod(sub.referenceId)
|
||||
logger.info('Reset organization billing period on subscription invoice payment', {
|
||||
invoiceId: invoice.id,
|
||||
organizationId: sub.referenceId,
|
||||
plan: sub.plan,
|
||||
})
|
||||
} else {
|
||||
await resetUserBillingPeriod(sub.referenceId)
|
||||
logger.info('Reset user billing period on subscription invoice payment', {
|
||||
invoiceId: invoice.id,
|
||||
userId: sub.referenceId,
|
||||
plan: sub.plan,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('Ignoring non-subscription invoice payment', { invoiceId: invoice.id })
|
||||
} catch (error) {
|
||||
logger.error('Failed to handle invoice payment succeeded', {
|
||||
eventId: event.id,
|
||||
@@ -105,67 +153,20 @@ export async function handleInvoicePaymentFailed(event: Stripe.Event) {
|
||||
export async function handleInvoiceFinalized(event: Stripe.Event) {
|
||||
try {
|
||||
const invoice = event.data.object as Stripe.Invoice
|
||||
|
||||
// Case 1: Overage invoices (metadata.type === 'overage_billing')
|
||||
// Do not reset usage on finalized; wait for payment success to avoid granting new period during dunning
|
||||
if (invoice.metadata?.type === 'overage_billing') {
|
||||
const customerId = invoice.customer as string
|
||||
const invoiceAmount = invoice.amount_due / 100
|
||||
const billingPeriod = invoice.metadata?.billingPeriod || 'unknown'
|
||||
|
||||
logger.info('Overage billing invoice finalized', {
|
||||
invoiceId: invoice.id,
|
||||
customerId,
|
||||
invoiceAmount,
|
||||
billingPeriod,
|
||||
customerEmail: invoice.customer_email,
|
||||
hostedInvoiceUrl: invoice.hosted_invoice_url,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Case 2: Subscription cycle invoices (primary period rollover)
|
||||
// When an invoice is finalized for a subscription cycle, align our usage reset to this boundary
|
||||
if (invoice.subscription) {
|
||||
const stripeSubscriptionId = String(invoice.subscription)
|
||||
|
||||
const records = await db
|
||||
.select()
|
||||
.from(subscriptionTable)
|
||||
.where(eq(subscriptionTable.stripeSubscriptionId, stripeSubscriptionId))
|
||||
.limit(1)
|
||||
|
||||
if (records.length === 0) {
|
||||
logger.warn('No matching internal subscription for Stripe invoice subscription', {
|
||||
invoiceId: invoice.id,
|
||||
stripeSubscriptionId,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const sub = records[0]
|
||||
|
||||
// Idempotent reset aligned to the subscription’s new cycle
|
||||
if (sub.plan === 'team' || sub.plan === 'enterprise') {
|
||||
await resetOrganizationBillingPeriod(sub.referenceId)
|
||||
logger.info('Reset organization billing period on subscription invoice finalization', {
|
||||
invoiceId: invoice.id,
|
||||
organizationId: sub.referenceId,
|
||||
plan: sub.plan,
|
||||
})
|
||||
} else {
|
||||
await resetUserBillingPeriod(sub.referenceId)
|
||||
logger.info('Reset user billing period on subscription invoice finalization', {
|
||||
invoiceId: invoice.id,
|
||||
userId: sub.referenceId,
|
||||
plan: sub.plan,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('Ignoring non-subscription invoice finalization', {
|
||||
logger.info('Ignoring subscription invoice finalization; will act on payment_succeeded', {
|
||||
invoiceId: invoice.id,
|
||||
billingReason: invoice.billing_reason,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user