Skip to content

Send the workspace's first successful call when cancelling - #1319

Merged
helpcodeai merged 1 commit into
mainfrom
helpcodeai/cancel-subscription-discount
Oct 28, 2026
Merged

helpcodeai merged 1 commit into
mainfrom
helpcodeai/cancel-subscription-discount

Conversation

@helpcodeai

Copy link
Copy Markdown
Contributor

Summary

"Cancel subscription" opens Stripe's cancellation page through the licence site, which shows a discount to keep the customer. Until now everyone got the same offer (30% for 3 months), including card trials that never made a single tool call, where a discount does not address why they leave. The licence site now picks the offer by segment (paid / card trial that used the product / card trial that never did / yearly plan). To tell the two card-trial cases apart it needs to know whether the workspace ever made a successful tool call; this PR sends that, and nothing else. The offer logic stays on the licence site.

Changes

  • LicenseService.createBillingPortalSession: on flow: 'cancel' only, the body to /api/billing/portal carries usage: { firstSuccessfulCallAt } — read from OrgSettings first_successful_call_at, or the workspace's earliest SUCCESS tool invocation, null when there is none.
  • Cloud: sent on cancel only. Self-hosted: nothing is sent (it has no in-app billing portal; the licence site refuses it without a service token anyway).
  • A failed lookup omits the field and the cancellation opens as before. The licence site treats a missing field as "used", so an older or newer side on either end keeps today's behaviour.

Type

  • Bug fix
  • New feature
  • Documentation
  • Refactoring
  • Performance improvement
  • Other (describe below)

Testing

  • Added/updated unit tests
  • Tested manually (describe steps below)
  • No tests needed (explain why)

Run in packages/backend after npm ci and npx prisma generate:

  • npx jest src/license: 8 suites, 175 tests passed (5 new: recorded first success, fallback to the earliest invocation, never used → null, failed lookup → no usage and the portal still opens, no usage for other flows nor on self-hosted).
  • npx tsc --noEmit -p tsconfig.json: no errors.
  • npm run lint: 0 errors, 11 existing warnings in other files; eslint --fix changed no file.
  • npm test: 0 failed, 23690 passed. The first full run had 1 failure that did not reproduce in two re-runs.

Checklist

  • My code follows the existing code style
  • I have tested my changes locally
  • I have updated documentation (if applicable) — no user-facing change in the app
  • All existing tests pass (npm test)
  • This PR has a descriptive title

Related Issues

The segmented offer itself is in the licence site (private repository).

"Cancel subscription" opens Stripe's cancellation page through the licence
site, which offers a discount to keep the customer. The site now picks the
offer by segment, and a card trial that never got a tool call through gets
no discount: a cheaper price does not fix a product that never worked for
them.

On the cancel flow only, the Cloud adds usage.firstSuccessfulCallAt (from
OrgSettings first_successful_call_at, or the workspace's earliest SUCCESS
invocation; null when there is none). Self-hosted sends nothing, and a
failed lookup omits the field so cancelling never waits on it. The offer
logic itself stays on the licence site.
@helpcodeai
helpcodeai merged commit 7067976 into main Oct 28, 2026
15 checks passed
@helpcodeai
helpcodeai deleted the helpcodeai/cancel-subscription-discount branch October 28, 2026 21:16
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 28, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant