/ Docs / Booking & Delivery Flow v1 Β· 2026
Core Flow Β· Booking

Booking & Delivery Flow

From "I want to send a parcel" to "delivered, money settled" β€” the entry points, modes, state machine, OTP/POD custody chain, relay journeys, refunds, and the no-smartphone path.

1. Overview & the two entry points

A RideChain booking can begin in two completely different places, and the most important thing to understand up front is this: you do not need to walk into a RideChain Point to book. The overwhelming majority of bookings are self-serve from a phone. A "Point" is a physical handover venue (and a fallback for people without a smartphone), not a booking counter you must visit.

πŸ“±

Entry A β€” Digital self-serve

The booker creates the order on their own device: the Book app (Flutter), the Next.js PWA on the web, the WhatsApp bot, or an IVR / missed-call callback. No travel needed β€” quote, pay and track all happen on the phone.

πŸͺ

Entry B β€” Walk-in at a Point

A customer with no smartphone (or who simply wants help) walks into a RideChain Point β€” a kirana, CSC, panchayat office or chai stall β€” and the operator books on the customer's behalf using the assisted-booking screen. The Point is also where a parcel is physically dropped off or collected.

Mental model: the digital entry decides where the parcel travels (pickup/drop modes), while a Point is just one possible endpoint of that journey. Booking digitally and choosing POINT drop is common; walking into a Point to book is the exception, used mainly for the no-smartphone segment.
flowchart TB
  subgraph Digital["Entry A Β· Digital self-serve (phone, no travel)"]
    BA["πŸ“± Book app (Flutter)"]
    WEB["πŸ’» Next.js PWA (web)"]
    WAB["πŸ’¬ WhatsApp bot"]
    IVR["πŸ“ž IVR / missed-call"]
  end
  subgraph Assisted["Entry B Β· Walk-in at a RideChain Point"]
    OP["πŸ§‘β€πŸ’Ό Kirana / CSC operator
books on customer's behalf"] end BA & WEB & WAB & IVR --> API["Go booking API"] OP --> API API --> SM["Booking state machine
(DRAFT β†’ QUOTED β†’ … β†’ SETTLED)"] classDef d fill:#e8f0fb,stroke:#1f5fae,color:#143d6e; classDef a fill:#fff3e0,stroke:#f4920b,color:#8a5200; class BA,WEB,WAB,IVR d; class OP a;
Both entry points funnel into the same Go booking API and the same state machine. The surface (app / web / WhatsApp / IVR / operator) differs; the lifecycle does not.

2. Pickup & drop modes (DOOR vs POINT)

At booking time the booker chooses a pickup mode and a drop mode, each independently DOOR or POINT. These four combinations are the single biggest lever on price and speed: a door visit is dedicated partner time (premium), while a Point lets the parcel ride a shared, batched milk-run (cheapest).

Pickup β†’ DropWhat it meansCostBest speed tier
DOOR β†’ DOORPartner collects from the sender's house and hands to the receiver's house. Two dedicated visits, no Point staging.Premium (highest)Urgent / on-demand
DOOR β†’ POINTPartner picks up at the sender's door; parcel rests at the destination Point for the receiver to collect.MidExpress
POINT β†’ DOORSender drops at a nearby Point; a partner delivers the last leg to the receiver's door.MidExpress
POINT β†’ POINTSender drops at origin Point; parcel rides a bundled milk-run to the destination Point for pickup. No door visit at all.Cheapest (bundled)Bundled milk-run (slowest, cheapest)
Why POINT β†’ POINT is cheapest: the parcel is consolidated with others at the Block Hub and moves on a shared route. No partner spends dedicated minutes finding a house. DOOR pickup carries a premium because it is dedicated, single-parcel partner time.
β‚Ή25–45
Bundled milk-run, 15 km interior
β‚Ή70–90
RideChain urgent, 15 km
β‚Ή150–250
Porter (comparison)
β‚Ή60–120
Bluedart 3–7 d, often fails

Speed tiers map onto modes: Urgent / on-demand wants a dedicated partner (favours DOOR ends); Express mixes a dedicated leg with a Point; Bundled milk-run requires POINT β†’ POINT so the parcel can be batched. See Commission & Pricing for the fare formula and surge.

3. The booking creation flow

Creation walks the booker through five inputs, produces a live quote, takes payment into escrow, then matches a partner. Pricing, payment and matching are separate idempotent steps β€” none assumes a single happy reply, and payment is only confirmed by the PG webhook, never by the client's own "I paid" message.

  1. Choose pickup & drop modes β€” DOOR/POINT for each end, with addresses or a Point picked from the map.
  2. Describe the package β€” category, size bucket (S/M/L/XL), weight bucket, and flags: fragile, perishable, urgent, oversized.
  3. Engine recommends a vehicle β€” from size/weight/flags/geo; the booker may override (e.g. force a Bolero for a bulky item).
  4. Live price quote β€” pricing returns fare + breakdown for the chosen tier; re-quoted if any input changes.
  5. Pay into escrow β€” a gateway order is created (Razorpay primary β†’ Cashfree fallback); funds are held, the platform holds no money.
  6. Match & assign β€” bundling-first, then on-demand cascade; the booker receives the assigned partner + ETA and live tracking begins.
sequenceDiagram
  autonumber
  participant U as "Booker (app / web / WA / operator)"
  participant GW as "Go booking API"
  participant PR as Pricing
  participant PAY as "Payments + escrow"
  participant PG as "Gateway (RZP β†’ CF)"
  participant MA as Matching
  participant PT as "Partner app"
  U->>GW: "Choose pickup/drop modes + addresses"
  U->>GW: "Package: category, size S/M/L/XL, weight, flags"
  GW->>PR: "Recommend vehicle (size, weight, flags, geo)"
  PR-->>U: "Suggested vehicle (user may override)"
  U->>GW: "Confirm vehicle + speed tier"
  GW->>PR: "quote(modes, vehicle, tier, geo)"
  PR-->>U: "Live price + breakdown"
  U->>GW: "Confirm + place booking (idempotency-key)"
  GW->>PAY: "Create escrow order"
  PAY->>PG: "Order (Razorpay, fallback Cashfree)"
  PG-->>U: "Payment intent (UPI / wallet)"
  U->>PG: "Pay"
  PG-->>PAY: "WEBHOOK: captured (source of truth)"
  PAY-->>GW: "Escrow held (idempotent)"
  GW->>MA: "Dispatch (bundling-first β†’ on-demand)"
  MA->>PT: "Offer leg(s)"
  PT-->>MA: "Accept"
  MA-->>U: "Assigned partner + ETA, live track begins"
        
Booking creation. Each external step is idempotent and keyed; the gateway webhook β€” not the client reply β€” confirms capture before matching runs. A double-tapped "place booking" with the same idempotency-key returns the existing booking, not a second one.
Capture β‰  client OK. The app may show "payment done" optimistically, but escrow is only marked held when the PG webhook arrives and is verified. Matching never starts on the client's word alone.

4. The booking state machine

A booking is a finite state machine. The happy path threads from DRAFT to SETTLED; every off-ramp (no partner, cancellation, failed handover, dispute) is an explicit side state with defined money behaviour. Relay bookings loop through AT_HUB once per intermediate leg.

stateDiagram-v2
  [*] --> DRAFT
  DRAFT --> QUOTED: "price returned"
  QUOTED --> PAYMENT_PENDING: "place booking"
  PAYMENT_PENDING --> ESCROW_HELD: "PG webhook captured"
  PAYMENT_PENDING --> CANCELLED_BY_BOOKER: "abandon / timeout"
  ESCROW_HELD --> MATCHING: "dispatch"
  MATCHING --> ASSIGNED: "partner accepts"
  MATCHING --> NO_PARTNER_FOUND: "cascade exhausted"
  NO_PARTNER_FOUND --> REFUNDED: "auto-refund escrow"
  ASSIGNED --> EN_ROUTE_PICKUP: "partner moving"
  ASSIGNED --> CANCELLED_BY_PARTNER: "partner drops"
  EN_ROUTE_PICKUP --> AT_PICKUP: "geofence reached"
  AT_PICKUP --> PICKED_UP: "pickup OTP verified"
  AT_PICKUP --> FAILED_PICKUP: "sender absent / refused"
  PICKED_UP --> IN_TRANSIT
  IN_TRANSIT --> AT_HUB: "relay leg"
  AT_HUB --> IN_TRANSIT: "next leg assigned"
  IN_TRANSIT --> AT_DROP: "final leg geofence"
  AT_DROP --> DELIVERED: "drop OTP + POD"
  AT_DROP --> FAILED_DELIVERY: "receiver absent"
  FAILED_DELIVERY --> RETURNED: "reverse logistics"
  DELIVERED --> SETTLED: "escrow released, splits paid"
  CANCELLED_BY_BOOKER --> REFUNDED
  CANCELLED_BY_PARTNER --> MATCHING: "re-dispatch"
  FAILED_PICKUP --> REFUNDED
  DELIVERED --> DISPUTED: "complaint window"
  DISPUTED --> SETTLED: "resolved in favour of partner"
  DISPUTED --> REFUNDED: "resolved in favour of booker"
  RETURNED --> REFUNDED
  SETTLED --> [*]
  REFUNDED --> [*]
        
Booking state machine. Solid happy path: DRAFT β†’ QUOTED β†’ PAYMENT_PENDING β†’ ESCROW_HELD β†’ MATCHING β†’ ASSIGNED β†’ EN_ROUTE_PICKUP β†’ AT_PICKUP β†’ PICKED_UP β†’ IN_TRANSIT β†’ (AT_HUB loop) β†’ AT_DROP β†’ DELIVERED β†’ SETTLED. Side states cover no-partner, both-side cancellation, failed pickup/delivery, returns, disputes and refunds.
ESCROW_HELD is the pivot. Before it, money is the booker's and a cancel is a clean refund. After it, money is committed and release follows handover. Each completed leg releases its slice on its own handover β€” see Split-Money Settlement.

5. OTP & POD handover

RideChain uses four OTPs across a booking, each closing a specific trust gap, plus a geo- and time-stamped Proof-of-Delivery photo and a tamper-evident QR on the parcel.

OTPWho proves itWhat it unlocks
Booking OTPBooker (phone)Confirms the booker's number / consent to place the order.
Pickup OTPSender β†’ partnerSender reads the code; partner enters it to prove the right parcel was collected from the right person.
Drop OTPReceiver β†’ partnerReceiver reads the code; partner enters it. This is what releases the leg's escrow.
Payment OTPBookerBank/UPI step-up during pay-in; owned by the gateway, surfaced in our flow.

On top of OTPs, every handover records a POD photo stamped with GPS + timestamp (stored in Cloudflare R2) and a QR scan of the parcel's unique ID. The QR scan must match the assigned parcel or the app blocks the handover β€” this is what stops a wrong parcel being given out.

flowchart LR
  A["Partner reaches drop geofence"] --> B{"Scan parcel QR"}
  B -- "mismatch" --> X["Block: wrong parcel"]
  B -- "match" --> C["Receiver reads drop OTP"]
  C --> D{"OTP correct?"}
  D -- "no" --> R["Retry / fallback channel"]
  D -- "yes" --> E["Capture POD photo (GPS + time)"]
  E --> F["Mark DELIVERED β†’ release leg escrow"]
        
A handover gate: QR match, then OTP, then geo-stamped POD photo, then escrow release. All four must line up before the leg closes.

6. Delivery journey β€” direct vs relay

A booking decomposes into ordered legs. A direct booking (Mode A) is one leg, one partner, door-to-door or Point-to-door. A relay / milk-run booking (Mode B) is multiple legs that pass through a Block Hub for consolidation, each leg a different partner. Money splits per leg, and each leg releases on its own handover.

flowchart TB
  subgraph ModeA["Mode A Β· Direct (one leg)"]
    SA["Sender"] -->|"pickup OTP"| PA["Partner A"]
    PA -->|"drop OTP + POD"| RA["Receiver"]
  end
  subgraph ModeB["Mode B Β· Relay / milk-run (multi-leg via Hub)"]
    SB["Sender / origin Point"] -->|"pickup OTP, leg 1"| PB1["Partner A"]
    PB1 -->|"handover at hub"| HUB["🏬 Block Hub
consolidate"] HUB -->|"leg 2 assigned"| PB2["Partner B"] PB2 -->|"drop OTP + POD, leg 2"| RB["Receiver / dest Point"] end classDef hub fill:#fff3e0,stroke:#f4920b,color:#8a5200; class HUB hub;
Direct (Mode A) is a single custody hop. Relay (Mode B) chains custody through the hub; the partner who completes leg 1 is paid for leg 1 even though a second partner finishes the journey.

The custody chain holds each parcel to exactly one partner per leg via four interlocking controls: an assignment lock, the unique parcel-ID / QR scan, the two-sided handover OTP, and a geofence + timestamp. A partner may serve many Points (coverage), but a given parcel is locked to one Point + one partner per leg.

ExampleLeg split (per _CONVENTIONS)
Single point-to-point, total β‚Ή120Partner β‚Ή80 Β· drop Point β‚Ή6 Β· collect Point β‚Ή6 Β· platform β‚Ή28 (absorbs PG ~β‚Ή2.4 + tax reserve ~β‚Ή3 β†’ net ~β‚Ή22.6)
Relay, total β‚Ή220Partner A leg1 β‚Ή55 Β· Partner B leg2 β‚Ή95 Β· drop Point β‚Ή6 Β· hub β‚Ή8 Β· collect Point β‚Ή6 Β· platform β‚Ή50
Currency in code and ledger is paise (integer); the β‚Ή figures above are display. Each leg's payout is a separate ledger release triggered by that leg's drop OTP + POD. See Split-Money Settlement.

7. Cancellation, failed pickup/delivery, returns & refunds

Who can cancel β€” and at what cost β€” depends on the state at the moment of cancellation. The rule of thumb: before ESCROW_HELD, cancels are free/full-refund; after a partner has started or completed work, work already done is paid for.

State at cancelWho can cancelFee / escrow outcome
DRAFT / QUOTEDBookerFree β€” no money taken yet.
PAYMENT_PENDINGBooker (or timeout)Order voided; nothing captured β†’ full refund of any auth.
ESCROW_HELD / MATCHINGBookerFull refund (no partner committed yet).
ASSIGNED / EN_ROUTE_PICKUPBookerSmall cancellation fee to compensate partner travel; remainder refunded.
ASSIGNED / mid-legPartnerAuto re-dispatch (back to MATCHING); a completed prior leg is still paid; booker not penalised.
AT_PICKUP β†’ FAILED_PICKUPSystemSender absent/refused after retries β†’ refund minus any travel fee.
AT_DROP β†’ FAILED_DELIVERYSystemReceiver absent β†’ fall back to nearest Point or move to RETURNED.
RETURNEDSystemReverse-logistics leg back to sender/origin Point; return leg charged or absorbed per policy, then REFUNDED for the undelivered portion.

Reverse logistics: a return creates a new leg in the opposite direction (receiver/drop-Point β†’ sender/origin-Point), re-using the same custody controls. The partner who carries the return leg is paid for it; the booker is refunded the value of the service not rendered (delivery), net of any return-leg charge.

8. No-app & low-literacy support

Rural reach means many bookers have no smartphone, intermittent data, or low literacy. The flow degrades gracefully across channels and reading levels.

πŸ’¬

WhatsApp & IVR

A WhatsApp bot walks through modes/package/quote in a chat; an IVR / missed-call flow calls the user back and books by voice prompts. Both produce the same DRAFT β†’ … β†’ SETTLED booking.

πŸͺ

Assisted booking at a Point

The kirana/CSC operator uses a simplified screen to book for a walk-in customer, prints/sticks the parcel QR, and stages the parcel for pickup or hands over a collection.

πŸ”Š

Voice & icon UI

Hindi-first, vernacular + audio prompts; icon- and photo-led screens so size/category can be chosen without reading. OTPs are read aloud where needed.

πŸ“΄

Offline outbox & sync

The Book/Partner apps queue the booking action in a local outbox (Drift/SQLite) when offline and replay it on reconnect β€” keyed by idempotency-key so a replay never double-books.

The offline outbox is why a booking made in a no-signal village still "sticks": it is persisted locally, then synced and reconciled against the gateway webhook once connectivity returns. See System Architecture.

9. Edge cases & failure modes

The booking flow is built assuming the unhappy path is the common path. Each risk below has a defined mitigation; the full catalogue lives in the Edge-Case Catalog.

ScenarioMitigation
OTP not received (SMS down / no signal)Fallback channels: WhatsApp β†’ IVR voice readout β†’ in-app display β†’ operator-assisted re-send.
Receiver absent at a DOOR dropAuto-fallback to nearest RideChain Point for collection; booker notified; else move to FAILED_DELIVERY β†’ RETURNED.
Partner cancels mid-legRe-dispatch from MATCHING; any already-completed leg stays paid; booker not penalised, journey resumes.
Payment captured but matching fails (NO_PARTNER_FOUND)Auto-refund the held escrow in full; booking moves NO_PARTNER_FOUND β†’ REFUNDED.
Double-tap / duplicate bookingIdempotency-key on "place booking"; the second request returns the existing booking, never a second order or charge.
GPS denied / spoofed at pickup geofenceBlock auto-confirm; require operator/manual confirm + photo; fraud module flags mock-location.
Wrong parcel handed overQR scan must match the assigned parcel-ID or the app blocks the handover β€” no OTP prompt, no release.
Perishable / urgent parcel delayedPerishable flag tightens SLA and routing priority; breach triggers proactive notify + partial-refund / re-route policy.
Declared-value dispute on damage/lossDeclared value is recorded at booking; claims reconcile against it via the dispute flow (DELIVERED β†’ DISPUTED).
Network drop during bookingLocal outbox persists the action; replays on reconnect, deduped by idempotency-key; webhook reconciles payment.
Relay second-leg partner never picks up at hubHub holds custody; leg-2 re-dispatched after timeout; SLA clock paused; booker kept informed.
Webhook lost / delayed after captureReconciliation job polls the gateway; idempotent escrow-held event prevents double-processing when the webhook later arrives.