@@ -104,78 +104,80 @@ async function createIdempotencyKey(usage){
104104 * @returns {Promise<bigint> } Previous cumulative usage (0n if not found)
105105 */
106106async function getPreviousUsage ( currentUsage , ctx ) {
107- // Calculate previous day's date (from - 24 hours)
108- // This works even on the 1st of the month (queries last day of previous month)
109- const previousFrom = new Date ( currentUsage . from . getTime ( ) - 24 * 60 * 60 * 1000 )
110-
111- // Query usage table with: customer PK, sk = previousFrom#provider#space
112- const result = await ctx . usageStore . get ( {
113- customer : currentUsage . customer ,
114- from : previousFrom ,
115- provider : currentUsage . provider ,
116- space : currentUsage . space
117- } )
118-
119- if ( result . ok ) {
120- return result . ok . usage
121- }
122-
123- if ( result . error && result . error . name !== 'RecordNotFound' ) {
124- throw result . error
125- }
126-
127- console . log ( `⚠️ No previous usage found for ${ currentUsage . space } on ${ previousFrom . toISOString ( ) } .\n Attempting to recover using Stripe meter event summaries...` )
128-
129- // Query Stripe for summaries (returns in reverse chronological order - newest first)
130- const summaries = await ctx . stripe . billing . meters . listEventSummaries ( STRIPE_BILLING_EVENT . id , {
131- customer : currentUsage . account . replace ( 'stripe:' , '' ) ,
132- start_time : startOfMonth ( currentUsage . from ) . getTime ( ) / 1000 ,
133- end_time : currentUsage . to . getTime ( ) / 1000 ,
134- value_grouping_window : 'day' ,
135- limit : 1
136- } ) ;
107+ try {
108+ // Calculate previous day's date (from - 24 hours)
109+ const previousFrom = new Date ( currentUsage . from . getTime ( ) - 24 * 60 * 60 * 1000 )
110+
111+ // Query usage table with: customer PK, sk = previousFrom#provider#space
112+ const result = await ctx . usageStore . get ( {
113+ customer : currentUsage . customer ,
114+ from : previousFrom ,
115+ provider : currentUsage . provider ,
116+ space : currentUsage . space
117+ } )
118+
119+ if ( result . ok ) {
120+ return result . ok . usage
121+ }
137122
138- if ( ! summaries . data || summaries . data . length === 0 ) {
139- console . log ( `No Stripe summaries found - treating as first-time customer` )
140- return 0n
141- }
123+ if ( result . error && result . error . name !== 'RecordNotFound' ) {
124+ throw result . error
125+ }
142126
143- const latestSummary = summaries . data [ 0 ]
144- const latestSummaryDate = new Date ( latestSummary . end_time * 1000 ) ;
127+ console . log ( `⚠️ No previous usage found for ${ currentUsage . space } on ${ previousFrom . toISOString ( ) } .\n Attempting to recover using Stripe meter event summaries...` )
145128
146- console . log ( `Found latest Stripe summary: ${ latestSummaryDate . toISOString ( ) } ` )
147-
148- // Query DynamoDB for usage at Stripe's latest date
149- const recoveryResult = await ctx . usageStore . get ( {
150- customer : currentUsage . customer ,
151- from : latestSummaryDate ,
152- provider : currentUsage . provider ,
153- space : currentUsage . space
154- } )
129+ // Query Stripe for summaries (returns in reverse chronological order - newest first)
130+ const summaries = await ctx . stripe . billing . meters . listEventSummaries ( STRIPE_BILLING_EVENT . id , {
131+ customer : currentUsage . account . replace ( 'stripe:' , '' ) ,
132+ start_time : startOfMonth ( currentUsage . from ) . getTime ( ) / 1000 ,
133+ end_time : currentUsage . to . getTime ( ) / 1000 ,
134+ value_grouping_window : 'day' ,
135+ limit : 1
136+ } ) ;
155137
156- if ( recoveryResult . ok ) {
157- console . log ( `⚠️ WARNING: Space ${ currentUsage . space } usage between ${ latestSummaryDate . toISOString ( ) } and ${ previousFrom . toISOString ( ) } is lost using Stripe summaries for recovery. ` )
158- return recoveryResult . ok . usage
159- }
138+ if ( ! summaries . data || summaries . data . length === 0 ) {
139+ console . log ( `No Stripe summaries found - treating as first-time customer ` )
140+ return 0n
141+ }
160142
161- if ( recoveryResult . error ?. name === 'RecordNotFound' ) {
162- console . error ( `CRITICAL DATA LOSS: Cannot calculate usage delta. Manual investigation and correction required.' \n ${ JSON . stringify ( {
163- previousDay : previousFrom . toISOString ( ) ,
164- latestSummaryDate : latestSummaryDate . toISOString ( ) ,
165- space : currentUsage . space ,
166- customer : currentUsage . customer ,
167- stripeAggregatedValue : latestSummary . aggregated_value ,
168- } ) } `)
169-
170- throw new Error (
171- `Critical: Cannot calculate usage delta for space ${ currentUsage . space } . ` +
172- `Both DynamoDB records missing (${ previousFrom . toISOString ( ) } and ${ latestSummaryDate . toISOString ( ) } ). ` +
173- `This indicates data loss. Manual investigation required.`
174- )
143+ const latestSummary = summaries . data [ 0 ]
144+ const latestSummaryDate = new Date ( latestSummary . end_time * 1000 ) ;
145+
146+ console . log ( `Found latest Stripe summary: ${ latestSummaryDate . toISOString ( ) } ` )
147+
148+ // Query DynamoDB for usage at Stripe's latest date
149+ const recoveryResult = await ctx . usageStore . get ( {
150+ customer : currentUsage . customer ,
151+ from : latestSummaryDate ,
152+ provider : currentUsage . provider ,
153+ space : currentUsage . space
154+ } )
155+
156+ if ( recoveryResult . ok ) {
157+ console . log ( `⚠️ WARNING: Space ${ currentUsage . space } usage between ${ latestSummaryDate . toISOString ( ) } and ${ previousFrom . toISOString ( ) } is lost using Stripe summaries for recovery.` )
158+ return recoveryResult . ok . usage
175159 }
176160
161+ if ( recoveryResult . error ?. name === 'RecordNotFound' ) {
162+ console . error ( `CRITICAL DATA LOSS: Cannot calculate usage delta. Manual investigation and correction required.' \n ${ JSON . stringify ( {
163+ previousDay : previousFrom . toISOString ( ) ,
164+ latestSummaryDate : latestSummaryDate . toISOString ( ) ,
165+ space : currentUsage . space ,
166+ customer : currentUsage . customer ,
167+ stripeAggregatedValue : latestSummary . aggregated_value ,
168+ } ) } `)
169+
170+ throw new Error (
171+ `Critical: Cannot calculate usage delta for space ${ currentUsage . space } . ` +
172+ `Both DynamoDB records missing (${ previousFrom . toISOString ( ) } and ${ latestSummaryDate . toISOString ( ) } ). ` +
173+ `This indicates data loss. Manual investigation required.`
174+ )
175+ }
176+ } catch ( error ) {
177+ console . error ( `Failed to recover previous usage for space ${ currentUsage . space } . Returning 0 to minimize losses. Error: ${ error . message } ` )
178+ }
177179
178- throw recoveryResult . error
180+ return 0n
179181}
180182
181183/**
@@ -227,9 +229,16 @@ export const reportUsage = async (usage, ctx) => {
227229 // Calculate cumulative byte quantity (for logging)
228230 const cumulativeByteQuantity = Math . floor ( new Big ( usage . usage . toString ( ) ) . div ( duration ) . toNumber ( ) )
229231
230- // Query previous day's usage to calculate delta
231- const previousCumulativeUsage = await getPreviousUsage ( usage , ctx )
232-
232+ let previousCumulativeUsage
233+ const isFirstOfMonth = usage . from . getUTCDate ( ) === 1
234+ if ( isFirstOfMonth ) {
235+ // NOTE: Since Stripe aggregates per billing period (monthly), each month starts fresh so no need to get previous usage and calculate delta.
236+ previousCumulativeUsage = 0n
237+ } else {
238+ // Query previous day's usage to calculate delta
239+ previousCumulativeUsage = await getPreviousUsage ( usage , ctx )
240+ }
241+
233242 // Calculate delta: current cumulative - previous cumulative (or 0 if no previous)
234243 // Note: Delta can be negative if users deleted data
235244 const deltaUsage = usage . usage - previousCumulativeUsage
@@ -277,6 +286,12 @@ export const reportUsage = async (usage, ctx) => {
277286 timestamp : referenceDate . toISOString ( ) ,
278287 idempotencyKey
279288 }
289+
290+ if ( deltaByteQuantity == 0 ) {
291+ console . log ( `No usage delta to report to Stripe. Skipping.\n${ JSON . stringify ( stripeRequest ) } ` )
292+ return { ok : { } }
293+ }
294+
280295 console . log ( `sending Stripe request:\n${ JSON . stringify ( stripeRequest ) } ` )
281296
282297 const meterEvent = await ctx . stripe . billing . meterEvents . create ( {
0 commit comments