1(function (root, factory) { 2 if (typeof module === 'object' && module.exports) { 3 module.exports = factory(); 4 } else { 5 root.PricingCalculator = factory(); 6 } 7}(typeof globalThis !== 'undefined' ? globalThis : this, function () { 8 9 function getAnnualDiscountPercent(plan) { 10 if (plan && plan.annual_discount_percent != null) { 11 return plan.annual_discount_percent; 12 } 13 return 20; 14 } 15 16 function calculatePlanPrice(plan, quantity, interval, currency) { 17 currency = currency || 'brl'; 18 var total = 0; 19 var perUnit = 0; 20 var perMonth = 0; 21 22 if (plan.is_legacy && interval === 'year') { 23 return { 24 total: null, 25 perUnit: null 26 }; 27 } 28 29 if (plan.is_free) { 30 return { 31 total: total, 32 perUnit: perUnit 33 }; 34 } 35 36 if (plan.users_limit && quantity > plan.users_limit) { 37 return { 38 total: null 39 }; 40 } 41 42 if (plan.price.charge_type === 'fixed') { 43 total = plan.price[interval]['unit_amount'][currency]; 44 perMonth = total; 45 46 if (interval === 'year') { 47 perMonth /= 12; 48 } 49 50 return { 51 total: total, 52 perMonth: perMonth 53 }; 54 } 55 56 if (!quantity) { 57 quantity = 1; 58 } 59 60 if (plan.minimun_users && quantity < plan.minimun_users) { 61 quantity = plan.minimun_users; 62 } 63 64 if (quantity > 10000) { 65 return { 66 total: null, 67 perUnit: null 68 }; 69 } 70 71 var tiers = plan.price[interval]; 72 var previousLimit = 0; 73 74 tiers.forEach(function (tier) { 75 if (quantity <= previousLimit) { 76 return; 77 } 78 79 var limit = tier.up_to;
80 var unitAmount = tier.unit_amount[currency]; 81 var quantityInThisTier = Math.min(quantity, limit) - previousLimit; 82 83 total += quantityInThisTier * unitAmount; 84 previousLimit = limit; 85 }); 86 87 perUnit = total / quantity; 88 perMonth = total; 89 90 if (interval === 'year') { 91 perUnit /= 12; 92 perMonth /= 12; 93 } 94 95 return { 96 total: total, 97 perUnit: perUnit, 98 perMonth: perMonth 99 }; 100 } 101 102 function calculateAnnualSavings(plan, quantity, currency) { 103 if (!plan || plan.is_free || !plan.price) { 104 return null; 105 } 106 107 var monthPrice = calculatePlanPrice(plan, quantity, 'month', currency); 108 var yearPrice = calculatePlanPrice(plan, quantity, 'year', currency); 109 var monthTotal = monthPrice.total; 110 var yearTotal = yearPrice.total; 111 112 if (monthTotal === null || monthTotal === undefined) { 113 return null; 114 } 115 116 if (yearTotal === null || yearTotal === undefined) { 117 return null; 118 } 119 120 var monthlyBilledForTwelveMonths = monthTotal * 12; 121 var annualSavings = monthlyBilledForTwelveMonths - yearTotal; 122 123 if (annualSavings <= 0) { 124 return null; 125 } 126 127 return annualSavings; 128 } 129 130 function buildPlanPriceTiers(plan, interval, currency) { 131 if (!plan || plan.is_free || !plan.price || plan.price.charge_type !== 'per_user') { 132 return []; 133 } 134 135 if (plan.is_legacy && interval === 'year') { 136 return []; 137 } 138 139 var tiers = plan.price[interval]; 140 141 if (!tiers || tiers.length <= 1) { 142 return []; 143 } 144 145 currency = currency || 'brl'; 146 147 var previousLimit = 0; 148 var priceTiers = []; 149 150 tiers.forEach(function (tier) { 151 var upTo = tier.up_to;
152 var unitAmount = tier.unit_amount[currency]; 153 154 if (unitAmount === undefined || unitAmount === null) { 155 return; 156 } 157 158 var displayUnitAmount = unitAmount; 159 160 if (interval === 'year') { 161 displayUnitAmount = unitAmount / 12; 162 } 163 164 priceTiers.push({ 165 from: previousLimit + 1, 166 to: upTo, 167 unitAmount: displayUnitAmount 168 }); 169 170 previousLimit = upTo; 171 }); 172 173 return priceTiers; 174 } 175 176 return { 177 getAnnualDiscountPercent: getAnnualDiscountPercent, 178 calculatePlanPrice: calculatePlanPrice, 179 calculateAnnualSavings: calculateAnnualSavings, 180 buildPlanPriceTiers: buildPlanPriceTiers 181 }; 182})); 183 184(function (root, factory) { 185 if (typeof module === 'object' && module.exports) { 186 module.exports = factory(); 187 } else { 188 root.PricingFormat = factory(); 189 } 190}(typeof globalThis !== 'undefined' ? globalThis : this, function () { 191 192 var CURRENCY_SYMBOLS = { 193 brl: 'R$', 194 usd: '$', 195 eur: 'â¬', 196 gbp: '£', 197 chf: 'CHF' 198 }; 199 200 function formatAmount(amount, alwaysShowDecimal) { 201 if (amount === null || amount === undefined || isNaN(amount)) { 202 return '0'; 203 } 204 205 var decimalPlaces = alwaysShowDecimal ? 2 : (amount % 1 === 0 ? 0 : 2); 206 207 return new Intl.NumberFormat('pt-BR', { 208 minimumFractionDigits: decimalPlaces, 209 maximumFractionDigits: decimalPlaces 210 }).format(amount); 211 } 212 213 function currencySymbol(currency) { 214 if (!currency) { 215 return ''; 216 } 217 var key = currency.toLowerCase(); 218 return CURRENCY_SYMBOLS[key] || key.toUpperCase(); 219 } 220 221 function detectCurrency() { 222 var language = (navigator.language || '').toLowerCase(); 223 var tz = Intl.DateTimeFormat().resolvedOptions().timeZone; 224 225 if (language.startsWith('pt-br') || tz.startsWith('America/Sao_Paulo')) { 226 return 'brl'; 227 } 228 229 if (language.startsWith('de-ch') || language.startsWith('fr-ch') || language.startsWith('it-ch') || tz === 'Europe/Zurich') { 230 return 'chf'; 231 } 232 233 var euroLocales = [ 234 'pt-pt', 'es-es', 'fr-fr', 'de-de', 'it-it', 'nl-nl', 235 'fi-fi', 'ie-ie', 'el-gr', 'sk-sk', 'si-si', 'lt-lt', 236 'lv-lv', 'ee-ee', 'mt-mt', 'lu-lu', 'cy-cy', 'at-at' 237 ]; 238 239 if (euroLocales.indexOf(language) !== -1 || tz.indexOf('Europe/') === 0) { 240 return 'eur'; 241 } 242 243 return 'usd'; 244 } 245 246 return { 247 formatAmount: formatAmount, 248 currencySymbol: currencySymbol, 249 detectCurrency: detectCurrency, 250 CURRENCY_SYMBOLS: CURRENCY_SYMBOLS 251 }; 252})); 253 254(function () { 255 'use strict'; 256 257 var Calculator = window.PricingCalculator; 258 var Format = window.PricingFormat; 259 260 if (!Calculator || !Format) { 261 return; 262 } 263 264 function readPricingData(root) { 265 var dataNode = root.querySelector('#rabbiit-pricing-data'); 266 267 if (!dataNode) { 268 return null; 269 } 270 271 try { 272 return JSON.parse(dataNode.textContent); 273 } catch (error) { 274 console.error('Rabbiit pricing widget: invalid pricing data', error); 275 return null; 276 } 277 } 278 279 function findPlanById(plans, planId) { 280 for (var i = 0; i < plans.length; i++) { 281 if (plans[i].id === planId) { 282 return plans[i]; 283 } 284 } 285 return null; 286 } 287 288 function interpolate(template, params) { 289 if (!template || !params) { 290 return template; 291 } 292 293 return template.replace(/\{\{\s*(\w+)\s*\}\}/g, function (_match, key) { 294 if (params[key] === undefined || params[key] === null) { 295 return ''; 296 } 297 return String(params[key]); 298 }); 299 } 300 301 function buildQuantityDescription(plan, quantity, strings) { 302 var html = strings.forLabel || 'para '; 303 304 if (plan.price.charge_type === 'fixed') { 305 html += plan.minimun_users + (strings.usersLabelSuffix || ' usuários'); 306 307 if (quantity > plan.minimun_users) { 308 html += strings.additionalLabel || ''; 309 } 310 } else if (plan.minimun_users && quantity < plan.minimun_users) { 311 html += plan.minimun_users + (strings.usersLabelSuffix || ' usuários'); 312 } else { 313 html += quantity + (quantity > 1 314 ? (strings.usersLabelSuffix || ' usuários') 315 : (strings.userLabelSuffix || ' usuário')); 316 } 317 318 return html; 319 } 320 321 function setNodeHidden(node, hidden) { 322 if (!node) { 323 return; 324 } 325 326 node.hidden = hidden; 327 } 328 329 function updatePlanCard(card, plan, state, strings) { 330 var priceCalculated = Calculator.calculatePlanPrice( 331 plan, 332 state.quantity, 333 state.interval, 334 state.currency 335 ); 336 var symbol = Format.currencySymbol(state.currency); 337 var priceContainer = card.querySelector('.directive-upgrade-plan-price--container'); 338 var unavailableNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price-disabled'); 339 var priceNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price'); 340 var afterPriceNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__after-price'); 341 var unitValueNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price-unit-value strong'); 342 var currencySymbolNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price-unit .currency'); 343 var totalNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price-total'); 344 var intervalNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price-interval'); 345 var isUnavailable = priceCalculated.total === null; 346 347 setNodeHidden(unavailableNode, !isUnavailable); 348 setNodeHidden(priceNode, isUnavailable); 349 setNodeHidden(afterPriceNode, isUnavailable); 350 351 if (isUnavailable) { 352 if (unavailableNode) { 353 if (plan.users_limit) { 354 unavailableNode.textContent = interpolate(strings.maxUsers || '', { limit: plan.users_limit }); 355 } else { 356 unavailableNode.textContent = strings.unavailable || ''; 357 } 358 } 359 return; 360 } 361 362 var unitValue = '0'; 363 364 if (!plan.is_free) { 365 if (plan.price.charge_type === 'fixed') {
366 unitValue = Format.formatAmount(priceCalculated.perMonth, false); 367 } else { 368 unitValue = Format.formatAmount(priceCalculated.perUnit, false); 369 } 370 } 371 372 if (unitValueNode) { 373 unitValueNode.textContent = unitValue; 374 } 375 376 if (currencySymbolNode) { 377 currencySymbolNode.textContent = symbol; 378 } 379 380 setNodeHidden(totalNode, plan.is_free); 381 setNodeHidden(intervalNode, plan.is_free); 382 383 if (!plan.is_free) { 384 if (totalNode) { 385 var totalTemplate = strings.totalPerMonth || 'Total {{symbol}}{{amount}} /mês'; 386 var totalText = interpolate(totalTemplate, { 387 symbol: symbol, 388 amount: Format.formatAmount(priceCalculated.perMonth, false) 389 }); 390 var quantityText = buildQuantityDescription(plan, state.quantity, strings); 391 totalNode.innerHTML = totalText + ' ' + quantityText; 392 } 393 394 if (intervalNode) { 395 intervalNode.textContent = state.interval === 'month' 396 ? (strings.billedMonthly || 'Cobrados mensalmente') 397 : (strings.billedYearly || 'Cobrados anualmente'); 398 } 399 } 400 } 401 402 function bindIntervalButtons(root, state, data, strings) { 403 var buttons = root.querySelectorAll('[data-interval]'); 404 405 buttons.forEach(function (button) { 406 button.addEventListener('click', function (event) { 407 event.preventDefault(); 408 409 var interval = button.getAttribute('data-interval'); 410 411 if (!interval || interval === state.interval) { 412 return; 413 } 414 415 state.interval = interval; 416 417 buttons.forEach(function (item) { 418 item.classList.toggle('active', item.getAttribute('data-interval') === interval); 419 }); 420 421 refreshPlans(root, state, data, strings); 422 }); 423 }); 424 } 425 426 function bindQuantityStepper(root, state, data, strings) { 427 var input = root.querySelector('.directive-input-number-stepper__input'); 428 var stepButtons = root.querySelectorAll('.directive-input-number-stepper__btn'); 429 430 if (!input) { 431 return; 432 } 433 434 var applyQuantity = function (normalizeInput) { 435 refreshPlans(root, state, data, strings, normalizeInput); 436 }; 437 438 input.addEventListener('input', function () { 439 applyQuantity(false); 440 }); 441 input.addEventListener('change', function () { 442 applyQuantity(true); 443 }); 444 input.addEventListener('blur', function () { 445 applyQuantity(true); 446 }); 447 input.addEventListener('keydown', function (event) { 448 if (event.key === 'Enter') { 449 applyQuantity(true); 450 } 451 }); 452 453 stepButtons.forEach(function (button) { 454 button.addEventListener('click', function () { 455 var step = parseInt(button.getAttribute('data-step'), 10); 456 var quantity = parseInt(input.value, 10) || 1; 457 quantity += step; 458 459 if (quantity < 1) { 460 quantity = 1; 461 } 462 463 input.value = String(quantity); 464 applyQuantity(true); 465 }); 466 }); 467 } 468 469 function syncQuantityFromInput(root, state, normalizeInput) { 470 var shouldNormalizeInput = normalizeInput !== false; 471 var input = root.querySelector('.directive-input-number-stepper__input'); 472 473 if (!input) { 474 return; 475 } 476 477 var rawValue = input.value.trim(); 478 479 if (rawValue === '') { 480 if (shouldNormalizeInput) { 481 state.quantity = 1; 482 input.value = '1'; 483 } 484 return; 485 } 486 487 var quantity = parseInt(rawValue, 10); 488 489 if (isNaN(quantity) || quantity < 1) { 490 if (shouldNormalizeInput) { 491 state.quantity = 1; 492 input.value = '1'; 493 } 494 return; 495 } 496 497 if (quantity > 10000) { 498 quantity = 10000; 499 } 500 501 state.quantity = quantity; 502 503 if (shouldNormalizeInput) {
504 input.value = String(quantity); 505 } 506 } 507 508 function refreshPlans(root, state, data, strings, normalizeInput) { 509 syncQuantityFromInput(root, state, normalizeInput); 510 var cards = root.querySelectorAll('[data-plan-id]'); 511 512 cards.forEach(function (card) { 513 var planId = card.getAttribute('data-plan-id'); 514 var plan = findPlanById(data.plans, planId); 515 516 if (plan) { 517 updatePlanCard(card, plan, state, strings); 518 } 519 }); 520 } 521 522 function buildStrings(data) { 523 var strings = data.strings || {}; 524 525 return { 526 forLabel: strings.forLabel || 'para ', 527 usersLabelSuffix: strings.usersLabelSuffix || ' usuários', 528 userLabelSuffix: strings.userLabelSuffix || ' usuário', 529 additionalLabel: strings.additionalLabel || '', 530 totalPerMonth: strings.totalPerMonth || 'Total {{symbol}}{{amount}} /mês', 531 billedMonthly: strings.billedMonthly || 'Cobrados mensalmente', 532 billedYearly: strings.billedYearly || 'Cobrados anualmente', 533 unavailable: strings.unavailable || 'IndisponÃvel', 534 maxUsers: strings.maxUsers || 'Máximo de {{limit}} usuários' 535 }; 536 } 537 538 function initRoot(root) { 539 var data = readPricingData(root); 540 541 if (!data || !data.plans) { 542 return; 543 } 544 545 var state = { 546 interval: (data.defaults && data.defaults.interval) || 'year', 547 quantity: (data.defaults && data.defaults.quantity) || 2, 548 currency: Format.detectCurrency() 549 }; 550 var strings = buildStrings(data); 551 552 bindIntervalButtons(root, state, data, strings); 553 bindQuantityStepper(root, state, data, strings); 554 refreshPlans(root, state, data, strings); 555 } 556 557 function init() { 558 var roots = document.querySelectorAll('[data-rabbiit-pricing], #rabbiit-pricing'); 559 560 roots.forEach(function (root) { 561 initRoot(root); 562 }); 563 } 564 565 if (document.readyState === 'loading') { 566 document.addEventListener('DOMContentLoaded', init); 567 } else { 568 init(); 569 } 570}());
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.