Skip to main content

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.

StatusMeaning
pendingRequested by the customer, awaiting provider action
confirmedProvider accepted; time is reserved
in_progressWork has started
completedWork finished (terminal)
cancelledCancelled by either party (terminal)

Transition table:

FromAllowed to
pendingconfirmed, cancelled
confirmedin_progress, cancelled
in_progresscompleted

Guards and side effects:

  • Who may do what. Advancing to confirmed, in_progress, or completed is restricted to the provider. cancelled may be requested by either the customer or the provider on the booking.
  • No-conflict on create. A booking is only born pending if its time window does not overlap an existing pending/confirmed/in_progress booking and does not fall inside a provider time block.
  • Reschedule is allowed only while pending or confirmed, 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 cancelled of a paid booking, the provider's cancellation policy is applied to compute a refund (see the booking journey).
  • Calendar sync. confirmed creates a Google Calendar event; cancelled deletes 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.

StatusMeaning
pendingCreated at checkout, not yet paid
confirmedPayment succeeded; ready to fulfil
processingSeller is preparing the order
shippedDispatched to the customer
deliveredReceived by the customer (terminal)
cancelledCancelled (terminal)

Transition table:

FromAllowed to
pendingconfirmed, cancelled
confirmedprocessing, cancelled
processingshipped
shippeddelivered

Guards and side effects:

  • pending → confirmed is 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, or delivered requires 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 shipped and delivered notifies 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.

StatusMeaning
draftBeing prepared; editable and deletable
sentDelivered to the customer
viewedOpened by the customer (auto-set on view)
acceptedCustomer accepted; an invoice is generated (terminal)
declinedCustomer declined (terminal)
expiredPassed its expiry without action (terminal)

Guards and side effects:

  • Editing and deleting are allowed only while draft. Sending is allowed only from draft.
  • viewed is automatic. When the customer opens a sent quote, it flips to viewed; the provider can see it was read.
  • Accept / decline require the caller to be the customer, and are legal only from sent or viewed. Acceptance sends a quote-accepted notification to the provider and triggers invoice creation (see Invoices). A provider-side convert action transitions any non-declined quote to accepted.
  • Expiry. A scheduled expiry job moves any sent or viewed quote past its expiry date to expired. This is the only producer of the expired status.

Invoices

An invoice is the bill. Its status set is a fixed list, paired with the system's transition logic.

StatusMeaning
draftBeing prepared; editable
sentDelivered to the customer
viewedOpened by the customer (auto-set on view)
paidFully paid (terminal)
overduePast its due date and still unpaid
cancelledCancelled before payment (terminal)

Guards and side effects:

  • Editing is allowed only while draft; sending is allowed only from draft. An invoice generated from an accepted quote is created directly in sent.
  • viewed is automatic when the customer opens a sent invoice.
  • Cancellation is allowed from any status except paid or cancelled.
  • Becoming paid happens 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):
    1. Payment-provider webhook → the invoice is marked paid, setting the amount paid to the full total.
    2. Manual → the provider records an off-platform (cash / e-transfer / cheque) payment.
    3. Partial payments accumulate against the amount paid; the invoice flips to paid only once the running total covers the invoice total.
  • overdue is applied by a scheduled overdue-check job, which moves any sent or viewed invoice whose due date has passed to overdue. 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.

StatusMeaning
draftA public post that's been created but not yet paid for
pending_reviewPaid and waiting in Fyndow's review queue
liveVisible in its feed (community members, or the public)
rejectedDeclined at review, with a reason; fixable and resubmittable
endedRun-window closed; dropped out of the feeds (terminal)

Transition table:

FromAllowed to
draftpending_review
pending_reviewlive, rejected
rejectedpending_review
liveended

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 born draft and 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 to live or rejected; 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 rejected public post returns it to pending_review. The reason for the decline is delivered to the author.
  • ended is time-driven. A scheduled expiry job moves any live Happening whose run-window has closed to ended, where it drops out of the feeds. This is the only producer of ended, 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), and ended (Happenings) have no outgoing transitions in the services.
  • Auto-viewed is a shared pattern across quotes and invoices: opening a sent record as the customer transitions it to viewed so the provider has read-receipt visibility.
  • Time-driven transitions — quote expired, invoice overdue, and Happening ended — are applied by scheduled jobs, not by any user action.
  • Payment-driven transitions — order pending → confirmed, invoice → paid, and Happening draft → 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.