Skip to content

feat(subscription): plan, trial, subscribe, and on-demand billing#28

Open
abhisek wants to merge 4 commits into
mainfrom
feat/subscription
Open

feat(subscription): plan, trial, subscribe, and on-demand billing#28
abhisek wants to merge 4 commits into
mainfrom
feat/subscription

Conversation

@abhisek

@abhisek abhisek commented Jul 23, 2026

Copy link
Copy Markdown
Member

What

Adds the safedep subscription domain — the tenant's plan and access lifecycle — over the control-plane SubscriptionService and BillingService.

safedep subscription status
safedep subscription trial enable
safedep subscription create [--seats N]
safedep subscription ondemand enable --accept-terms | disable | status
safedep subscription customer create | show
safedep subscription portal open

Design

  • Domain named for intent, not mechanism. All operations are "manage my plan/access." Low-level billing (invoices, payment methods, cancellation) is never surfaced in the CLI — it's delegated to the provider-hosted portal. That's why the domain is subscription, not billing.
  • create targets the Professional plan (the only self-serve tier; Enterprise is custom → contact sales), takes --seats (default 5, min 1), opens the provider checkout in the browser, and polls GetSubscriptionAccountStatus until active (completion is webhook-gated). Checkout/portal return URLs match the web app's billing flow (app.safedep.io/settings/billing[...]).
  • ensureCustomer runs before trial and subscribe: interactive billing-profile prompts for humans, flags for agents, keyed off GetBillingCustomer returning NOT_FOUND.
  • ondemand enable requires --accept-terms (records terms version 2026-07-23, prints safedep.io/terms/) and maps the typed ErrorReason to the next action: ENTITLEMENT_NOT_AVAILABLE → subscribe, PAYMENT_METHOD_REQUIRED → portal.
  • status is the hub — status, tier, trial days, on-demand summary, entitlements — and every dead-end ends with the exact next command.
  • Output follows the repo's Renderable contract (table/plain/json); browser is opened for humans, only printed for agents.

Verbs

Adds open to the allow-list (internal/cmd/verbs.go) for browser/portal session commands (subscription portal open). Justification: opening a provider-hosted browser session is a distinct action not covered by existing verbs. All other leaves use existing verbs.

API bump

Bumps the safedep/api generated bindings to ...20260723065119 to pick up the on-demand billing messages/RPCs (GetOnDemandBillingState, EnableOnDemandBilling, DisableOnDemandBilling, TenantOnDemandBillingState).

Follow-ups (noted in the design spec)

  • Public terms content/version API (the CLI ships a constant terms_version for now).
  • Multi-environment app base URL (return URLs hardcode production).
  • --tier selection when more self-serve tiers exist.

Testing

make build, make test, golangci-lint run ./..., and the convention tests all pass locally. Table-driven unit tests cover enum mapping, ErrorReason routing, ensureCustomer (exists / missing-flags / creates-from-flags), trial enable, the create branches (already-active / checkout-error / needs-checkout), seat min-1 validation, and the renderers.

🤖 Generated with Claude Code

…mands

Add the `safedep subscription` domain over the control-plane
SubscriptionService and BillingService:

  subscription status
  subscription trial enable
  subscription create [--seats N]
  subscription ondemand enable --accept-terms | disable | status
  subscription customer create | show
  subscription portal open

- Named for user intent (plan/access), not billing mechanism: low-level
  billing (invoices, payment methods) is delegated to the hosted portal.
- create targets the Professional plan (only self-serve tier; Enterprise is
  custom), takes --seats (default 5, min 1), opens checkout in the browser,
  and polls status until active. Return URLs match the web app's billing flow.
- trial and create share an ensureCustomer step: interactive billing-profile
  prompts for humans, flags for agents, keyed off GetBillingCustomer NOT_FOUND.
- ondemand enable requires --accept-terms (records terms version 2026-07-23)
  and routes the typed ErrorReason to the next action: ENTITLEMENT_NOT_AVAILABLE
  -> subscribe, PAYMENT_METHOD_REQUIRED -> portal.
- Bumps the safedep/api bindings to pick up on-demand billing.

Adds the `open` verb to the allow-list for browser/portal session commands.

Docs: one page per leaf under docs/cmd, linked from README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@safedep

safedep Bot commented Jul 23, 2026

Copy link
Copy Markdown

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

Package Details
Package Malware Vulnerability Risky License Report
icon buf.build/gen/go/safedep/api/grpc/go @ v1.6.2-20260723065119-39b8380c713a.1
go.mod
ok icon
ok icon
ok icon
🔗
icon buf.build/gen/go/safedep/api/protocolbuffers/go @ v1.36.11-20260723065119-39b8380c713a.1
go.mod
ok icon
ok icon
ok icon
🔗
icon github.com/package-url/packageurl-go @ v0.1.3
go.mod
ok icon
ok icon
ok icon
🔗
icon github.com/safedep/dry @ v0.0.0-20260722071734-21a71cf9d6e3
go.mod
ok icon
ok icon
ok icon
🔗
icon golang.org/x/term @ v0.42.0
go.mod
ok icon
ok icon
ok icon
🔗
icon sigs.k8s.io/yaml @ v1.4.0
go.mod
ok icon
ok icon
ok icon
🔗

View complete scan results →

This report is generated by SafeDep Github App

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 264a55d4cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/cmd/subscription/service.go Outdated
Comment thread internal/cmd/subscription/trial.go Outdated
Comment thread internal/cmd/subscription/flow.go Outdated
abhisek and others added 3 commits July 23, 2026 20:46
The entitlements table added noise to the common `subscription status`
view. Hide it by default and surface it only when --entitlements is
passed, consistently across table, plain, and json output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Trial enable no longer claims activation before it completes: runTrialEnable
  reports whether a trial/active status was actually observed, and the command
  prints a "still syncing" warning otherwise (covers --wait=false and timeout).
- openInBrowser surfaces a browser-launch failure via dry/log plus a manual-open
  hint instead of discarding the error.
- Billing remediation and status hints now point to `subscription portal open`
  (the actionable leaf) instead of the `portal` parent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ctivity

Address review on the wait/poll and interactivity behavior:

- pollUntilStatus derives a timeout-bounded context so each status RPC is
  capped by --timeout (a stalled control-plane call can no longer outlast
  the flag), and a timeout now returns an error instead of (acct,false):
  waited trial-enable and create fail non-zero instead of exiting 0 on an
  unconfirmed wait, so automation does not treat it as success.
- interactive() keys off real terminal state (stdin is a TTY), excluding
  agent mode, instead of the user-overridable --output/SAFEDEP_OUTPUT
  presentation mode. Prompts and browser launch no longer break under
  SAFEDEP_OUTPUT=plain in a terminal, nor block on stdin under forced rich
  in CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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