1jQuery(function($){ 2 'use strict'; 3 4 function triggerPayment(planId, gateway, btn) { 5 if (!window.DMP_CFG || !DMP_CFG.logged_in) { 6 var currentUrl = window.location.href; 7 var loginUrl = (DMP_CFG && DMP_CFG.login_url) ? DMP_CFG.login_url : '/wp-login.php'; 8 window.location.href = loginUrl + (loginUrl.indexOf('?') !== -1 ? '&' : '?') + 'redirect_to=' + encodeURIComponent(currentUrl); 9 return; 10 } 11 12 var originalText = ''; 13 if (btn && btn.length) { 14 originalText = btn.is('input') ? btn.val() : btn.text(); 15 btn.prop('disabled', true); 16 if (btn.is('input')) btn.val('Processing...'); else btn.text('Processing...'); 17 } 18 19 var resetBtn = function() { 20 if (btn && btn.length) { 21 btn.prop('disabled', false); 22 if (btn.is('input')) btn.val(originalText); else btn.text(originalText); 23 } 24 }; 25 26 $.post(DMP_CFG.ajax, { 27 action: 'dmp_start_payment', 28 nonce: DMP_CFG.nonce, 29 plan_id: planId, 30 gateway: gateway || '' 31 }, function(r) { 32 if (!r.success) { 33 alert(r.data && r.data.message ? r.data.message : 'Payment initiation failed.'); 34 resetBtn(); 35 return; 36 } 37 if (r.data.provider === 'phonepe') { 38 window.location.href = r.data.redirect_url; 39 return; 40 } 41 if (r.data.provider === 'razorpay') { 42 var openRzp = function() { 43 var rzp = new Razorpay({ 44 key: r.data.key_id, 45 amount: r.data.amount, 46 currency: r.data.currency, 47 name: r.data.name, 48 description: r.data.description, 49 order_id: r.data.order_id, 50 handler: function(resp) { 51 $.post(DMP_CFG.ajax, { 52 action: 'dmp_verify_razorpay', 53 nonce: DMP_CFG.nonce, 54 razorpay_payment_id: resp.razorpay_payment_id, 55 razorpay_order_id: resp.razorpay_order_id, 56 razorpay_signature: resp.razorpay_signature 57 }, function(vr) { 58 if (vr.success) { 59 // Broadcast cross-tab sync event 60 try { 61 localStorage.setItem('dmp_membership_synced', Date.now().toString()); 62 } catch(e){} 63 64 if (vr.data.redirect) { 65 window.location.href = vr.data.redirect; 66 } else { 67 syncMembershipState(); 68 resetBtn(); 69 } 70 } else { 71 alert(vr.data && vr.data.message ? vr.data.message : 'Payment verification failed.'); 72 resetBtn(); 73 } 74 }).fail(function() { 75 alert('Verification request failed. Please check your dashboard.'); 76 resetBtn(); 77 }); 78 }, 79 modal: { 80 ondismiss: function() { 81 resetBtn(); 82 } 83 } 84 }); 85 rzp.open(); 86 }; 87 88 if (window.Razorpay) { 89 openRzp(); 90 } else { 91 $.getScript('https://checkout.razorpay.com/v1/checkout.js').done(openRzp).fail(function() { 92 alert('Could not load Razorpay checkout SDK.'); 93 resetBtn(); 94 }); 95 } 96 } 97 }).fail(function() { 98 alert('Network error connecting to payment gateway.'); 99 resetBtn(); 100 }); 101 } 102 103 // Click handler for plan shortcode buttons 104 $(document).on('click', '.dmp-pay', function(e) { 105 e.preventDefault(); 106 var b = $(this); 107 triggerPayment(b.data('plan'), b.data('gateway'), b); 108 }); 109 110 // Click handler for Elementor buttons or links targeting dmp_pay / dmp_checkout 111 $(document).on('click', 'a[href*="dmp_pay="], a[href*="dmp_checkout="], a[href*="dmp_plan="]', function(e) { 112 var href = $(this).attr('href'); 113 if (!href) return; 114 var match = href.match(/[?&](?:dmp_pay|dmp_checkout|dmp_plan)=([^&#]+)/); 115 if (match && match[1]) { 116 e.preventDefault(); 117 var gwMatch = href.match(/[?&]gateway=([^&#]+)/); 118 var gw = gwMatch ? gwMatch[1] : ''; 119 triggerPayment(match[1], gw, $(this)); 120 } 121 }); 122 123 /* ---------------------------------------------------- 124 * REAL-TIME COUNTDOWN TIMER (Year, Month, Day, Hour, Min, Sec) 125 * Uses authoritative server target timestamp with client offset. 126 * ---------------------------------------------------- */ 127 function padZero(num) { 128 return num < 10 ? '0' + num : '' + num; 129 } 130 131 var countdownIntervals = []; 132 133 function clearAllCountdownIntervals() { 134 for (var i = 0; i < countdownIntervals.length; i++) { 135 clearInterval(countdownIntervals[i]); 136 } 137 countdownIntervals = []; 138 } 139 140 function padZero(n) { 141 var num = parseInt(n, 10); 142 if (isNaN(num)) return n; 143 return (num < 10 && num >= 0) ? '0' + num : '' + num; 144 } 145 146 function initCountdowns() { 147 clearAllCountdownIntervals(); 148 149 var timers = $('[data-dmp-countdown]'); 150 if (!timers.length) return; 151 152 var clientInitTime = Date.now(); 153 154 timers.each(function() { 155 var wrapper = $(this); 156 var targetTs = parseInt(wrapper.attr('data-dmp-countdown'), 10); 157 var serverNowTs = parseInt(wrapper.attr('data-dmp-now'), 10) || Math.floor(Date.now() / 1000); 158 var expiredText = wrapper.attr('data-expired-text') || 'Membership Expired'; 159 160 if (!targetTs || isNaN(targetTs)) return; 161 162 var hasYears = wrapper.find('[data-dmp-unit="years"]').length > 0; 163 var hasMonths = wrapper.find('[data-dmp-unit="months"]').length > 0; 164 165 function updateTimer() { 166 var elapsedSeconds = Math.floor((Date.now() - clientInitTime) / 1000); 167 var currentServerTs = serverNowTs + elapsedSeconds; 168 var remaining = targetTs - currentServerTs; 169 170 if (remaining <= 0) { 171 wrapper.find('[data-dmp-unit="years"]').text('00'); 172 wrapper.find('[data-dmp-unit="months"]').text('00'); 173 wrapper.find('[data-dmp-unit="days"]').text('00'); 174 wrapper.find('[data-dmp-unit="hours"]').text('00'); 175 wrapper.find('[data-dmp-unit="minutes"]').text('00'); 176 wrapper.find('[data-dmp-unit="seconds"]').text('00'); 177 wrapper.addClass('dmp-countdown-ended'); 178 wrapper.find('.dmp-countdown-status-badge').text('EXPIRED').css({ 179 'border-color':'#ef4444','color':'#f87171','background':'rgba(239,68,68,.12)' 180 }); 181 // Ask server once if renewed/extended 182 if (!wrapper.data('rechecked')) { 183 wrapper.data('rechecked', true); 184 setTimeout(syncMembershipState, 2000); 185 } 186 return; 187 } 188 189 var DAY_SEC = 86400; 190 var HOUR_SEC = 3600; 191 var MIN_SEC = 60; 192 var MONTH_SEC = 30.4375 * DAY_SEC; 193 var YEAR_SEC = 365.25 * DAY_SEC; 194 195 var years = 0, months = 0, days = 0, hours = 0, minutes = 0, seconds = 0; 196 var rem = remaining; 197 198 if (hasYears) { 199 years = Math.floor(rem / YEAR_SEC); 200 rem = rem % YEAR_SEC; 201 } 202 203 if (hasMonths) { 204 months = Math.floor(rem / MONTH_SEC); 205 rem = rem % MONTH_SEC; 206 } 207 208 days = Math.floor(rem / DAY_SEC); 209 rem = rem % DAY_SEC; 210 211 hours = Math.floor(rem / HOUR_SEC); 212 rem = rem % HOUR_SEC; 213 214 minutes = Math.floor(rem / MIN_SEC); 215 seconds = Math.floor(rem % MIN_SEC); 216 217 if (hasYears) wrapper.find('[data-dmp-unit="years"]').text(padZero(years)); 218 if (hasMonths) wrapper.find('[data-dmp-unit="months"]').text(padZero(months)); 219 wrapper.find('[data-dmp-unit="days"]').text(padZero(days)); 220 wrapper.find('[data-dmp-unit="hours"]').text(padZero(hours)); 221 wrapper.find('[data-dmp-unit="minutes"]').text(padZero(minutes)); 222 wrapper.find('[data-dmp-unit="seconds"]').text(padZero(seconds)); 223 } 224 225 updateTimer(); 226 var timerId = setInterval(updateTimer, 1000); 227 countdownIntervals.push(timerId); 228 }); 229 } 230 231 /* ---------------------------------------------------- 232 * REAL-TIME CLIENT HYDRATION & CACHE BYPASS ENGINE 233 * Solves page caching issues by asynchronously validating live status. 234 * ---------------------------------------------------- */ 235 var isSyncing = false;
236 var lastKnownStatus = null; 237 238 function syncMembershipState() { 239 if (isSyncing || !window.DMP_CFG || !DMP_CFG.logged_in) return; 240 241 isSyncing = true; 242 243 var customContainer = $('.dmp-countdown-realtime-container[data-dmp-user-id]'); 244 var customUid = (customContainer.length && customContainer.data('dmp-user-id')) ? customContainer.data('dmp-user-id') : null; 245 246 var reqData = { 247 action: 'dmp_get_user_status', 248 _t: Date.now() 249 }; 250 if (customUid) { 251 reqData.user_id = customUid; 252 } 253 254 $.ajax({ 255 url: DMP_CFG.ajax, 256 type: 'GET', 257 cache: false, 258 data: reqData, 259 dataType: 'json', 260 success: function(res) { 261 isSyncing = false; 262 if (!res || !res.success || !res.data) return; 263 var d = res.data; 264 265 // Real-time access sync: If on a locked content page and now active, reload page to unlock! 266 if (d.active && $('.dmp-locked-content-box').length) { 267 window.location.reload(); 268 return; 269 } 270 271 // Real-time role & status sync: If status changed (e.g. active -> expired), reload page to reflect real-time permissions! 272 if (lastKnownStatus !== null && lastKnownStatus !== d.status) { 273 lastKnownStatus = d.status; 274 try { 275 if (window.localStorage) localStorage.setItem('dmp_user_status', d.status); 276 } catch(e){} 277 window.location.reload(); 278 return; 279 } 280 lastKnownStatus = d.status; 281 try { 282 if (window.localStorage) localStorage.setItem('dmp_user_status', d.status); 283 } catch(e){} 284 285 // 1. Update Status Component Badges 286 $('[data-dmp-status-live]').each(function() { 287 var el = $(this); 288 el.removeClass('dmp-status-comp-active dmp-status-comp-expired dmp-status-comp-suspended dmp-status-comp-lifetime dmp-status-comp-inactive') 289 .addClass('dmp-status-comp-' + d.status); 290 var labelSpan = el.find('.dmp-status-text'); 291 if (labelSpan.length) { 292 labelSpan.text(d.status_label); 293 } else { 294 el.text(d.status_label); 295 } 296 }); 297 298 // 2. If user is active but page was showing "Expired" notice: Hydrate with Live Countdown! 299 var expiredCards = $('.dmp-countdown-expired-card'); 300 if (d.active && expiredCards.length) { 301 expiredCards.each(function() { 302 var expCard = $(this); 303 var container = expCard.closest('.dmp-countdown-realtime-container'); 304 if (container.length && container.data('dmp-template')) { 305 // Replace expired card with the live countdown template 306 container.html(container.data('dmp-template')); 307 var newWrapper = container.find('[data-dmp-countdown]'); 308 newWrapper.attr('data-dmp-countdown', d.target_ts); 309 newWrapper.attr('data-dmp-now', d.now_ts); 310 initCountdowns(); 311 } else { 312 // Graceful inline transformation 313 expCard.replaceWith( 314 '<div class="dmp-countdown-wrapper dmp-countdown-layout-boxes" data-dmp-countdown="' + d.target_ts + '" data-dmp-now="' + d.now_ts + '" style="margin:20px auto;max-width:560px;">' + 315 '<div class="dmp-countdown-header"><div class="dmp-countdown-title-wrap"><span class="dmp-countdown-icon">â±ï¸</span><span class="dmp-countdown-title">MEMBERSHIP ACCESS TIME REMAINING</span></div><span class="dmp-countdown-status-badge">ACTIVE</span></div>' + 316 '<div class="dmp-countdown-grid">' + 317 '<div class="dmp-countdown-box" data-unit="days"><span class="dmp-countdown-num" data-dmp-unit="days">' + padZero(d.days) + '</span><span class="dmp-countdown-label">DAYS</span></div><div class="dmp-countdown-sep">:</div>' + 318 '<div class="dmp-countdown-box" data-unit="hours"><span class="dmp-countdown-num" data-dmp-unit="hours">' + padZero(d.hours) + '</span><span class="dmp-countdown-label">HOURS</span></div><div class="dmp-countdown-sep">:</div>' + 319 '<div class="dmp-countdown-box" data-unit="minutes"><span class="dmp-countdown-num" data-dmp-unit="minutes">' + padZero(d.minutes) + '</span><span class="dmp-countdown-label">MIN</span></div><div class="dmp-countdown-sep">:</div>' + 320 '<div class="dmp-countdown-box" data-unit="seconds"><span class="dmp-countdown-num" data-dmp-unit="seconds">' + padZero(d.seconds) + '</span><span class="dmp-countdown-label">SEC</span></div>' + 321 '</div>' + 322 '<div class="dmp-countdown-footer"><div class="dmp-countdown-footer-pill"><span>Expires: <strong>' + (d.formatted_expiry || 'Active') + '</strong></span></div></div>' + 323 '</div>' 324 ); 325 initCountdowns(); 326 } 327 }); 328 } 329 330 // 3. Update Existing Active Countdown Timers if Expiry Changed (e.g. admin extended date) 331 $('[data-dmp-countdown]').each(function() { 332 var wrap = $(this); 333 var curTarget = parseInt(wrap.attr('data-dmp-countdown'), 10); 334 if (d.active && d.target_ts && d.target_ts !== curTarget) { 335 wrap.attr('data-dmp-countdown', d.target_ts); 336 wrap.attr('data-dmp-now', d.now_ts); 337 wrap.removeClass('dmp-countdown-ended'); 338 wrap.find('.dmp-countdown-status-badge').text('ACTIVE').removeAttr('style'); 339 wrap.find('.dmp-countdown-footer strong').text(d.formatted_expiry); 340 initCountdowns(); 341 } 342 }); 343 344 // 4. Update Member Card if on page 345 $('.dmp-member-card').each(function() { 346 var card = $(this); 347 card.find('.dmp-mc-plan').text(d.plan_name || 'Membership'); 348 card.find('.dmp-mc-status').text(d.status_label).removeClass('dmp-mc-status-active dmp-mc-status-expired').addClass('dmp-mc-status-' + d.status); 349 card.find('.dmp-mc-expiry').text(d.formatted_expiry || 'Lifetime'); 350 }); 351 352 // 5. Update Days Remaining shortcode outputs 353 $('[data-dmp-days-count]').text(d.is_unlimited ? 'Unlimited' : (d.days + ' days')); 354 }, 355 error: function() { 356 isSyncing = false;
357 } 358 }); 359 } 360 361 /* ---------------------------------------------------- 362 * CROSS-TAB REAL-TIME SYNCHRONIZATION 363 * Listen for storage events across open browser tabs. 364 * ---------------------------------------------------- */ 365 window.addEventListener('storage', function(e) { 366 if (e.key === 'dmp_membership_synced') { 367 window.location.reload(); 368 } 369 }); 370 371 // Re-check when window regains focus or becomes visible 372 document.addEventListener('visibilitychange', function() { 373 if (!document.hidden) { 374 syncMembershipState(); 375 } 376 }); 377 $(window).on('focus', function() { 378 syncMembershipState(); 379 }); 380 381 // Periodic live heartbeat every 20 seconds 382 setInterval(syncMembershipState, 20000); 383 384 /* ---------------------------------------------------- 385 * INITIALIZATION & ELEMENTOR INTEGRATION 386 * ---------------------------------------------------- */ 387 $(document).ready(function() { 388 initCountdowns(); 389 // Hydrate live state on mount 390 setTimeout(syncMembershipState, 300); 391 }); 392 393 // Elementor Editor live preview hooks 394 if (window.elementorFrontend && window.elementorFrontend.hooks) { 395 window.elementorFrontend.hooks.addAction('frontend/element_ready/global', function() { 396 initCountdowns(); 397 syncMembershipState(); 398 }); 399 } 400 401 // Global on-demand status sync function 402 window.dmpSyncStatus = function() { 403 isSyncing = false; 404 syncMembershipState(); 405 }; 406});
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.