Math.min(12, Math.max(1, Math.round(month)
export function KiloPassBonusRampDialog(props: {
tier: KiloPassTier;
showFirstMonthPromo?: boolean;
- showSecondMonthPromo?: boolean;
streakMonths?: number;
showSlider?: boolean;
- subscriptionStartedAtIso?: string;
}) {
- const {
- tier,
- showFirstMonthPromo = false,
- showSecondMonthPromo = false,
- streakMonths,
- showSlider = true,
- subscriptionStartedAtIso,
- } = props;
+ const { tier, showFirstMonthPromo = false, streakMonths, showSlider = true } = props;
const [open, setOpen] = useState(false);
- const fallbackSubscriptionStartedAtIso = useMemo(() => {
- const now = new Date();
- now.setSeconds(0, 0);
- return now.toISOString();
- }, []);
-
- const resolvedSubscriptionStartedAtIso =
- subscriptionStartedAtIso ?? fallbackSubscriptionStartedAtIso;
const resolvedMonth =
typeof streakMonths === 'number' && !Number.isNaN(streakMonths) ? clampMonth(streakMonths) : 1;
const [sliderMonth, setSliderMonth] = useState(resolvedMonth);
@@ -60,7 +43,6 @@ export function KiloPassBonusRampDialog(props: {
tier,
streakMonths: effectiveMonth,
isFirstTimeSubscriberEver: showFirstMonthPromo,
- subscriptionStartedAtIso: resolvedSubscriptionStartedAtIso,
});
const totalBonusUsd = useMemo(
@@ -71,18 +53,10 @@ export function KiloPassBonusRampDialog(props: {
tier,
streakMonths: index + 1,
isFirstTimeSubscriberEver: showFirstMonthPromo,
- subscriptionStartedAtIso: resolvedSubscriptionStartedAtIso,
})
).reduce((total, percent) => total + config.monthlyPriceUsd * percent, 0)
: 0,
- [
- config.monthlyPriceUsd,
- effectiveMonth,
- showFirstMonthPromo,
- showSlider,
- resolvedSubscriptionStartedAtIso,
- tier,
- ]
+ [config.monthlyPriceUsd, effectiveMonth, showFirstMonthPromo, showSlider, tier]
);
return (
@@ -172,9 +146,7 @@ export function KiloPassBonusRampDialog(props: {
{showPromoCallout && (
- As a new subscriber, your{' '}
- first {showSecondMonthPromo ? '2 paid months' : 'paid month'} get a
- one-time promo of{' '}
+ As a new subscriber, your first paid month gets a one-time promo of{' '}
+{formatPercent(KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT)} free bonus credits
{' '}
diff --git a/apps/web/src/components/profile/kilo-pass/KiloPassSubscribeCard.tsx b/apps/web/src/components/profile/kilo-pass/KiloPassSubscribeCard.tsx
index 6c3e06f1ff..c3ce7f78bc 100644
--- a/apps/web/src/components/profile/kilo-pass/KiloPassSubscribeCard.tsx
+++ b/apps/web/src/components/profile/kilo-pass/KiloPassSubscribeCard.tsx
@@ -7,12 +7,8 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { KiloPassCadence } from '@/lib/kilo-pass/enums';
import type { KiloPassTier } from '@/lib/kilo-pass/enums';
-import {
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF,
- KILO_PASS_TIER_CONFIG,
-} from '@/lib/kilo-pass/constants';
+import { KILO_PASS_TIER_CONFIG } from '@/lib/kilo-pass/constants';
import { cn } from '@/lib/utils';
-import { formatIsoDateString_UsaDateOnlyFormat } from '@/lib/utils';
import { KiloPassTierCard } from './KiloPassTierCard';
@@ -21,7 +17,6 @@ export function KiloPassSubscribeCard(props: {
setCadence: (cadence: KiloPassCadence) => void;
pending: boolean;
showFirstMonthPromo?: boolean;
- showSecondMonthPromo: boolean;
showHeader?: boolean;
className?: string;
contentClassName?: string;
@@ -33,7 +28,6 @@ export function KiloPassSubscribeCard(props: {
setCadence,
pending,
showFirstMonthPromo = false,
- showSecondMonthPromo,
showHeader = true,
className,
contentClassName,
@@ -43,14 +37,9 @@ export function KiloPassSubscribeCard(props: {
const tiers = Object.keys(KILO_PASS_TIER_CONFIG) as KiloPassTier[];
- const promoCutoffLabel = formatIsoDateString_UsaDateOnlyFormat(
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.toISOString()
- );
const monthlyPromoDescription =
cadence === KiloPassCadence.Monthly && showFirstMonthPromo
- ? showSecondMonthPromo
- ? `First-time subscribers receive 50% free bonus credits for the first two months when they start before ${promoCutoffLabel}.`
- : 'First-time subscribers receive 50% free bonus credits for the first month.'
+ ? 'First-time subscribers receive 50% free bonus credits for the first month.'
: null;
const cadenceOptions = [
{ value: KiloPassCadence.Monthly, label: 'Monthly' },
@@ -121,7 +110,6 @@ export function KiloPassSubscribeCard(props: {
cadence={cadence}
pending={pending}
showFirstMonthPromo={showFirstMonthPromo}
- showSecondMonthPromo={showSecondMonthPromo}
isRecommended={recommendedTier != null && tier === recommendedTier}
onSelect={onSelectTier}
/>
diff --git a/apps/web/src/components/profile/kilo-pass/KiloPassTierCard.tsx b/apps/web/src/components/profile/kilo-pass/KiloPassTierCard.tsx
index b352cdbd77..192d9f0fed 100644
--- a/apps/web/src/components/profile/kilo-pass/KiloPassTierCard.tsx
+++ b/apps/web/src/components/profile/kilo-pass/KiloPassTierCard.tsx
@@ -24,19 +24,10 @@ export function KiloPassTierCard(props: {
cadence: KiloPassCadence;
pending: boolean;
showFirstMonthPromo?: boolean;
- showSecondMonthPromo?: boolean;
isRecommended: boolean;
onSelect: (tier: KiloPassTier) => void;
}) {
- const {
- tier,
- cadence,
- pending,
- showFirstMonthPromo = false,
- showSecondMonthPromo = false,
- isRecommended,
- onSelect,
- } = props;
+ const { tier, cadence, pending, showFirstMonthPromo = false, isRecommended, onSelect } = props;
const config = KILO_PASS_TIER_CONFIG[tier];
const handleSelect = () => {
if (pending) return;
@@ -99,17 +90,13 @@ export function KiloPassTierCard(props: {
{' '}
free bonus credits
-
+
{showFirstMonthPromo && (
- {showSecondMonthPromo ? 'First 2 months:' : 'First month:'} +
- {formatPercent(KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT)} free bonus credits
+ First month: +{formatPercent(KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT)} free bonus
+ credits
)}
>
diff --git a/apps/web/src/components/referrals/ImpactAdvocateReferralCard.test.ts b/apps/web/src/components/referrals/ImpactAdvocateReferralCard.test.ts
new file mode 100644
index 0000000000..58a10fced4
--- /dev/null
+++ b/apps/web/src/components/referrals/ImpactAdvocateReferralCard.test.ts
@@ -0,0 +1,15 @@
+import { describe, expect, it } from '@jest/globals';
+
+import { buildImpactAdvocateTokenUrl } from './ImpactAdvocateReferralCard.utils';
+
+describe('buildImpactAdvocateTokenUrl', () => {
+ it('defaults to the KiloClaw Advocate token endpoint for existing callers', () => {
+ expect(buildImpactAdvocateTokenUrl()).toBe('/api/impact-advocate/token');
+ });
+
+ it('requests the Kilo Pass Advocate token without falling back to KiloClaw config', () => {
+ expect(buildImpactAdvocateTokenUrl('kilo_pass')).toBe(
+ '/api/impact-advocate/token?product=kilo_pass'
+ );
+ });
+});
diff --git a/apps/web/src/components/referrals/ImpactAdvocateReferralCard.tsx b/apps/web/src/components/referrals/ImpactAdvocateReferralCard.tsx
index 407ee68595..343bca7f3e 100644
--- a/apps/web/src/components/referrals/ImpactAdvocateReferralCard.tsx
+++ b/apps/web/src/components/referrals/ImpactAdvocateReferralCard.tsx
@@ -1,18 +1,58 @@
'use client';
-import { createElement, useEffect, useState } from 'react';
+import { useQuery } from '@tanstack/react-query';
+import { createElement, useEffect } from 'react';
+
+import {
+ buildImpactAdvocateTokenUrl,
+ type ImpactAdvocateReferralProduct,
+} from './ImpactAdvocateReferralCard.utils';
+
+type WidgetToken = {
+ token: string;
+ widgetId: string;
+};
type WidgetState =
| { status: 'loading' }
| { status: 'ready'; token: string; widgetId: string }
| { status: 'unavailable'; message: string };
-function renderWidgetContent(state: WidgetState) {
+async function getWidgetToken(product: ImpactAdvocateReferralProduct): Promise {
+ const response = await fetch(buildImpactAdvocateTokenUrl(product), {
+ method: 'GET',
+ credentials: 'same-origin',
+ headers: {
+ Accept: 'application/json',
+ },
+ });
+
+ const payload = (await response.json().catch(() => null)) as {
+ token?: string;
+ widgetId?: string;
+ error?: string;
+ } | null;
+
+ if (!response.ok || !payload?.token || !payload.widgetId) {
+ throw new Error(
+ payload?.error ??
+ (response.status === 503
+ ? 'Referral sharing is not configured in this environment.'
+ : 'Referral sharing is temporarily unavailable.')
+ );
+ }
+
+ return { token: payload.token, widgetId: payload.widgetId };
+}
+
+function WidgetContent({ state }: { state: WidgetState }) {
switch (state.status) {
case 'loading':
- return Loading referral sharing…
;
+ return (
+ Loading referral sharing…
+ );
case 'unavailable':
- return {state.message}
;
+ return {state.message} ;
case 'ready':
return (
@@ -29,72 +69,44 @@ function renderWidgetContent(state: WidgetState) {
}
}
-export function ImpactAdvocateReferralWidget() {
- const [state, setState] = useState
({ status: 'loading' });
+export function ImpactAdvocateReferralWidget({
+ product = 'kiloclaw',
+}: {
+ product?: ImpactAdvocateReferralProduct;
+}) {
+ const tokenQuery = useQuery({
+ queryKey: ['impact-advocate-widget-token', product],
+ queryFn: () => getWidgetToken(product),
+ retry: false,
+ });
useEffect(() => {
- let cancelled = false;
- delete window.impactToken;
-
- const loadWidgetToken = async () => {
- try {
- const response = await fetch('/api/impact-advocate/token', {
- method: 'GET',
- credentials: 'same-origin',
- headers: {
- Accept: 'application/json',
- },
- });
-
- const payload = (await response.json().catch(() => null)) as {
- token?: string;
- widgetId?: string;
- error?: string;
- } | null;
-
- if (cancelled) {
- return;
- }
-
- if (!response.ok || !payload?.token || !payload.widgetId) {
- delete window.impactToken;
- setState({
- status: 'unavailable',
- message:
- payload?.error ??
- (response.status === 503
- ? 'Referral sharing is not configured in this environment.'
- : 'Referral sharing is temporarily unavailable.'),
- });
- return;
- }
-
- window.impactToken = payload.token;
- setState({
- status: 'ready',
- token: payload.token,
- widgetId: payload.widgetId,
- });
- } catch (error) {
- if (cancelled) {
- return;
- }
-
- delete window.impactToken;
- setState({
- status: 'unavailable',
- message: error instanceof Error ? error.message : 'Failed to load referral sharing.',
- });
- }
- };
-
- void loadWidgetToken();
+ if (tokenQuery.data) {
+ window.impactToken = tokenQuery.data.token;
+ } else {
+ delete window.impactToken;
+ }
return () => {
- cancelled = true;
delete window.impactToken;
};
- }, []);
+ }, [tokenQuery.data]);
+
+ const state: WidgetState = tokenQuery.isPending
+ ? { status: 'loading' }
+ : tokenQuery.isError
+ ? {
+ status: 'unavailable',
+ message:
+ tokenQuery.error instanceof Error
+ ? tokenQuery.error.message
+ : 'Failed to load referral sharing.',
+ }
+ : { status: 'ready', token: tokenQuery.data.token, widgetId: tokenQuery.data.widgetId };
- return {renderWidgetContent(state)}
;
+ return (
+
+
+
+ );
}
diff --git a/apps/web/src/components/referrals/ImpactAdvocateReferralCard.utils.ts b/apps/web/src/components/referrals/ImpactAdvocateReferralCard.utils.ts
new file mode 100644
index 0000000000..78124b20d7
--- /dev/null
+++ b/apps/web/src/components/referrals/ImpactAdvocateReferralCard.utils.ts
@@ -0,0 +1,6 @@
+export type ImpactAdvocateReferralProduct = 'kiloclaw' | 'kilo_pass';
+
+export function buildImpactAdvocateTokenUrl(product: ImpactAdvocateReferralProduct = 'kiloclaw') {
+ if (product === 'kiloclaw') return '/api/impact-advocate/token';
+ return `/api/impact-advocate/token?product=${encodeURIComponent(product)}`;
+}
diff --git a/apps/web/src/components/referrals/KiloPassReferralButton.tsx b/apps/web/src/components/referrals/KiloPassReferralButton.tsx
new file mode 100644
index 0000000000..68456f95ea
--- /dev/null
+++ b/apps/web/src/components/referrals/KiloPassReferralButton.tsx
@@ -0,0 +1,19 @@
+import Link from 'next/link';
+import { Gift } from 'lucide-react';
+
+import { Button } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+
+export function KiloPassReferralButton({ className }: { className?: string }) {
+ return (
+
+
+
+ Refer & earn
+
+ New
+
+
+
+ );
+}
diff --git a/apps/web/src/components/referrals/KiloPassReferralPageContent.test.ts b/apps/web/src/components/referrals/KiloPassReferralPageContent.test.ts
new file mode 100644
index 0000000000..fcf9e2451e
--- /dev/null
+++ b/apps/web/src/components/referrals/KiloPassReferralPageContent.test.ts
@@ -0,0 +1,135 @@
+import React from 'react';
+import { renderToStaticMarkup } from 'react-dom/server';
+import { describe, expect, it } from '@jest/globals';
+
+import { KiloPassReferralPageContent } from './KiloPassReferralPageContent';
+import type { KiloPassReferralRewardSummary } from './KiloPassReferralPageContent';
+
+const emptySummary: KiloPassReferralRewardSummary = {
+ totals: {
+ totalRewards: 0,
+ pendingRewards: 0,
+ appliedRewards: 0,
+ totalRewardAmountUsd: 0,
+ pendingRewardAmountUsd: 0,
+ appliedRewardAmountUsd: 0,
+ },
+ referrerCap: {
+ grantedRewards: 0,
+ limit: 5,
+ reached: false,
+ },
+ rewards: [],
+};
+
+describe('KiloPassReferralPageContent', () => {
+ it('renders Kilo Pass-specific empty copy and an accessible widget region', () => {
+ const html = renderToStaticMarkup(
+ React.createElement(
+ KiloPassReferralPageContent,
+ { summary: emptySummary },
+ React.createElement('div', { 'data-testid': 'share-widget' }, 'widget body')
+ )
+ );
+
+ expect(html).toContain('Earn Kilo Pass referral bonuses');
+ expect(html).toContain('50% monthly Kilo Pass bonus');
+ expect(html).toContain('No Kilo Pass referral rewards yet.');
+ expect(html).toContain('aria-label="Kilo Pass referral sharing"');
+ expect(html).toContain('data-testid="share-widget"');
+ expect(html).not.toContain('Share your Kilo Pass referral link');
+ expect(html).not.toContain('Use the Kilo Pass referral widget');
+ expect(html).not.toContain('KiloClaw');
+ expect(html).not.toContain('free month');
+ });
+
+ it('renders loading and non-sensitive error states without color-only messaging', () => {
+ const loadingHtml = renderToStaticMarkup(
+ React.createElement(KiloPassReferralPageContent, {
+ summary: null,
+ isLoading: true,
+ })
+ );
+ const errorHtml = renderToStaticMarkup(
+ React.createElement(KiloPassReferralPageContent, {
+ summary: null,
+ errorMessage: 'Rewards are temporarily unavailable. Try again in a minute.',
+ })
+ );
+
+ expect(loadingHtml).toContain('Loading Kilo Pass referral rewards…');
+ expect(loadingHtml).toContain(' {
+ const html = renderToStaticMarkup(
+ React.createElement(KiloPassReferralPageContent, {
+ summary: {
+ totals: {
+ totalRewards: 3,
+ pendingRewards: 1,
+ appliedRewards: 1,
+ totalRewardAmountUsd: 58.5,
+ pendingRewardAmountUsd: 24.5,
+ appliedRewardAmountUsd: 9.5,
+ },
+ referrerCap: {
+ grantedRewards: 5,
+ limit: 5,
+ reached: true,
+ },
+ rewards: [
+ {
+ id: 'reward-pending',
+ role: 'referrer',
+ status: 'pending',
+ rewardAmountUsd: 24.5,
+ earnedAt: '2026-05-10T00:00:00.000Z',
+ appliedAt: null,
+ expiresAt: '2027-05-10T00:00:00.000Z',
+ sourceTier: 'tier_49',
+ reviewReason: null,
+ },
+ {
+ id: 'reward-applied',
+ role: 'referee',
+ status: 'applied',
+ rewardAmountUsd: 9.5,
+ earnedAt: '2026-05-11T00:00:00.000Z',
+ appliedAt: '2026-06-01T00:00:00.000Z',
+ expiresAt: null,
+ sourceTier: 'tier_19',
+ reviewReason: null,
+ },
+ {
+ id: 'reward-review-required',
+ role: 'referrer',
+ status: 'review_required',
+ rewardAmountUsd: 24.5,
+ earnedAt: '2026-05-12T00:00:00.000Z',
+ appliedAt: null,
+ expiresAt: null,
+ sourceTier: 'tier_49',
+ reviewReason: 'payment_refunded',
+ },
+ ],
+ },
+ })
+ );
+
+ expect(html).toContain('$58.50');
+ expect(html).toContain('$24.50');
+ expect(html).toContain('$9.50');
+ expect(html).toContain('Cap reached');
+ expect(html).toContain('5 of 5 referrer rewards');
+ expect(html).toContain('Waiting for a future eligible monthly issuance');
+ expect(html).toContain('Applied');
+ expect(html).toContain('Needs review');
+ expect(html).toContain('May 10, 2026');
+ expect(html).toContain('Jun 1, 2026');
+ });
+});
diff --git a/apps/web/src/components/referrals/KiloPassReferralPageContent.tsx b/apps/web/src/components/referrals/KiloPassReferralPageContent.tsx
new file mode 100644
index 0000000000..b695991a28
--- /dev/null
+++ b/apps/web/src/components/referrals/KiloPassReferralPageContent.tsx
@@ -0,0 +1,370 @@
+'use client';
+
+import React, { type ReactNode } from 'react';
+import Link from 'next/link';
+import { CalendarDays, Gift, History, Info, Sparkles } from 'lucide-react';
+
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent } from '@/components/ui/card';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { formatIsoDateString_UsaDateOnlyFormat } from '@/lib/utils';
+
+const SHARE_WIDGET_ANCHOR_ID = 'kilo-pass-referral-share';
+
+export type KiloPassReferralRewardStatus =
+ | 'pending'
+ | 'earned'
+ | 'applied'
+ | 'expired'
+ | 'canceled'
+ | 'reversed'
+ | 'review_required';
+
+export type KiloPassReferralRewardSummary = {
+ totals: {
+ totalRewards: number;
+ pendingRewards: number;
+ appliedRewards: number;
+ totalRewardAmountUsd: number;
+ pendingRewardAmountUsd: number;
+ appliedRewardAmountUsd: number;
+ };
+ referrerCap: {
+ grantedRewards: number;
+ limit: number;
+ reached: boolean;
+ };
+ rewards: Array<{
+ id: string;
+ role: 'referrer' | 'referee';
+ status: KiloPassReferralRewardStatus;
+ rewardAmountUsd: number;
+ earnedAt: string;
+ appliedAt: string | null;
+ expiresAt: string | null;
+ sourceTier: string | null;
+ reviewReason: string | null;
+ }>;
+};
+
+type KiloPassReferralPageContentProps = {
+ summary: KiloPassReferralRewardSummary | null;
+ isLoading?: boolean;
+ errorMessage?: string | null;
+ children?: ReactNode;
+};
+
+type StatusPresentation = {
+ label: string;
+ className: string;
+};
+
+const usdFormatter = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+});
+
+function formatUsd(amount: number): string {
+ return usdFormatter.format(amount);
+}
+
+function formatTier(tier: string | null): string {
+ switch (tier) {
+ case 'tier_19':
+ return '$19 monthly tier';
+ case 'tier_49':
+ return '$49 monthly tier';
+ case 'tier_199':
+ return '$199 monthly tier';
+ default:
+ return 'monthly tier';
+ }
+}
+
+function roleLabel(role: 'referrer' | 'referee'): string {
+ return role === 'referrer' ? 'Referral you shared' : 'Referral you used';
+}
+
+function rewardStatusPresentation(status: KiloPassReferralRewardStatus): StatusPresentation {
+ switch (status) {
+ case 'applied':
+ return {
+ label: 'Applied',
+ className: 'bg-emerald-500/20 text-emerald-400 ring-emerald-500/20',
+ };
+ case 'earned':
+ case 'pending':
+ return {
+ label: 'Waiting for a future eligible monthly issuance',
+ className: 'bg-yellow-500/20 text-yellow-400 ring-yellow-500/20',
+ };
+ case 'expired':
+ return {
+ label: 'Expired',
+ className: 'bg-zinc-500/20 text-zinc-400 ring-zinc-500/20',
+ };
+ case 'canceled':
+ return {
+ label: 'Canceled',
+ className: 'bg-zinc-500/20 text-zinc-400 ring-zinc-500/20',
+ };
+ case 'reversed':
+ return {
+ label: 'Reversed',
+ className: 'bg-red-500/20 text-red-400 ring-red-500/20',
+ };
+ case 'review_required':
+ return {
+ label: 'Needs review',
+ className: 'bg-orange-500/20 text-orange-400 ring-orange-500/20',
+ };
+ }
+}
+
+export function KiloPassReferralPageContent({
+ summary,
+ isLoading = false,
+ errorMessage,
+ children,
+}: KiloPassReferralPageContentProps) {
+ return (
+
+
+
+
+ Kilo Pass referrals
+
+
+
+
Earn Kilo Pass referral bonuses
+
+ Share Kilo Pass with someone else and when their first eligible monthly payment is
+ confirmed, you both earn a 50% monthly Kilo Pass bonus based on their tier.
+
+
+
+ Back to Kilo Pass
+
+
+
+
+
+
+
+
+ {isLoading ? (
+
+ Loading Kilo Pass referral rewards…
+
+ ) : errorMessage ? (
+
+
+ Kilo Pass referral rewards are unavailable
+ {errorMessage || 'Try again in a minute.'}
+
+
+ ) : summary ? (
+
+ ) : null}
+
+
+
+ );
+}
+
+function KiloPassReferralSummary({ summary }: { summary: KiloPassReferralRewardSummary }) {
+ return (
+
+
+
+ Reward summary
+
+
+ Track pending referral bonuses and previous Kilo Pass referral reward history.
+
+
+
+ {summary.referrerCap.reached ? (
+
+
+
Cap reached
+
+ {summary.referrerCap.grantedRewards} of {summary.referrerCap.limit} referrer rewards
+ granted. Referee rewards do not count toward this cap.
+
+
+
+ ) : null}
+
+
+
+ 0 ? 'warning' : undefined}
+ />
+
+
+
+
+
+
+
+
+ Reward history
+
+ {summary.rewards.length === 0 ? (
+
+ ) : (
+
+ {summary.rewards.map(reward => (
+
+ ))}
+
+ )}
+
+
+ );
+}
+
+type IndicatorTone = 'warning';
+
+function SummaryTile({
+ label,
+ value,
+ info,
+ indicator,
+}: {
+ label: string;
+ value: string;
+ info?: string;
+ indicator?: IndicatorTone;
+}) {
+ return (
+
+
+ {indicator === 'warning' ? (
+
+ ) : null}
+ {label}
+ {info ? (
+
+
+
+
+
+
+ {info}
+
+ ) : null}
+
+
+ {value}
+
+
+ );
+}
+
+function RewardRow({ reward }: { reward: KiloPassReferralRewardSummary['rewards'][number] }) {
+ const status = rewardStatusPresentation(reward.status);
+ return (
+
+
+
{roleLabel(reward.role)}
+
+
+ {formatTier(reward.sourceTier)}
+
+
+
+
+ {status.label}
+
+
+ ·
+
+
+ {formatUsd(reward.rewardAmountUsd)}
+
+
+
+
+
+
+ Earned{' '}
+
+ {formatIsoDateString_UsaDateOnlyFormat(reward.earnedAt)}
+
+
+
+ {reward.appliedAt ? (
+
+
+
+ Applied{' '}
+
+ {formatIsoDateString_UsaDateOnlyFormat(reward.appliedAt)}
+
+
+
+ ) : reward.expiresAt ? (
+
+ Expires{' '}
+
+ {formatIsoDateString_UsaDateOnlyFormat(reward.expiresAt)}
+
+
+ ) : reward.status === 'review_required' ? (
+
Support review required before this reward changes.
+ ) : (
+
Application details appear after the referral bonus is issued.
+ )}
+
+
+ );
+}
diff --git a/apps/web/src/components/subscriptions/PersonalSubscriptions.tsx b/apps/web/src/components/subscriptions/PersonalSubscriptions.tsx
index c8638ecba9..0250a140ad 100644
--- a/apps/web/src/components/subscriptions/PersonalSubscriptions.tsx
+++ b/apps/web/src/components/subscriptions/PersonalSubscriptions.tsx
@@ -12,9 +12,12 @@ import { KiloClawGroup } from './kiloclaw/KiloClawGroup';
import { CodingPlansGroup } from './coding-plans/CodingPlansGroup';
import { ENABLE_CODING_PLAN_SUBSCRIPTIONS } from '@/lib/constants';
+const defaultExpandedSections = ENABLE_CODING_PLAN_SUBSCRIPTIONS
+ ? ['kilo-pass', 'kiloclaw', 'coding-plans']
+ : ['kilo-pass', 'kiloclaw'];
+
export function PersonalSubscriptions() {
const [showTerminal, setShowTerminal] = useState(false);
- const [expandedSection, setExpandedSection] = useState('kilo-pass');
const trpc = useTRPC();
const kiloPassQuery = useQuery(trpc.kiloPass.getState.queryOptions());
const kiloClawQuery = useQuery(trpc.kiloclaw.listPersonalSubscriptions.queryOptions());
@@ -41,13 +44,7 @@ export function PersonalSubscriptions() {
) : null
}
>
-
+
{ENABLE_CODING_PLAN_SUBSCRIPTIONS ? (
diff --git a/apps/web/src/components/subscriptions/kilo-pass/KiloPassDetail.tsx b/apps/web/src/components/subscriptions/kilo-pass/KiloPassDetail.tsx
index c2352203f6..c67ba2fbcd 100644
--- a/apps/web/src/components/subscriptions/kilo-pass/KiloPassDetail.tsx
+++ b/apps/web/src/components/subscriptions/kilo-pass/KiloPassDetail.tsx
@@ -21,6 +21,7 @@ import { cn } from '@/lib/utils';
import { useRawTRPCClient, useTRPC } from '@/lib/trpc/utils';
import { formatDollars, formatIsoDateString_UsaDateOnlyFormat } from '@/lib/utils';
import { DetailPageHeader } from '@/components/subscriptions/DetailPageHeader';
+import { KiloPassReferralButton } from '@/components/referrals/KiloPassReferralButton';
import { BillingHistoryTable } from '@/components/subscriptions/BillingHistoryTable';
import { CreditHistory } from './CreditHistory';
import {
@@ -110,23 +111,10 @@ export function KiloPassDetail() {
tier: subscription.tier,
streakMonths: Math.max(1, subscription.currentStreakMonths),
isFirstTimeSubscriberEver: subscription.isFirstTimeSubscriberEver,
- subscriptionStartedAtIso: subscription.startedAt,
});
return promoPercent === KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT;
}, [subscription]);
- const showSecondMonthPromoInDialog = useMemo(() => {
- if (!subscription || subscription.cadence !== 'monthly') return false;
- if (subscription.currentStreakMonths > 2) return false;
- const month2Percent = computeMonthlyCadenceBonusPercent({
- tier: subscription.tier,
- streakMonths: 2,
- isFirstTimeSubscriberEver: subscription.isFirstTimeSubscriberEver,
- subscriptionStartedAtIso: subscription.startedAt,
- });
- return month2Percent === KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT;
- }, [subscription]);
-
async function refreshData() {
await Promise.all([
queryClient.invalidateQueries({ queryKey: trpc.kiloPass.getState.queryKey() }),
@@ -189,6 +177,7 @@ export function KiloPassDetail() {
backLabel="Back to subscriptions"
title="Kilo Pass"
status={subscriptionDisplay.status}
+ actions={isKiloPassTerminal(subscription.status) ? null : }
/>
{subscriptionDisplay.detailAlert ? (
@@ -262,9 +251,7 @@ export function KiloPassDetail() {
diff --git a/apps/web/src/components/subscriptions/kilo-pass/KiloPassGroup.tsx b/apps/web/src/components/subscriptions/kilo-pass/KiloPassGroup.tsx
index 1fed8b70d8..987dae2caf 100644
--- a/apps/web/src/components/subscriptions/kilo-pass/KiloPassGroup.tsx
+++ b/apps/web/src/components/subscriptions/kilo-pass/KiloPassGroup.tsx
@@ -5,8 +5,6 @@ import { useState } from 'react';
import { toast } from 'sonner';
import { Crown } from 'lucide-react';
import { useTRPC } from '@/lib/trpc/utils';
-import { KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF } from '@/lib/kilo-pass/constants';
-import { dayjs } from '@/lib/kilo-pass/dayjs';
import { KiloPassCadence } from '@/lib/kilo-pass/enums';
import type { KiloPassTier } from '@/lib/kilo-pass/enums';
import { recommendKiloPassTierFromAverageMonthlyUsageUsd } from '@/lib/kilo-pass/recommend-tier';
@@ -27,12 +25,6 @@ import {
getKiloPassSubscriptionDisplayModel,
} from './KiloPassDetail.logic';
-function getShowKiloPassTwoMonthPromo(showFirstMonthPromo: boolean): boolean {
- return (
- showFirstMonthPromo && dayjs().utc().isBefore(KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF)
- );
-}
-
export function KiloPassGroup({
showTerminal,
accordionValue,
@@ -60,7 +52,7 @@ export function KiloPassGroup({
toast.error('Failed to create Stripe checkout session');
return;
}
- window.location.href = result.url;
+ window.location.assign(result.url);
},
onError: error => {
toast.error(error.message || 'Failed to start checkout');
@@ -73,7 +65,6 @@ export function KiloPassGroup({
}
const showFirstMonthPromo = query.data?.isEligibleForFirstMonthPromo ?? false;
- const showSecondMonthPromo = getShowKiloPassTwoMonthPromo(showFirstMonthPromo);
const averageMonthlyUsageUsd = averageMonthlyUsageQuery.data?.averageMonthlyUsageUsd;
const recommendedTier =
typeof averageMonthlyUsageUsd === 'number'
@@ -134,7 +125,6 @@ export function KiloPassGroup({
setCadence={setCadence}
pending={checkout.isPending}
showFirstMonthPromo={showFirstMonthPromo}
- showSecondMonthPromo={showSecondMonthPromo}
recommendedTier={recommendedTier}
onSelectTier={tier => void startCheckout(tier)}
showHeader={false}
diff --git a/apps/web/src/lib/config.server.ts b/apps/web/src/lib/config.server.ts
index d69640eb1d..28dced06b6 100644
--- a/apps/web/src/lib/config.server.ts
+++ b/apps/web/src/lib/config.server.ts
@@ -45,10 +45,16 @@ export const IMPACT_ACCOUNT_SID = getEnvVariable('IMPACT_ACCOUNT_SID') || '';
export const IMPACT_AUTH_TOKEN = getEnvVariable('IMPACT_AUTH_TOKEN') || '';
export const IMPACT_CAMPAIGN_ID = getEnvVariable('IMPACT_CAMPAIGN_ID') || '';
export const IMPACT_ADVOCATE_TENANT_ALIAS = getEnvVariable('IMPACT_ADVOCATE_TENANT_ALIAS') || '';
-export const IMPACT_ADVOCATE_PROGRAM_ID = getEnvVariable('IMPACT_ADVOCATE_PROGRAM_ID') || '';
export const IMPACT_ADVOCATE_ACCOUNT_SID = getEnvVariable('IMPACT_ADVOCATE_ACCOUNT_SID') || '';
export const IMPACT_ADVOCATE_AUTH_TOKEN = getEnvVariable('IMPACT_ADVOCATE_AUTH_TOKEN') || '';
-export const IMPACT_ADVOCATE_WIDGET_ID = getEnvVariable('IMPACT_ADVOCATE_WIDGET_ID') || '';
+export const IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID =
+ getEnvVariable('IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID') || '';
+export const IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID =
+ getEnvVariable('IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID') || '';
+export const IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID =
+ getEnvVariable('IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID') || '';
+export const IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID =
+ getEnvVariable('IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID') || '';
export const IMPACT_ADVOCATE_API_BASE_URL =
getEnvVariable('IMPACT_ADVOCATE_API_BASE_URL') || 'https://app.referralsaasquatch.com';
export const IMPACT_ADVOCATE_DEBUG_LOGGING =
diff --git a/apps/web/src/lib/getSignInCallbackUrl.test.ts b/apps/web/src/lib/getSignInCallbackUrl.test.ts
index 285db50534..39ca045cdf 100644
--- a/apps/web/src/lib/getSignInCallbackUrl.test.ts
+++ b/apps/web/src/lib/getSignInCallbackUrl.test.ts
@@ -98,6 +98,11 @@ describe('getSignInCallbackUrl', () => {
)
).toBe(true);
});
+
+ test('accepts Kilo Pass referral paths', () => {
+ expect(isValidCallbackPath('/subscriptions/kilo-pass')).toBe(true);
+ expect(isValidCallbackPath('/subscriptions/kilo-pass/refer')).toBe(true);
+ });
});
describe('invalid paths', () => {
@@ -276,6 +281,21 @@ describe('getSignInCallbackUrl', () => {
'/users/after-sign-in?_saasquatch=opaque-referral-cookie&rsCode=ref-code&utm_source=invite&utm_medium=link&utm_campaign=saasquatch&callbackPath=%2Fclaw%2Fnew'
);
});
+
+ test('preserves Kilo Pass callback paths and referral UTM metadata', () => {
+ const result = getSignInCallbackUrl({
+ callbackPath: '/subscriptions/kilo-pass/refer',
+ _saasquatch: 'opaque-referral-cookie',
+ rsCode: 'ref-code',
+ utm_source: 'invite',
+ utm_medium: 'link',
+ utm_campaign: 'saasquatch',
+ });
+
+ expect(result).toBe(
+ '/users/after-sign-in?_saasquatch=opaque-referral-cookie&rsCode=ref-code&utm_source=invite&utm_medium=link&utm_campaign=saasquatch&callbackPath=%2Fsubscriptions%2Fkilo-pass%2Frefer'
+ );
+ });
});
describe('stripHost', () => {
diff --git a/apps/web/src/lib/getSignInCallbackUrl.ts b/apps/web/src/lib/getSignInCallbackUrl.ts
index 6b4f2a317a..0bcc16f37e 100644
--- a/apps/web/src/lib/getSignInCallbackUrl.ts
+++ b/apps/web/src/lib/getSignInCallbackUrl.ts
@@ -23,6 +23,8 @@ export function isValidCallbackPath(path: string): boolean {
path === '/claw' ||
path.startsWith('/claw/') ||
path.startsWith('/cloud') ||
+ path === '/subscriptions/kilo-pass' ||
+ path.startsWith('/subscriptions/kilo-pass/') ||
path.startsWith('/integrations/') ||
// Admin-managed URL bonus campaigns. Stricter shape enforcement
// (slug format, prefix-match guard) happens in
diff --git a/apps/web/src/lib/impact/advocate.test.ts b/apps/web/src/lib/impact/advocate.test.ts
index 912a86b2f9..b690f7da96 100644
--- a/apps/web/src/lib/impact/advocate.test.ts
+++ b/apps/web/src/lib/impact/advocate.test.ts
@@ -9,6 +9,10 @@ describe('impact advocate', () => {
IMPACT_ADVOCATE_PROGRAM_ID: process.env.IMPACT_ADVOCATE_PROGRAM_ID,
IMPACT_ADVOCATE_TENANT_ALIAS: process.env.IMPACT_ADVOCATE_TENANT_ALIAS,
IMPACT_ADVOCATE_WIDGET_ID: process.env.IMPACT_ADVOCATE_WIDGET_ID,
+ IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID: process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID,
+ IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID: process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID,
+ IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID: process.env.IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID,
+ IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID: process.env.IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID,
IMPACT_ACCOUNT_SID: process.env.IMPACT_ACCOUNT_SID,
};
@@ -20,12 +24,19 @@ describe('impact advocate', () => {
process.env.IMPACT_ADVOCATE_PROGRAM_ID = originalEnv.IMPACT_ADVOCATE_PROGRAM_ID;
process.env.IMPACT_ADVOCATE_TENANT_ALIAS = originalEnv.IMPACT_ADVOCATE_TENANT_ALIAS;
process.env.IMPACT_ADVOCATE_WIDGET_ID = originalEnv.IMPACT_ADVOCATE_WIDGET_ID;
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID =
+ originalEnv.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID;
+ process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID = originalEnv.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID;
+ process.env.IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID =
+ originalEnv.IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID;
+ process.env.IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID =
+ originalEnv.IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID;
process.env.IMPACT_ACCOUNT_SID = originalEnv.IMPACT_ACCOUNT_SID;
jest.resetModules();
});
it('builds register participant payloads with exact cookie attribution', async () => {
- process.env.IMPACT_ADVOCATE_PROGRAM_ID = '51699';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID = '51699';
process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'kilo';
process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'secret';
process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'account-sid';
@@ -51,22 +62,153 @@ describe('impact advocate', () => {
});
it('normalizes bare widget IDs to the full Impact embed widget path', async () => {
- process.env.IMPACT_ADVOCATE_PROGRAM_ID = '51699';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID = '51699';
process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'tenant-alias';
process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'secret';
process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'impact-account-sid';
- process.env.IMPACT_ADVOCATE_WIDGET_ID = '51699';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID = '51699';
const { getImpactAdvocateWidgetId } = await import('@/lib/impact/advocate');
expect(getImpactAdvocateWidgetId()).toBe('p/51699/w/referrerWidget');
});
- it('logs debug data without tokens, credentials, authorization headers, cookie values, or email identities', async () => {
+ it('uses KiloClaw-scoped Advocate config for widget and token issuance', async () => {
+ process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'kiloclaw-account';
+ process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'kiloclaw-secret';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID = '51699-scoped';
+ process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'kiloclaw-tenant';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID = '51699-scoped';
+
+ const {
+ getImpactAdvocateProgramId,
+ getImpactAdvocateWidgetId,
+ issueImpactAdvocateVerifiedAccessToken,
+ } = await import('@/lib/impact/advocate');
+
+ expect(getImpactAdvocateProgramId()).toBe('51699-scoped');
+ expect(getImpactAdvocateWidgetId()).toBe('p/51699-scoped/w/referrerWidget');
+
+ const token = issueImpactAdvocateVerifiedAccessToken(
+ { id: 'user_123', google_user_email: 'referrer@example.com' },
+ new Date('2026-04-23T12:00:00.000Z')
+ );
+ const decoded = jwt.decode(token ?? '', { complete: true });
+ expect(decoded && typeof decoded === 'object' ? decoded.header.kid : null).toBe(
+ 'kiloclaw-account'
+ );
+ });
+
+ it('does not configure KiloClaw from legacy unscoped program/widget config', async () => {
+ process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'legacy-account';
+ process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'legacy-secret';
process.env.IMPACT_ADVOCATE_PROGRAM_ID = '51699';
+ process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'legacy-tenant';
+ process.env.IMPACT_ADVOCATE_WIDGET_ID = '51699';
+
+ const {
+ getImpactAdvocateProgramId,
+ getImpactAdvocateWidgetId,
+ isImpactAdvocateConfigured,
+ issueImpactAdvocateVerifiedAccessToken,
+ } = await import('@/lib/impact/advocate');
+
+ expect(isImpactAdvocateConfigured()).toBe(false);
+ expect(getImpactAdvocateProgramId()).toBeNull();
+ expect(getImpactAdvocateWidgetId()).toBeNull();
+ expect(
+ issueImpactAdvocateVerifiedAccessToken(
+ { id: 'user_123', google_user_email: 'referrer@example.com' },
+ new Date('2026-04-23T12:00:00.000Z')
+ )
+ ).toBeNull();
+ });
+
+ it('does not use KiloClaw config for Kilo Pass', async () => {
+ process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'fallback-account';
+ process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'fallback-secret';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID = '51699';
+ process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'fallback-tenant';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID = 'p/51699/w/referrerWidget';
+
+ const {
+ getImpactAdvocateWidgetId,
+ isImpactAdvocateConfigured,
+ issueImpactAdvocateVerifiedAccessToken,
+ } = await import('@/lib/impact/advocate');
+
+ const scope = { product: 'kilo_pass' as const };
+ expect(isImpactAdvocateConfigured(scope)).toBe(false);
+ expect(getImpactAdvocateWidgetId(scope)).toBeNull();
+ expect(
+ issueImpactAdvocateVerifiedAccessToken(
+ { id: 'user_123', google_user_email: 'referrer@example.com' },
+ new Date('2026-04-23T12:00:00.000Z'),
+ scope
+ )
+ ).toBeNull();
+ });
+
+ it('uses Kilo Pass-scoped program and widget config for token issuance', async () => {
+ process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'shared-account';
+ process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'shared-secret';
+ process.env.IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID = '52766';
+ process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'shared-tenant';
+ process.env.IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID = 'p/52766/w/referrerWidget';
+
+ const {
+ getImpactAdvocateProgramId,
+ getImpactAdvocateWidgetId,
+ issueImpactAdvocateVerifiedAccessToken,
+ } = await import('@/lib/impact/advocate');
+
+ const scope = { product: 'kilo_pass' as const };
+ expect(getImpactAdvocateProgramId(scope)).toBe('52766');
+ expect(getImpactAdvocateWidgetId(scope)).toBe('p/52766/w/referrerWidget');
+
+ const token = issueImpactAdvocateVerifiedAccessToken(
+ { id: 'user_123', google_user_email: 'referrer@example.com' },
+ new Date('2026-04-23T12:00:00.000Z'),
+ scope
+ );
+ const decoded = jwt.decode(token ?? '', { complete: true });
+ expect(decoded && typeof decoded === 'object' ? decoded.header.kid : null).toBe(
+ 'shared-account'
+ );
+ });
+
+ it('rejects Kilo Pass Advocate config that reuses KiloClaw program or widget IDs', async () => {
+ process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'shared-account';
+ process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'shared-secret';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID = '51699';
+ process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'shared-tenant';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID = 'p/51699/w/referrerWidget';
+
+ const scope = { product: 'kilo_pass' as const };
+
+ process.env.IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID = '51699';
+ process.env.IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID = 'p/52766/w/referrerWidget';
+ jest.resetModules();
+ let advocate = await import('@/lib/impact/advocate');
+ expect(advocate.isImpactAdvocateConfigured(scope)).toBe(false);
+ expect(advocate.getImpactAdvocateProgramId(scope)).toBeNull();
+ expect(advocate.getImpactAdvocateWidgetId(scope)).toBeNull();
+
+ process.env.IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID = '52766';
+ process.env.IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID = 'p/51699/w/referrerWidget';
+ jest.resetModules();
+ advocate = await import('@/lib/impact/advocate');
+ expect(advocate.isImpactAdvocateConfigured(scope)).toBe(false);
+ expect(advocate.getImpactAdvocateProgramId(scope)).toBeNull();
+ expect(advocate.getImpactAdvocateWidgetId(scope)).toBeNull();
+ });
+
+ it('logs debug data without tokens, credentials, authorization headers, cookie values, or email identities', async () => {
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID = '51699';
process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'tenant-alias';
process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'secret';
process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'impact-account-sid';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID = 'p/51699/w/referrerWidget';
process.env.IMPACT_ADVOCATE_DEBUG_LOGGING = 'true';
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined);
@@ -98,11 +240,11 @@ describe('impact advocate', () => {
});
it('issues verified access JWTs with the account sid in the kid header', async () => {
- process.env.IMPACT_ADVOCATE_PROGRAM_ID = '51699';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID = '51699';
process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'tenant-alias';
process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'secret';
process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'impact-account-sid';
- process.env.IMPACT_ADVOCATE_WIDGET_ID = 'p/51699/w/referrerWidget';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID = 'p/51699/w/referrerWidget';
const { getImpactAdvocateWidgetId, issueImpactAdvocateVerifiedAccessToken } =
await import('@/lib/impact/advocate');
@@ -133,10 +275,11 @@ describe('impact advocate', () => {
});
it('looks up account rewards with account and user filters', async () => {
- process.env.IMPACT_ADVOCATE_PROGRAM_ID = '51699';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID = '51699';
process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'tenant-alias';
process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'secret';
process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'impact-account-sid';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID = 'p/51699/w/referrerWidget';
process.env.IMPACT_ADVOCATE_DEBUG_LOGGING = 'true';
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined);
const fetchMock = jest.fn().mockResolvedValue(
@@ -177,10 +320,11 @@ describe('impact advocate', () => {
});
it('redeems a credit reward with amount and unit', async () => {
- process.env.IMPACT_ADVOCATE_PROGRAM_ID = '51699';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID = '51699';
process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'tenant-alias';
process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'secret';
process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'impact-account-sid';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID = 'p/51699/w/referrerWidget';
const fetchMock = jest
.fn()
.mockResolvedValue(new Response('{"ok":true}', { status: 200 }));
diff --git a/apps/web/src/lib/impact/advocate.ts b/apps/web/src/lib/impact/advocate.ts
index a033bc53a3..055a9e57b1 100644
--- a/apps/web/src/lib/impact/advocate.ts
+++ b/apps/web/src/lib/impact/advocate.ts
@@ -4,15 +4,17 @@ import jwt from 'jsonwebtoken';
import type { SignOptions } from 'jsonwebtoken';
import type { User } from '@kilocode/db/schema';
import {
- IMPACT_ACCOUNT_SID,
IMPACT_ADVOCATE_ACCOUNT_SID,
IMPACT_ADVOCATE_API_BASE_URL,
IMPACT_ADVOCATE_AUTH_TOKEN,
- IMPACT_ADVOCATE_PROGRAM_ID,
+ IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID,
+ IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID,
+ IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID,
+ IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID,
IMPACT_ADVOCATE_TENANT_ALIAS,
- IMPACT_ADVOCATE_WIDGET_ID,
} from '@/lib/config.server';
import { logImpactReferralDebug, truncateForLog } from '@/lib/impact/debug';
+import { ImpactAdvocateProgramKey, ImpactReferralProduct } from '@kilocode/db/schema-types';
/**
* SaaSquatch / Impact Advocate expects locale tags formatted as `en_US`,
@@ -25,11 +27,39 @@ function normalizeAdvocateLocale(locale: string | null | undefined): string | nu
return trimmed.replace(/-/g, '_');
}
-export const IMPACT_ADVOCATE_DEFAULT_PROGRAM_ID = '51699';
-export const IMPACT_ADVOCATE_DEFAULT_WIDGET_ID = 'p/51699/w/referrerWidget';
const IMPACT_ADVOCATE_WIDGET_NAME = 'referrerWidget';
const IMPACT_ADVOCATE_VERIFIED_ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
+type ImpactAdvocateConfigScope = {
+ product?: ImpactReferralProduct;
+ programKey?: ImpactAdvocateProgramKey;
+};
+
+type ImpactAdvocateConfig = {
+ accountSid: string;
+ authToken: string;
+ tenantAlias: string;
+ programId: string;
+ widgetId: string;
+ programKey: ImpactAdvocateProgramKey;
+};
+
+export function getImpactAdvocateProgramKeyForProduct(
+ product: ImpactReferralProduct
+): ImpactAdvocateProgramKey {
+ return product === ImpactReferralProduct.KiloPass
+ ? ImpactAdvocateProgramKey.KiloPass
+ : ImpactAdvocateProgramKey.KiloClaw;
+}
+
+function resolveImpactAdvocateProgramKey(
+ scope?: ImpactAdvocateConfigScope
+): ImpactAdvocateProgramKey {
+ if (scope?.programKey) return scope.programKey;
+ if (scope?.product) return getImpactAdvocateProgramKeyForProduct(scope.product);
+ return ImpactAdvocateProgramKey.KiloClaw;
+}
+
export type ImpactAdvocateIdentityPayload = {
id: string;
accountId: string;
@@ -179,6 +209,11 @@ function getDebuggableVerifiedAccessTokenPayload(
};
}
+function configuredValue(value: string | null | undefined): string {
+ const trimmed = value?.trim();
+ return trimmed && trimmed !== 'undefined' ? trimmed : '';
+}
+
function getImpactAdvocateWidgetPath(widgetId: string, programId: string): string {
const trimmedWidgetId = widgetId.trim();
if (!trimmedWidgetId) return `p/${programId}/w/${IMPACT_ADVOCATE_WIDGET_NAME}`;
@@ -186,14 +221,70 @@ function getImpactAdvocateWidgetPath(widgetId: string, programId: string): strin
return `p/${trimmedWidgetId}/w/${IMPACT_ADVOCATE_WIDGET_NAME}`;
}
-function getImpactAdvocateConfig() {
- const accountSid = IMPACT_ADVOCATE_ACCOUNT_SID || IMPACT_ACCOUNT_SID;
- const authToken = IMPACT_ADVOCATE_AUTH_TOKEN;
- const tenantAlias = IMPACT_ADVOCATE_TENANT_ALIAS;
- const programId = IMPACT_ADVOCATE_PROGRAM_ID || IMPACT_ADVOCATE_DEFAULT_PROGRAM_ID;
- const widgetId = getImpactAdvocateWidgetPath(IMPACT_ADVOCATE_WIDGET_ID, programId);
+function getKiloClawAdvocateIdentifiersForComparison(): {
+ programIds: Set;
+ widgetIds: Set;
+} {
+ const programId = configuredValue(IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID);
+ const rawWidgetId = configuredValue(IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID);
+ const programIds = new Set();
+ const widgetIds = new Set();
+
+ if (programId) {
+ programIds.add(programId);
+ }
+ if (programId && rawWidgetId) {
+ widgetIds.add(getImpactAdvocateWidgetPath(rawWidgetId, programId));
+ }
+
+ return { programIds, widgetIds };
+}
+
+function kiloPassAdvocateConfigReusesKiloClawIdentifiers(params: {
+ programId: string;
+ widgetId: string;
+}): boolean {
+ const kiloClawIdentifiers = getKiloClawAdvocateIdentifiersForComparison();
+ return (
+ kiloClawIdentifiers.programIds.has(params.programId) ||
+ kiloClawIdentifiers.widgetIds.has(params.widgetId)
+ );
+}
+
+function getImpactAdvocateConfig(scope?: ImpactAdvocateConfigScope): ImpactAdvocateConfig | null {
+ const programKey = resolveImpactAdvocateProgramKey(scope);
+
+ const scopedValues =
+ programKey === ImpactAdvocateProgramKey.KiloPass
+ ? {
+ programId: configuredValue(IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID),
+ widgetId: configuredValue(IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID),
+ }
+ : {
+ programId: configuredValue(IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID),
+ widgetId: configuredValue(IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID),
+ };
+
+ const accountSid = configuredValue(IMPACT_ADVOCATE_ACCOUNT_SID);
+ const authToken = configuredValue(IMPACT_ADVOCATE_AUTH_TOKEN);
+ const tenantAlias = configuredValue(IMPACT_ADVOCATE_TENANT_ALIAS);
+ const programId = scopedValues.programId;
+ const rawWidgetId = scopedValues.widgetId;
+
+ if (!accountSid || !authToken || !tenantAlias || !programId || !rawWidgetId) {
+ return null;
+ }
- if (!accountSid || !authToken || !tenantAlias) {
+ const widgetId = getImpactAdvocateWidgetPath(rawWidgetId, programId);
+
+ if (!widgetId) {
+ return null;
+ }
+
+ if (
+ programKey === ImpactAdvocateProgramKey.KiloPass &&
+ kiloPassAdvocateConfigReusesKiloClawIdentifiers({ programId, widgetId })
+ ) {
return null;
}
@@ -203,19 +294,22 @@ function getImpactAdvocateConfig() {
tenantAlias,
programId,
widgetId,
+ programKey,
};
}
-export function isImpactAdvocateConfigured(): boolean {
- return getImpactAdvocateConfig() !== null;
+export function isImpactAdvocateConfigured(scope?: ImpactAdvocateConfigScope): boolean {
+ return getImpactAdvocateConfig(scope) !== null;
}
-export function getImpactAdvocateWidgetId(): string {
- return getImpactAdvocateConfig()?.widgetId ?? IMPACT_ADVOCATE_DEFAULT_WIDGET_ID;
+export function getImpactAdvocateWidgetId(scope?: ImpactAdvocateConfigScope): string | null {
+ const programKey = resolveImpactAdvocateProgramKey(scope);
+ return getImpactAdvocateConfig({ programKey })?.widgetId ?? null;
}
-export function getImpactAdvocateProgramId(): string {
- return getImpactAdvocateConfig()?.programId ?? IMPACT_ADVOCATE_DEFAULT_PROGRAM_ID;
+export function getImpactAdvocateProgramId(scope?: ImpactAdvocateConfigScope): string | null {
+ const programKey = resolveImpactAdvocateProgramKey(scope);
+ return getImpactAdvocateConfig({ programKey })?.programId ?? null;
}
/**
@@ -282,9 +376,7 @@ export function buildImpactAdvocateRegisterParticipantPayload(params: {
return payload;
}
-function getImpactAdvocateAuthorizationHeader(
- config: NonNullable>
-): string {
+function getImpactAdvocateAuthorizationHeader(config: ImpactAdvocateConfig): string {
return `Basic ${Buffer.from(`${config.accountSid}:${config.authToken}`).toString('base64')}`;
}
@@ -346,7 +438,7 @@ export function extractImpactAdvocateRewards(responseBody: string | null | undef
* integration spec; we URL-encode them because the path segment contains '@'.
*/
function getImpactAdvocateRegisterParticipantUrl(
- config: NonNullable>,
+ config: ImpactAdvocateConfig,
payload: ImpactAdvocateRegisterParticipantPayload
): string {
const base = trimTrailingSlashes(IMPACT_ADVOCATE_API_BASE_URL);
@@ -356,16 +448,14 @@ function getImpactAdvocateRegisterParticipantUrl(
return `${base}/api/v1/${tenant}/open/account/${accountId}/user/${userId}`;
}
-function getDebuggableImpactAdvocateRegisterParticipantUrl(
- config: NonNullable>
-): string {
+function getDebuggableImpactAdvocateRegisterParticipantUrl(config: ImpactAdvocateConfig): string {
const base = trimTrailingSlashes(IMPACT_ADVOCATE_API_BASE_URL);
const tenant = encodeURIComponent(config.tenantAlias);
return `${base}/api/v1/${tenant}/open/account/[redacted-account-id]/user/[redacted-user-id]`;
}
function getImpactAdvocateRewardsUrl(
- config: NonNullable>,
+ config: ImpactAdvocateConfig,
payload: ImpactAdvocateRewardLookupPayload
): string {
const base = trimTrailingSlashes(IMPACT_ADVOCATE_API_BASE_URL);
@@ -378,7 +468,7 @@ function getImpactAdvocateRewardsUrl(
}
function getDebuggableImpactAdvocateRewardsUrl(
- config: NonNullable>,
+ config: ImpactAdvocateConfig,
payload: ImpactAdvocateRewardLookupPayload
): string {
const base = trimTrailingSlashes(IMPACT_ADVOCATE_API_BASE_URL);
@@ -390,19 +480,17 @@ function getDebuggableImpactAdvocateRewardsUrl(
return url.toString();
}
-function getImpactAdvocateRedeemRewardUrl(
- config: NonNullable>,
- rewardId: string
-): string {
+function getImpactAdvocateRedeemRewardUrl(config: ImpactAdvocateConfig, rewardId: string): string {
const base = trimTrailingSlashes(IMPACT_ADVOCATE_API_BASE_URL);
const tenant = encodeURIComponent(config.tenantAlias);
return `${base}/api/v1/${tenant}/credit/${encodeURIComponent(rewardId)}/redeem`;
}
export async function sendImpactAdvocateRegisterParticipantPayload(
- payload: ImpactAdvocateRegisterParticipantPayload
+ payload: ImpactAdvocateRegisterParticipantPayload,
+ scope?: ImpactAdvocateConfigScope
): Promise {
- const config = getImpactAdvocateConfig();
+ const config = getImpactAdvocateConfig(scope);
if (!config) {
return {
ok: false,
@@ -474,9 +562,10 @@ export async function sendImpactAdvocateRegisterParticipantPayload(
}
export async function sendImpactAdvocateRewardLookupPayload(
- payload: ImpactAdvocateRewardLookupPayload
+ payload: ImpactAdvocateRewardLookupPayload,
+ scope?: ImpactAdvocateConfigScope
): Promise {
- const config = getImpactAdvocateConfig();
+ const config = getImpactAdvocateConfig(scope);
if (!config) {
return {
ok: false,
@@ -539,9 +628,10 @@ export async function sendImpactAdvocateRewardLookupPayload(
}
export async function sendImpactAdvocateRewardRedemptionPayload(
- payload: ImpactAdvocateRewardRedemptionPayload
+ payload: ImpactAdvocateRewardRedemptionPayload,
+ scope?: ImpactAdvocateConfigScope
): Promise {
- const config = getImpactAdvocateConfig();
+ const config = getImpactAdvocateConfig(scope);
if (!config) {
return {
ok: false,
@@ -610,9 +700,10 @@ export async function sendImpactAdvocateRewardRedemptionPayload(
export function issueImpactAdvocateVerifiedAccessToken(
user: Pick,
- now: Date = new Date()
+ now: Date = new Date(),
+ scope?: ImpactAdvocateConfigScope
): string | null {
- const config = getImpactAdvocateConfig();
+ const config = getImpactAdvocateConfig(scope);
if (!config) return null;
const header: ImpactAdvocateJwtHeaderInput = {
diff --git a/apps/web/src/lib/impact/kilo-pass-referrals.test.ts b/apps/web/src/lib/impact/kilo-pass-referrals.test.ts
new file mode 100644
index 0000000000..bc7101886f
--- /dev/null
+++ b/apps/web/src/lib/impact/kilo-pass-referrals.test.ts
@@ -0,0 +1,1057 @@
+import { randomUUID } from 'crypto';
+import { eq } from 'drizzle-orm';
+
+jest.mock('@/lib/impact', () => {
+ const actual = jest.requireActual('@/lib/impact');
+ return {
+ ...actual,
+ isImpactConfigured: jest.fn(() => true),
+ sendImpactConversionPayload: jest.fn(async () => ({ ok: true, delivery: 'accepted' })),
+ };
+});
+
+jest.mock('@/lib/impact/advocate', () => {
+ const actual = jest.requireActual('@/lib/impact/advocate');
+ return {
+ ...actual,
+ isImpactAdvocateConfigured: jest.fn(() => true),
+ sendImpactAdvocateRewardLookupPayload: jest.fn(async () => ({
+ ok: true,
+ statusCode: 200,
+ rewards: [
+ {
+ id: 'impact-kilo-pass-reward',
+ type: 'CREDIT',
+ amount: 24.5,
+ unit: 'Kilo Pass Bonus Credits',
+ },
+ ],
+ responseBody:
+ '{"rewards":[{"id":"impact-kilo-pass-reward","type":"CREDIT","amount":24.5,"unit":"Kilo Pass Bonus Credits"}]}',
+ })),
+ sendImpactAdvocateRewardRedemptionPayload: jest.fn(async () => ({
+ ok: true,
+ statusCode: 200,
+ responseBody: '{}',
+ })),
+ };
+});
+
+jest.mock('@/lib/stripe-client', () => ({
+ client: {
+ subscriptions: {
+ update: jest.fn(async () => ({})),
+ },
+ },
+}));
+
+import { cleanupDbForTest, db } from '@/lib/drizzle';
+import type { isImpactConfigured, sendImpactConversionPayload } from '@/lib/impact';
+import type {
+ isImpactAdvocateConfigured,
+ sendImpactAdvocateRewardLookupPayload,
+ sendImpactAdvocateRewardRedemptionPayload,
+} from '@/lib/impact/advocate';
+import {
+ expirePendingKiloPassReferralRewards,
+ markPersonalKiloPassReferralPaymentAdverse,
+ processPersonalKiloPassStripePaidConversion,
+} from '@/lib/impact/kilo-pass-referrals';
+import { dispatchQueuedImpactAdvocateRewardRedemptions } from '@/lib/impact/kiloclaw-referrals';
+import { insertTestUser } from '@/tests/helpers/user.helper';
+import {
+ deleted_user_email_tombstones,
+ impact_advocate_participants,
+ impact_advocate_reward_redemptions,
+ impact_attribution_touches,
+ impact_conversion_reports,
+ impact_referral_conversions,
+ impact_referral_reward_decisions,
+ impact_referral_rewards,
+ kilo_pass_issuances,
+ kilo_pass_subscriptions,
+ user_affiliate_attributions,
+} from '@kilocode/db/schema';
+import {
+ ImpactAdvocateProgramKey,
+ ImpactAttributionTouchProvider,
+ ImpactAttributionTouchType,
+ ImpactConversionReportState,
+ ImpactReferralBeneficiaryRole,
+ ImpactReferralDecisionOutcome,
+ ImpactReferralPaymentProvider,
+ ImpactReferralProduct,
+ ImpactReferralRewardKind,
+ ImpactReferralRewardStatus,
+ ImpactReferralWinningTouchType,
+ KiloPassCadence,
+ KiloPassIssuanceSource,
+ KiloPassPaymentProvider,
+ KiloPassTier,
+ KiloPassWelcomePromoEligibilityReason,
+} from '@kilocode/db/schema-types';
+
+const impactMock = jest.requireMock('@/lib/impact') as {
+ isImpactConfigured: jest.MockedFunction;
+ sendImpactConversionPayload: jest.MockedFunction;
+};
+const advocateMock = jest.requireMock('@/lib/impact/advocate') as {
+ isImpactAdvocateConfigured: jest.MockedFunction;
+ sendImpactAdvocateRewardLookupPayload: jest.MockedFunction<
+ typeof sendImpactAdvocateRewardLookupPayload
+ >;
+ sendImpactAdvocateRewardRedemptionPayload: jest.MockedFunction<
+ typeof sendImpactAdvocateRewardRedemptionPayload
+ >;
+};
+const mockIsImpactConfigured = impactMock.isImpactConfigured;
+const mockIsImpactAdvocateConfigured = advocateMock.isImpactAdvocateConfigured;
+const mockSendImpactAdvocateRewardLookupPayload =
+ advocateMock.sendImpactAdvocateRewardLookupPayload;
+const mockSendImpactAdvocateRewardRedemptionPayload =
+ advocateMock.sendImpactAdvocateRewardRedemptionPayload;
+const mockSendImpactConversionPayload = impactMock.sendImpactConversionPayload;
+
+beforeEach(async () => {
+ await cleanupDbForTest();
+ jest.clearAllMocks();
+ mockIsImpactConfigured.mockReturnValue(true);
+ mockIsImpactAdvocateConfigured.mockReturnValue(true);
+ mockSendImpactConversionPayload.mockResolvedValue({ ok: true, delivery: 'accepted' });
+ mockSendImpactAdvocateRewardLookupPayload.mockResolvedValue({
+ ok: true,
+ statusCode: 200,
+ rewards: [
+ {
+ id: 'impact-kilo-pass-reward',
+ type: 'CREDIT',
+ amount: 24.5,
+ unit: 'Kilo Pass Bonus Credits',
+ },
+ ],
+ responseBody:
+ '{"rewards":[{"id":"impact-kilo-pass-reward","type":"CREDIT","amount":24.5,"unit":"Kilo Pass Bonus Credits"}]}',
+ });
+ mockSendImpactAdvocateRewardRedemptionPayload.mockResolvedValue({
+ ok: true,
+ statusCode: 200,
+ responseBody: '{}',
+ });
+});
+
+async function insertKiloPassSubscription(params: {
+ userId: string;
+ tier?: KiloPassTier;
+ cadence?: KiloPassCadence;
+ stripeSubscriptionId?: string;
+}) {
+ const stripeSubscriptionId = params.stripeSubscriptionId ?? `sub_${randomUUID()}`;
+ const [subscription] = await db
+ .insert(kilo_pass_subscriptions)
+ .values({
+ kilo_user_id: params.userId,
+ payment_provider: KiloPassPaymentProvider.Stripe,
+ provider_subscription_id: stripeSubscriptionId,
+ stripe_subscription_id: stripeSubscriptionId,
+ tier: params.tier ?? KiloPassTier.Tier49,
+ cadence: params.cadence ?? KiloPassCadence.Monthly,
+ status: 'active',
+ started_at: '2026-01-02T00:00:00.000Z',
+ })
+ .returning({ id: kilo_pass_subscriptions.id });
+ if (!subscription) throw new Error('Failed to insert Kilo Pass subscription');
+ return subscription.id;
+}
+
+async function seedCurrentIssuance(subscriptionId: string, invoiceId: string): Promise {
+ await db.insert(kilo_pass_issuances).values({
+ kilo_pass_subscription_id: subscriptionId,
+ issue_month: '2026-01-01',
+ source: KiloPassIssuanceSource.StripeInvoice,
+ stripe_invoice_id: invoiceId,
+ });
+}
+
+async function insertParticipant(userId: string, referralCode = `code_${randomUUID()}`) {
+ await db.insert(impact_advocate_participants).values({
+ program_key: ImpactAdvocateProgramKey.KiloPass,
+ user_id: userId,
+ advocate_id: `${userId}@example.com`,
+ advocate_account_id: `${userId}@example.com`,
+ contact_email: `${userId}@example.com`,
+ opaque_referral_identifier: referralCode,
+ registration_state: 'registered',
+ registered_at: '2026-01-01T00:00:00.000Z',
+ });
+ return referralCode;
+}
+
+async function insertTouch(params: {
+ userId: string;
+ type: 'referral' | 'affiliate';
+ referralCode?: string;
+ touchedAt?: string;
+ saleAttributedAt?: string | null;
+}) {
+ const touchedAt = params.touchedAt ?? '2026-01-01T00:00:00.000Z';
+ const [touch] = await db
+ .insert(impact_attribution_touches)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ program_key: params.type === 'referral' ? ImpactAdvocateProgramKey.KiloPass : null,
+ dedupe_key: randomUUID(),
+ user_id: params.userId,
+ touch_type:
+ params.type === 'referral'
+ ? ImpactAttributionTouchType.Referral
+ : ImpactAttributionTouchType.Affiliate,
+ provider:
+ params.type === 'referral'
+ ? ImpactAttributionTouchProvider.ImpactAdvocate
+ : ImpactAttributionTouchProvider.ImpactPerformance,
+ opaque_tracking_value:
+ params.type === 'referral' ? 'opaque-referral-cookie' : 'impact-click-id',
+ tracking_value_length: 20,
+ is_tracking_value_accepted: true,
+ rs_code: params.type === 'referral' ? params.referralCode : null,
+ im_ref: params.type === 'affiliate' ? 'impact-click-id' : null,
+ touched_at: touchedAt,
+ expires_at: '2026-01-31T00:00:00.000Z',
+ sale_attributed_at: params.saleAttributedAt ?? null,
+ })
+ .returning({ id: impact_attribution_touches.id });
+ if (!touch) throw new Error('Failed to insert touch');
+ return touch.id;
+}
+
+async function processInvoice(params: {
+ refereeId: string;
+ subscriptionId: string;
+ invoiceId?: string;
+ tier?: KiloPassTier;
+ cadence?: KiloPassCadence;
+ amount?: number;
+ welcomePromoEligibilityReason?: KiloPassWelcomePromoEligibilityReason;
+}) {
+ const invoiceId = params.invoiceId ?? `inv_${randomUUID()}`;
+ await seedCurrentIssuance(params.subscriptionId, invoiceId);
+ return await processPersonalKiloPassStripePaidConversion({
+ userId: params.refereeId,
+ kiloPassSubscriptionId: params.subscriptionId,
+ sourcePaymentId: invoiceId,
+ orderId: invoiceId,
+ amount: params.amount ?? 49,
+ currencyCode: 'usd',
+ itemCategory: 'kilo-pass-tier-49-monthly',
+ itemName: 'Kilo Pass Tier 49 Monthly',
+ itemSku: 'price_kilo_pass_49_monthly',
+ sourceTier: params.tier ?? KiloPassTier.Tier49,
+ cadence: params.cadence ?? KiloPassCadence.Monthly,
+ welcomePromoEligibilityReason: params.welcomePromoEligibilityReason,
+ convertedAt: new Date('2026-01-03T00:00:00.000Z'),
+ });
+}
+
+async function seedKiloPassReferralRewardsForAdversePayment(params: {
+ invoiceId?: string;
+ statuses: Array<(typeof ImpactReferralRewardStatus)[keyof typeof ImpactReferralRewardStatus]>;
+}) {
+ const invoiceId = params.invoiceId ?? `inv_adverse_${randomUUID()}`;
+ const referrer = await insertTestUser({ created_at: '2025-12-01T00:00:00.000Z' });
+ const referee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ const [conversion] = await db
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: referee.id,
+ referrer_user_id: referrer.id,
+ winning_touch_type: ImpactReferralWinningTouchType.Referral,
+ payment_provider: ImpactReferralPaymentProvider.Stripe,
+ source_payment_id: invoiceId,
+ qualified: true,
+ converted_at: '2026-01-03T00:00:00.000Z',
+ })
+ .returning({ id: impact_referral_conversions.id });
+ if (!conversion) throw new Error('Failed to insert conversion');
+
+ const roles = [ImpactReferralBeneficiaryRole.Referee, ImpactReferralBeneficiaryRole.Referrer];
+ const beneficiaries = [referee.id, referrer.id];
+ const rewards = [];
+ for (const [index, status] of params.statuses.entries()) {
+ const [decision] = await db
+ .insert(impact_referral_reward_decisions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ beneficiary_user_id: beneficiaries[index] ?? referee.id,
+ beneficiary_role: roles[index] ?? ImpactReferralBeneficiaryRole.Referee,
+ outcome: ImpactReferralDecisionOutcome.Granted,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: 0.5,
+ source_tier: KiloPassTier.Tier49,
+ reward_amount_usd: 24.5,
+ })
+ .returning({ id: impact_referral_reward_decisions.id });
+ if (!decision) throw new Error('Failed to insert decision');
+
+ const [reward] = await db
+ .insert(impact_referral_rewards)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ decision_id: decision.id,
+ beneficiary_user_id: beneficiaries[index] ?? referee.id,
+ beneficiary_role: roles[index] ?? ImpactReferralBeneficiaryRole.Referee,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: 0.5,
+ source_tier: KiloPassTier.Tier49,
+ reward_amount_usd: 24.5,
+ status,
+ earned_at: '2026-01-03T00:00:00.000Z',
+ applied_at:
+ status === ImpactReferralRewardStatus.Applied ? '2026-02-01T00:00:00.000Z' : null,
+ expires_at: '2027-01-03T00:00:00.000Z',
+ })
+ .returning({ id: impact_referral_rewards.id });
+ if (!reward) throw new Error('Failed to insert reward');
+ rewards.push(reward);
+ }
+
+ return { invoiceId, conversionId: conversion.id, rewardIds: rewards.map(reward => reward.id) };
+}
+
+describe('Kilo Pass Impact referral conversions', () => {
+ test('referral winner grants double-sided pending rewards, queues Impact SALE and reward redemptions, and suppresses affiliate SALE', async () => {
+ const referrer = await insertTestUser({
+ google_user_email: 'referrer@example.com',
+ normalized_email: 'referrer@example.com',
+ created_at: '2025-12-01T00:00:00.000Z',
+ });
+ const referee = await insertTestUser({
+ google_user_email: 'referee@example.com',
+ created_at: '2026-01-02T00:00:00.000Z',
+ normalized_email: 'referee@example.com',
+ });
+ const referralCode = await insertParticipant(referrer.id);
+ await insertTouch({
+ userId: referee.id,
+ type: 'affiliate',
+ touchedAt: '2026-01-01T00:00:00.000Z',
+ });
+ await insertTouch({
+ userId: referee.id,
+ type: 'referral',
+ referralCode,
+ touchedAt: '2026-01-01T01:00:00.000Z',
+ });
+ const subscriptionId = await insertKiloPassSubscription({ userId: referee.id });
+
+ const disposition = await processInvoice({ refereeId: referee.id, subscriptionId });
+
+ expect(disposition).toEqual(
+ expect.objectContaining({
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.Referral,
+ disqualificationReason: null,
+ })
+ );
+
+ const rewards = await db
+ .select()
+ .from(impact_referral_rewards)
+ .where(eq(impact_referral_rewards.conversion_id, disposition.conversionId ?? ''));
+ expect(rewards).toHaveLength(2);
+ expect(rewards).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ product: ImpactReferralProduct.KiloPass,
+ beneficiary_user_id: referee.id,
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referee,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ reward_amount_usd: 24.5,
+ status: ImpactReferralRewardStatus.Pending,
+ }),
+ expect.objectContaining({
+ product: ImpactReferralProduct.KiloPass,
+ beneficiary_user_id: referrer.id,
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referrer,
+ reward_amount_usd: 24.5,
+ status: ImpactReferralRewardStatus.Pending,
+ }),
+ ])
+ );
+
+ const redemptions = await db.select().from(impact_advocate_reward_redemptions);
+ expect(redemptions).toHaveLength(2);
+ expect(redemptions).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ beneficiary_user_id: referee.id,
+ state: 'queued',
+ request_payload: expect.objectContaining({
+ programKey: ImpactAdvocateProgramKey.KiloPass,
+ lookup: expect.objectContaining({
+ accountId: 'referee@example.com',
+ userId: 'referee@example.com',
+ rewardTypeFilter: 'CREDIT',
+ }),
+ redemption: { amount: 24.5, unit: 'Kilo Pass Bonus Credits' },
+ }),
+ }),
+ expect.objectContaining({
+ beneficiary_user_id: referrer.id,
+ state: 'queued',
+ request_payload: expect.objectContaining({
+ programKey: ImpactAdvocateProgramKey.KiloPass,
+ lookup: expect.objectContaining({
+ accountId: 'referrer@example.com',
+ userId: 'referrer@example.com',
+ rewardTypeFilter: 'CREDIT',
+ }),
+ redemption: { amount: 24.5, unit: 'Kilo Pass Bonus Credits' },
+ }),
+ }),
+ ])
+ );
+
+ const redemptionSummary = await dispatchQueuedImpactAdvocateRewardRedemptions();
+ expect(redemptionSummary).toEqual({ claimed: 2, redeemed: 2, retried: 0, failed: 0 });
+ expect(mockSendImpactAdvocateRewardLookupPayload).toHaveBeenCalledWith(
+ expect.objectContaining({ accountId: 'referee@example.com' }),
+ { programKey: ImpactAdvocateProgramKey.KiloPass }
+ );
+ expect(mockSendImpactAdvocateRewardLookupPayload).toHaveBeenCalledWith(
+ expect.objectContaining({ accountId: 'referrer@example.com' }),
+ { programKey: ImpactAdvocateProgramKey.KiloPass }
+ );
+ expect(mockSendImpactAdvocateRewardRedemptionPayload).toHaveBeenCalledWith(
+ { rewardId: 'impact-kilo-pass-reward', amount: 24.5, unit: 'Kilo Pass Bonus Credits' },
+ { programKey: ImpactAdvocateProgramKey.KiloPass }
+ );
+
+ const report = await db.query.impact_conversion_reports.findFirst({
+ where: eq(impact_conversion_reports.conversion_id, disposition.conversionId ?? ''),
+ });
+ expect(report).toEqual(
+ expect.objectContaining({
+ action_tracker_id: 71659,
+ order_id: expect.stringMatching(/^inv_/),
+ state: ImpactConversionReportState.Delivered,
+ request_payload: expect.objectContaining({
+ ItemCategory1: 'kilo-pass-tier-49-monthly',
+ ItemSubTotal1: '49.00',
+ }),
+ })
+ );
+ });
+
+ test('affiliate sale-attributed before referral wins and is marked sale-attributed for Kilo Pass', async () => {
+ const referrer = await insertTestUser();
+ const referee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ const referralCode = await insertParticipant(referrer.id);
+ const affiliateTouchId = await insertTouch({
+ userId: referee.id,
+ type: 'affiliate',
+ touchedAt: '2026-01-01T00:00:00.000Z',
+ saleAttributedAt: '2026-01-01T00:30:00.000Z',
+ });
+ await insertTouch({
+ userId: referee.id,
+ type: 'referral',
+ referralCode,
+ touchedAt: '2026-01-01T01:00:00.000Z',
+ });
+ const subscriptionId = await insertKiloPassSubscription({ userId: referee.id });
+
+ const disposition = await processInvoice({ refereeId: referee.id, subscriptionId });
+
+ expect(disposition).toEqual(
+ expect.objectContaining({
+ shouldEnqueueAffiliateSale: true,
+ winningTouchType: ImpactReferralWinningTouchType.Affiliate,
+ disqualificationReason: 'referral_affiliate_won',
+ })
+ );
+ const touch = await db.query.impact_attribution_touches.findFirst({
+ where: eq(impact_attribution_touches.id, affiliateTouchId),
+ });
+ expect(new Date(touch?.sale_attributed_at ?? '').toISOString()).toBe(
+ '2026-01-01T00:30:00.000Z'
+ );
+ });
+
+ test('only affiliate attribution preserves affiliate SALE and creates no referral rewards', async () => {
+ const referee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ const affiliateTouchId = await insertTouch({ userId: referee.id, type: 'affiliate' });
+ const subscriptionId = await insertKiloPassSubscription({ userId: referee.id });
+
+ const disposition = await processInvoice({ refereeId: referee.id, subscriptionId });
+
+ expect(disposition).toEqual(
+ expect.objectContaining({
+ shouldEnqueueAffiliateSale: true,
+ winningTouchType: ImpactReferralWinningTouchType.Affiliate,
+ disqualificationReason: 'referral_affiliate_won',
+ })
+ );
+ expect(await db.select().from(impact_referral_rewards)).toHaveLength(0);
+ const touch = await db.query.impact_attribution_touches.findFirst({
+ where: eq(impact_attribution_touches.id, affiliateTouchId),
+ });
+ expect(new Date(touch?.sale_attributed_at ?? '').toISOString()).toBe(
+ '2026-01-03T00:00:00.000Z'
+ );
+ });
+
+ test('historical affiliate attribution without product-scoped touch preserves affiliate SALE', async () => {
+ const referee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ await db.insert(user_affiliate_attributions).values({
+ user_id: referee.id,
+ provider: 'impact',
+ tracking_id: '',
+ });
+ const subscriptionId = await insertKiloPassSubscription({ userId: referee.id });
+
+ const disposition = await processInvoice({ refereeId: referee.id, subscriptionId });
+
+ expect(disposition).toEqual(
+ expect.objectContaining({
+ shouldEnqueueAffiliateSale: true,
+ winningTouchType: ImpactReferralWinningTouchType.Affiliate,
+ disqualificationReason: 'referral_affiliate_won',
+ })
+ );
+ expect(await db.select().from(impact_referral_rewards)).toHaveLength(0);
+ });
+
+ test('missing attribution and expired product-scoped touches suppress affiliate SALE reporting', async () => {
+ const noTouchReferee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ const noTouchSubscriptionId = await insertKiloPassSubscription({
+ userId: noTouchReferee.id,
+ });
+
+ const noTouchDisposition = await processInvoice({
+ refereeId: noTouchReferee.id,
+ subscriptionId: noTouchSubscriptionId,
+ });
+
+ expect(noTouchDisposition).toEqual(
+ expect.objectContaining({
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.None,
+ disqualificationReason: 'referral_no_valid_attribution',
+ })
+ );
+
+ await cleanupDbForTest();
+ const expiredAffiliateReferee = await insertTestUser({
+ created_at: '2026-01-02T00:00:00.000Z',
+ });
+ await db.insert(user_affiliate_attributions).values({
+ user_id: expiredAffiliateReferee.id,
+ provider: 'impact',
+ tracking_id: 'historical-affiliate-click',
+ });
+ await insertTouch({
+ userId: expiredAffiliateReferee.id,
+ type: 'affiliate',
+ touchedAt: '2025-12-01T00:00:00.000Z',
+ });
+ const expiredAffiliateSubscriptionId = await insertKiloPassSubscription({
+ userId: expiredAffiliateReferee.id,
+ });
+
+ const expiredAffiliateDisposition = await processInvoice({
+ refereeId: expiredAffiliateReferee.id,
+ subscriptionId: expiredAffiliateSubscriptionId,
+ });
+
+ expect(expiredAffiliateDisposition).toEqual(
+ expect.objectContaining({
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.None,
+ disqualificationReason: 'referral_no_valid_attribution',
+ })
+ );
+
+ await cleanupDbForTest();
+ const expiredReferralReferee = await insertTestUser({
+ created_at: '2026-01-02T00:00:00.000Z',
+ });
+ await db.insert(user_affiliate_attributions).values({
+ user_id: expiredReferralReferee.id,
+ provider: 'impact',
+ tracking_id: 'historical-affiliate-click',
+ });
+ await insertTouch({
+ userId: expiredReferralReferee.id,
+ type: 'referral',
+ touchedAt: '2025-12-01T00:00:00.000Z',
+ });
+ const expiredReferralSubscriptionId = await insertKiloPassSubscription({
+ userId: expiredReferralReferee.id,
+ });
+
+ const expiredReferralDisposition = await processInvoice({
+ refereeId: expiredReferralReferee.id,
+ subscriptionId: expiredReferralSubscriptionId,
+ });
+
+ expect(expiredReferralDisposition).toEqual(
+ expect.objectContaining({
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.None,
+ disqualificationReason: 'referral_no_valid_attribution',
+ })
+ );
+ });
+
+ test('only referral attribution grants double-sided pending rewards', async () => {
+ const referrer = await insertTestUser({ created_at: '2025-12-01T00:00:00.000Z' });
+ const referee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ const referralCode = await insertParticipant(referrer.id);
+ await insertTouch({ userId: referee.id, type: 'referral', referralCode });
+ const subscriptionId = await insertKiloPassSubscription({ userId: referee.id });
+
+ const disposition = await processInvoice({ refereeId: referee.id, subscriptionId });
+
+ expect(disposition).toEqual(
+ expect.objectContaining({
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.Referral,
+ disqualificationReason: null,
+ })
+ );
+ const rewards = await db
+ .select()
+ .from(impact_referral_rewards)
+ .where(eq(impact_referral_rewards.conversion_id, disposition.conversionId ?? ''));
+ expect(rewards).toHaveLength(2);
+ expect(rewards).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ beneficiary_user_id: referee.id }),
+ expect.objectContaining({ beneficiary_user_id: referrer.id }),
+ ])
+ );
+ });
+
+ test('reused payment fingerprint does not grant referral rewards', async () => {
+ const referrer = await insertTestUser({ created_at: '2025-12-01T00:00:00.000Z' });
+ const referee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ const referralCode = await insertParticipant(referrer.id);
+ await insertTouch({ userId: referee.id, type: 'referral', referralCode });
+ const subscriptionId = await insertKiloPassSubscription({ userId: referee.id });
+
+ const disposition = await processInvoice({
+ refereeId: referee.id,
+ subscriptionId,
+ welcomePromoEligibilityReason:
+ KiloPassWelcomePromoEligibilityReason.FingerprintPreviouslyClaimed,
+ });
+
+ expect(disposition).toEqual(
+ expect.objectContaining({
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.Referral,
+ disqualificationReason: 'referral_payment_fingerprint_previously_claimed',
+ })
+ );
+ expect(await db.select().from(impact_referral_rewards)).toHaveLength(0);
+ });
+
+ test('renewal, prior subscription, deleted tombstone, and self-referral do not grant rewards', async () => {
+ const cases = ['renewal', 'prior_subscription', 'deleted_tombstone', 'self_referral'] as const;
+
+ for (const scenario of cases) {
+ await cleanupDbForTest();
+ const referrer = await insertTestUser({ created_at: '2025-12-01T00:00:00.000Z' });
+ const referee =
+ scenario === 'self_referral'
+ ? referrer
+ : await insertTestUser({
+ created_at: '2026-01-02T00:00:00.000Z',
+ normalized_email: `${scenario}@example.com`,
+ });
+ const referralCode = await insertParticipant(referrer.id);
+ await insertTouch({ userId: referee.id, type: 'referral', referralCode });
+ const subscriptionId = await insertKiloPassSubscription({ userId: referee.id });
+ if (scenario === 'renewal') {
+ await db.insert(kilo_pass_issuances).values({
+ kilo_pass_subscription_id: subscriptionId,
+ issue_month: '2025-12-01',
+ source: KiloPassIssuanceSource.StripeInvoice,
+ stripe_invoice_id: `inv_prior_${randomUUID()}`,
+ });
+ }
+ if (scenario === 'prior_subscription') {
+ await insertKiloPassSubscription({ userId: referee.id });
+ }
+ if (scenario === 'deleted_tombstone') {
+ await db.insert(deleted_user_email_tombstones).values({
+ normalized_email_hash: '3c19ee1212333d8548ac77b54240971338dd8e4c3d5b6723b1c219666e74eac3',
+ });
+ }
+
+ const disposition = await processInvoice({ refereeId: referee.id, subscriptionId });
+ const rewards = await db.select().from(impact_referral_rewards);
+ expect({ scenario, rewardCount: rewards.length }).toEqual({ scenario, rewardCount: 0 });
+ expect(disposition.winningTouchType).toBe(ImpactReferralWinningTouchType.Referral);
+ expect(disposition.disqualificationReason).toMatch(/^referral_/);
+ }
+ });
+
+ test('referrer cap limits only referrer reward and invoice retry is idempotent', async () => {
+ const referrer = await insertTestUser({ created_at: '2025-12-01T00:00:00.000Z' });
+ const referee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ const referralCode = await insertParticipant(referrer.id);
+ for (let i = 0; i < 5; i++) {
+ const [conversion] = await db
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: referee.id,
+ referrer_user_id: referrer.id,
+ winning_touch_type: ImpactReferralWinningTouchType.Referral,
+ payment_provider: ImpactReferralPaymentProvider.Stripe,
+ source_payment_id: `seed_inv_${i}`,
+ qualified: true,
+ converted_at: '2025-12-15T00:00:00.000Z',
+ })
+ .returning({ id: impact_referral_conversions.id });
+ if (!conversion) throw new Error('seed conversion missing');
+ await db.insert(impact_referral_reward_decisions).values({
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ beneficiary_user_id: referrer.id,
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referrer,
+ outcome: ImpactReferralDecisionOutcome.Granted,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ });
+ }
+ await insertTouch({ userId: referee.id, type: 'referral', referralCode });
+ const subscriptionId = await insertKiloPassSubscription({ userId: referee.id });
+ const invoiceId = `inv_${randomUUID()}`;
+
+ const first = await processInvoice({ refereeId: referee.id, subscriptionId, invoiceId });
+ const second = await processPersonalKiloPassStripePaidConversion({
+ userId: referee.id,
+ kiloPassSubscriptionId: subscriptionId,
+ sourcePaymentId: invoiceId,
+ orderId: invoiceId,
+ amount: 49,
+ currencyCode: 'usd',
+ itemCategory: 'kilo-pass-tier-49-monthly',
+ itemName: 'Kilo Pass Tier 49 Monthly',
+ sourceTier: KiloPassTier.Tier49,
+ cadence: KiloPassCadence.Monthly,
+ convertedAt: new Date('2026-01-03T00:00:00.000Z'),
+ });
+
+ expect(second.conversionId).toBe(first.conversionId);
+ const decisions = await db
+ .select()
+ .from(impact_referral_reward_decisions)
+ .where(eq(impact_referral_reward_decisions.conversion_id, first.conversionId ?? ''));
+ expect(decisions).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referee,
+ outcome: ImpactReferralDecisionOutcome.Granted,
+ }),
+ expect.objectContaining({
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referrer,
+ outcome: ImpactReferralDecisionOutcome.CapLimited,
+ }),
+ ])
+ );
+ const rewards = await db
+ .select()
+ .from(impact_referral_rewards)
+ .where(eq(impact_referral_rewards.conversion_id, first.conversionId ?? ''));
+ expect(rewards).toHaveLength(1);
+ expect(rewards[0]?.beneficiary_user_id).toBe(referee.id);
+ expect(mockSendImpactConversionPayload).toHaveBeenCalledTimes(1);
+ });
+
+ test('missing configuration fails closed and Impact network failures leave retryable reports', async () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
+ const referrer = await insertTestUser({ created_at: '2025-12-01T00:00:00.000Z' });
+ const missingConfigReferee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ const referralCode = await insertParticipant(referrer.id);
+ await insertTouch({ userId: missingConfigReferee.id, type: 'referral', referralCode });
+ const missingConfigSubscriptionId = await insertKiloPassSubscription({
+ userId: missingConfigReferee.id,
+ });
+ mockIsImpactAdvocateConfigured.mockReturnValue(false);
+
+ const missingConfigDisposition = await processInvoice({
+ refereeId: missingConfigReferee.id,
+ subscriptionId: missingConfigSubscriptionId,
+ });
+
+ expect(missingConfigDisposition.disqualificationReason).toBe('referral_missing_configuration');
+ expect(await db.select().from(impact_referral_rewards)).toHaveLength(0);
+ const failedReport = await db.query.impact_conversion_reports.findFirst({
+ where: eq(
+ impact_conversion_reports.conversion_id,
+ missingConfigDisposition.conversionId ?? ''
+ ),
+ });
+ expect(failedReport?.state).toBe(ImpactConversionReportState.Failed);
+ consoleErrorSpy.mockRestore();
+
+ await cleanupDbForTest();
+ mockIsImpactAdvocateConfigured.mockReturnValue(true);
+ mockSendImpactConversionPayload.mockResolvedValue({
+ ok: false,
+ failureKind: 'network',
+ error: 'network down',
+ });
+ const retryReferrer = await insertTestUser({ created_at: '2025-12-01T00:00:00.000Z' });
+ const retryReferee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ const retryReferralCode = await insertParticipant(retryReferrer.id);
+ await insertTouch({
+ userId: retryReferee.id,
+ type: 'referral',
+ referralCode: retryReferralCode,
+ });
+ const retrySubscriptionId = await insertKiloPassSubscription({ userId: retryReferee.id });
+
+ const retryDisposition = await processInvoice({
+ refereeId: retryReferee.id,
+ subscriptionId: retrySubscriptionId,
+ });
+
+ const retryReport = await db.query.impact_conversion_reports.findFirst({
+ where: eq(impact_conversion_reports.conversion_id, retryDisposition.conversionId ?? ''),
+ });
+ expect(retryReport?.state).toBe(ImpactConversionReportState.Retrying);
+ expect(await db.select().from(impact_referral_rewards)).toHaveLength(2);
+ });
+
+ test('redemption dispatcher backfills missing Kilo Pass reward redemption rows', async () => {
+ const { rewardIds } = await seedKiloPassReferralRewardsForAdversePayment({
+ statuses: [ImpactReferralRewardStatus.Pending, ImpactReferralRewardStatus.Earned],
+ });
+
+ expect(await db.select().from(impact_advocate_reward_redemptions)).toHaveLength(0);
+
+ const summary = await dispatchQueuedImpactAdvocateRewardRedemptions();
+
+ expect(summary).toEqual({ claimed: 2, redeemed: 2, retried: 0, failed: 0 });
+ const redemptions = await db.select().from(impact_advocate_reward_redemptions);
+ expect(redemptions).toEqual(
+ expect.arrayContaining(
+ rewardIds.map(rewardId =>
+ expect.objectContaining({
+ reward_id: rewardId,
+ state: 'redeemed',
+ request_payload: expect.objectContaining({
+ programKey: ImpactAdvocateProgramKey.KiloPass,
+ redemption: { amount: 24.5, unit: 'Kilo Pass Bonus Credits' },
+ }),
+ })
+ )
+ )
+ );
+ });
+
+ test('expires stale pending and earned Kilo Pass referral rewards independently of issuance', async () => {
+ const { rewardIds } = await seedKiloPassReferralRewardsForAdversePayment({
+ statuses: [ImpactReferralRewardStatus.Pending, ImpactReferralRewardStatus.Earned],
+ });
+ await db
+ .update(impact_referral_rewards)
+ .set({ expires_at: '2026-01-02T00:00:00.000Z' })
+ .where(eq(impact_referral_rewards.id, rewardIds[0] ?? ''));
+ await db
+ .update(impact_referral_rewards)
+ .set({ expires_at: '2026-01-02T00:00:00.000Z' })
+ .where(eq(impact_referral_rewards.id, rewardIds[1] ?? ''));
+
+ const firstSummary = await expirePendingKiloPassReferralRewards({
+ now: new Date('2026-01-03T00:00:00.000Z'),
+ });
+ const retrySummary = await expirePendingKiloPassReferralRewards({
+ now: new Date('2026-01-03T00:00:00.000Z'),
+ });
+
+ expect(firstSummary).toEqual({ expiredRewards: 2 });
+ expect(retrySummary).toEqual({ expiredRewards: 0 });
+ const rewards = await db
+ .select({
+ status: impact_referral_rewards.status,
+ reviewReason: impact_referral_rewards.review_reason,
+ reversedAt: impact_referral_rewards.reversed_at,
+ })
+ .from(impact_referral_rewards);
+ expect(
+ rewards.map(reward => ({
+ ...reward,
+ reversedAt: new Date(reward.reversedAt ?? '').toISOString(),
+ }))
+ ).toEqual([
+ {
+ status: ImpactReferralRewardStatus.Expired,
+ reviewReason: 'expired_kilo_pass_referral_reward',
+ reversedAt: '2026-01-03T00:00:00.000Z',
+ },
+ {
+ status: ImpactReferralRewardStatus.Expired,
+ reviewReason: 'expired_kilo_pass_referral_reward',
+ reversedAt: '2026-01-03T00:00:00.000Z',
+ },
+ ]);
+ });
+
+ test('adverse Stripe payment cancels pending and earned Kilo Pass referral rewards idempotently', async () => {
+ const { invoiceId, conversionId } = await seedKiloPassReferralRewardsForAdversePayment({
+ statuses: [ImpactReferralRewardStatus.Pending, ImpactReferralRewardStatus.Earned],
+ });
+
+ const firstSummary = await markPersonalKiloPassReferralPaymentAdverse({
+ sourcePaymentId: invoiceId,
+ reason: 'refund',
+ occurredAt: new Date('2026-01-10T00:00:00.000Z'),
+ });
+
+ expect(firstSummary).toEqual({
+ conversionId,
+ canceledRewards: 2,
+ reviewRequiredRewards: 0,
+ });
+ const rewardsAfterFirst = await db
+ .select({
+ status: impact_referral_rewards.status,
+ reviewReason: impact_referral_rewards.review_reason,
+ reversedAt: impact_referral_rewards.reversed_at,
+ })
+ .from(impact_referral_rewards)
+ .where(eq(impact_referral_rewards.conversion_id, conversionId));
+ expect(
+ rewardsAfterFirst.map(reward => ({
+ ...reward,
+ reversedAt: new Date(reward.reversedAt ?? '').toISOString(),
+ }))
+ ).toEqual([
+ {
+ status: ImpactReferralRewardStatus.Canceled,
+ reviewReason: 'referral_payment_refund',
+ reversedAt: '2026-01-10T00:00:00.000Z',
+ },
+ {
+ status: ImpactReferralRewardStatus.Canceled,
+ reviewReason: 'referral_payment_refund',
+ reversedAt: '2026-01-10T00:00:00.000Z',
+ },
+ ]);
+
+ const retrySummary = await markPersonalKiloPassReferralPaymentAdverse({
+ sourcePaymentId: invoiceId,
+ reason: 'refund',
+ occurredAt: new Date('2026-01-10T00:00:00.000Z'),
+ });
+
+ expect(retrySummary).toEqual({
+ conversionId,
+ canceledRewards: 0,
+ reviewRequiredRewards: 0,
+ });
+ });
+
+ test('adverse Stripe dispute moves applied Kilo Pass referral rewards to support review without clawback', async () => {
+ const { invoiceId, conversionId, rewardIds } =
+ await seedKiloPassReferralRewardsForAdversePayment({
+ statuses: [ImpactReferralRewardStatus.Applied],
+ });
+
+ const summary = await markPersonalKiloPassReferralPaymentAdverse({
+ sourcePaymentId: invoiceId,
+ reason: 'chargeback',
+ occurredAt: new Date('2026-01-12T00:00:00.000Z'),
+ });
+
+ expect(summary).toEqual({
+ conversionId,
+ canceledRewards: 0,
+ reviewRequiredRewards: 1,
+ });
+ const reward = await db.query.impact_referral_rewards.findFirst({
+ where: eq(impact_referral_rewards.id, rewardIds[0] ?? ''),
+ });
+ expect(reward).toEqual(
+ expect.objectContaining({
+ status: ImpactReferralRewardStatus.ReviewRequired,
+ review_reason: 'referral_payment_chargeback',
+ })
+ );
+ expect(new Date(reward?.reversed_at ?? '').toISOString()).toBe('2026-01-12T00:00:00.000Z');
+ expect(new Date(reward?.applied_at ?? '').toISOString()).toBe('2026-02-01T00:00:00.000Z');
+ });
+
+ test('Kilo Pass adverse payment lookup is scoped to Stripe invoice conversion identity', async () => {
+ await seedKiloPassReferralRewardsForAdversePayment({
+ invoiceId: 'shared-payment-id',
+ statuses: [ImpactReferralRewardStatus.Pending],
+ });
+ const otherUser = await insertTestUser();
+ const [creditsConversion] = await db
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: otherUser.id,
+ winning_touch_type: ImpactReferralWinningTouchType.Referral,
+ payment_provider: ImpactReferralPaymentProvider.Credits,
+ source_payment_id: 'shared-payment-id',
+ qualified: true,
+ converted_at: '2026-01-03T00:00:00.000Z',
+ })
+ .returning({ id: impact_referral_conversions.id });
+
+ const summary = await markPersonalKiloPassReferralPaymentAdverse({
+ sourcePaymentId: 'shared-payment-id',
+ reason: 'fraud',
+ occurredAt: new Date('2026-01-14T00:00:00.000Z'),
+ });
+
+ expect(summary.conversionId).not.toBe(creditsConversion?.id);
+ const rewards = await db.select().from(impact_referral_rewards);
+ expect(rewards).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ status: ImpactReferralRewardStatus.Canceled,
+ review_reason: 'referral_payment_fraud',
+ }),
+ ])
+ );
+ });
+
+ test('annual invoices are ineligible for referral rewards', async () => {
+ const referrer = await insertTestUser({ created_at: '2025-12-01T00:00:00.000Z' });
+ const referee = await insertTestUser({ created_at: '2026-01-02T00:00:00.000Z' });
+ const referralCode = await insertParticipant(referrer.id);
+ await insertTouch({ userId: referee.id, type: 'referral', referralCode });
+ const subscriptionId = await insertKiloPassSubscription({
+ userId: referee.id,
+ cadence: KiloPassCadence.Yearly,
+ });
+
+ const disposition = await processInvoice({
+ refereeId: referee.id,
+ subscriptionId,
+ cadence: KiloPassCadence.Yearly,
+ amount: 588,
+ });
+
+ expect(disposition.disqualificationReason).toBe('referral_non_monthly_kilo_pass_subscription');
+ expect(
+ await db
+ .select()
+ .from(impact_referral_rewards)
+ .where(eq(impact_referral_rewards.conversion_id, disposition.conversionId ?? ''))
+ ).toHaveLength(0);
+ });
+});
diff --git a/apps/web/src/lib/impact/kilo-pass-referrals.ts b/apps/web/src/lib/impact/kilo-pass-referrals.ts
new file mode 100644
index 0000000000..d9b0118e13
--- /dev/null
+++ b/apps/web/src/lib/impact/kilo-pass-referrals.ts
@@ -0,0 +1,975 @@
+import 'server-only';
+
+import { addMonths } from 'date-fns';
+import { and, asc, count, eq, inArray, isNull, lte, ne, sql } from 'drizzle-orm';
+
+import { db, type DrizzleTransaction } from '@/lib/drizzle';
+import {
+ IMPACT_ACTION_TRACKER_IDS,
+ buildSalePayload,
+ hashEmailForImpact,
+ isImpactConfigured,
+} from '@/lib/impact';
+import { isImpactAdvocateConfigured } from '@/lib/impact/advocate';
+import {
+ dispatchImpactConversionReportById,
+ queueImpactAdvocateRewardRedemption,
+ resolveWinningAttributionTouch,
+ type AdverseReferralPaymentReason,
+} from '@/lib/impact/kiloclaw-referrals';
+import { hashNormalizedEmailForDeletionTombstone } from '@/lib/impact/referral';
+import { logImpactReferralDebug } from '@/lib/impact/debug';
+import { KILO_PASS_TIER_CONFIG } from '@/lib/kilo-pass/constants';
+import {
+ deleted_user_email_tombstones,
+ impact_advocate_participants,
+ impact_attribution_touches,
+ impact_conversion_reports,
+ impact_referral_conversions,
+ impact_referral_reward_decisions,
+ impact_referral_rewards,
+ impact_referrals,
+ kilo_pass_issuances,
+ kilo_pass_subscriptions,
+ kilocode_users,
+ user_affiliate_attributions,
+ type ImpactAttributionTouch,
+} from '@kilocode/db/schema';
+import {
+ ImpactAdvocateProgramKey,
+ ImpactAttributionTouchType,
+ ImpactConversionReportState,
+ ImpactReferralBeneficiaryRole,
+ ImpactReferralDecisionOutcome,
+ ImpactReferralPaymentProvider,
+ ImpactReferralProduct,
+ ImpactReferralRewardKind,
+ ImpactReferralRewardStatus,
+ ImpactReferralWinningTouchType,
+ KiloPassCadence,
+ KiloPassWelcomePromoEligibilityReason,
+ type KiloPassTier,
+} from '@kilocode/db/schema-types';
+
+type DatabaseClient = typeof db | DrizzleTransaction;
+
+export type KiloPassPaidConversionDisposition = {
+ shouldEnqueueAffiliateSale: boolean;
+ winningTouchType: 'referral' | 'affiliate' | 'none';
+ conversionId: string | null;
+ disqualificationReason: string | null;
+};
+
+export type KiloPassAdverseReferralPaymentSummary = {
+ conversionId: string | null;
+ canceledRewards: number;
+ reviewRequiredRewards: number;
+};
+
+export type KiloPassReferralRewardExpirationSummary = {
+ expiredRewards: number;
+};
+
+export const KILO_PASS_REFERRER_REWARD_CAP = 5;
+const KILO_PASS_REFERRAL_REWARD_PERCENT = 0.5;
+const SIGNUP_REFERRAL_TOUCH_CAPTURE_GRACE_MS = 10 * 60 * 1000;
+
+function referralDisqualificationReason(reason: string): string {
+ return `referral_${reason}`;
+}
+
+function getAdversePaymentReason(reason: AdverseReferralPaymentReason): string {
+ return `referral_payment_${reason}`;
+}
+
+function getKiloPassReferralConfigurationState() {
+ const impactPerformanceConfigured = isImpactConfigured();
+ const impactAdvocateConfigured = isImpactAdvocateConfigured({
+ product: ImpactReferralProduct.KiloPass,
+ });
+
+ return {
+ impactPerformanceConfigured,
+ impactAdvocateConfigured,
+ isConfigured: impactPerformanceConfigured && impactAdvocateConfigured,
+ };
+}
+
+function logKiloPassReferralConfigurationFailure(params: {
+ sourcePaymentId?: string;
+ conversionId?: string;
+ userId?: string;
+}): void {
+ const configurationState = getKiloPassReferralConfigurationState();
+ console.error('[kilo-pass-referrals] reward-bearing referral configuration is incomplete', {
+ ...params,
+ impactPerformanceConfigured: configurationState.impactPerformanceConfigured,
+ impactAdvocateConfigured: configurationState.impactAdvocateConfigured,
+ });
+}
+
+function buildImpactReferralId(touch: ImpactAttributionTouch): string | null {
+ return touch.rs_code?.trim() || touch.opaque_tracking_value?.trim() || null;
+}
+
+async function findAcceptedUserTouches(params: {
+ userId: string;
+ convertedAt: Date;
+ database: DatabaseClient;
+}): Promise {
+ return await params.database
+ .select()
+ .from(impact_attribution_touches)
+ .where(
+ and(
+ eq(impact_attribution_touches.product, ImpactReferralProduct.KiloPass),
+ eq(impact_attribution_touches.user_id, params.userId),
+ lte(impact_attribution_touches.touched_at, params.convertedAt.toISOString())
+ )
+ )
+ .orderBy(
+ asc(impact_attribution_touches.touched_at),
+ asc(impact_attribution_touches.created_at)
+ );
+}
+
+async function hasHistoricalImpactAffiliateAttribution(params: {
+ userId: string;
+ database: DatabaseClient;
+}): Promise {
+ const [attribution] = await params.database
+ .select({ id: user_affiliate_attributions.id })
+ .from(user_affiliate_attributions)
+ .where(
+ and(
+ eq(user_affiliate_attributions.user_id, params.userId),
+ eq(user_affiliate_attributions.provider, 'impact')
+ )
+ )
+ .limit(1);
+
+ return Boolean(attribution);
+}
+
+async function markAffiliateTouchSaleAttributed(params: {
+ database: DatabaseClient;
+ affiliateTouchId: string;
+ convertedAt: Date;
+}): Promise {
+ await params.database
+ .update(impact_attribution_touches)
+ .set({
+ sale_attributed_at: sql`COALESCE(${impact_attribution_touches.sale_attributed_at}, ${params.convertedAt.toISOString()}::timestamptz)`,
+ })
+ .where(eq(impact_attribution_touches.id, params.affiliateTouchId));
+}
+
+async function resolveReferrerUserIdFromReferralTouch(params: {
+ referralTouch: ImpactAttributionTouch;
+ database: DatabaseClient;
+}): Promise {
+ const opaqueReferralIdentifier = buildImpactReferralId(params.referralTouch)?.trim();
+ if (!opaqueReferralIdentifier) return null;
+
+ const [participant] = await params.database
+ .select({ userId: impact_advocate_participants.user_id })
+ .from(impact_advocate_participants)
+ .where(
+ and(
+ eq(impact_advocate_participants.program_key, ImpactAdvocateProgramKey.KiloPass),
+ eq(impact_advocate_participants.opaque_referral_identifier, opaqueReferralIdentifier)
+ )
+ )
+ .limit(1);
+
+ return participant?.userId ?? null;
+}
+
+async function upsertReferralRelationship(params: {
+ refereeUserId: string;
+ referrerUserId: string | null;
+ sourceTouchId: string;
+ impactReferralId: string | null;
+ database: DatabaseClient;
+}): Promise {
+ await params.database
+ .insert(impact_referrals)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: params.refereeUserId,
+ referrer_user_id: params.referrerUserId,
+ source_touch_id: params.sourceTouchId,
+ impact_referral_id: params.impactReferralId,
+ })
+ .onConflictDoUpdate({
+ target: [impact_referrals.product, impact_referrals.referee_user_id],
+ set: {
+ referrer_user_id: params.referrerUserId,
+ source_touch_id: params.sourceTouchId,
+ impact_referral_id: params.impactReferralId,
+ },
+ });
+}
+
+function wasReferralTouchCapturedDuringSignup(params: {
+ userCreatedAt: string;
+ referralTouch: ImpactAttributionTouch;
+}): boolean {
+ if (!params.referralTouch.landing_path) return false;
+
+ const touchTime = new Date(params.referralTouch.touched_at).getTime();
+ const userCreatedTime = new Date(params.userCreatedAt).getTime();
+ if (touchTime < userCreatedTime) return false;
+ if (touchTime - userCreatedTime > SIGNUP_REFERRAL_TOUCH_CAPTURE_GRACE_MS) return false;
+
+ try {
+ const landingUrl = new URL(params.referralTouch.landing_path, 'http://localhost');
+ return landingUrl.searchParams.get('signup') === 'true';
+ } catch {
+ return false;
+ }
+}
+
+async function hasDeletedUserEmailTombstone(params: {
+ normalizedEmail: string | null;
+ database: DatabaseClient;
+}): Promise {
+ if (!params.normalizedEmail) return false;
+
+ const [row] = await params.database
+ .select({ hash: deleted_user_email_tombstones.normalized_email_hash })
+ .from(deleted_user_email_tombstones)
+ .where(
+ eq(
+ deleted_user_email_tombstones.normalized_email_hash,
+ hashNormalizedEmailForDeletionTombstone(params.normalizedEmail)
+ )
+ )
+ .limit(1);
+
+ return Boolean(row);
+}
+
+async function lockReferrerRewardCapacity(
+ referrerUserId: string,
+ database: DatabaseClient
+): Promise {
+ await database.execute(
+ sql`SELECT ${kilocode_users.id} FROM ${kilocode_users} WHERE ${kilocode_users.id} = ${referrerUserId} FOR UPDATE`
+ );
+}
+
+async function getGrantedKiloPassReferrerRewardCount(
+ referrerUserId: string,
+ database: DatabaseClient
+): Promise {
+ const [result] = await database
+ .select({ rewardCount: count() })
+ .from(impact_referral_reward_decisions)
+ .where(
+ and(
+ eq(impact_referral_reward_decisions.product, ImpactReferralProduct.KiloPass),
+ eq(impact_referral_reward_decisions.reward_kind, ImpactReferralRewardKind.KiloPassBonus),
+ eq(impact_referral_reward_decisions.beneficiary_user_id, referrerUserId),
+ eq(
+ impact_referral_reward_decisions.beneficiary_role,
+ ImpactReferralBeneficiaryRole.Referrer
+ ),
+ eq(impact_referral_reward_decisions.outcome, ImpactReferralDecisionOutcome.Granted)
+ )
+ );
+
+ return result?.rewardCount ?? 0;
+}
+
+async function hasPriorKiloPassSubscriptionHistory(params: {
+ userId: string;
+ currentSubscriptionId: string;
+ currentStripeInvoiceId: string;
+ database: DatabaseClient;
+}): Promise {
+ const [priorSubscription] = await params.database
+ .select({ id: kilo_pass_subscriptions.id })
+ .from(kilo_pass_subscriptions)
+ .where(
+ and(
+ eq(kilo_pass_subscriptions.kilo_user_id, params.userId),
+ ne(kilo_pass_subscriptions.id, params.currentSubscriptionId)
+ )
+ )
+ .limit(1);
+ if (priorSubscription) return true;
+
+ const [priorIssuance] = await params.database
+ .select({ id: kilo_pass_issuances.id })
+ .from(kilo_pass_issuances)
+ .where(
+ and(
+ eq(kilo_pass_issuances.kilo_pass_subscription_id, params.currentSubscriptionId),
+ ne(kilo_pass_issuances.stripe_invoice_id, params.currentStripeInvoiceId)
+ )
+ )
+ .limit(1);
+
+ return Boolean(priorIssuance);
+}
+
+function getRewardAmountUsd(sourceTier: KiloPassTier): number {
+ return (
+ Math.round(
+ KILO_PASS_TIER_CONFIG[sourceTier].monthlyPriceUsd * KILO_PASS_REFERRAL_REWARD_PERCENT * 100
+ ) / 100
+ );
+}
+
+function shouldPreserveAffiliateSale(winningTouchType: string): boolean {
+ return winningTouchType === ImpactReferralWinningTouchType.Affiliate;
+}
+
+export async function expirePendingKiloPassReferralRewards(params?: {
+ now?: Date;
+ database?: DatabaseClient;
+}): Promise {
+ const now = params?.now ?? new Date();
+ const database = params?.database ?? db;
+ const nowIso = now.toISOString();
+
+ const expiredRewards = await database
+ .update(impact_referral_rewards)
+ .set({
+ status: ImpactReferralRewardStatus.Expired,
+ reversed_at: nowIso,
+ review_reason: 'expired_kilo_pass_referral_reward',
+ })
+ .where(
+ and(
+ eq(impact_referral_rewards.product, ImpactReferralProduct.KiloPass),
+ eq(impact_referral_rewards.reward_kind, ImpactReferralRewardKind.KiloPassBonus),
+ inArray(impact_referral_rewards.status, [
+ ImpactReferralRewardStatus.Pending,
+ ImpactReferralRewardStatus.Earned,
+ ]),
+ isNull(impact_referral_rewards.applied_at),
+ isNull(impact_referral_rewards.consumed_kilo_pass_issuance_id),
+ sql`${impact_referral_rewards.expires_at} IS NOT NULL`,
+ lte(impact_referral_rewards.expires_at, nowIso)
+ )
+ )
+ .returning({ id: impact_referral_rewards.id });
+
+ return { expiredRewards: expiredRewards.length };
+}
+
+export async function markPersonalKiloPassReferralPaymentAdverse(params: {
+ sourcePaymentId: string;
+ reason: AdverseReferralPaymentReason;
+ occurredAt: Date;
+ paymentProvider?: ImpactReferralPaymentProvider;
+}): Promise {
+ const paymentProvider = params.paymentProvider ?? ImpactReferralPaymentProvider.Stripe;
+ const reviewReason = getAdversePaymentReason(params.reason);
+ const occurredAt = params.occurredAt.toISOString();
+
+ return await db.transaction(async tx => {
+ const conversion = await tx.query.impact_referral_conversions.findFirst({
+ where: and(
+ eq(impact_referral_conversions.product, ImpactReferralProduct.KiloPass),
+ eq(impact_referral_conversions.payment_provider, paymentProvider),
+ eq(impact_referral_conversions.source_payment_id, params.sourcePaymentId)
+ ),
+ columns: { id: true },
+ });
+
+ if (!conversion) {
+ return {
+ conversionId: null,
+ canceledRewards: 0,
+ reviewRequiredRewards: 0,
+ } satisfies KiloPassAdverseReferralPaymentSummary;
+ }
+
+ const canceledRewards = await tx
+ .update(impact_referral_rewards)
+ .set({
+ status: ImpactReferralRewardStatus.Canceled,
+ review_reason: reviewReason,
+ reversed_at: occurredAt,
+ })
+ .where(
+ and(
+ eq(impact_referral_rewards.product, ImpactReferralProduct.KiloPass),
+ eq(impact_referral_rewards.reward_kind, ImpactReferralRewardKind.KiloPassBonus),
+ eq(impact_referral_rewards.conversion_id, conversion.id),
+ inArray(impact_referral_rewards.status, [
+ ImpactReferralRewardStatus.Pending,
+ ImpactReferralRewardStatus.Earned,
+ ]),
+ sql`${impact_referral_rewards.applied_at} IS NULL`,
+ sql`${impact_referral_rewards.consumed_kilo_pass_issuance_id} IS NULL`
+ )
+ )
+ .returning({ id: impact_referral_rewards.id });
+
+ const reviewRequiredRewards = await tx
+ .update(impact_referral_rewards)
+ .set({
+ status: ImpactReferralRewardStatus.ReviewRequired,
+ review_reason: reviewReason,
+ reversed_at: occurredAt,
+ })
+ .where(
+ and(
+ eq(impact_referral_rewards.product, ImpactReferralProduct.KiloPass),
+ eq(impact_referral_rewards.reward_kind, ImpactReferralRewardKind.KiloPassBonus),
+ eq(impact_referral_rewards.conversion_id, conversion.id),
+ eq(impact_referral_rewards.status, ImpactReferralRewardStatus.Applied)
+ )
+ )
+ .returning({ id: impact_referral_rewards.id });
+
+ return {
+ conversionId: conversion.id,
+ canceledRewards: canceledRewards.length,
+ reviewRequiredRewards: reviewRequiredRewards.length,
+ } satisfies KiloPassAdverseReferralPaymentSummary;
+ });
+}
+
+export async function processPersonalKiloPassStripePaidConversion(params: {
+ userId: string;
+ kiloPassSubscriptionId: string;
+ sourcePaymentId: string;
+ orderId: string;
+ amount: number;
+ currencyCode: string;
+ itemCategory: string;
+ itemName: string;
+ itemSku?: string;
+ sourceTier: KiloPassTier;
+ cadence: KiloPassCadence;
+ welcomePromoEligibilityReason?: KiloPassWelcomePromoEligibilityReason | null;
+ convertedAt: Date;
+}): Promise {
+ const paymentProvider = ImpactReferralPaymentProvider.Stripe;
+ const referralSaleDedupeKey = `impact-referral-sale:${ImpactReferralProduct.KiloPass}:${paymentProvider}:${params.sourcePaymentId}`;
+
+ logImpactReferralDebug('Processing personal Kilo Pass paid conversion for Impact referrals', {
+ userId: params.userId,
+ sourcePaymentId: params.sourcePaymentId,
+ orderId: params.orderId,
+ amount: params.amount,
+ currencyCode: params.currencyCode,
+ itemCategory: params.itemCategory,
+ cadence: params.cadence,
+ sourceTier: params.sourceTier,
+ welcomePromoEligibilityReason: params.welcomePromoEligibilityReason ?? null,
+ });
+
+ let impactReportId: string | null = null;
+ const disposition = await db.transaction(async tx => {
+ const existingConversion = await tx.query.impact_referral_conversions.findFirst({
+ where: and(
+ eq(impact_referral_conversions.product, ImpactReferralProduct.KiloPass),
+ eq(impact_referral_conversions.payment_provider, paymentProvider),
+ eq(impact_referral_conversions.source_payment_id, params.sourcePaymentId)
+ ),
+ });
+
+ if (existingConversion) {
+ const existingReport = await tx.query.impact_conversion_reports.findFirst({
+ where: eq(impact_conversion_reports.conversion_id, existingConversion.id),
+ columns: { id: true, state: true },
+ });
+ const reportIsRetryable =
+ existingReport?.state === ImpactConversionReportState.Queued ||
+ existingReport?.state === ImpactConversionReportState.Retrying;
+ impactReportId =
+ existingConversion.qualified &&
+ existingConversion.winning_touch_type === ImpactReferralWinningTouchType.Referral &&
+ reportIsRetryable
+ ? (existingReport?.id ?? null)
+ : null;
+
+ return {
+ shouldEnqueueAffiliateSale: shouldPreserveAffiliateSale(
+ existingConversion.winning_touch_type
+ ),
+ winningTouchType: existingConversion.winning_touch_type,
+ conversionId: existingConversion.id,
+ disqualificationReason: existingConversion.disqualification_reason,
+ } satisfies KiloPassPaidConversionDisposition;
+ }
+
+ const [user] = await tx
+ .select({
+ id: kilocode_users.id,
+ createdAt: kilocode_users.created_at,
+ email: kilocode_users.google_user_email,
+ normalizedEmail: kilocode_users.normalized_email,
+ })
+ .from(kilocode_users)
+ .where(eq(kilocode_users.id, params.userId))
+ .limit(1);
+
+ if (!user) {
+ return {
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.None,
+ conversionId: null,
+ disqualificationReason: 'user_missing',
+ } satisfies KiloPassPaidConversionDisposition;
+ }
+
+ const touches = await findAcceptedUserTouches({
+ userId: params.userId,
+ convertedAt: params.convertedAt,
+ database: tx,
+ });
+ const resolution = resolveWinningAttributionTouch({
+ product: ImpactReferralProduct.KiloPass,
+ touches,
+ convertedAt: params.convertedAt,
+ });
+
+ logImpactReferralDebug('Resolved Kilo Pass Impact attribution touches for paid conversion', {
+ userId: params.userId,
+ sourcePaymentId: params.sourcePaymentId,
+ touchCount: touches.length,
+ affiliateTouchCount: touches.filter(
+ touch => touch.touch_type === ImpactAttributionTouchType.Affiliate
+ ).length,
+ referralTouchCount: touches.filter(
+ touch => touch.touch_type === ImpactAttributionTouchType.Referral
+ ).length,
+ winner: resolution.winner,
+ affiliateTouchId: resolution.affiliateTouch?.id ?? null,
+ referralTouchId: resolution.referralTouch?.id ?? null,
+ });
+
+ // Preserve legacy first-touch Impact affiliate attribution for Kilo Pass SALE reporting
+ // when no product-scoped touch exists, per the affiliate spec. Expired scoped touches
+ // still suppress this fallback so they cannot bypass the referral attribution window.
+ if (
+ resolution.winner === 'none' &&
+ touches.length === 0 &&
+ (await hasHistoricalImpactAffiliateAttribution({ userId: params.userId, database: tx }))
+ ) {
+ const [conversion] = await tx
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: params.userId,
+ referrer_user_id: null,
+ source_touch_id: null,
+ winning_touch_type: ImpactReferralWinningTouchType.Affiliate,
+ source_payment_id: params.sourcePaymentId,
+ payment_provider: paymentProvider,
+ qualified: false,
+ disqualification_reason: referralDisqualificationReason('affiliate_won'),
+ converted_at: params.convertedAt.toISOString(),
+ })
+ .returning({ id: impact_referral_conversions.id });
+
+ return {
+ shouldEnqueueAffiliateSale: true,
+ winningTouchType: ImpactReferralWinningTouchType.Affiliate,
+ conversionId: conversion?.id ?? null,
+ disqualificationReason: referralDisqualificationReason('affiliate_won'),
+ } satisfies KiloPassPaidConversionDisposition;
+ }
+
+ if (resolution.winner === 'none') {
+ const [conversion] = await tx
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: params.userId,
+ referrer_user_id: null,
+ source_touch_id: null,
+ winning_touch_type: ImpactReferralWinningTouchType.None,
+ source_payment_id: params.sourcePaymentId,
+ payment_provider: paymentProvider,
+ qualified: false,
+ disqualification_reason: referralDisqualificationReason('no_valid_attribution'),
+ converted_at: params.convertedAt.toISOString(),
+ })
+ .returning({ id: impact_referral_conversions.id });
+
+ return {
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.None,
+ conversionId: conversion?.id ?? null,
+ disqualificationReason: referralDisqualificationReason('no_valid_attribution'),
+ } satisfies KiloPassPaidConversionDisposition;
+ }
+
+ if (resolution.winner === 'affiliate') {
+ await markAffiliateTouchSaleAttributed({
+ database: tx,
+ affiliateTouchId: resolution.affiliateTouch.id,
+ convertedAt: params.convertedAt,
+ });
+
+ const [conversion] = await tx
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: params.userId,
+ referrer_user_id: null,
+ source_touch_id: resolution.affiliateTouch.id,
+ winning_touch_type: ImpactReferralWinningTouchType.Affiliate,
+ source_payment_id: params.sourcePaymentId,
+ payment_provider: paymentProvider,
+ qualified: false,
+ disqualification_reason: referralDisqualificationReason('affiliate_won'),
+ converted_at: params.convertedAt.toISOString(),
+ })
+ .returning({ id: impact_referral_conversions.id });
+
+ return {
+ shouldEnqueueAffiliateSale: true,
+ winningTouchType: ImpactReferralWinningTouchType.Affiliate,
+ conversionId: conversion?.id ?? null,
+ disqualificationReason: referralDisqualificationReason('affiliate_won'),
+ } satisfies KiloPassPaidConversionDisposition;
+ }
+
+ const referrerUserId = await resolveReferrerUserIdFromReferralTouch({
+ referralTouch: resolution.referralTouch,
+ database: tx,
+ });
+ await upsertReferralRelationship({
+ refereeUserId: params.userId,
+ referrerUserId,
+ sourceTouchId: resolution.referralTouch.id,
+ impactReferralId: buildImpactReferralId(resolution.referralTouch),
+ database: tx,
+ });
+
+ const isYearly = params.cadence === KiloPassCadence.Yearly;
+ const isFreeOrComped = params.amount <= 0;
+ const hasPreviouslyClaimedPaymentFingerprint =
+ params.welcomePromoEligibilityReason ===
+ KiloPassWelcomePromoEligibilityReason.FingerprintPreviouslyClaimed;
+ const hasPriorSubscriptionHistory = await hasPriorKiloPassSubscriptionHistory({
+ userId: params.userId,
+ currentSubscriptionId: params.kiloPassSubscriptionId,
+ currentStripeInvoiceId: params.sourcePaymentId,
+ database: tx,
+ });
+ const deletedUser = await hasDeletedUserEmailTombstone({
+ normalizedEmail: user.normalizedEmail,
+ database: tx,
+ });
+ const userExistedBeforeReferral =
+ new Date(user.createdAt).getTime() <
+ new Date(resolution.referralTouch.touched_at).getTime() &&
+ !wasReferralTouchCapturedDuringSignup({
+ userCreatedAt: user.createdAt,
+ referralTouch: resolution.referralTouch,
+ });
+ const isSelfReferral = referrerUserId !== null && referrerUserId === params.userId;
+
+ const disqualificationReason = isYearly
+ ? referralDisqualificationReason('non_monthly_kilo_pass_subscription')
+ : isFreeOrComped
+ ? referralDisqualificationReason('fully_comped_payment')
+ : hasPreviouslyClaimedPaymentFingerprint
+ ? referralDisqualificationReason('payment_fingerprint_previously_claimed')
+ : hasPriorSubscriptionHistory
+ ? referralDisqualificationReason('prior_kilo_pass_subscription')
+ : deletedUser
+ ? referralDisqualificationReason('deleted_user_tombstone')
+ : userExistedBeforeReferral
+ ? referralDisqualificationReason('existing_user_before_touch')
+ : !referrerUserId
+ ? referralDisqualificationReason('referrer_unresolved')
+ : isSelfReferral
+ ? referralDisqualificationReason('self_referral')
+ : null;
+
+ if (disqualificationReason) {
+ const [conversion] = await tx
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: params.userId,
+ referrer_user_id: referrerUserId,
+ source_touch_id: resolution.referralTouch.id,
+ winning_touch_type: ImpactReferralWinningTouchType.Referral,
+ source_payment_id: params.sourcePaymentId,
+ payment_provider: paymentProvider,
+ qualified: false,
+ disqualification_reason: disqualificationReason,
+ converted_at: params.convertedAt.toISOString(),
+ })
+ .returning({ id: impact_referral_conversions.id });
+
+ return {
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.Referral,
+ conversionId: conversion?.id ?? null,
+ disqualificationReason,
+ } satisfies KiloPassPaidConversionDisposition;
+ }
+
+ if (!referrerUserId) {
+ throw new Error('Kilo Pass referral referrer unexpectedly missing after eligibility checks');
+ }
+
+ if (!getKiloPassReferralConfigurationState().isConfigured) {
+ const missingConfigReason = referralDisqualificationReason('missing_configuration');
+ logKiloPassReferralConfigurationFailure({
+ sourcePaymentId: params.sourcePaymentId,
+ userId: params.userId,
+ });
+
+ const [conversion] = await tx
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: params.userId,
+ referrer_user_id: referrerUserId,
+ source_touch_id: resolution.referralTouch.id,
+ winning_touch_type: ImpactReferralWinningTouchType.Referral,
+ source_payment_id: params.sourcePaymentId,
+ payment_provider: paymentProvider,
+ qualified: false,
+ disqualification_reason: missingConfigReason,
+ converted_at: params.convertedAt.toISOString(),
+ })
+ .returning({ id: impact_referral_conversions.id });
+
+ if (!conversion) {
+ throw new Error(
+ `Failed to create Kilo Pass referral conversion for ${params.sourcePaymentId}`
+ );
+ }
+
+ await tx.insert(impact_referral_reward_decisions).values([
+ {
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ beneficiary_user_id: params.userId,
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referee,
+ outcome: ImpactReferralDecisionOutcome.Disqualified,
+ reason: missingConfigReason,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: KILO_PASS_REFERRAL_REWARD_PERCENT,
+ source_tier: params.sourceTier,
+ reward_amount_usd: 0,
+ },
+ {
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ beneficiary_user_id: referrerUserId,
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referrer,
+ outcome: ImpactReferralDecisionOutcome.Disqualified,
+ reason: missingConfigReason,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: KILO_PASS_REFERRAL_REWARD_PERCENT,
+ source_tier: params.sourceTier,
+ reward_amount_usd: 0,
+ },
+ ]);
+
+ const payload = buildSalePayload({
+ customerId: params.userId,
+ customerEmailHash: hashEmailForImpact(user.email),
+ eventDate: params.convertedAt,
+ orderId: params.orderId,
+ amount: params.amount,
+ currencyCode: params.currencyCode,
+ itemCategory: params.itemCategory,
+ itemName: params.itemName,
+ itemSku: params.itemSku,
+ trackingId: null,
+ });
+
+ await tx
+ .insert(impact_conversion_reports)
+ .values({
+ conversion_id: conversion.id,
+ dedupe_key: referralSaleDedupeKey,
+ action_tracker_id: IMPACT_ACTION_TRACKER_IDS.sale,
+ order_id: params.orderId,
+ state: ImpactConversionReportState.Failed,
+ request_payload: payload satisfies Record,
+ response_payload: {
+ error: 'missing_reward_bearing_referral_configuration',
+ } satisfies Record,
+ })
+ .onConflictDoNothing({ target: [impact_conversion_reports.dedupe_key] });
+
+ return {
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.Referral,
+ conversionId: conversion.id,
+ disqualificationReason: missingConfigReason,
+ } satisfies KiloPassPaidConversionDisposition;
+ }
+
+ await lockReferrerRewardCapacity(referrerUserId, tx);
+ const referrerGrantedRewardCount = await getGrantedKiloPassReferrerRewardCount(
+ referrerUserId,
+ tx
+ );
+ const referrerAtCap = referrerGrantedRewardCount >= KILO_PASS_REFERRER_REWARD_CAP;
+ const rewardAmountUsd = getRewardAmountUsd(params.sourceTier);
+ const earnedAt = params.convertedAt.toISOString();
+ const expiresAt = addMonths(params.convertedAt, 12).toISOString();
+
+ const [conversion] = await tx
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: params.userId,
+ referrer_user_id: referrerUserId,
+ source_touch_id: resolution.referralTouch.id,
+ winning_touch_type: ImpactReferralWinningTouchType.Referral,
+ source_payment_id: params.sourcePaymentId,
+ payment_provider: paymentProvider,
+ qualified: true,
+ disqualification_reason: null,
+ converted_at: earnedAt,
+ })
+ .returning({ id: impact_referral_conversions.id });
+
+ if (!conversion) {
+ throw new Error(
+ `Failed to create Kilo Pass referral conversion for ${params.sourcePaymentId}`
+ );
+ }
+
+ const decisions = await tx
+ .insert(impact_referral_reward_decisions)
+ .values([
+ {
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ beneficiary_user_id: params.userId,
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referee,
+ outcome: ImpactReferralDecisionOutcome.Granted,
+ reason: null,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: KILO_PASS_REFERRAL_REWARD_PERCENT,
+ source_tier: params.sourceTier,
+ reward_amount_usd: rewardAmountUsd,
+ },
+ {
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ beneficiary_user_id: referrerUserId,
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referrer,
+ outcome: referrerAtCap
+ ? ImpactReferralDecisionOutcome.CapLimited
+ : ImpactReferralDecisionOutcome.Granted,
+ reason: referrerAtCap ? referralDisqualificationReason('referrer_cap_reached') : null,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: KILO_PASS_REFERRAL_REWARD_PERCENT,
+ source_tier: params.sourceTier,
+ reward_amount_usd: referrerAtCap ? 0 : rewardAmountUsd,
+ },
+ ])
+ .returning({
+ id: impact_referral_reward_decisions.id,
+ beneficiary_user_id: impact_referral_reward_decisions.beneficiary_user_id,
+ beneficiary_role: impact_referral_reward_decisions.beneficiary_role,
+ outcome: impact_referral_reward_decisions.outcome,
+ reward_amount_usd: impact_referral_reward_decisions.reward_amount_usd,
+ });
+
+ const grantedRewards = decisions
+ .filter(decision => decision.outcome === ImpactReferralDecisionOutcome.Granted)
+ .map(decision => ({
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ decision_id: decision.id,
+ beneficiary_user_id: decision.beneficiary_user_id,
+ beneficiary_role: decision.beneficiary_role,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: KILO_PASS_REFERRAL_REWARD_PERCENT,
+ source_tier: params.sourceTier,
+ reward_amount_usd: decision.reward_amount_usd,
+ status: ImpactReferralRewardStatus.Pending,
+ applies_to_kilo_pass_subscription_id: null,
+ consumed_kilo_pass_issuance_id: null,
+ consumed_kilo_pass_issuance_item_id: null,
+ earned_at: earnedAt,
+ expires_at: expiresAt,
+ }));
+
+ if (grantedRewards.length > 0) {
+ const insertedRewards = await tx
+ .insert(impact_referral_rewards)
+ .values(grantedRewards)
+ .returning({ id: impact_referral_rewards.id });
+
+ for (const reward of insertedRewards) {
+ await queueImpactAdvocateRewardRedemption({ rewardId: reward.id, database: tx });
+ }
+ }
+
+ const payload = buildSalePayload({
+ customerId: params.userId,
+ customerEmailHash: hashEmailForImpact(user.email),
+ eventDate: params.convertedAt,
+ orderId: params.orderId,
+ amount: params.amount,
+ currencyCode: params.currencyCode,
+ itemCategory: params.itemCategory,
+ itemName: params.itemName,
+ itemSku: params.itemSku,
+ trackingId: null,
+ });
+
+ const [report] = await tx
+ .insert(impact_conversion_reports)
+ .values({
+ conversion_id: conversion.id,
+ dedupe_key: referralSaleDedupeKey,
+ action_tracker_id: IMPACT_ACTION_TRACKER_IDS.sale,
+ order_id: params.orderId,
+ state: ImpactConversionReportState.Queued,
+ request_payload: payload satisfies Record,
+ })
+ .onConflictDoNothing({ target: [impact_conversion_reports.dedupe_key] })
+ .returning({ id: impact_conversion_reports.id });
+
+ const existingReport =
+ report ??
+ (await tx.query.impact_conversion_reports.findFirst({
+ where: eq(impact_conversion_reports.dedupe_key, referralSaleDedupeKey),
+ columns: { id: true },
+ }));
+ impactReportId = existingReport?.id ?? null;
+
+ return {
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: ImpactReferralWinningTouchType.Referral,
+ conversionId: conversion.id,
+ disqualificationReason: null,
+ } satisfies KiloPassPaidConversionDisposition;
+ });
+
+ logImpactReferralDebug('Processed personal Kilo Pass paid conversion for Impact referrals', {
+ userId: params.userId,
+ sourcePaymentId: params.sourcePaymentId,
+ shouldEnqueueAffiliateSale: disposition.shouldEnqueueAffiliateSale,
+ winningTouchType: disposition.winningTouchType,
+ conversionId: disposition.conversionId,
+ disqualificationReason: disposition.disqualificationReason,
+ impactReportId,
+ });
+
+ if (impactReportId) {
+ await dispatchImpactConversionReportById(impactReportId);
+ }
+
+ return disposition;
+}
diff --git a/apps/web/src/lib/impact/kiloclaw-referrals.test.ts b/apps/web/src/lib/impact/kiloclaw-referrals.test.ts
index 53eb697740..6b941c593d 100644
--- a/apps/web/src/lib/impact/kiloclaw-referrals.test.ts
+++ b/apps/web/src/lib/impact/kiloclaw-referrals.test.ts
@@ -486,7 +486,8 @@ describe('kiloclaw referrals', () => {
id: 'affiliate-touch',
touch_type: 'affiliate',
im_ref: 'im-ref',
- expires_at: '2026-04-05T00:00:00.000Z',
+ touched_at: '2026-03-01T00:00:00.000Z',
+ expires_at: '2026-03-31T00:00:00.000Z',
});
const invalidReferralTouch = makeTouch({
id: 'referral-touch',
@@ -507,6 +508,63 @@ describe('kiloclaw referrals', () => {
referralTouch: null,
});
});
+
+ it('filters touches by product for mirrored Kilo Pass attribution', () => {
+ const kiloClawReferralTouch = makeTouch({
+ id: 'kiloclaw-referral-touch',
+ product: 'kiloclaw',
+ touch_type: 'referral',
+ touched_at: '2026-04-01T00:00:00.000Z',
+ rs_code: 'claw-ref-code',
+ });
+ const kiloPassAffiliateTouch = makeTouch({
+ id: 'kilo-pass-affiliate-touch',
+ product: 'kilo_pass',
+ program_key: null,
+ touch_type: 'affiliate',
+ touched_at: '2026-04-02T00:00:00.000Z',
+ im_ref: 'pass-im-ref',
+ });
+
+ expect(
+ resolveWinningAttributionTouch({
+ product: 'kilo_pass',
+ touches: [kiloClawReferralTouch, kiloPassAffiliateTouch],
+ convertedAt,
+ })
+ ).toMatchObject({
+ winner: 'affiliate',
+ affiliateTouch: { id: 'kilo-pass-affiliate-touch' },
+ referralTouch: null,
+ });
+ });
+
+ it('uses the exact 30-day UTC expiration boundary', () => {
+ const referralTouch = makeTouch({
+ id: 'boundary-referral-touch',
+ touch_type: 'referral',
+ touched_at: '2026-04-01T00:00:00.000Z',
+ expires_at: '2026-06-01T00:00:00.000Z',
+ rs_code: 'ref-code',
+ });
+
+ expect(
+ resolveWinningAttributionTouch({
+ touches: [referralTouch],
+ convertedAt: new Date('2026-04-30T23:59:59.999Z'),
+ })
+ ).toMatchObject({ winner: 'referral' });
+ expect(
+ resolveWinningAttributionTouch({
+ touches: [referralTouch],
+ convertedAt: new Date('2026-05-01T00:00:00.000Z'),
+ })
+ ).toEqual({
+ winner: 'none',
+ affiliateTouch: null,
+ referralTouch: null,
+ });
+ });
});
describe('processPersonalKiloClawPaidConversion', () => {
diff --git a/apps/web/src/lib/impact/kiloclaw-referrals.ts b/apps/web/src/lib/impact/kiloclaw-referrals.ts
index 59d47c8fd7..c7056a67a1 100644
--- a/apps/web/src/lib/impact/kiloclaw-referrals.ts
+++ b/apps/web/src/lib/impact/kiloclaw-referrals.ts
@@ -22,6 +22,7 @@ import {
} from '@/lib/impact/advocate';
import { logImpactReferralDebug } from '@/lib/impact/debug';
import { hashNormalizedEmailForDeletionTombstone } from '@/lib/impact/referral';
+import { IMPACT_REFERRAL_TOUCH_VALIDITY_MS } from '@/lib/impact/referral-utils';
import { resolveCurrentPersonalSubscriptionRow } from '@/lib/kiloclaw/current-personal-subscription';
import { client as stripe } from '@/lib/stripe-client';
import { insertKiloClawSubscriptionChangeLog } from '@kilocode/db';
@@ -51,6 +52,7 @@ import {
ImpactReferralPaymentProvider,
ImpactReferralProduct,
ImpactReferralRewardKind,
+ ImpactReferralRewardStatus,
ImpactAttributionTouchType,
KiloClawReferralBeneficiaryRole,
KiloClawReferralDecisionOutcome,
@@ -126,7 +128,8 @@ const REFERRAL_REWARD_ACTOR = {
} as const;
const SIGNUP_REFERRAL_TOUCH_CAPTURE_GRACE_MS = 10 * 60 * 1000;
-const IMPACT_ADVOCATE_REWARD_UNIT = 'MONTH';
+const IMPACT_ADVOCATE_KILOCLAW_REWARD_UNIT = 'MONTH';
+const IMPACT_ADVOCATE_KILO_PASS_REWARD_UNIT = 'Kilo Pass Bonus Credits';
function getDatabaseClient(database?: DatabaseClient): DatabaseClient {
return database ?? db;
@@ -155,25 +158,32 @@ function hasAcceptedTrackingValue(touch: ImpactAttributionTouch): boolean {
}
function isTouchValidAtConversion(touch: ImpactAttributionTouch, convertedAt: Date): boolean {
+ const touchedAt = new Date(touch.touched_at).getTime();
+ const convertedAtMs = convertedAt.getTime();
+ // Qualification intentionally ignores the denormalized expires_at column: the referral
+ // spec defines validity as touched_at + 30 * 24h using server UTC timestamps.
+ const exactExpiration = touchedAt + IMPACT_REFERRAL_TOUCH_VALIDITY_MS;
return (
- hasAcceptedTrackingValue(touch) &&
- new Date(touch.touched_at).getTime() <= convertedAt.getTime() &&
- convertedAt.getTime() < new Date(touch.expires_at).getTime()
+ hasAcceptedTrackingValue(touch) && touchedAt <= convertedAtMs && convertedAtMs < exactExpiration
);
}
export function resolveWinningAttributionTouch(params: {
+ product?: ImpactReferralProduct | null;
touches: ImpactAttributionTouch[];
convertedAt: Date;
}): WinningAttributionResolution {
- const validReferralTouches = params.touches
+ const scopedTouches = params.product
+ ? params.touches.filter(touch => touch.product === params.product)
+ : params.touches;
+ const validReferralTouches = scopedTouches
.filter(
touch =>
touch.touch_type === ImpactAttributionTouchType.Referral &&
isTouchValidAtConversion(touch, params.convertedAt)
)
.sort((a, b) => new Date(a.touched_at).getTime() - new Date(b.touched_at).getTime());
- const validAffiliateTouches = params.touches
+ const validAffiliateTouches = scopedTouches
.filter(
touch =>
touch.touch_type === ImpactAttributionTouchType.Affiliate &&
@@ -990,7 +1000,7 @@ export async function processQueuedKiloClawReferralRewards(params?: {
return summary;
}
-async function queueImpactAdvocateRewardRedemption(params: {
+export async function queueImpactAdvocateRewardRedemption(params: {
rewardId: string;
database: DatabaseClient;
}): Promise {
@@ -999,6 +1009,7 @@ async function queueImpactAdvocateRewardRedemption(params: {
id: impact_referral_rewards.id,
beneficiaryUserId: impact_referral_rewards.beneficiary_user_id,
monthsGranted: impact_referral_rewards.months_granted,
+ rewardAmountUsd: impact_referral_rewards.reward_amount_usd,
status: impact_referral_rewards.status,
product: impact_referral_rewards.product,
rewardKind: impact_referral_rewards.reward_kind,
@@ -1009,18 +1020,40 @@ async function queueImpactAdvocateRewardRedemption(params: {
.where(eq(impact_referral_rewards.id, params.rewardId))
.limit(1);
+ if (!reward) {
+ return;
+ }
+
+ let programKey: ImpactAdvocateProgramKey | null = null;
+ let amount: number | null = null;
+ let unit: string | null = null;
+
if (
- !reward ||
- reward.status !== KiloClawReferralRewardStatus.Applied ||
- reward.product !== ImpactReferralProduct.KiloClaw ||
- reward.rewardKind !== ImpactReferralRewardKind.KiloClawFreeMonth
+ reward.status === KiloClawReferralRewardStatus.Applied &&
+ reward.product === ImpactReferralProduct.KiloClaw &&
+ reward.rewardKind === ImpactReferralRewardKind.KiloClawFreeMonth
) {
+ amount = reward.monthsGranted;
+ unit = IMPACT_ADVOCATE_KILOCLAW_REWARD_UNIT;
+ } else if (
+ reward.product === ImpactReferralProduct.KiloPass &&
+ reward.rewardKind === ImpactReferralRewardKind.KiloPassBonus &&
+ (reward.status === ImpactReferralRewardStatus.Pending ||
+ reward.status === ImpactReferralRewardStatus.Earned ||
+ reward.status === ImpactReferralRewardStatus.Applied) &&
+ reward.rewardAmountUsd !== null &&
+ reward.rewardAmountUsd > 0
+ ) {
+ programKey = ImpactAdvocateProgramKey.KiloPass;
+ amount = reward.rewardAmountUsd;
+ unit = IMPACT_ADVOCATE_KILO_PASS_REWARD_UNIT;
+ } else {
return;
}
const accountId = reward.email.trim();
if (!accountId) {
- console.error('[kiloclaw-referrals] missing beneficiary email for Impact reward redemption', {
+ console.error('[impact-referrals] missing beneficiary email for Impact reward redemption', {
rewardId: params.rewardId,
beneficiaryUserId: reward.beneficiaryUserId,
});
@@ -1035,14 +1068,15 @@ async function queueImpactAdvocateRewardRedemption(params: {
beneficiary_user_id: reward.beneficiaryUserId,
state: ImpactAdvocateRewardRedemptionState.Queued,
request_payload: {
+ ...(programKey ? { programKey } : {}),
lookup: {
accountId,
userId: accountId,
rewardTypeFilter: 'CREDIT',
},
redemption: {
- amount: reward.monthsGranted,
- unit: IMPACT_ADVOCATE_REWARD_UNIT,
+ amount,
+ unit,
},
} satisfies Record,
})
@@ -1050,6 +1084,7 @@ async function queueImpactAdvocateRewardRedemption(params: {
}
type ImpactAdvocateRewardRedemptionRequestPayload = {
+ programKey?: ImpactAdvocateProgramKey;
lookup: {
accountId: string;
userId: string;
@@ -1077,11 +1112,15 @@ function isRewardRedemptionRequestPayload(
): payload is ImpactAdvocateRewardRedemptionRequestPayload {
const lookup = getObjectProperty(payload, 'lookup');
const redemption = getObjectProperty(payload, 'redemption');
+ const programKey = getObjectProperty(payload, 'programKey');
return (
typeof lookup === 'object' &&
lookup !== null &&
typeof redemption === 'object' &&
redemption !== null &&
+ (programKey === undefined ||
+ programKey === ImpactAdvocateProgramKey.KiloClaw ||
+ programKey === ImpactAdvocateProgramKey.KiloPass) &&
typeof getObjectProperty(lookup, 'accountId') === 'string' &&
typeof getObjectProperty(lookup, 'userId') === 'string' &&
getObjectProperty(lookup, 'rewardTypeFilter') === 'CREDIT' &&
@@ -1090,6 +1129,12 @@ function isRewardRedemptionRequestPayload(
);
}
+function getRewardRedemptionProgramKey(
+ payload: ImpactAdvocateRewardRedemptionRequestPayload
+): ImpactAdvocateProgramKey {
+ return payload.programKey ?? ImpactAdvocateProgramKey.KiloClaw;
+}
+
function buildFailurePayload(result: ImpactAdvocateDispatchResult): Record {
return {
failureKind: result.ok ? null : result.failureKind,
@@ -1162,9 +1207,13 @@ async function dispatchImpactAdvocateRewardRedemptionById(
return 'failed';
}
- const lookupResult = await sendImpactAdvocateRewardLookupPayload(
- redemption.request_payload.lookup
- );
+ const programKey = getRewardRedemptionProgramKey(redemption.request_payload);
+ const advocateScope =
+ programKey === ImpactAdvocateProgramKey.KiloClaw ? undefined : { programKey };
+
+ const lookupResult = advocateScope
+ ? await sendImpactAdvocateRewardLookupPayload(redemption.request_payload.lookup, advocateScope)
+ : await sendImpactAdvocateRewardLookupPayload(redemption.request_payload.lookup);
if (!lookupResult.ok) {
return await persistRewardRedemptionFailure({
redemptionId: redemption.id,
@@ -1212,10 +1261,13 @@ async function dispatchImpactAdvocateRewardRedemptionById(
.where(eq(impact_advocate_reward_redemptions.id, redemption.id));
}
- const redeemResult = await sendImpactAdvocateRewardRedemptionPayload({
+ const redemptionPayload = {
rewardId: impactRewardId,
...redemption.request_payload.redemption,
- });
+ };
+ const redeemResult = advocateScope
+ ? await sendImpactAdvocateRewardRedemptionPayload(redemptionPayload, advocateScope)
+ : await sendImpactAdvocateRewardRedemptionPayload(redemptionPayload);
const isIdempotentAlreadyRedeemed =
!redeemResult.ok &&
persistedImpactRewardId === impactRewardId &&
@@ -1254,10 +1306,49 @@ async function dispatchImpactAdvocateRewardRedemptionById(
return 'redeemed';
}
+async function queueMissingImpactAdvocateRewardRedemptions(limit: number): Promise {
+ const rows = await db
+ .select({ id: impact_referral_rewards.id })
+ .from(impact_referral_rewards)
+ .where(
+ and(
+ or(
+ and(
+ eq(impact_referral_rewards.product, ImpactReferralProduct.KiloClaw),
+ eq(impact_referral_rewards.reward_kind, ImpactReferralRewardKind.KiloClawFreeMonth),
+ eq(impact_referral_rewards.status, ImpactReferralRewardStatus.Applied)
+ ),
+ and(
+ eq(impact_referral_rewards.product, ImpactReferralProduct.KiloPass),
+ eq(impact_referral_rewards.reward_kind, ImpactReferralRewardKind.KiloPassBonus),
+ inArray(impact_referral_rewards.status, [
+ ImpactReferralRewardStatus.Pending,
+ ImpactReferralRewardStatus.Earned,
+ ImpactReferralRewardStatus.Applied,
+ ]),
+ sql`${impact_referral_rewards.reward_amount_usd} > 0`
+ )
+ ),
+ sql`NOT EXISTS (
+ SELECT 1
+ FROM ${impact_advocate_reward_redemptions}
+ WHERE ${impact_advocate_reward_redemptions.reward_id} = ${impact_referral_rewards.id}
+ )`
+ )
+ )
+ .orderBy(asc(impact_referral_rewards.earned_at), asc(impact_referral_rewards.created_at))
+ .limit(limit);
+
+ for (const row of rows) {
+ await queueImpactAdvocateRewardRedemption({ rewardId: row.id, database: db });
+ }
+}
+
export async function dispatchQueuedImpactAdvocateRewardRedemptions(params?: {
limit?: number;
}): Promise {
const limit = params?.limit ?? 100;
+ await queueMissingImpactAdvocateRewardRedemptions(limit);
const nowIso = new Date().toISOString();
const rows = await db
.update(impact_advocate_reward_redemptions)
@@ -1584,7 +1675,7 @@ async function persistImpactConversionReportResult(params: {
.where(eq(impact_conversion_reports.id, params.reportId));
}
-async function dispatchImpactConversionReportById(
+export async function dispatchImpactConversionReportById(
reportId: string
): Promise<'delivered' | 'retried' | 'failed'> {
logImpactReferralDebug('Dispatching Impact referral conversion report', {
@@ -1847,6 +1938,7 @@ export async function processPersonalKiloClawPaidConversion(params: {
database: tx,
});
const resolution = resolveWinningAttributionTouch({
+ product: ImpactReferralProduct.KiloClaw,
touches,
convertedAt: params.convertedAt,
});
diff --git a/apps/web/src/lib/impact/referral-utils.test.ts b/apps/web/src/lib/impact/referral-utils.test.ts
index 54898c23e6..9cebefaa28 100644
--- a/apps/web/src/lib/impact/referral-utils.test.ts
+++ b/apps/web/src/lib/impact/referral-utils.test.ts
@@ -49,6 +49,8 @@ describe('impact referral utils', () => {
);
expect(touch).toEqual({
+ product: 'kiloclaw',
+ programKey: 'kiloclaw',
opaqueTrackingValue: 'sq-cookie',
trackingValueLength: 9,
isTrackingValueAccepted: true,
@@ -67,6 +69,16 @@ describe('impact referral utils', () => {
});
});
+ it('marks Kilo Pass callback paths as Kilo Pass referral touches', () => {
+ const touch = parseImpactReferralTouchFromUrl(
+ 'https://kilo.ai/users/after-sign-in?callbackPath=%2Fsubscriptions%2Fkilo-pass&_saasquatch=pass-cookie'
+ );
+
+ expect(touch?.product).toBe('kilo_pass');
+ expect(touch?.programKey).toBe('kilo_pass');
+ expect(touch?.opaqueTrackingValue).toBe('pass-cookie');
+ });
+
it('keeps referral metadata for diagnostics when _saasquatch is missing', () => {
const touch = parseImpactReferralTouchFromUrl(
'https://kilo.ai/get-started?rsCode=abc&rsShareMedium=email'
@@ -78,11 +90,43 @@ describe('impact referral utils', () => {
expect(touch?.rsCode).toBe('abc');
});
+ it('ignores over-limit referral values for attribution and metadata', () => {
+ const tooLongValue = 'x'.repeat(IMPACT_OPAQUE_TRACKING_VALUE_MAX_LENGTH + 1);
+ const touch = parseImpactReferralTouchFromUrl(
+ `https://kilo.ai/get-started?_saasquatch=${tooLongValue}&rsCode=${tooLongValue}&rsShareMedium=email`
+ );
+
+ expect(touch?.opaqueTrackingValue).toBeNull();
+ expect(touch?.trackingValueLength).toBe(tooLongValue.length);
+ expect(touch?.isTrackingValueAccepted).toBe(false);
+ expect(touch?.rsCode).toBeNull();
+ expect(touch?.rsShareMedium).toBe('email');
+ });
+
it('parses affiliate touches from im_ref and override cookies', () => {
const fromQuery = parseImpactAffiliateTouchFromUrl('https://kilo.ai/?im_ref=impact-click');
expect(fromQuery?.trackingId).toBe('impact-click');
+ expect(fromQuery?.product).toBe('kiloclaw');
const fromCookie = parseImpactAffiliateTouchFromUrl('https://kilo.ai/', 'impact-cookie-click');
expect(fromCookie?.trackingId).toBe('impact-cookie-click');
});
+
+ it('marks Kilo Pass callback paths as Kilo Pass affiliate touches', () => {
+ const touch = parseImpactAffiliateTouchFromUrl(
+ 'https://kilo.ai/users/after-sign-in?callbackPath=%2Fsubscriptions%2Fkilo-pass&im_ref=impact-click'
+ );
+
+ expect(touch?.product).toBe('kilo_pass');
+ expect(touch?.trackingId).toBe('impact-click');
+ });
+
+ it('ignores over-limit affiliate values for attribution', () => {
+ const tooLongValue = 'x'.repeat(IMPACT_OPAQUE_TRACKING_VALUE_MAX_LENGTH + 1);
+ const touch = parseImpactAffiliateTouchFromUrl(`https://kilo.ai/?im_ref=${tooLongValue}`);
+
+ expect(touch?.trackingId).toBeNull();
+ expect(touch?.trackingValueLength).toBe(tooLongValue.length);
+ expect(touch?.isTrackingValueAccepted).toBe(false);
+ });
});
diff --git a/apps/web/src/lib/impact/referral-utils.ts b/apps/web/src/lib/impact/referral-utils.ts
index e557d90b88..c44c770b74 100644
--- a/apps/web/src/lib/impact/referral-utils.ts
+++ b/apps/web/src/lib/impact/referral-utils.ts
@@ -1,3 +1,5 @@
+import { ImpactAdvocateProgramKey, ImpactReferralProduct } from '@kilocode/db/schema-types';
+
export const IMPACT_OPAQUE_TRACKING_VALUE_MAX_LENGTH = 512;
export const IMPACT_REFERRAL_TOUCH_VALIDITY_MS = 30 * 24 * 60 * 60 * 1000;
export const IMPACT_CUSTOM_PROFILE_ID_STORAGE_KEY = 'impact_custom_profile_id';
@@ -9,6 +11,8 @@ export type SanitizedOpaqueTrackingValue = {
};
export type ParsedImpactReferralTouch = {
+ product?: ImpactReferralProduct;
+ programKey?: ImpactAdvocateProgramKey;
opaqueTrackingValue: string | null;
trackingValueLength: number;
isTrackingValueAccepted: boolean;
@@ -26,6 +30,7 @@ export type ParsedImpactReferralTouch = {
};
export type ParsedImpactAffiliateTouch = {
+ product?: ImpactReferralProduct;
trackingId: string | null;
trackingValueLength: number;
isTrackingValueAccepted: boolean;
@@ -57,6 +62,38 @@ function landingPathFromUrl(url: URL): string | null {
return path ? path : null;
}
+function pathTargetsKiloPass(path: string | null | undefined): boolean {
+ const pathname = path?.split(/[?#]/, 1)[0];
+ return (
+ pathname === '/subscriptions/kilo-pass' ||
+ Boolean(pathname?.startsWith('/subscriptions/kilo-pass/'))
+ );
+}
+
+function getCallbackPath(url: URL): string | null {
+ const callbackPath = url.searchParams.get('callbackPath')?.trim();
+ if (!callbackPath?.startsWith('/')) return null;
+ return callbackPath;
+}
+
+function resolveImpactTouchScope(url: URL): {
+ product: ImpactReferralProduct;
+ programKey: ImpactAdvocateProgramKey;
+} {
+ const callbackPath = getCallbackPath(url);
+ if (pathTargetsKiloPass(url.pathname) || pathTargetsKiloPass(callbackPath)) {
+ return {
+ product: ImpactReferralProduct.KiloPass,
+ programKey: ImpactAdvocateProgramKey.KiloPass,
+ };
+ }
+
+ return {
+ product: ImpactReferralProduct.KiloClaw,
+ programKey: ImpactAdvocateProgramKey.KiloClaw,
+ };
+}
+
export function sanitizeOpaqueTrackingValue(
value: string | null | undefined
): SanitizedOpaqueTrackingValue {
@@ -131,8 +168,10 @@ export function parseImpactReferralTouchFromUrl(
}
const trackingValue = sanitizeOpaqueTrackingValue(searchParams.get('_saasquatch'));
+ const scope = resolveImpactTouchScope(url);
return {
+ ...scope,
opaqueTrackingValue: trackingValue.acceptedValue,
trackingValueLength: trackingValue.originalLength,
isTrackingValueAccepted: trackingValue.isAccepted,
@@ -166,7 +205,10 @@ export function parseImpactAffiliateTouchFromUrl(
return null;
}
+ const scope = resolveImpactTouchScope(url);
+
return {
+ product: scope.product,
trackingId: trackingValue.acceptedValue,
trackingValueLength: trackingValue.originalLength,
isTrackingValueAccepted: trackingValue.isAccepted,
diff --git a/apps/web/src/lib/impact/referral.test.ts b/apps/web/src/lib/impact/referral.test.ts
index cbc51c841c..f0c90751ae 100644
--- a/apps/web/src/lib/impact/referral.test.ts
+++ b/apps/web/src/lib/impact/referral.test.ts
@@ -9,6 +9,7 @@ import { insertTestUser } from '@/tests/helpers/user.helper';
import {
impact_advocate_participants,
impact_advocate_registration_attempts,
+ impact_attribution_touches,
kilocode_users,
} from '@kilocode/db/schema';
@@ -19,14 +20,18 @@ describe('impact referral participant registration dispatch', () => {
process.env.IMPACT_ACCOUNT_SID = 'impact-account-sid';
process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'impact-advocate-account-sid';
process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'impact-advocate-auth-token';
- process.env.IMPACT_ADVOCATE_PROGRAM_ID = '51699';
process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'tenant-alias';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_PROGRAM_ID = '51699';
+ process.env.IMPACT_ADVOCATE_KILOCLAW_WIDGET_ID = 'p/51699/w/referrerWidget';
+ delete process.env.IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID;
+ delete process.env.IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID;
});
afterEach(async () => {
jest.restoreAllMocks();
await db.delete(impact_advocate_registration_attempts).where(sql`true`);
await db.delete(impact_advocate_participants).where(sql`true`);
+ await db.delete(impact_attribution_touches).where(sql`true`);
await db.delete(kilocode_users).where(sql`true`);
});
@@ -131,6 +136,220 @@ describe('impact referral participant registration dispatch', () => {
});
});
+ it('delivers Kilo Pass participant registrations through Kilo Pass-scoped config', async () => {
+ process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'kilo-pass-account-sid';
+ process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'kilo-pass-auth-token';
+ process.env.IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID = '52766';
+ process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'kilo-pass-tenant';
+ process.env.IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID = 'p/52766/w/referrerWidget';
+
+ const fetchMock = jest.fn().mockResolvedValue(
+ new Response(
+ JSON.stringify({
+ id: 'sq-kilo-pass-id',
+ email: 'pass-participant@example.com',
+ referralCodes: { '52766': 'PASS9001' },
+ referable: true,
+ }),
+ { status: 200 }
+ )
+ );
+ global.fetch = fetchMock;
+
+ const user = await insertTestUser({
+ google_user_email: 'pass-participant@example.com',
+ normalized_email: 'pass-participant@example.com',
+ });
+
+ const {
+ dispatchQueuedImpactAdvocateRegistrationAttempts,
+ queueImpactAdvocateParticipantRegistration,
+ } = await import('@/lib/impact/referral');
+
+ await queueImpactAdvocateParticipantRegistration({
+ programKey: 'kilo_pass',
+ user,
+ referralTouch: {
+ opaqueTrackingValue: 'pass-sq-cookie',
+ trackingValueLength: 14,
+ isTrackingValueAccepted: true,
+ rsCode: 'pass-ref-code',
+ rsShareMedium: 'email',
+ rsEngagementMedium: 'link',
+ landingPath: '/subscriptions/kilo-pass?_saasquatch=pass-sq-cookie',
+ utmSource: null,
+ utmMedium: null,
+ utmCampaign: null,
+ utmTerm: null,
+ utmContent: null,
+ touchedAt: new Date('2026-05-23T00:00:00.000Z'),
+ expiresAt: new Date('2026-06-22T00:00:00.000Z'),
+ },
+ });
+
+ const summary = await dispatchQueuedImpactAdvocateRegistrationAttempts();
+ expect(summary).toEqual({ claimed: 1, delivered: 1, retried: 0, failed: 0 });
+
+ const [participant] = await db.select().from(impact_advocate_participants);
+ expect(participant.program_key).toBe('kilo_pass');
+ expect(participant.registration_state).toBe('registered');
+ expect(participant.opaque_referral_identifier).toBe('PASS9001');
+
+ const [attempt] = await db.select().from(impact_advocate_registration_attempts);
+ expect(attempt.program_key).toBe('kilo_pass');
+ expect(attempt.delivery_state).toBe('succeeded');
+
+ expect(fetchMock.mock.calls[0]?.[0]).toBe(
+ 'https://app.referralsaasquatch.com/api/v1/kilo-pass-tenant/open/account/pass-participant%40example.com/user/pass-participant%40example.com'
+ );
+ expect(fetchMock.mock.calls[0]?.[1]).toMatchObject({
+ headers: expect.objectContaining({
+ Authorization:
+ 'Basic ' + Buffer.from('kilo-pass-account-sid:kilo-pass-auth-token').toString('base64'),
+ }),
+ });
+ });
+
+ it('allows the same SaaSquatch referral code in different Advocate programs', async () => {
+ process.env.IMPACT_ADVOCATE_ACCOUNT_SID = 'kilo-pass-account-sid';
+ process.env.IMPACT_ADVOCATE_AUTH_TOKEN = 'kilo-pass-auth-token';
+ process.env.IMPACT_ADVOCATE_KILO_PASS_PROGRAM_ID = '52766';
+ process.env.IMPACT_ADVOCATE_TENANT_ALIAS = 'kilo-pass-tenant';
+ process.env.IMPACT_ADVOCATE_KILO_PASS_WIDGET_ID = 'p/52766/w/referrerWidget';
+
+ const clawUser = await insertTestUser({
+ google_user_email: 'claw-holder@example.com',
+ normalized_email: 'claw-holder@example.com',
+ });
+ await db.insert(impact_advocate_participants).values({
+ program_key: 'kiloclaw',
+ user_id: clawUser.id,
+ advocate_id: clawUser.google_user_email,
+ advocate_account_id: clawUser.google_user_email,
+ opaque_referral_identifier: 'SHARED_CODE',
+ registration_state: 'registered',
+ });
+
+ const fetchMock = jest
+ .fn()
+ .mockResolvedValue(
+ new Response(JSON.stringify({ referralCodes: { '52766': 'SHARED_CODE' } }), { status: 200 })
+ );
+ global.fetch = fetchMock;
+
+ const passUser = await insertTestUser({
+ google_user_email: 'pass-holder@example.com',
+ normalized_email: 'pass-holder@example.com',
+ });
+
+ const {
+ dispatchQueuedImpactAdvocateRegistrationAttempts,
+ queueImpactAdvocateParticipantRegistration,
+ } = await import('@/lib/impact/referral');
+
+ await queueImpactAdvocateParticipantRegistration({
+ programKey: 'kilo_pass',
+ user: passUser,
+ referralTouch: {
+ opaqueTrackingValue: 'pass-cookie',
+ trackingValueLength: 11,
+ isTrackingValueAccepted: true,
+ rsCode: null,
+ rsShareMedium: null,
+ rsEngagementMedium: null,
+ landingPath: '/subscriptions/kilo-pass',
+ utmSource: null,
+ utmMedium: null,
+ utmCampaign: null,
+ utmTerm: null,
+ utmContent: null,
+ touchedAt: new Date('2026-05-23T00:00:00.000Z'),
+ expiresAt: new Date('2026-06-22T00:00:00.000Z'),
+ },
+ });
+
+ await dispatchQueuedImpactAdvocateRegistrationAttempts();
+
+ const passParticipant = await db.query.impact_advocate_participants.findFirst({
+ where: eq(impact_advocate_participants.user_id, passUser.id),
+ });
+ expect(passParticipant?.program_key).toBe('kilo_pass');
+ expect(passParticipant?.opaque_referral_identifier).toBe('SHARED_CODE');
+ });
+
+ it('records Kilo Pass-scoped affiliate and referral touches without KiloClaw defaults', async () => {
+ const user = await insertTestUser({
+ google_user_email: 'kilo-pass-touch@example.com',
+ normalized_email: 'kilo-pass-touch@example.com',
+ });
+
+ const { recordImpactAffiliateTouch, recordImpactReferralTouch } =
+ await import('@/lib/impact/referral');
+
+ await recordImpactAffiliateTouch({
+ product: 'kilo_pass',
+ userId: user.id,
+ touch: {
+ product: 'kilo_pass',
+ trackingId: 'impact-click-pass',
+ trackingValueLength: 17,
+ isTrackingValueAccepted: true,
+ landingPath: '/subscriptions/kilo-pass?im_ref=impact-click-pass',
+ utmSource: 'impact',
+ utmMedium: null,
+ utmCampaign: null,
+ utmTerm: null,
+ utmContent: null,
+ touchedAt: new Date('2026-05-23T00:00:00.000Z'),
+ expiresAt: new Date('2026-06-22T00:00:00.000Z'),
+ },
+ });
+ await recordImpactReferralTouch({
+ userId: user.id,
+ touch: {
+ product: 'kilo_pass',
+ programKey: 'kilo_pass',
+ opaqueTrackingValue: 'pass-cookie',
+ trackingValueLength: 11,
+ isTrackingValueAccepted: true,
+ rsCode: 'PASSCODE',
+ rsShareMedium: 'email',
+ rsEngagementMedium: 'link',
+ landingPath: '/subscriptions/kilo-pass?_saasquatch=pass-cookie&rsCode=PASSCODE',
+ utmSource: null,
+ utmMedium: null,
+ utmCampaign: null,
+ utmTerm: null,
+ utmContent: null,
+ touchedAt: new Date('2026-05-23T00:01:00.000Z'),
+ expiresAt: new Date('2026-06-22T00:01:00.000Z'),
+ },
+ });
+
+ const touches = await db
+ .select()
+ .from(impact_attribution_touches)
+ .orderBy(impact_attribution_touches.touch_type);
+ expect(touches).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ product: 'kilo_pass',
+ program_key: null,
+ touch_type: 'affiliate',
+ provider: 'impact_performance',
+ im_ref: 'impact-click-pass',
+ }),
+ expect.objectContaining({
+ product: 'kilo_pass',
+ program_key: 'kilo_pass',
+ touch_type: 'referral',
+ provider: 'impact_advocate',
+ rs_code: 'PASSCODE',
+ }),
+ ])
+ );
+ });
+
it('keeps transient failures retryable until a later dispatch succeeds', async () => {
const fetchMock = jest
.fn()
diff --git a/apps/web/src/lib/impact/referral.ts b/apps/web/src/lib/impact/referral.ts
index ca544a4893..af8f715be5 100644
--- a/apps/web/src/lib/impact/referral.ts
+++ b/apps/web/src/lib/impact/referral.ts
@@ -29,6 +29,7 @@ import {
ImpactAdvocateRegistrationState,
ImpactAttributionTouchProvider,
ImpactAttributionTouchType,
+ ImpactReferralProduct,
} from '@kilocode/db/schema-types';
import { and, asc, eq, lte, ne, or, sql } from 'drizzle-orm';
@@ -52,6 +53,28 @@ function getDatabaseClient(database?: DatabaseClient): DatabaseClient {
return database ?? db;
}
+function productForProgramKey(programKey: ImpactAdvocateProgramKey): ImpactReferralProduct {
+ return programKey === ImpactAdvocateProgramKey.KiloPass
+ ? ImpactReferralProduct.KiloPass
+ : ImpactReferralProduct.KiloClaw;
+}
+
+function resolveReferralProgramKey(params: {
+ product?: ImpactReferralProduct | null;
+ programKey?: ImpactAdvocateProgramKey | null;
+}): ImpactAdvocateProgramKey {
+ if (params.programKey) return params.programKey;
+ if (params.product === ImpactReferralProduct.KiloPass) return ImpactAdvocateProgramKey.KiloPass;
+ return ImpactAdvocateProgramKey.KiloClaw;
+}
+
+function resolveReferralProduct(params: {
+ product?: ImpactReferralProduct | null;
+ programKey?: ImpactAdvocateProgramKey | null;
+}): ImpactReferralProduct {
+ return params.product ?? productForProgramKey(resolveReferralProgramKey(params));
+}
+
function buildHashedDedupeKey(parts: Array): string {
const normalized = parts.map(part => part?.trim() ?? '').join('|');
return createHash('sha256').update(normalized, 'utf8').digest('hex');
@@ -93,12 +116,15 @@ export function hashNormalizedEmailForDeletionTombstone(normalizedEmail: string)
export async function recordImpactAffiliateTouch(params: {
database?: DatabaseClient;
+ product?: ImpactReferralProduct | null;
userId?: string | null;
anonymousId?: string | null;
touch: ParsedImpactAffiliateTouch;
}): Promise {
const database = getDatabaseClient(params.database);
+ const product = resolveReferralProduct({ product: params.product ?? params.touch.product });
const dedupeKey = buildHashedDedupeKey([
+ product,
touchIdentity(params),
ImpactAttributionTouchType.Affiliate,
ImpactAttributionTouchProvider.ImpactPerformance,
@@ -111,6 +137,8 @@ export async function recordImpactAffiliateTouch(params: {
.insert(impact_attribution_touches)
.values({
dedupe_key: dedupeKey,
+ product,
+ program_key: null,
anonymous_id: params.anonymousId ?? null,
user_id: params.userId ?? null,
touch_type: ImpactAttributionTouchType.Affiliate,
@@ -148,12 +176,24 @@ export async function recordImpactAffiliateTouch(params: {
export async function recordImpactReferralTouch(params: {
database?: DatabaseClient;
+ product?: ImpactReferralProduct | null;
+ programKey?: ImpactAdvocateProgramKey | null;
userId?: string | null;
anonymousId?: string | null;
touch: ParsedImpactReferralTouch;
}): Promise {
const database = getDatabaseClient(params.database);
+ const programKey = resolveReferralProgramKey({
+ product: params.product ?? params.touch.product ?? null,
+ programKey: params.programKey ?? params.touch.programKey ?? null,
+ });
+ const product = resolveReferralProduct({
+ product: params.product ?? params.touch.product ?? null,
+ programKey,
+ });
const dedupeKey = buildHashedDedupeKey([
+ product,
+ programKey,
touchIdentity(params),
ImpactAttributionTouchType.Referral,
ImpactAttributionTouchProvider.ImpactAdvocate,
@@ -167,6 +207,8 @@ export async function recordImpactReferralTouch(params: {
.insert(impact_attribution_touches)
.values({
dedupe_key: dedupeKey,
+ product,
+ program_key: programKey,
anonymous_id: params.anonymousId ?? null,
user_id: params.userId ?? null,
touch_type: ImpactAttributionTouchType.Referral,
@@ -207,18 +249,21 @@ export async function recordImpactReferralTouch(params: {
export async function ensureImpactAdvocateParticipantProfile(params: {
database?: DatabaseClient;
+ programKey?: ImpactAdvocateProgramKey | null;
user: Pick;
locale?: string | null;
countryCode?: string | null;
opaqueReferralIdentifier?: string | null;
}): Promise<{ id: string }> {
const database = getDatabaseClient(params.database);
+ const programKey = params.programKey ?? ImpactAdvocateProgramKey.KiloClaw;
- const isConfigured = isImpactAdvocateConfigured();
+ const isConfigured = isImpactAdvocateConfigured({ programKey });
const [insertedParticipant] = await database
.insert(impact_advocate_participants)
.values({
+ program_key: programKey,
user_id: params.user.id,
advocate_id: params.user.google_user_email,
advocate_account_id: params.user.google_user_email,
@@ -241,7 +286,7 @@ export async function ensureImpactAdvocateParticipantProfile(params: {
insertedParticipant ??
(await database.query.impact_advocate_participants.findFirst({
where: and(
- eq(impact_advocate_participants.program_key, ImpactAdvocateProgramKey.KiloClaw),
+ eq(impact_advocate_participants.program_key, programKey),
eq(impact_advocate_participants.user_id, params.user.id)
),
columns: { id: true },
@@ -270,6 +315,8 @@ export async function ensureImpactAdvocateParticipantProfile(params: {
export async function queueImpactAdvocateParticipantRegistration(params: {
database?: DatabaseClient;
+ product?: ImpactReferralProduct | null;
+ programKey?: ImpactAdvocateProgramKey | null;
user: Pick;
referralTouch: ParsedImpactReferralTouch;
locale?: string | null;
@@ -287,6 +334,10 @@ export async function queueImpactAdvocateParticipantRegistration(params: {
}
const database = getDatabaseClient(params.database);
+ const programKey = resolveReferralProgramKey({
+ product: params.product ?? params.referralTouch.product ?? null,
+ programKey: params.programKey ?? params.referralTouch.programKey ?? null,
+ });
const payload = buildImpactAdvocateRegisterParticipantPayload({
user: params.user,
referralCookieValue: params.referralTouch.opaqueTrackingValue,
@@ -294,9 +345,10 @@ export async function queueImpactAdvocateParticipantRegistration(params: {
countryCode: params.countryCode,
});
const nowIso = new Date().toISOString();
- const isConfigured = isImpactAdvocateConfigured();
+ const isConfigured = isImpactAdvocateConfigured({ programKey });
const participant = await ensureImpactAdvocateParticipantProfile({
database,
+ programKey,
user: params.user,
locale: params.locale,
countryCode: params.countryCode,
@@ -304,6 +356,7 @@ export async function queueImpactAdvocateParticipantRegistration(params: {
const attemptDedupeKey = buildHashedDedupeKey([
'impact-advocate-registration',
+ programKey,
params.user.id,
params.referralTouch.opaqueTrackingValue,
]);
@@ -311,6 +364,7 @@ export async function queueImpactAdvocateParticipantRegistration(params: {
const [insertedAttempt] = await database
.insert(impact_advocate_registration_attempts)
.values({
+ program_key: programKey,
participant_id: participant.id,
dedupe_key: attemptDedupeKey,
opaque_cookie_value: params.referralTouch.opaqueTrackingValue,
@@ -335,6 +389,7 @@ export async function queueImpactAdvocateParticipantRegistration(params: {
userId: params.user.id,
participantId: participant.id,
attemptId: insertedAttempt?.id ?? null,
+ programKey,
impactAdvocateConfigured: isConfigured,
trackingValueLength: params.referralTouch.trackingValueLength,
localePresent: Boolean(params.locale?.trim()),
@@ -380,12 +435,18 @@ export async function queueImpactAdvocateParticipantRegistration(params: {
*/
export async function queueImpactAdvocateSelfRegistration(params: {
database?: DatabaseClient;
+ product?: ImpactReferralProduct | null;
+ programKey?: ImpactAdvocateProgramKey | null;
user: Pick;
locale?: string | null;
countryCode?: string | null;
}): Promise {
const database = getDatabaseClient(params.database);
- const isConfigured = isImpactAdvocateConfigured();
+ const programKey = resolveReferralProgramKey({
+ product: params.product ?? null,
+ programKey: params.programKey ?? null,
+ });
+ const isConfigured = isImpactAdvocateConfigured({ programKey });
const nowIso = new Date().toISOString();
// Empty cookie envelope — advocate-only users have no inbound attribution.
@@ -401,6 +462,7 @@ export async function queueImpactAdvocateSelfRegistration(params: {
const participant = await ensureImpactAdvocateParticipantProfile({
database,
+ programKey,
user: params.user,
locale: params.locale,
countryCode: params.countryCode,
@@ -426,12 +488,14 @@ export async function queueImpactAdvocateSelfRegistration(params: {
const attemptDedupeKey = buildHashedDedupeKey([
'impact-advocate-self-registration',
+ programKey,
params.user.id,
]);
const [insertedAttempt] = await database
.insert(impact_advocate_registration_attempts)
.values({
+ program_key: programKey,
participant_id: participant.id,
dedupe_key: attemptDedupeKey,
opaque_cookie_value: null,
@@ -456,6 +520,7 @@ export async function queueImpactAdvocateSelfRegistration(params: {
userId: params.user.id,
participantId: participant.id,
attemptId: insertedAttempt?.id ?? null,
+ programKey,
impactAdvocateConfigured: isConfigured,
localePresent: Boolean(params.locale?.trim()),
countryCode: params.countryCode ?? null,
@@ -580,7 +645,9 @@ async function dispatchImpactAdvocateRegistrationAttemptById(
attemptCount: attempt.attempt_count,
});
- const result = await sendImpactAdvocateRegisterParticipantPayload(payload);
+ const result = await sendImpactAdvocateRegisterParticipantPayload(payload, {
+ programKey: participant.program_key,
+ });
const attemptCount = attempt.attempt_count + 1;
const completedAt = new Date().toISOString();
@@ -603,16 +670,16 @@ async function dispatchImpactAdvocateRegistrationAttemptById(
// (vanishingly unlikely — SaaSquatch issues unique codes per tenant — but
// a violation here would otherwise roll back the whole success transaction
// and put us in a retry loop).
- const programId = getImpactAdvocateProgramId();
- const advocateCode = extractAdvocateReferralCodeFromUpsertResponse(
- result.responseBody,
- programId
- );
+ const programId = getImpactAdvocateProgramId({ programKey: participant.program_key });
+ const advocateCode = programId
+ ? extractAdvocateReferralCodeFromUpsertResponse(result.responseBody, programId)
+ : null;
let advocateCodeToPersist: string | null = null;
if (advocateCode) {
const conflicting = await db.query.impact_advocate_participants.findFirst({
where: and(
+ eq(impact_advocate_participants.program_key, participant.program_key),
eq(impact_advocate_participants.opaque_referral_identifier, advocateCode),
ne(impact_advocate_participants.id, participant.id)
),
diff --git a/apps/web/src/lib/kilo-pass/affiliate-sale.ts b/apps/web/src/lib/kilo-pass/affiliate-sale.ts
index a81f50cfbe..aaf1ff8513 100644
--- a/apps/web/src/lib/kilo-pass/affiliate-sale.ts
+++ b/apps/web/src/lib/kilo-pass/affiliate-sale.ts
@@ -54,7 +54,7 @@ const KILO_PASS_AFFILIATE_SALE_REPORTING = {
Record
>;
-function getKiloPassAffiliateSaleReportingFields(context: KiloPassAffiliateSaleContext) {
+export function getKiloPassAffiliateSaleReportingFields(context: KiloPassAffiliateSaleContext) {
const reportingFields = KILO_PASS_AFFILIATE_SALE_REPORTING[context.tier][context.cadence];
return context.itemSku ? { ...reportingFields, itemSku: context.itemSku } : reportingFields;
}
diff --git a/apps/web/src/lib/kilo-pass/bonus.test.ts b/apps/web/src/lib/kilo-pass/bonus.test.ts
index 42803933b3..64155a8a3d 100644
--- a/apps/web/src/lib/kilo-pass/bonus.test.ts
+++ b/apps/web/src/lib/kilo-pass/bonus.test.ts
@@ -8,7 +8,6 @@ import {
import {
KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT,
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF,
KILO_PASS_MONTHLY_RAMP_BASE_BONUS_PERCENT,
KILO_PASS_MONTHLY_RAMP_CAP_BONUS_PERCENT,
KILO_PASS_MONTHLY_RAMP_STEP_BONUS_PERCENT,
@@ -165,19 +164,12 @@ describe('kilo pass bonus utilities', () => {
});
describe('computeMonthlyCadenceBonusPercent', () => {
- it('keeps the second-month grandfather cutoff at midnight May 7 UTC', () => {
- expect(KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.toISOString()).toBe(
- '2026-05-07T00:00:00.000Z'
- );
- });
-
- it('applies the 50% promo for streak months 1 and 2 when eligible (strictly before cutoff)', () => {
+ it('applies the 50% promo only for first-time subscribers in streak month 1', () => {
expect(
computeMonthlyCadenceBonusPercent({
tier: KiloPassTier.Tier19,
streakMonths: 1,
isFirstTimeSubscriberEver: true,
- subscriptionStartedAtIso: '2026-01-26T23:59:59.000Z',
})
).toBeCloseTo(KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT);
@@ -186,37 +178,14 @@ describe('kilo pass bonus utilities', () => {
tier: KiloPassTier.Tier19,
streakMonths: 2,
isFirstTimeSubscriberEver: true,
- subscriptionStartedAtIso: '2026-01-26T23:59:59.000Z',
- })
- ).toBeCloseTo(KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT);
-
- expect(
- computeMonthlyCadenceBonusPercent({
- tier: KiloPassTier.Tier19,
- streakMonths: 3,
- isFirstTimeSubscriberEver: true,
- subscriptionStartedAtIso: '2026-01-26T23:59:59.000Z',
})
).toBeCloseTo(
KILO_PASS_TIER_CONFIG.tier_19.monthlyBaseBonusPercent +
- KILO_PASS_TIER_CONFIG.tier_19.monthlyStepBonusPercent * 2
+ KILO_PASS_TIER_CONFIG.tier_19.monthlyStepBonusPercent
);
});
- it('applies the first-month promo for first-time subscribers after the grandfather cutoff', () => {
- expect(
- computeMonthlyCadenceBonusPercent({
- tier: KiloPassTier.Tier19,
- streakMonths: 1,
- isFirstTimeSubscriberEver: true,
- subscriptionStartedAtIso: new Date(
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.valueOf() + 1
- ).toISOString(),
- })
- ).toBeCloseTo(KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT);
- });
-
- it('does not apply the override when isFirstTimeSubscriberEver is false', () => {
+ it('does not apply the first-month promo when isFirstTimeSubscriberEver is false', () => {
expect(
computeMonthlyCadenceBonusPercent({
tier: KiloPassTier.Tier49,
@@ -225,57 +194,15 @@ describe('kilo pass bonus utilities', () => {
})
).toBeCloseTo(KILO_PASS_TIER_CONFIG.tier_49.monthlyBaseBonusPercent);
});
- });
-
- describe('computeMonthlyCadenceBonusPercent (promo cutoff behavior)', () => {
- const tier = KiloPassTier.Tier49;
-
- const computeFallback = (params: {
- streakMonths: number;
- isFirstTimeSubscriberEver: boolean;
- }): number => {
- return computeMonthlyCadenceBonusPercent({
- tier,
- streakMonths: params.streakMonths,
- isFirstTimeSubscriberEver: params.isFirstTimeSubscriberEver,
- subscriptionStartedAtIso: KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.toISOString(),
- });
- };
-
- it('applies the first-month promo at the second-month grandfather cutoff', () => {
- expect(
- computeMonthlyCadenceBonusPercent({
- tier,
- streakMonths: 1,
- isFirstTimeSubscriberEver: true,
- subscriptionStartedAtIso: KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.toISOString(),
- })
- ).toBe(KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT);
- });
- it('does not apply the second-month promo at the grandfather cutoff', () => {
- expect(
+ it('throws when streakMonths is below 1', () => {
+ expect(() =>
computeMonthlyCadenceBonusPercent({
- tier,
- streakMonths: 2,
+ tier: KiloPassTier.Tier19,
+ streakMonths: 0,
isFirstTimeSubscriberEver: true,
- subscriptionStartedAtIso: KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.toISOString(),
- })
- ).toBeCloseTo(
- KILO_PASS_TIER_CONFIG.tier_49.monthlyBaseBonusPercent +
- KILO_PASS_TIER_CONFIG.tier_49.monthlyStepBonusPercent
- );
- });
-
- it('does not apply promo when isFirstTimeSubscriberEver is false', () => {
- expect(
- computeMonthlyCadenceBonusPercent({
- tier,
- streakMonths: 1,
- isFirstTimeSubscriberEver: false,
- subscriptionStartedAtIso: '2026-01-26T23:59:59.000Z',
})
- ).toBe(computeFallback({ streakMonths: 1, isFirstTimeSubscriberEver: false }));
+ ).toThrow('streakMonths must be >= 1');
});
});
diff --git a/apps/web/src/lib/kilo-pass/bonus.ts b/apps/web/src/lib/kilo-pass/bonus.ts
index 2d66e5c889..02a9d677ab 100644
--- a/apps/web/src/lib/kilo-pass/bonus.ts
+++ b/apps/web/src/lib/kilo-pass/bonus.ts
@@ -1,12 +1,9 @@
import { KiloPassCadence, type KiloPassTier } from '@/lib/kilo-pass/enums';
import {
KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT,
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_BONUS_PERCENT,
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF,
KILO_PASS_TIER_CONFIG,
KILO_PASS_YEARLY_MONTHLY_BONUS_PERCENT,
} from '@/lib/kilo-pass/constants';
-import { dayjs } from '@/lib/kilo-pass/dayjs';
export const getMonthlyPriceUsd = (tier: KiloPassTier): number => {
return KILO_PASS_TIER_CONFIG[tier].monthlyPriceUsd;
@@ -28,9 +25,8 @@ export const computeMonthlyCadenceBonusPercent = (params: {
tier: KiloPassTier;
streakMonths: number;
isFirstTimeSubscriberEver: boolean;
- subscriptionStartedAtIso?: string | null;
}): number => {
- const { tier, streakMonths, isFirstTimeSubscriberEver, subscriptionStartedAtIso } = params;
+ const { tier, streakMonths, isFirstTimeSubscriberEver } = params;
if (streakMonths < 1) {
throw new Error('streakMonths must be >= 1');
@@ -40,22 +36,6 @@ export const computeMonthlyCadenceBonusPercent = (params: {
return KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT;
}
- // Limited-time grandfathered promo: first-time subscribers who started strictly before the
- // cutoff keep the 50% bonus for streak month 2.
- if (streakMonths === 2 && isFirstTimeSubscriberEver) {
- const startedAt = subscriptionStartedAtIso ?? null;
- if (startedAt != null) {
- const startedAtUtc = dayjs(startedAt).utc();
-
- if (
- startedAtUtc.isValid() &&
- startedAtUtc.isBefore(KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF)
- ) {
- return KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_BONUS_PERCENT;
- }
- }
- }
-
const config = KILO_PASS_TIER_CONFIG[tier];
const nMinus1 = streakMonths - 1;
const uncapped = config.monthlyBaseBonusPercent + config.monthlyStepBonusPercent * nMinus1;
diff --git a/apps/web/src/lib/kilo-pass/constants.ts b/apps/web/src/lib/kilo-pass/constants.ts
index 720573548f..87405fc7fd 100644
--- a/apps/web/src/lib/kilo-pass/constants.ts
+++ b/apps/web/src/lib/kilo-pass/constants.ts
@@ -1,4 +1,3 @@
-import { dayjs } from '@/lib/kilo-pass/dayjs';
export {
KILO_PASS_MONTHLY_RAMP_BASE_BONUS_PERCENT,
KILO_PASS_MONTHLY_RAMP_CAP_BONUS_PERCENT,
@@ -8,9 +7,3 @@ export {
} from '@kilocode/worker-utils/kilo-pass-bonus-projection';
export const KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT = 0.5;
-
-// First-time subscribers receive a 50% bonus for month 2 only if they started
-// strictly before this grandfather cutoff. Month 1 remains 50% for new subscribers.
-export const KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF = dayjs('2026-05-07T00:00:00Z').utc();
-
-export const KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_BONUS_PERCENT = 0.5;
diff --git a/apps/web/src/lib/kilo-pass/issuance.test.ts b/apps/web/src/lib/kilo-pass/issuance.test.ts
index 1669d74ac9..5b07e34059 100644
--- a/apps/web/src/lib/kilo-pass/issuance.test.ts
+++ b/apps/web/src/lib/kilo-pass/issuance.test.ts
@@ -5,6 +5,10 @@ import { insertTestUser } from '@/tests/helpers/user.helper';
import { dayjs } from '@/lib/kilo-pass/dayjs';
import {
credit_transactions,
+ impact_referral_conversions,
+ impact_referral_reward_applications,
+ impact_referral_reward_decisions,
+ impact_referral_rewards,
kilo_pass_audit_log,
kilo_pass_issuance_items,
kilo_pass_subscriptions,
@@ -16,10 +20,20 @@ import { KiloPassIssuanceItemKind } from './enums';
import { KiloPassIssuanceSource } from './enums';
import { KiloPassCadence } from './enums';
import { KiloPassTier } from '@/lib/kilo-pass/enums';
-import { and, eq } from 'drizzle-orm';
+import {
+ ImpactReferralBeneficiaryRole,
+ ImpactReferralDecisionOutcome,
+ ImpactReferralPaymentProvider,
+ ImpactReferralProduct,
+ ImpactReferralRewardKind,
+ ImpactReferralRewardStatus,
+ ImpactReferralWinningTouchType,
+} from '@kilocode/db/schema-types';
+import { and, eq, inArray } from 'drizzle-orm';
import { forceImmediateExpirationRecomputation } from '@/lib/balanceCache';
import {
+ applyPendingKiloPassReferralBonusForIssuance,
computeIssueMonth,
createOrGetIssuanceHeader,
issueBaseCreditsForIssuance,
@@ -61,6 +75,75 @@ async function createTestSubscription(params: {
return row;
}
+async function createPendingKiloPassReferralReward(params: {
+ beneficiaryUserId: string;
+ beneficiaryRole?: ImpactReferralBeneficiaryRole;
+ earnedAt: string;
+ expiresAt?: string | null;
+ rewardAmountUsd?: number;
+ sourcePaymentId?: string;
+}): Promise<{ rewardId: string; conversionId: string }> {
+ const referee = await insertTestUser({ total_microdollars_acquired: 0, microdollars_used: 0 });
+ const sourcePaymentId = params.sourcePaymentId ?? `inv-referral-source-${crypto.randomUUID()}`;
+ const beneficiaryRole = params.beneficiaryRole ?? ImpactReferralBeneficiaryRole.Referrer;
+
+ const [conversion] = await db
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: referee.id,
+ referrer_user_id:
+ beneficiaryRole === ImpactReferralBeneficiaryRole.Referrer
+ ? params.beneficiaryUserId
+ : null,
+ winning_touch_type: ImpactReferralWinningTouchType.Referral,
+ payment_provider: ImpactReferralPaymentProvider.Stripe,
+ source_payment_id: sourcePaymentId,
+ qualified: true,
+ converted_at: params.earnedAt,
+ })
+ .returning({ id: impact_referral_conversions.id });
+ if (!conversion) throw new Error('Failed to create impact_referral_conversion');
+
+ const [decision] = await db
+ .insert(impact_referral_reward_decisions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ beneficiary_user_id: params.beneficiaryUserId,
+ beneficiary_role: beneficiaryRole,
+ outcome: ImpactReferralDecisionOutcome.Granted,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ reward_percent: 0.5,
+ source_tier: KiloPassTier.Tier49,
+ reward_amount_usd: params.rewardAmountUsd ?? 24.5,
+ })
+ .returning({ id: impact_referral_reward_decisions.id });
+ if (!decision) throw new Error('Failed to create impact_referral_reward_decision');
+
+ const [reward] = await db
+ .insert(impact_referral_rewards)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ decision_id: decision.id,
+ beneficiary_user_id: params.beneficiaryUserId,
+ beneficiary_role: beneficiaryRole,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: 0.5,
+ source_tier: KiloPassTier.Tier49,
+ reward_amount_usd: params.rewardAmountUsd ?? 24.5,
+ status: ImpactReferralRewardStatus.Pending,
+ earned_at: params.earnedAt,
+ expires_at: params.expiresAt ?? '2027-01-01T00:00:00.000Z',
+ })
+ .returning({ id: impact_referral_rewards.id });
+ if (!reward) throw new Error('Failed to create impact_referral_reward');
+
+ return { rewardId: reward.id, conversionId: conversion.id };
+}
+
test('base issuance is idempotent: calling twice only creates one credit_transaction', async () => {
const user = await insertTestUser({ total_microdollars_acquired: 0, microdollars_used: 0 });
const { subscriptionId } = await createTestSubscription({
@@ -391,6 +474,274 @@ test('bonus issuance skips when a referral bonus item already exists', async ()
);
});
+test('monthly issuance consumes the oldest pending Kilo Pass referral reward and blocks normal bonus', async () => {
+ const user = await insertTestUser({ total_microdollars_acquired: 0, microdollars_used: 123 });
+ const { subscriptionId } = await createTestSubscription({
+ kiloUserId: user.id,
+ tier: KiloPassTier.Tier49,
+ cadence: KiloPassCadence.Monthly,
+ startedAt: '2026-02-01T00:00:00.000Z',
+ });
+
+ const newerReward = await createPendingKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ earnedAt: '2026-01-15T00:00:00.000Z',
+ rewardAmountUsd: 10,
+ });
+ const olderReward = await createPendingKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ earnedAt: '2026-01-10T00:00:00.000Z',
+ rewardAmountUsd: 24.5,
+ });
+
+ const { issuanceId } = await db.transaction(async tx => {
+ return await createOrGetIssuanceHeader(tx, {
+ subscriptionId,
+ issueMonth: '2026-02-01',
+ source: KiloPassIssuanceSource.StripeInvoice,
+ stripeInvoiceId: `inv-referral-application-${crypto.randomUUID()}`,
+ });
+ });
+
+ const result = await db.transaction(async tx => {
+ return await applyPendingKiloPassReferralBonusForIssuance(tx, {
+ issuanceId,
+ subscriptionId,
+ kiloUserId: user.id,
+ });
+ });
+ await forceImmediateExpirationRecomputation(user.id);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ wasIssued: true,
+ rewardId: olderReward.rewardId,
+ amountUsd: 24.5,
+ amountMicrodollars: 24_500_000,
+ })
+ );
+
+ const [referralItem] = await db
+ .select({
+ id: kilo_pass_issuance_items.id,
+ creditTransactionId: kilo_pass_issuance_items.credit_transaction_id,
+ amountUsd: kilo_pass_issuance_items.amount_usd,
+ bonusPercentApplied: kilo_pass_issuance_items.bonus_percent_applied,
+ })
+ .from(kilo_pass_issuance_items)
+ .where(
+ and(
+ eq(kilo_pass_issuance_items.kilo_pass_issuance_id, issuanceId),
+ eq(kilo_pass_issuance_items.kind, KiloPassIssuanceItemKind.ReferralBonus)
+ )
+ );
+ expect(referralItem).toEqual(
+ expect.objectContaining({
+ creditTransactionId: result.creditTransactionId,
+ amountUsd: 24.5,
+ bonusPercentApplied: 0.5,
+ })
+ );
+
+ const credit = await db.query.credit_transactions.findFirst({
+ where: eq(credit_transactions.id, result.creditTransactionId ?? ''),
+ });
+ expect(credit).toEqual(
+ expect.objectContaining({
+ is_free: true,
+ amount_microdollars: 24_500_000,
+ expiration_baseline_microdollars_used: 123,
+ original_baseline_microdollars_used: 123,
+ })
+ );
+ expect(new Date(credit?.expiry_date ?? '').toISOString()).toBe('2026-03-01T00:00:00.000Z');
+
+ const appliedReward = await db.query.impact_referral_rewards.findFirst({
+ where: eq(impact_referral_rewards.id, olderReward.rewardId),
+ });
+ expect(appliedReward).toEqual(
+ expect.objectContaining({
+ status: ImpactReferralRewardStatus.Applied,
+ applies_to_kilo_pass_subscription_id: subscriptionId,
+ consumed_kilo_pass_issuance_id: issuanceId,
+ consumed_kilo_pass_issuance_item_id: referralItem?.id,
+ })
+ );
+ expect(appliedReward?.applied_at).toBeTruthy();
+
+ const application = await db.query.impact_referral_reward_applications.findFirst({
+ where: eq(impact_referral_reward_applications.reward_id, olderReward.rewardId),
+ });
+ expect(application).toEqual(
+ expect.objectContaining({
+ product: ImpactReferralProduct.KiloPass,
+ beneficiary_user_id: user.id,
+ subscription_id: subscriptionId,
+ local_operation_id: result.creditTransactionId,
+ })
+ );
+
+ const pendingNewerReward = await db.query.impact_referral_rewards.findFirst({
+ where: eq(impact_referral_rewards.id, newerReward.rewardId),
+ });
+ expect(pendingNewerReward?.status).toBe(ImpactReferralRewardStatus.Pending);
+
+ const normalBonusResult = await db.transaction(async tx => {
+ return await issueBonusCreditsForIssuance(tx, {
+ issuanceId,
+ subscriptionId,
+ kiloUserId: user.id,
+ baseAmountUsd: KILO_PASS_TIER_CONFIG.tier_49.monthlyPriceUsd,
+ bonusPercentApplied: 0.1,
+ description: `kilo-pass-normal-bonus-after-referral-${crypto.randomUUID()}`,
+ });
+ });
+ expect(normalBonusResult.wasIssued).toBe(false);
+});
+
+test('monthly issuance expires stale rewards, consumes one unexpired reward per issuance, and retries idempotently', async () => {
+ const user = await insertTestUser({ total_microdollars_acquired: 0, microdollars_used: 0 });
+ const { subscriptionId } = await createTestSubscription({
+ kiloUserId: user.id,
+ tier: KiloPassTier.Tier49,
+ cadence: KiloPassCadence.Monthly,
+ startedAt: '2026-02-01T00:00:00.000Z',
+ });
+
+ const expiredReward = await createPendingKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ earnedAt: '2026-01-01T00:00:00.000Z',
+ expiresAt: '2026-01-31T00:00:00.000Z',
+ });
+ const firstReward = await createPendingKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ earnedAt: '2026-01-02T00:00:00.000Z',
+ rewardAmountUsd: 7,
+ });
+ const secondReward = await createPendingKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ earnedAt: '2026-01-03T00:00:00.000Z',
+ rewardAmountUsd: 8,
+ });
+
+ const { issuanceId: issuanceId1 } = await db.transaction(async tx => {
+ return await createOrGetIssuanceHeader(tx, {
+ subscriptionId,
+ issueMonth: '2026-02-01',
+ source: KiloPassIssuanceSource.StripeInvoice,
+ stripeInvoiceId: `inv-referral-stack-1-${crypto.randomUUID()}`,
+ });
+ });
+
+ const firstApply = await db.transaction(async tx => {
+ return await applyPendingKiloPassReferralBonusForIssuance(tx, {
+ issuanceId: issuanceId1,
+ subscriptionId,
+ kiloUserId: user.id,
+ });
+ });
+ expect(firstApply.wasIssued).toBe(true);
+ expect(firstApply.rewardId).toBe(firstReward.rewardId);
+ expect(firstApply.expiredRewardIds).toContain(expiredReward.rewardId);
+
+ const retry = await db.transaction(async tx => {
+ return await applyPendingKiloPassReferralBonusForIssuance(tx, {
+ issuanceId: issuanceId1,
+ subscriptionId,
+ kiloUserId: user.id,
+ });
+ });
+ expect(retry.wasIssued).toBe(false);
+
+ const { issuanceId: issuanceId2 } = await db.transaction(async tx => {
+ return await createOrGetIssuanceHeader(tx, {
+ subscriptionId,
+ issueMonth: '2026-03-01',
+ source: KiloPassIssuanceSource.StripeInvoice,
+ stripeInvoiceId: `inv-referral-stack-2-${crypto.randomUUID()}`,
+ });
+ });
+
+ const secondApply = await db.transaction(async tx => {
+ return await applyPendingKiloPassReferralBonusForIssuance(tx, {
+ issuanceId: issuanceId2,
+ subscriptionId,
+ kiloUserId: user.id,
+ });
+ });
+ expect(secondApply.wasIssued).toBe(true);
+ expect(secondApply.rewardId).toBe(secondReward.rewardId);
+
+ const rewards = await db
+ .select({ id: impact_referral_rewards.id, status: impact_referral_rewards.status })
+ .from(impact_referral_rewards)
+ .where(
+ inArray(impact_referral_rewards.id, [
+ expiredReward.rewardId,
+ firstReward.rewardId,
+ secondReward.rewardId,
+ ])
+ );
+ expect(rewards).toEqual(
+ expect.arrayContaining([
+ { id: expiredReward.rewardId, status: ImpactReferralRewardStatus.Expired },
+ { id: firstReward.rewardId, status: ImpactReferralRewardStatus.Applied },
+ { id: secondReward.rewardId, status: ImpactReferralRewardStatus.Applied },
+ ])
+ );
+
+ const items1 = await db
+ .select({ id: kilo_pass_issuance_items.id })
+ .from(kilo_pass_issuance_items)
+ .where(
+ and(
+ eq(kilo_pass_issuance_items.kilo_pass_issuance_id, issuanceId1),
+ eq(kilo_pass_issuance_items.kind, KiloPassIssuanceItemKind.ReferralBonus)
+ )
+ );
+ expect(items1).toHaveLength(1);
+});
+
+test('monthly issuance does not apply a referral reward to its source conversion issuance', async () => {
+ const user = await insertTestUser({ total_microdollars_acquired: 0, microdollars_used: 0 });
+ const { subscriptionId } = await createTestSubscription({
+ kiloUserId: user.id,
+ tier: KiloPassTier.Tier49,
+ cadence: KiloPassCadence.Monthly,
+ startedAt: '2026-02-01T00:00:00.000Z',
+ });
+ const sourceInvoiceId = `inv-referral-source-${crypto.randomUUID()}`;
+ const reward = await createPendingKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ earnedAt: '2026-01-01T00:00:00.000Z',
+ sourcePaymentId: sourceInvoiceId,
+ });
+
+ const { issuanceId } = await db.transaction(async tx => {
+ return await createOrGetIssuanceHeader(tx, {
+ subscriptionId,
+ issueMonth: '2026-02-01',
+ source: KiloPassIssuanceSource.StripeInvoice,
+ stripeInvoiceId: sourceInvoiceId,
+ });
+ });
+
+ const result = await db.transaction(async tx => {
+ return await applyPendingKiloPassReferralBonusForIssuance(tx, {
+ issuanceId,
+ subscriptionId,
+ kiloUserId: user.id,
+ stripeInvoiceId: sourceInvoiceId,
+ });
+ });
+
+ expect(result.wasIssued).toBe(false);
+ const rewardAfter = await db.query.impact_referral_rewards.findFirst({
+ where: eq(impact_referral_rewards.id, reward.rewardId),
+ });
+ expect(rewardAfter?.status).toBe(ImpactReferralRewardStatus.Pending);
+});
+
test('monthly cadence: bonus expiry is end of the subscription month (period end), not month end', async () => {
const user = await insertTestUser({ total_microdollars_acquired: 0, microdollars_used: 0 });
const startedAt = '2025-04-04T00:00:00.000Z';
diff --git a/apps/web/src/lib/kilo-pass/issuance.ts b/apps/web/src/lib/kilo-pass/issuance.ts
index 5728e03316..fdb37b60ad 100644
--- a/apps/web/src/lib/kilo-pass/issuance.ts
+++ b/apps/web/src/lib/kilo-pass/issuance.ts
@@ -1,5 +1,8 @@
import {
credit_transactions,
+ impact_referral_conversions,
+ impact_referral_reward_applications,
+ impact_referral_rewards,
kilo_pass_audit_log,
kilo_pass_issuance_items,
kilo_pass_issuances,
@@ -7,6 +10,11 @@ import {
kilocode_users,
} from '@kilocode/db/schema';
import type { User } from '@kilocode/db/schema';
+import {
+ ImpactReferralProduct,
+ ImpactReferralRewardKind,
+ ImpactReferralRewardStatus,
+} from '@kilocode/db/schema-types';
import { KiloPassAuditLogResult } from './enums';
import { KiloPassAuditLogAction } from './enums';
import { KiloPassCadence } from './enums';
@@ -17,7 +25,7 @@ import type { db as defaultDb } from '@/lib/drizzle';
import { processTopUp } from '@/lib/credits';
import { grantCreditForCategory, type GrantCreditOptions } from '@/lib/promotionalCredits';
import { toMicrodollars } from '@/lib/utils';
-import { and, eq } from 'drizzle-orm';
+import { and, asc, eq, gt, inArray, isNull, lt, lte, ne, sql } from 'drizzle-orm';
import type { DrizzleTransaction } from '@/lib/drizzle';
import { dayjs } from '@/lib/kilo-pass/dayjs';
@@ -442,6 +450,273 @@ export async function issueBaseCreditsForIssuance(
};
}
+async function getExistingBonusLikeIssuanceItem(
+ tx: DrizzleTransaction,
+ params: { issuanceId: string }
+): Promise<{
+ issuanceItemId: string;
+ creditTransactionId: string;
+ kind: KiloPassIssuanceItemKind;
+} | null> {
+ const rows = await tx
+ .select({
+ issuanceItemId: kilo_pass_issuance_items.id,
+ creditTransactionId: kilo_pass_issuance_items.credit_transaction_id,
+ kind: kilo_pass_issuance_items.kind,
+ })
+ .from(kilo_pass_issuance_items)
+ .where(
+ and(
+ eq(kilo_pass_issuance_items.kilo_pass_issuance_id, params.issuanceId),
+ inArray(kilo_pass_issuance_items.kind, [
+ KiloPassIssuanceItemKind.Bonus,
+ KiloPassIssuanceItemKind.PromoFirstMonth50Pct,
+ KiloPassIssuanceItemKind.ReferralBonus,
+ ])
+ )
+ )
+ .limit(1);
+
+ return rows[0] ?? null;
+}
+
+export type KiloPassReferralBonusApplicationResult = IssueCreditResult & {
+ rewardId: string | null;
+ expiredRewardIds: string[];
+};
+
+export async function applyPendingKiloPassReferralBonusForIssuance(
+ tx: DrizzleTransaction,
+ params: {
+ issuanceId: string;
+ subscriptionId: string;
+ kiloUserId: string;
+ stripeInvoiceId?: string | null;
+ }
+): Promise {
+ const { issuanceId, subscriptionId, kiloUserId, stripeInvoiceId } = params;
+
+ await lockIssuanceRow(tx, issuanceId);
+
+ const issuanceRows = await tx
+ .select({
+ issueMonth: kilo_pass_issuances.issue_month,
+ stripeInvoiceId: kilo_pass_issuances.stripe_invoice_id,
+ createdAt: kilo_pass_issuances.created_at,
+ })
+ .from(kilo_pass_issuances)
+ .where(eq(kilo_pass_issuances.id, issuanceId))
+ .limit(1);
+
+ const issuance = issuanceRows[0];
+ if (!issuance) {
+ throw new Error(`Issuance not found: ${issuanceId}`);
+ }
+
+ const applicationCutoff = issuance.createdAt;
+ const sourcePaymentId = stripeInvoiceId ?? issuance.stripeInvoiceId;
+
+ const expiredRewards = await tx
+ .update(impact_referral_rewards)
+ .set({
+ status: ImpactReferralRewardStatus.Expired,
+ reversed_at: applicationCutoff,
+ review_reason: 'expired_before_kilo_pass_referral_bonus_application',
+ })
+ .where(
+ and(
+ eq(impact_referral_rewards.product, ImpactReferralProduct.KiloPass),
+ eq(impact_referral_rewards.reward_kind, ImpactReferralRewardKind.KiloPassBonus),
+ eq(impact_referral_rewards.beneficiary_user_id, kiloUserId),
+ inArray(impact_referral_rewards.status, [
+ ImpactReferralRewardStatus.Pending,
+ ImpactReferralRewardStatus.Earned,
+ ]),
+ sql`${impact_referral_rewards.expires_at} IS NOT NULL`,
+ lte(impact_referral_rewards.expires_at, applicationCutoff),
+ isNull(impact_referral_rewards.applied_at),
+ isNull(impact_referral_rewards.consumed_kilo_pass_issuance_id)
+ )
+ )
+ .returning({ id: impact_referral_rewards.id });
+
+ const existingBonusLikeItem = await getExistingBonusLikeIssuanceItem(tx, { issuanceId });
+ if (existingBonusLikeItem) {
+ return {
+ wasIssued: false,
+ rewardId: null,
+ expiredRewardIds: expiredRewards.map(reward => reward.id),
+ issuanceItemId: existingBonusLikeItem.issuanceItemId,
+ creditTransactionId: existingBonusLikeItem.creditTransactionId,
+ amountUsd: 0,
+ amountMicrodollars: 0,
+ };
+ }
+
+ const rewardRows = await tx
+ .select({
+ id: impact_referral_rewards.id,
+ rewardAmountUsd: impact_referral_rewards.reward_amount_usd,
+ rewardPercent: impact_referral_rewards.reward_percent,
+ sourcePaymentId: impact_referral_conversions.source_payment_id,
+ })
+ .from(impact_referral_rewards)
+ .innerJoin(
+ impact_referral_conversions,
+ eq(impact_referral_conversions.id, impact_referral_rewards.conversion_id)
+ )
+ .where(
+ and(
+ eq(impact_referral_rewards.product, ImpactReferralProduct.KiloPass),
+ eq(impact_referral_rewards.reward_kind, ImpactReferralRewardKind.KiloPassBonus),
+ eq(impact_referral_rewards.beneficiary_user_id, kiloUserId),
+ inArray(impact_referral_rewards.status, [
+ ImpactReferralRewardStatus.Pending,
+ ImpactReferralRewardStatus.Earned,
+ ]),
+ isNull(impact_referral_rewards.applied_at),
+ isNull(impact_referral_rewards.consumed_kilo_pass_issuance_id),
+ lt(impact_referral_rewards.earned_at, applicationCutoff),
+ gt(impact_referral_rewards.expires_at, applicationCutoff),
+ sourcePaymentId
+ ? ne(impact_referral_conversions.source_payment_id, sourcePaymentId)
+ : undefined
+ )
+ )
+ .orderBy(asc(impact_referral_rewards.earned_at), asc(impact_referral_rewards.created_at))
+ .for('update')
+ .limit(1);
+
+ const reward = rewardRows[0];
+ if (!reward || reward.rewardAmountUsd == null) {
+ return {
+ wasIssued: false,
+ rewardId: null,
+ expiredRewardIds: expiredRewards.map(expiredReward => expiredReward.id),
+ issuanceItemId: null,
+ creditTransactionId: null,
+ amountUsd: 0,
+ amountMicrodollars: 0,
+ };
+ }
+
+ const user = await getUserForCreditMutations(tx, kiloUserId);
+ const rewardCents = roundUsdToCents(reward.rewardAmountUsd);
+ const rewardAmountUsd = centsToUsd(rewardCents);
+ const rewardAmountMicrodollars = toMicrodollars(rewardAmountUsd);
+ const bonusPercentApplied = reward.rewardPercent ?? 0.5;
+
+ const creditExpiryDate = await computeKiloPassBonusExpiryDate(tx, {
+ issuanceId,
+ subscriptionId,
+ });
+
+ const creditCategory = 'kilo-pass-bonus';
+ const grantResult = await grantCreditForCategory(user, {
+ credit_category: creditCategory,
+ counts_as_selfservice: false,
+ amount_usd: rewardAmountUsd,
+ description: `Kilo Pass referral bonus (${issuance.issueMonth})`,
+ credit_expiry_date: creditExpiryDate ?? undefined,
+ dbOrTx: tx,
+ });
+ if (!grantResult.success) {
+ throw new Error(`Failed to grant Kilo Pass referral bonus credits: ${grantResult.message}`);
+ }
+
+ const creditTransactionId = grantResult.credit_transaction_id;
+ const issuanceItemInsert = await tx
+ .insert(kilo_pass_issuance_items)
+ .values({
+ kilo_pass_issuance_id: issuanceId,
+ kind: KiloPassIssuanceItemKind.ReferralBonus,
+ credit_transaction_id: creditTransactionId,
+ amount_usd: rewardAmountUsd,
+ bonus_percent_applied: bonusPercentApplied,
+ })
+ .returning({ issuanceItemId: kilo_pass_issuance_items.id });
+
+ const issuanceItemId = issuanceItemInsert[0]?.issuanceItemId;
+ if (!issuanceItemId) {
+ throw new Error('Failed to insert issuance item for referral bonus credits');
+ }
+
+ const appliedAt = new Date().toISOString();
+ const appliedRows = await tx
+ .update(impact_referral_rewards)
+ .set({
+ status: ImpactReferralRewardStatus.Applied,
+ applies_to_kilo_pass_subscription_id: subscriptionId,
+ consumed_kilo_pass_issuance_id: issuanceId,
+ consumed_kilo_pass_issuance_item_id: issuanceItemId,
+ applied_at: appliedAt,
+ review_reason: null,
+ })
+ .where(
+ and(
+ eq(impact_referral_rewards.id, reward.id),
+ inArray(impact_referral_rewards.status, [
+ ImpactReferralRewardStatus.Pending,
+ ImpactReferralRewardStatus.Earned,
+ ]),
+ isNull(impact_referral_rewards.applied_at),
+ isNull(impact_referral_rewards.consumed_kilo_pass_issuance_id)
+ )
+ )
+ .returning({ id: impact_referral_rewards.id });
+
+ if (!appliedRows[0]) {
+ throw new Error(`Failed to mark Kilo Pass referral reward applied: ${reward.id}`);
+ }
+
+ const existingApplication = await tx.query.impact_referral_reward_applications.findFirst({
+ columns: { id: true },
+ where: eq(impact_referral_reward_applications.reward_id, reward.id),
+ });
+
+ if (!existingApplication) {
+ const issueMonthStart = `${issuance.issueMonth}T00:00:00.000Z`;
+ await tx.insert(impact_referral_reward_applications).values({
+ product: ImpactReferralProduct.KiloPass,
+ reward_id: reward.id,
+ beneficiary_user_id: kiloUserId,
+ subscription_id: subscriptionId,
+ previous_renewal_boundary: issueMonthStart,
+ new_renewal_boundary: creditExpiryDate?.toISOString() ?? issueMonthStart,
+ local_operation_id: creditTransactionId,
+ stripe_operation_id: sourcePaymentId ?? null,
+ applied_at: appliedAt,
+ });
+ }
+
+ await appendKiloPassAuditLog(tx, {
+ action: KiloPassAuditLogAction.BonusCreditsIssued,
+ result: KiloPassAuditLogResult.Success,
+ kiloUserId,
+ kiloPassSubscriptionId: subscriptionId,
+ stripeInvoiceId: sourcePaymentId ?? null,
+ relatedCreditTransactionId: creditTransactionId,
+ relatedMonthlyIssuanceId: issuanceId,
+ payload: {
+ kind: KiloPassIssuanceItemKind.ReferralBonus,
+ rewardId: reward.id,
+ bonusPercentApplied,
+ bonusAmountUsd: rewardAmountUsd,
+ creditCategory,
+ },
+ });
+
+ return {
+ wasIssued: true,
+ rewardId: reward.id,
+ expiredRewardIds: expiredRewards.map(expiredReward => expiredReward.id),
+ issuanceItemId,
+ creditTransactionId,
+ amountUsd: rewardAmountUsd,
+ amountMicrodollars: rewardAmountMicrodollars,
+ };
+}
+
export async function issueBonusCreditsForIssuance(
tx: DrizzleTransaction,
params: {
diff --git a/apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.test.ts b/apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.test.ts
index 5e69ef72ac..935eea699f 100644
--- a/apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.test.ts
+++ b/apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.test.ts
@@ -465,6 +465,88 @@ describe('handleKiloPassInvoicePaid', () => {
);
});
+ test('monthly: referral conversion processor suppresses affiliate SALE when referral wins', async () => {
+ const processPersonalKiloPassStripePaidConversion = jest.fn(async (_params: unknown) => ({
+ shouldEnqueueAffiliateSale: false,
+ winningTouchType: 'referral',
+ conversionId: 'conversion_referral_winner',
+ disqualificationReason: null,
+ }));
+
+ try {
+ jest.resetModules();
+ jest.doMock('@/lib/impact/kilo-pass-referrals', () => ({
+ __esModule: true,
+ processPersonalKiloPassStripePaidConversion,
+ }));
+
+ const { handleKiloPassInvoicePaid } =
+ await import('@/lib/kilo-pass/stripe-handlers-invoice-paid');
+ const user = await insertTestUser({ total_microdollars_acquired: 0, microdollars_used: 0 });
+ await seedDeliveredImpactSignupEvent(user.id, user.google_user_email);
+ const stripeSubId = `sub_referral_suppresses_affiliate_${Math.random()}`;
+ const meta = kiloPassMetadata({
+ kiloUserId: user.id,
+ tier: KiloPassTier.Tier49,
+ cadence: KiloPassCadence.Monthly,
+ });
+ const subscription = makeStripeSubscription({
+ id: stripeSubId,
+ start_date_seconds: 1_767_225_600,
+ metadata: meta,
+ });
+ const priceId = await getKiloPassPriceId({
+ tier: KiloPassTier.Tier49,
+ cadence: KiloPassCadence.Monthly,
+ });
+ const invoiceId = `inv_referral_suppresses_affiliate_${Math.random()}`;
+
+ await handleKiloPassInvoicePaid({
+ eventId: 'evt_referral_suppresses_affiliate',
+ invoice: makeStripeInvoice({
+ id: invoiceId,
+ amount_paid_cents: 4900,
+ period_start_seconds: 1_767_225_600,
+ created_seconds: 1_767_225_600,
+ paid_seconds: 1_767_225_660,
+ priceId,
+ subscriptionIdOrExpanded: stripeSubId,
+ metadata: meta,
+ }),
+ stripe: {
+ subscriptions: {
+ retrieve: jest.fn(async () => subscription),
+ },
+ } as unknown as Stripe,
+ });
+
+ expect(processPersonalKiloPassStripePaidConversion).toHaveBeenCalledWith(
+ expect.objectContaining({
+ userId: user.id,
+ sourcePaymentId: invoiceId,
+ amount: 49,
+ itemCategory: 'kilo-pass-tier-49-monthly',
+ sourceTier: KiloPassTier.Tier49,
+ cadence: KiloPassCadence.Monthly,
+ welcomePromoEligibilityReason: KiloPassWelcomePromoEligibilityReason.SettlementUnresolved,
+ })
+ );
+ const saleEvents = await db
+ .select()
+ .from(user_affiliate_events)
+ .where(
+ and(
+ eq(user_affiliate_events.user_id, user.id),
+ eq(user_affiliate_events.event_type, 'sale')
+ )
+ );
+ expect(saleEvents).toHaveLength(0);
+ } finally {
+ jest.dontMock('@/lib/impact/kilo-pass-referrals');
+ jest.resetModules();
+ }
+ });
+
test('monthly: sale fallback occurrence time preserves recoverable Stripe charge identity', async () => {
const observedBeforeHandling = Date.now();
const { handleKiloPassInvoicePaid } =
diff --git a/apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.ts b/apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.ts
index 393f671a97..bdfe758303 100644
--- a/apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.ts
+++ b/apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.ts
@@ -17,6 +17,7 @@ import { KILO_PASS_TIER_CONFIG } from '@/lib/kilo-pass/constants';
import { KiloPassError } from '@/lib/kilo-pass/errors';
import {
appendKiloPassAuditLog,
+ applyPendingKiloPassReferralBonusForIssuance,
createOrGetIssuanceHeader,
issueBaseCreditsForIssuance,
} from '@/lib/kilo-pass/issuance';
@@ -54,8 +55,10 @@ import {
} from '@/lib/kilo-pass/subscription-accounting';
import {
enqueueKiloPassAffiliateSaleForInvoice,
+ getKiloPassAffiliateSaleReportingFields,
type KiloPassAffiliateSaleContext,
} from '@/lib/kilo-pass/affiliate-sale';
+import { processPersonalKiloPassStripePaidConversion } from '@/lib/impact/kilo-pass-referrals';
import type { SupportedReusablePaymentMethodType } from '@/lib/kilo-pass/stripe-handlers-utils';
function getPaymentFingerprintType(
@@ -355,8 +358,26 @@ export async function handleKiloPassInvoicePaid(params: {
let didMutateBalance = false;
let kiloUserIdForCache: string | null = null;
- const affiliateSaleState: { context: KiloPassAffiliateSaleContext | null } = {
+ const affiliateSaleState: {
+ context: KiloPassAffiliateSaleContext | null;
+ shouldEnqueueAffiliateSale: boolean;
+ } = {
context: null,
+ shouldEnqueueAffiliateSale: true,
+ };
+ const referralConversionState: {
+ kiloPassSubscriptionId: string | null;
+ userId: string | null;
+ tier: KiloPassAffiliateSaleContext['tier'] | null;
+ cadence: KiloPassAffiliateSaleContext['cadence'] | null;
+ welcomePromoEligibilityReason: KiloPassWelcomePromoEligibilityReason | null;
+ itemSku?: string;
+ } = {
+ kiloPassSubscriptionId: null,
+ userId: null,
+ tier: null,
+ cadence: null,
+ welcomePromoEligibilityReason: null,
};
// Track context for failure audit logging
@@ -484,6 +505,13 @@ export async function handleKiloPassInvoicePaid(params: {
const kiloPassSubscriptionId = row.id;
kiloPassSubscriptionIdForAudit = kiloPassSubscriptionId;
+ referralConversionState.kiloPassSubscriptionId = kiloPassSubscriptionId;
+ referralConversionState.userId = kiloUserId;
+ referralConversionState.tier = tier;
+ referralConversionState.cadence = cadence;
+ if (priceMetadata) {
+ referralConversionState.itemSku = priceMetadata.priceId;
+ }
const priorStatus = existingSubscription?.status ?? null;
const issuanceHeader = await createOrGetIssuanceHeader(tx, {
@@ -497,6 +525,7 @@ export async function handleKiloPassInvoicePaid(params: {
cadence === KiloPassCadence.Monthly && hasPositiveSettlement
? await claimMonthlyPaymentFingerprint({ tx, stripe, invoice })
: null;
+ referralConversionState.welcomePromoEligibilityReason = positiveSettlementReason;
const initialWelcomePromoEligibilityReason =
cadence === KiloPassCadence.Monthly
? await getOrCreateInitialMonthlyWelcomePromoReason({
@@ -546,7 +575,20 @@ export async function handleKiloPassInvoicePaid(params: {
});
didMutateBalance ||= baseCreditsResult.wasIssued;
- if (baseCreditsResult.wasIssued) {
+ let referralBonusBlocksNormalBonus = false;
+ if (cadence === KiloPassCadence.Monthly) {
+ const referralBonusResult = await applyPendingKiloPassReferralBonusForIssuance(tx, {
+ issuanceId: issuanceHeader.issuanceId,
+ subscriptionId: kiloPassSubscriptionId,
+ kiloUserId,
+ stripeInvoiceId: invoice.id,
+ });
+ referralBonusBlocksNormalBonus =
+ referralBonusResult.wasIssued || referralBonusResult.issuanceItemId !== null;
+ didMutateBalance ||= referralBonusResult.wasIssued;
+ }
+
+ if (baseCreditsResult.wasIssued && !referralBonusBlocksNormalBonus) {
await updateKiloPassThresholdAfterBaseCredits(tx, {
kiloUserId,
baseAmountUsd: tierConfig.monthlyPriceUsd,
@@ -614,12 +656,50 @@ export async function handleKiloPassInvoicePaid(params: {
throw error;
}
- await enqueueKiloPassAffiliateSaleForInvoice({
- eventId,
- invoice,
- stripe,
- context: affiliateSaleState.context,
- });
+ if (
+ affiliateSaleState.context &&
+ referralConversionState.kiloPassSubscriptionId &&
+ referralConversionState.userId &&
+ referralConversionState.tier &&
+ referralConversionState.cadence
+ ) {
+ const convertedAt =
+ invoice.status_transitions?.paid_at != null
+ ? new Date(invoice.status_transitions.paid_at * 1000)
+ : invoice.created != null
+ ? new Date(invoice.created * 1000)
+ : invoice.period_start != null
+ ? new Date(invoice.period_start * 1000)
+ : new Date();
+ const reportingFields = getKiloPassAffiliateSaleReportingFields(affiliateSaleState.context);
+ const referralDisposition = await processPersonalKiloPassStripePaidConversion({
+ userId: referralConversionState.userId,
+ kiloPassSubscriptionId: referralConversionState.kiloPassSubscriptionId,
+ sourcePaymentId: invoice.id,
+ orderId: invoice.id,
+ amount: invoice.amount_paid / 100,
+ currencyCode: invoice.currency ?? 'usd',
+ itemCategory: reportingFields.itemCategory,
+ itemName: reportingFields.itemName,
+ ...('itemSku' in reportingFields && reportingFields.itemSku
+ ? { itemSku: reportingFields.itemSku }
+ : {}),
+ sourceTier: referralConversionState.tier,
+ cadence: referralConversionState.cadence,
+ welcomePromoEligibilityReason: referralConversionState.welcomePromoEligibilityReason,
+ convertedAt,
+ });
+ affiliateSaleState.shouldEnqueueAffiliateSale = referralDisposition.shouldEnqueueAffiliateSale;
+ }
+
+ if (affiliateSaleState.shouldEnqueueAffiliateSale) {
+ await enqueueKiloPassAffiliateSaleForInvoice({
+ eventId,
+ invoice,
+ stripe,
+ context: affiliateSaleState.context,
+ });
+ }
if (didMutateBalance && kiloUserIdForCache !== null) {
await forceImmediateExpirationRecomputation(kiloUserIdForCache);
diff --git a/apps/web/src/lib/kilo-pass/usage-triggered-bonus.test.ts b/apps/web/src/lib/kilo-pass/usage-triggered-bonus.test.ts
index f4c9d30d28..5ece3c4bf0 100644
--- a/apps/web/src/lib/kilo-pass/usage-triggered-bonus.test.ts
+++ b/apps/web/src/lib/kilo-pass/usage-triggered-bonus.test.ts
@@ -19,7 +19,6 @@ import {
KiloPassWelcomePromoEligibilityReason,
} from '@/lib/kilo-pass/enums';
import { computeMonthlyCadenceBonusPercent, getMonthlyPriceUsd } from '@/lib/kilo-pass/bonus';
-import { KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF } from '@/lib/kilo-pass/constants';
import { maybeIssueKiloPassBonusFromUsageThreshold } from '@/lib/kilo-pass/usage-triggered-bonus';
import { and, eq } from 'drizzle-orm';
@@ -129,10 +128,7 @@ describe('maybeIssueKiloPassBonusFromUsageThreshold', () => {
stripeInvoiceId: 'inv_test_monthly',
currentStreakMonths: 2,
nextYearlyIssueAt: null,
- // Ensure this test remains a "regular ramp" case, not eligible for the month-2 grandfathered promo.
- startedAtIso: new Date(
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.valueOf() + 1
- ).toISOString(),
+ startedAtIso: '2026-01-01T00:00:00.000Z',
});
await maybeIssueKiloPassBonusFromUsageThreshold({
@@ -162,7 +158,7 @@ describe('maybeIssueKiloPassBonusFromUsageThreshold', () => {
expect(userRow?.kilo_pass_threshold).toBeNull();
});
- test('monthly: first-2-months promo eligible => 50% bonus (tier_49, streak=2)', async () => {
+ test('monthly: skips usage-triggered bonus when referral_bonus item already exists and clears threshold', async () => {
const user = await insertTestUser({
microdollars_used: 55_000_000,
kilo_pass_threshold: 49_000_000,
@@ -172,78 +168,35 @@ describe('maybeIssueKiloPassBonusFromUsageThreshold', () => {
kiloUserId: user.id,
cadence: KiloPassCadence.Monthly,
tier: KiloPassTier.Tier49,
- issueMonth: '2026-02-01',
- stripeInvoiceId: 'inv_test_monthly_month2_grandfathered_eligible',
- currentStreakMonths: 2,
+ issueMonth: '2026-01-01',
+ stripeInvoiceId: 'inv_test_monthly_referral_bonus_blocks_usage_bonus',
+ currentStreakMonths: 1,
nextYearlyIssueAt: null,
- startedAtIso: '2026-01-26T23:59:59.000Z',
- });
-
- await maybeIssueKiloPassBonusFromUsageThreshold({
- kiloUserId: user.id,
- nowIso: new Date('2026-02-15T00:00:00.000Z').toISOString(),
- db,
- });
-
- const bonusItem = await db.query.kilo_pass_issuance_items.findFirst({
- where: and(
- eq(kilo_pass_issuance_items.kilo_pass_issuance_id, issuanceId),
- eq(kilo_pass_issuance_items.kind, KiloPassIssuanceItemKind.Bonus)
- ),
});
- expect(bonusItem).toBeTruthy();
-
- const bonusTx = await db.query.credit_transactions.findFirst({
- where: eq(credit_transactions.id, bonusItem?.credit_transaction_id ?? ''),
- });
- // tier_49 monthly price is $49, 50% => $24.50.
- expect(bonusTx?.amount_microdollars).toBe(24_500_000);
-
- const auditRows = await db
- .select({ payload: kilo_pass_audit_log.payload_json })
- .from(kilo_pass_audit_log)
- .where(
- and(
- eq(kilo_pass_audit_log.action, KiloPassAuditLogAction.BonusCreditsIssued),
- eq(kilo_pass_audit_log.related_monthly_issuance_id, issuanceId)
- )
- );
- const payload = auditRows[0]?.payload ?? null;
- expect(isRecord(payload)).toBe(true);
- if (!isRecord(payload))
- throw new Error('Expected bonus issuance audit payload to be an object');
-
- const decision = payload.monthlyBonusDecision;
- expect(isRecord(decision)).toBe(true);
- if (!isRecord(decision)) {
- throw new Error('Expected audit payload to include monthlyBonusDecision object');
- }
-
- expect(decision.streakMonths).toBe(2);
- expect(decision.issueMonth).toBe('2026-02-01');
- });
-
- test('monthly: first-2-months promo ineligible at cutoff => ramp applies (not 50%)', async () => {
- const user = await insertTestUser({
- microdollars_used: 55_000_000,
- kilo_pass_threshold: 49_000_000,
- });
+ const [referralCredit] = await db
+ .insert(credit_transactions)
+ .values({
+ kilo_user_id: user.id,
+ amount_microdollars: 24_500_000,
+ is_free: true,
+ description: 'seed referral bonus credits',
+ credit_category: `test-kilo-pass-referral-bonus-${crypto.randomUUID()}`,
+ })
+ .returning({ id: credit_transactions.id });
+ if (!referralCredit) throw new Error('Failed to insert referral credit transaction');
- const { issuanceId } = await seedBaseIssuance({
- kiloUserId: user.id,
- cadence: KiloPassCadence.Monthly,
- tier: KiloPassTier.Tier49,
- issueMonth: '2026-02-01',
- stripeInvoiceId: 'inv_test_monthly_month2_grandfathered_ineligible_cutoff',
- currentStreakMonths: 2,
- nextYearlyIssueAt: null,
- startedAtIso: KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.toISOString(),
+ await db.insert(kilo_pass_issuance_items).values({
+ kilo_pass_issuance_id: issuanceId,
+ kind: KiloPassIssuanceItemKind.ReferralBonus,
+ credit_transaction_id: referralCredit.id,
+ amount_usd: 24.5,
+ bonus_percent_applied: 0.5,
});
await maybeIssueKiloPassBonusFromUsageThreshold({
kiloUserId: user.id,
- nowIso: new Date('2026-02-15T00:00:00.000Z').toISOString(),
+ nowIso: new Date('2026-01-15T00:00:00.000Z').toISOString(),
db,
});
@@ -253,16 +206,15 @@ describe('maybeIssueKiloPassBonusFromUsageThreshold', () => {
eq(kilo_pass_issuance_items.kind, KiloPassIssuanceItemKind.Bonus)
),
});
- expect(bonusItem).toBeTruthy();
+ expect(bonusItem).toBeFalsy();
- const bonusTx = await db.query.credit_transactions.findFirst({
- where: eq(credit_transactions.id, bonusItem?.credit_transaction_id ?? ''),
+ const userRow = await db.query.kilocode_users.findFirst({
+ where: eq(kilocode_users.id, user.id),
});
- // tier_49 at streak=2 => base 5% + step 5% * 1 = 10% of $49.00 = $4.90.
- expect(bonusTx?.amount_microdollars).toBe(4_900_000);
+ expect(userRow?.kilo_pass_threshold).toBeNull();
});
- test('monthly: first-2-months promo started AFTER cutoff => ramp applies (not 50%)', async () => {
+ test('monthly: first-time month 2 uses ramp (not 50%)', async () => {
const user = await insertTestUser({
microdollars_used: 55_000_000,
kilo_pass_threshold: 49_000_000,
@@ -270,16 +222,14 @@ describe('maybeIssueKiloPassBonusFromUsageThreshold', () => {
const tier = KiloPassTier.Tier49;
const streakMonths = 2;
- const startedAtIso = new Date(
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.valueOf() + 1
- ).toISOString();
+ const startedAtIso = '2026-01-01T00:00:00.000Z';
const { issuanceId } = await seedBaseIssuance({
kiloUserId: user.id,
cadence: KiloPassCadence.Monthly,
tier,
issueMonth: '2026-02-01',
- stripeInvoiceId: 'inv_test_monthly_month2_grandfathered_ineligible_after_cutoff',
+ stripeInvoiceId: 'inv_test_monthly_month2_first_time_ramp',
currentStreakMonths: streakMonths,
nextYearlyIssueAt: null,
startedAtIso,
@@ -342,7 +292,7 @@ describe('maybeIssueKiloPassBonusFromUsageThreshold', () => {
expect(decision.issueMonth).toBe('2026-02-01');
});
- test('monthly: month-3 regression (started before cutoff) => ramp applies', async () => {
+ test('monthly: month 3 uses ramp', async () => {
const user = await insertTestUser({
microdollars_used: 55_000_000,
kilo_pass_threshold: 49_000_000,
diff --git a/apps/web/src/lib/kilo-pass/usage-triggered-bonus.ts b/apps/web/src/lib/kilo-pass/usage-triggered-bonus.ts
index b7c8f84ccc..c6291b886f 100644
--- a/apps/web/src/lib/kilo-pass/usage-triggered-bonus.ts
+++ b/apps/web/src/lib/kilo-pass/usage-triggered-bonus.ts
@@ -64,10 +64,9 @@ export function computeUsageTriggeredMonthlyBonusDecision(params: {
tier: params.tier,
streakMonths,
isFirstTimeSubscriberEver: isEligibleForFirstMonthPromo,
- subscriptionStartedAtIso: params.startedAtIso,
});
- const shouldIssueFirstMonthPromo = bonusPercentApplied === 0.5 && streakMonths <= 2;
+ const shouldIssueFirstMonthPromo = bonusPercentApplied === 0.5 && streakMonths === 1;
const decisionWithContext = {
monthlyBonusDecision: {
diff --git a/apps/web/src/lib/kilo-pass/usage-triggered-bonus.unit.test.ts b/apps/web/src/lib/kilo-pass/usage-triggered-bonus.unit.test.ts
index 2038e456e9..6a6349242d 100644
--- a/apps/web/src/lib/kilo-pass/usage-triggered-bonus.unit.test.ts
+++ b/apps/web/src/lib/kilo-pass/usage-triggered-bonus.unit.test.ts
@@ -5,7 +5,6 @@ import {
computeUsageTriggeredMonthlyBonusDecision,
computeUsageTriggeredYearlyIssueMonth,
} from '@/lib/kilo-pass/usage-triggered-bonus';
-import { KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF } from '@/lib/kilo-pass/constants';
describe('usage-triggered-bonus (unit)', () => {
describe('computeUsageTriggeredMonthlyBonusDecision', () => {
@@ -28,12 +27,10 @@ describe('usage-triggered-bonus (unit)', () => {
);
});
- test('eligible promo => shouldIssueFirstMonthPromo=true, bonusKind=promo-50pct, and promo description', () => {
+ test('first-time month 1 promo => shouldIssueFirstMonthPromo=true, bonusKind=promo-50pct, and promo description', () => {
const d = computeUsageTriggeredMonthlyBonusDecision({
tier: KiloPassTier.Tier19,
- startedAtIso: new Date(
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.valueOf() - 1
- ).toISOString(),
+ startedAtIso: '2026-01-01T00:00:00.000Z',
currentStreakMonths: 1,
isFirstTimeSubscriberEver: true,
issueMonth: '2026-01-01',
@@ -116,10 +113,10 @@ describe('usage-triggered-bonus (unit)', () => {
}
);
- test('ineligible at promo cutoff => uses ramp (not 50%) and bonusKind=monthly-ramp', () => {
+ test('first-time month 2 uses ramp (not 50%) and bonusKind=monthly-ramp', () => {
const d = computeUsageTriggeredMonthlyBonusDecision({
tier: KiloPassTier.Tier49,
- startedAtIso: KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.toISOString(),
+ startedAtIso: '2026-01-01T00:00:00.000Z',
currentStreakMonths: 2,
isFirstTimeSubscriberEver: true,
issueMonth: '2026-02-01',
diff --git a/apps/web/src/lib/referral.ts b/apps/web/src/lib/referral.ts
index 93330137b7..5b46e0c5e7 100644
--- a/apps/web/src/lib/referral.ts
+++ b/apps/web/src/lib/referral.ts
@@ -7,7 +7,7 @@ import {
} from '@kilocode/db/schema';
import { ImpactReferralProduct } from '@kilocode/db/schema-types';
import { db } from '@/lib/drizzle';
-import { eq, and, count, sql, isNull, isNotNull } from 'drizzle-orm';
+import { eq, and, count, sql, isNull, isNotNull, inArray } from 'drizzle-orm';
import { captureMessage } from '@sentry/nextjs';
import { grantCreditForCategory } from '@/lib/promotionalCredits';
import {
@@ -57,17 +57,20 @@ const redeemingReferralPromoCode = referralRedeemingBonus.credit_category;
const referringReferralPromoCode = referralReferringBonus.credit_category;
export async function processReferralTopUp(redeemingKiloUserId: string) {
- const [kiloclawReferralConversion] = await db
+ const [impactGovernedReferralConversion] = await db
.select({ id: impact_referral_conversions.id })
.from(impact_referral_conversions)
.where(
and(
- eq(impact_referral_conversions.product, ImpactReferralProduct.KiloClaw),
+ inArray(impact_referral_conversions.product, [
+ ImpactReferralProduct.KiloClaw,
+ ImpactReferralProduct.KiloPass,
+ ]),
eq(impact_referral_conversions.referee_user_id, redeemingKiloUserId)
)
)
.limit(1);
- if (kiloclawReferralConversion) {
+ if (impactGovernedReferralConversion) {
return;
}
diff --git a/apps/web/src/lib/referrals.test.ts b/apps/web/src/lib/referrals.test.ts
index a95f78e3cd..e0c4235575 100644
--- a/apps/web/src/lib/referrals.test.ts
+++ b/apps/web/src/lib/referrals.test.ts
@@ -328,7 +328,7 @@ describe('referrals', () => {
expect(creditTransactions).toHaveLength(0);
});
- it('grants legacy referral-code credits when only a Kilo Pass referral conversion exists', async () => {
+ it('does not grant legacy referral-code credits when a Kilo Pass Impact referral conversion exists', async () => {
const redeemingUser = await insertTestUser({
google_user_email: 'kilo-pass-referee@example.com',
google_user_name: 'Kilo Pass Referee',
@@ -364,14 +364,13 @@ describe('referrals', () => {
.select()
.from(credit_transactions)
.where(eq(credit_transactions.kilo_user_id, redeemingUser.id));
- expect(legacyCredits).toHaveLength(1);
- expect(legacyCredits[0].credit_category).toBe(referralRedeemingBonus.credit_category);
+ expect(legacyCredits).toHaveLength(0);
const [usage] = await db
.select()
.from(referral_code_usages)
.where(eq(referral_code_usages.redeeming_kilo_user_id, redeemingUser.id));
- expect(usage?.paid_at).not.toBeNull();
+ expect(usage?.paid_at).toBeNull();
});
it('does not grant legacy referral-code credits when a kiloclaw referral conversion exists', async () => {
diff --git a/apps/web/src/lib/stripe/index.test.ts b/apps/web/src/lib/stripe/index.test.ts
index 5b5c85f217..fdaa92f4fd 100644
--- a/apps/web/src/lib/stripe/index.test.ts
+++ b/apps/web/src/lib/stripe/index.test.ts
@@ -78,6 +78,9 @@ import {
stripe_early_fraud_warning_cases,
user_affiliate_attributions,
user_affiliate_events,
+ impact_referral_conversions,
+ impact_referral_reward_decisions,
+ impact_referral_rewards,
} from '@kilocode/db/schema';
import { db, auto_deleted_at } from '@/lib/drizzle';
import { insertTestUser } from '@/tests/helpers/user.helper';
@@ -92,6 +95,15 @@ import {
KiloPassScheduledChangeStatus,
KiloPassTier,
} from '@/lib/kilo-pass/enums';
+import {
+ ImpactReferralBeneficiaryRole,
+ ImpactReferralDecisionOutcome,
+ ImpactReferralPaymentProvider,
+ ImpactReferralProduct,
+ ImpactReferralRewardKind,
+ ImpactReferralRewardStatus,
+ ImpactReferralWinningTouchType,
+} from '@kilocode/db/schema-types';
import type * as kiloclawStripeHandlersModule from '@/lib/kiloclaw/stripe-handlers';
import type * as kiloPassStripeHandlersModule from '@/lib/kilo-pass/stripe-handlers';
import { cleanupDbForTest } from '@/lib/drizzle';
@@ -212,6 +224,90 @@ async function mockChargeRetrieveForKiloClaw(priceId: string) {
} as unknown as Stripe.Response);
}
+async function mockChargeRetrieveForKiloPass(invoiceId: string, userId: string) {
+ const { client } = await import('@/lib/stripe-client');
+ const invoice = {
+ id: invoiceId,
+ object: 'invoice',
+ parent: {
+ subscription_details: {
+ metadata: {
+ type: 'kilo-pass',
+ kiloUserId: userId,
+ tier: KiloPassTier.Tier19,
+ cadence: KiloPassCadence.Monthly,
+ },
+ },
+ },
+ lines: { data: [] },
+ } as unknown as Stripe.Invoice;
+ return jest.spyOn(client.charges, 'retrieve').mockResolvedValue({
+ invoice,
+ lastResponse: { headers: {}, requestId: 'req_test', statusCode: 200 },
+ } as unknown as Stripe.Response);
+}
+
+async function seedKiloPassReferralReward(params: {
+ userId: string;
+ invoiceId: string;
+ status: ImpactReferralRewardStatus;
+}) {
+ const [conversion] = await db
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: params.userId,
+ winning_touch_type: ImpactReferralWinningTouchType.Referral,
+ payment_provider: ImpactReferralPaymentProvider.Stripe,
+ source_payment_id: params.invoiceId,
+ qualified: true,
+ converted_at: '2026-01-03T00:00:00.000Z',
+ })
+ .returning({ id: impact_referral_conversions.id });
+ if (!conversion) throw new Error('Failed to insert Kilo Pass referral conversion');
+
+ const [decision] = await db
+ .insert(impact_referral_reward_decisions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ beneficiary_user_id: params.userId,
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referee,
+ outcome: ImpactReferralDecisionOutcome.Granted,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: 0.5,
+ source_tier: KiloPassTier.Tier19,
+ reward_amount_usd: 9.5,
+ })
+ .returning({ id: impact_referral_reward_decisions.id });
+ if (!decision) throw new Error('Failed to insert Kilo Pass referral decision');
+
+ const [reward] = await db
+ .insert(impact_referral_rewards)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ decision_id: decision.id,
+ beneficiary_user_id: params.userId,
+ beneficiary_role: ImpactReferralBeneficiaryRole.Referee,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: 0.5,
+ source_tier: KiloPassTier.Tier19,
+ reward_amount_usd: 9.5,
+ status: params.status,
+ earned_at: '2026-01-03T00:00:00.000Z',
+ applied_at:
+ params.status === ImpactReferralRewardStatus.Applied ? '2026-02-01T00:00:00.000Z' : null,
+ expires_at: '2027-01-03T00:00:00.000Z',
+ })
+ .returning({ id: impact_referral_rewards.id });
+ if (!reward) throw new Error('Failed to insert Kilo Pass referral reward');
+
+ return reward.id;
+}
+
const sampleStripeDispute = (
overrides: Partial & Pick
): Stripe.Dispute => {
@@ -1523,6 +1619,124 @@ describe('processStripePaymentEventHook', () => {
expect(reversalEvents[0]?.payload_json.disputeId).toBe('dp_kilo_pass_metadata_partial');
});
+ test('charge.dispute.created marks applied Kilo Pass referral rewards for support review', async () => {
+ await cleanupDbForTest();
+ testUser = await insertTestUser();
+ const invoiceId = 'in_kilo_pass_referral_dispute';
+ const rewardId = await seedKiloPassReferralReward({
+ userId: testUser.id,
+ invoiceId,
+ status: ImpactReferralRewardStatus.Applied,
+ });
+ const retrieveSpy = await mockChargeRetrieveForKiloPass(invoiceId, testUser.id);
+
+ const event: Stripe.Event = {
+ ...baseStripeEvent(),
+ id: 'evt_dispute_kilo_pass_referral_reward',
+ type: 'charge.dispute.created',
+ data: {
+ object: sampleStripeDispute({
+ id: 'dp_kilo_pass_referral_reward',
+ charge: 'ch_kilo_pass_referral_dispute',
+ }),
+ previous_attributes: {},
+ },
+ };
+
+ await processStripePaymentEventHook(event);
+ retrieveSpy.mockRestore();
+
+ const reward = await db.query.impact_referral_rewards.findFirst({
+ where: eq(impact_referral_rewards.id, rewardId),
+ });
+ expect(reward).toEqual(
+ expect.objectContaining({
+ status: ImpactReferralRewardStatus.ReviewRequired,
+ review_reason: 'referral_payment_chargeback',
+ })
+ );
+ });
+
+ test('charge.refunded cancels pending Kilo Pass referral rewards by Stripe invoice identity', async () => {
+ await cleanupDbForTest();
+ testUser = await insertTestUser();
+ const invoiceId = 'in_kilo_pass_referral_refund';
+ const rewardId = await seedKiloPassReferralReward({
+ userId: testUser.id,
+ invoiceId,
+ status: ImpactReferralRewardStatus.Pending,
+ });
+ const retrieveSpy = await mockChargeRetrieveForKiloPass(invoiceId, testUser.id);
+
+ const event: Stripe.Event = {
+ ...baseStripeEvent(),
+ id: 'evt_refund_kilo_pass_referral_reward',
+ type: 'charge.refunded',
+ data: {
+ object: {
+ id: 'ch_kilo_pass_referral_refund',
+ object: 'charge',
+ amount_refunded: 1900,
+ created: 1712743200,
+ } as unknown as Stripe.Charge,
+ previous_attributes: {},
+ },
+ };
+
+ await processStripePaymentEventHook(event);
+ retrieveSpy.mockRestore();
+
+ const reward = await db.query.impact_referral_rewards.findFirst({
+ where: eq(impact_referral_rewards.id, rewardId),
+ });
+ expect(reward).toEqual(
+ expect.objectContaining({
+ status: ImpactReferralRewardStatus.Canceled,
+ review_reason: 'referral_payment_refund',
+ })
+ );
+ });
+
+ test('charge.updated fraud marking cancels earned Kilo Pass referral rewards by Stripe invoice identity', async () => {
+ await cleanupDbForTest();
+ testUser = await insertTestUser();
+ const invoiceId = 'in_kilo_pass_referral_fraud';
+ const rewardId = await seedKiloPassReferralReward({
+ userId: testUser.id,
+ invoiceId,
+ status: ImpactReferralRewardStatus.Earned,
+ });
+ const retrieveSpy = await mockChargeRetrieveForKiloPass(invoiceId, testUser.id);
+
+ const event: Stripe.Event = {
+ ...baseStripeEvent(),
+ id: 'evt_fraud_kilo_pass_referral_reward',
+ type: 'charge.updated',
+ data: {
+ object: {
+ id: 'ch_kilo_pass_referral_fraud',
+ object: 'charge',
+ created: 1712743200,
+ fraud_details: { stripe_report: 'fraudulent' },
+ } as unknown as Stripe.Charge,
+ previous_attributes: {},
+ },
+ };
+
+ await processStripePaymentEventHook(event);
+ retrieveSpy.mockRestore();
+
+ const reward = await db.query.impact_referral_rewards.findFirst({
+ where: eq(impact_referral_rewards.id, rewardId),
+ });
+ expect(reward).toEqual(
+ expect.objectContaining({
+ status: ImpactReferralRewardStatus.Canceled,
+ review_reason: 'referral_payment_fraud',
+ })
+ );
+ });
+
test('charge.dispute.created persists pending row for unmatched KiloClaw charge', async () => {
await cleanupDbForTest();
testUser = await insertTestUser();
diff --git a/apps/web/src/lib/stripe/index.ts b/apps/web/src/lib/stripe/index.ts
index 5844891302..844e61fc55 100644
--- a/apps/web/src/lib/stripe/index.ts
+++ b/apps/web/src/lib/stripe/index.ts
@@ -57,6 +57,7 @@ import {
} from '@/lib/kiloclaw/stripe-handlers';
import { enqueueImpactSaleReversalForCharge } from '@/lib/impact/affiliate-events';
import { markPersonalKiloClawReferralPaymentAdverse } from '@/lib/impact/kiloclaw-referrals';
+import { markPersonalKiloPassReferralPaymentAdverse } from '@/lib/impact/kilo-pass-referrals';
import { ImpactReferralPaymentProvider } from '@kilocode/db/schema-types';
import { invoiceLooksLikeKiloClawByPriceId } from '@/lib/kiloclaw/stripe-invoice-classifier.server';
import { reportEvents } from '@/lib/ai-gateway/abuse-service';
@@ -89,20 +90,6 @@ type AffiliateDisputeChargeContext = KiloClawChargeContext & {
saleKind: AffiliateDisputeSaleKind;
};
-async function getKiloClawChargeContext(chargeId: string): Promise {
- const charge: Stripe.Charge & { invoice?: string | Stripe.Invoice | null } =
- await client.charges.retrieve(chargeId, { expand: ['invoice'] });
- const invoice = charge.invoice;
- if (!invoice || typeof invoice === 'string' || !invoiceLooksLikeKiloClawByPriceId(invoice)) {
- return null;
- }
-
- return {
- chargeId,
- invoiceId: invoice.id,
- };
-}
-
function getAffiliateDisputeSaleKind(invoice: Stripe.Invoice): AffiliateDisputeSaleKind | null {
if (invoiceLooksLikeKiloClawByPriceId(invoice)) {
return 'kiloclaw';
@@ -986,6 +973,13 @@ export async function processStripePaymentEventHook(event: Stripe.Event) {
reason: 'chargeback',
occurredAt: new Date(dispute.created * 1000),
});
+ } else {
+ await markPersonalKiloPassReferralPaymentAdverse({
+ sourcePaymentId: affiliateDisputeCharge.invoiceId,
+ paymentProvider: ImpactReferralPaymentProvider.Stripe,
+ reason: 'chargeback',
+ occurredAt: new Date(dispute.created * 1000),
+ });
}
break;
}
@@ -996,17 +990,26 @@ export async function processStripePaymentEventHook(event: Stripe.Event) {
break;
}
- const kiloClawCharge = await getKiloClawChargeContext(charge.id);
- if (!kiloClawCharge) {
+ const referralAdverseCharge = await getAffiliateDisputeChargeContext(charge.id);
+ if (!referralAdverseCharge) {
break;
}
- await markPersonalKiloClawReferralPaymentAdverse({
- sourcePaymentId: kiloClawCharge.invoiceId,
- paymentProvider: ImpactReferralPaymentProvider.Stripe,
- reason: 'refund',
- occurredAt: new Date(charge.created * 1000),
- });
+ if (referralAdverseCharge.saleKind === 'kiloclaw') {
+ await markPersonalKiloClawReferralPaymentAdverse({
+ sourcePaymentId: referralAdverseCharge.invoiceId,
+ paymentProvider: ImpactReferralPaymentProvider.Stripe,
+ reason: 'refund',
+ occurredAt: new Date(charge.created * 1000),
+ });
+ } else {
+ await markPersonalKiloPassReferralPaymentAdverse({
+ sourcePaymentId: referralAdverseCharge.invoiceId,
+ paymentProvider: ImpactReferralPaymentProvider.Stripe,
+ reason: 'refund',
+ occurredAt: new Date(charge.created * 1000),
+ });
+ }
break;
}
@@ -1019,17 +1022,26 @@ export async function processStripePaymentEventHook(event: Stripe.Event) {
break;
}
- const kiloClawCharge = await getKiloClawChargeContext(charge.id);
- if (!kiloClawCharge) {
+ const referralAdverseCharge = await getAffiliateDisputeChargeContext(charge.id);
+ if (!referralAdverseCharge) {
break;
}
- await markPersonalKiloClawReferralPaymentAdverse({
- sourcePaymentId: kiloClawCharge.invoiceId,
- paymentProvider: ImpactReferralPaymentProvider.Stripe,
- reason: 'fraud',
- occurredAt: new Date(charge.created * 1000),
- });
+ if (referralAdverseCharge.saleKind === 'kiloclaw') {
+ await markPersonalKiloClawReferralPaymentAdverse({
+ sourcePaymentId: referralAdverseCharge.invoiceId,
+ paymentProvider: ImpactReferralPaymentProvider.Stripe,
+ reason: 'fraud',
+ occurredAt: new Date(charge.created * 1000),
+ });
+ } else {
+ await markPersonalKiloPassReferralPaymentAdverse({
+ sourcePaymentId: referralAdverseCharge.invoiceId,
+ paymentProvider: ImpactReferralPaymentProvider.Stripe,
+ reason: 'fraud',
+ occurredAt: new Date(charge.created * 1000),
+ });
+ }
break;
}
diff --git a/apps/web/src/lib/user/index.test.ts b/apps/web/src/lib/user/index.test.ts
index 09a18a3e8e..cc5cd9277d 100644
--- a/apps/web/src/lib/user/index.test.ts
+++ b/apps/web/src/lib/user/index.test.ts
@@ -653,6 +653,7 @@ describe('User', () => {
});
const touchId = randomUUID();
const participantId = randomUUID();
+ const kiloPassParticipantId = randomUUID();
const conversionId = randomUUID();
const decisionId = randomUUID();
const rewardId = randomUUID();
@@ -669,21 +670,46 @@ describe('User', () => {
touched_at: '2026-04-23T00:00:00.000Z',
expires_at: '2026-05-23T00:00:00.000Z',
});
- await db.insert(impact_advocate_participants).values({
- id: participantId,
- user_id: user.id,
- advocate_id: user.id,
- advocate_account_id: user.id,
- contact_email: user.google_user_email,
- registration_state: 'pending',
- });
- await db.insert(impact_advocate_registration_attempts).values({
- participant_id: participantId,
- dedupe_key: 'registration-dedupe',
- opaque_cookie_value: 'sq-cookie',
- cookie_value_length: 9,
- delivery_state: 'queued',
- });
+ await db.insert(impact_advocate_participants).values([
+ {
+ id: participantId,
+ program_key: 'kiloclaw',
+ user_id: user.id,
+ advocate_id: user.google_user_email,
+ advocate_account_id: user.google_user_email,
+ contact_email: user.google_user_email,
+ registration_state: 'pending',
+ },
+ {
+ id: kiloPassParticipantId,
+ program_key: 'kilo_pass',
+ user_id: user.id,
+ advocate_id: user.google_user_email,
+ advocate_account_id: user.google_user_email,
+ contact_email: user.google_user_email,
+ registration_state: 'pending',
+ },
+ ]);
+ await db.insert(impact_advocate_registration_attempts).values([
+ {
+ program_key: 'kiloclaw',
+ participant_id: participantId,
+ dedupe_key: 'registration-dedupe-kiloclaw',
+ opaque_cookie_value: 'sq-cookie',
+ cookie_value_length: 9,
+ delivery_state: 'queued',
+ request_payload: { id: user.google_user_email, email: user.google_user_email },
+ },
+ {
+ program_key: 'kilo_pass',
+ participant_id: kiloPassParticipantId,
+ dedupe_key: 'registration-dedupe-kilo-pass',
+ opaque_cookie_value: 'sq-cookie-kilo-pass',
+ cookie_value_length: 19,
+ delivery_state: 'queued',
+ request_payload: { id: user.google_user_email, email: user.google_user_email },
+ },
+ ]);
await db.insert(impact_referrals).values({
referee_user_id: user.id,
referrer_user_id: referrer.id,
@@ -771,6 +797,11 @@ describe('User', () => {
.where(eq(impact_advocate_participants.user_id, user.id));
expect(participantCount.count).toBe(0);
+ const [registrationAttemptCount] = await db
+ .select({ count: count() })
+ .from(impact_advocate_registration_attempts);
+ expect(registrationAttemptCount.count).toBe(0);
+
const [redemptionCount] = await db
.select({ count: count() })
.from(impact_advocate_reward_redemptions)
@@ -782,6 +813,15 @@ describe('User', () => {
.from(impact_referral_conversions)
.where(eq(impact_referral_conversions.referee_user_id, user.id));
expect(conversionCount.count).toBe(0);
+
+ expect((await db.select({ count: count() }).from(impact_referrals))[0].count).toBe(0);
+ expect((await db.select({ count: count() }).from(impact_referral_rewards))[0].count).toBe(0);
+ expect(
+ (await db.select({ count: count() }).from(impact_referral_reward_applications))[0].count
+ ).toBe(0);
+ expect((await db.select({ count: count() }).from(impact_conversion_reports))[0].count).toBe(
+ 0
+ );
});
it('falls back to google_user_email when normalized_email is null', async () => {
diff --git a/apps/web/src/lib/user/server.ts b/apps/web/src/lib/user/server.ts
index 71fb3a6b13..a449a7f374 100644
--- a/apps/web/src/lib/user/server.ts
+++ b/apps/web/src/lib/user/server.ts
@@ -428,14 +428,18 @@ async function getImpactTrackingContextFromAuthFlow(requestHeaders?: Headers): P
const ignoreUrlImRefForReferralTouch = Boolean(
referralTouch?.opaqueTrackingValue && urlImRefParam
);
- const fallbackUrl = new URL('http://localhost/users/after-sign-in');
+ const affiliateCookieFallbackUrl = new URL('http://localhost/users/after-sign-in');
+ const callbackPath = callbackUrl.searchParams.get('callbackPath')?.trim();
+ if (callbackPath) {
+ affiliateCookieFallbackUrl.searchParams.set('callbackPath', callbackPath);
+ }
const affiliateTouch = ignoreUrlImRefForReferralTouch
? cookieTrackingId && cookieTrackingId !== urlImRefParam
- ? parseImpactAffiliateTouchFromUrl(fallbackUrl, cookieTrackingId)
+ ? parseImpactAffiliateTouchFromUrl(affiliateCookieFallbackUrl, cookieTrackingId)
: null
: (parseImpactAffiliateTouchFromUrl(callbackUrl) ??
(cookieTrackingId
- ? parseImpactAffiliateTouchFromUrl(fallbackUrl, cookieTrackingId)
+ ? parseImpactAffiliateTouchFromUrl(affiliateCookieFallbackUrl, cookieTrackingId)
: null));
logImpactReferralDebug('Auth flow parsed Impact tracking context from callback URL cookie', {
diff --git a/apps/web/src/routers/admin/kiloclaw-referrals-router.test.ts b/apps/web/src/routers/admin/kiloclaw-referrals-router.test.ts
index 7a910e9f15..bfc0d0d8f6 100644
--- a/apps/web/src/routers/admin/kiloclaw-referrals-router.test.ts
+++ b/apps/web/src/routers/admin/kiloclaw-referrals-router.test.ts
@@ -5,6 +5,8 @@ import { cleanupDbForTest, db } from '@/lib/drizzle';
import { createCallerForUser } from '@/routers/test-utils';
import { insertTestUser } from '@/tests/helpers/user.helper';
import {
+ impact_advocate_participants,
+ impact_advocate_registration_attempts,
impact_advocate_reward_redemptions,
impact_conversion_reports,
impact_attribution_touches,
@@ -35,13 +37,50 @@ beforeEach(async () => {
});
});
+async function insertParticipantRegistration(params: {
+ product: 'kiloclaw' | 'kilo_pass';
+ state: 'pending' | 'registered' | 'failed';
+ attemptState: 'queued' | 'succeeded' | 'failed';
+}) {
+ const [participant] = await db
+ .insert(impact_advocate_participants)
+ .values({
+ program_key: params.product,
+ user_id: referrer.id,
+ advocate_id: `${params.product}:${referrer.google_user_email}`,
+ advocate_account_id: `${params.product}:${referrer.google_user_email}`,
+ contact_email: referrer.google_user_email,
+ opaque_referral_identifier: `${params.product}-RS-SUPPORT`,
+ registration_state: params.state,
+ last_error_code: params.state === 'failed' ? 'invalid_payload' : null,
+ last_error_message: params.state === 'failed' ? 'Payload rejected' : null,
+ })
+ .returning({ id: impact_advocate_participants.id });
+
+ await db.insert(impact_advocate_registration_attempts).values({
+ program_key: params.product,
+ participant_id: participant.id,
+ dedupe_key: `${params.product}-registration-attempt`,
+ opaque_cookie_value: `${params.product}-opaque-cookie`,
+ cookie_value_length: 23,
+ delivery_state: params.attemptState,
+ response_status_code: params.attemptState === 'failed' ? 400 : null,
+ next_retry_at: params.attemptState === 'queued' ? '2026-04-11T00:00:00.000Z' : null,
+ });
+}
+
async function insertReferralInvestigationRow(params: {
+ product?: 'kiloclaw' | 'kilo_pass';
refereeEmail: string;
sourcePaymentId: string;
qualified: boolean;
disqualificationReason: string | null;
- reportState: 'delivered' | 'failed';
+ reportState: 'queued' | 'delivered' | 'failed';
+ rewardStatus?: 'pending' | 'applied' | 'canceled' | 'review_required';
}) {
+ const product = params.product ?? 'kiloclaw';
+ const rewardKind = product === 'kilo_pass' ? 'kilo_pass_bonus' : 'kiloclaw_free_month';
+ const rewardStatus = params.rewardStatus ?? 'applied';
const referee = await insertTestUser({
google_user_email: params.refereeEmail,
normalized_email: params.refereeEmail,
@@ -49,7 +88,9 @@ async function insertReferralInvestigationRow(params: {
const [touch] = await db
.insert(impact_attribution_touches)
.values({
- dedupe_key: `touch-${params.sourcePaymentId}`,
+ product,
+ program_key: product,
+ dedupe_key: `touch-${product}-${params.sourcePaymentId}`,
user_id: referee.id,
touch_type: 'referral',
provider: 'impact_advocate',
@@ -62,6 +103,7 @@ async function insertReferralInvestigationRow(params: {
})
.returning({ id: impact_attribution_touches.id });
await db.insert(impact_referrals).values({
+ product,
referee_user_id: referee.id,
referrer_user_id: referrer.id,
source_touch_id: touch.id,
@@ -70,10 +112,12 @@ async function insertReferralInvestigationRow(params: {
const [conversion] = await db
.insert(impact_referral_conversions)
.values({
+ product,
referee_user_id: referee.id,
referrer_user_id: referrer.id,
source_touch_id: touch.id,
winning_touch_type: 'referral',
+ payment_provider: product === 'kilo_pass' ? 'stripe' : 'credits',
source_payment_id: params.sourcePaymentId,
qualified: params.qualified,
disqualification_reason: params.disqualificationReason,
@@ -83,12 +127,17 @@ async function insertReferralInvestigationRow(params: {
const [decision] = await db
.insert(impact_referral_reward_decisions)
.values({
+ product,
conversion_id: conversion.id,
beneficiary_user_id: referrer.id,
beneficiary_role: 'referrer',
outcome: params.qualified ? 'granted' : 'disqualified',
reason: params.disqualificationReason,
- months_granted: params.qualified ? 1 : 0,
+ reward_kind: rewardKind,
+ months_granted: product === 'kiloclaw' && params.qualified ? 1 : 0,
+ reward_percent: product === 'kilo_pass' ? 0.5 : null,
+ source_tier: product === 'kilo_pass' ? 'tier_49' : null,
+ reward_amount_usd: product === 'kilo_pass' && params.qualified ? 24.5 : null,
})
.returning({ id: impact_referral_reward_decisions.id });
@@ -96,37 +145,49 @@ async function insertReferralInvestigationRow(params: {
const [reward] = await db
.insert(impact_referral_rewards)
.values({
+ product,
conversion_id: conversion.id,
decision_id: decision.id,
beneficiary_user_id: referrer.id,
beneficiary_role: 'referrer',
- months_granted: 1,
- status: 'applied',
+ reward_kind: rewardKind,
+ months_granted: product === 'kiloclaw' ? 1 : 0,
+ reward_percent: product === 'kilo_pass' ? 0.5 : null,
+ source_tier: product === 'kilo_pass' ? 'tier_49' : null,
+ reward_amount_usd: product === 'kilo_pass' ? 24.5 : null,
+ status: rewardStatus,
earned_at: '2026-04-10T00:00:00.000Z',
- applied_at: '2026-04-10T00:05:00.000Z',
+ applied_at: rewardStatus === 'applied' ? '2026-04-10T00:05:00.000Z' : null,
+ expires_at: '2027-04-10T00:00:00.000Z',
+ review_reason: rewardStatus === 'review_required' ? 'referral_payment_chargeback' : null,
})
.returning({ id: impact_referral_rewards.id });
- await db.insert(impact_referral_reward_applications).values({
- reward_id: reward.id,
- beneficiary_user_id: referrer.id,
- subscription_id: crypto.randomUUID(),
- previous_renewal_boundary: '2026-05-01T00:00:00.000Z',
- new_renewal_boundary: '2026-06-01T00:00:00.000Z',
- applied_at: '2026-04-10T00:05:00.000Z',
- });
- await db.insert(impact_advocate_reward_redemptions).values({
- reward_id: reward.id,
- dedupe_key: `reward-redemption-${params.sourcePaymentId}`,
- beneficiary_user_id: referrer.id,
- state: 'redeemed',
- impact_reward_id: `impact-reward-${params.sourcePaymentId}`,
- redeemed_at: '2026-04-10T00:06:00.000Z',
- });
+ if (rewardStatus === 'applied') {
+ await db.insert(impact_referral_reward_applications).values({
+ product,
+ reward_id: reward.id,
+ beneficiary_user_id: referrer.id,
+ subscription_id: crypto.randomUUID(),
+ previous_renewal_boundary: '2026-05-01T00:00:00.000Z',
+ new_renewal_boundary: '2026-06-01T00:00:00.000Z',
+ applied_at: '2026-04-10T00:05:00.000Z',
+ });
+ }
+ if (product === 'kiloclaw') {
+ await db.insert(impact_advocate_reward_redemptions).values({
+ reward_id: reward.id,
+ dedupe_key: `reward-redemption-${params.sourcePaymentId}`,
+ beneficiary_user_id: referrer.id,
+ state: 'redeemed',
+ impact_reward_id: `impact-reward-${params.sourcePaymentId}`,
+ redeemed_at: '2026-04-10T00:06:00.000Z',
+ });
+ }
}
await db.insert(impact_conversion_reports).values({
conversion_id: conversion.id,
- dedupe_key: `impact-report-${params.sourcePaymentId}`,
+ dedupe_key: `impact-report-${product}-${params.sourcePaymentId}`,
action_tracker_id: 71659,
order_id: params.sourcePaymentId,
state: params.reportState,
@@ -167,6 +228,9 @@ describe('admin kiloclaw referrals investigation', () => {
search: referrer.google_user_email,
});
+ expect(result.product).toBe('kiloclaw');
+ expect(result.productLabel).toBe('KiloClaw');
+ expect(result.participantRegistrations).toEqual([]);
expect(result.referrer).toEqual(
expect.objectContaining({ id: referrer.id, email: referrer.google_user_email })
);
@@ -177,8 +241,21 @@ describe('admin kiloclaw referrals investigation', () => {
id: qualifiedReferee.id,
email: qualifiedReferee.google_user_email,
}),
- conversion: expect.objectContaining({ qualified: true, disqualificationReason: null }),
- rewardDecisions: [expect.objectContaining({ outcome: 'granted', monthsGranted: 1 })],
+ referral: expect.objectContaining({ product: 'kiloclaw', productLabel: 'KiloClaw' }),
+ conversion: expect.objectContaining({
+ product: 'kiloclaw',
+ paymentProvider: 'credits',
+ qualified: true,
+ disqualificationReason: null,
+ }),
+ rewardDecisions: [
+ expect.objectContaining({
+ product: 'kiloclaw',
+ rewardKind: 'kiloclaw_free_month',
+ outcome: 'granted',
+ monthsGranted: 1,
+ }),
+ ],
rewardApplications: [
expect.objectContaining({
previousRenewalBoundary: '2026-05-01T00:00:00.000Z',
@@ -212,4 +289,100 @@ describe('admin kiloclaw referrals investigation', () => {
.where(eq(impact_conversion_reports.state, 'failed'));
expect(reports).toHaveLength(1);
});
+
+ it('filters Kilo Pass referrals and exposes operations states', async () => {
+ await insertParticipantRegistration({
+ product: 'kilo_pass',
+ state: 'pending',
+ attemptState: 'queued',
+ });
+ await insertReferralInvestigationRow({
+ product: 'kiloclaw',
+ refereeEmail: `claw-referee-${Math.random()}@example.com`,
+ sourcePaymentId: 'claw-payment',
+ qualified: true,
+ disqualificationReason: null,
+ reportState: 'delivered',
+ });
+ const pendingReferee = await insertReferralInvestigationRow({
+ product: 'kilo_pass',
+ refereeEmail: `kilo-pass-pending-${Math.random()}@example.com`,
+ sourcePaymentId: 'kp-pending-invoice',
+ qualified: true,
+ disqualificationReason: null,
+ reportState: 'queued',
+ rewardStatus: 'pending',
+ });
+ const reviewReferee = await insertReferralInvestigationRow({
+ product: 'kilo_pass',
+ refereeEmail: `kilo-pass-review-${Math.random()}@example.com`,
+ sourcePaymentId: 'kp-review-invoice',
+ qualified: true,
+ disqualificationReason: null,
+ reportState: 'failed',
+ rewardStatus: 'review_required',
+ });
+
+ const caller = await createCallerForUser(admin.id);
+ const result = await caller.admin.kiloclawReferrals.investigateReferrer({
+ search: referrer.id,
+ product: 'kilo_pass',
+ });
+
+ expect(result.product).toBe('kilo_pass');
+ expect(result.productLabel).toBe('Kilo Pass');
+ expect(result.participantRegistrations).toEqual([
+ expect.objectContaining({
+ programKey: 'kilo_pass',
+ registrationState: 'pending',
+ latestAttempt: expect.objectContaining({
+ deliveryState: 'queued',
+ nextRetryAt: '2026-04-11T00:00:00.000Z',
+ }),
+ }),
+ ]);
+ expect(result.referrals).toHaveLength(2);
+ expect(result.referrals).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ referee: expect.objectContaining({ id: pendingReferee.id }),
+ referral: expect.objectContaining({ product: 'kilo_pass', productLabel: 'Kilo Pass' }),
+ conversion: expect.objectContaining({
+ product: 'kilo_pass',
+ paymentProvider: 'stripe',
+ qualified: true,
+ }),
+ rewardDecisions: [
+ expect.objectContaining({
+ rewardKind: 'kilo_pass_bonus',
+ rewardPercent: 0.5,
+ sourceTier: 'tier_49',
+ rewardAmountUsd: 24.5,
+ }),
+ ],
+ rewards: [
+ expect.objectContaining({
+ rewardKind: 'kilo_pass_bonus',
+ status: 'pending',
+ rewardAmountUsd: 24.5,
+ expiresAt: '2027-04-10T00:00:00.000Z',
+ }),
+ ],
+ rewardApplications: [],
+ impactReports: [expect.objectContaining({ state: 'queued' })],
+ impactRewardRedemptions: [],
+ }),
+ expect.objectContaining({
+ referee: expect.objectContaining({ id: reviewReferee.id }),
+ rewards: [
+ expect.objectContaining({
+ status: 'review_required',
+ reviewReason: 'referral_payment_chargeback',
+ }),
+ ],
+ impactReports: [expect.objectContaining({ state: 'failed' })],
+ }),
+ ])
+ );
+ });
});
diff --git a/apps/web/src/routers/admin/kiloclaw-referrals-router.ts b/apps/web/src/routers/admin/kiloclaw-referrals-router.ts
index f7ae532f1e..3c994e12c7 100644
--- a/apps/web/src/routers/admin/kiloclaw-referrals-router.ts
+++ b/apps/web/src/routers/admin/kiloclaw-referrals-router.ts
@@ -5,6 +5,8 @@ import { and, desc, eq, inArray, or } from 'drizzle-orm';
import { adminProcedure, createTRPCRouter } from '@/lib/trpc/init';
import { db } from '@/lib/drizzle';
import {
+ impact_advocate_participants,
+ impact_advocate_registration_attempts,
impact_advocate_reward_redemptions,
impact_conversion_reports,
impact_attribution_touches,
@@ -17,22 +19,52 @@ import {
} from '@kilocode/db/schema';
import { ImpactReferralProduct, ImpactReferralRewardKind } from '@kilocode/db/schema-types';
+const ReferralProductSchema = z.enum([
+ ImpactReferralProduct.KiloClaw,
+ ImpactReferralProduct.KiloPass,
+]);
+
const ReferralInvestigationInputSchema = z.object({
search: z.string().trim().min(1),
+ product: ReferralProductSchema.default(ImpactReferralProduct.KiloClaw),
});
const NullableString = z.string().nullable();
const ReferralInvestigationOutputSchema = z.object({
+ product: ReferralProductSchema,
+ productLabel: z.string(),
referrer: z.object({
id: z.string(),
email: NullableString,
name: NullableString,
}),
+ participantRegistrations: z.array(
+ z.object({
+ id: z.string().uuid(),
+ programKey: ReferralProductSchema,
+ registrationState: z.string(),
+ registeredAt: NullableString,
+ lastRegistrationAttemptAt: NullableString,
+ lastErrorCode: NullableString,
+ lastErrorMessage: NullableString,
+ latestAttempt: z
+ .object({
+ id: z.string().uuid(),
+ deliveryState: z.string(),
+ responseStatusCode: z.number().nullable(),
+ nextRetryAt: NullableString,
+ createdAt: z.string(),
+ })
+ .nullable(),
+ })
+ ),
referrals: z.array(
z.object({
referral: z.object({
id: z.string().uuid(),
+ product: ReferralProductSchema,
+ productLabel: z.string(),
impactReferralId: NullableString,
createdAt: z.string(),
}),
@@ -56,6 +88,8 @@ const ReferralInvestigationOutputSchema = z.object({
conversion: z
.object({
id: z.string().uuid(),
+ product: ReferralProductSchema,
+ paymentProvider: z.string(),
winningTouchType: z.string(),
sourcePaymentId: z.string(),
qualified: z.boolean(),
@@ -66,34 +100,50 @@ const ReferralInvestigationOutputSchema = z.object({
rewardDecisions: z.array(
z.object({
id: z.string().uuid(),
+ product: ReferralProductSchema,
beneficiaryUserId: z.string(),
beneficiaryRole: z.string(),
outcome: z.string(),
reason: NullableString,
+ rewardKind: z.string(),
monthsGranted: z.number(),
+ rewardPercent: z.number().nullable(),
+ sourceTier: NullableString,
+ rewardAmountUsd: z.number().nullable(),
createdAt: z.string(),
})
),
rewards: z.array(
z.object({
id: z.string().uuid(),
+ product: ReferralProductSchema,
beneficiaryUserId: z.string(),
beneficiaryRole: z.string(),
+ rewardKind: z.string(),
status: z.string(),
monthsGranted: z.number(),
+ rewardPercent: z.number().nullable(),
+ sourceTier: NullableString,
+ rewardAmountUsd: z.number().nullable(),
earnedAt: z.string(),
appliedAt: NullableString,
expiresAt: NullableString,
reviewReason: NullableString,
+ appliesToKiloPassSubscriptionId: z.string().uuid().nullable(),
+ consumedKiloPassIssuanceId: z.string().uuid().nullable(),
+ consumedKiloPassIssuanceItemId: z.string().uuid().nullable(),
})
),
rewardApplications: z.array(
z.object({
id: z.string().uuid(),
+ product: ReferralProductSchema,
beneficiaryUserId: z.string(),
subscriptionId: z.string().uuid().nullable(),
previousRenewalBoundary: z.string(),
newRenewalBoundary: z.string(),
+ localOperationId: NullableString,
+ stripeOperationId: NullableString,
appliedAt: z.string(),
})
),
@@ -139,6 +189,22 @@ function listByConversionId(
return rows.filter(row => row.conversionId === conversionId);
}
+function getProductLabel(product: ImpactReferralProduct): string {
+ return product === ImpactReferralProduct.KiloPass ? 'Kilo Pass' : 'KiloClaw';
+}
+
+function getRewardKindForProduct(product: ImpactReferralProduct): ImpactReferralRewardKind {
+ return product === ImpactReferralProduct.KiloPass
+ ? ImpactReferralRewardKind.KiloPassBonus
+ : ImpactReferralRewardKind.KiloClawFreeMonth;
+}
+
+function latestAttemptForParticipant<
+ T extends { participantId: string; createdAt: string | null | undefined },
+>(attempts: T[], participantId: string): T | null {
+ return attempts.find(attempt => attempt.participantId === participantId) ?? null;
+}
+
async function findReferrer(search: string) {
const normalizedSearch = search.trim().toLowerCase();
const [referrer] = await db
@@ -161,15 +227,62 @@ async function findReferrer(search: string) {
return referrer ?? null;
}
-async function investigateReferrer(search: string): Promise {
+async function investigateReferrer(
+ search: string,
+ product: ImpactReferralProduct
+): Promise {
const referrer = await findReferrer(search);
if (!referrer) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Referrer not found.' });
}
+ const productLabel = getProductLabel(product);
+ const rewardKind = getRewardKindForProduct(product);
+
+ const participantRows = await db
+ .select({
+ id: impact_advocate_participants.id,
+ programKey: impact_advocate_participants.program_key,
+ registrationState: impact_advocate_participants.registration_state,
+ registeredAt: impact_advocate_participants.registered_at,
+ lastRegistrationAttemptAt: impact_advocate_participants.last_registration_attempt_at,
+ lastErrorCode: impact_advocate_participants.last_error_code,
+ lastErrorMessage: impact_advocate_participants.last_error_message,
+ })
+ .from(impact_advocate_participants)
+ .where(
+ and(
+ eq(impact_advocate_participants.program_key, product),
+ eq(impact_advocate_participants.user_id, referrer.id)
+ )
+ )
+ .orderBy(desc(impact_advocate_participants.created_at));
+
+ const participantIds = participantRows.map(participant => participant.id);
+ const participantAttempts = participantIds.length
+ ? await db
+ .select({
+ participantId: impact_advocate_registration_attempts.participant_id,
+ id: impact_advocate_registration_attempts.id,
+ deliveryState: impact_advocate_registration_attempts.delivery_state,
+ responseStatusCode: impact_advocate_registration_attempts.response_status_code,
+ nextRetryAt: impact_advocate_registration_attempts.next_retry_at,
+ createdAt: impact_advocate_registration_attempts.created_at,
+ })
+ .from(impact_advocate_registration_attempts)
+ .where(
+ and(
+ eq(impact_advocate_registration_attempts.program_key, product),
+ inArray(impact_advocate_registration_attempts.participant_id, participantIds)
+ )
+ )
+ .orderBy(desc(impact_advocate_registration_attempts.created_at))
+ : [];
+
const referralRows = await db
.select({
referralId: impact_referrals.id,
+ referralProduct: impact_referrals.product,
impactReferralId: impact_referrals.impact_referral_id,
referralCreatedAt: impact_referrals.created_at,
refereeId: kilocode_users.id,
@@ -191,17 +304,16 @@ async function investigateReferrer(search: string): Promise {
+ const latestAttempt = latestAttemptForParticipant(participantAttempts, participant.id);
+ return {
+ id: participant.id,
+ programKey: participant.programKey,
+ registrationState: participant.registrationState,
+ registeredAt: normalizeTimestamp(participant.registeredAt),
+ lastRegistrationAttemptAt: normalizeTimestamp(participant.lastRegistrationAttemptAt),
+ lastErrorCode: participant.lastErrorCode,
+ lastErrorMessage: participant.lastErrorMessage,
+ latestAttempt: latestAttempt
+ ? {
+ id: latestAttempt.id,
+ deliveryState: latestAttempt.deliveryState,
+ responseStatusCode: latestAttempt.responseStatusCode,
+ nextRetryAt: normalizeTimestamp(latestAttempt.nextRetryAt),
+ createdAt: normalizeTimestamp(latestAttempt.createdAt) ?? latestAttempt.createdAt,
+ }
+ : null,
+ };
+ }),
referrals: referralRows.map(referral => {
const conversion = conversions.find(row => row.refereeUserId === referral.refereeId) ?? null;
const conversionId = conversion?.id ?? null;
@@ -350,6 +500,8 @@ async function investigateReferrer(search: string): Promise ({
id: decision.id,
+ product: decision.product,
beneficiaryUserId: decision.beneficiaryUserId,
beneficiaryRole: decision.beneficiaryRole,
outcome: decision.outcome,
reason: decision.reason,
+ rewardKind: decision.rewardKind,
monthsGranted: decision.monthsGranted,
+ rewardPercent: decision.rewardPercent,
+ sourceTier: decision.sourceTier,
+ rewardAmountUsd: decision.rewardAmountUsd,
createdAt: normalizeTimestamp(decision.createdAt) ?? decision.createdAt,
}))
: [],
rewards: conversionId
? listByConversionId(rewards, conversionId).map(reward => ({
id: reward.id,
+ product: reward.product,
beneficiaryUserId: reward.beneficiaryUserId,
beneficiaryRole: reward.beneficiaryRole,
+ rewardKind: reward.rewardKind,
status: reward.status,
monthsGranted: reward.monthsGranted,
+ rewardPercent: reward.rewardPercent,
+ sourceTier: reward.sourceTier,
+ rewardAmountUsd: reward.rewardAmountUsd,
earnedAt: normalizeTimestamp(reward.earnedAt) ?? reward.earnedAt,
appliedAt: normalizeTimestamp(reward.appliedAt),
expiresAt: normalizeTimestamp(reward.expiresAt),
reviewReason: reward.reviewReason,
+ appliesToKiloPassSubscriptionId: reward.appliesToKiloPassSubscriptionId,
+ consumedKiloPassIssuanceId: reward.consumedKiloPassIssuanceId,
+ consumedKiloPassIssuanceItemId: reward.consumedKiloPassIssuanceItemId,
}))
: [],
rewardApplications: conversionId
? listByConversionId(rewardApplications, conversionId).map(application => ({
id: application.id,
+ product: application.product,
beneficiaryUserId: application.beneficiaryUserId,
subscriptionId: application.subscriptionId,
previousRenewalBoundary:
@@ -415,6 +583,8 @@ async function investigateReferrer(search: string): Promise {
- return await investigateReferrer(input.search);
+ return await investigateReferrer(input.search, input.product);
}),
});
diff --git a/apps/web/src/routers/kilo-pass-router.test.ts b/apps/web/src/routers/kilo-pass-router.test.ts
index 4cfdd6784b..a137e6466a 100644
--- a/apps/web/src/routers/kilo-pass-router.test.ts
+++ b/apps/web/src/routers/kilo-pass-router.test.ts
@@ -6,6 +6,9 @@ import {
kilo_pass_issuance_items,
kilo_pass_issuances,
kilo_pass_pause_events,
+ impact_referral_conversions,
+ impact_referral_reward_decisions,
+ impact_referral_rewards,
kilo_pass_scheduled_changes,
kilo_pass_store_purchases,
kilo_pass_subscriptions,
@@ -22,6 +25,15 @@ import {
KiloPassTier,
KiloPassWelcomePromoEligibilityReason,
} from '@/lib/kilo-pass/enums';
+import {
+ ImpactReferralBeneficiaryRole,
+ ImpactReferralDecisionOutcome,
+ ImpactReferralPaymentProvider,
+ ImpactReferralProduct,
+ ImpactReferralRewardKind,
+ ImpactReferralRewardStatus,
+ ImpactReferralWinningTouchType,
+} from '@kilocode/db/schema-types';
import { and, eq, isNull } from 'drizzle-orm';
import crypto from 'crypto';
import {
@@ -29,10 +41,7 @@ import {
computeYearlyCadenceMonthlyBonusUsd,
getMonthlyPriceUsd,
} from '@/lib/kilo-pass/bonus';
-import {
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_BONUS_PERCENT,
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF,
-} from '@/lib/kilo-pass/constants';
+import { KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT } from '@/lib/kilo-pass/constants';
import { insertTestUser } from '@/tests/helpers/user.helper';
import type { insertMicrodollarUsageWithDailyRollup as insertMicrodollarUsageWithDailyRollupType } from '@/tests/helpers/microdollar-usage.helper';
@@ -43,8 +52,8 @@ import type dayjsType from 'dayjs';
import type utcType from 'dayjs/plugin/utc';
import type * as Sentry from '@sentry/nextjs';
-const PROMO_OFFER_ACTIVE_TEST_TIME = '2026-05-06T12:00:00.000Z';
-const PROMO_OFFER_EXPIRED_TEST_TIME = '2026-05-07T00:00:00.000Z';
+const FIRST_MONTH_PROMO_TEST_TIME = '2026-05-25T00:00:00.000Z';
+const LATER_FIRST_MONTH_PROMO_TEST_TIME = '2026-05-26T00:00:00.000Z';
let mockKiloPassNowIso: string | null = null;
@@ -183,6 +192,31 @@ type KiloPassCaller = {
hasMore: boolean;
cursor: string | null;
}>;
+ getReferralRewardSummary: () => Promise<{
+ totals: {
+ totalRewards: number;
+ pendingRewards: number;
+ appliedRewards: number;
+ totalRewardAmountUsd: number;
+ pendingRewardAmountUsd: number;
+ appliedRewardAmountUsd: number;
+ };
+ referrerCap: {
+ grantedRewards: number;
+ limit: number;
+ reached: boolean;
+ };
+ rewards: Array<{
+ role: 'referrer' | 'referee';
+ status: string;
+ rewardAmountUsd: number;
+ earnedAt: string;
+ appliedAt: string | null;
+ expiresAt: string | null;
+ sourceTier: string | null;
+ reviewReason: string | null;
+ }>;
+ }>;
};
type Caller = { kiloPass: KiloPassCaller };
@@ -401,6 +435,75 @@ async function insertBaseCreditsIssuance(params: {
}
}
+async function insertKiloPassReferralReward(params: {
+ beneficiaryUserId: string;
+ role: ImpactReferralBeneficiaryRole;
+ status: ImpactReferralRewardStatus;
+ rewardAmountUsd: number;
+ sourceTier: KiloPassTier;
+ earnedAt: string;
+ appliedAt?: string | null;
+ expiresAt?: string | null;
+ sourcePaymentId?: string;
+}): Promise {
+ const otherUser = await insertTestUser();
+ const isReferrerReward = params.role === ImpactReferralBeneficiaryRole.Referrer;
+ const [conversion] = await db
+ .insert(impact_referral_conversions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ referee_user_id: isReferrerReward ? otherUser.id : params.beneficiaryUserId,
+ referrer_user_id: isReferrerReward ? params.beneficiaryUserId : otherUser.id,
+ winning_touch_type: ImpactReferralWinningTouchType.Referral,
+ payment_provider: ImpactReferralPaymentProvider.Stripe,
+ source_payment_id: params.sourcePaymentId ?? `in_referral_${crypto.randomUUID()}`,
+ qualified: true,
+ converted_at: params.earnedAt,
+ })
+ .returning({ id: impact_referral_conversions.id });
+
+ if (!conversion) {
+ throw new Error('Failed to insert impact_referral_conversions row for test');
+ }
+
+ const [decision] = await db
+ .insert(impact_referral_reward_decisions)
+ .values({
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ beneficiary_user_id: params.beneficiaryUserId,
+ beneficiary_role: params.role,
+ outcome: ImpactReferralDecisionOutcome.Granted,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: 0.5,
+ source_tier: params.sourceTier,
+ reward_amount_usd: params.rewardAmountUsd,
+ })
+ .returning({ id: impact_referral_reward_decisions.id });
+
+ if (!decision) {
+ throw new Error('Failed to insert impact_referral_reward_decisions row for test');
+ }
+
+ await db.insert(impact_referral_rewards).values({
+ product: ImpactReferralProduct.KiloPass,
+ conversion_id: conversion.id,
+ decision_id: decision.id,
+ beneficiary_user_id: params.beneficiaryUserId,
+ beneficiary_role: params.role,
+ reward_kind: ImpactReferralRewardKind.KiloPassBonus,
+ months_granted: 0,
+ reward_percent: 0.5,
+ source_tier: params.sourceTier,
+ reward_amount_usd: params.rewardAmountUsd,
+ status: params.status,
+ earned_at: params.earnedAt,
+ applied_at: params.appliedAt ?? null,
+ expires_at: params.expiresAt ?? null,
+ });
+}
+
describe('kiloPassRouter', () => {
beforeAll(async () => {
// Delay importing the tRPC caller factory until after mocks are registered,
@@ -556,7 +659,7 @@ describe('kiloPassRouter', () => {
describe('getState', () => {
it('returns null subscription when user has no Kilo Pass subscription', async () => {
- freezeKiloPassClock(PROMO_OFFER_ACTIVE_TEST_TIME);
+ freezeKiloPassClock(FIRST_MONTH_PROMO_TEST_TIME);
const user = await insertTestUser({
google_user_email: 'kilo-pass-get-state-empty@example.com',
@@ -854,7 +957,6 @@ describe('kiloPassRouter', () => {
tier: KiloPassTier.Tier19,
streakMonths: 1,
isFirstTimeSubscriberEver: true,
- subscriptionStartedAtIso: '2026-01-01T00:00:00.000Z',
});
const currentBonusUsd = Math.round(baseAmountUsd * currentBonusPercent * 100) / 100;
@@ -1102,13 +1204,13 @@ describe('kiloPassRouter', () => {
);
});
- it('keeps App Store month-2 grandfather bonus after a post-cutoff renewal', async () => {
+ it('uses the App Store month-2 ramp bonus after renewal', async () => {
freezeKiloPassClock('2026-06-15T00:00:00.000Z');
const user = await insertTestUser({
- google_user_email: 'kilo-pass-get-state-app-store-grandfathered-renewal@example.com',
+ google_user_email: 'kilo-pass-get-state-app-store-month2_ramp-renewal@example.com',
});
- const providerSubscriptionId = 'orig_get_state_app_store_grandfathered_renewal';
+ const providerSubscriptionId = 'orig_get_state_app_store_month2_ramp_renewal';
const renewalExpiresAt = '2026-07-01T00:00:00.000Z';
const { id: subscriptionId } = await insertSubscription({
kiloUserId: user.id,
@@ -1129,7 +1231,7 @@ describe('kiloPassRouter', () => {
payment_provider: KiloPassPaymentProvider.AppStore,
product_id: 'kilo_pass_tier_19_monthly',
provider_subscription_id: providerSubscriptionId,
- provider_transaction_id: 'tx_get_state_app_store_grandfathered_initial',
+ provider_transaction_id: 'tx_get_state_app_store_month2_ramp_initial',
provider_original_transaction_id: providerSubscriptionId,
app_account_token: user.app_store_account_token,
environment: 'Sandbox',
@@ -1143,7 +1245,7 @@ describe('kiloPassRouter', () => {
payment_provider: KiloPassPaymentProvider.AppStore,
product_id: 'kilo_pass_tier_19_monthly',
provider_subscription_id: providerSubscriptionId,
- provider_transaction_id: 'tx_get_state_app_store_grandfathered_renewal',
+ provider_transaction_id: 'tx_get_state_app_store_month2_ramp_renewal',
provider_original_transaction_id: providerSubscriptionId,
app_account_token: user.app_store_account_token,
environment: 'Sandbox',
@@ -1157,10 +1259,12 @@ describe('kiloPassRouter', () => {
const result = await caller.kiloPass.getState();
const baseAmountUsd = getMonthlyPriceUsd(KiloPassTier.Tier19);
- const expectedCurrentBonusUsd =
- Math.round(
- Math.round(baseAmountUsd * 100) * KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_BONUS_PERCENT
- ) / 100;
+ const expectedPercent = computeMonthlyCadenceBonusPercent({
+ tier: KiloPassTier.Tier19,
+ streakMonths: 2,
+ isFirstTimeSubscriberEver: true,
+ });
+ const expectedCurrentBonusUsd = Math.round(baseAmountUsd * expectedPercent * 100) / 100;
expect(result.subscription).toEqual(
expect.objectContaining({
@@ -1173,12 +1277,12 @@ describe('kiloPassRouter', () => {
);
});
- it('predicts monthly nextBonusCreditsUsd as 50% for promo month 2 (streak=1 -> predicted=2)', async () => {
+ it('predicts monthly nextBonusCreditsUsd using the month-2 ramp (streak=1 -> predicted=2)', async () => {
const stripeMock = getStripeMock();
const currentPeriodEndSeconds = 1_700_123_456;
const currentPeriodStartSeconds = currentPeriodEndSeconds - 2_592_000;
stripeMock.subscriptions.retrieve.mockResolvedValue({
- id: 'sub_test_monthly_grandfathered_month2_next',
+ id: 'sub_test_monthly_month2_ramp_month2_next',
status: 'active',
items: {
data: [
@@ -1191,12 +1295,12 @@ describe('kiloPassRouter', () => {
});
const user = await insertTestUser({
- google_user_email: 'kilo-pass-get-state-monthly-grandfathered-month2-next@example.com',
+ google_user_email: 'kilo-pass-get-state-monthly-month2_ramp-month2-next@example.com',
});
await insertSubscription({
kiloUserId: user.id,
- stripeSubscriptionId: 'sub_test_monthly_grandfathered_month2_next',
+ stripeSubscriptionId: 'sub_test_monthly_month2_ramp_month2_next',
tier: KiloPassTier.Tier19,
cadence: KiloPassCadence.Monthly,
status: 'active',
@@ -1208,19 +1312,22 @@ describe('kiloPassRouter', () => {
const result = await caller.kiloPass.getState();
const baseAmountUsd = getMonthlyPriceUsd(KiloPassTier.Tier19);
- const baseCents = Math.round(baseAmountUsd * 100);
- const expectedNextBonusUsd =
- Math.round(baseCents * KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_BONUS_PERCENT) / 100;
+ const expectedPercent = computeMonthlyCadenceBonusPercent({
+ tier: KiloPassTier.Tier19,
+ streakMonths: 2,
+ isFirstTimeSubscriberEver: true,
+ });
+ const expectedNextBonusUsd = Math.round(baseAmountUsd * expectedPercent * 100) / 100;
expect(result.subscription?.nextBonusCreditsUsd).toBe(expectedNextBonusUsd);
});
- it('computes monthly currentPeriodBonusCreditsUsd as 50% for promo month 2 (streak=2)', async () => {
+ it('computes monthly currentPeriodBonusCreditsUsd using the month-2 ramp (streak=2)', async () => {
const stripeMock = getStripeMock();
const currentPeriodEndSeconds = 1_700_123_456;
const currentPeriodStartSeconds = currentPeriodEndSeconds - 2_592_000;
stripeMock.subscriptions.retrieve.mockResolvedValue({
- id: 'sub_test_monthly_grandfathered_month2_current',
+ id: 'sub_test_monthly_month2_ramp_month2_current',
status: 'active',
items: {
data: [
@@ -1233,12 +1340,12 @@ describe('kiloPassRouter', () => {
});
const user = await insertTestUser({
- google_user_email: 'kilo-pass-get-state-monthly-grandfathered-month2-current@example.com',
+ google_user_email: 'kilo-pass-get-state-monthly-month2_ramp-month2-current@example.com',
});
await insertSubscription({
kiloUserId: user.id,
- stripeSubscriptionId: 'sub_test_monthly_grandfathered_month2_current',
+ stripeSubscriptionId: 'sub_test_monthly_month2_ramp_month2_current',
tier: KiloPassTier.Tier19,
cadence: KiloPassCadence.Monthly,
status: 'active',
@@ -1250,20 +1357,22 @@ describe('kiloPassRouter', () => {
const result = await caller.kiloPass.getState();
const baseAmountUsd = getMonthlyPriceUsd(KiloPassTier.Tier19);
- const expectedCurrentBonusUsd =
- Math.round(
- Math.round(baseAmountUsd * 100) * KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_BONUS_PERCENT
- ) / 100;
+ const expectedPercent = computeMonthlyCadenceBonusPercent({
+ tier: KiloPassTier.Tier19,
+ streakMonths: 2,
+ isFirstTimeSubscriberEver: true,
+ });
+ const expectedCurrentBonusUsd = Math.round(baseAmountUsd * expectedPercent * 100) / 100;
expect(result.subscription?.currentPeriodBonusCreditsUsd).toBe(expectedCurrentBonusUsd);
});
- it('does not apply 50% month-2 promo when started_at is at/after the cutoff (streak=2)', async () => {
+ it('does not apply the 50% first-month promo in streak month 2', async () => {
const stripeMock = getStripeMock();
const currentPeriodEndSeconds = 1_700_123_456;
const currentPeriodStartSeconds = currentPeriodEndSeconds - 2_592_000;
stripeMock.subscriptions.retrieve.mockResolvedValue({
- id: 'sub_test_monthly_grandfathered_month2_cutoff_ineligible',
+ id: 'sub_test_monthly_month2_ramp_month2_ineligible',
status: 'active',
items: {
data: [
@@ -1276,17 +1385,17 @@ describe('kiloPassRouter', () => {
});
const user = await insertTestUser({
- google_user_email: 'kilo-pass-get-state-monthly-grandfathered-month2-cutoff@example.com',
+ google_user_email: 'kilo-pass-get-state-monthly-month2-ramp@example.com',
});
await insertSubscription({
kiloUserId: user.id,
- stripeSubscriptionId: 'sub_test_monthly_grandfathered_month2_cutoff_ineligible',
+ stripeSubscriptionId: 'sub_test_monthly_month2_ramp_month2_ineligible',
tier: KiloPassTier.Tier19,
cadence: KiloPassCadence.Monthly,
status: 'active',
currentStreakMonths: 2,
- startedAt: KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF.toISOString(),
+ startedAt: '2026-01-01T00:00:00.000Z',
});
const caller = await createCallerForUser(user.id);
@@ -1302,18 +1411,17 @@ describe('kiloPassRouter', () => {
expect(result.subscription?.currentPeriodBonusCreditsUsd).toBe(expectedCurrentBonusUsd);
expect(result.subscription?.currentPeriodBonusCreditsUsd).not.toBe(
- Math.round(
- Math.round(baseAmountUsd * 100) * KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_BONUS_PERCENT
- ) / 100
+ Math.round(Math.round(baseAmountUsd * 100) * KILO_PASS_FIRST_MONTH_PROMO_BONUS_PERCENT) /
+ 100
);
});
- it('keeps month 3+ bonus ramp unchanged even for grandfathered subscriptions (streak=3)', async () => {
+ it('keeps month 3+ bonus ramp unchanged (streak=3)', async () => {
const stripeMock = getStripeMock();
const currentPeriodEndSeconds = 1_700_123_456;
const currentPeriodStartSeconds = currentPeriodEndSeconds - 2_592_000;
stripeMock.subscriptions.retrieve.mockResolvedValue({
- id: 'sub_test_monthly_grandfathered_month3_regression',
+ id: 'sub_test_monthly_month2_ramp_month3_regression',
status: 'active',
items: {
data: [
@@ -1326,12 +1434,12 @@ describe('kiloPassRouter', () => {
});
const user = await insertTestUser({
- google_user_email: 'kilo-pass-get-state-monthly-grandfathered-month3@example.com',
+ google_user_email: 'kilo-pass-get-state-monthly-month2_ramp-month3@example.com',
});
await insertSubscription({
kiloUserId: user.id,
- stripeSubscriptionId: 'sub_test_monthly_grandfathered_month3_regression',
+ stripeSubscriptionId: 'sub_test_monthly_month2_ramp_month3_regression',
tier: KiloPassTier.Tier19,
cadence: KiloPassCadence.Monthly,
status: 'active',
@@ -1443,7 +1551,7 @@ describe('kiloPassRouter', () => {
describe('isEligibleForFirstMonthPromo in getState', () => {
it('returns isEligibleForFirstMonthPromo=true when user has no subscriptions', async () => {
- freezeKiloPassClock(PROMO_OFFER_ACTIVE_TEST_TIME);
+ freezeKiloPassClock(FIRST_MONTH_PROMO_TEST_TIME);
const user = await insertTestUser({
google_user_email: 'kilo-pass-promo-eligible-no-sub@example.com',
@@ -1456,25 +1564,25 @@ describe('kiloPassRouter', () => {
expect(result.subscription).toBeNull();
});
- it('returns isEligibleForFirstMonthPromo=false after the promo cutoff', async () => {
- freezeKiloPassClock(PROMO_OFFER_EXPIRED_TEST_TIME);
+ it('keeps first-month promo eligibility after the first-month promo check', async () => {
+ freezeKiloPassClock(LATER_FIRST_MONTH_PROMO_TEST_TIME);
const user = await insertTestUser({
- google_user_email: 'kilo-pass-promo-expired-no-sub@example.com',
+ google_user_email: 'kilo-pass-promo-after-first-month-promo-no-sub@example.com',
});
const caller = await createCallerForUser(user.id);
const result = await caller.kiloPass.getState();
- expect(result.isEligibleForFirstMonthPromo).toBe(false);
+ expect(result.isEligibleForFirstMonthPromo).toBe(true);
expect(result.subscription).toBeNull();
});
it('keeps isEligibleForFirstMonthPromo=true for a never-subscribed user', async () => {
- freezeKiloPassClock(PROMO_OFFER_ACTIVE_TEST_TIME);
+ freezeKiloPassClock(FIRST_MONTH_PROMO_TEST_TIME);
const user = await insertTestUser({
- google_user_email: 'kilo-pass-promo-cutoff-still-eligible@example.com',
+ google_user_email: 'kilo-pass-promo-first-month-promo-check-still-eligible@example.com',
});
const caller = await createCallerForUser(user.id);
@@ -2940,6 +3048,156 @@ describe('kiloPassRouter', () => {
});
});
+ describe('getReferralRewardSummary', () => {
+ it('returns an empty Kilo Pass referral summary without KiloClaw fallback state', async () => {
+ const user = await insertTestUser({
+ google_user_email: 'kilo-pass-referral-empty@example.com',
+ });
+ const caller = await createCallerForUser(user.id);
+
+ const result = await caller.kiloPass.getReferralRewardSummary();
+
+ expect(result).toEqual({
+ totals: {
+ totalRewards: 0,
+ pendingRewards: 0,
+ appliedRewards: 0,
+ totalRewardAmountUsd: 0,
+ pendingRewardAmountUsd: 0,
+ appliedRewardAmountUsd: 0,
+ },
+ referrerCap: {
+ grantedRewards: 0,
+ limit: 5,
+ reached: false,
+ },
+ rewards: [],
+ });
+ });
+
+ it('summarizes pending, applied, history, and cap-reached Kilo Pass rewards', async () => {
+ const user = await insertTestUser({
+ google_user_email: 'kilo-pass-referral-summary@example.com',
+ });
+
+ await insertKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ role: ImpactReferralBeneficiaryRole.Referrer,
+ status: ImpactReferralRewardStatus.Pending,
+ rewardAmountUsd: 24.5,
+ sourceTier: KiloPassTier.Tier49,
+ earnedAt: '2026-05-10T00:00:00.000Z',
+ expiresAt: '2027-05-10T00:00:00.000Z',
+ });
+ await insertKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ role: ImpactReferralBeneficiaryRole.Referee,
+ status: ImpactReferralRewardStatus.Applied,
+ rewardAmountUsd: 9.5,
+ sourceTier: KiloPassTier.Tier19,
+ earnedAt: '2026-05-11T00:00:00.000Z',
+ appliedAt: '2026-06-01T00:00:00.000Z',
+ });
+ await insertKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ role: ImpactReferralBeneficiaryRole.Referrer,
+ status: ImpactReferralRewardStatus.ReviewRequired,
+ rewardAmountUsd: 24.5,
+ sourceTier: KiloPassTier.Tier49,
+ earnedAt: '2026-05-12T00:00:00.000Z',
+ });
+ await insertKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ role: ImpactReferralBeneficiaryRole.Referrer,
+ status: ImpactReferralRewardStatus.Expired,
+ rewardAmountUsd: 99.5,
+ sourceTier: KiloPassTier.Tier199,
+ earnedAt: '2026-05-13T00:00:00.000Z',
+ });
+ await insertKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ role: ImpactReferralBeneficiaryRole.Referrer,
+ status: ImpactReferralRewardStatus.Canceled,
+ rewardAmountUsd: 99.5,
+ sourceTier: KiloPassTier.Tier199,
+ earnedAt: '2026-05-14T00:00:00.000Z',
+ });
+ await insertKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ role: ImpactReferralBeneficiaryRole.Referrer,
+ status: ImpactReferralRewardStatus.Reversed,
+ rewardAmountUsd: 0,
+ sourceTier: KiloPassTier.Tier19,
+ earnedAt: '2026-05-15T00:00:00.000Z',
+ });
+
+ const caller = await createCallerForUser(user.id);
+ const result = await caller.kiloPass.getReferralRewardSummary();
+
+ expect(result.totals).toEqual({
+ totalRewards: 6,
+ pendingRewards: 1,
+ appliedRewards: 1,
+ totalRewardAmountUsd: 257.5,
+ pendingRewardAmountUsd: 24.5,
+ appliedRewardAmountUsd: 9.5,
+ });
+ expect(result.referrerCap).toEqual({
+ grantedRewards: 5,
+ limit: 5,
+ reached: true,
+ });
+ expect(result.rewards.map(reward => reward.status)).toEqual([
+ 'reversed',
+ 'canceled',
+ 'expired',
+ 'review_required',
+ 'applied',
+ 'pending',
+ ]);
+ expect(result.rewards[1]).toEqual(
+ expect.objectContaining({
+ role: 'referrer',
+ rewardAmountUsd: 99.5,
+ sourceTier: 'tier_199',
+ })
+ );
+ });
+
+ it('does not count expired pending rewards as pending future rewards', async () => {
+ const user = await insertTestUser({
+ google_user_email: 'kilo-pass-referral-expired-pending@example.com',
+ });
+
+ await insertKiloPassReferralReward({
+ beneficiaryUserId: user.id,
+ role: ImpactReferralBeneficiaryRole.Referrer,
+ status: ImpactReferralRewardStatus.Pending,
+ rewardAmountUsd: 24.5,
+ sourceTier: KiloPassTier.Tier49,
+ earnedAt: '2025-01-01T00:00:00.000Z',
+ expiresAt: '2025-12-31T00:00:00.000Z',
+ });
+
+ const caller = await createCallerForUser(user.id);
+ const result = await caller.kiloPass.getReferralRewardSummary();
+
+ expect(result.totals).toEqual(
+ expect.objectContaining({
+ totalRewards: 1,
+ pendingRewards: 0,
+ pendingRewardAmountUsd: 0,
+ })
+ );
+ expect(result.rewards[0]).toEqual(
+ expect.objectContaining({
+ status: ImpactReferralRewardStatus.Pending,
+ expiresAt: '2025-12-31T00:00:00.000Z',
+ })
+ );
+ });
+ });
+
describe('createCheckoutSession', () => {
it('rejects when an active/pending subscription already exists', async () => {
const user = await insertTestUser({
diff --git a/apps/web/src/routers/kilo-pass-router.ts b/apps/web/src/routers/kilo-pass-router.ts
index 35882048b7..6da155abdf 100644
--- a/apps/web/src/routers/kilo-pass-router.ts
+++ b/apps/web/src/routers/kilo-pass-router.ts
@@ -6,9 +6,12 @@ import { client as stripe } from '@/lib/stripe-client';
import { getStripePriceIdForKiloPass } from '@/lib/kilo-pass/stripe-price-ids.server';
import { getAffiliateAttribution } from '@/lib/affiliate-attribution';
import { APP_URL } from '@/lib/constants';
+import { KILO_PASS_REFERRER_REWARD_CAP } from '@/lib/impact/kilo-pass-referrals';
import { TRPCError } from '@trpc/server';
import {
credit_transactions,
+ impact_referral_reward_decisions,
+ impact_referral_rewards,
kilo_pass_issuance_items,
kilo_pass_issuances,
kilo_pass_scheduled_changes,
@@ -26,6 +29,13 @@ import {
KiloPassPaymentProvider,
KiloPassWelcomePromoEligibilityReason,
} from '@/lib/kilo-pass/enums';
+import {
+ ImpactReferralBeneficiaryRole,
+ ImpactReferralDecisionOutcome,
+ ImpactReferralProduct,
+ ImpactReferralRewardKind,
+ ImpactReferralRewardStatus,
+} from '@kilocode/db/schema-types';
import { KiloPassIssuanceItemKind } from '@/lib/kilo-pass/enums';
import { and, asc, desc, eq, inArray, isNull, ne, sql, sum } from 'drizzle-orm';
import * as z from 'zod';
@@ -38,10 +48,7 @@ import { KiloPassError } from '@/lib/kilo-pass/errors';
import { isStripeSubscriptionEnded } from '@/lib/kilo-pass/stripe-subscription-status';
import { releaseScheduledChangeForSubscription } from '@/lib/kilo-pass/scheduled-change-release';
import { appendKiloPassAuditLog } from '@/lib/kilo-pass/issuance';
-import {
- KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF,
- KILO_PASS_TIER_CONFIG,
-} from '@/lib/kilo-pass/constants';
+import { KILO_PASS_TIER_CONFIG } from '@/lib/kilo-pass/constants';
import { fromMicrodollars } from '@/lib/utils';
import { timedUsageQuery } from '@/lib/usage-query';
import {
@@ -143,6 +150,35 @@ const GetAverageMonthlyUsageLast3MonthsOutputSchema = z.object({
averageMonthlyUsageUsd: z.number(),
});
+const KiloPassReferralRewardSummaryOutputSchema = z.object({
+ totals: z.object({
+ totalRewards: z.number(),
+ pendingRewards: z.number(),
+ appliedRewards: z.number(),
+ totalRewardAmountUsd: z.number(),
+ pendingRewardAmountUsd: z.number(),
+ appliedRewardAmountUsd: z.number(),
+ }),
+ referrerCap: z.object({
+ grantedRewards: z.number(),
+ limit: z.number(),
+ reached: z.boolean(),
+ }),
+ rewards: z.array(
+ z.object({
+ id: z.string(),
+ role: z.enum(ImpactReferralBeneficiaryRole),
+ status: z.enum(ImpactReferralRewardStatus),
+ rewardAmountUsd: z.number(),
+ earnedAt: z.string(),
+ appliedAt: z.string().nullable(),
+ expiresAt: z.string().nullable(),
+ sourceTier: z.string().nullable(),
+ reviewReason: z.string().nullable(),
+ })
+ ),
+});
+
const CompleteStorePurchaseOutputSchema = z.object({
subscriptionId: z.string(),
tier: KiloPassTierSchema,
@@ -165,6 +201,11 @@ type StripeManagedKiloPassSubscription = KiloPassSubscriptionState & {
stripeSubscriptionId: string;
};
+const KILO_PASS_PENDING_REFERRAL_REWARD_STATUSES = new Set([
+ ImpactReferralRewardStatus.Pending,
+ ImpactReferralRewardStatus.Earned,
+]);
+
const APP_STORE_ACCOUNT_TOKEN_MISMATCH_MESSAGE =
'App Store purchase account token does not match the signed-in user.';
const APP_STORE_PURCHASE_NOT_LINKED_TO_ACCOUNT_MESSAGE =
@@ -231,10 +272,6 @@ function mapAppStoreCompletionError(error: unknown, userId: string): TRPCError {
});
}
-function isTwoMonthPromoOfferActive(): boolean {
- return dayjs().utc().isBefore(KILO_PASS_MONTHLY_FIRST_2_MONTHS_PROMO_CUTOFF);
-}
-
function roundToCents(usd: number): number {
return Math.round(usd * 100) / 100;
}
@@ -275,7 +312,6 @@ function getNextKiloPassBonusCreditsUsd(params: {
tier: params.subscription.tier,
streakMonths: predictedStreakMonths,
isFirstTimeSubscriberEver: params.isFirstTimeSubscriberEver,
- subscriptionStartedAtIso: params.subscription.startedAt,
});
const baseCents = Math.round(params.baseAmountUsd * 100);
@@ -297,7 +333,6 @@ function getCurrentKiloPassBonusCreditsUsd(params: {
tier: params.subscription.tier,
streakMonths,
isFirstTimeSubscriberEver: params.isFirstTimeSubscriberEver,
- subscriptionStartedAtIso: params.subscription.startedAt,
});
const cents = Math.round(params.baseAmountUsd * bonusPercentApplied * 100);
return cents / 100;
@@ -823,10 +858,101 @@ export const kiloPassRouter = createTRPCRouter({
return { averageMonthlyUsageUsd };
}),
+ getReferralRewardSummary: baseProcedure
+ .output(KiloPassReferralRewardSummaryOutputSchema)
+ .query(async ({ ctx }) => {
+ const [rewardRows, capRows] = await Promise.all([
+ db
+ .select({
+ id: impact_referral_rewards.id,
+ role: impact_referral_rewards.beneficiary_role,
+ status: impact_referral_rewards.status,
+ rewardAmountUsd: impact_referral_rewards.reward_amount_usd,
+ earnedAt: impact_referral_rewards.earned_at,
+ appliedAt: impact_referral_rewards.applied_at,
+ expiresAt: impact_referral_rewards.expires_at,
+ sourceTier: impact_referral_rewards.source_tier,
+ reviewReason: impact_referral_rewards.review_reason,
+ })
+ .from(impact_referral_rewards)
+ .where(
+ and(
+ eq(impact_referral_rewards.product, ImpactReferralProduct.KiloPass),
+ eq(impact_referral_rewards.reward_kind, ImpactReferralRewardKind.KiloPassBonus),
+ eq(impact_referral_rewards.beneficiary_user_id, ctx.user.id)
+ )
+ )
+ .orderBy(
+ desc(impact_referral_rewards.earned_at),
+ desc(impact_referral_rewards.created_at)
+ ),
+ db
+ .select({ grantedRewards: sql`COUNT(*)::int` })
+ .from(impact_referral_reward_decisions)
+ .where(
+ and(
+ eq(impact_referral_reward_decisions.product, ImpactReferralProduct.KiloPass),
+ eq(
+ impact_referral_reward_decisions.reward_kind,
+ ImpactReferralRewardKind.KiloPassBonus
+ ),
+ eq(impact_referral_reward_decisions.beneficiary_user_id, ctx.user.id),
+ eq(
+ impact_referral_reward_decisions.beneficiary_role,
+ ImpactReferralBeneficiaryRole.Referrer
+ ),
+ eq(impact_referral_reward_decisions.outcome, ImpactReferralDecisionOutcome.Granted)
+ )
+ ),
+ ]);
+
+ const rewards = rewardRows.map(row => ({
+ id: row.id,
+ role: row.role,
+ status: row.status,
+ rewardAmountUsd: row.rewardAmountUsd ?? 0,
+ earnedAt: normalizeTimestampToIso(row.earnedAt) ?? row.earnedAt,
+ appliedAt: normalizeTimestampToIso(row.appliedAt),
+ expiresAt: normalizeTimestampToIso(row.expiresAt),
+ sourceTier: row.sourceTier,
+ reviewReason: row.reviewReason,
+ }));
+
+ const nowMs = Date.now();
+ const pendingRewards = rewards.filter(reward => {
+ if (!KILO_PASS_PENDING_REFERRAL_REWARD_STATUSES.has(reward.status)) return false;
+ if (!reward.expiresAt) return true;
+ return new Date(reward.expiresAt).getTime() > nowMs;
+ });
+ const appliedRewards = rewards.filter(
+ reward => reward.status === ImpactReferralRewardStatus.Applied
+ );
+ const sumRewardAmounts = (items: typeof rewards) =>
+ roundToCents(items.reduce((total, reward) => total + reward.rewardAmountUsd, 0));
+ const grantedRewards = capRows[0]?.grantedRewards ?? 0;
+
+ return {
+ totals: {
+ totalRewards: rewards.length,
+ pendingRewards: pendingRewards.length,
+ appliedRewards: appliedRewards.length,
+ totalRewardAmountUsd: sumRewardAmounts(rewards),
+ pendingRewardAmountUsd: sumRewardAmounts(pendingRewards),
+ appliedRewardAmountUsd: sumRewardAmounts(appliedRewards),
+ },
+ referrerCap: {
+ grantedRewards,
+ limit: KILO_PASS_REFERRER_REWARD_CAP,
+ reached: grantedRewards >= KILO_PASS_REFERRER_REWARD_CAP,
+ },
+ rewards,
+ };
+ }),
+
getState: baseProcedure.output(GetStateOutputSchema).query(async ({ ctx }) => {
const subscriptionBase = await getKiloPassStateForUser(db, ctx.user.id);
if (!subscriptionBase) {
- return { subscription: null, isEligibleForFirstMonthPromo: isTwoMonthPromoOfferActive() };
+ return { subscription: null, isEligibleForFirstMonthPromo: true };
}
if (subscriptionBase.paymentProvider !== KiloPassPaymentProvider.Stripe) {
diff --git a/apps/web/src/scripts/dev-apply-kilo-pass-referral.ts b/apps/web/src/scripts/dev-apply-kilo-pass-referral.ts
new file mode 100644
index 0000000000..9184227e82
--- /dev/null
+++ b/apps/web/src/scripts/dev-apply-kilo-pass-referral.ts
@@ -0,0 +1,210 @@
+import { existsSync } from 'node:fs';
+import { resolve } from 'node:path';
+import process from 'node:process';
+
+type ScriptOptions = {
+ email: string;
+ issueMonth?: string;
+ invoiceId: string;
+ includeBaseCredits: boolean;
+};
+
+function loadLocalEnv(): void {
+ for (const envPath of [
+ resolve(process.cwd(), '../../.env.local'),
+ resolve(process.cwd(), '.env.local'),
+ ]) {
+ if (existsSync(envPath)) {
+ process.loadEnvFile(envPath);
+ break;
+ }
+ }
+
+ Object.assign(process.env, { NODE_ENV: 'development' });
+ process.env.IS_IN_AUTOMATED_TEST ??= 'true';
+ process.env.POSTGRES_CONNECT_TIMEOUT ??= '30000';
+ process.env.POSTGRES_MAX_QUERY_TIME ??= '30000';
+ process.env.POSTGRES_SCRIPT_URL ??= process.env.POSTGRES_URL;
+ process.env.NEXT_PUBLIC_GASTOWN_URL ??= 'http://localhost:8787';
+ process.env.NEXT_PUBLIC_KILO_CHAT_URL ??= 'http://localhost:8788';
+ process.env.NEXT_PUBLIC_EVENT_SERVICE_URL ??= 'http://localhost:8789';
+ process.env.NEXT_PUBLIC_WASTELAND_URL ??= 'http://localhost:8790';
+}
+
+function printUsage(): void {
+ console.log(`Usage:
+ pnpm --filter web script src/scripts/dev-apply-kilo-pass-referral.ts [--issue-month YYYY-MM-01] [--invoice-id in_local_...] [--include-base]
+
+Examples:
+ pnpm --filter web script src/scripts/dev-apply-kilo-pass-referral.ts kilopass-referee1@example.com
+ pnpm --filter web script src/scripts/dev-apply-kilo-pass-referral.ts kilopass-referrer@example.com --include-base
+`);
+}
+
+function parseArgs(argv: string[]): ScriptOptions {
+ const email = argv.find(arg => !arg.startsWith('--'));
+ if (!email) {
+ printUsage();
+ throw new Error('Missing email argument');
+ }
+
+ const getFlagValue = (flag: string): string | undefined => {
+ const index = argv.indexOf(flag);
+ if (index === -1) return undefined;
+ const value = argv[index + 1];
+ if (!value || value.startsWith('--')) {
+ throw new Error(`${flag} requires a value`);
+ }
+ return value;
+ };
+
+ return {
+ email,
+ issueMonth: getFlagValue('--issue-month'),
+ invoiceId: getFlagValue('--invoice-id') ?? `in_local_referral_bonus_${Date.now()}`,
+ includeBaseCredits: argv.includes('--include-base'),
+ };
+}
+
+function assertIssueMonth(issueMonth: string): void {
+ if (!/^\d{4}-\d{2}-01$/.test(issueMonth)) {
+ throw new Error(`Invalid --issue-month ${issueMonth}; expected YYYY-MM-01`);
+ }
+}
+
+async function main(): Promise {
+ loadLocalEnv();
+
+ const [
+ drizzleOrm,
+ balanceCache,
+ drizzle,
+ constants,
+ dayjsModule,
+ enums,
+ issuance,
+ state,
+ schema,
+ ] = await Promise.all([
+ import('drizzle-orm'),
+ import('@/lib/balanceCache'),
+ import('@/lib/drizzle'),
+ import('@/lib/kilo-pass/constants'),
+ import('@/lib/kilo-pass/dayjs'),
+ import('@/lib/kilo-pass/enums'),
+ import('@/lib/kilo-pass/issuance'),
+ import('@/lib/kilo-pass/state'),
+ import('@kilocode/db/schema'),
+ ]);
+
+ const { eq, desc } = drizzleOrm;
+ const { forceImmediateExpirationRecomputation } = balanceCache;
+ const { db, closeAllDrizzleConnections } = drizzle;
+ const { KILO_PASS_TIER_CONFIG } = constants;
+ const { dayjs } = dayjsModule;
+ const { KiloPassCadence, KiloPassIssuanceSource } = enums;
+ const {
+ applyPendingKiloPassReferralBonusForIssuance,
+ computeIssueMonth,
+ createOrGetIssuanceHeader,
+ issueBaseCreditsForIssuance,
+ } = issuance;
+ const { getKiloPassStateForUser } = state;
+ const { kilo_pass_issuances, kilocode_users } = schema;
+
+ try {
+ const options = parseArgs(process.argv.slice(2));
+ const user = await db.query.kilocode_users.findFirst({
+ where: eq(kilocode_users.google_user_email, options.email),
+ });
+
+ if (!user) {
+ throw new Error(`User not found: ${options.email}`);
+ }
+
+ const subscription = await getKiloPassStateForUser(db, user.id);
+ if (!subscription) {
+ throw new Error(`No Kilo Pass subscription for ${options.email}`);
+ }
+ if (subscription.cadence !== KiloPassCadence.Monthly) {
+ throw new Error(`Referral bonus application is monthly-only; got ${subscription.cadence}`);
+ }
+ if (subscription.status !== 'active') {
+ throw new Error(`Kilo Pass subscription must be active; got ${subscription.status}`);
+ }
+
+ const getDefaultNextIssueMonth = async (): Promise => {
+ const latestIssuance = await db.query.kilo_pass_issuances.findFirst({
+ where: eq(kilo_pass_issuances.kilo_pass_subscription_id, subscription.subscriptionId),
+ orderBy: desc(kilo_pass_issuances.issue_month),
+ });
+
+ const baseMonth = latestIssuance?.issue_month
+ ? dayjs(`${latestIssuance.issue_month}T00:00:00.000Z`).utc()
+ : dayjs(subscription.startedAt ?? new Date().toISOString()).utc();
+
+ return computeIssueMonth(baseMonth.add(1, 'month'));
+ };
+
+ const issueMonth = options.issueMonth ?? (await getDefaultNextIssueMonth());
+ assertIssueMonth(issueMonth);
+
+ const result = await db.transaction(async tx => {
+ const createdIssuance = await createOrGetIssuanceHeader(tx, {
+ subscriptionId: subscription.subscriptionId,
+ issueMonth,
+ source: KiloPassIssuanceSource.StripeInvoice,
+ stripeInvoiceId: options.invoiceId,
+ });
+
+ const baseCreditsResult = options.includeBaseCredits
+ ? await issueBaseCreditsForIssuance(tx, {
+ issuanceId: createdIssuance.issuanceId,
+ subscriptionId: subscription.subscriptionId,
+ kiloUserId: user.id,
+ amountUsd: KILO_PASS_TIER_CONFIG[subscription.tier].monthlyPriceUsd,
+ stripeInvoiceId: options.invoiceId,
+ description: `Local Kilo Pass base credits (${subscription.tier}, ${subscription.cadence})`,
+ })
+ : null;
+
+ const referralBonusResult = await applyPendingKiloPassReferralBonusForIssuance(tx, {
+ issuanceId: createdIssuance.issuanceId,
+ subscriptionId: subscription.subscriptionId,
+ kiloUserId: user.id,
+ stripeInvoiceId: options.invoiceId,
+ });
+
+ return {
+ issuance: createdIssuance,
+ baseCreditsResult,
+ referralBonusResult,
+ };
+ });
+
+ await forceImmediateExpirationRecomputation(user.id);
+
+ console.log(
+ JSON.stringify(
+ {
+ email: options.email,
+ userId: user.id,
+ subscriptionId: subscription.subscriptionId,
+ issueMonth,
+ invoiceId: options.invoiceId,
+ includeBaseCredits: options.includeBaseCredits,
+ result,
+ },
+ null,
+ 2
+ )
+ );
+ } finally {
+ await closeAllDrizzleConnections();
+ }
+}
+
+main().catch(error => {
+ console.error(error);
+ process.exitCode = 1;
+});
diff --git a/apps/web/vercel.json b/apps/web/vercel.json
index cc4cf54be0..6a71f1ba18 100644
--- a/apps/web/vercel.json
+++ b/apps/web/vercel.json
@@ -28,6 +28,10 @@
"path": "/api/cron/kilo-pass-store-subscription-reconcile",
"schedule": "*/15 * * * *"
},
+ {
+ "path": "/api/cron/kilo-pass-expire-referral-rewards",
+ "schedule": "0 * * * *"
+ },
{
"path": "/api/cron/deployment-threat-scan",
"schedule": "*/5 * * * *"
diff --git a/dev/seed/app/user-id.ts b/dev/seed/app/user-id.ts
new file mode 100644
index 0000000000..502a05af8c
--- /dev/null
+++ b/dev/seed/app/user-id.ts
@@ -0,0 +1,80 @@
+import { kilocode_users } from '@kilocode/db/schema';
+import { eq, or } from 'drizzle-orm';
+
+import { getSeedDb } from '../lib/db';
+import { normalizeSeedEmail } from '../lib/email';
+import type { SeedResult } from '../index';
+
+export const usage = '';
+
+function printUsage(): void {
+ console.log(`Usage: pnpm dev:seed app:user-id ${usage}`);
+ console.log('');
+ console.log('Prints the Kilo Code user id for a local development user by email.');
+ console.log('Matches either google_user_email exactly or normalized_email.');
+ console.log('');
+ console.log('Examples:');
+ console.log(' pnpm dev:seed app:user-id ada@example.com');
+ console.log(' pnpm -s dev:seed app:user-id ada@example.com --json | jq -r .userId');
+}
+
+function isValidEmail(email: string): boolean {
+ // Intentionally permissive; we only guard against obvious nonsense in dev.
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
+}
+
+export async function run(...args: string[]): Promise {
+ if (args.includes('--help') || args.includes('-h')) {
+ printUsage();
+ return;
+ }
+
+ const [email, ...rest] = args;
+ if (!email) {
+ printUsage();
+ throw new Error('email is required');
+ }
+ if (rest.length > 0) {
+ printUsage();
+ throw new Error(`Unexpected extra arguments: ${rest.join(' ')}`);
+ }
+
+ const trimmedEmail = email.trim();
+ if (!isValidEmail(trimmedEmail)) {
+ throw new Error(`email is not a valid address: ${trimmedEmail}`);
+ }
+
+ const normalizedEmail = normalizeSeedEmail(trimmedEmail);
+ const db = getSeedDb();
+ const matches = await db
+ .select({
+ userId: kilocode_users.id,
+ email: kilocode_users.google_user_email,
+ normalizedEmail: kilocode_users.normalized_email,
+ })
+ .from(kilocode_users)
+ .where(
+ or(
+ eq(kilocode_users.google_user_email, trimmedEmail),
+ eq(kilocode_users.normalized_email, normalizedEmail)
+ )
+ );
+
+ if (matches.length === 0) {
+ throw new Error(`No user found for email ${trimmedEmail}`);
+ }
+
+ const exactMatches = matches.filter(match => match.email === trimmedEmail);
+ const resolvedMatches = exactMatches.length > 0 ? exactMatches : matches;
+ if (resolvedMatches.length > 1) {
+ const matchList = resolvedMatches.map(match => `${match.email} (${match.userId})`).join(', ');
+ throw new Error(`Multiple users matched ${trimmedEmail}: ${matchList}`);
+ }
+
+ const [user] = resolvedMatches;
+ return {
+ userId: user.userId,
+ email: user.email,
+ normalizedEmail: user.normalizedEmail ?? null,
+ };
+}