1/** 2 * money-format.js â THE ONE MONEY FORMATTER for the coach's screens, and the 3 * one list of currencies a business can sell in. Shared verbatim by the 4 * browser and the server (the same dual-mode export as date-format.js). 5 * 6 * Alex, 2026-09-29: *"there is no GBP sign. I just want to make sure that the 7 * currency that the accounts are in is similar to how we determine it so that 8 * we default to the country⦠put a setting in Maya Brain about what currency 9 * is the main currency for each customer so they can change it if they want 10 * to. For example I want to always charge in USD although I'm UK-based."* 11 * 12 * ââ What lives here âââââââââââââââââââââââââââââââââââââââââââââââââââââ 13 * CURRENCIES the currencies a business can choose as its main one 14 * and price in: all two-decimal Stripe currencies, the 15 * common ones first. Zero-decimal ones (JPY, HUFâ¦) are 16 * left out on purpose: every price box on the platform 17 * works in hundredths. 18 * currencyForCountry an account's country â its default main currency. 19 * format(minor, cur) "£49.00", "$1,497.00", "A$49.00", "CHF 49.00". It is 20 * Intl.NumberFormat('en', { style: 'currency' }), the 21 * call the order screens already made, in one place. 22 * symbol(cur) "£", "$", "â¬", "A$", "CHF". 23 * 24 * ââ The business's main currency (browser only) âââââââââââââââââââââââââ 25 * `App.Money.main()` is the signed-in business profile's main currency 26 * (Maya's settings â Main currency; /auth/me `main_currency`, resolved on the 27 * server by services/business-currency.js). It only chooses the DEFAULT for a 28 * new price and what is shown where nothing else names a currency. A price 29 * that exists keeps its own currency, always. 30 * 31 * â ï¸ The buyer-facing runtimes (checkout, receipt, course access) are inlined 32 * into published pages and carry their own character-identical formatter 33 * (html-sanitiser `_deriveMainPriceLabel`); they are NOT this file. 34 */ 35(function (root) { 36 'use strict'; 37 38 /* Common first, in the order Alex named them, then the rest by name. */ 39 var CURRENCIES = [ 40 { code: 'gbp', name: 'British pound' }, 41 { code: 'usd', name: 'US dollar' }, 42 { code: 'eur', name: 'Euro' }, 43 { code: 'aud', name: 'Australian dollar' }, 44 { code: 'cad', name: 'Canadian dollar' }, 45 { code: 'nzd', name: 'New Zealand dollar' }, 46 { code: 'czk', name: 'Czech koruna' }, 47 { code: 'dkk', name: 'Danish krone' }, 48 { code: 'hkd', name: 'Hong Kong dollar' }, 49 { code: 'mxn', name: 'Mexican peso' }, 50 { code: 'nok', name: 'Norwegian krone' }, 51 { code: 'pln', name: 'Polish zloty' }, 52 { code: 'sgd', name: 'Singapore dollar' }, 53 { code: 'zar', name: 'South African rand' }, 54 { code: 'sek', name: 'Swedish krona' }, 55 { code: 'chf', name: 'Swiss franc' }, 56 { code: 'aed', name: 'UAE dirham' }, 57 ]; 58 var CODES = CURRENCIES.map(function (c) { return c.code; }); 59 var DEFAULT_CURRENCY = 'usd'; 60 61 /* The eurozone, plus the places that use the euro without being in it. */ 62 var EURO = ['at', 'be', 'hr', 'cy', 'ee', 'fi', 'fr', 'de', 'gr', 'ie', 'it', 'lv', 'lt', 63 'lu', 'mt', 'nl', 'pt', 'sk', 'si', 'es', 'ad', 'mc', 'sm', 'va', 'me', 'xk']; 64 var BY_COUNTRY = { 65 gb: 'gbp', uk: 'gbp', je: 'gbp', gg: 'gbp', im: 'gbp', 66 us: 'usd', pr: 'usd', 67 au: 'aud', ca: 'cad', nz: 'nzd', 68 ch: 'chf', li: 'chf', dk: 'dkk', no: 'nok', se: 'sek', pl: 'pln', cz: 'czk', 69 hk: 'hkd', sg: 'sgd', za: 'zar', ae: 'aed', mx: 'mxn', 70 }; 71 EURO.forEach(function (c) { BY_COUNTRY[c] = 'eur'; }); 72 73 /** 'GBP' / ' gbp ' â 'gbp'; anything not in CURRENCIES â null. */ 74 function normalise(code) { 75 var c = String(code == null ? '' : code).trim().toLowerCase(); 76 return CODES.indexOf(c) === -1 ? null : c; 77 } 78 79 function isSupported(code) { return normalise(code) !== null; } 80 81 /** An ISO 3166 alpha-2 country ('gb', 'US') â its currency; unknown â 'usd'. */ 82 function currencyForCountry(cc) { 83 var c = String(cc == null ? '' : cc).trim().toLowerCase(); 84 return BY_COUNTRY[c] || DEFAULT_CURRENCY; 85 } 86 87 function _nf(code, whole) { 88 var opts = { style: 'currency', currency: String(code).toUpperCase() }; 89 if (whole) { opts.minimumFractionDigits = 0; opts.maximumFractionDigits = 0; } 90 return new Intl.NumberFormat('en', opts); 91 } 92 93 /** 94 * Minor units â money with its sign: format(4900, 'gbp') â "£49.00". 95 * `{ whole: true }` drops ".00" from a whole amount ("£49", but "£49.50"). 96 * A currency Intl does not know still says what it is ("49.00 XYZ"), never 97 * a bare number and never a wrong sign. 98 */ 99 function format(minor, currency, opts) { 100 var n = (Number(minor) || 0) / 100; 101 var code = String(currency || '').trim(); 102 var whole = !!(opts && opts.whole) && Math.round(n * 100) % 100 === 0; 103 if (!code) return whole ? String(n) : n.toFixed(2); 104 try { return _nf(code, whole).format(n); } catch (_) { 105 return (whole ? String(n) : n.toFixed(2)) + ' ' + code.toUpperCase(); 106 } 107 } 108 109 /** The same, from major units (a figure a coach typed): 49 â "£49.00". */ 110 function formatMajor(major, currency, opts) { 111 return format(Math.round((Number(major) || 0) * 100), currency, opts); 112 } 113 114 /** 'gbp' â '£', 'aud' â 'A$', 'chf' â 'CHF'. Unknown â the code in capitals. */ 115 function symbol(currency) { 116 var code = String(currency || '').trim(); 117 if (!code) return ''; 118 try { 119 var parts = _nf(code, false).formatToParts(1); 120 for (var i = 0; i < parts.length; i++) if (parts[i].type === 'currency') return parts[i].value; 121 } catch (_) { /* below */ } 122 return code.toUpperCase(); 123 } 124 125 /** '<option>' rows for a currency select, the given one selected. */ 126 function optionsHtml(selected) { 127 var sel = normalise(selected); 128 return CURRENCIES.map(function (c) {
129 return '<option value="' + c.code + '"' + (c.code === sel ? ' selected' : '') + '>' + 130 c.code.toUpperCase() + ' ' + symbol(c.code).replace(/&/g, '&').replace(/</g, '<') + 131 ' · ' + c.name + '</option>'; 132 }).join(''); 133 } 134 135 var API = { 136 CURRENCIES: CURRENCIES, CODES: CODES, DEFAULT_CURRENCY: DEFAULT_CURRENCY, 137 normalise: normalise, isSupported: isSupported, currencyForCountry: currencyForCountry, 138 format: format, formatMajor: formatMajor, symbol: symbol, optionsHtml: optionsHtml, 139 }; 140 141 if (typeof module !== 'undefined' && module.exports) module.exports = API; 142 if (!root || typeof root !== 'object') return; 143 root.CBMoney = API; 144 145 /* ââ Browser: the signed-in business's main currency âââââââââââââââââââ */ 146 var _main = null; 147 function main() { 148 if (_main) return _main; 149 var u = null; 150 try { u = (typeof currentUser !== 'undefined' && currentUser) || root.currentUser || null; } catch (_) { u = root.currentUser || null; } 151 return normalise(u && u.main_currency) || currencyForCountry(u && u.home_country); 152 } 153 function setMain(code) { 154 var c = normalise(code); 155 if (!c) return; 156 _main = c; 157 try { if (typeof currentUser !== 'undefined' && currentUser) currentUser.main_currency = c; } catch (_) {} 158 } 159 var Money = {}; 160 for (var k in API) Money[k] = API[k]; 161 Money.main = main; 162 Money.setMain = setMain; 163 root.App = root.App || {}; 164 root.App.Money = Money; 165})(typeof window !== 'undefined' ? window : this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.