/* global usScheduler */ (function () { 'use strict'; // Cadence wording for each offering billing mode, in the phrasing a student // sees beside a price. Mirrors Offering::VALID_BILLING_MODES. const CADENCE = { one_time: 'at booking', full_term: 'up front', weekly: 'weekly', monthly: 'monthly', }; // How each cadence is actually collected, spelled out beneath the price so // the one-word cadence is never the only thing a student has to go on. const CADENCE_NOTE = { one_time: 'Charged once, when you book.', full_term: 'Charged once, up front, for the whole term.', weekly: 'Charged for each lesson, 24 hours before it starts.', monthly: 'Charged on the 1st of each month, for that month’s lessons.', }; // The billing modes whose price is a per-lesson fee billed again and again, // rather than a single charge. Mirrors Offering::SCHEDULED_BILLING_MODES. const RECURRING = ['weekly', 'monthly']; function escHtml(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function mode(billingMode) { return CADENCE[billingMode] ? billingMode : 'one_time'; } // A monthly charge rolls up every lesson that falls in the month, so a // private lesson's monthly price is quoted *per lesson* — the fee is // multiplied by the lessons booked that month. A group class is enrolled in // once, as one schedule, so its monthly figure is quoted as it stands. function isPerLessonMonthly(billingMode, kind) { return 'monthly' === billingMode && 'group_class' !== kind; } // "50.00 CAD" — amount then currency code, the format used throughout the // ledger, receipts and payment notices. function money(amount, currency) { return `${(Number(amount) || 0).toFixed(2)} ${String(currency || '')}`.trim(); } // The studio's HST rate as a percentage, frozen onto every payment at // booking time (comped students are the one exception — they are not taxed). function taxRate() { return Number(usScheduler.taxRate) || 0; } // Tax on a pre-tax amount, rounded the same way PaymentService does. function tax(amount) { return Math.round((Number(amount) || 0) * taxRate()) / 100; } function total(amount) { return (Number(amount) || 0) + tax(amount); } // "50.00 CAD at booking" / "50.00 CAD per lesson monthly" / "Free" — the // catalogue label, always carrying the cadence so a price is never shown // without saying when it is due. function priceLabel(offering) { const price = Number(offering.price) || 0; if (price <= 0) { return 'Free'; } const billingMode = mode(offering.billing_mode); const perLesson = isPerLessonMonthly(billingMode, offering.kind) ? 'per lesson ' : ''; return `${money(price, offering.currency)} ${perLesson}${CADENCE[billingMode]}`; } // The price block shown on a booking/enrolment form, followed by the // agreement the student must tick to confirm they will pay it. A free // offering has nothing to agree to, so it renders nothing at all. // // opts: { price, currency, billing_mode, kind, occurrences } // `occurrences` is how many lessons a one-time price is charged for in this // one registration (a weekly reservation claims several at once); it is // ignored for the other modes, whose price is charged per period regardless. function summaryHtml(opts) { const price = Number(opts.price) || 0; if (price <= 0) { return ''; } const billingMode = mode(opts.billing_mode); const currency = opts.currency; const each = total(price); const count = 'one_time' === billingMode ? Math.max(1, Number(opts.occurrences) || 1) : 1; const taxLine = taxRate() > 0 ? `

${escHtml(`Plus ${taxRate()}% HST — ${money(each, currency)}${count > 1 ? ' per lesson' : ''}.`)}

` : ''; return `

Price

${escHtml(money(price, currency))} ${escHtml(cadenceLabel(billingMode, opts.kind))}

${taxLine}

${escHtml(count > 1 ? 'Charged once, when you book — for every week reserved.' : CADENCE_NOTE[billingMode])}

`; } // The cadence as it reads beside an amount: a private lesson billed monthly // adds "per lesson", since the month's charge is that fee times the lessons // it covers. function cadenceLabel(billingMode, kind) { return isPerLessonMonthly(billingMode, kind) ? `per lesson ${CADENCE[billingMode]}` : CADENCE[billingMode]; } // What the student is ticking: the amount actually billed (tax included), // and when. A weekly reservation is charged per lesson for every week it // claims, and the claim can come up short when another student takes one of // the times first — so its total is stated as a ceiling, never a promise. function agreeText(each, currency, billingMode, count, kind) { if (RECURRING.indexOf(billingMode) !== -1) { // A monthly group class is enrolled in once and quoted as it stands; // everything else recurring is a per-lesson fee. return 'monthly' === billingMode && !isPerLessonMonthly(billingMode, kind) ? `I agree to pay ${money(each, currency)} monthly.` : `I agree to pay ${money(each, currency)} per lesson, billed ${CADENCE[billingMode]}.`; } if (count > 1) { return `I agree to pay ${money(each, currency)} per lesson at booking — ` + `up to ${count} lessons, ${money(each * count, currency)} in total.`; } return `I agree to pay ${money(each, currency)} ${CADENCE[billingMode]}.`; } // Whether the payment agreement has been ticked. A form without one (a free // offering) has nothing outstanding, so it counts as agreed. function agreed(root) { const box = root.querySelector('.us-price-accept'); return !box || box.checked; } // Shared by the booking and group-class flows so a price reads the same // wherever a student meets it. window.usPricing = { priceLabel, summaryHtml, agreed, AGREE_REQUIRED: 'Please confirm you agree to pay the amount shown.', }; }());