Documentation
¶
Overview ¶
Package customer is the CUSTOMER management surface (/v1/admin/customers*) — the operator cockpit's core: the live fleet customer list (incl. new self-serve signups), one-customer detail, and the audited management ACTIONS (grant credit, suspend, reactivate).
It aggregates the SAME real upstreams the rest of admin reads — IAM for the org directory + user/owner/status, commerce for balance/spend/plan/ledger — and adds the two write levers an operator needs:
- GRANT CREDIT is a real commerce Deposit landing in the org's own wallet, via the ONE core credit-write path (core.ApplyGrant).
- SUSPEND / REACTIVATE flips IAM `isForbidden` on the org's users — IAM refuses a forbidden user at login AND at token issuance, so a suspended customer cannot sign in or mint a fresh token. Fully reversible.
SECURITY. Every op calls core.Admit (SuperAdmin only, fail-closed) on its first line. The write actions REPLAY THE CALLER'S OWN SuperAdmin credential to IAM, and each is recorded to cloud's tamper-evident audit trail with a redacted BEFORE/AFTER.
Index ¶
- func GrantFilter(org, result string, limit int) audit.Filter
- func Routes(z *zip.App, s *cloud.Service[core.State])
- type AccessChange
- type AccessOut
- type CustomerDetailData
- type CustomerDetailOut
- type CustomerRow
- type CustomerTxn
- type CustomerUser
- type CustomersOut
- type GrantIn
- type GrantRow
- type GrantsIn
- type GrantsOut
- type OrgIn
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GrantFilter ¶
GrantFilter is the ONE audit query that identifies a credit grant. Both the grants ledger and the consolidated money board select rows through it, so "what counts as a grant" is defined once. org filters the ACTOR's org; result is "" (all), "success" or "error".
Types ¶
type AccessChange ¶
type AccessChange struct {
// Org is the tenant acted on.
Org string `json:"org"`
// Suspended is the state applied: true for suspend, false for reactivate.
Suspended bool `json:"suspended"`
// Affected lists the usernames that were updated.
Affected []string `json:"affected"`
// Failed lists the usernames that were NOT updated. Non-empty means the org is in
// a mixed state and the action should be retried.
Failed []string `json:"failed"`
}
AccessChange is what a suspend or reactivate DID, per user. A partial failure is reported honestly here rather than masked as a clean success.
type AccessOut ¶
type AccessOut struct {
Status string `json:"status"`
Msg string `json:"msg"`
Data *AccessChange `json:"data"`
}
AccessOut is the envelope of the suspend and reactivate ops.
type CustomerDetailData ¶
type CustomerDetailData struct {
Org string `json:"org"`
Display string `json:"display"`
OwnerEmail string `json:"ownerEmail"`
Plan string `json:"plan"`
Status string `json:"status"`
Created string `json:"created"`
BalanceCents int64 `json:"balanceCents"`
SpendCents int64 `json:"spendCents"`
MRRCents int64 `json:"mrrCents"`
APIKeys int `json:"apiKeys"`
Users []CustomerUser `json:"users"`
Transactions []CustomerTxn `json:"transactions"`
}
CustomerDetailData is the GET /v1/admin/customers/:org payload.
type CustomerDetailOut ¶
type CustomerDetailOut struct {
Status string `json:"status"`
Msg string `json:"msg"`
Data *CustomerDetailData `json:"data"`
}
CustomerDetailOut is the GET /v1/admin/customers/:org envelope.
type CustomerRow ¶
type CustomerRow struct {
Org string `json:"org"`
Display string `json:"display"`
OwnerEmail string `json:"ownerEmail"`
Plan string `json:"plan"`
Status string `json:"status"` // "active" | "suspended"
Users int `json:"users"`
BalanceCents int64 `json:"balanceCents"`
SpendCents int64 `json:"spendCents"`
MRRCents int64 `json:"mrrCents"`
Created string `json:"created"`
LastActive string `json:"lastActive"`
}
CustomerRow is one row in GET /v1/admin/customers — a fleet customer at a glance.
type CustomerTxn ¶
type CustomerTxn struct {
ID string `json:"id"`
Type string `json:"type"` // "deposit" (credit) | "withdraw" (usage)
Cents int64 `json:"cents"`
Currency string `json:"currency"`
Notes string `json:"notes,omitempty"`
Time string `json:"time"`
}
CustomerTxn is one ledger row in the detail's top-up/usage history.
type CustomerUser ¶
type CustomerUser struct {
Name string `json:"name"`
Email string `json:"email"`
IsAdmin bool `json:"isAdmin"`
Forbidden bool `json:"forbidden"`
HasAPIKey bool `json:"hasApiKey"`
LastSignin string `json:"lastSignin"`
Created string `json:"created"`
}
CustomerUser is one member in the customer detail (no secrets — the AccessKey PRESENCE is surfaced as hasApiKey, never the key itself).
type CustomersOut ¶
type CustomersOut struct {
Status string `json:"status"`
Msg string `json:"msg"`
Data []CustomerRow `json:"data"`
Data2 *int `json:"data2,omitempty"`
}
CustomersOut is the GET /v1/admin/customers envelope. data2 == len(data): the list is every customer, unpaginated.
type GrantIn ¶
type GrantIn struct {
// Org is the tenant to credit. Required.
Org string `json:"org"`
// User optionally names a MEMBER to credit, by bare IAM username. Empty credits
// the org. Which of the two the money actually lands on is decided by
// account.Payer, not here: a pooled org keeps one balance whatever is named.
User string `json:"user"`
// AmountCents is the credit, in whole cents. Must be positive and within the
// per-grant cap.
AmountCents int64 `json:"amountCents"`
// Currency is the ISO code, lower-cased. Empty means usd.
Currency string `json:"currency"`
// Reason is the operator's justification, recorded on the audit row.
Reason string `json:"reason"`
// Source is the money bucket: "trial" (default) for a non-cash comp that is never
// refundable, or "prepaid" for real money. Anything unknown falls back to trial.
Source string `json:"source"`
}
GrantIn is the input of BOTH credit-grant ops. They differ only in where the target org comes from — the path on /v1/admin/customers/:org/credit, the body on /v1/admin/grants — and the URL wins where both are present, so one type serves both and there is one contract to read.
type GrantRow ¶
type GrantRow struct {
Org string `json:"org"`
AmountCents int64 `json:"amountCents"`
Currency string `json:"currency"`
Source string `json:"source"` // "trial" | "prepaid"
Reason string `json:"reason,omitempty"`
Actor string `json:"actor"` // staff email (or sub) who issued it
CreatedAt string `json:"createdAt"`
TransactionID string `json:"transactionId,omitempty"`
Result string `json:"result"` // success | error
}
GrantRow is one row in GET /v1/admin/grants.
func GrantRows ¶
func GrantRows(s *cloud.Service[core.State], ctx context.Context, f audit.Filter) ([]GrantRow, int, error)
GrantRows projects the audit trail into grant rows. It is split out of the handler so the consolidated money board (/v1/admin/money) totals the SAME grants this endpoint lists — one projection of the trail, two views. No audit store is an honest empty result, not an error: the caller decides how to report that (the board marks the source not-ok).
type GrantsIn ¶
type GrantsIn struct {
// Org filters by the ACTOR's org (the staff org that issued the grant), which is
// rarely what a reader wants — the target org is a row field, not a filter.
Org string `json:"org"`
// Result filters by outcome: "success" or "error". Empty returns both, which is
// the point of this view — a refused grant is as interesting as a granted one.
Result string `json:"result"`
// Limit caps the rows returned. Default 200.
Limit string `json:"limit"`
}
GrantsIn is the GET /v1/admin/grants filter.
type GrantsOut ¶
type GrantsOut struct {
Status string `json:"status"`
Msg string `json:"msg"`
Data []GrantRow `json:"data"`
Data2 *int `json:"data2,omitempty"`
}
GrantsOut is the GET /v1/admin/grants envelope. data2 is the store's total for the filter, which can exceed len(data) when limit truncates.