@@ -30,6 +30,7 @@ import { cleanupDbForTest, db } from '@/lib/drizzle';
3030import type { isImpactConfigured , sendImpactConversionPayload } from '@/lib/impact' ;
3131import type { isImpactAdvocateConfigured } from '@/lib/impact/advocate' ;
3232import {
33+ expirePendingKiloPassReferralRewards ,
3334 markPersonalKiloPassReferralPaymentAdverse ,
3435 processPersonalKiloPassStripePaidConversion ,
3536} from '@/lib/impact/kilo-pass-referrals' ;
@@ -393,6 +394,50 @@ describe('Kilo Pass Impact referral conversions', () => {
393394 ) ;
394395 } ) ;
395396
397+ test ( 'missing or expired attribution suppresses affiliate SALE reporting' , async ( ) => {
398+ const noTouchReferee = await insertTestUser ( { created_at : '2026-01-02T00:00:00.000Z' } ) ;
399+ const noTouchSubscriptionId = await insertKiloPassSubscription ( {
400+ userId : noTouchReferee . id ,
401+ } ) ;
402+
403+ const noTouchDisposition = await processInvoice ( {
404+ refereeId : noTouchReferee . id ,
405+ subscriptionId : noTouchSubscriptionId ,
406+ } ) ;
407+
408+ expect ( noTouchDisposition ) . toEqual (
409+ expect . objectContaining ( {
410+ shouldEnqueueAffiliateSale : false ,
411+ winningTouchType : ImpactReferralWinningTouchType . None ,
412+ disqualificationReason : 'referral_no_valid_attribution' ,
413+ } )
414+ ) ;
415+
416+ await cleanupDbForTest ( ) ;
417+ const expiredTouchReferee = await insertTestUser ( { created_at : '2026-01-02T00:00:00.000Z' } ) ;
418+ await insertTouch ( {
419+ userId : expiredTouchReferee . id ,
420+ type : 'affiliate' ,
421+ touchedAt : '2025-12-01T00:00:00.000Z' ,
422+ } ) ;
423+ const expiredTouchSubscriptionId = await insertKiloPassSubscription ( {
424+ userId : expiredTouchReferee . id ,
425+ } ) ;
426+
427+ const expiredTouchDisposition = await processInvoice ( {
428+ refereeId : expiredTouchReferee . id ,
429+ subscriptionId : expiredTouchSubscriptionId ,
430+ } ) ;
431+
432+ expect ( expiredTouchDisposition ) . toEqual (
433+ expect . objectContaining ( {
434+ shouldEnqueueAffiliateSale : false ,
435+ winningTouchType : ImpactReferralWinningTouchType . None ,
436+ disqualificationReason : 'referral_no_valid_attribution' ,
437+ } )
438+ ) ;
439+ } ) ;
440+
396441 test ( 'only referral attribution grants double-sided pending rewards' , async ( ) => {
397442 const referrer = await insertTestUser ( { created_at : '2025-12-01T00:00:00.000Z' } ) ;
398443 const referee = await insertTestUser ( { created_at : '2026-01-02T00:00:00.000Z' } ) ;
@@ -593,6 +638,54 @@ describe('Kilo Pass Impact referral conversions', () => {
593638 expect ( await db . select ( ) . from ( impact_referral_rewards ) ) . toHaveLength ( 2 ) ;
594639 } ) ;
595640
641+ test ( 'expires stale pending and earned Kilo Pass referral rewards independently of issuance' , async ( ) => {
642+ const { rewardIds } = await seedKiloPassReferralRewardsForAdversePayment ( {
643+ statuses : [ ImpactReferralRewardStatus . Pending , ImpactReferralRewardStatus . Earned ] ,
644+ } ) ;
645+ await db
646+ . update ( impact_referral_rewards )
647+ . set ( { expires_at : '2026-01-02T00:00:00.000Z' } )
648+ . where ( eq ( impact_referral_rewards . id , rewardIds [ 0 ] ?? '' ) ) ;
649+ await db
650+ . update ( impact_referral_rewards )
651+ . set ( { expires_at : '2026-01-02T00:00:00.000Z' } )
652+ . where ( eq ( impact_referral_rewards . id , rewardIds [ 1 ] ?? '' ) ) ;
653+
654+ const firstSummary = await expirePendingKiloPassReferralRewards ( {
655+ now : new Date ( '2026-01-03T00:00:00.000Z' ) ,
656+ } ) ;
657+ const retrySummary = await expirePendingKiloPassReferralRewards ( {
658+ now : new Date ( '2026-01-03T00:00:00.000Z' ) ,
659+ } ) ;
660+
661+ expect ( firstSummary ) . toEqual ( { expiredRewards : 2 } ) ;
662+ expect ( retrySummary ) . toEqual ( { expiredRewards : 0 } ) ;
663+ const rewards = await db
664+ . select ( {
665+ status : impact_referral_rewards . status ,
666+ reviewReason : impact_referral_rewards . review_reason ,
667+ reversedAt : impact_referral_rewards . reversed_at ,
668+ } )
669+ . from ( impact_referral_rewards ) ;
670+ expect (
671+ rewards . map ( reward => ( {
672+ ...reward ,
673+ reversedAt : new Date ( reward . reversedAt ?? '' ) . toISOString ( ) ,
674+ } ) )
675+ ) . toEqual ( [
676+ {
677+ status : ImpactReferralRewardStatus . Expired ,
678+ reviewReason : 'expired_kilo_pass_referral_reward' ,
679+ reversedAt : '2026-01-03T00:00:00.000Z' ,
680+ } ,
681+ {
682+ status : ImpactReferralRewardStatus . Expired ,
683+ reviewReason : 'expired_kilo_pass_referral_reward' ,
684+ reversedAt : '2026-01-03T00:00:00.000Z' ,
685+ } ,
686+ ] ) ;
687+ } ) ;
688+
596689 test ( 'adverse Stripe payment cancels pending and earned Kilo Pass referral rewards idempotently' , async ( ) => {
597690 const { invoiceId, conversionId } = await seedKiloPassReferralRewardsForAdversePayment ( {
598691 statuses : [ ImpactReferralRewardStatus . Pending , ImpactReferralRewardStatus . Earned ] ,
0 commit comments