The billing and payments backend for a ride-hailing platform: fare estimation, payment processing across personal, corporate and family-package rides, driver earnings and payouts, and financial reconciliation. Every movement of money is posted as a balanced double-entry transaction against a separate accounting ledger service, so the platform’s financial state stays auditable and defensible under retries and audits.
Background
RidePlus is a ride-hailing product built at 2F Capital. I contributed backend services on the money side of it: how a trip turns into a fare, how a fare turns into a charge, how a driver gets paid, and how the books are closed at month end.
The deliberate design choice was to keep the ride application out of the bookkeeping business. RidePlus owns trips, riders, drivers, programs and pricing; a standalone accounting service owns the general ledger. RidePlus never mutates balances directly; it asks the ledger to post a balanced set of debits and credits. That single boundary is what makes the system reconcilable: the ride app can be wrong, but the ledger is always balanced.
Architecture
RidePlus itself is a modular Go monolith (clean layering, constructor-injected) that sits inside a small set of cooperating services: a shared SSO for identity, Open Policy Agent for authorization, and the accounting ledger for money. Each service is independently deployable, owns its own data, and talks to the others over HTTP.
flowchart LR
client["Rider / Driver<br/>Admin apps"]
kafka(["Kafka<br/>driver/vehicle sync"])
subgraph rp["RidePlus service (Go)"]
direction TB
api["REST API /api<br/>auth · activity-log · errors"]
mods["Use cases<br/>account · street_pickup<br/>invoice · transaction<br/>program · driver · company"]
cron["Cron workers<br/>invoices · overdue · cleanup"]
api --> mods
cron --> mods
end
subgraph data["Data stores"]
direction TB
db[("CockroachDB")]
cache[("Redis")]
end
subgraph ext["External services"]
direction TB
acct["Accounting<br/>double-entry ledger"]
sso["SSO · identity"]
opa["OPA · RBAC"]
maps["Google Maps"]
end
client --> api
kafka -- events --> mods
mods --> db
mods --> cache
api -. authenticate .-> sso
api -. authorize .-> opa
mods -- post debits/credits --> acct
mods -- route and distance --> maps
A request passes auth (SSO token → OPA decision) and an activity-log middleware that records every mutation, lands on a use case, which reads and writes CockroachDB and calls the ledger for anything financial.
Billing Data Flow
Fares come from street_pickup: Google Maps gives distance and duration, and the cost is base + distance·perKm + duration·perMin with separate day and night rate tiers, all driven by configurable parameters.
A payment is a two-step, idempotent state machine (create an intent, then confirm it), so a retried confirmation can’t double-charge. Confirmation is where the ledger entry is written.
sequenceDiagram
participant App as Rider app
participant RP as RidePlus
participant L as Accounting ledger
participant T as Trip (street_pickup)
App->>RP: create payment intent (trip / amount)
RP->>RP: validate limits, persist PENDING
RP-->>App: intent id
App->>RP: confirm intent
RP->>RP: PENDING → PROCESSING (guarded)
RP->>L: MakeTransaction(debit rider, credit driver, +tip)
L-->>RP: transaction id (balanced)
RP->>T: mark trip paid
RP->>RP: status → SUCCEEDED, record rating
RP-->>App: receipt (both parties)
The same mechanism covers the harder cases:
- Split billing. Corporate rides debit the company’s account and credit the driver; family-package rides debit the package owner. Each posting is tracked against the program’s spending limits.
- Driver money. Per-trip commission is posted on trip completion (
commission = cost · rate, debit driver-commission, credit platform service-charge). Wallet credits, top-ups and cash-outs are all balanced ledger entries; the RidePlus-credit conversion splits out VAT. - Reconciliation. Cron workers generate monthly (and on-demand) invoices per company from the ledger, mark overdue invoices daily, and clean up stale trips. Reversals are first-class: a transaction can be reversed rather than deleted.
Design Decisions
The load-bearing decision is keeping the ledger out of the ride app. The accounting service is a general double-entry bookkeeping API, and nothing in it knows what a ride is. That buys a reusable ledger of record and a boundary the ride code can’t corrupt; it costs a network hop and a second database whose consistency with the ride data has to be managed rather than assumed. The intent-then-confirm flow, idempotency keys, and first-class reversals are what make that boundary safe to cross, and the invariant underneath does the rest: the ledger rejects an unbalanced transaction, so the books reconcile by construction instead of by a nightly job.
Two platform choices are worth being honest about. CockroachDB wasn’t picked because money needs strong consistency (any ACID database, Postgres included, gives you that); what it adds over Postgres is distributed, horizontally scalable SQL, which for a single-region billing service was arguably more than the problem demanded. Where it earned its place was the posting path, where serializable isolation and automatic retries let the balance updates sit in one transaction without hand-rolled locking. Open Policy Agent is a similar trade: authorization moves out of the handlers so policy changes without a redeploy, at the cost of another hop and rules that live outside the code.
Contributions
I worked across the billing backend as one of the backend engineers, alongside senior engineers. I built the programs subsystem, covering corporate ride accounts and family packages: enrollment and invitations, program assignment, and spend tracking settled against the shared ledger. I also built driver rating and review into the payment flow. Every endpoint I shipped came with an automated acceptance test that exercised the route end to end against a real database, which is also how I reverse-engineered the parts of the system I inherited. I also contributed to the shared pagination/filtering library used across the platform’s services.
Backend contribution on the RidePlus billing platform at 2F Capital, July 2022 – February 2023.