@@ -104,80 +104,76 @@ async function createIdempotencyKey(usage){
104104 * @returns {Promise<bigint> } Previous cumulative usage (0n if not found)
105105 */
106106async function getPreviousUsage ( currentUsage , ctx ) {
107- try {
108- // Calculate previous day's date (from - 24 hours)
109- const previousFrom = new Date ( currentUsage . from . getTime ( ) - 24 * 60 * 60 * 1000 )
107+ // Calculate previous day's date (from - 24 hours)
108+ const previousFrom = new Date ( currentUsage . from . getTime ( ) - 24 * 60 * 60 * 1000 )
109+
110+ // Query usage table with: customer PK, sk = previousFrom#provider#space
111+ const result = await ctx . usageStore . get ( {
112+ customer : currentUsage . customer ,
113+ from : previousFrom ,
114+ provider : currentUsage . provider ,
115+ space : currentUsage . space
116+ } )
110117
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+ if ( result . ok ) {
119+ return result . ok . usage
120+ }
118121
119- if ( result . ok ) {
120- return result . ok . usage
121- }
122+ if ( result . error && result . error . name !== 'RecordNotFound' ) {
123+ throw result . error
124+ }
122125
123- if ( result . error && result . error . name !== 'RecordNotFound' ) {
124- throw result . error
125- }
126+ console . log ( `⚠️ No previous usage found for ${ currentUsage . space } on ${ previousFrom . toISOString ( ) } .\n Attempting to recover using Stripe meter event summaries...` )
127+
128+ // Query Stripe for summaries (returns in reverse chronological order - newest first)
129+ const summaries = await ctx . stripe . billing . meters . listEventSummaries ( STRIPE_BILLING_EVENT . id , {
130+ customer : currentUsage . account . replace ( 'stripe:' , '' ) ,
131+ start_time : startOfMonth ( currentUsage . from ) . getTime ( ) / 1000 ,
132+ end_time : currentUsage . to . getTime ( ) / 1000 ,
133+ value_grouping_window : 'day' ,
134+ limit : 1
135+ } ) ;
126136
127- console . log ( `⚠️ No previous usage found for ${ currentUsage . space } on ${ previousFrom . toISOString ( ) } .\n Attempting to recover using Stripe meter event summaries...` )
137+ if ( ! summaries . data || summaries . data . length === 0 ) {
138+ console . log ( `No Stripe summaries found - treating as first-time customer` )
139+ return 0n
140+ }
128141
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- } ) ;
142+ const latestSummary = summaries . data [ 0 ]
143+ const latestSummaryDate = new Date ( latestSummary . end_time * 1000 ) ;
137144
138- if ( ! summaries . data || summaries . data . length === 0 ) {
139- console . log ( `No Stripe summaries found - treating as first-time customer` )
140- return 0n
141- }
145+ console . log ( `Found latest Stripe summary: ${ latestSummaryDate . toISOString ( ) } ` )
146+
147+ // Query DynamoDB for usage at Stripe's latest date
148+ const recoveryResult = await ctx . usageStore . get ( {
149+ customer : currentUsage . customer ,
150+ from : latestSummaryDate ,
151+ provider : currentUsage . provider ,
152+ space : currentUsage . space
153+ } )
142154
143- const latestSummary = summaries . data [ 0 ]
144- const latestSummaryDate = new Date ( latestSummary . end_time * 1000 ) ;
155+ if ( recoveryResult . ok ) {
156+ console . log ( `⚠️ WARNING: Space ${ currentUsage . space } usage between ${ latestSummaryDate . toISOString ( ) } and ${ previousFrom . toISOString ( ) } is lost using Stripe summaries for recovery.` )
157+ return recoveryResult . ok . usage
158+ }
145159
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 ( {
160+ if ( recoveryResult . error ?. name === 'RecordNotFound' ) {
161+ console . error ( `CRITICAL DATA LOSS: Cannot calculate usage delta. Manual investigation and correction required. \n ${ JSON . stringify ( {
162+ previousDay : previousFrom . toISOString ( ) ,
163+ latestSummaryDate : latestSummaryDate . toISOString ( ) ,
164+ space : currentUsage . space ,
150165 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
159- }
166+ stripeAggregatedValue : latestSummary . aggregated_value ,
167+ } ) } `)
160168
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 } ` )
169+ throw new Error (
170+ `Critical: Cannot calculate usage delta for space ${ currentUsage . space } . ` +
171+ `Both DynamoDB records missing (${ previousFrom . toISOString ( ) } and ${ latestSummaryDate . toISOString ( ) } ). ` +
172+ `This indicates data loss. Manual investigation required.`
173+ )
178174 }
179-
180- return 0n
175+
176+ throw recoveryResult . error ?? new Error ( 'Unknown error querying usage store during recovery' )
181177}
182178
183179/**
@@ -231,15 +227,20 @@ export const reportUsage = async (usage, ctx) => {
231227
232228 const isFirstOfMonth = usage . from . getUTCDate ( ) === 1
233229 // NOTE: Since Stripe aggregates per billing period (monthly), each month starts fresh so no need to get previous usage and calculate delta.
234- const previousCumulativeUsage = isFirstOfMonth
235- ? 0n
236- : await getPreviousUsage ( usage , ctx )
237-
230+ let previousCumulativeUsage
231+ try {
232+ previousCumulativeUsage = isFirstOfMonth ? 0n : await getPreviousUsage ( usage , ctx )
233+ } catch ( /** @type {any } */ err ) {
234+ return { error : err }
235+ }
236+
238237 // Calculate delta: current cumulative - previous cumulative (or 0 if no previous)
239238 // Note: Delta can be negative if users deleted data
240239 const deltaUsage = usage . usage - previousCumulativeUsage
241240
242- if ( previousCumulativeUsage === 0n ) {
241+ if ( isFirstOfMonth ) {
242+ console . log ( `First of month reset - reporting full usage as delta (no previous lookup)` , JSON . stringify ( { customer : usageContext . customer , space : usageContext . space } ) )
243+ } else if ( previousCumulativeUsage === 0n ) {
243244 console . log ( `No previous usage found - reporting full current usage as delta` , JSON . stringify ( { customer : usageContext . customer , space : usageContext . space } ) )
244245 } else {
245246 console . log ( 'Delta calculation:' , JSON . stringify ( {
0 commit comments