← 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 };