// Start polling to find the error container const timer1 = setInterval(function () { const errorsDiv = document.querySelector("#view-state2 div[error-list]"); if (!errorsDiv) return; const scope = angular.element(errorsDiv).scope(); if (!scope) return; clearInterval(timer1); // stop once it's found // --- Cardholder name: show error when purchase is clicked and field is empty --- const timerCardholder = setInterval(function () { const cardholderBox = document.getElementById("cardholder-name-box"); if (!cardholderBox) return; clearInterval(timerCardholder); const cardholderInput = document.getElementById("cardholder-name-input") || cardholderBox.querySelector(".cardholder-name-input"); if (!cardholderInput) return; document.addEventListener("click", function (e) { var t = e.target; while (t && t !== document) { if (t.classList && t.classList.contains("complete-purchase-button")) { if (cardholderBox.offsetParent === null) return; // cardholder section not visible var val = (cardholderInput.value || "").trim(); if (val === "") { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); cardholderBox.classList.add("error-cardholder-name"); cardholderInput.focus(); } return; } t = t.parentElement; } }, true); cardholderInput.addEventListener("input", function () { if ((cardholderInput.value || "").trim() !== "") { cardholderBox.classList.remove("error-cardholder-name"); } }); }, 300); // Now start polling for active errors const timer2 = setInterval(() => { const errors = scope.errors; if (!errors || !Array.isArray(errors) || errors.length === 0) return; clearInterval(timer2); // stop polling after errors are found // Check for postal code error (no "field" property or type === "global") const hasPostalError = errors.some(e => !e.field && e.message && e.message.toLowerCase().includes("postal")); // Check for promo code error (field === "promocode") const hasPromoError = errors.some(e => e.field === "promocode"); // --- Postal Code Box --- const postalDiv = document.getElementById("checkout-billing-postal-code"); if (postalDiv) { const postalInput = postalDiv.querySelector("input"); if (hasPostalError && postalInput) { const updatePostalBorder = () => { if (!postalInput.value) { postalDiv.classList.add("error-postal-code"); } else { postalDiv.classList.remove("error-postal-code"); } }; postalInput.addEventListener("input", updatePostalBorder); updatePostalBorder(); // initial call } } // --- Promo Code Box --- const promoDiv = document.getElementById("promo-code-input-wrapper"); if (promoDiv && hasPromoError) { promoDiv.classList.add("error-postal-code"); } }, 300); // check every 300ms }, 300); // initial container check every 300ms

{{terminalErrorHeading}}

An error has occurred

{{terminalError}}

{{user.displayName || user.email}}

const allowed = ['United States', 'Canada'] setInterval(() => { const selectedCountry = document .querySelector('.billing-country-group-column .tp-dropdown__select .ng-binding:not(.ng-hide)') ?.textContent?.trim(); document.querySelectorAll('.tp-dropdown__scrollable-list').forEach((list) => { const signature = list.innerText; if (list.dataset.filteredSignature !== signature) { list.querySelectorAll('.tp-dropdown__link').forEach((link) => { const name = link.querySelector('span')?.textContent?.trim(); if (!allowed.includes(name)) { link.remove(); } }); } if (selectedCountry && allowed.includes(selectedCountry)) { const selectedLink = Array.from(list.querySelectorAll('.tp-dropdown__link')).find((link) => { const name = link.querySelector('span')?.textContent?.trim(); return name === selectedCountry; }); if (selectedLink && list.firstElementChild !== selectedLink) { list.prepend(selectedLink); } } list.dataset.filteredSignature = list.innerText; /* first visible country link: bold (700) */ list.querySelectorAll('.tp-dropdown__link').forEach((link, i) => { link.style.fontWeight = i === 0 ? '700' : ''; }); }); }, 50); // CUSTOM DATA script ------------------------------------------------------------ (function () { const log = (...a) => console.log('[Summary-dbg]', new Date().toISOString(), ...a); function toNumber(value) { if (value === null || value === undefined) return 0; if (typeof value === 'number') return value; const n = parseFloat(String(value).replace(/[^\d.-]/g, '')); return isNaN(n) ? 0 : n; } function formatMoney(value, currency) { const prefix = currency === 'CAD' ? 'CAN$' : '$'; return prefix + toNumber(value).toFixed(2); } // Existing behavior (fallback if billingPlanTable missing) function buildPlanPrice(input) { const raw = input.promotionBillingPlan ? (input.originalChargeAmount || (input.promotionBillingPlan[0] && input.promotionBillingPlan[0].priceValue) || '') : ((input.firstRealPriceWithTax || '').split(' + ')[0] || ''); return formatMoney(raw, input.chargeCurrency); } // Base plan price: exclude tax and promo discounts. // Prefers selectedTerm.originalBillingPlan when present. function buildBasePlanPrice(selectedTerm, input) { const obp = selectedTerm && selectedTerm.originalBillingPlan; const obpRows = (obp && obp.billingPlanTable) || []; if (obpRows.length) { const r = obpRows[0]; const currency = r.currency || (input && input.chargeCurrency); return formatMoney(r.priceValue, currency); } const rows = (selectedTerm && selectedTerm.billingPlanTable) || []; if (rows.length) { const currency = rows[0].currency || (input && input.chargeCurrency); // Term-level price (e.g. list/selected anchor) — not the renewal row after a trial. // Example: annual trial shows 69.99 today then 124.99/yr thereafter; priceValue is 69.99. if (selectedTerm.priceValue !== undefined && selectedTerm.priceValue !== null) { return formatMoney(selectedTerm.priceValue, currency); } return formatMoney(rows[0].priceValue, currency); } return buildPlanPrice(input); } function unitAdverb(unit) { return unit === 'year' ? 'yearly' : 'monthly'; } function unitPer(unit) { return unit === 'year' ? 'per year' : 'per month'; } function normalizeAccessDuration(duration) { if (!duration) return ''; return String(duration).replace(' of access', ' access'); } // New: build the plan summary string from selectedTerm.billingPlanTable function buildPlanSummary(selectedTerm, input) { const rows = (selectedTerm && selectedTerm.billingPlanTable) || []; if (!rows.length) return buildPlanPrice(input); const currency = rows[0].currency || (input && input.chargeCurrency); // If there's only one element: // "$8.99 charged monthly" (or yearly for annual) if (rows.length === 1) { const r = rows[0]; const unit = r.paymentIntervalUnit || r.period; const amt = formatMoney(r.priceValue, currency); return `${amt} charged ${unitAdverb(unit)}`; } // If there're more elements: // "$4.99 per month for 6 months access, then $8.99 per month thereafter" const first = rows[0]; const last = rows[rows.length - 1]; const firstUnit = first.paymentIntervalUnit || first.period; const lastUnit = last.paymentIntervalUnit || last.period; const firstAmt = formatMoney(first.priceValue, currency); const lastAmt = formatMoney(last.priceValue, currency); const duration = normalizeAccessDuration(first.duration); const durationPart = duration ? ` for ${duration}` : ''; return `${firstAmt} ${unitPer(firstUnit)}${durationPart}, then ${lastAmt} ${unitPer(lastUnit)} thereafter`; } // New: total line should reflect computed total, but follow desired schedule format function buildTotalSummary(totalFormatted, selectedTerm, input) { const rows = (selectedTerm && selectedTerm.billingPlanTable) || []; if (!rows.length) return totalFormatted; // If there's only one element in billingPlanTable: // "CAN$ 12.99 charged monthly" (or yearly) if (rows.length === 1) { const r = rows[0]; const unit = r.paymentIntervalUnit || r.period; return `${totalFormatted} charged ${unitAdverb(unit)}`; } // Multi-row: use priceAndTax (actual charged amounts including tax) const first = rows[0]; const last = rows[rows.length - 1]; const currency = first.currency || (input && input.chargeCurrency); const firstUnit = first.paymentIntervalUnit || first.period; const lastUnit = last.paymentIntervalUnit || last.period; const firstAmt = formatMoney(first.priceAndTax, currency); const lastAmt = formatMoney(last.priceAndTax, currency); const duration = normalizeAccessDuration(first.duration); const durationPart = duration ? ` for ${duration}` : ''; return `${firstAmt} ${unitPer(firstUnit)}${durationPart}, then ${lastAmt} ${unitPer(lastUnit)} thereafter`; } function buildTaxPrice(input) { if (input.firstRealPriceWithTax && input.firstRealPriceWithTax.includes(' + ')) { const taxPart = input.firstRealPriceWithTax.split(' + ')[1] || ''; return formatMoney(taxPart.replace(' tax', ''), input.chargeCurrency); } if (input.promotionBillingPlan && input.promotionBillingPlan[0] && input.promotionBillingPlan[0].taxValue !== undefined) { return formatMoney(input.promotionBillingPlan[0].taxValue, input.chargeCurrency); } return formatMoney(0, input.chargeCurrency); } function buildDiscountPrice(selectedTerm, input) { if (input.promotionBillingPlan && input.promotionBillingPlan[0]) { const row = input.promotionBillingPlan[0]; // Discount must be pre-tax to satisfy: planPrice + tax - discount = total const obp = selectedTerm && selectedTerm.originalBillingPlan; const obpRows = (obp && obp.billingPlanTable) || []; const originalPreTax = obpRows.length ? toNumber(obpRows[0].priceValue) : toNumber(row.originalPriceValue) / (row.taxRate ? (1 + toNumber(row.taxRate) / 100) : 1); const discount = originalPreTax - toNumber(row.priceValue); return formatMoney(Math.max(discount, 0), input.chargeCurrency); } return formatMoney(0, input.chargeCurrency); } function buildTotalPrice(input) { if (input.chargeAmountAndTax !== undefined && input.chargeAmountAndTax !== null) { return formatMoney(input.chargeAmountAndTax, input.chargeCurrency); } if (input.chargeDisplayAmountAndTax) { return formatMoney(input.chargeDisplayAmountAndTax, input.chargeCurrency); } if (input.chargeDisplayAmount) { return formatMoney(input.chargeDisplayAmount, input.chargeCurrency); } if (input.chargeAmount !== undefined && input.chargeAmount !== null) { return formatMoney(input.chargeAmount, input.chargeCurrency); } return formatMoney(0, input.chargeCurrency); } function setTextById(id, value) { const el = document.getElementById(id); if (!el) return; if (el.textContent !== value) { el.textContent = value; } } setInterval(() => { const host = document.querySelector('[view="state2"]'); if (!host || !window.angular) return; const scope = angular.element(host).scope(); if (!scope || !scope.input) return; const input = scope.input; // Plan Price should be base price (no tax/discount) const plan = buildBasePlanPrice(scope.selectedTerm, input); const tax = buildTaxPrice(input); const discount = buildDiscountPrice(scope.selectedTerm, input); const total = buildTotalPrice(input); setTextById('summaryPlanPrice', plan); setTextById('summaryTaxPrice', tax); setTextById('summaryDiscountPrice', discount); // Total follows desired schedule format without duplication const totalSummary = buildTotalSummary(total, scope.selectedTerm, input); setTextById('summaryTotalPrice', totalSummary); log('updated', { plan, tax, discount, total, totalSummary }); }, 300); })();
Ribbon
var angularScope = angular.element("#getting-scope").scope(); function navigateToSubscription(termIdParam) { parent.window.location.href = "https://www.bbc.com/subscribe?aid=" + angularScope.params.aid + "&redirect_uri=" + encodeURIComponent(angularScope.params.url) + "&_ptid=" + encodeURIComponent(angularScope.params.trackingId) }