Skip to content

Commit 3703027

Browse files
authored
feat(kilo-pass): disallow duplicate card fingerprints across Kilo Pass subscriptions (#3309)
* feat(kilo-pass): add card fingerprint gate for Kilo Pass subscriptions * fix(kilo-pass): remove unused imports and fix formatting in card-fingerprint-gate test * fix(kilo-pass): break circular dependency by moving findActiveKiloPassByCardFingerprint to card-fingerprint-gate * fix(kilo-pass): pass dbOrTx to checkDuplicateCardFingerprintGate for transaction-safe audit logs * fix(kilo-pass): only use dbOrTx for audit log, keep email/fingerprint lookups on db * fix(kilo-pass): mock email sender in card-fingerprint-gate tests so email_log markers persist * fix(kilo-pass): simplify email mock to fix TS2698 spread type error * fix(kilo-pass): move email notification after transaction commit so marker persists * fix(kilo-pass): use invoice.id instead of gateResult.stripeInvoiceId for blockedGateResult * fix(kilo-pass): rename blockedGateResult to blockedEmailParams and use invoice.id * fix(kilo-pass): fix tsgo type error and replace raw SQL NOT IN with notInArray - Return blockedEmailParams from db.transaction instead of mutating outer variable, fixing tsgo narrowing the type to 'never' because it doesn't track mutations across async closures. - Replace raw sql`NOT IN` with Drizzle's notInArray for type safety. * fix(kilo-pass): regenerate migration after rebase onto main * fix(kilo-pass): regenerate migration after rebase onto main * fix(kilo-pass): clear affiliate sale context when card fingerprint gate blocks subscription * fix(kilo-pass): regenerate migration to fix corrupted snapshot with merge conflict markers The 0141_snapshot.json had unresolved merge conflict markers from a previous rebase. Deleted branch-local migration files and regenerated a clean migration (0142) using pnpm drizzle generate. * fix(kilo-pass): regenerate migration after rebase onto main Rename branch migration from 0142 to 0143 since main added 0142_dashing_blue_marvel. Restore main's 0142 snapshot and journal, regenerate clean migration. * fix(kilo-pass): regenerate migration after rebase onto main * fix(kilo-pass): clean up email dedup marker when user has no email When user.google_user_email is falsy in maybeSendDuplicateCardCanceledEmail, the transactional_email_log marker was left in the DB permanently. This suppressed future email sends even if the user later acquired an email. Now the marker is deleted before returning, consistent with the error-path cleanup logic. * feat(kilo-pass): block user when duplicate card fingerprint gate triggers When the card fingerprint gate detects a duplicate card across users, the offending user's account is now blocked (blocked_reason set to 'kilo_pass_duplicate_card') in addition to the existing cancel + refund. Does not overwrite an existing blocked_reason. Follows the same pattern as cancel-and-refund.ts. * style(kilo-pass): fix oxfmt formatting in card-fingerprint-gate.ts * fix(kilo-pass): move audit log and user blocking from gate to handler to fix FK violation The checkDuplicateCardFingerprintGate function was passing dbOrTx through a parameter to appendKiloPassAuditLog, but the FK constraint on kilo_pass_audit_log.kilo_pass_subscription_id was being violated inside the transaction. Move the audit log insert and user-blocking update into the handler where tx is used directly. Also fix the catch block to omit kiloPassSubscriptionId when the transaction may have rolled back, and wrap the failure audit log in a try-catch so it doesn't mask the original error. * style(kilo-pass): fix oxfmt formatting * fix(kilo-pass): add migration for duplicate_card_subscription_canceled check constraint
1 parent 2e17562 commit 3703027

10 files changed

Lines changed: 26153 additions & 17 deletions
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Kilo Pass Subscription Cancelled</title>
7+
</head>
8+
<body
9+
style="
10+
margin: 0;
11+
padding: 0;
12+
font-family:
13+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
14+
background-color: #ffffff;
15+
color: #1a1a1a;
16+
"
17+
>
18+
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #ffffff">
19+
<tr>
20+
<td align="center" style="padding: 40px 20px">
21+
<table width="520" cellpadding="0" cellspacing="0">
22+
<!-- Header -->
23+
<tr>
24+
<td style="padding: 40px 40px 20px">
25+
<h1
26+
style="
27+
margin: 0;
28+
font-size: 24px;
29+
font-weight: 700;
30+
color: #1a1a1a;
31+
font-family:
32+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
33+
sans-serif;
34+
"
35+
>
36+
Your Kilo Pass subscription was cancelled
37+
</h1>
38+
</td>
39+
</tr>
40+
<!-- Body -->
41+
<tr>
42+
<td style="padding: 0 40px 20px">
43+
<p
44+
style="
45+
margin: 0 0 16px;
46+
font-size: 14px;
47+
line-height: 22px;
48+
color: #333;
49+
font-family:
50+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
51+
sans-serif;
52+
"
53+
>
54+
Your Kilo Pass subscription could not be activated because the payment method used
55+
is already associated with an active Kilo Pass subscription on another Kilo
56+
account. Each credit card may only be used for one active Kilo Pass subscription
57+
at a time.
58+
</p>
59+
<p
60+
style="
61+
margin: 0 0 16px;
62+
font-size: 14px;
63+
line-height: 22px;
64+
color: #333;
65+
font-family:
66+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
67+
sans-serif;
68+
"
69+
>
70+
Your subscription has been cancelled and the charge has been refunded. If you
71+
believe this was a mistake, or if you need to use a shared card for legitimate
72+
reasons, please contact our support team.
73+
</p>
74+
</td>
75+
</tr>
76+
<!-- CTA -->
77+
<tr>
78+
<td style="padding: 0 40px 20px">
79+
<table width="100%" cellpadding="0" cellspacing="0">
80+
<tr>
81+
<td align="center" style="padding: 10px 0 20px">
82+
<a
83+
href="{{ support_url }}"
84+
style="
85+
display: inline-block;
86+
padding: 10px 20px;
87+
background-color: #1a1a1a;
88+
color: #ffffff;
89+
text-decoration: none;
90+
border-radius: 7px;
91+
font-size: 13px;
92+
font-weight: 600;
93+
font-family:
94+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
95+
sans-serif;
96+
"
97+
>
98+
Contact Support
99+
</a>
100+
</td>
101+
</tr>
102+
</table>
103+
</td>
104+
</tr>
105+
<!-- Sign-off -->
106+
<tr>
107+
<td style="padding: 0 40px 40px; border-top: 1px solid #ebebea">
108+
<p
109+
style="
110+
margin: 20px 0 0;
111+
font-size: 14px;
112+
line-height: 20px;
113+
color: #333;
114+
font-family:
115+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
116+
sans-serif;
117+
"
118+
>
119+
If you have any questions, reply to this email — we're here to help.
120+
</p>
121+
<p
122+
style="
123+
margin: 16px 0 0;
124+
font-size: 14px;
125+
line-height: 20px;
126+
color: #333;
127+
font-family:
128+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
129+
sans-serif;
130+
"
131+
>
132+
— The Kilo Team
133+
</p>
134+
</td>
135+
</tr>
136+
</table>
137+
<!-- Branding Footer -->
138+
<table width="520" cellpadding="0" cellspacing="0">
139+
<tr>
140+
<td align="center" style="padding: 30px 20px; border-top: 1px solid #eee">
141+
<p
142+
style="
143+
margin: 0;
144+
font-size: 11px;
145+
color: #ccc;
146+
font-family:
147+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
148+
sans-serif;
149+
"
150+
>
151+
© {{ year }} Kilo Code, Inc<br />455 Market St, Ste 1940 PMB 993504<br />San
152+
Francisco, CA 94105, USA
153+
</p>
154+
</td>
155+
</tr>
156+
</table>
157+
</td>
158+
</tr>
159+
</table>
160+
</body>
161+
</html>

apps/web/src/lib/email.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const subjects = {
5454
accountDeletionRequest: 'Kilo: Account Deletion Request Received',
5555
creditsTopUp: 'Your Kilo credit top-up',
5656
kiloClawSubscriptionStarted: 'Your KiloClaw subscription is active',
57+
kiloPassDuplicateCardCanceled: 'Kilo Pass: Subscription Cancelled',
5758
} as const;
5859

5960
export type TemplateName = keyof typeof subjects;
@@ -555,3 +556,15 @@ export async function sendKiloClawSubscriptionStartedEmail(
555556
},
556557
});
557558
}
559+
560+
export async function sendKiloPassDuplicateCardCanceledEmail(
561+
to: string,
562+
props: { supportUrl?: string }
563+
): Promise<SendResult> {
564+
const support_url = props.supportUrl ?? `mailto:hi@kilocode.ai`;
565+
return send({
566+
to,
567+
templateName: 'kiloPassDuplicateCardCanceled',
568+
templateVars: { support_url },
569+
});
570+
}

0 commit comments

Comments
 (0)