Skip to content

Commit 348c107

Browse files
committed
fix(referrals): preserve historical Kilo Pass affiliate sales
1 parent dcd83ab commit 348c107

2 files changed

Lines changed: 119 additions & 9 deletions

File tree

apps/web/src/lib/impact/kilo-pass-referrals.test.ts

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
impact_referral_rewards,
4646
kilo_pass_issuances,
4747
kilo_pass_subscriptions,
48+
user_affiliate_attributions,
4849
} from '@kilocode/db/schema';
4950
import {
5051
ImpactAdvocateProgramKey,
@@ -394,7 +395,28 @@ describe('Kilo Pass Impact referral conversions', () => {
394395
);
395396
});
396397

397-
test('missing or expired attribution suppresses affiliate SALE reporting', async () => {
398+
test('historical affiliate attribution without product-scoped touch preserves affiliate SALE', async () => {
399+
const referee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
400+
await db.insert(user_affiliate_attributions).values({
401+
user_id: referee.id,
402+
provider: 'impact',
403+
tracking_id: '',
404+
});
405+
const subscriptionId = await insertKiloPassSubscription({ userId: referee.id });
406+
407+
const disposition = await processInvoice({ refereeId: referee.id, subscriptionId });
408+
409+
expect(disposition).toEqual(
410+
expect.objectContaining({
411+
shouldEnqueueAffiliateSale: true,
412+
winningTouchType: ImpactReferralWinningTouchType.Affiliate,
413+
disqualificationReason: 'referral_affiliate_won',
414+
})
415+
);
416+
expect(await db.select().from(impact_referral_rewards)).toHaveLength(0);
417+
});
418+
419+
test('missing attribution and expired product-scoped touches suppress affiliate SALE reporting', async () => {
398420
const noTouchReferee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
399421
const noTouchSubscriptionId = await insertKiloPassSubscription({
400422
userId: noTouchReferee.id,
@@ -414,22 +436,60 @@ describe('Kilo Pass Impact referral conversions', () => {
414436
);
415437

416438
await cleanupDbForTest();
417-
const expiredTouchReferee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
439+
const expiredAffiliateReferee = await insertTestUser({
440+
created_at: '2026-01-02T00:00:00.000Z',
441+
});
442+
await db.insert(user_affiliate_attributions).values({
443+
user_id: expiredAffiliateReferee.id,
444+
provider: 'impact',
445+
tracking_id: 'historical-affiliate-click',
446+
});
418447
await insertTouch({
419-
userId: expiredTouchReferee.id,
448+
userId: expiredAffiliateReferee.id,
420449
type: 'affiliate',
421450
touchedAt: '2025-12-01T00:00:00.000Z',
422451
});
423-
const expiredTouchSubscriptionId = await insertKiloPassSubscription({
424-
userId: expiredTouchReferee.id,
452+
const expiredAffiliateSubscriptionId = await insertKiloPassSubscription({
453+
userId: expiredAffiliateReferee.id,
454+
});
455+
456+
const expiredAffiliateDisposition = await processInvoice({
457+
refereeId: expiredAffiliateReferee.id,
458+
subscriptionId: expiredAffiliateSubscriptionId,
459+
});
460+
461+
expect(expiredAffiliateDisposition).toEqual(
462+
expect.objectContaining({
463+
shouldEnqueueAffiliateSale: false,
464+
winningTouchType: ImpactReferralWinningTouchType.None,
465+
disqualificationReason: 'referral_no_valid_attribution',
466+
})
467+
);
468+
469+
await cleanupDbForTest();
470+
const expiredReferralReferee = await insertTestUser({
471+
created_at: '2026-01-02T00:00:00.000Z',
472+
});
473+
await db.insert(user_affiliate_attributions).values({
474+
user_id: expiredReferralReferee.id,
475+
provider: 'impact',
476+
tracking_id: 'historical-affiliate-click',
477+
});
478+
await insertTouch({
479+
userId: expiredReferralReferee.id,
480+
type: 'referral',
481+
touchedAt: '2025-12-01T00:00:00.000Z',
482+
});
483+
const expiredReferralSubscriptionId = await insertKiloPassSubscription({
484+
userId: expiredReferralReferee.id,
425485
});
426486

427-
const expiredTouchDisposition = await processInvoice({
428-
refereeId: expiredTouchReferee.id,
429-
subscriptionId: expiredTouchSubscriptionId,
487+
const expiredReferralDisposition = await processInvoice({
488+
refereeId: expiredReferralReferee.id,
489+
subscriptionId: expiredReferralSubscriptionId,
430490
});
431491

432-
expect(expiredTouchDisposition).toEqual(
492+
expect(expiredReferralDisposition).toEqual(
433493
expect.objectContaining({
434494
shouldEnqueueAffiliateSale: false,
435495
winningTouchType: ImpactReferralWinningTouchType.None,

apps/web/src/lib/impact/kilo-pass-referrals.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
kilo_pass_issuances,
3232
kilo_pass_subscriptions,
3333
kilocode_users,
34+
user_affiliate_attributions,
3435
type ImpactAttributionTouch,
3536
} from '@kilocode/db/schema';
3637
import {
@@ -130,6 +131,24 @@ async function findAcceptedUserTouches(params: {
130131
);
131132
}
132133

134+
async function hasHistoricalImpactAffiliateAttribution(params: {
135+
userId: string;
136+
database: DatabaseClient;
137+
}): Promise<boolean> {
138+
const [attribution] = await params.database
139+
.select({ id: user_affiliate_attributions.id })
140+
.from(user_affiliate_attributions)
141+
.where(
142+
and(
143+
eq(user_affiliate_attributions.user_id, params.userId),
144+
eq(user_affiliate_attributions.provider, 'impact')
145+
)
146+
)
147+
.limit(1);
148+
149+
return Boolean(attribution);
150+
}
151+
133152
async function markAffiliateTouchSaleAttributed(params: {
134153
database: DatabaseClient;
135154
affiliateTouchId: string;
@@ -523,6 +542,37 @@ export async function processPersonalKiloPassStripePaidConversion(params: {
523542
referralTouchId: resolution.referralTouch?.id ?? null,
524543
});
525544

545+
// Preserve pre-touch-model affiliate attribution without allowing an expired scoped touch
546+
// to bypass the referral attribution window.
547+
if (
548+
resolution.winner === 'none' &&
549+
touches.length === 0 &&
550+
(await hasHistoricalImpactAffiliateAttribution({ userId: params.userId, database: tx }))
551+
) {
552+
const [conversion] = await tx
553+
.insert(impact_referral_conversions)
554+
.values({
555+
product: ImpactReferralProduct.KiloPass,
556+
referee_user_id: params.userId,
557+
referrer_user_id: null,
558+
source_touch_id: null,
559+
winning_touch_type: ImpactReferralWinningTouchType.Affiliate,
560+
source_payment_id: params.sourcePaymentId,
561+
payment_provider: paymentProvider,
562+
qualified: false,
563+
disqualification_reason: referralDisqualificationReason('affiliate_won'),
564+
converted_at: params.convertedAt.toISOString(),
565+
})
566+
.returning({ id: impact_referral_conversions.id });
567+
568+
return {
569+
shouldEnqueueAffiliateSale: true,
570+
winningTouchType: ImpactReferralWinningTouchType.Affiliate,
571+
conversionId: conversion?.id ?? null,
572+
disqualificationReason: referralDisqualificationReason('affiliate_won'),
573+
} satisfies KiloPassPaidConversionDisposition;
574+
}
575+
526576
if (resolution.winner === 'none') {
527577
const [conversion] = await tx
528578
.insert(impact_referral_conversions)

0 commit comments

Comments
 (0)