← back to Costa Rica

lib/money.js

48 lines

'use strict';
// Money helpers. Everything is integer MINOR UNITS (USD cents, CRC centimos).
// The marketplace fee math lives here so bookings + payouts agree to the cent.

const CURRENCIES = { USD: 2, CRC: 2 }; // both use 2 minor digits

function assertCurrency(c) {
  if (!(c in CURRENCIES)) throw new Error(`unsupported currency ${c}`);
}

// bps = basis points (100 bps = 1%). platformFeeBps default 1000 = 10%.
function computeSplit({ subtotal, cleaningFee = 0, currency, platformFeeBps = 1000, processorFeeBps = 0 }) {
  assertCurrency(currency);
  // R5 — guard inputs so bad numbers can never yield NaN/negative money that a
  // DB CHECK (bookings_money_nonneg / bookings_total_reconciles) would 500 on.
  if (!Number.isInteger(subtotal) || subtotal < 0) throw new Error('subtotal must be a non-negative integer (minor units)');
  if (!Number.isInteger(cleaningFee) || cleaningFee < 0) throw new Error('cleaningFee must be a non-negative integer (minor units)');
  if (!(platformFeeBps >= 0 && platformFeeBps <= 10000)) throw new Error('platformFeeBps out of range (0..10000)');
  if (!(processorFeeBps >= 0 && processorFeeBps <= 10000)) throw new Error('processorFeeBps out of range (0..10000)');
  const chargeableFees = cleaningFee;                     // fees the guest pays on top of subtotal
  const platformFee = Math.round((subtotal + chargeableFees) * platformFeeBps / 10000);
  const total = subtotal + chargeableFees;                // what the traveler is charged
  const processorFee = Math.round(total * processorFeeBps / 10000);
  const hostPayout = Math.max(0, total - platformFee - processorFee);  // what the host receives (never negative)
  return {
    currency,
    subtotal,
    fees: chargeableFees + platformFee,
    cleaningFee,
    platformFee,
    processorFee,
    total,
    hostPayout,
  };
}

// Nightly booking subtotal from a base nightly price and a date range.
function nights(checkIn, checkOut) {
  const a = new Date(checkIn + 'T00:00:00Z'), b = new Date(checkOut + 'T00:00:00Z');
  const d = (b - a) / 86400000;
  if (Number.isNaN(d)) return 0; // defensive: invalid dates → 0 (the route is the real gate, C2)
  return Math.max(0, Math.round(d));
}

const fmt = (minor, currency) => `${currency} ${(minor / 100).toFixed(2)}`;

module.exports = { computeSplit, nights, fmt, assertCurrency, CURRENCIES };