subs-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 1ae4feb2bebf
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 5af1a249bbbef1db1b4c5ec8
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/lib/money.ts
// Money in minor units (cents for USD): parse what a person types, format for display.
export const CURRENCIES = [
  "USD", "EUR", "GBP", "CAD", "AUD", "NZD", "JPY", "CHF", "SEK", "NOK", "DKK", "PLN", "CZK", "HUF",
  "INR", "SGD", "HKD", "CNY", "KRW", "MXN", "BRL", "ARS", "CLP", "COP", "ZAR", "AED", "ILS", "TRY", "THB", "IDR", "PHP", "VND",
];

export function minorDigits(currency: string): number {
  try {
    return new Intl.NumberFormat("en", { style: "currency", currency }).resolvedOptions().maximumFractionDigits ?? 2;
  } catch {
    return 2;
  }
}

/** "12.5" in USD -> 1250. NaN for anything that is not an amount of zero or more. */
export function toMinor(value: string | number, currency: string): number {
  const digits = minorDigits(currency);
  const text = String(value).trim().replace(/[\s,]/g, "");
  if (!/^\d+(\.\d+)?$/.test(text)) return NaN;
  const [whole, frac = ""] = text.split(".");
  if (frac.length > digits) return NaN;
  return Number(whole) * 10 ** digits + Number((frac + "0".repeat(digits)).slice(0, digits) || 0);
}

export function fromMinor(minor: number, currency: string): string {
  const digits = minorDigits(currency);
  const sign = minor < 0 ? "-" : "";
  const abs = Math.abs(Math.round(minor));
  if (digits === 0) return `${sign}${abs}`;
  const s = String(abs).padStart(digits + 1, "0");
  return `${sign}${s.slice(0, -digits)}.${s.slice(-digits)}`;
}

export function money(minor: number, currency: string): string {
  try {
    return new Intl.NumberFormat(undefined, { style: "currency", currency }).format(Math.round(minor) / 10 ** minorDigits(currency));
  } catch {
    return `${fromMinor(minor, currency)} ${currency}`;
  }
}

/** Keeps the same written amount when the currency changes (9.99 USD becomes 9.99 EUR, 10 JPY). */
export function rescale(minor: number, from: string, to: string): number {
  return Math.round(minor * 10 ** (minorDigits(to) - minorDigits(from)));
}

/** A first guess at the person's currency from their browser's region. */
export function localCurrency(): string {
  const region = (navigator.language || "en-US").split("-")[1]?.toUpperCase();
  const byRegion: Record<string, string> = {
    US: "USD", GB: "GBP", CA: "CAD", AU: "AUD", NZ: "NZD", JP: "JPY", CH: "CHF", SE: "SEK", NO: "NOK", DK: "DKK", PL: "PLN",
    CZ: "CZK", HU: "HUF", IN: "INR", SG: "SGD", HK: "HKD", CN: "CNY", KR: "KRW", MX: "MXN", BR: "BRL", AR: "ARS", CL: "CLP",
    CO: "COP", ZA: "ZAR", AE: "AED", IL: "ILS", TR: "TRY", TH: "THB", ID: "IDR", PH: "PHP", VN: "VND",
    DE: "EUR", FR: "EUR", ES: "EUR", IT: "EUR", NL: "EUR", BE: "EUR", AT: "EUR", IE: "EUR", PT: "EUR", FI: "EUR", GR: "EUR", LU: "EUR", SK: "EUR", SI: "EUR", EE: "EUR", LV: "EUR", LT: "EUR", HR: "EUR",
  };
  return (region && byRegion[region]) || "USD";
}

Change history

Point store links to openapps.free
1ae4feb2be · 18 hours ago
Bind agent address in policy
99bbab3f0c · 19 hours ago
Use OpenApps service URLs and add contribution strip
781c3c23e5 · 19 hours ago
Bounded source sync
048e3257a5 · 1 day ago
Bounded source sync
7a358a3be6 · 1 day ago
Bounded source sync
0c833fcb0e · 1 day ago