Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ yarn-error.log*
/.plans/*
!/.plans/experimental-models-1.md
!/.plans/experimental-models-2.md
!/.plans/kilo-pass-welcome-promo-card-fingerprint-guard.md
/.plan/
.kilo/plans
.superpowers/
Expand Down
173 changes: 173 additions & 0 deletions .plans/kilo-pass-welcome-promo-card-fingerprint-guard.md

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions .specs/impact-referrals.md
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,45 @@ conversion, local referral rewards are authoritative and affiliate SALE reportin
175. Before launch, the existing internal referral system MUST be scoped away from KiloClaw and Kilo Pass, disabled for
those products, or migrated into this program's rules to prevent double rewards.

### Kilo Pass Reusable Payment-Fingerprint Welcome-Promo Guard

176. The Kilo Pass introductory welcome promo MUST be claimable at most once per reusable Stripe payment-instrument
fingerprint that the system supports. Initial supported instrument types are `card`, `sepa_debit`,
`us_bank_account`, `bacs_debit`, and `au_becs_debit`. Annual subscriptions are excluded.

177. An instrument fingerprint opportunity MUST be claimed only when a personal monthly Kilo Pass Stripe payment with
`amount_paid > 0` settles using that instrument, not when the instrument is merely attached, when a zero-value
invoice is finalized, when usage crosses the bonus threshold, or when welcome-promo credits are later issued.

178. A first-time account whose positively paid monthly Kilo Pass settlement uses a previously claimed reusable instrument
fingerprint MUST NOT receive the introductory `50%` welcome promo. It MAY receive the ordinary monthly-ramp bonus
using the same behavior as an existing or previously canceled Kilo Pass subscriber.

179. A positively paid monthly Kilo Pass settlement using a previously claimed reusable instrument fingerprint MUST NOT be
an eligible Kilo Pass referral conversion and MUST NOT grant a Kilo Pass referral reward to either beneficiary role.

180. A positively paid monthly settlement whose payment method is confirmed not to provide a supported reusable
fingerprint MUST NOT be disqualified solely because no cross-account instrument signal exists. A supported reusable
method with an absent fingerprint MAY follow that fallback. An unresolvable settlement MUST NOT be treated as a
confirmed eligible fallback.

181. Shared household, business, or other jointly used payment instruments are governed by the same one-claim rule; the
system MUST NOT provide additional welcome-promo or Kilo Pass referral-conversion eligibility solely because a later
buyer is a different person.

182. A claimed reusable instrument fingerprint MUST remain claimed after refund, dispute, fraud marking, cancellation,
failure to redeem welcome-promo credits, or account deletion. Credit or reward handling for adverse payments is
separate from instrument-claim retention.

183. When a paid monthly purchase is welcome-promo ineligible because its reusable payment fingerprint was previously
claimed, the post-payment Kilo Pass confirmation flow MUST inform the customer that the introductory bonus does not
apply. The message MUST NOT expose the fingerprint or the existence or identity of another account.

184. Durable instrument-claim records MAY retain the minimum Stripe fingerprint and payment identity data needed to
enforce these anti-abuse rules after account deletion. Customer-facing surfaces MUST NOT expose that retained
evidence, and any direct user identity references stored with such records MUST be deleted or anonymized under GDPR
deletion flows.

## Error Handling

1. If referral touch capture fails, the system SHOULD log the failure and continue the primary request.
Expand Down Expand Up @@ -727,6 +766,13 @@ conversion, local referral rewards are authoritative and affiliate SALE reportin

Classified an enforced Stripe Early Fraud Warning refund as an adverse qualifying payment for both covered products. Pending or earned-but-unapplied rewards cancel, already-applied rewards require support review, and later refund or chargeback delivery must remain idempotent.

### 2026-05-27 -- Prevent repeated Kilo Pass welcome claims by payment fingerprint

Added the Kilo Pass reusable Stripe payment-fingerprint guard for monthly introductory welcome promos and referral
conversions. The first positively paid settlement using a supported fingerprintable instrument permanently claims that
instrument opportunity; reused instruments retain ordinary monthly-ramp bonus behavior but do not receive the
introductory promo or create Kilo Pass referral rewards. Annual behavior remains outside this restriction.

### 2026-05-22 -- Rename and expand to Kilo Pass

Renamed `.specs/kiloclaw-referrals.md` to `.specs/impact-referrals.md`. Generalized shared Impact Advocate referral
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useRouter, useSearchParams } from 'next/navigation';

import BigLoader from '@/components/BigLoader';
import { PageContainer } from '@/components/layouts/PageContainer';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useTRPC } from '@/lib/trpc/utils';
Expand Down Expand Up @@ -37,13 +38,27 @@ function stepStatus(step: ActivationStep, current: ActivationStep): 'done' | 'ac
return 'pending';
}

function WelcomePromoIneligibleNotice() {
return (
<Alert variant="warning">
<AlertTriangle />
<AlertTitle>Introductory bonus not available</AlertTitle>
<AlertDescription>
This payment method has already been used for the introductory Kilo Pass bonus. Your
subscription remains active and standard monthly bonus terms apply.
</AlertDescription>
</Alert>
);
}

export function KiloPassAwardingCreditsClient() {
const trpc = useTRPC();
const router = useRouter();
const searchParams = useSearchParams();
const [didTimeout, setDidTimeout] = useState(false);
const [redirectSecondsRemaining, setRedirectSecondsRemaining] = useState<number | null>(null);

const checkoutSessionId = searchParams.get('session_id') ?? '';
const clawHostingPlan = searchParams.get('clawHostingPlan');
const clawInstanceId = searchParams.get('clawInstanceId');
const isClawAutoActivation = !!clawHostingPlan;
Expand Down Expand Up @@ -76,7 +91,8 @@ export function KiloPassAwardingCreditsClient() {
}, []);

const query = useQuery({
...trpc.kiloPass.getCheckoutReturnState.queryOptions(),
...trpc.kiloPass.getCheckoutReturnState.queryOptions({ sessionId: checkoutSessionId }),
enabled: checkoutSessionId.length > 0,
refetchInterval: query => {
const data = query.state.data;
if (didTimeout) return false;
Expand All @@ -89,6 +105,8 @@ export function KiloPassAwardingCreditsClient() {

const isReady = query.data?.creditsAwarded === true;
const hasSubscription = query.data?.subscription != null;
const showWelcomePromoIneligibleNotice =
query.data?.welcomePromoIneligibleDueToReusedFingerprint === true;

// For KiloClaw auto-activation: advance step when credits are awarded, then enroll
useEffect(() => {
Expand Down Expand Up @@ -276,6 +294,7 @@ export function KiloPassAwardingCreditsClient() {
<div className="text-muted-foreground text-sm">
You can activate hosting manually from the KiloClaw dashboard.
</div>
{showWelcomePromoIneligibleNotice ? <WelcomePromoIneligibleNotice /> : null}
<div className="flex flex-wrap gap-2">
<Button type="button" onClick={() => router.replace('/claw')}>
Go to KiloClaw
Expand Down Expand Up @@ -303,6 +322,7 @@ export function KiloPassAwardingCreditsClient() {
</CardHeader>
<CardContent className="grid gap-4">
<ActivationSteps current="done" />
{showWelcomePromoIneligibleNotice ? <WelcomePromoIneligibleNotice /> : null}
<div className="flex flex-wrap items-center gap-3">
<Button type="button" onClick={() => router.replace('/claw')}>
Continue to KiloClaw
Expand Down Expand Up @@ -357,6 +377,8 @@ export function KiloPassAwardingCreditsClient() {
Your Kilo Pass is active and your credits are ready.
</div>

{showWelcomePromoIneligibleNotice ? <WelcomePromoIneligibleNotice /> : null}

<div className="flex flex-wrap items-center gap-3">
<Button type="button" onClick={() => router.replace('/profile')}>
Continue to profile
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/lib/kilo-pass/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export {
KiloPassPaymentProvider,
KiloPassIssuanceSource,
KiloPassIssuanceItemKind,
KiloPassWelcomePromoPaymentFingerprintType,
KiloPassWelcomePromoEligibilityReason,
KiloPassAuditLogAction,
KiloPassAuditLogResult,
KiloPassScheduledChangeStatus,
Expand Down
Loading
Loading