Services About Us Why Choose Us Our Team Development Workflow Technology Stack Case Studies Portfolio Blog Free Guides Shopify Audit ($499) Estimate Project Contact Us
← All Case Studies

Rentcaar — Peer-to-Peer Car Rental Marketplace for the Australian Market

A production-grade P2P vehicle rental platform with Stripe Connect multi-party payouts, weekly subscription billing, identity verification, real-time matching, and a fully self-serve admin control plane.

Client: Rentcaar Ruby on Rails 7.2 PostgreSQL Hotwire Turbo Streams Stimulus JS Tailwind CSS Stripe Connect AWS S3 ActionCable ViewComponent Solid Queue Pundit Devise Didit Mapbox Docker RSpec
Rentcaar — Peer-to-Peer Car Rental Marketplace for the Australian Market
💼
Client
Rentcaar
🌎
Industry
Marketplace / Automotive
Tech Stack
Ruby on Rails 7.2, PostgreSQL, Hotwire
🔗
Website

Overview

The founder of Rentcaar came to us with a specific problem: Australia's vehicle rental market is dominated by large fleets with inflexible pricing, while thousands of private car owners sit on idle vehicles they rarely use. His vision was a peer-to-peer marketplace — verified owners earning income from their cars, verified renters accessing flexible weekly rentals at rates traditional companies can't match.

That vision is straightforward. Building the software behind it is not. A P2P rental platform isn't just a listings site. It's a financial services product: you're moving real money between strangers, holding security bonds, managing failed payments, detecting no-shows, adjudicating disputes, and verifying identities — all in a regulated market (Australia) with real legal and financial consequences if any of it goes wrong.

We built Rentcaar on Rails 7.2 with a Hotwire-driven frontend, Stripe Connect for multi-party payments, and Didit for identity verification. The platform is live at rentcaar.com, handling the full lifecycle from host onboarding through weekly subscription billing through bond dispute resolution — without needing a developer in the loop for day-to-day operations.

💡

The Challenge

Multi-Party Payment Architecture


Every rental involves three parties: the renter paying, the host receiving, and the platform taking a commission. That's not a simple payment — it's a multi-party financial transaction with bond holds, weekly recurring billing, partial refunds, and dispute-triggered transfers. The bond alone has at least five resolution paths: full refund to renter (normal completion), full transfer to host (no-show), split decision (damage dispute), forfeiture (admin override), and additional charge (damage exceeding the bond). Building this on Stripe Connect without introducing race conditions, double-charges, or unrecoverable states was the hardest single engineering problem on the project.

Availability Race Conditions


Between the moment a renter checks availability and the moment their payment clears on Stripe, another renter could book the same car. This is a classic time-of-check to time-of-use race condition — entirely possible on Stripe's hosted checkout where the renter is off your server for 30–90 seconds. Most marketplaces ignore this until it causes a double-booking in production. We had to solve it before launch.

Two-Sided Matching System


Beyond "browse and book," Rentcaar needed an active matching layer: renters post requirements, hosts browse demand and reach out; renters browse listings and initiate contact. Both directions needed quota enforcement (to prevent spam), 24-hour expiry windows, verification gates (identity check before accepting), and a cooldown mechanism after quota fill. The state machine had to be correct — an accepted match that bypasses verification or exceeds quota without the cooldown is a trust problem, not just a UX glitch.

Dual-Role Identity and Document Verification


Hosts need insurance documents (Certificate of Currency, CTP registration) reviewed and approved before they can publish a listing. Renters need identity verification (government ID + liveness check) before certain matching interactions. These two pipelines are different in nature: one is async human review by an admin, the other is automated via a third-party API (Didit). Both gate critical actions, so any failure or delay in either pipeline stalls the user and needs clear status communication throughout.

Operational Control Without Engineering


The founder needed to run the marketplace — adjusting commission rates, tuning match quotas, reviewing bond claims, approving documents, banning bad actors — without filing tickets to a developer for every operational decision. Building an admin dashboard that is both powerful enough for all these operations and safe enough to use without breaking production is harder than it sounds, especially when financial operations like bond decisions trigger irreversible Stripe API calls.

Real-Time Notifications Across 35 Event Types


Renters and hosts need instant feedback: when a match request arrives, when a payment succeeds or fails, when a document gets approved, when an identity check completes. Email alone isn't enough for a marketplace where timing matters (match expires in 24 hours). A real-time notification system needed to work across 35 distinct event types without becoming a maintenance nightmare as the event list grew.

Our Solution

Stripe Connect with Bond Checkout + Weekly Subscription Billing


We architected payments in two distinct phases. Phase one: the renter pays the security bond via a Stripe-hosted Checkout session before the booking is confirmed. This keeps us fully outside PCI scope — we never touch card data. Phase two: once bond payment clears and our BookingFinalizationService runs, we create a Stripe Subscription with 13 weekly invoices anchored to the pickup date. The subscription handles all recurring billing automatically, fires webhooks on each success or failure, and cancels itself on the second consecutive payment failure — terminating the booking without manual intervention. Host payouts flow through Stripe Connect Express accounts, with the platform's commission taken as an application fee on each invoice. Bond resolution is handled entirely through our StripeService class — the single gateway rule means every Stripe API call goes through one place, returns a typed Result object, and logs every request/response with PII stripped.

BookingFinalizationService with Optimistic Locking


The double-booking race condition is solved in BookingFinalizationService. When the renter returns from Stripe checkout, before we create the Bond record or start the subscription, we acquire a row-level lock on the listing (listing.with_lock), re-run the full availability check inside the transaction, and only proceed if the car is still free. If another booking snuck through during checkout, we immediately refund the bond via Stripe and surface a clear error. The entire finalization — lock, availability check, Bond record, Subscription creation, confirmed_at timestamp — runs in a single database transaction. Either all of it succeeds or none of it does.

Match State Machine with XOR Database Constraint


The matching system handles two fundamentally different flows — renter-initiated (against a listing) and host-initiated (against a requirement) — using a single Match model. We enforced correct usage at the database level with a CHECK constraint that ensures exactly one of listing_id or requirement_id is set, never both, never neither. Quota limits (renter: 5 concurrent, host: 2 across all cars, per-listing: 3) are enforced at validation time with clear error messages, not silently rejected. The 24-hour expiry runs via a Solid Queue recurring job — no Redis, no separate scheduler daemon. Verification states (needs_renter_verification, needs_host_verification) are first-class enum values in the state machine, so the UI can always show exactly what's blocking an acceptance without conditional logic scattered across views.

Didit Identity Verification + Document Moderation Queue


We integrated Didit's hosted identity verification flow (government ID + passive liveness + face match) to replace Stripe Identity at a fraction of the cost — 500 free verifications per month plus $0.33 each versus $1.50–2.50 per check with Stripe Identity. The integration handles both the webhook path (Didit fires async confirmation) and the return URL path (user comes back before webhook arrives), setting identity_verified_at from whichever fires first. For host documents, we built a moderation queue in the admin panel: pending uploads arrive with expiry dates, admins approve or reject with notes, and PaperTrail captures the full audit trail including who reviewed what and when. Both pipelines trigger in-app bell notifications and emails on every status change.

NotificationService with ActionCable + Turbo Streams


Rather than polling or page refreshes, notifications push instantly via ActionCable. Every call to NotificationService.notify() creates a database record then broadcasts a Turbo Stream action to the recipient's private channel — prepending the new notification to the dropdown and updating the unread badge count. The 35 event types share a single JSONB payload field for flexible context without schema migrations every time a new event type ships. Mailers deliver email copies async via Solid Queue. The result is a notification system that feels real-time to users and is trivially extensible for new event types.

SiteSetting Singleton + Full Admin Control Plane


We built a SiteSetting singleton record that controls every tunable parameter in the marketplace: commission percentage, bond multiplier, minimum rental weeks, match quotas and cooldowns, feature flags for the matching system and verified car badge, and featured city display. Changes take effect immediately (5-minute cache TTL) without a deploy. The admin dashboard covers the full operational surface: document moderation queue, bond claims adjudication (award host, award renter, split with custom amounts, or deny — all with Stripe API calls wired up), user management with risk scoring and ban actions, listing promotion, and a complete PaperTrail audit log on every irreversible operation. Staff roles can read but not mutate financial decisions; admin roles have full access. Pundit policies enforce this consistently across every controller action.

📈

Results & Impact

Platform Launch



  • Rentcaar is live at rentcaar.com, serving the Australian vehicle rental market as a fully operational peer-to-peer marketplace.

  • The full renter and host journeys — registration, identity verification, listing creation, search, matching, booking, payment, and bond resolution — are end-to-end functional in production.

  • No-show detection, match expiry, and document expiry all run on scheduled background jobs without manual intervention.

Technical Architecture



  • 23 Rails models with clean domain boundaries: Users, Listings, Bookings, Bonds, BondClaims, Matches, Requirements, Documents, Notifications, and more.

  • 15+ service objects follow a uniform Result pattern — every complex operation returns a typed success/failure with no exceptions used for expected flows.

  • 18 ViewComponent classes provide reusable, testable UI across the listing cards, badges, progress indicators, and admin panels.

  • 15+ Stimulus controllers handle client-side interactivity — search filters, photo carousels, form validation, mobile navigation — without a JavaScript framework dependency.

  • Solid Queue handles all background processing with no Redis required — a significant operational simplification for a two-person startup.

Payment Security and Compliance



  • Full PCI DSS compliance maintained: all card data handled by Stripe-hosted checkout. We store customer IDs and payment method IDs only — no raw card numbers ever touch our servers.

  • Every Stripe webhook is HMAC-verified. All Stripe API calls include idempotency keys to prevent duplicate charges on retries.

  • Bond resolution paths — refund, host transfer, split, additional charge — are fully automated through StripeService with audit trails in PaperTrail and the StripeEvent log.

Operational Efficiency



  • The admin panel handles the full operational lifecycle — document approvals, bond claim decisions, user risk scoring, match quota tuning — without any developer involvement in day-to-day operations.

  • Identity verification costs reduced by ~80% by switching from Stripe Identity to Didit (500 free checks/month + $0.33 each versus $1.50–2.50 per check).

  • PaperTrail audit trails on all financial and administrative decisions provide a complete evidence record for disputes, refunds, and compliance inquiries.

Security and Trust Infrastructure



  • Rack-attack rate limiting on authentication endpoints and webhooks prevents brute-force and replay attacks.

  • Address privacy built into the data layer: suburb-only display for non-paying users, full address revealed only to confirmed booking parties and admins.

  • Dual capability model (is_renter, is_host as orthogonal booleans) with PaperTrail on every capability change supports fraud investigation without needing to reconstruct state from logs.

  • BannedIdentifier table with hashed phone/email values blocks known bad actors from re-registering under new accounts.

Technology Stack

Ruby on Rails 7.2
PostgreSQL
Hotwire
Turbo Streams
Stimulus JS
Tailwind CSS
Stripe Connect
AWS S3
ActionCable
ViewComponent
Solid Queue
Pundit
Devise
Didit
Mapbox
Docker
RSpec

Want Similar Results?

Let's discuss how we can apply our expertise to your project.

TechVinta Assistant

Online - Ready to help

Hi there!

Need help with your project? We're online and ready to assist.

🍪

We use cookies for analytics to improve your experience. See our Cookie Policy.