@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, jest, test } from '@jest/globals';
33import { db , cleanupDbForTest } from '@/lib/drizzle' ;
44import {
55 credit_transactions ,
6+ kilocode_users ,
67 kilo_pass_audit_log ,
78 kilo_pass_subscriptions ,
89 payment_methods ,
@@ -285,6 +286,13 @@ describe('card fingerprint gate', () => {
285286 . from ( credit_transactions )
286287 . where ( eq ( credit_transactions . kilo_user_id , newUser . id ) ) ;
287288 expect ( creditRows ) . toHaveLength ( 0 ) ;
289+
290+ const blockedUser = await db . query . kilocode_users . findFirst ( {
291+ columns : { blocked_reason : true , blocked_at : true } ,
292+ where : eq ( kilocode_users . id , newUser . id ) ,
293+ } ) ;
294+ expect ( blockedUser ?. blocked_reason ) . toBe ( 'kilo_pass_duplicate_card' ) ;
295+ expect ( blockedUser ?. blocked_at ) . not . toBeNull ( ) ;
288296 } ) ;
289297
290298 test ( 'does not block same user re-subscribing with the same card' , async ( ) => {
@@ -389,6 +397,103 @@ describe('card fingerprint gate', () => {
389397 )
390398 ) ;
391399 expect ( creditRows . length ) . toBeGreaterThanOrEqual ( 1 ) ;
400+
401+ const userRow = await db . query . kilocode_users . findFirst ( {
402+ columns : { blocked_reason : true } ,
403+ where : eq ( kilocode_users . id , user . id ) ,
404+ } ) ;
405+ expect ( userRow ?. blocked_reason ) . toBeNull ( ) ;
406+ } ) ;
407+
408+ test ( 'does not overwrite existing blocked_reason when user is already blocked' , async ( ) => {
409+ const { handleKiloPassInvoicePaid } =
410+ await import ( '@/lib/kilo-pass/stripe-handlers-invoice-paid' ) ;
411+
412+ const existingUser = await insertTestUser ( {
413+ total_microdollars_acquired : 0 ,
414+ microdollars_used : 0 ,
415+ } ) ;
416+ const newUser = await insertTestUser ( {
417+ total_microdollars_acquired : 0 ,
418+ microdollars_used : 0 ,
419+ blocked_reason : 'preexisting_block' ,
420+ blocked_at : new Date ( ) . toISOString ( ) ,
421+ } ) ;
422+
423+ const fingerprint = `fp_already_blocked_${ Math . random ( ) } ` ;
424+ const existingPmId = `pm_already_blocked_existing_${ Math . random ( ) } ` ;
425+ const newPmId = `pm_already_blocked_new_${ Math . random ( ) } ` ;
426+
427+ await insertPaymentMethod ( { userId : existingUser . id , stripeId : existingPmId , fingerprint } ) ;
428+ await insertPaymentMethod ( { userId : newUser . id , stripeId : newPmId , fingerprint } ) ;
429+
430+ const existingSubId = `sub_already_blocked_existing_${ Math . random ( ) } ` ;
431+ await insertKiloPassSubscription ( {
432+ kiloUserId : existingUser . id ,
433+ stripeSubscriptionId : existingSubId ,
434+ tier : KiloPassTier . Tier19 ,
435+ cadence : KiloPassCadence . Monthly ,
436+ } ) ;
437+
438+ const newSubId = `sub_already_blocked_new_${ Math . random ( ) } ` ;
439+ const meta = kiloPassMetadata ( {
440+ kiloUserId : newUser . id ,
441+ tier : KiloPassTier . Tier19 ,
442+ cadence : KiloPassCadence . Monthly ,
443+ } ) ;
444+ const subscription = makeStripeSubscription ( {
445+ id : newSubId ,
446+ start_date_seconds : 1_735_689_600 ,
447+ metadata : meta ,
448+ } ) ;
449+
450+ const priceId = await getKiloPassPriceId ( {
451+ tier : KiloPassTier . Tier19 ,
452+ cadence : KiloPassCadence . Monthly ,
453+ } ) ;
454+
455+ const paymentIntentId = `pi_already_blocked_${ Math . random ( ) } ` ;
456+ const invoice = makeStripeInvoice ( {
457+ id : `inv_already_blocked_${ Math . random ( ) } ` ,
458+ amount_paid_cents : 1900 ,
459+ created_seconds : 1_735_689_600 ,
460+ priceId,
461+ subscriptionIdOrExpanded : newSubId ,
462+ metadata : meta ,
463+ paymentIntentId,
464+ } ) ;
465+
466+ const mockCancel = jest . fn ( async ( ) => ( {
467+ id : newSubId ,
468+ status : 'canceled' ,
469+ } ) ) ;
470+ const mockRefund = jest . fn ( async ( ) => ( { id : `re_${ Math . random ( ) } ` } ) ) ;
471+ const mockRetrievePm = jest . fn ( async ( ) => ( {
472+ id : newPmId ,
473+ card : { fingerprint } ,
474+ } ) ) ;
475+ const mockRetrieveSub = jest . fn ( async ( ) => subscription ) ;
476+
477+ const stripe = {
478+ subscriptions : { retrieve : mockRetrieveSub , cancel : mockCancel } ,
479+ refunds : { create : mockRefund } ,
480+ paymentMethods : { retrieve : mockRetrievePm } ,
481+ paymentIntents : {
482+ retrieve : jest . fn ( async ( ) => ( { id : paymentIntentId , payment_method : newPmId } ) ) ,
483+ } ,
484+ } ;
485+
486+ await handleKiloPassInvoicePaid ( {
487+ eventId : 'evt_already_blocked_test' ,
488+ invoice,
489+ stripe : stripe as unknown as Stripe ,
490+ } ) ;
491+
492+ const blockedUser = await db . query . kilocode_users . findFirst ( {
493+ columns : { blocked_reason : true } ,
494+ where : eq ( kilocode_users . id , newUser . id ) ,
495+ } ) ;
496+ expect ( blockedUser ?. blocked_reason ) . toBe ( 'preexisting_block' ) ;
392497 } ) ;
393498
394499 test ( 'does not block when other user has ended Kilo Pass with same fingerprint' , async ( ) => {
0 commit comments