SRS — Booking
Module owner: Domain 5 — Booking. A Booking is the binding agreement created the moment a customer accepts a quote: it fixes the vendor, service scope, price and event date, collects the deposit through Payments, and — once the event has happened — unlocks the Review.
Related: Quotations · Events & Planner · Payments · Reviews
Purpose
Convert an accepted quote into a tracked commitment with clear money rules (deposit, cancellation windows, refunds) so both sides know exactly what is owed, when, and what happens if plans change.
Actors
| Actor |
Role |
| Customer |
Pays deposit, requests cancellation/refund, confirms completion |
| Vendor |
Confirms booking, sets deposit % (within platform bounds), requests cancellation (exceptional), confirms delivery |
| Admin (finance/ops) |
Approves refunds, resolves disputes, force-transitions in edge cases (logged) |
| System |
Creates booking on quote acceptance, enforces payment deadline, triggers commission capture + vendor settlement on confirmation, runs the completion job |
User stories
- As a customer, I want my accepted quote to turn into a booking with a clear deposit amount and deadline, so that I know exactly what to pay to lock the date.
- As a customer, I want to cancel within the allowed window and get my deposit back automatically, so that changing plans isn't a fight.
- As a vendor, I want the booking confirmed only after the deposit is paid, so that I never block a date for free.
- As a vendor, I want completed bookings to prompt the customer for a review, so that my profile grows with every job.
- As an admin, I want every out-of-policy refund to require my approval, so that money movements are controlled.
Functional requirements
Creation & confirmation — MVP
| ID |
Requirement |
Phase |
| SRS-BKG-001 |
Accepting a quote creates a Booking atomically (same transaction as SRS-RFQ-031) in state pending, copying an immutable snapshot: vendor, event, category, quote version, line items, total_amount (KWD 3 dp), service_date (= event date unless the quote specified another delivery date). |
MVP |
| SRS-BKG-002 |
Deposit rules: deposit percentage is set by the vendor per listing/quote within platform bounds 10–50% (default 20%). deposit_amount = round(total_amount × deposit_pct, 3) KWD, minimum 5.000 KWD. The deposit is collected on-platform via Payments, and the platform's commission is captured from it at confirmation (SRS-BKG-007). The remainder (balance_amount) is settled directly between customer and vendor off-platform in MVP (recorded as a note on the booking); on-platform balance collection arrives with automated escrow (Vision). |
MVP |
| SRS-BKG-003 |
On creation the customer gets a payment call-to-action with a deposit deadline of 48 hours (configurable per platform). Unpaid past deadline → booking auto-cancels (cancelled, reason deposit_timeout), the quote acceptance is voided, and the RFQ reopens to quoted if other live quotes remain. |
MVP |
| SRS-BKG-004 |
Successful deposit payment (webhook-confirmed, see SRS-PAY-030) transitions the booking to awaiting vendor confirmation; the vendor must confirm within 24 hours. Vendor confirmation → confirmed; both parties notified, event dashboard updated. |
MVP |
| SRS-BKG-005 |
If the vendor fails to confirm within 24 h or declines: booking → cancelled (reason vendor_no_confirm / vendor_declined), deposit auto-refunded in full without admin approval (system-initiated refund, see SRS-PAY-041), and the vendor's response-rate metric is penalized. |
MVP |
| SRS-BKG-006 |
Instant-booking listings (vendor opt-in flag) skip the manual confirmation step: deposit paid → confirmed immediately. |
MVP |
| SRS-BKG-007 |
Commission & settlement on confirmation: when the booking becomes confirmed, the platform captures its commission (5% of total_amount, configurable — see Confirmed Decisions) from the already-paid deposit and schedules the vendor's share (deposit_amount − commission) for settlement via Payments (SRS-PAY-053/054). The deposit always exceeds the commission, so the customer pays nothing extra; the remaining balance is settled directly with the vendor at the event (SRS-BKG-002). Settlement of a disputed booking stays frozen until Operations resolves it. |
MVP |
Cancellation & refunds — MVP
| ID |
Requirement |
Phase |
| SRS-BKG-010 |
Platform cancellation policy (applied to the deposit; customer-initiated, relative to service_date): ≥ 30 days → 100% refund; 14–29 days → 50% refund; < 14 days → no refund. Vendors may only offer more generous terms (per listing setting); never stricter. The applicable policy is shown before deposit payment and frozen into the booking snapshot. |
MVP |
| SRS-BKG-011 |
Customer cancellation inside a 100%-refund window executes automatically: booking → cancelled, refund auto-issued. Any cancellation with a partial/zero refund, or any dispute ("vendor didn't show"), moves the booking to refund_requested and enters the admin refund-approval queue (SRS-PAY-040). |
MVP |
| SRS-BKG-012 |
Vendor-initiated cancellation of a confirmed booking always refunds the deposit 100%, notifies the customer with rebooking suggestions (same category, same date availability), flags the vendor account (3 vendor cancellations in 90 days → automatic admin review of the account). |
MVP |
| SRS-BKG-013 |
refund_requested resolves to cancelled (refund approved, full or partial per admin decision) or back to confirmed (refund denied). Every resolution requires a reason and is written to the audit log. |
MVP |
Completion — MVP
| ID |
Requirement |
Phase |
| SRS-BKG-015 |
Completion trigger: nightly job finds confirmed bookings with service_date passed, then asks the customer "Did {vendor} deliver?" (push + email). Customer confirms → completed. No customer response within 7 days and no dispute opened → auto-completed. Customer answers "there was a problem" → guided flow: open dispute (→ refund_requested) or contact vendor. |
MVP |
| SRS-BKG-016 |
A booking still pending (deposit unpaid) when service_date passes auto-cancels. A booking awaiting vendor confirmation when the date passes auto-cancels with full refund. |
MVP |
| SRS-BKG-017 |
On completed: the review window opens for the customer (SRS-REV-001), vendor completed-jobs counter increments, and the event dashboard reflects it. |
MVP |
Vision
| ID |
Requirement |
Phase |
| SRS-BKG-020 |
E-contracts: platform-generated bilingual service agreement attached to the booking, e-signed by both parties. |
Vision |
| SRS-BKG-021 |
Escrow: full amount held by platform, released to vendor on completion. |
Vision |
| SRS-BKG-022 |
Milestone payments: schedule of partial payments tied to timeline milestones (e.g. 20% book, 30% one week out, 50% after delivery). |
Vision |
Business rules
- One booking per accepted quote; one accepted quote per RFQ. A customer may hold multiple bookings on one event (different categories/vendors).
- The booking snapshot is immutable — later edits to the vendor listing, quote or event never change
total_amount, line items or the frozen cancellation policy. Date changes require a mutual-consent reschedule flow: customer proposes, vendor accepts, service_date updates with full history retained.
- Money only moves through Payments; the booking module never talks to the PSP directly.
- All amounts KWD, 3 decimal places, half-up rounding at the final step only.
- Refund deadlines compute against Asia/Kuwait midnight of
service_date.
- Admin force-transitions are restricted to super admin + operations roles, always require a reason, and always hit the audit log.
- Platform commission (5% of
total_amount, configurable) is captured from the deposit at confirmation and is never charged on top of it; the vendor's settled share is deposit_amount − commission, executed by Payments (SRS-PAY-053/054). Automated escrow and milestone releases remain Vision (SRS-BKG-021/022).
Validation rules
| Field |
Rule |
deposit_pct |
integer 10–50 (platform bounds, admin-configurable) |
deposit_amount |
decimal(12,3) KWD, ≥ 5.000, = round(total × pct, 3) |
service_date |
≥ today at creation; reschedule target ≥ today + 1 day |
| Cancellation reason |
required on any cancellation; enum + optional free text ≤ 500 chars |
| Dispute description |
required to open a dispute, 20–2,000 chars, up to 5 photo/PDF attachments (≤ 10 MB each) |
| Vendor confirmation |
only from the vendor account owning the quote; only in the awaiting-confirmation window |
Permissions matrix
| Action |
Customer (owner) |
Vendor (party) |
Other users |
Admin ops |
Admin finance |
Admin super |
| View booking |
✔ |
✔ |
✘ |
✔ |
✔ |
✔ |
| Pay deposit |
✔ |
✘ |
✘ |
✘ |
✘ |
✘ |
| Confirm / decline booking |
✘ |
✔ |
✘ |
✘ |
✘ |
✘ |
| Cancel (policy applied) |
✔ |
✔ (full-refund rule) |
✘ |
✘ |
✘ |
✘ |
| Open dispute |
✔ |
✘ |
✘ |
✘ |
✘ |
✘ |
| Approve/deny refund |
✘ |
✘ |
✘ |
✘ |
✔ |
✔ |
| Force state transition |
✘ |
✘ |
✘ |
✔ (logged) |
✘ |
✔ (logged) |
| Propose / accept reschedule |
✔ propose |
✔ accept |
✘ |
✘ |
✘ |
✘ |
State machine
stateDiagram-v2
[*] --> Pending : quote accepted (booking created)
Pending --> Pending : deposit paid → awaiting vendor confirmation (substate)
Pending --> Confirmed : vendor confirms (or instant booking) after deposit paid
Pending --> Cancelled : deposit timeout 48h / vendor declines or no confirm 24h (full refund) / customer aborts
Confirmed --> Completed : service date passed + customer confirms or 7-day auto
Confirmed --> RefundRequested : customer cancels (partial/zero window) or opens dispute
Confirmed --> Cancelled : customer cancels in 100% window (auto refund) / vendor cancels (full refund)
RefundRequested --> Cancelled : admin approves refund (full or partial)
RefundRequested --> Confirmed : admin denies refund
Completed --> [*] : review window opens
Cancelled --> [*]
Substates of Pending tracked via payment_status (unpaid → deposit_paid) and vendor_confirmation (awaiting → confirmed/declined) — kept as columns, not extra top-level states, so client status chips stay simple.
Edge cases
- Deposit webhook arrives after the 48 h auto-cancel already ran: payment is auto-refunded in full (system refund, no approval), customer notified with "booking expired — you have not been charged" messaging.
- Vendor confirms at the same moment the 24 h no-confirm job fires: transitions are guarded by row lock + state precondition; whichever commits first wins, the loser is a no-op.
- Customer opens a dispute on a booking already auto-completed (e.g. day 8): allowed up to 14 days after
service_date; booking stays completed but a dispute case is created for admin (refund can still be issued from the payment record without reversing the state).
- Reschedule proposed but vendor never responds: proposal expires after 72 h; original date stands; customer notified with cancel-policy reminder.
- Vendor account suspended while bookings are
confirmed: bookings remain valid; admin ops decides per case (fulfil or cancel-with-full-refund); new bookings blocked.
- Partial refund math: 50% window refunds round half-up to 3 dp (e.g. deposit 33.333 → refund 16.667 KWD).
Empty / loading / error states
| Surface |
Empty |
Loading |
Error |
| Customer bookings list |
"No bookings yet — accepted quotes appear here" + link to open RFQs |
Skeleton rows |
Retry banner with cached data |
| Vendor booking inbox |
"New bookings land here the moment a customer accepts your quote" |
Skeleton rows |
Retry banner |
| Deposit payment return screen |
— |
"Confirming your payment…" poll state (webhook may lag) up to 60 s |
If unconfirmed after 60 s: "We're verifying — you'll get a notification. You have not been double-charged." |
| Cancellation flow |
— |
Policy computation inline |
Server recomputes on submit; mismatch → refreshed policy shown for re-confirm |
Acceptance criteria
Data entities touched
bookings, booking_line_items (snapshot), booking_status_history, booking_cancellation_policies (frozen snapshot), booking_reschedules, disputes, plus reads/writes on quotes, quotation_requests, events, payments, refunds, reviews (window open), vendor_metrics (response rate, completed jobs, cancellation flags); confirmation emits domain events that create commissions/vendor_settlements in Payments.
GET /api/v1/bookings # role-scoped list (customer or vendor)
GET /api/v1/bookings/{id}
POST /api/v1/bookings/{id}/deposit-intent # hands off to Payments
POST /api/v1/bookings/{id}/confirm # vendor
POST /api/v1/bookings/{id}/decline # vendor
POST /api/v1/bookings/{id}/cancel # either party, policy applied
POST /api/v1/bookings/{id}/complete # customer confirms delivery
POST /api/v1/bookings/{id}/dispute
POST /api/v1/bookings/{id}/reschedule # propose
POST /api/v1/bookings/{id}/reschedule/{proposalId}/accept
GET /api/v1/bookings/{id}/policy # frozen cancellation policy + live refund preview
# admin
GET /api/v1/admin/bookings
POST /api/v1/admin/bookings/{id}/transition # force, reason required