The buyer’s flow
1
Start Purchase
The deployed AIO panel has one button. Pressing it creates a draft purchase session and
shows the category picker.
2
Choose a category
A dropdown of the categories configured in
/aio_manage. Categories are the top level of
the catalog — think “Games” or “Services” — and can be disabled without deleting them.3
Choose a product
A dropdown of the products under the chosen category.
4
Choose a duration/variant
A dropdown of the durations (or plan variants) under the chosen product, each showing its
own base price — e.g. “1 Month — 89.99” as separate entries under the
same product.
5
Confirm
A summary shows Category, Product, Duration, Base Price, any applied Discount, and the
Final Price, with Confirm & Create Ticket, Start Over, and Cancel. Confirming is
what actually creates the ticket.
DynamicItems whose state
lives in their own custom_id, not in memory, so a mid-purchase deploy is invisible to the
buyer.
Pricing
Catalog structure is generic on purpose — Category → Product → Duration/Variant — so it works for anything sold this way, not just game keys. Each duration/variant carries its own base price in the guild’s currency, stored as integer minor units (cents) to avoid floating-point rounding on money.Discounts
/discount create scopes a discount to a single Product, a whole Category, or
Global (everything), as either a Percentage or a Fixed minor-unit amount, with an
optional minimum subtotal to qualify and a maximum discount cap. A discount also has an
optional schedule (/discount schedule) and can be toggled off without deleting it.
When more than one discount could apply to the same purchase, resolution is deterministic:
- Every enabled, currently-valid discount is checked for applicability (scope match, schedule window, minimum subtotal).
- Among the applicable ones, whichever produces the lowest legitimate final price wins.
- Ties are broken by scope priority: Product beats Category beats Global.
Prices are never trusted from the client
The confirmation screen’s price is a snapshot for display, but it is never what actually gets charged conceptually or recorded. At the moment a buyer presses Confirm, the price is recalculated from live catalog and discount data — not read back from the button that was clicked or a value cached earlier in the session. A discount that changed, or a price edited mid-purchase, is reflected correctly rather than letting a stale confirmation screen lock in an old number.Ticket creation
Confirming goes through the sameprovision_ticket() engine a Standard panel button uses, so
everything downstream — claiming, permissions, transcripts, auto-close, payments — behaves
identically to a Standard ticket. Duplicate-ticket protection (a buyer double-clicking Confirm,
or two requests racing) is enforced by a database constraint rather than in-process state, so it
holds even across multiple bot instances.
/aiostaffroles sets which staff roles are automatically added to every new AIO ticket, so the
right people can see it without a manual invite.
Fulfillment — delivering the key
Stock for a duration/variant is added with/keys add, which opens a modal for pasting values
one per line. Every value is Fernet-encrypted at rest and never written to logs, audit records,
or analytics — audit entries reference the reservation/delivery row’s id, not the key itself.
;confirm, run inside the ticket, reserves and DMs the next available key for that ticket’s
product/variant:
- Concurrency-safe — two staff running
;confirmon the same ticket at the same moment can never receive or reserve the same key. - DM failure doesn’t lose stock — if the DM fails, staff get Retry, Secure Reveal, Mark Manual, and Release Reservation controls instead of the key silently vanishing from inventory.
/analytics reports AIO purchase-request value alongside confirmed-fulfillment value, kept
deliberately separate from “revenue” — neither number is payment-verified, since Ticket Bot
never processes payments itself (see Payments).