Stateless preview — resolves the route + fee template + amount math without writing an audit row and without enforcing usage-window limits. Use this for UI previews and any caller that has no intent to trade.
When amount is null returns the fee template + go/no-go (route
status); no concrete amounts are computed.
Usage-based matchers (usage30dUsd, etc.) only fire when the caller
pre-fetches usage and passes it via input.usage. Estimate does not
fetch on its own — that's quote()'s job.
Look up a single audit row by external id, scoped to the caller's customer. The audit table is append-only, so callers get a frozen snapshot of what the resolver decided at quote time — the on-disk row is the source of truth for replay, receipts, and downstream reconciliation.
Returns null when the row doesn't exist or belongs to a different
customer (treated as not-found per the service-pattern convention —
no QUOTE_NOT_FOUND error code surfaced).
Public read-only entry point for callers (admin dashboards, internal
reports) that need rolling-window usage outside the quote() flow.
Same query and semantics as the internal pre-fetch — counts every
product_quote row stamped with accepted_at, scoped to the entity.
Transactional quote — same math as estimate(), plus per-product and
aggregate-domain limit enforcement, plus a write to product_quote for
end-to-end audit. Returns the inserted row's external id as quoteId,
which downstream consumers (deposit/withdrawal/swap) persist on their
own quote rows via db.helpers.productQuoteInternalId({ quoteId }) —
a scalar subquery that resolves the externalId string to the int FK
at insert time. Service interfaces stay string-only; ints are
confined to the DB layer.
Usage is fetched internally from product_quote (rows where
acceptedAt IS NOT NULL count toward the rolling windows). Pass
bypassLimits: true to opt out — for documented internal admin
flows that have authorization to skip enforcement.
Auditing scope: a row is written iff a candidate route was picked —
i.e. OK, LIMIT_EXCEEDED, or INACTIVE (winning activation rule's
value is INACTIVE). Pre-pick failures (PRODUCT_INACTIVE,
NO_ELIGIBLE_ROUTE, ENTITY_*, PRODUCT_BLOCKED) do not write.
Re-resolve a pending product_quote against new inputs and patch the
row in place — single audit row per logical transaction, mutated as
the deposit lifecycle learns more about it. Designed for floating
quotes (created with amount: null for a barcode-style placeholder)
that need to be settled later with the real amount + recomputed fees
Only the genuinely updatable inputs are exposed on the signature —
amount, regionCode, bankId, speed, bypassLimits. The static
dimensions (productName, entity id, source/target currency,
cashDepositRetailerId, decimalPlaces, idempotencyKey) are read
back from the existing row and merged in. Callers can't change which
customer / entity / product the quote belongs to, by construction.
Re-runs the full resolver pipeline, then UPDATEs the row's fee
snapshot, route version FKs, outcome, violation, and pinned criteria
columns. bypassLimits: true skips usage prefetch + enforcement
(e.g. AuthCommit settling the final amount after limits already
passed at Auth); otherwise a violation lands as outcome = LIMIT_EXCEEDED + a snapshot and the method returns LIMIT_EXCEEDED.
Once the row is accepted (accepted_at IS NOT NULL) the DB-level
trigger rejects every UPDATE — at that point the row is frozen so
rolling-window usage stays stable. Callers see QUOTE_ALREADY_ACCEPTED
surfaced from the pre-check below; the trigger backs that guard up.
Mark a
product_quoterow as accepted. Stampsaccepted_at = now()and surfaces typed errors when the quote isn't acceptable. Once the stamp lands, the row counts toward rolling-window usage (the read path filtersWHERE accepted_at IS NOT NULL).Single round-trip on the happy path — a guarded UPDATE flips only acceptable rows. On miss we re-SELECT to disambiguate the failure (NOT_FOUND vs ALREADY_ACCEPTED vs EXPIRED vs NOT_OK).
Idempotency: re-calling on an already-accepted quote returns
QUOTE_ALREADY_ACCEPTED. Callers that legitimately replay (e.g. Temporal activity retries) should map that code to success.