/ Docs / Payments & Integration v1 Β· 2026
Money Β· Payments

Payments & Integration

How money flows without RideChain ever holding it: a gateway-escrow model, a success-rate router across Razorpay and Cashfree, idempotent webhook-reconciled state, and an append-only ledger that only releases on dual-OTP + POD.

1. Overview & principles

The single most important design constraint on RideChain's money stack is regulatory, not technical: the platform holds no money. Funds are never swept into a RideChain bank account that the platform controls. Instead they sit in the payment gateway's escrow / nodal arrangement, and RideChain rides on the gateway's own RBI PA-PG licence. That one decision means RideChain does not need its own RBI nodal account or Payment Aggregator licence β€” a multi-year, capital-heavy approval we sidestep entirely while remaining compliant.

Mental model: RideChain is an orchestrator of money it never touches. We instruct a licensed gateway when to hold, split, release and refund; the gateway is the regulated entity that actually moves the rupees. Our ledger is a faithful mirror of what the gateway did, never the custodian of cash.
🏦

No money held

Gateway escrow means no RBI nodal/PA licence for RideChain. Funds rest with the gateway until a handover-triggered release instruction. The platform's only "balance" is the immutable ledger describing those movements.

πŸ”€

Razorpay primary, Cashfree fallback

Razorpay is the default rail; Cashfree is the failover. The choice is not hard-coded β€” a router picks per transaction by live, rolling success rate (per method & region).

🧩

Thin abstraction layer

A Go PaymentGateway interface hides both vendors. Order create, capture, split, payout, refund and webhook-verify are vendor-neutral, so either gateway is swappable per transaction with no call-site changes.

πŸ“’

Idempotent, ledger-first

Every money operation carries an idempotency key; the webhook is the source of truth, never the client's "I paid". Money is an append-only ledger β€” you never mutate a balance, you append a signed entry.

Five principles govern every line of payment code:

  1. Hold no money β€” escrow lives at the gateway; RideChain only issues instructions.
  2. Swappable gateways β€” a Go interface abstracts Razorpay and Cashfree so routing is a runtime decision, not a deploy.
  3. Route by live success rate β€” pick the rail most likely to succeed for this method/region right now; fail over fast.
  4. Webhook is truth β€” capture, refund and payout state advance only on a signature-verified gateway webhook (with a polling reconciliation safety net), never on a client callback.
  5. Append-only ledger β€” every paise movement is an immutable, double-entry ledger row keyed by idempotency key; balances are derived, never stored as mutable counters.
flowchart LR
  BOOK["πŸ“± Book app / PWA"] --> GO["Go payments module
(abstraction + router)"] GO --> ABS["PaymentGateway interface"] ABS --> RZP["Razorpay (primary)"] ABS --> CF["Cashfree (fallback)"] RZP --> ESC["Gateway escrow
(RideChain holds NO money)"] CF --> ESC ESC -. "release on drop-OTP + POD" .-> PARTNER["Partner / Point payouts"] GO --> LEDGER[("Append-only ledger
paise, double-entry")] RZP -. "signed webhook (source of truth)" .-> GO CF -. "signed webhook (source of truth)" .-> GO classDef gw fill:#e8f0fb,stroke:#1f5fae,color:#143d6e; classDef store fill:#fff3e0,stroke:#f4920b,color:#8a5200; class RZP,CF gw; class ESC,LEDGER store;
The payments module sits behind a single Go interface. The router chooses a gateway; the gateway holds escrow and emits the authoritative webhook; RideChain's append-only ledger mirrors every movement. Release to partners/Points happens only on dual-OTP + POD.

2. Payment methods

Rural India is UPI-first and cash-comfortable, so the method mix is built for both. UPI is the primary rail (ubiquitous even in interior markets), the RideChain Wallet smooths repeat use and refunds, COD / cash serves the unbanked and the distrustful, and pay-after-delivery escrow lets a booker authorize now but only be debited when the parcel actually lands.

MethodWhen usedTrust / COD-tier limits
UPI (primary)Default for almost every digital booking β€” collect request, dynamic QR, or intent. Ubiquitous in rural markets, instant settlement to gateway escrow.No tier cap on pay-in (money is captured up front). The amount is fully held in escrow before matching.
RideChain WalletRepeat bookers, refunds, COD float for partners, and instant re-spend of a refunded amount. Backed by gateway balance, not platform-held cash.Top-up via UPI; spendable balance only. Refunds land here fastest, sidestepping closed-handle failures.
COD / cashUnbanked senders/receivers, low-trust first orders, or where the receiver pays on collection. Partner collects physical cash.Tiered by trust tier β€” higher COD ceilings unlock as a partner/booker builds history (see Onboarding & KYC trust tiers).
Pay-after-delivery escrowBooker authorizes a debit at booking but is only charged when the drop-OTP + POD confirm delivery. Reduces "pay then it never arrives" fear.Authorized amount is mandate/auth-held; capture happens on drop-OTP. Eligibility gated by trust tier for high-value parcels.

For pay-on-delivery, three authorization shapes are used depending on the channel and device:

πŸ“²

UPI collect

A collect request is pushed to the booker's UPI handle; they approve in their UPI app. Best for self-serve digital bookings where the handle is known.

πŸ”³

Dynamic QR

A per-order QR (amount-bound) is shown in-app or at a Point. The booker scans and pays from any UPI app β€” ideal for assisted/walk-in booking.

πŸ”

OTP-authorized debit

For pay-after-delivery, a payment OTP authorizes the debit mandate up front; the actual capture is released only when the drop-OTP fires. See Β§6.

Whatever the method, the amount is stored in paise as an integer end-to-end. The β‚Ή figures shown to users (β‚Ή120, β‚Ή220) are display formatting over paise (12000, 22000). This keeps the canonical baselines in Commission & Pricing and Split-Money Settlement consistent and rounding-free.

3. The gateway abstraction & success-rate routing

Behind a single Go interface, both gateways look identical to the rest of the backend. The interface exposes the operations RideChain actually needs β€” create an order, verify a webhook, capture, split, payout, refund β€” and each vendor has its own adapter. Routing then becomes a pure runtime decision: for this method and region, which rail is succeeding right now?

flowchart TB
  REQ["Payment request
(method, region, amount)"] --> ROUTER{"Router: pick gateway by
rolling success rate
(per method / region)"} ROUTER -->|"Razorpay healthy"| RZP["Attempt via Razorpay"] ROUTER -->|"breaker OPEN for RZP"| CF1["Attempt via Cashfree"] RZP --> OK1{"Success?"} OK1 -->|"yes"| REC["Record outcome
(append ledger + metrics)"] OK1 -->|"failure / timeout"| RETRY["Retry via Cashfree
(idempotency key preserved)"] RETRY --> OK2{"Success?"} OK2 -->|"yes"| REC OK2 -->|"no"| QUEUE["Queue + notify
(both rails unhealthy)"] CF1 --> OK2 REC --> UPD["Update rolling success-rate
+ circuit-breaker state"] UPD --> ROUTER classDef gw fill:#e8f0fb,stroke:#1f5fae,color:#143d6e; classDef bad fill:#fdecea,stroke:#c0392b,color:#7b241c; class RZP,CF1,RETRY gw; class QUEUE bad;
Success-rate routing. The router picks the rail with the best rolling success rate for this method/region. A failure or timeout retries on the other gateway with the same idempotency key; outcomes feed back into the success-rate metric and a per-gateway circuit breaker that trips a rail OPEN when its failure rate spikes, so traffic shifts automatically before users feel it.

The circuit breaker is the safety valve: each gateway+method has a rolling window of attempts. When the failure rate crosses a threshold the breaker trips OPEN and the router stops sending that combination there (probing with a trickle of HALF_OPEN traffic to detect recovery). This prevents hammering a degraded gateway and keeps the overall success rate high. All retries reuse the same idempotency key so failing over from Razorpay to Cashfree can never double-charge.

CapabilityRazorpay (primary)Cashfree (fallback)
Escrow / holdOrder capture into gateway escrowOrder capture into gateway escrow
Split payoutsRazorpay Route (linked accounts)Cashfree Easy Split (vendor split)
Payout to partner/PointRazorpayX / Route transfersCashfree Payouts
Vendor / partner KYCβ€”Cashfree Easy KYC (Aadhaar OKYC, PAN, penny-drop)
Webhook signatureHMAC-SHA256 over raw bodyHMAC signature over raw body
Role in routingDefault rail (highest priority)Failover when Razorpay breaker is OPEN or attempt fails
KYC is Cashfree's job. Even when a transaction routes through Razorpay Route for the split, partner/Point identity verification is standardized on Cashfree Easy KYC. The abstraction layer keeps these orthogonal: which gateway moves the money is independent of who verified the payee. See Onboarding & KYC.

4. Escrow & pay-after-delivery lifecycle

Money in a booking is itself a state machine, sitting alongside the booking's own lifecycle (see Booking state machine). The pivot is release: funds leave escrow toward a partner or Point only after that leg's drop-OTP + POD are verified. For a relay booking, release is per leg β€” leg 1's partner is paid on leg 1's handover even before leg 2 completes.

stateDiagram-v2
  [*] --> ORDER_CREATED
  ORDER_CREATED --> AUTHORIZED: "booker pays / authorizes (UPI)"
  ORDER_CREATED --> FAILED: "pay-in fails / timeout"
  AUTHORIZED --> CAPTURED: "webhook captured (signature-verified)"
  CAPTURED --> ESCROW_HELD: "funds held at gateway"
  ESCROW_HELD --> SPLIT_RELEASED: "drop-OTP + POD per leg"
  SPLIT_RELEASED --> SETTLED: "payouts confirmed (Route / Easy Split)"
  ESCROW_HELD --> REFUND_INITIATED: "no partner / cancel / fail"
  SPLIT_RELEASED --> REFUND_INITIATED: "partial relay failure"
  REFUND_INITIATED --> REFUNDED: "gateway refund webhook"
  SETTLED --> CHARGEBACK: "bank dispute raised"
  CHARGEBACK --> SETTLED: "resolved with POD + OTP evidence"
  CHARGEBACK --> REFUNDED: "resolved for booker"
  FAILED --> [*]
  REFUNDED --> [*]
  SETTLED --> [*]
        
Payment lifecycle. Happy path: ORDER_CREATED β†’ AUTHORIZED β†’ CAPTURED β†’ ESCROW_HELD β†’ SPLIT_RELEASED β†’ SETTLED. Side paths: FAILED on pay-in, REFUND_INITIATED β†’ REFUNDED for cancels / no-partner / partial relay, and CHARGEBACK after settlement defended with POD + OTP evidence. Funds release from ESCROW_HELD only on drop-OTP + POD, per leg.

Pay-after-delivery threads the same machine but defers capture: the booker reaches AUTHORIZED at booking (a mandate/auth hold), and CAPTURED is delayed until the drop-OTP fires. If delivery fails, the auth is simply voided β€” the booker is never debited for a parcel that did not arrive.

Release is gated on physical proof, not time. No timer, cron, or "assume delivered" path releases escrow. Only a verified drop-OTP plus a geo/time-stamped POD photo (stored in Cloudflare R2) advances ESCROW_HELD β†’ SPLIT_RELEASED. For relay, each leg has its own gate and its own ledger release.

5. Integration sequence

End to end, a pay-in is three idempotent legs: create an order, let the booker pay, and advance state only on the signature-verified webhook. A background reconciliation job covers any webhook that is lost or delayed.

sequenceDiagram
  autonumber
  participant APP as "Book app / PWA"
  participant GO as "Go payments module"
  participant GW as "Gateway (RZP β†’ CF)"
  participant LED as "Append-only ledger"
  participant MA as Matching
  APP->>GO: "Place booking (idempotency-key)"
  GO->>GW: "Create order (amount in paise)"
  GW-->>GO: "order_id"
  GO-->>APP: "order_id + pay intent (UPI)"
  APP->>GW: "Pay (UPI collect / QR)"
  Note over APP,GW: "Client may show 'paid' optimistically β€” not trusted"
  GW-->>GO: "WEBHOOK: payment.captured (signed)"
  GO->>GO: "Verify HMAC signature + dedupe by event-id"
  GO->>LED: "Append CAPTURED β†’ ESCROW_HELD (idempotent)"
  GO-->>GW: "200 OK (ack webhook)"
  GO->>MA: "Dispatch matching"
  Note over GO,GW: "Reconciliation job polls gateway for any missed webhook"
        
Pay-in sequence. The webhook β€” not the app's reply β€” verifies capture. The Go module verifies the HMAC signature over the raw body, dedupes by the gateway event-id (idempotency), appends the ledger transition, then acks. Matching is dispatched only after ESCROW_HELD. A reconciliation job polls the gateway as a safety net for lost webhooks.

Three integration guarantees make this robust:

  1. Webhook signature verification β€” every inbound webhook's HMAC is verified against the raw request body using the gateway secret. An unverified or tampered webhook is rejected and logged; it never advances state.
  2. Idempotency keys β€” the client's booking idempotency-key flows into order creation, and the gateway's event-id dedupes webhooks. A replayed or duplicate event maps to the same ledger entry and changes nothing twice.
  3. Reconciliation job β€” a periodic job fetches gateway settlement/payment status for any order stuck in AUTHORIZED or missing its webhook, and applies the same idempotent transition the webhook would have. This is the lost-webhook safety net (see Β§8).
Never advance money state from the client. The app's "payment succeeded" screen is optimistic UX only. Escrow is held, matching dispatched, and payouts released solely on verified webhooks plus the reconciliation poller. This is the same rule the booking flow enforces.

6. The OTPs in payment

RideChain uses four OTPs across a journey (booking, pickup, drop, payment β€” see Booking & Delivery Flow Β§5). Three of them touch money directly: the booking OTP confirms intent, the payment OTP authorizes a pay-after-delivery debit, and the drop-OTP is what releases escrow.

OTPOwnerMoney effect
Booking OTPBooker (phone)Confirms consent to place the order; precedes any pay-in. No debit yet.
Payment OTPBooker (bank / UPI step-up)Authorizes the pay-after-delivery debit mandate. Owned by the gateway/bank, surfaced in our flow. Sets the order to AUTHORIZED without capturing.
Drop-OTPReceiver β†’ partnerTriggers escrow release. With the POD photo, it advances ESCROW_HELD β†’ SPLIT_RELEASED for that leg and captures any deferred pay-after-delivery debit.
flowchart LR
  PO["Payment OTP
(authorize debit)"] --> AUTH["Order AUTHORIZED
(no capture yet)"] AUTH --> WAIT["Parcel in transit
(escrow / auth held)"] WAIT --> DO{"Drop-OTP + POD
verified?"} DO -->|"yes"| CAP["Capture + SPLIT_RELEASED
(per leg)"] DO -->|"no / fail"| VOID["Void auth β†’ no debit"] classDef good fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20; classDef bad fill:#fdecea,stroke:#c0392b,color:#7b241c; class CAP good; class VOID bad;
How the OTPs gate money in pay-after-delivery: the payment OTP only authorizes; the drop-OTP (with POD) is what captures and releases. A failed delivery voids the auth so the booker is never charged for a non-delivery.

7. COD & cash reconciliation

Cash never disappears into a black box. When a partner collects cash (COD), every rupee is logged as a digital ledger event the moment it changes hands, then reconciled at the Block Hub or against the partner's wallet, and finally settled to the platform's split structure. The partner is effectively holding the platform's money in physical form, so trust limits and a tight digital trail are essential.

flowchart TB
  COL["Partner collects cash
(COD on delivery)"] --> EV["Log digital ledger event
(amount in paise, parcel-id, geo, time)"] EV --> REC{"Reconcile at hub / wallet"} REC -->|"deposit at hub or top-up wallet"| OK["Cash matched to ledger"] REC -->|"shortfall"| SHORT["Debit partner wallet
+ trust-tier hit"] OK --> SET["Settle splits
(partner / Point / platform)"] classDef bad fill:#fdecea,stroke:#c0392b,color:#7b241c; class SHORT bad;
COD reconciliation. Each cash event is logged digitally as it happens; the cash is then reconciled at the hub or against the partner's wallet. A shortfall debits the partner's wallet and dents their trust tier; a clean match flows into the normal split settlement.

COD limits are tiered by trust. A brand-new partner can carry only small COD totals before a mandatory hub deposit; as their verified history and successful reconciliations grow, the ceiling rises. The same applies to bookers/receivers paying COD on high-value parcels. Trust tiers are defined in Onboarding & KYC.

Trust tierCOD postureReconciliation cadence
New / unverifiedLow COD ceiling; small parcels onlyDeposit at hub before next COD assignment
VerifiedModerate COD ceilingDaily reconciliation at hub / wallet
TrustedHigher COD ceiling, fewer forced depositsPeriodic; auto wallet offset
COD settlement reuses the same split structure as digital payments β€” the cash a partner collected is treated as a pay-in that has already "captured", then split to partner/Point/platform. The mechanics of the split itself live in Split-Money Settlement.

8. Refunds, chargebacks, failures & reconciliation

Because money is captured into escrow up front, RideChain must be excellent at giving it back cleanly. Refund triggers, partial refunds for partial relays, chargeback defence, and a daily reconciliation job together keep the ledger and the gateway perfectly in agreement.

SituationMoney behaviour
Payment captured but no partner foundAuto-refund the full held escrow; booking goes NO_PARTNER_FOUND β†’ REFUNDED.
Partial relay then failurePartial refund: completed legs are paid (their drop-OTP fired), the undelivered remainder is refunded.
Chargeback raised by bankDefend with POD photo + drop-OTP + QR-scan evidence; resolved either back to SETTLED or REFUNDED.
Lost / delayed webhookPolling reconciliation fetches gateway status and applies the same idempotent transition the webhook would have.
Daily settlement driftReconciliation job diffs the gateway's settlement report against the internal ledger; mismatches are flagged for ops.
flowchart TB
  CRON["Daily reconciliation job"] --> PULL["Pull gateway settlement report
(Razorpay + Cashfree)"] PULL --> DIFF{"Diff vs internal
append-only ledger"} DIFF -->|"match"| CLEAN["Mark reconciled"] DIFF -->|"missing webhook"| APPLY["Apply idempotent transition
(replay-safe)"] DIFF -->|"unexplained mismatch"| FLAG["Flag to ops queue
(manual review)"] subgraph Recovery["Lost-webhook recovery"] POLL["Poller checks orders stuck
in AUTHORIZED"] --> APPLY end APPLY --> CLEAN classDef bad fill:#fdecea,stroke:#c0392b,color:#7b241c; class FLAG bad;
Reconciliation. A daily job diffs the gateway settlement reports against the internal ledger. Missing webhooks are recovered by replaying the idempotent transition (the poller also catches orders stuck in AUTHORIZED); only genuinely unexplained mismatches reach the ops queue. Because the ledger is append-only and idempotent, replay can never double-apply.
Idempotency makes recovery boring. Whether a transition arrives via the live webhook, a webhook replay, or the reconciliation poller, it keys to the same ledger entry β€” so the worst case for a lost or doubled webhook is a slightly delayed but correct ledger, never a double-charge or double-refund.

9. Compliance

Holding no money keeps RideChain on the right side of the heaviest regulation, but several obligations still apply. They are handled by riding on the gateways' licences and by never storing what we are not allowed to.

πŸ›οΈ

RBI PA-PG guidelines

RideChain is not a Payment Aggregator β€” it operates on Razorpay's and Cashfree's PA-PG licences. No nodal account, no PA approval. Escrow and settlement are the licensed gateways' responsibility.

🧾

GST e-invoicing

GST-compliant invoices / e-invoices are generated for platform fees and applicable services, with correct tax components on the commission line.

βœ‚οΈ

TDS on payouts

TDS is deducted where applicable on partner/vendor payouts, withheld and reported per the payout records the gateways execute on RideChain's instruction.

πŸ’³

PCI & tokenization

No card data is ever stored on RideChain systems. Card flows are tokenized by the gateway; RideChain only sees tokens and order/payment ids, keeping PCI scope minimal.

πŸ”

DPDP

Personal data (phone, KYC, addresses) is handled under India's DPDP regime β€” consent, purpose-limitation, and minimal retention. KYC artefacts live with Cashfree Easy KYC, not duplicated in-platform.

πŸ”‘

Keys & entity

Both Razorpay and Cashfree accounts (test + live keys) are held under Wetware Systems Private Limited. Live keys are vaulted; test keys are used in staging only.

The compliance posture is deliberately delegated: by holding no money, storing no card data, and routing KYC through Cashfree, RideChain keeps its own regulatory surface as small as possible while still operating a full split-settlement marketplace.

10. Edge cases & failure modes

The payment stack assumes gateways flake, networks drop, and webhooks vanish. Every money-touching failure has a defined, idempotent mitigation; the full catalogue lives in the Edge-Case Catalog.

Risk / scenarioMitigation
Both gateways downQueue the payment intent, retry with backoff across both rails, and notify the booker; nothing is matched until a real capture lands. Circuit breakers shed load off degraded rails.
Webhook never arrives after capturePolling reconciliation job fetches gateway status and applies the same idempotent ESCROW_HELD transition; orders stuck in AUTHORIZED are swept.
Double / replayed webhookDedupe by gateway event-id; the append-only ledger keys to one entry, so a duplicate event changes nothing twice.
Payment captured but matching failsAuto-refund the full escrow (NO_PARTNER_FOUND β†’ REFUND_INITIATED β†’ REFUNDED); booker made whole.
User pays twice (double-tap)Shared idempotency key on the order; a second pay is detected and the duplicate is auto-refunded β€” never two captures into escrow.
UPI timeout / pending statusPoll the gateway for the true status before any retry; never re-charge on a pending β€” only on a confirmed failure, and always with the same idempotency key.
Partial relay then failurePay the completed legs (their drop-OTP fired) and partial-refund the undelivered remainder; per-leg ledger releases make this exact.
Refund to a closed UPI handleRefund falls back to the RideChain Wallet (or alternate verified instrument); booker notified to withdraw, no funds stuck mid-air.
COD shortfall at reconciliationDebit the partner's wallet for the gap and apply a trust-tier hit; repeated shortfalls lower the partner's COD ceiling.
Chargeback after deliveryDefend with POD photo + drop-OTP + QR-scan evidence bundle; the custody chain is the proof the parcel was delivered.
Split target (partner / Point) KYC incomplete at releaseHold that slice in escrow until the vendor is onboarded via Cashfree Easy KYC; the rest of the split still releases, the held portion settles on completion.
Currency roundingAll amounts are paise integers end-to-end; splits are computed in paise with deterministic remainder allocation β€” no floating-point rounding drift.
Network drop mid-paymentIdempotent resume: the client replays the same order/idempotency-key on reconnect; the webhook (or poller) reconciles the true state without a second charge.