Trip lifecycle: the state machine of record
- Model the trip as an explicit state machine with guarded, event-driven transitions.
- Record the actual route for fare calculation, receipts, and disputes.
Same pattern as driver onboarding (1.2): long-running process = persisted state machine. A trip is shorter (minutes not days) but higher stakes: money (1.8) and safety hang off its transitions. Matching (1.6) hands us a trip in Assigned.
The Trip service is the system of record for what actually happened. Every transition is an immutable event row (who, when, where) appended alongside the current-state column — cancellation fees, fare calculation, support disputes, and regulatory reports (1.13) all replay this record. States and guards:
stateDiagram-v2
[*] --> Requested : rider confirms quote
Requested --> Assigned : matcher assigns driver (1.6)
Requested --> NoDriver : candidates exhausted
Assigned --> Arriving : driver en route to pickup
Arriving --> Waiting : driver within 50 m of pickup
Waiting --> InProgress : driver taps start — GPS at pickup
InProgress --> Completed : driver taps end — fare finalized
Assigned --> CancelledByRider : may incur fee after grace period
Arriving --> CancelledByRider : fee if driver near
Waiting --> CancelledByDriver : no-show after 5 min wait
Assigned --> CancelledByDriver : penalized — re-dispatch rider
Completed --> [*]
NoDriver --> [*]
CancelledByRider --> [*]
CancelledByDriver --> [*]
Two design rules keep this robust. First, transitions are commands with guards, not notifications: "start trip" is rejected unless the trip is in Waiting and the driver's GPS is near the pickup — the state machine defends itself against buggy or malicious clients. Second, during the trip, the location stream (1.4) is forked: pings from a driver on an active trip are additionally appended to that trip's route trail, which becomes the billing-grade distance record at completion. Quotes estimated with cell math; fares settle on the recorded route.
sequenceDiagram
autonumber
participant D as Driver app
participant T as Trip service
participant L as Location service
participant R as Rider app
participant P as Payments (1.8)
D->>T: start trip
T->>T: guard — state Waiting, GPS at pickup? OK → InProgress
T->>R: trip started
loop every 4 s during trip
D->>L: ping
L->>T: append to trip route trail
T-->>R: live position + updated ETA
end
D->>T: end trip
T->>T: guard OK → Completed. Fare = rates × recorded route
T->>P: capture fare {trip id, amount}
T-->>R: trip complete — receipt pending
T-->>D: earnings updated
GPS lies: tunnels, urban canyons, phones dying mid-trip. The trail recorder must tolerate gaps (interpolate for billing, flag large gaps for review) and the state machine must allow ops to repair a trip stuck in InProgress because a phone died at the destination. "What does support do when this wedges?" is a design requirement, not an afterthought.