affiliates

package
v1.801.468 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package affiliates is a partner program that pays commission on what your referrals spend.

Partners apply, get approved with a commission rate and a share link, and earn an ONGOING COMMISSION on the metered spend of every customer they refer, accrued per period and paid out in credits or cash.

It is one of THREE programs in this repo built on the same shape — apply/connect, approve, attribute, accrue at-most-once per (party, counterparty, period), pay out against pending = accrued − paid. apps/referrals is the one-time bonus for both sides; apps/authors is the royalty for OSS authors on deploy spend. All three share the commerce ledger path (a credits payout is a grant, tag grant:affiliate) and each carries a byte-identical copy of commerce.go over apps/payout.

The loop, end to end:

  1. An org APPLIES to be an affiliate (POST /v1/affiliates/apply), optionally requesting a vanity code. Staff APPROVE it (POST /v1/admin/affiliates/:id/ approve), which mints the code (vanity if free, else a derived slug) and sets a commission rate (default 20%). The affiliate now has a link https://<brand>/?aff=<code>.
  2. A new org signs up via the link → the console posts POST /v1/affiliates/ attribute with the code → we record referred_org↔affiliate (first-touch, one per referred org, self-attribution blocked).
  3. The ACCRUAL SWEEP (POST /v1/admin/affiliates/sweep, SuperAdmin; also lazy on the affiliate's own dashboard read) folds over each affiliate's referred orgs: commission = the referred org's metered spend THIS PERIOD × the rate, accrued into the affiliate's balance as an affiliate_event. The accrual is LATCHED at-most-once per (affiliate, referred_org, period) — a re-run in the same period never double-accrues, mirroring the referral credit latch.
  4. Staff RECORD a payout of accrued commission (POST /v1/admin/affiliates/:id/payout, record-only — a human settles it): a "credits" method issues a commerce grant into the affiliate's wallet; cash methods (wire/paypal/…) are record-only. A payout can never exceed pending (accrued − paid), guarded atomically.

Surface:

GET  /v1/affiliates                        (org)          my status, code, link, referred count, accrued/pending/paid, payouts
POST /v1/affiliates/apply                  (org)          apply to the program (optional vanity code)
POST /v1/affiliates/attribute              (org=referred) record attribution from an ?aff code
GET  /v1/admin/affiliates                  (SuperAdmin) every affiliate + a summary
POST /v1/admin/affiliates/:id/approve      (SuperAdmin) approve + mint the code
POST /v1/admin/affiliates/:id/suspend      (SuperAdmin) suspend
POST /v1/admin/affiliates/:id/payout       (SuperAdmin) RECORD a payout (record-only; a human settles it)
POST /v1/admin/affiliates/sweep            (SuperAdmin) accrue commission for every referred org this period

serve.go auto-registers GET /v1/affiliates/health.

Index

Constants

View Source
const (
	StatusApplied   = "applied"
	StatusApproved  = "approved"
	StatusSuspended = "suspended"
)

Status values. An affiliate advances applied → approved (and can be suspended). Only an APPROVED affiliate has a code and accrues commission.

Variables

This section is empty.

Functions

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires the affiliates surface onto app per HIP-0106. Complex flavour: it holds a package-global (mounted) so Shutdown can release the store, so it constructs the Service value directly rather than via cloud.Mount.

func Shutdown

func Shutdown() error

Shutdown flushes any pending link clicks, then closes the affiliates store. Idempotent.

Types

type Accrual

type Accrual struct {
	ID              string `json:"id"`
	AffiliateID     string `json:"affiliateId"`
	ReferredOrg     string `json:"referredOrg"`
	Period          string `json:"period"`
	Level           int    `json:"level"`
	SpendCents      int64  `json:"spendCents"`  // the source org's gross charge (revenue), unchanged truth
	MarginCents     int64  `json:"marginCents"` // Hanzo's margin on that spend — the share base
	CommissionCents int64  `json:"commissionCents"`
	CreatedAt       int64  `json:"createdAt"`
}

Accrual is one per-period PROFIT-SHARE event (the affiliate_event) — the derived share-ledger row keyed by referrer. For the source org's period it records the source's gross metered spend (SpendCents, the customer charge, unchanged truth), the MARGIN Hanzo earned on it (MarginCents = spend × the platform margin fraction), and the affiliate's SHARE of that margin (CommissionCents = margin × the level rate). The share comes OUT OF Hanzo's margin — never the customer's bill — so the invariant CommissionCents ≤ MarginCents holds by construction (level rate ≤ 100%). UNIQUE(affiliate, referred, period) makes the sweep at-most-once per period — the commission latch. Level is the upline distance from the source org to this affiliate (1=direct, 2, 3); a given (affiliate, source, period) has exactly one level because the graph is a forest.

type Affiliate

type Affiliate struct {
	ID            string `json:"id"`
	Org           string `json:"-"` // the affiliate's own org; admin view re-exposes it
	OwnerUser     string `json:"-"` // the user who applied; the head of this affiliate's user-referral chain
	Code          string `json:"code"`
	RequestedCode string `json:"-"` // vanity code requested at apply, pending staff approval
	Status        string `json:"status"`
	RateBps       int64  `json:"rateBps"`      // the affiliate's DIRECT (L1) commission rate; upline levels use platform constants
	AccruedCents  int64  `json:"accruedCents"` // lifetime commission accrued
	PaidCents     int64  `json:"paidCents"`    // lifetime commission paid out
	// Handle is the OPT-IN public leaderboard display name. Empty ⟹ NOT listed on the
	// public leaderboard by name; the affiliate's own rank stays private-visible to
	// itself. The org identity is NEVER exposed on the leaderboard — only this
	// self-chosen handle, for affiliates who opt in.
	Handle      string `json:"handle,omitempty"`
	CreatedAt   int64  `json:"createdAt"`
	ApprovedAt  int64  `json:"approvedAt"`
	SuspendedAt int64  `json:"suspendedAt"`
}

Affiliate is one partner org enrolled in the commission program. Org is UNIQUE (one affiliate per org); Code is UNIQUE across affiliates (minted on approval, vanity opt-in). RateBps is the commission rate in basis points (2000 = 20%).

func (Affiliate) PendingCents

func (a Affiliate) PendingCents() int64

PendingCents is the commission earned but not yet paid (never negative).

type AffiliateReferral

type AffiliateReferral struct {
	ID          string `json:"id"`
	AffiliateID string `json:"affiliateId"`
	ReferredOrg string `json:"referredOrg"`
	ReferrerOrg string `json:"referrerOrg"`
	Code        string `json:"code"`
	CreatedAt   int64  `json:"createdAt"`
}

AffiliateReferral is one referred_org → affiliate attribution edge — ALSO the referredBy graph edge. ReferredOrg is UNIQUE across the table (an org is attributed to at most one affiliate, ever — first-touch, i.e. referredBy is set-once/immutable), which is also the idempotency key for POST /v1/affiliates/ attribute. ReferrerOrg is the attributing affiliate's own org, denormalized so the upline climb is a pure edge walk (referred_org → referrer_org → …) with no per-hop join. The recursive walk over these edges IS the multi-level upline.

type LeaderboardEntry

type LeaderboardEntry struct {
	AffiliateID   string `json:"-"`
	Handle        string `json:"handle"`
	AccruedCents  int64  `json:"accruedCents"`
	ReferredCount int    `json:"referredCount"`
}

LeaderboardEntry is one ranked affiliate for the privacy-preserving leaderboard: the opt-in handle (never the org), the aggregate accrued share, and the referred count. The affiliate id is carried only so the handler can flag the caller's OWN row; it is never emitted for other affiliates.

type Link struct {
	ID          string `json:"id"`
	AffiliateID string `json:"-"`
	Code        string `json:"code"`
	Label       string `json:"label"`
	Clicks      int64  `json:"clicks"`
	CreatedAt   int64  `json:"createdAt"`
}

Link is one shareable referral link an affiliate created: a unique code (in the SAME global directory as an affiliate's primary code, so either resolves an attribution) plus a label and a click counter. The primary code is mirrored as a link row on approval so click tracking is uniform across every code. Signups and conversions are DERIVED (counted from the attribution + accrual tables by code), never stored, so they can never drift from the ledger.

type OrgEarning

type OrgEarning struct {
	ReferredOrg     string `json:"referredOrg"`
	MarginCents     int64  `json:"marginCents"`
	CommissionCents int64  `json:"commissionCents"`
}

OrgEarning is one row of an affiliate's per-referred-org contribution: AGGREGATE margin + share attributed to that org across all periods. It is the affiliate's OWN downline (orgs it referred) — aggregate only, never the referred org's raw usage.

type Payout

type Payout struct {
	ID          string `json:"id"`
	AffiliateID string `json:"affiliateId"`
	AmountCents int64  `json:"amountCents"`
	Method      string `json:"method"`
	Reference   string `json:"reference"`
	Txn         string `json:"txn,omitempty"`
	CreatedAt   int64  `json:"createdAt"`
}

Payout is one recorded disbursement of accrued commission. A "credits" method issues a commerce grant (Txn set); cash methods (wire/paypal/…) are record-only.

type PeriodEarning

type PeriodEarning struct {
	Period          string `json:"period"`
	MarginCents     int64  `json:"marginCents"`
	CommissionCents int64  `json:"commissionCents"`
}

PeriodEarning is one row of an affiliate's per-period share ledger: the margin base and the share earned in that period, summed over every referred org + level.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is the affiliates database. ONE SQLite file holds every org's affiliate record, attribution edges, accrual events, and payouts. A code→affiliate lookup is a GLOBAL directory by design (a referred org presents a code minted by ANY affiliate); every /v1/affiliates read is scoped by the caller's org server-side.

func (*Store) AccrualsForSource

func (s *Store) AccrualsForSource(ctx context.Context, referredOrg string) ([]Accrual, error)

AccrualsForSource returns every accrual generated by ONE source org's spend, across the upline levels — the per-event share-ledger rows for that source. It is the drill-down that proves the invariant Σ(commission) ≤ margin for a source, and each row's MarginCents is the same source margin base. Ordered by level.

func (*Store) Accrue

func (s *Store) Accrue(ctx context.Context, accrualID, affiliateID, referredOrg, period string, level int, spendCents, marginCents, commissionCents, now int64) (moved bool, err error)

Accrue records — or, for the still-open current period, TOPS UP — the one accrual for (affiliate, referredOrg, period) and moves the affiliate's accrued balance by the change, in a single transaction. UNIQUE(affiliate, referred, period) is the latch key.

Why top-up: the period key is monthly and the source spend is MONTH-TO-DATE (commerce's usage rollup), so one period is swept many times as the month fills in. The FIRST sweep inserts the row; each later sweep in the SAME period recomputes the share from the current (higher) month-to-date spend and SETS the row to it, adding only the positive delta to accrued_cents. The share therefore CONVERGES to the true month-end value instead of freezing at whatever partial spend the first sweep happened to read.

Monotone + bounded by construction:

  • month-to-date spend never decreases, so a later commissionCents is never smaller; a lower-or-equal recompute (an unchanged, late, or corrected reading) is a NO-OP, so accrued_cents only ever RISES and never overshoots the month-end value (each reading ≤ the final month-to-date).
  • the row always stores margin + commission from the SAME spend reading, so the per-event invariant commissionCents ≤ marginCents holds at EVERY step, and the level schedule cap keeps Σ(commission) over the levels ≤ that source's margin — the money invariant is untouched by the top-up.

Returns moved=true when THIS call changed the balance (an insert or a top-up), so the caller counts real accrual movements and writes one audit row per movement. level is the upline distance recorded for analytics (1=direct, 2, 3), fixed per (affiliate, source) by the forest, so only the spend-derived columns move on a top-up.

func (*Store) AccruedByLevel

func (s *Store) AccruedByLevel(ctx context.Context) (map[int]int64, error)

AccruedByLevel returns level → total commission accrued at that upline level, the analytics breakdown of platform commission liability by depth.

func (*Store) AffiliateForCode

func (s *Store) AffiliateForCode(ctx context.Context, code string) (Affiliate, error)

AffiliateForCode reverse-resolves an affiliate code to its APPROVED owner (an un-approved affiliate has no code). It matches the affiliate's PRIMARY code first, then any secondary shareable link code — both live in the ONE global code directory. Trims + lower-cases the client-supplied code. Only an approved affiliate resolves (a suspended/applied affiliate's codes stop attributing).

func (*Store) AllReferredOrgs

func (s *Store) AllReferredOrgs(ctx context.Context, limit int) ([]string, error)

AllReferredOrgs returns every org that has a referredBy edge (the source set the admin sweep folds over), oldest-first, bounded.

func (*Store) Apply

func (s *Store) Apply(ctx context.Context, id, org, ownerUser, requestedCode string, rateBps int64) (Affiliate, bool, error)

Apply enrolls org as an affiliate at status=applied with the default rate, idempotently. requestedCode is the optional vanity code (validated + minted at approval); ownerUser is the applying user (the head of this affiliate's user chain). A repeat apply returns the EXISTING record (first apply wins). Returns (affiliate, created).

func (*Store) Approve

func (s *Store) Approve(ctx context.Context, id, wantCode string, now int64) (Affiliate, error)

Approve moves an affiliate to approved and mints its code: wantCode wins, else the requested vanity code, else a deterministic derived slug. A non-empty code is validated + uniqueness-enforced (errCodeTaken on collision with another affiliate). Idempotent for the same resolved code.

func (*Store) Attribute

func (s *Store) Attribute(ctx context.Context, id, affiliateID, referredOrg, affiliateOrg, code string) (AffiliateReferral, bool, error)

Attribute records a referredOrg → affiliate edge (referrer = affiliateOrg), idempotently — this is the set-once referredBy edge of the upline graph. The referred org is the VALIDATED caller (never client-supplied). One-per-referred-org (UNIQUE) makes a repeat attribute a no-op returning the FIRST edge (first-touch wins, immutable). Self-attribution is refused; an edge that would close an upline loop is refused (errCycle). Returns (edge, created).

func (*Store) Close

func (s *Store) Close() error

Close closes the underlying database. Idempotent-safe via sql.DB.

func (*Store) ConversionsByCode

func (s *Store) ConversionsByCode(ctx context.Context, affiliateID string) (map[string]int, error)

ConversionsByCode returns code → count of DISTINCT referred orgs (attributed with that code) that produced positive commission for THIS affiliate. Scoped by affiliate_id (the leading bound predicate).

func (s *Store) CountLinks(ctx context.Context, affiliateID string) (int, error)

CountLinks returns how many links an affiliate has (the per-affiliate cap guard).

func (*Store) CountReferrals

func (s *Store) CountReferrals(ctx context.Context, affiliateID string) (int, error)

CountReferrals returns how many orgs an affiliate has referred.

func (s *Store) CreateLink(ctx context.Context, id, affiliateID, code, label string, now int64) (Link, error)

CreateLink records a new shareable link. The code must be valid + free across the ONE global directory (no affiliate primary code, no other link). errInvalidCode on a malformed code, errCodeTaken on a collision. Returns the created row.

func (*Store) DownlineByLevel

func (s *Store) DownlineByLevel(ctx context.Context, ancestorOrg string, depth int) (map[string]int, error)

DownlineByLevel walks DOWN from ancestorOrg over referredBy edges to `depth`, returning source org → its level below ancestorOrg (1=direct child, 2, 3). This is the per-affiliate accrual set for the lazy dashboard sweep (mirror of UplineOrgs).

func (*Store) EarningsByPeriod

func (s *Store) EarningsByPeriod(ctx context.Context, affiliateID string, limit int) ([]PeriodEarning, error)

EarningsByPeriod returns an affiliate's per-period share ledger (margin base + share earned), newest period first, bounded. Scoped by affiliate_id (leading bound).

func (*Store) EarningsByReferredOrg

func (s *Store) EarningsByReferredOrg(ctx context.Context, affiliateID string, limit int) ([]OrgEarning, error)

EarningsByReferredOrg returns the AGGREGATE margin + share an affiliate earned per org it DIRECTLY referred (level 1), largest share first, bounded. Restricting to the direct level means the breakdown never exposes a sub-downline org's identity to an upline affiliate that did not refer it (deeper-level earnings still count in the period + lifetime totals). Aggregate totals only — never the referred org's raw usage. Scoped by affiliate_id (leading bound predicate).

func (s *Store) EnsureLink(ctx context.Context, id, affiliateID, code, label string, now int64) error

EnsureLink idempotently mirrors an affiliate's PRIMARY code as a link row (called on approval so click tracking is uniform across every code). A pre-existing code is a silent no-op — never an error.

func (*Store) FlushClicks

func (s *Store) FlushClicks(ctx context.Context, tally map[string]int64) error

FlushClicks folds a batch of coalesced click tallies (code → count) into affiliate_links in ONE transaction (clicks += n per code); an unknown code no-ops. This is the ONLY path that writes the vanity click counter to the money DB, and it is batched + read/shutdown-driven (never per-click), so a public click flood can never contend with the accrual/payout write path. Clicks are never read by any accrual or payout, so a tally lost on a flush error or a crash is harmless.

func (*Store) GetByID

func (s *Store) GetByID(ctx context.Context, id string) (Affiliate, error)

GetByID re-reads an affiliate by id (post-mutation refresh).

func (*Store) GetByOrg

func (s *Store) GetByOrg(ctx context.Context, org string) (Affiliate, error)

GetByOrg reads the affiliate record for org, or errNotFound.

func (*Store) LeaderboardTop

func (s *Store) LeaderboardTop(ctx context.Context, limit int) ([]LeaderboardEntry, error)

LeaderboardTop returns the top approved affiliates by lifetime accrued share (descending, id tiebreak) with handle + referred count, bounded. The handler applies the opt-in privacy filter (only handled rows are public) and flags the caller's own row. NEVER exposes org identity.

func (*Store) ListAll

func (s *Store) ListAll(ctx context.Context, limit int) ([]Affiliate, error)

ListAll returns every affiliate newest-first (the admin directory), bounded.

func (*Store) ListApproved

func (s *Store) ListApproved(ctx context.Context, limit int) ([]Affiliate, error)

ListApproved returns every approved affiliate (the sweep set), oldest-first.

func (s *Store) ListLinks(ctx context.Context, affiliateID string, limit int) ([]Link, error)

ListLinks returns an affiliate's links, primary first (created_at ASC), bounded.

func (*Store) ListPayouts

func (s *Store) ListPayouts(ctx context.Context, affiliateID string, limit int) ([]Payout, error)

ListPayouts returns an affiliate's payout history, newest-first, bounded.

func (*Store) ListReferrals

func (s *Store) ListReferrals(ctx context.Context, affiliateID string, limit int) ([]AffiliateReferral, error)

ListReferrals returns an affiliate's attribution edges (the orgs it referred), newest-first, bounded.

func (*Store) RankOf

func (s *Store) RankOf(ctx context.Context, id string, accruedCents int64) (rank, total int, err error)

RankOf returns the exact 1-based rank of an affiliate among all APPROVED affiliates by accrued share (rank 1 = highest), plus the total approved count — computed over the WHOLE set (not a truncated list), so the caller's own rank is always accurate. Ties break by id (a stable, deterministic order). Returns (0,total,nil) if the affiliate is not approved (no rank).

func (*Store) RecordPayout

func (s *Store) RecordPayout(ctx context.Context, payoutID, affiliateID string, amountCents int64, method, reference string, now int64) (Payout, error)

RecordPayout atomically RESERVES amountCents against the affiliate's pending commission (accrued − paid) and records a payout row — in one transaction. The WHERE guard `(accrued_cents − paid_cents) >= amount` makes it impossible to pay out more than is owed, even under concurrency (RowsAffected 0 → errInsufficient Pending). The commerce grant (for a credits payout) happens AFTER, outside the tx; SetPayoutTxn records the receipt. errNotFound if the affiliate is missing.

func (*Store) ReferralCountsByAffiliate

func (s *Store) ReferralCountsByAffiliate(ctx context.Context) (map[string]int, error)

ReferralCountsByAffiliate returns affiliate_id → referred-org count in ONE GROUP BY (the admin directory's per-row count, no N+1 fan-out).

func (*Store) ReferredOrgCounts

func (s *Store) ReferredOrgCounts(ctx context.Context) (total, converted int, err error)

ReferredOrgCounts returns the total number of distinct referred orgs and the number that have CONVERTED (produced at least one positive commission accrual) — the conversion numerator/denominator for the admin analytics board.

func (*Store) SetHandle

func (s *Store) SetHandle(ctx context.Context, id, handle string) (Affiliate, error)

SetHandle sets an affiliate's opt-in public leaderboard handle (empty clears it, removing the affiliate from the public board by name). Returns the refreshed row.

func (*Store) SetPayoutTxn

func (s *Store) SetPayoutTxn(ctx context.Context, payoutID, txn string) error

SetPayoutTxn records the commerce ledger transaction id after a credits payout deposit lands (best-effort receipt; the pending reservation is the authority).

func (*Store) SetRate

func (s *Store) SetRate(ctx context.Context, id string, rateBps int64) (Affiliate, error)

SetRate sets an affiliate's DIRECT (L1) commission rate in basis points. The admin handler validates the range (it must leave headroom for the L2/L3 upline so the per-event share can never exceed the margin); the store persists it.

func (*Store) SetUserReferrer

func (s *Store) SetUserReferrer(ctx context.Context, referredUser, referrerUser, code string) (bool, error)

SetUserReferrer records referredUser → referrerUser, set-once (PRIMARY KEY) and cycle-checked (mirrors the org edge). Returns created=false when the user already has a referrer (immutable — first wins) or the pair is a self/cycle no-op signalled by the error. errSelfAttribution when referred==referrer; errCycle on a loop.

func (*Store) SignupsByCode

func (s *Store) SignupsByCode(ctx context.Context, affiliateID string) (map[string]int, error)

SignupsByCode returns code → count of orgs THIS affiliate attributed with that code. Scoped by affiliate_id (the leading bound predicate).

func (*Store) Suspend

func (s *Store) Suspend(ctx context.Context, id string, now int64) (Affiliate, error)

Suspend moves an affiliate to suspended (its code stops resolving for new attribution; earned commission is unaffected). errNotFound if missing.

func (*Store) UplineOrgs

func (s *Store) UplineOrgs(ctx context.Context, sourceOrg string, depth int) ([]string, error)

UplineOrgs returns the ancestors of sourceOrg by climbing referredBy edges, up to `depth` levels — index 0 is the direct (L1) referrer, index 1 its referrer (L2), etc. The climb stops at a root, at `depth`, or on a revisited node (forest-safety).

func (*Store) UplineUsers

func (s *Store) UplineUsers(ctx context.Context, user string, depth int) ([]string, error)

UplineUsers returns a user's ancestors up to `depth` (index 0 = direct referrer).

func (*Store) VoidPayout

func (s *Store) VoidPayout(ctx context.Context, payoutID, affiliateID string, amountCents int64) error

VoidPayout reverses a RecordPayout that could not be BACKED by the treasury reserve: it deletes the payout row and restores the reserved amount to pending (paid_cents −= amount), in one transaction. It is the compensating action when the fund cannot cover a payout the pending-guard already reserved — so a blocked payout leaves the affiliate's pending intact, honestly, instead of silently burning it.

Jump to

Keyboard shortcuts

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