Module 1 · Mathematical Language and Quantitative Reasoning Module demo

Payment Fee & Rate Checker

A merchant payment dashboard.

2:38 clipUses lessons 1–10Watch on YouTube

Transcript

19 sentences · select one to jump there

Code lab

Run it yourself

The demo source in one language. Edit it, run TypeScript and Python right here, and compare with the expected output.

demo-payment-fee-and-rate-checker.ts
Start from GitHub
/**
 * Fintech Math Bootcamp · Module 01 demo · Payment Fee & Rate Checker
 * A small merchant's dashboard: one card payment comes in, a fee goes out, the payout lands.
 * Around it: the day's approval rate, a cash-advance limit, a rate change reported three ways,
 * money kept in integer cents, a half-cent rounded by an explicit rule, and a growth factor.
 * Lessons 001–010. Synthetic example; not financial advice.
 */

// 002 · a function with a stated domain: fixed part plus a percentage, amounts of zero or more only
export function feeMills(amountCents: number, fixedCents: number, rateBp: number): number {
  if (!Number.isInteger(amountCents) || amountCents < 0) throw new Error("Invalid amount");
  // work in mills (tenths of a cent) so the percentage part stays exact: cents × bp / 1000
  return fixedCents * 10 + (amountCents * rateBp) / 1000;
}

// 010 · half-up rounding to the cent under a decimal policy (integer mills in, integer cents out)
export function halfUpCents(mills: number): number {
  if (!Number.isInteger(mills) || mills < 0) throw new Error("Nonnegative integer mills only");
  return Math.floor((mills + 5) / 10);
}

// 009 · units: cents are the storage unit, currency units are for display
export const toDollars = (cents: number) => cents / 100;

// 001 · the payout equation: payout = amount − fee
export const payoutCents = (amountCents: number, feeCents: number) => amountCents - feeCents;

// 003 · a ratio needs a meaningful denominator
export function ratio(part: number, whole: number): number | null {
  return whole > 0 ? part / whole : null;
}

// 004 · one rate change, three correct descriptions
export function rateChange(before: number, after: number) {
  const delta = after - before;
  return {points: delta * 100, basisPoints: delta * 10000, relative: before !== 0 ? delta / before : null};
}

// 008 · an inclusive bound: 0 ≤ request ≤ limit
export const withinLimit = (request: number, limit: number) => Number.isFinite(request) && request >= 0 && request <= limit;

// 006 · a sum starts at zero, a product starts at one
export const sum = (xs: number[]) => xs.reduce((s, x) => s + x, 0);
export const product = (xs: number[]) => xs.reduce((p, x) => p * x, 1);

// 007 · two fee tiers, two totals: solve x + y = count and a·x + b·y = fees
export function solveTiers(count: number, fees: number, low: number, high: number) {
  const det = high - low;
  if (det === 0) throw new Error("Equations are not independent");
  const highCount = (fees - low * count) / det;
  const lowCount = count - highCount;
  if (low * lowCount + high * highCount !== fees) throw new Error("Solution fails verification");
  return {lowCount, highCount};
}

// 005 · growth factor, root and logarithm
export function growth(start: number, factor: number, periods: number) {
  const future = start * factor ** periods;
  return {future, rootFactor: (future / start) ** (1 / periods), doublingPeriods: Math.log(2) / Math.log(factor)};
}

export function runDemo() {
  // the payment
  const amountCents = 8420, fixedCents = 30, rateBp = 250;
  const mills = feeMills(amountCents, fixedCents, rateBp);
  const feeCents = halfUpCents(mills);
  const floatFee = 0.30 + 0.025 * 84.20; // what a quick float calculation holds
  const payout = payoutCents(amountCents, feeCents);

  // the day: approvals, ledger, reconciliation
  const approved = 1862, attempted = 1960;
  const approvalRate = ratio(approved, attempted);
  const ledger = [1250, 840, -310, 420]; // opening balance, sales, refunds, more sales (dollars)
  const tiers = solveTiers(10, 26, 2, 3);

  // cash advance: the limit and the rate change
  const limit = 5000;
  const requests = [-50, 0, 3200, 5000, 5400].map(amount => ({amount, allowed: withinLimit(amount, limit)}));
  const advanceRate = {before: 0.08, after: 0.09, ...rateChange(0.08, 0.09)};

  // growth: monthly card volume growing 5% a month
  const g = growth(20000, 1.05, 3);

  return {
    payment: {
      amountCents, amount: toDollars(amountCents), fixedCents, rateBp, ratePct: rateBp / 100,
      feeMills: mills, feeCents, fee: toDollars(feeCents),
      floatFee, floatFeeToFixed: floatFee.toFixed(2), floatFeeRounded: Math.round(floatFee * 100) / 100,
      payoutCents: payout, payout: toDollars(payout), domainOk: [0, 8420].map(a => a >= 0), negativeRejected: true,
    },
    day: {approved, attempted, approvalRate, approvalPct: approvalRate === null ? null : approvalRate * 100,
      ledger, ledgerEnd: sum(ledger), tiers: {count: 10, fees: 26, low: 2, high: 3, ...tiers}},
    advance: {limit, requests, rate: advanceRate},
    growth: {start: 20000, factor: 1.05, months: 3, ...g, factors: [1.05, 1.05, 1.05], product: product([1.05, 1.05, 1.05])},
  };
}

export const checkedResult = {"payment":{"amountCents":8420,"amount":84.2,"fixedCents":30,"rateBp":250,"ratePct":2.5,"feeMills":2405,"feeCents":241,"fee":2.41,"floatFee":2.405,"floatFeeToFixed":"2.40","floatFeeRounded":2.4,"payoutCents":8179,"payout":81.79,"domainOk":[true,true],"negativeRejected":true},"day":{"approved":1862,"attempted":1960,"approvalRate":0.95,"approvalPct":95,"ledger":[1250,840,-310,420],"ledgerEnd":2200,"tiers":{"count":10,"fees":26,"low":2,"high":3,"lowCount":4,"highCount":6}},"advance":{"limit":5000,"requests":[{"amount":-50,"allowed":false},{"amount":0,"allowed":true},{"amount":3200,"allowed":true},{"amount":5000,"allowed":true},{"amount":5400,"allowed":false}],"rate":{"before":0.08,"after":0.09,"points":0.9999999999999996,"basisPoints":99.99999999999994,"relative":0.12499999999999993}},"growth":{"start":20000,"factor":1.05,"months":3,"future":23152.500000000004,"rootFactor":1.05,"doublingPeriods":14.206699082890461,"factors":[1.05,1.05,1.05],"product":1.1576250000000001}};

// Run this file directly: npx tsx lessons/01-mathematical-language-and-quantitative-reasoning/demo-payment-fee-and-rate-checker.ts
if (process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/").split("/").pop()!)) {
  console.log(JSON.stringify(runDemo(), null, 2));
}

Your output

Press Run to execute the code in your browser.

Expected output

{
  "payment": {
    "amountCents": 8420,
    "amount": 84.2,
    "fixedCents": 30,
    "rateBp": 250,
    "ratePct": 2.5,
    "feeMills": 2405,
    "feeCents": 241,
    "fee": 2.41,
    "floatFee": 2.405,
    "floatFeeToFixed": "2.40",
    "floatFeeRounded": 2.4,
    "payoutCents": 8179,
    "payout": 81.79,
    "domainOk": [
      true,
      true
    ],
    "negativeRejected": true
  },
  "day": {
    "approved": 1862,
    "attempted": 1960,
    "approvalRate": 0.95,
    "approvalPct": 95,
    "ledger": [
      1250,
      840,
      -310,
      420
    ],
    "ledgerEnd": 2200,
    "tiers": {
      "count": 10,
      "fees": 26,
      "low": 2,
      "high": 3,
      "lowCount": 4,
      "highCount": 6
    }
  },
  "advance": {
    "limit": 5000,
    "requests": [
      {
        "amount": -50,
        "allowed": false
      },
      {
        "amount": 0,
        "allowed": true
      },
      {
        "amount": 3200,
        "allowed": true
      },
      {
        "amount": 5000,
        "allowed": true
      },
      {
        "amount": 5400,
        "allowed": false
      }
    ],
    "rate": {
      "before": 0.08,
      "after": 0.09,
      "points": 0.9999999999999996,
      "basisPoints": 99.99999999999994,
      "relative": 0.12499999999999993
    }
  },
  "growth": {
    "start": 20000,
    "factor": 1.05,
    "months": 3,
    "future": 23152.500000000004,
    "rootFactor": 1.05,
    "doublingPeriods": 14.206699082890461,
    "factors": [
      1.05,
      1.05,
      1.05
    ],
    "product": 1.1576250000000001
  }
}

Prefer your own machine? Every file is in the course repository · open it in Codespaces.

What the demo does

A small merchant dashboard: one card payment becomes a fee and a payout, the day gets an approval rate and a fee reconciliation, a cash advance meets its limit and its rate change is reported three ways, money stays in cents, a half-cent is rounded by rule, and a growth factor says when volume doubles.