State Machines
Every transactional record in Fyndow — a booking, an order, a quote, an invoice — moves through a defined set of statuses, and only certain transitions between them are legal. These rules are enforced in the backend services, not merely suggested by the UI. This chapter documents the real status sets and transition tables exactly as the services implement them.
Throughout, a guarded transition is one that requires more than the source status — for example, that the caller is the seller, that no time conflict exists, or that a payment has succeeded. Guards are noted alongside each machine. For the journeys these machines support, see End-to-End Journeys.
A recurring pattern: the backend maintains an explicit set of allowed-transition rules and rejects any move not in it with a message like "This … can't be changed to 'X' right now. It's currently 'Y'." This makes the legal transitions the single source of truth.
Bookings
A booking is a request for a service at a concrete time. Its lifecycle is driven
by a fixed set of allowed-transition rules plus a terminal cancelled state.
| Status | Meaning |
|---|---|
pending | Requested by the customer, awaiting provider action |
confirmed | Provider accepted; time is reserved |
in_progress | Work has started |
completed | Work finished (terminal) |
cancelled | Cancelled by either party (terminal) |
Transition table:
| From | Allowed to |
|---|---|
pending | confirmed, cancelled |
confirmed | in_progress, cancelled |
in_progress | completed |
Guards and side effects:
- Who may do what. Advancing to
confirmed,in_progress, orcompletedis restricted to the provider.cancelledmay be requested by either the customer or the provider on the booking. - No-conflict on create. A booking is only born
pendingif its time window does not overlap an existingpending/confirmed/in_progressbooking and does not fall inside a provider time block. - Reschedule is allowed only while
pendingorconfirmed, and re-runs the conflict check at the new time. It changes the time, not the status. - On
completed, the provider's client record has its job count incremented. - On
cancelledof a paid booking, the provider's cancellation policy is applied to compute a refund (see the booking journey). - Calendar sync.
confirmedcreates a Google Calendar event;cancelleddeletes it. Best-effort — never blocks the transition.
Note there is no path out of completed or cancelled; both are terminal.
Orders
An order represents stocked goods bought through the marketplace. Its statuses
come from a fixed set of allowed-transition rules plus the entry pending
state and the terminal cancelled state.
| Status | Meaning |
|---|---|
pending | Created at checkout, not yet paid |
confirmed | Payment succeeded; ready to fulfil |
processing | Seller is preparing the order |
shipped | Dispatched to the customer |
delivered | Received by the customer (terminal) |
cancelled | Cancelled (terminal) |
Transition table:
| From | Allowed to |
|---|---|
pending | confirmed, cancelled |
confirmed | processing, cancelled |
processing | shipped |
shipped | delivered |
Guards and side effects:
pending → confirmedis payment-driven. Only the payment-confirmation path (invoked from the payment provider's webhook) performs this transition, linking the payment to the order. It is not a status a user sets directly.- Seller-only advancement. Moving an order to
processing,shipped, ordeliveredrequires the caller to own the order's business. - Cancellation is special. The order-status update endpoint accepts
cancelled, but the customer-facing cancel path actually deletes an unpaid (pending, no payment) order and its items outright. A paid order cannot be cancelled through this flow — that is a return, which is out of scope. - Inventory is decremented at checkout (when the order is created), not at payment.
- Notifications. Reaching
shippedanddeliverednotifies the customer; the seller is notified earlier when the order is received and when it is paid.
A multi-business cart produces several independent orders, each with its own status — see Order → Fulfillment.
Quotes
A quote is a provider's priced proposal. Its status set is a fixed list, paired with the system's transition logic.
| Status | Meaning |
|---|---|
draft | Being prepared; editable and deletable |
sent | Delivered to the customer |
viewed | Opened by the customer (auto-set on view) |
accepted | Customer accepted; an invoice is generated (terminal) |
declined | Customer declined (terminal) |
expired | Passed its expiry without action (terminal) |
Guards and side effects:
- Editing and deleting are allowed only while
draft. Sending is allowed only fromdraft. viewedis automatic. When the customer opens asentquote, it flips toviewed; the provider can see it was read.- Accept / decline require the caller to be the customer, and are legal
only from
sentorviewed. Acceptance sends a quote-accepted notification to the provider and triggers invoice creation (see Invoices). A provider-side convert action transitions any non-declinedquote toaccepted. - Expiry. A scheduled expiry job moves any
sentorviewedquote past its expiry date toexpired. This is the only producer of theexpiredstatus.
Invoices
An invoice is the bill. Its status set is a fixed list, paired with the system's transition logic.
| Status | Meaning |
|---|---|
draft | Being prepared; editable |
sent | Delivered to the customer |
viewed | Opened by the customer (auto-set on view) |
paid | Fully paid (terminal) |
overdue | Past its due date and still unpaid |
cancelled | Cancelled before payment (terminal) |
Guards and side effects:
- Editing is allowed only while
draft; sending is allowed only fromdraft. An invoice generated from an accepted quote is created directly insent. viewedis automatic when the customer opens asentinvoice.- Cancellation is allowed from any status except
paidorcancelled. - Becoming
paidhappens through three doors, all converging on the same side effects (inventory deduction for material line items, best-effort accounting export, and an invoice-paid notification to the provider):- Payment-provider webhook → the invoice is marked paid, setting the amount paid to the full total.
- Manual → the provider records an off-platform (cash / e-transfer / cheque) payment.
- Partial payments accumulate against the amount paid; the invoice flips
to
paidonly once the running total covers the invoice total.
overdueis applied by a scheduled overdue-check job, which moves anysentorviewedinvoice whose due date has passed tooverdue. An overdue invoice can still be paid.
Happenings
A Happening is a time-boxed post — an event, a promotion, or an ad. Unlike the machines above, where it starts depends on its reach: a post to a community goes live the moment it's created, while a post to the public feed is paid and reviewed before anyone sees it. The narrative of this feature lives in Happenings; here is the machine that backs it.
| Status | Meaning |
|---|---|
draft | A public post that's been created but not yet paid for |
pending_review | Paid and waiting in Fyndow's review queue |
live | Visible in its feed (community members, or the public) |
rejected | Declined at review, with a reason; fixable and resubmittable |
ended | Run-window closed; dropped out of the feeds (terminal) |
Transition table:
| From | Allowed to |
|---|---|
draft | pending_review |
pending_review | live, rejected |
rejected | pending_review |
live | ended |
Guards and side effects:
- Reach decides the entry state. A community Happening is born
live— free, immediate, and seen only by that community's approved members. A public Happening is borndraftand reaches no one until it is paid for and approved. - Payment drives
draft → pending_review. When the per-post charge for a public Happening settles, the post moves itself into the review queue and the author is told it's awaiting review. This is not a state a user sets by hand. - Approve / decline are Fyndow's call, and only from
pending_review. A reviewer can move a queued post toliveorrejected; attempting either on a post that isn't awaiting review is refused. Approval notifies the author and, when the post is attached to a business, fans out to that business's followers. - Rejection is "fix and resubmit", not "money back". A declined post keeps
its paid status, so the author can edit it and send it back through review
without paying again — editing a
rejectedpublic post returns it topending_review. The reason for the decline is delivered to the author. endedis time-driven. A scheduled expiry job moves anyliveHappening whose run-window has closed toended, where it drops out of the feeds. This is the only producer ofended, and it has no outgoing transitions.- Starting-soon reminders. A separate job nudges followers of an upcoming public event shortly before it begins — see Notifications & Digests.
The economics behind public reach are covered in Paying for Reach.
Cross-cutting notes
- Terminal states.
completed/cancelled(bookings),delivered/cancelled(orders),accepted/declined/expired(quotes),paid/cancelled(invoices), andended(Happenings) have no outgoing transitions in the services. - Auto-
viewedis a shared pattern across quotes and invoices: opening asentrecord as the customer transitions it toviewedso the provider has read-receipt visibility. - Time-driven transitions — quote
expired, invoiceoverdue, and Happeningended— are applied by scheduled jobs, not by any user action. - Payment-driven transitions — order
pending → confirmed, invoice→ paid, and Happeningdraft → pending_review— are owned by the payments layer and its payment-provider webhook, not set directly by clients.
For how status changes turn into user-facing messages, continue to Notifications & Digests.