Documentation
¶
Overview ¶
Package domain is Hanzo Domains — the DOMAIN-REGISTRATION product: search a name, see its price (with Hanzo's markup), buy it billed through the customer's prepaid wallet, and have it born pointing at Hanzo's own authoritative nameservers.
This is DISTINCT from Hanzo DNS (hanzoai/dns): DNS manages records for a domain you already control; Domains ACQUIRES the domain. After a purchase, Domains hands the new zone to hanzoai/dns and points the registrar's nameservers at it — the two products compose (buy here, manage records there).
Wholesale is resold from a registrar behind the Registrar interface (name.com Core API v4 today, clients/domain/namecom). The core here is transport-free: it orchestrates availability → price → authorize → register → provision-zone → capture → record over four interfaces (Registrar, Biller, Zones, Store), so the policy is unit-testable with no HTTP/registrar/billing backend. mount.go is the thin cloud adapter that binds the real backends and exposes /v1/domain/*.
Index ¶
- Variables
- func Mount(app cloud.Router, deps cloud.Deps) error
- type Biller
- type Config
- type Markup
- type MemStore
- type Quote
- type Record
- type RegisterResult
- type Registrar
- type RenewResult
- type Service
- func (s *Service) Availability(ctx context.Context, names ...string) ([]Quote, error)
- func (s *Service) Configured() bool
- func (s *Service) Env() string
- func (s *Service) ListByOrg(org string) ([]Record, error)
- func (s *Service) Register(ctx context.Context, org, domainName string, years int, ...) (*RegisterResult, error)
- func (s *Service) Renew(ctx context.Context, org, domainName string, years int) (*RenewResult, error)
- func (s *Service) Search(ctx context.Context, keyword string, tldFilter ...string) ([]Quote, error)
- func (s *Service) Transfer(ctx context.Context, org, domainName, authCode string, years int) (*RegisterResult, error)
- type Store
- type Zones
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInsufficientFunds — the org's prepaid balance cannot cover the price. → 402. ErrInsufficientFunds = errors.New("domain: insufficient balance for this purchase") ErrUnavailable = errors.New("domain: name is not available to register") // ErrAlreadyOwned — this org already holds this domain. → 409. ErrAlreadyOwned = errors.New("domain: already registered to this org") // ErrNotOwned — a renew/transfer/manage on a domain this org does not hold. → 404/403. ErrNotOwned = errors.New("domain: not registered to this org") // ErrNotConfigured — the registrar has no credentials. → 503. ErrNotConfigured = errors.New("domain: registrar not configured") )
Sentinels the orchestration returns; the HTTP adapter maps them to status codes.
Functions ¶
func Mount ¶
Mount wires Hanzo Domains onto the unified cloud binary as /v1/domain/*:
GET /v1/domain/health registrar reachability (no auth)
GET /v1/domain/search?q=&tld= keyword search + alternate TLDs (priced)
GET /v1/domain/availability?domain=a,b exact-name availability + pricing
GET /v1/domain/domains the org's registered domains
POST /v1/domain/register {domain,years,contacts?} buy (billed)
POST /v1/domain/renew {domain,years} renew (billed)
POST /v1/domain/transfer {domain,authCode,years} transfer-in (billed)
Every mutating route is org-scoped: a validated principal's org owns the purchase and is the ledger the charge lands on. The registrar's wholesale credentials come from the platform secret store (KMS) via the operator-injected env NAMECOM_USER / NAMECOM_TOKEN — never hard-coded, exactly as clients/sites reads CF_API_TOKEN.
Types ¶
type Biller ¶
type Biller interface {
// Authorize returns ErrInsufficientFunds when the balance can't cover cents, nil
// to proceed, or another error when the balance is unknown (fail-closed).
Authorize(ctx context.Context, org string, cents int64) error
// Capture records the debit against the org's ledger. ref is the idempotency /
// attribution key (e.g. "domain:register:acme.ai").
Capture(org string, cents int64, ref string)
}
Biller is the two-phase deposit→charge a purchase bills through. Authorize refuses when the org's prepaid balance cannot cover the marked-up price (BEFORE the registrar is touched); Capture debits it AFTER the registrar succeeds. Backed by cloud's ResourceMeter (Gate → Authorize, Meter → Capture).
type Config ¶
type Config struct {
Markup Markup // wholesale → sell
Nameservers []string // Hanzo authoritative NS to point purchased domains at (fallback when Zones returns none)
Env string // registrar env label (test/prod) — attribution only
}
Config tunes pricing and the DNS handoff.
type Markup ¶
type Markup struct {
Multiplier float64 // e.g. 1.15 = +15% over wholesale; <1 is clamped to 1 (never sell below cost)
MinMarginCents int64 // floor absolute margin over cost, e.g. 300 = at least $3
}
Markup turns a wholesale registrar cost into the price a customer pays. The multiplier is applied, then a minimum absolute margin is enforced (so a cheap TLD still clears fixed per-order cost), then the result is rounded UP to whole cents.
This is the ONE place a margin is added, mirroring cloud/clients/pricing's THIRD_PARTY_MARKUP multiplier — kept as data (Config) so it is tunable without code.
type MemStore ¶
type MemStore struct {
// contains filtered or unexported fields
}
MemStore is an in-memory Store: a concurrency-safe map keyed by (org, domain). It is the default ownership store — sufficient for a single-process deployment and every test. A multi-replica deployment swaps in a SQLite/Postgres-backed Store with the SAME interface (the clients/finance per-org ledger pattern); nothing else changes.
type Quote ¶
type Quote struct {
Domain string `json:"domain"`
Available bool `json:"available"`
Premium bool `json:"premium,omitempty"`
CostCents int64 `json:"-"` // wholesale (internal — not exposed to customers)
PriceCents int64 `json:"priceCents"` // sell (first-term registration)
RenewalPriceCents int64 `json:"renewalPriceCents"` // sell (renewal)
Currency string `json:"currency"`
TLD string `json:"tld,omitempty"`
}
Quote is a priced availability result: the wholesale cost and the customer sell price (marked up), both in cents. It is what search/availability return and what a register bills against.
type Record ¶
type Record struct {
Org string `json:"org"`
Domain string `json:"domain"`
RegisteredAt int64 `json:"registeredAt"` // unix seconds
ExpiresAt string `json:"expiresAt,omitempty"`
PriceCents int64 `json:"priceCents"` // what the customer paid (sell)
CostCents int64 `json:"costCents"` // wholesale cost
Nameservers []string `json:"nameservers,omitempty"`
Order int64 `json:"order,omitempty"` // registrar order id
}
Record is the domain↔org ownership row Hanzo issues on a successful purchase.
type RegisterResult ¶
RegisterResult is a successful purchase.
type Registrar ¶
type Registrar interface {
CheckAvailability(ctx context.Context, names ...string) (*namecom.SearchResponse, error)
Search(ctx context.Context, keyword string, tldFilter ...string) (*namecom.SearchResponse, error)
CreateDomain(ctx context.Context, req namecom.CreateDomainRequest) (*namecom.CreateDomainResponse, error)
RenewDomain(ctx context.Context, domainName string, req namecom.RenewDomainRequest) (*namecom.RenewDomainResponse, error)
SetNameservers(ctx context.Context, domainName string, nameservers []string) (*namecom.Domain, error)
CreateTransfer(ctx context.Context, req namecom.TransferRequest) (*namecom.TransferResponse, error)
Hello(ctx context.Context) (*namecom.HelloResponse, error)
Configured() bool
}
Registrar is the wholesale registrar Hanzo resells. *namecom.Client satisfies it.
type RenewResult ¶
RenewResult is a successful renewal.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the transport-free orchestrator.
func NewService ¶
NewService builds the orchestrator. All four backends are required; pass a no-op Zones/Store in a context that doesn't need them.
func (*Service) Availability ¶
Availability checks exact names and returns priced quotes (availability + pricing).
func (*Service) Configured ¶
Configured reports whether the registrar has credentials.
func (*Service) Register ¶
func (s *Service) Register(ctx context.Context, org, domainName string, years int, contacts *namecom.Contacts) (*RegisterResult, error)
Register buys a domain for org: quote → guard → authorize (deposit) → provision the DNS zone → register at the registrar pointing at Hanzo nameservers → capture (charge) → record ownership. The customer is charged ONLY after the registrar confirms — a registrar failure leaves the balance untouched.
contacts is optional; when nil the registrar uses the reseller account's default WHOIS contacts. years defaults to 1.
func (*Service) Renew ¶
func (s *Service) Renew(ctx context.Context, org, domainName string, years int) (*RenewResult, error)
Renew extends a domain this org owns: guard ownership → quote renewal → authorize → renew at the registrar → capture → update the record's expiry.
func (*Service) Search ¶
Search runs a keyword search (with alternate-TLD suggestions) and returns priced quotes. tldFilter (optional) narrows the TLDs.
func (*Service) Transfer ¶
func (s *Service) Transfer(ctx context.Context, org, domainName, authCode string, years int) (*RegisterResult, error)
Transfer starts an inbound transfer of a domain the customer owns elsewhere: quote (the transfer price is the registration price) → authorize → create the transfer → capture → record. authCode is the EPP auth code from the losing registrar.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package namecom is a minimal client for the name.com Core API v4 — the wholesale registrar surface Hanzo Domains resells: check availability, price, register, renew, transfer, and set nameservers/contacts on a domain.
|
Package namecom is a minimal client for the name.com Core API v4 — the wholesale registrar surface Hanzo Domains resells: check availability, price, register, renew, transfer, and set nameservers/contacts on a domain. |