Fix KiloClaw early bird checkout to link payments to existing Stripe customer#773
Fix KiloClaw early bird checkout to link payments to existing Stripe customer#773kilo-code-bot[bot] wants to merge 4 commits intomainfrom
Conversation
…ession Replace the static Stripe Payment Link with a server-side Stripe Checkout Session that passes the user's existing stripe_customer_id. This prevents duplicate/guest Stripe customer accounts from being created during early bird purchases.
|
|
||
| createEarlybirdCheckoutSession: baseProcedure | ||
| .output(z.object({ url: z.url().nullable() })) | ||
| .mutation(async ({ ctx }) => { |
There was a problem hiding this comment.
[WARNING]: No guard against duplicate early bird subscriptions
Unlike createCheckoutSession in the Kilo Pass router, which checks for an existing active subscription before creating a checkout session (lines 914-920), this endpoint has no such check. A user could:
- Click the button multiple times (the frontend
isPendingguard only prevents concurrent clicks, not sequential ones after a failure/navigation) - Call the tRPC endpoint directly to create multiple checkout sessions
- End up with multiple active early bird subscriptions on the same Stripe customer
Consider adding a check for an existing active kiloclaw-earlybird subscription on the Stripe customer before creating a new checkout session, similar to how kilo-pass-router.ts does it.
There was a problem hiding this comment.
This is not a subscription.
Code Review SummaryStatus: 4 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
WARNING
Files Reviewed (4 files)
|
| const couponId = getEnvVariable('STRIPE_KILOCLAW_EARLYBIRD_COUPON_ID'); | ||
|
|
||
| const session = await stripe.checkout.sessions.create({ | ||
| mode: 'payment', |
There was a problem hiding this comment.
[WARNING]: No visible server-side fulfillment after payment
This creates a one-time mode: 'payment' checkout session, but there's no webhook handler or server-side logic to actually provision the earlybird benefit after payment completes. The user is redirected to success_url but nothing records the purchase or activates the 6-month compute benefit.
If fulfillment is handled manually or via a separate system (e.g., Stripe dashboard automation), consider adding a code comment documenting that. Otherwise, a checkout.session.completed or charge.succeeded handler for type: 'kiloclaw-earlybird' is needed to complete the flow.
There was a problem hiding this comment.
Fulfillment doesn't exist yet so this is known I think.
We could optionally add the user to a cohort via a webhook but I think we should do that as a follow up PR
| }, | ||
| success_url: `${APP_URL}/claw?earlybird_checkout=success`, | ||
| cancel_url: `${APP_URL}/claw/earlybird?checkout=cancelled`, | ||
| metadata: { |
There was a problem hiding this comment.
[CRITICAL]: Metadata is set on the Checkout Session, but the charge.succeeded webhook handler in stripe.ts checks paymentIntent.metadata.type. Stripe does not automatically copy session-level metadata to the PaymentIntent.
This means isKiloclawEarlybird in handleSuccessfulChargeWithPayment will never be true, and the charge will fall through to the "Unknown charge type" warning branch, triggering a Sentry warning on every earlybird purchase.
Other checkout flows in this codebase (e.g. auto-topup-setup at stripe.ts:829) correctly use payment_intent_data: { metadata: { ... } } to propagate metadata to the PaymentIntent.
Fix: add payment_intent_data to the session creation:
payment_intent_data: {
metadata: {
type: 'kiloclaw-earlybird',
kiloUserId: ctx.user.id,
},
},You may still want session-level metadata for your own lookup purposes, but the PaymentIntent metadata is what the webhook reads.
| const isAutoTopUpSetup = paymentIntent?.metadata?.type === 'auto-topup-setup'; | ||
|
|
||
| // KiloClaw earlybird purchases are handled separately - not a credit top-up | ||
| const isKiloclawEarlybird = paymentIntent?.metadata?.type === 'kiloclaw-earlybird'; |
There was a problem hiding this comment.
[WARNING]: This check will never match unless the metadata is propagated to the PaymentIntent.
The metadata in kiloclaw-router.ts is set on the Checkout Session object (via stripe.checkout.sessions.create({ metadata: { type: 'kiloclaw-earlybird' } })), but Stripe does not automatically copy session-level metadata to the underlying PaymentIntent. To make this guard work, the checkout session creation needs to use payment_intent_data: { metadata: { type: 'kiloclaw-earlybird', kiloUserId: ctx.user.id } } instead of (or in addition to) top-level metadata.
Without this fix, earlybird charges will fall through to the else branch and trigger the "Unknown charge type" Sentry warning.
Summary
buy.stripe.com/...) with a server-side Stripe Checkout Session that passes the user's existingstripe_customer_id, preventing duplicate/guest Stripe accountscreateEarlybirdCheckoutSessiontRPC mutation to thekiloclawRouter, following the same patterns used by Kilo Pass and other checkout flowsProblem
The early bird checkout used a static Stripe Payment Link with only
prefilled_email, which is cosmetic and does not link the payment to the user's existing Stripe customer record. This caused:CleanShot.2026-03-03.at.10.40.01.mp4
This is different than before as it remembers my Stripe user.
NOTE: This requires two new environment variables I've already added to Vercel:
STRIPE_KILOCLAW_EARLYBIRD_PRICE_IDandSTRIPE_KILOCLAW_EARLYBIRD_COUPON_IDChanges
src/routers/kiloclaw-router.tsAdded
createEarlybirdCheckoutSessionmutation that:stripe_customer_idSTRIPE_KILOCLAW_EARLYBIRD_PRICE_IDfrom env vars (the Stripe price configured for the early bird offer)customer: stripeCustomerId— the key fixkiloPass.createCheckoutSession(billing address collection, tax ID collection, customer_update, metadata withtypeandkiloUserId)src/app/(app)/claw/earlybird/page.tsxbuy.stripe.comlink with a tRPC mutation call (trpc.kiloclaw.createEarlybirdCheckoutSession)window.location.hrefafter checkout session creation (same pattern as Kilo Pass)sonnertoast.env.testSTRIPE_KILOCLAW_EARLYBIRD_PRICE_IDtest env varSetup required
The
STRIPE_KILOCLAW_EARLYBIRD_PRICE_IDenvironment variable must be set in Vercel/production to the Stripe Price ID for the early bird subscription product.Built for Brendan by Kilo for Slack