@@ -286,4 +286,64 @@ describe('POST /webhooks', () => {
286
286
// Verify that the new timestamp is newer than the old timestamp
287
287
expect ( new Date ( updatedInvoice . last_synced_at ) . getTime ( ) ) . toBeGreaterThan ( new Date ( oldTimestamp ) . getTime ( ) ) ;
288
288
} ) ;
289
+
290
+ it ( 'should NOT update invoice when webhook timestamp is older than last_synced_at' , async ( ) => {
291
+ let payload = loadWebhookPayload ( 'invoice' ) ;
292
+ const postgresClient = orbSync . postgresClient ;
293
+
294
+ const webhookData = JSON . parse ( payload ) ;
295
+ const invoiceId = webhookData . invoice . id ;
296
+ await deleteTestData ( orbSync . postgresClient , 'invoices' , [ invoiceId ] ) ;
297
+
298
+ webhookData . type = 'invoice.payment_succeeded' ;
299
+
300
+ // Insert an invoice with a "new" timestamp and known values
301
+ const originalAmount = 2000 ;
302
+ const originalStatus = 'paid' ;
303
+ webhookData . invoice . amount_due = originalAmount . toString ( ) ;
304
+ webhookData . invoice . total = originalAmount . toString ( ) ;
305
+ webhookData . invoice . status = originalStatus ;
306
+
307
+ const newTimestamp = new Date ( '2025-01-15T10:30:00.000Z' ) . toISOString ( ) ;
308
+ const initialInvoiceData = {
309
+ ...webhookData . invoice ,
310
+ amount_due : originalAmount . toString ( ) ,
311
+ total : originalAmount . toString ( ) ,
312
+ status : originalStatus ,
313
+ } ;
314
+ await syncInvoices ( postgresClient , [ initialInvoiceData ] , newTimestamp ) ;
315
+
316
+ // Verify the invoice was created with the new timestamp
317
+ const [ initialInvoice ] = await fetchInvoicesFromDatabase ( orbSync . postgresClient , [ invoiceId ] ) ;
318
+ expect ( initialInvoice ) . toBeDefined ( ) ;
319
+ expect ( Number ( initialInvoice . total ) ) . toBe ( originalAmount ) ;
320
+ expect ( initialInvoice . status ) . toBe ( originalStatus ) ;
321
+ expect ( new Date ( initialInvoice . last_synced_at ) . toISOString ( ) ) . toBe ( newTimestamp ) ;
322
+
323
+ // Now attempt to update with an older webhook timestamp and different values
324
+ const outdatedAmount = 1000 ;
325
+ const outdatedStatus = 'pending' ;
326
+ webhookData . invoice . amount_due = outdatedAmount . toString ( ) ;
327
+ webhookData . invoice . total = outdatedAmount . toString ( ) ;
328
+ webhookData . invoice . status = outdatedStatus ;
329
+ webhookData . invoice . paid_at = undefined ;
330
+ const oldWebhookTimestamp = new Date ( '2025-01-10T10:00:00.000Z' ) . toISOString ( ) ;
331
+ webhookData . created_at = oldWebhookTimestamp ;
332
+ payload = JSON . stringify ( webhookData ) ;
333
+
334
+ // Send the webhook with the outdated data
335
+ const response = await sendWebhookRequest ( payload ) ;
336
+ expect ( response . statusCode ) . toBe ( 200 ) ;
337
+ const data = response . json ( ) ;
338
+ expect ( data ) . toMatchObject ( { received : true } ) ;
339
+
340
+ // Fetch the invoice again and verify it was NOT updated
341
+ const [ afterWebhookInvoice ] = await fetchInvoicesFromDatabase ( orbSync . postgresClient , [ invoiceId ] ) ;
342
+ expect ( afterWebhookInvoice ) . toBeDefined ( ) ;
343
+ // Data should remain unchanged
344
+ expect ( Number ( afterWebhookInvoice . total ) ) . toBe ( originalAmount ) ;
345
+ expect ( afterWebhookInvoice . status ) . toBe ( originalStatus ) ;
346
+ // last_synced_at should remain the new timestamp
347
+ expect ( new Date ( afterWebhookInvoice . last_synced_at ) . toISOString ( ) ) . toBe ( newTimestamp ) ;
348
+ } ) ;
289
349
} ) ;
0 commit comments