// Who is holding the money, and who still has to hand it over. // // A completed ride splits into a driver payout and a platform fee, but the // split alone doesn't say whether anyone has actually been paid. That depends // on how the rider paid, because it decides who ends up holding the cash: // // Card — the rider pays the platform. The company already has its fee the // moment the card settles, and now OWES THE DRIVER their payout. // // Cash — the driver takes the whole fare at the kerb. They already have // their payout in their pocket, and now OWE THE COMPANY its fee. // // Unpaid — a cash ride the driver couldn't collect. Nobody has been paid and // nothing is owed between them; the fare itself is simply lost. // // So "has the company collected?" has no single answer per ride — it's one // question for card rides and the opposite question for cash ones. This module // is the single place that knows the difference, so the admin ledger, the // driver's balance and the settle endpoint can't drift apart. /** Payment states in which a completed ride actually produced money. */ export const SETTLED_PAYMENT_STATUSES = ["paid", "cash_collected"] as const; export const isPaidRide = (paymentStatus: string): boolean => (SETTLED_PAYMENT_STATUSES as readonly string[]).includes(paymentStatus); /** Which side of a ride's money a settlement action refers to. */ export const SETTLEMENT_SIDES = ["platform_fee", "driver_payout"] as const; export type SettlementSide = (typeof SETTLEMENT_SIDES)[number]; export const isSettlementSide = (v: unknown): v is SettlementSide => typeof v === "string" && (SETTLEMENT_SIDES as readonly string[]).includes(v); export type RideMoney = { status: string; payment_status: string; platform_fee_cents: number | null; driver_payout_cents: number | null; platform_fee_settled_at: string | null; driver_payout_settled_at: string | null; }; /** * What a single completed ride still owes, and to whom. * * `companyOwedCents` is money the company is waiting on — a cash ride whose * fee the driver hasn't remitted. `driverOwedCents` is money the company still * has to pay out — a card ride the driver hasn't been paid for. A ride that * never happened, or was never paid for, owes nothing in either direction. */ export const rideBalance = ( ride: RideMoney, ): { companyOwedCents: number; driverOwedCents: number } => { if (ride.status !== "completed" || !isPaidRide(ride.payment_status)) { return { companyOwedCents: 0, driverOwedCents: 0 }; } const fee = ride.platform_fee_cents ?? 0; const payout = ride.driver_payout_cents ?? 0; return { companyOwedCents: ride.platform_fee_settled_at === null ? fee : 0, driverOwedCents: ride.driver_payout_settled_at === null ? payout : 0, }; }; /** * The settlement timestamps a ride should be born with, given how it was paid. * * Whoever physically ends up holding their own share is settled the instant * the ride completes — there is no transfer left to make. Only the other side * is left outstanding, and that's the one somebody has to act on. */ export const initialSettlement = ( paymentStatus: string, ): { platformFeeSettled: boolean; driverPayoutSettled: boolean } => { switch (paymentStatus) { // Company holds the fare: its fee is in hand, the driver is owed. case "paid": return { platformFeeSettled: true, driverPayoutSettled: false }; // Driver holds the fare: their payout is in hand, the company is owed. case "cash_collected": return { platformFeeSettled: false, driverPayoutSettled: true }; // Nobody was paid; there is nothing to settle between them. default: return { platformFeeSettled: false, driverPayoutSettled: false }; } };