trial

package
v1.46.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 2, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package trial implements the new-signup on-ramp: a trialing subscription of the $20/mo entry plan funded with a single unified trial credit.

The model (settled 2026-07-02):

  • Entry plan is the $20/mo "starter" plan (billing/tier.Starter): its allowance is metered down across usage. After the trial the subscriber is on $20/mo, metered, with overage billed.
  • A brand-new signup with NO card gets a 7-day trial; adding a card extends it to 30 days (ExtendForCard). A signup that adds a card up front is just the 7-day path immediately extended to 30 — ExtendForCard starts a 30-day trial when none exists yet.
  • The trial allowance is ONE unified credit spendable across compute OR AI: it is an ordinary tag-scoped, expiring Deposit — the SAME mechanism as billing/allotment's included credit — so it nets into the single balance the gateway's prepaid gate (available > 0) reads and both compute and AI usage debit. No separate buckets, no second gate.

Decomplected from the plan catalog: this package never reads @hanzo/plans. The catalog owner (api/billing) wires the entry plan's economics once via SetEntryPlanResolver; here we only orchestrate subscription + credit.

Index

Constants

View Source
const (
	// PlanSlug is the entry ($20/mo) plan trials seed from. It aligns with the
	// "starter" billing tier (billing/tier.Starter).
	PlanSlug = "starter"

	// CreditTag marks the one unified trial-credit deposit. Exactly one per
	// subject; it is both the funding record and the idempotency anchor.
	CreditTag = "trial-credit"

	// Kind is the destination kind for IAM-bound balance transactions — the
	// same kind the balance read/gate resolves (models/transaction/util).
	Kind = "iam-user"

	// NoCardTrialDays / CardTrialDays are the trial windows. A card on file
	// extends the trial from 7 to 30 days.
	NoCardTrialDays = 7
	CardTrialDays   = 30

	// ProviderType marks trial subscriptions (vs "manual_gift" / "stripe").
	ProviderType = "trial"
)

Variables

View Source
var ErrInvalidSubject = errors.New("trial: invalid subject")

ErrInvalidSubject is returned when the billing subject is empty.

Functions

func SetEntryPlanResolver

func SetEntryPlanResolver(fn func() Plan)

SetEntryPlanResolver wires the catalog-backed resolver for the entry ($20) plan. Called once at startup by the catalog owner (api/billing). Kept as a resolver (not a value) so this package never imports the catalog and startup init-order does not matter. When unset, trial operations are safe no-ops so binaries that never load the catalog still build and run.

Types

type Plan

type Plan struct {
	Slug        string
	Name        string
	Description string
	PriceCents  int64 // recurring monthly price after the trial
	CreditCents int64 // unified trial credit funded for the trial window
	Currency    string
}

Plan is the entry-plan projection the trial needs: the recurring price to bill after the trial, and the unified credit to fund during it. The catalog owner supplies it (see SetEntryPlanResolver).

type Result

type Result struct {
	Started        bool      `json:"started"`  // this call created a new trial
	Extended       bool      `json:"extended"` // this call extended 7 -> 30 days
	Reason         string    `json:"reason,omitempty"`
	SubscriptionID string    `json:"subscriptionId,omitempty"`
	Plan           string    `json:"plan"`
	TrialDays      int       `json:"trialDays"`
	TrialEnd       time.Time `json:"trialEnd,omitempty"`
	CreditCents    int64     `json:"creditCents"`
	TransactionID  string    `json:"transactionId,omitempty"`
}

Result describes the outcome of a trial operation.

func ExtendForCard

func ExtendForCard(db *datastore.Datastore, subject string, isTest bool) (Result, error)

ExtendForCard extends a subject's trial to 30 days when a card is added.

  • Trialing entry-plan subscription found and shorter than 30 days: extend the trial window (from its original start) and stretch the trial credit's expiry to match. (Extended=true)
  • No trial yet: start a fresh 30-day trial (the "signup WITH a card" path), which is a no-op for existing users via Start's new-signup gate.
  • Trial already >= 30 days, or subject has no eligible trial: no-op.

func Start

func Start(db *datastore.Datastore, subject string, cardPresent bool, isTest bool) (Result, error)

Start begins a trial for a brand-new signup: a trialing subscription of the entry plan plus the single unified trial credit. cardPresent selects the window (7 days without a card, 30 with one). isTest routes the credit to the test ledger (org.TestMode()).

New-signup only: if the subject already has a subscription OR any prior balance deposit (an existing/ comped user, or a repeat call), Start is a no-op (Started=false, Reason="not_new"). This is what leaves existing users — and Dave's real $10k ledger — untouched, and makes Start idempotent.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL