Skip to content

feat(kilo-pass): integrate Churnkey for cancel flow and pause/resume support#2113

Merged
iscekic merged 1 commit intomainfrom
feat/churnkey-integration-v2
Apr 7, 2026
Merged

feat(kilo-pass): integrate Churnkey for cancel flow and pause/resume support#2113
iscekic merged 1 commit intomainfrom
feat/churnkey-integration-v2

Conversation

@iscekic
Copy link
Copy Markdown
Contributor

@iscekic iscekic commented Apr 7, 2026

Summary

Migrates #1799 (feat/churnkey-integration) to the new monorepo structure per the migration prompt. Supersedes #1799.

  • Replace custom 2-step cancel/feedback modals with Churnkey SDK for intelligent cancel flows
  • Add full pause/resume subscription support with kilo_pass_pause_events table
  • Monthly streak calculation skips paused months instead of breaking the streak
  • Yearly cron includes paused subs with 12-issuance cap
  • Rename resumeSubscriptionresumeCancelledSubscription to distinguish from pause resume
  • Fix KiloPassDetail.tsx (from feat(subscriptions): add subscription center with unified billing management #2047) to use renamed procedure

Churnkey integration

  • apps/web/src/lib/churnkey/auth.ts — server-side HMAC-SHA256 auth hash
  • apps/web/src/lib/churnkey/loader.ts — client-side SDK loader with showCancelFlow() and fallback
  • getChurnkeyAuthHash tRPC query
  • Falls back to window.confirm + direct cancellation if SDK fails to load
  • Requires NEXT_PUBLIC_CHURNKEY_APP_ID and CHURNKEY_API_SECRET env vars

Pause/resume support

  • New kilo_pass_pause_events table (migration 0074) with partial unique index and CHECK constraint
  • Pause event DB operations: openPauseEvent, closePauseEvent, getOpenPauseEvent, getPausedMonthSet
  • Detect pause_collection transitions in subscription webhook handler
  • resumePausedSubscription mutation
  • UI shows paused state with resumesAt date and resume button

Verification

  • pnpm --filter web typecheck — passed
  • pnpm --filter @kilocode/db typecheck — passed
  • pnpm --filter web lint — 0 warnings, 0 errors
  • pnpm run format:changed — clean
  • All 59 kilo-pass tests pass (full suite has pre-existing DB timeout flakes in unrelated files)

Test plan

  • Verify Churnkey cancel flow opens with valid auth hash
  • Verify fallback to window.confirm when Churnkey SDK is unavailable
  • Verify pause/resume round-trip via Stripe test mode
  • Verify streak preservation across paused months
  • Verify yearly issuance cap at 12

…support

Migrates feat/churnkey-integration to monorepo structure. Replaces custom
cancel/feedback modals with Churnkey SDK, adds pause/resume subscription
support with pause event tracking, streak preservation across pauses, and
yearly issuance cap.
@iscekic iscekic self-assigned this Apr 7, 2026
@iscekic iscekic requested a review from jeanduplessis April 7, 2026 10:30
@iscekic iscekic enabled auto-merge (squash) April 7, 2026 10:33
: null;
await openPauseEvent(tx, {
kiloPassSubscriptionId,
pausedAt: dayjs().utc().toISOString(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Pause timestamps use webhook processing time

getPausedMonthSet() treats paused_at / resumed_at as the source of truth for streak reconstruction, but this code records the current server time instead of a Stripe-derived pause/resume timestamp. If the webhook is delayed, retried, or delivered out of order, the stored pause window shifts and monthly streak calculation can skip or count the wrong months.

// Close the open pause event so the UI reflects the change immediately.
// The state query derives paused status from open pause events, so closing
// it is sufficient — no need to update the DB status column directly.
await closePauseEvent(db, {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Closing the pause event does not immediately unpause state

getKiloPassStateForUser() falls back to the row's stored status when there is no open pause event. If this subscription already has status = 'paused' in kilo_pass_subscriptions, invalidating getState right after this mutation still returns paused until the webhook updates the row, so the UI can stay stuck in the paused state after a successful resume. This mutation needs to persist the resumed Stripe status as well.

@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot bot commented Apr 7, 2026

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Fix these issues in Kilo Cloud

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/kilo-pass/stripe-handlers-subscription-events.ts 104 Pause history records webhook processing time instead of a Stripe-derived pause timestamp, which can corrupt streak reconstruction when events arrive late or out of order.
apps/web/src/routers/kilo-pass-router.ts 763 Resuming a paused subscription closes the pause event but leaves the stored subscription status as paused, so the UI can remain stuck until a later webhook updates the row.
Other Observations (not in diff)

None.

Files Reviewed (10 files)
  • apps/web/src/lib/kilo-pass/stripe-handlers-subscription-events.ts - 1 issue
  • apps/web/src/routers/kilo-pass-router.ts - 1 issue
  • apps/web/src/lib/kilo-pass/pause-events.ts
  • apps/web/src/lib/kilo-pass/state.ts
  • apps/web/src/lib/kilo-pass/stripe-handlers-invoice-paid.ts
  • apps/web/src/lib/kilo-pass/yearly-monthly-base-cron.ts
  • apps/web/src/lib/churnkey/loader.ts
  • apps/web/src/components/profile/kilo-pass/KiloPassSubscriptionSettingsModal.tsx
  • apps/web/src/components/profile/kilo-pass/useKiloPassSubscriptionInfo.tsx
  • packages/db/src/schema.ts

Reviewed by gpt-5.4-20260305 · 1,716,273 tokens

@iscekic iscekic merged commit 729e2d1 into main Apr 7, 2026
33 checks passed
@iscekic iscekic deleted the feat/churnkey-integration-v2 branch April 7, 2026 11:01
kilo-code-bot bot pushed a commit that referenced this pull request Apr 8, 2026
…support (#2113)

Migrates feat/churnkey-integration to monorepo structure. Replaces custom
cancel/feedback modals with Churnkey SDK, adds pause/resume subscription
support with pause event tracking, streak preservation across pauses, and
yearly issuance cap.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants