Skip to content

feat(billing): replace coupon with intro pricing and enable promo codes#1362

Open
jeanduplessis wants to merge 3 commits intomainfrom
kilocode-billing-promo-codes
Open

feat(billing): replace coupon with intro pricing and enable promo codes#1362
jeanduplessis wants to merge 3 commits intomainfrom
kilocode-billing-promo-codes

Conversation

@jeanduplessis
Copy link
Contributor

Summary

  • Replace the Stripe coupon-based first-month discount for Standard plan with an introductory price approach (STRIPE_KILOCLAW_STANDARD_INTRO_PRICE_ID), enabling allow_promotion_codes: true on all KiloClaw checkout sessions (both Standard and Commit plans). This unblocks Rewardful affiliate promo code entry, which was previously blocked by Stripe's mutual exclusivity between discounts and allow_promotion_codes.
  • New ensureAutoIntroSchedule idempotent helper creates a 2-phase Stripe subscription schedule (intro → regular standard price) with end_behavior: 'release', called from subscription.created webhook, reactivation, and cancel-plan-switch flows. Auto schedules are tagged with metadata.origin: 'auto-intro' to distinguish them from user-initiated plan switches.
  • All billing flows (cancel, reactivate, switchPlan, cancelPlanSwitch) updated to handle auto schedules transparently — live-fetch reconciliation detects "hidden schedules" (Stripe has schedule but DB pointer is null) and handles them correctly. Auto schedules are invisible in the UI; users can still switch plans or cancel without interference.
  • New billing lifecycle cron sweep repairs stranded intro-price subscriptions that are missing their auto schedule (e.g., due to partial failure during schedule creation).

Verification

  • pnpm typecheck — passed (all packages)
  • pnpm jest src/routers/kiloclaw-billing-router.test.ts — 42 tests passed
  • pnpm run format:check — passed
  • pnpm run lint — passed (0 warnings, 0 errors)
  • Pre-push hooks (format, lint, typecheck) — all passed
  • Spec verification: all 12 relevant spec rules verified as PASS against implementation

Visual Changes

N/A

Reviewer Notes

  • Pre-deploy manual step: Create a new Stripe Price for Standard at the intro amount (recurring monthly), set its ID in STRIPE_KILOCLAW_STANDARD_INTRO_PRICE_ID. After deploy, STRIPE_KILOCLAW_STANDARD_FIRST_MONTH_COUPON_ID can be removed from environment.
  • Key architectural pattern: ensureAutoIntroSchedule uses a live Stripe fetch as single source of truth — it never trusts stale webhook payloads or DB state for schedule decisions. Race guards handle concurrent schedule creation. Partial failures are tolerated: the cron sweep (sweep 5) repairs stranded subscriptions on subsequent runs.
  • Hidden-schedule reconciliation in cancel/switchPlan flows ensures that even if the DB loses its schedule pointer (e.g., Stripe schedule created but DB persist failed), the schedule is still detected and handled correctly via live fetch.
  • as casts minimized: Stripe schedule/price references use typeof checks with .id fallback instead of as string casts, per coding style rules.
  • Assumption: Rewardful promo codes are configured as one-time (first-invoice-only) discounts. If recurring promo discounts are needed, schedule phase definitions must forward discounts from the current phase.

Replace the standard plan first-month coupon with an introductory Stripe
Price and automatic schedule-based transition to the regular price.
Enable allow_promotion_codes on all checkout sessions for Rewardful
affiliate promo codes. Add switchPlan, cancelPlanSwitch flows, and a
cron sweep to repair stranded intro-price subscriptions.
@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Mar 21, 2026

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0

Fix these issues in Kilo Cloud

Issue Details (click to expand)

N/A

Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
src/routers/kiloclaw-router.ts N/A switchPlan still ignores the false return from releaseScheduleIfActive when releasing a hidden schedule, so a transient Stripe failure can leave the old schedule attached while local state proceeds as if it was removed.
Files Reviewed (1 file)
  • src/lib/kiloclaw/stripe-handlers.ts - 0 issues

Reviewed by gpt-5.4-20260305 · 134,413 tokens

…or handling

- Validate attached auto-intro schedules have the expected 2-phase
  structure before reusing them. If only phase 1 exists (half-configured
  from a prior failed rewrite), repair by adding the regular-price phase 2.
- Abort switchPlan when releasing a hidden non-auto schedule fails with a
  transient error, preventing the subsequent create from being rejected by
  Stripe (subscription still attached to old schedule).
- Derive phase-1 price from the newly created schedule's current phase
  (which mirrors the subscription at create-time) instead of the earlier
  subscriptions.retrieve(), avoiding stale price if a schedule released at
  a billing boundary between the two calls.
…riting user plan switches

- Re-throw non-race errors from subscriptionSchedules.create() instead of
  silently swallowing them, so transient Stripe failures surface to callers
- Check scheduled_by before repairing auto-intro schedules to avoid
  overwriting user-initiated plan switches that reuse the same schedule
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.

1 participant