charity.donation-matching@1.0.0
impl/typescript.ts
2,214 bytes · the TypeScript implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { applyRate } from "./money_apply_rate.ts"; ← from money.apply-rate ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { type DonationMatch, type MatchLimit } from "./charity_donation_matching_types.ts";
function check(name: string, currency: string, amount: Money): void {
if (amount.currency !== currency) throw new RangeError(`currency mismatch: ${amount.currency} and ${currency}`);
if (amount.minor < 0) throw new RangeError(`${name} must not be negative, received ${amount.minor}`);
}
/**
* An employer's matched-giving contribution. The ratio is applied first and
* rounded down; then the match is held to whatever room is left under the
* donor's cap and the programme's budget, never below zero, so a donor who
* has already used their cap gets nothing rather than a negative match.
*/
export function donationMatch(
donation: Money,
ratioBasisPoints: number,
donorCap: Money | null,
donorMatchedSoFar: Money,
programmeCap: Money | null,
programmeMatchedSoFar: Money
): DonationMatch {
const currency = donation.currency;
check("donation", currency, donation);
if (!Number.isInteger(ratioBasisPoints) || ratioBasisPoints < 0) {
throw new RangeError(`ratioBasisPoints must be a non-negative integer, received ${ratioBasisPoints}`);
}
if (donorCap !== null) check("donorCap", currency, donorCap);
check("donorMatchedSoFar", currency, donorMatchedSoFar);
if (programmeCap !== null) check("programmeCap", currency, programmeCap);
check("programmeMatchedSoFar", currency, programmeMatchedSoFar);
const uncapped = applyRate(donation, ratioBasisPoints, "down").minor;
let matched = uncapped;
let cappedBy: MatchLimit = "none";
const donorRoom = donorCap === null ? null : Math.max(0, donorCap.minor - donorMatchedSoFar.minor);
const programmeRoom = programmeCap === null ? null : Math.max(0, programmeCap.minor - programmeMatchedSoFar.minor);
if (donorRoom !== null && donorRoom < matched) {
matched = donorRoom;
cappedBy = "donor";
}
if (programmeRoom !== null && programmeRoom < matched) {
matched = programmeRoom;
cappedBy = "programme";
}
return { matched: money(matched, currency), uncapped: money(uncapped, currency), cappedBy };
}