Skip to main content

Order Commands

Ledgerline supports two parallel surfaces for starting an order:

  1. Rich /order flow — the modern, component-driven UX that guides the customer through seven steps with select menus, modals, buttons, and rich embeds. This is the recommended flow.
  2. Legacy /order-start/order-item → … → /order-submit — the original text-field slash commands, kept for back-compat.

Both flows share the same underlying DiscordOrderFlowService and OrderSessionService, so the state machine, discount resolution, admin approval seam, and inventory reservation behave identically.

Sources: src/data/order/order.rich.ts, src/data/order/order.command.ts, src/data/discord-commands/discord-command-router.ts

/order

Starts (or resumes) an order session and renders the product-selection step.

OptionTypeRequiredDescription
productIdstringNoSkips the product select and opens the quantity modal for this product.

Flow shape

Each step is ephemeral — only the invoking customer sees each message.

/order
→ (1) product select custom_id: order:product-select
→ (2) quantity modal custom_id: order:qty-modal:<productId>
→ (3) address step (string select of saved addresses + "Enter new address" button)
custom_ids: order:use-existing, order:address:new
→ (3a) new-address modal custom_id: order:address:modal
→ (3b) corrected embed custom_ids: order:address:accept-corrected / :keep-original
→ (4) shipping-type select custom_id: order:shipping-type-select
→ (5) payment-method select custom_id: order:payment-method-select
→ (6) review summary + instruction embed (Confirm & submit / Cancel)
custom_ids: order:confirm, order:cancel
→ (7) "Order submitted" ephemeral embed

Side effects on confirm

When the customer clicks Confirm & submit:

  1. discountService.resolveBestForCustomer picks the best eligible discount (assignment- or global-scoped).
  2. orderService.create materializes the order with status=PENDING_PAYMENT. Product role-gating is enforced at the service boundary.
  3. paymentService.createPending creates the payment record, then paymentService.submitForApproval transitions it to awaiting_admin_approval.
  4. Inventory is reserved for each line item (idempotent per order + product).
  5. An order.created event is emitted. The notifications consumer picks it up and posts an approve/reject embed to the admin channel and DMs the customer with payment instructions.
  6. The session is marked SUBMITTED. Double-clicking the confirm button is idempotent — the same order is returned; no duplicate order or payment is created.

Catalog entry point

Clicking the Order <product> button on a /catalog embed dispatches catalog:order:<productId>, which routes to the same rich flow with the product prefilled. The customer goes straight from catalog → quantity modal.

/orders

Lists the calling user's order history. Ephemeral — only the invoking customer can see it.

No slash options.

Up to 10 orders are shown per page, sorted most-recent-first. Each row shows order number, status, total, and date. A View details button expands a full itemized embed (line items, subtotal, discount, total, shipping address, payment method, shipping status).

Previous / Next pagination buttons appear when the customer has more than 10 orders.

Component IDDescription
orders:detail:<id>:<p>View full detail for order <id>, return to page <p>
orders:page:<n>Navigate to page <n> (0-indexed)

Source: src/data/order/orders-list.command.ts


/refund

Admin-only. Refunds an order, reverts inventory, and emits order.refunded.

OptionTypeRequiredDescription
orderstringYesThe order ID to refund.
reasonstringNoReason for the refund (default: "Admin-initiated refund").

Legacy commands

These remain wired for back-compat but are not surfaced in /help by default.

CommandNotes
/order-startBegin/resume session.
/order-itemAdd an item with explicit product/qty/price fields.
/order-shippingCapture address via slash-command fields (no validator).
/order-paymentSet payment method.
/order-submitFinalize.
/payment-confirmAdmin-only confirm by order id.

Custom-id reference

All component IDs use the {prefix}:{action}:{param} format (max 100 chars). Ordering IDs live in src/data/discord-commands/discord-custom-id.constants.ts.

Custom IDHandler
order:product-selecthandleOrderProductChosen
order:qty-modal:<productId>handleOrderQuantityModalSubmit
order:use-existinghandleOrderUseExistingAddress
order:address:newhandleOrderNewAddressButton
order:address:modalhandleOrderAddressModalSubmit
order:address:accept-correctedhandleOrderAcceptCorrectedAddress
order:address:keep-originalhandleOrderKeepOriginalAddress
order:shipping-type-selecthandleOrderShippingTypeSelected
order:payment-method-selecthandleOrderPaymentMethodSelected
order:confirmhandleOrderConfirm
order:cancelhandleOrderCancel
payment:approve:<paymentId>handlePaymentApprove
payment:reject:<paymentId>handlePaymentRejectButton
payment:reject-modal:<paymentId>handlePaymentRejectModal