invite

package
v1.49.49 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package invite is the commerce paywall's invite-code engine: a platform-minted code that grants ONE org subscription-free access to the commerce admin.

It reimplements the cloud referrals code→org primitive (OrgForCode / Claim) natively on commerce's datastore — commerce never imports cloud. The design is the same one: a GLOBAL directory (the "system" namespace, like Organization / the platform catalog) so a code minted by the platform is redeemable by ANY org, keyed on the code with first-touch, idempotent binding.

One Invite row per code. Its storage id is DETERMINISTIC on the normalized code (DeterministicID), so:

  • Mint is idempotent: re-minting the same code resolves to the SAME row via the storage ON CONFLICT(id,kind,namespace) upsert — never a duplicate.
  • Redeem is first-touch: the first org to redeem a code claims it; a repeat redeem by the SAME org is a no-op replay, and a redeem of an already-claimed code by a DIFFERENT org is refused.

Index

Constants

View Source
const Namespace = "system"

Namespace is the global directory the invite records live in — the same "system" namespace Organization / the platform catalog use, so a platform-minted code is visible to (and redeemable by) every tenant. Never a per-org namespace: the redeeming org is not known at mint time.

Variables

View Source
var (
	// ErrUnknownCode — no invite exists for the presented code.
	ErrUnknownCode = errors.New("invite: unknown code")
	// ErrAlreadyRedeemed — the code was already claimed by a DIFFERENT org.
	ErrAlreadyRedeemed = errors.New("invite: code already redeemed by another org")
	// ErrEmptyCode / ErrEmptyOrg — malformed input.
	ErrEmptyCode = errors.New("invite: empty code")
	ErrEmptyOrg  = errors.New("invite: empty org")
)

Sentinel errors mapped to HTTP status by the handlers.

Functions

func DeterministicID

func DeterministicID(code string) string

DeterministicID derives the stable storage id for a normalized code, so mint is idempotent and each code is a single row (ON CONFLICT dedup at storage).

func Normalize

func Normalize(code string) string

Normalize canonicalizes a code: trimmed + upper-cased so a code pasted in any case resolves. Kept in one place so mint and redeem agree.

func OrgRedeemed

func OrgRedeemed(db *datastore.Datastore, org string) (bool, error)

OrgRedeemed reports whether org holds a redeemed invite — the paywall's invite allow-path. db must be the SystemDB. A single-field query (Org=) then an in-memory Redeemed check avoids a two-equality-filter composite index (which is not guaranteed across datastore backends — see the same rule in billing/trial).

func Query

Query returns a datastore query for invites.

func SystemDB

func SystemDB(ctx context.Context) *datastore.Datastore

SystemDB builds the datastore scoped to the global invite directory. It uses datastore.New with the "system" namespace on the context (NOT NewNamespaced, which fail-closes to no DB for reserved namespaces) — identical to how the platform catalog reaches its system-namespace store.

Types

type Invite

type Invite struct {
	mixin.Model[Invite]

	// Code is the normalized (upper-cased, trimmed) invite code — also the
	// natural key the deterministic storage id is derived from.
	Code string `json:"code"`

	// Org is the org that redeemed the code (empty until redeemed). Once set it
	// is immutable — first-touch attribution.
	Org string `json:"org,omitempty"`

	// Note is optional free text captured at mint time (who/why), for audit.
	Note string `json:"note,omitempty"`

	// Redeemed latches true on the first successful redeem.
	Redeemed   bool      `json:"redeemed"`
	CreatedAt  time.Time `json:"createdAt"`
	RedeemedAt time.Time `json:"redeemedAt,omitempty"`
}

Invite is one code→org grant. Redeemed+Org record the first-touch binding.

func Mint

func Mint(db *datastore.Datastore, code, note string) (*Invite, error)

Mint creates (or returns the existing) invite for a code. Idempotent on the normalized code: minting the same code twice returns the same row rather than a duplicate — the deterministic id makes concurrent first-mints collapse to ONE row via the storage ON CONFLICT(id,kind,namespace) upsert. db must be the SystemDB.

func New

func New(db *datastore.Datastore) *Invite

New returns a datastore-wired Invite.

func Redeem

func Redeem(db *datastore.Datastore, code, org string) (*Invite, bool, error)

Redeem binds a code to org, first-touch. Returns (invite, redeemedNow, err):

  • unknown code → ErrUnknownCode
  • already redeemed by the SAME org → (invite, false, nil) — idempotent replay
  • already redeemed by a DIFFERENT org → ErrAlreadyRedeemed
  • fresh → (invite, true, nil) — org bound

db must be the SystemDB. org is the VALIDATED caller's org (never client-supplied).

func (*Invite) Load

func (i *Invite) Load(ps []datastore.Property) error

func (*Invite) Save

func (i *Invite) Save() ([]datastore.Property, error)

func (*Invite) Validator

func (i *Invite) Validator() *val.Validator

Jump to

Keyboard shortcuts

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