Skip to content

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

Merged
jeanduplessis merged 6 commits intomainfrom
kilocode-billing-promo-codes
Mar 23, 2026
Merged

feat(billing): replace coupon with intro pricing and enable promo codes#1362
jeanduplessis merged 6 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.

@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Mar 21, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • src/routers/kiloclaw-billing-router.test.ts
  • src/routers/kiloclaw-router.ts

Reviewed by gpt-5.4-20260305 · 275,297 tokens

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.
…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
…le helpers, harden error paths

- Export and reuse resolvePhasePrice in switchPlan instead of inline extraction
- Extract createAutoIntroSchedule and handleAutoIntroCreateRace from ensureAutoIntroSchedule
- Check validateOrRepairAutoIntroSchedule return value and log unrecoverable schedules
- Make cancelPlanSwitch tolerant of already-terminated schedules
- Remove trial_end tests that are no longer applicable
@jeanduplessis jeanduplessis force-pushed the kilocode-billing-promo-codes branch from 0694c27 to a529c10 Compare March 23, 2026 06:47
…witch behavior changes

- Add items array to mock schedule phases so resolvePhasePrice can extract prices
- Update cancelPlanSwitch test to expect success+clear instead of rejection on release failure
- Fix expected error message in orphaned schedule release test
…anSwitch

Reverts the change that cleared DB state when releaseScheduleIfActive returned
false. Transient failures leave the schedule attached in Stripe, so clearing
local state would create a divergence where the user can't cancel a switch that
will still execute. Instead, adds 'completed' to isScheduleAlreadyInactive to
handle that terminal state properly.
@jeanduplessis jeanduplessis merged commit 76450b7 into main Mar 23, 2026
18 checks passed
@jeanduplessis jeanduplessis deleted the kilocode-billing-promo-codes branch March 23, 2026 07:06
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