{"id":478,"date":"2025-12-11T18:39:36","date_gmt":"2025-12-11T18:39:36","guid":{"rendered":"https:\/\/buddyupapi-staging.us35.cdn-alpha.com\/?page_id=478"},"modified":"2025-12-11T18:39:36","modified_gmt":"2025-12-11T18:39:36","slug":"subscription-tiers","status":"publish","type":"page","link":"https:\/\/buddyupapi-staging.us35.cdn-alpha.com\/subscription-tiers\/","title":{"rendered":"Subscription Tiers"},"content":{"rendered":"<div class=\"buddyUpPjaxContainer\" data-buddyup-pjax-container=\"1\"><style>@import\"https:\/\/fonts.googleapis.com\/css2?family=Google+Sans+Code:ital,wght@0,300..800;1,300..800&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap\";#subscriptionTiers,section#tiersOverview,section#tiersFootNotes{width:90%;max-width:1200px;margin:0 auto;padding:0 15px}#subscriptionTiers{display:flex;flex-direction:column}section#subscriptionTiers{background-image:url(https:\/\/www.buddyupgo.com\/wp-content\/uploads\/interests-bg.jpg);background-position:center;background-repeat:no-repeat;background-size:cover;border-radius:var(--buddyup-panel-radius);padding:clamp(20px,5vw,40px)}section#tiersOverview{border-radius:var(--buddyup-panel-radius);padding:clamp(20px,5vw,40px);margin:clamp(20px,5vw,40px) auto;text-align:center}section#tiersFootNotes{margin:0 auto 40px;font-size:clamp(.75rem,1.5vw,.9rem);color:#555}.centeredHeader{text-align:center;padding:20px 0}.centeredHeader h1{font-family:Roboto,sans-serif;font-style:italic;font-weight:600;font-size:clamp(1.5rem,5vw,2.5rem);margin-bottom:.5rem}.tiercardContainer{display:flex;justify-content:center;gap:clamp(20px,4vw,30px);flex-wrap:wrap;margin:0 auto;flex-direction:row}.tierCard{display:flex;flex-direction:column;align-items:center;border:1px solid #ccc;border-radius:var(--buddyup-panel-radius);padding:clamp(15px,4vw,20px);text-align:center;background-color:var(--buddyup-background);width:350px;transition:transform .3s ease}.tierCard:hover{transform:scale(1.05)}.tierCard h2{font-family:Roboto,sans-serif;font-weight:600;font-size:clamp(1.2rem,3vw,1.5rem);margin:0;padding:0}.tierCard h3{font-family:Roboto,sans-serif;font-style:italic;font-size:clamp(.9rem,2vw,1.1rem);margin:5px 0 10px;padding:0}.tierCard ul.featuresList{font-size:clamp(.85rem,2vw,1rem);padding-top:unset;justify-self:center}.tierCard ul.featuresList li{text-align:left;list-style:disc;padding:0}.tierCard ul.featuresList li:before{content:\"\"}.buttonContainer{margin:0 auto!important}.subscribeButton{background-color:var(--primary-color);color:#fff;border:none;border-radius:999px;padding:10px 20px;font-size:clamp(.9rem,2vw,1rem);cursor:pointer;transition:background-color .3s ease}section#tiersOverview table{margin:0 auto;width:100%;border-collapse:collapse;border-radius:var(--buddyup-control-radius);font-size:clamp(.85rem,2vw,1rem)}section#tiersOverview td{padding:clamp(8px,2vw,12px);text-align:center}section#tiersOverview table#desktopViewTable thead,section#tiersOverview .tierCardsOverview thead.tierNameTableMobile{background-color:var(--buddyup-background);color:#000}section#tiersOverview .tierCardsOverview tbody.featureNameTableMobile{background-color:#fff;color:#000}section#tiersOverview .tierCardsOverview{display:none}@media (max-width: 870px){section#tiersOverview,section#subscriptionTiers .tierCard{width:300px;align-items:unset}section#tiersOverview{padding:0}section#tiersOverview table#desktopViewTable{display:none}section#tiersOverview .tierCardsOverview{display:grid;grid-template-columns:1fr;gap:15px}.tiercardContainer{grid-template-columns:1fr;margin:0 auto}}@media (max-width: 480px){#subscriptionTiers,section#tiersOverview,section#tiersFootNotes{padding:10px}.tierCard{padding:15px}}@media (max-width: 355px){section#subscriptionTiers .tierCard{width:100%}}\n<\/style>\n<script>\n    \/\/ Prime state from cached login data, but keep UI in loading mode until account-get confirms.\n    (function() {\n        function normalizeTier(t) {\n            t = parseInt(t);\n            if (isNaN(t)) t = 0;\n            if (![0, 1, 2].includes(t)) t = 0;\n            return t;\n        }\n\n        function normalizeResubscribeFlag(value) {\n            if (value === true || value === 1 || value === '1') return 1;\n            var raw = String(value || '').toLowerCase().trim();\n            return ['true', 'yes', 'on'].includes(raw) ? 1 : 0;\n        }\n\n        document.documentElement.setAttribute('data-buddyup-sub-ui', 'loading');\n\n        try {\n            const raw = window.localStorage ? localStorage.getItem('buddyup_user') : null;\n            if (!raw) return;\n            const user = JSON.parse(raw);\n\n            let tier = (typeof user.user_subscription !== 'undefined' ? user.user_subscription : null);\n            tier = tier === null && typeof user.subscription !== 'undefined' ? user.subscription : tier;\n            tier = tier === null && typeof user.subscription_tier !== 'undefined' ? user.subscription_tier : tier;\n            tier = tier === null && typeof user.tier !== 'undefined' ? user.tier : tier;\n\n            document.documentElement.setAttribute('data-buddyup-tier', String(normalizeTier(tier)));\n\n            var canResubscribe = 0;\n            if (typeof user.subscription_can_resubscribe !== 'undefined') {\n                canResubscribe = normalizeResubscribeFlag(user.subscription_can_resubscribe);\n            } else {\n                var status = String(user.subscription_status || user.hyfin_subscription_status || '').toLowerCase().trim();\n                if (['closed', 'cancelled', 'canceled', 'inactive', 'ended', 'expired', 'refunded'].includes(status)) {\n                    canResubscribe = 1;\n                }\n            }\n            document.documentElement.setAttribute('data-buddyup-can-resubscribe', String(canResubscribe));\n        } catch (e) {\n            \/\/ no-op\n        }\n    })();\n<\/script>\n\n<style>\n    #buddyUpSubscriptionCards {\n        display: grid;\n        grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));\n        gap: 18px;\n        width: 100%;\n        margin: 0 0 20px;\n    }\n\n    .buddyUpTierCard {\n        background: #fff;\n        border: 1px solid rgba(0,0,0,0.08);\n        border-radius: 12px;\n        padding: 18px;\n        box-shadow: 0 8px 20px rgba(0,0,0,0.06);\n    }\n\n    .buddyUpTierCard h3 {\n        margin: 0 0 10px;\n    }\n\n    .buddyUpTierCard ul {\n        margin: 0 0 14px;\n        padding-left: 18px;\n    }\n\n    .buddyUpTierCard .buddyUpButton1 {\n        display: inline-block;\n        text-decoration: none;\n        cursor: pointer;\n    }\n\n    #buddyUpTier2Already {\n        display: none;\n        padding: 18px;\n        border-radius: 12px;\n        border: 1px solid rgba(0,0,0,0.08);\n        background: #f8f9fb;\n        width: 100%;\n        text-align: center;\n        margin: 0 0 20px;\n    }\n\n    \/* Hide cards based on tier unless this user is eligible to resubscribe now. *\/\n    html[data-buddyup-tier=\"1\"]:not([data-buddyup-can-resubscribe=\"1\"]) #buddyUpCardAdventurer { display: none; }\n    html[data-buddyup-tier=\"2\"]:not([data-buddyup-can-resubscribe=\"1\"]) #buddyUpSubscriptionCards { display: none; }\n    html[data-buddyup-tier=\"2\"]:not([data-buddyup-can-resubscribe=\"1\"]) #buddyUpTier2Already { display: block; }\n\n    \/* Prevent stale-state flash: keep cards\/message hidden until first live account check settles. *\/\n    #buddyUpSubscriptionLoading {\n        display: none;\n        width: 100%;\n        text-align: center;\n        margin: 0 0 20px;\n        padding: 14px 16px;\n        border: 1px dashed rgba(0,0,0,0.18);\n        border-radius: 12px;\n        background: #f8f9fb;\n        color: #444;\n        font-size: 0.95rem;\n    }\n\n    html[data-buddyup-sub-ui=\"loading\"] #buddyUpSubscriptionCards,\n    html[data-buddyup-sub-ui=\"loading\"] #buddyUpTier2Already {\n        display: none;\n    }\n\n    html[data-buddyup-sub-ui=\"loading\"] #buddyUpSubscriptionLoading {\n        display: block;\n    }\n\n    \/* Checkout modal styles *\/\n    #buddyUpCheckoutOverlay {\n        position: fixed;\n        top: 0; left: 0; right: 0; bottom: 0;\n        background: rgba(0,0,0,0.5);\n        z-index: 99999;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n    }\n    #buddyUpCheckoutModal {\n        background: #fff;\n        border-radius: 14px;\n        width: 95vw;\n        max-width: 520px;\n        height: 85vh;\n        max-height: 85vh;\n        display: flex;\n        flex-direction: column;\n        overflow: hidden;\n        box-shadow: 0 12px 40px rgba(0,0,0,0.18);\n    }\n    #buddyUpCheckoutHeader {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        padding: 14px 18px;\n        border-bottom: 1px solid #eee;\n    }\n    #buddyUpCheckoutHeader h3 { margin: 0; font-size: 16px; }\n    #buddyUpCheckoutClose {\n        background: none; border: none; font-size: 22px; cursor: pointer; color: #888; padding: 0 4px;\n    }\n    #buddyUpCheckoutClose:hover { color: #333; }\n    #buddyUpCheckoutBody {\n        flex: 1;\n        overflow-y: auto;\n        overflow-x: hidden;\n        position: relative;\n        min-height: 400px;\n    }\n    #buddyUpCheckoutBody iframe {\n        width: 100%;\n        height: 100%;\n        min-height: 620px;\n        border: none;\n        display: block;\n    }\n    #buddyUpCheckoutLoading {\n        position: absolute;\n        top: 0; left: 0; right: 0; bottom: 0;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        background: #fff;\n        font-size: 15px;\n        color: #666;\n    }\n    #buddyUpCheckoutProcessing {\n        position: absolute;\n        top: 0; left: 0; right: 0; bottom: 0;\n        display: none;\n        align-items: center;\n        justify-content: center;\n        background: rgba(255,255,255,0.94);\n        text-align: center;\n        z-index: 2;\n        padding: 20px;\n    }\n    .buddyUpCheckoutProcessingInner {\n        max-width: 340px;\n    }\n    .buddyUpCheckoutSpinner {\n        width: 34px;\n        height: 34px;\n        border-radius: 50%;\n        border: 3px solid #d9d9d9;\n        border-top-color: #3d7ef0;\n        margin: 0 auto 12px;\n        animation: buddyUpCheckoutSpin 0.9s linear infinite;\n    }\n    @keyframes buddyUpCheckoutSpin {\n        to { transform: rotate(360deg); }\n    }\n    #buddyUpCheckoutSuccess {\n        display: none;\n        text-align: center;\n        padding: 40px 24px;\n    }\n    #buddyUpCheckoutSuccess i { font-size: 48px; color: #34a853; margin-bottom: 14px; }\n\n    #buddyUpCheckoutProfileForm {\n        max-width: 680px;\n        margin: 0 auto;\n    }\n\n    .buddyUpCheckoutProfileGrid {\n        display: grid;\n        grid-template-columns: repeat(2, minmax(0, 1fr));\n        gap: 10px 14px;\n    }\n\n    .buddyUpCheckoutProfileGrid .buddyUpInputWrapper {\n        margin-bottom: 0;\n    }\n\n    .buddyUpCheckoutProfileGrid .fullWidth {\n        grid-column: 1 \/ -1;\n    }\n\n    #buddyUpCheckoutProfileError {\n        margin-top: .7rem;\n    }\n\n    .buddyUpCheckoutProfileActions {\n        display: flex;\n        gap: .6rem;\n        justify-content: flex-end;\n        margin-top: 1rem;\n    }\n\n    @media (max-width: 700px) {\n        .buddyUpCheckoutProfileGrid {\n            grid-template-columns: 1fr;\n        }\n    }\n\n    @media (max-width: 600px) {\n        #buddyUpCheckoutModal { width: 100vw; max-width: 100vw; height: var(--buddyup-mobile-usable-vh, 100dvh); max-height: var(--buddyup-mobile-usable-vh, 100dvh); border-radius: 0; }\n        #buddyUpCheckoutBody { min-height: calc(var(--buddyup-mobile-usable-vh, 100dvh) - 60px); }\n    }\n<\/style>\n\n<section id=\"subscriptionTiers\" class=\"flexWrapper buddyUpContainer\">\n\n    <div id=\"buddyUpSubscriptionLoading\">Checking your subscription status...<\/div>\n\n    <div id=\"buddyUpTier2Already\">\n        <h3>You are already subscribed (Enthusiast).<\/h3>\n        <p>No upgrades are available at this time.<\/p>\n    <\/div>\n\n    <div id=\"buddyUpSubscriptionCards\">\n        <div class=\"buddyUpTierCard\" id=\"buddyUpCardAdventurer\">\n            <h3>Adventurer<\/h3>\n            <ul>\n                                    <li>Unlimited right-swipes and messaging<\/li>\n                                    <li>Create up to 3 premium groups and 4 events per month<\/li>\n                                    <li>Up to 75 buddies<\/li>\n                                    <li>Custom profile uploads + up to 3 profile videos<\/li>\n                                    <li>Enhanced event comments (up to 3 media items)<\/li>\n                                    <li>Planned: advanced planning\/filter tools<\/li>\n                            <\/ul>\n            <button class=\"buddyUpButton1\" data-tier=\"1\" data-product=\"adventurer\" onclick=\"buddyUpStartCheckout(this)\">Purchase Adventurer<\/button>\n        <\/div>\n\n        <div class=\"buddyUpTierCard\" id=\"buddyUpCardEnthusiast\">\n            <h3>Enthusiast<\/h3>\n            <ul>\n                                    <li>See who swiped right on you<\/li>\n                                    <li>Ad-free experience<\/li>\n                                    <li>Create up to 15 premium groups + unlimited events<\/li>\n                                    <li>Up to 1,500 buddies<\/li>\n                                    <li>Comprehensive privacy controls<\/li>\n                                    <li>Profile videos up to 10 uploads and richer media limits<\/li>\n                                    <li>Planned: premium spotlight placement in Explore\/MatchUp<\/li>\n                                    <li>Everything in Adventurer<\/li>\n                            <\/ul>\n            <button class=\"buddyUpButton1\" data-tier=\"2\" data-product=\"enthusiast\" onclick=\"buddyUpStartCheckout(this)\">Purchase Enthusiast<\/button>\n        <\/div>\n    <\/div>\n<\/section>\n\n<script>\n(function() {\n    'use strict';\n\n    var _subscriptionUiHydrated = false;\n\n    function markSubscriptionUiReady() {\n        document.documentElement.setAttribute('data-buddyup-sub-ui', 'ready');\n        _subscriptionUiHydrated = true;\n    }\n\n    function parseBoolish(value) {\n        if (value === true || value === 1 || value === '1') return true;\n        var raw = String(value || '').toLowerCase().trim();\n        return ['true', 'yes', 'on'].includes(raw);\n    }\n\n    function parseCanResubscribeFromSource(src) {\n        if (!src || typeof src !== 'object') return null;\n\n        var explicit = src.subscription_can_resubscribe;\n        if (typeof explicit !== 'undefined') return parseBoolish(explicit);\n\n        explicit = src.can_resubscribe;\n        if (typeof explicit !== 'undefined') return parseBoolish(explicit);\n\n        explicit = src.subscription_is_closed;\n        if (typeof explicit !== 'undefined') return parseBoolish(explicit);\n\n        var status = String(src.subscription_status || src.hyfin_subscription_status || '').toLowerCase().trim();\n        if (status === '') return null;\n\n        if (['closed', 'cancelled', 'canceled', 'inactive', 'ended', 'expired', 'refunded'].includes(status)) {\n            return true;\n        }\n\n        return false;\n    }\n\n    function parseCanResubscribeFromAccount(res) {\n        var sources = [\n            res,\n            res && res.data,\n            res && res.user,\n            res && res.data && res.data.user\n        ];\n\n        for (var i = 0; i < sources.length; i++) {\n            var parsed = parseCanResubscribeFromSource(sources[i]);\n            if (parsed !== null) return parsed;\n        }\n\n        return false;\n    }\n\n    function applySubscriptionUiState(tier, canResubscribe) {\n        var normalizedTier = normalizeTier(tier);\n        document.documentElement.setAttribute('data-buddyup-tier', String(normalizedTier));\n        document.documentElement.setAttribute('data-buddyup-can-resubscribe', canResubscribe ? '1' : '0');\n        markSubscriptionUiReady();\n    }\n\n    function refreshSubscriptionUiState(forceSync) {\n        if (typeof BUDDYUP === 'undefined' || !BUDDYUP.apiRequest) {\n            if (!_subscriptionUiHydrated) {\n                var currentTier = document.documentElement.getAttribute('data-buddyup-tier') || '0';\n                var currentResubscribe = document.documentElement.getAttribute('data-buddyup-can-resubscribe') === '1';\n                applySubscriptionUiState(currentTier, currentResubscribe);\n            }\n            return;\n        }\n\n        BUDDYUP.apiRequest('account-get', {\n            _force: !!forceSync,\n            _cb: Date.now()\n        }).then(function(res) {\n            if (!res) return;\n            applySubscriptionUiState(parseTierFromAccount(res), parseCanResubscribeFromAccount(res));\n        }).catch(function(){\n            if (!_subscriptionUiHydrated) {\n                var currentTier = document.documentElement.getAttribute('data-buddyup-tier') || '0';\n                var currentResubscribe = document.documentElement.getAttribute('data-buddyup-can-resubscribe') === '1';\n                applySubscriptionUiState(currentTier, currentResubscribe);\n            }\n        });\n    }\n\n    \/\/ Single source of truth on this page: account-get result controls subscription UI.\n    refreshSubscriptionUiState(true);\n\n    window.addEventListener('focus', function() {\n        refreshSubscriptionUiState(false);\n    });\n\n    document.addEventListener('visibilitychange', function() {\n        if (document.visibilityState === 'visible') {\n            refreshSubscriptionUiState(false);\n        }\n    });\n\n    \/\/ Avoid noisy periodic polling on this page; rely on focus\/visibility and explicit checkout events.\n\n    \/\/Checkout flow: create Hyfin customer with user context, then open payment link in modal\n\n    let _checkoutToken = null;\n    let _checkoutPollTimer = null;\n    let _checkoutPollInFlight = false;\n    let _checkoutPollAttempts = 0;\n    let _checkoutComplete = false;\n    let _checkoutStartedAt = null;\n    let _checkoutTargetTier = 0;\n    let _checkoutIframeLoadCount = 0;\n    let _checkoutLastIframeLoadAt = 0;\n    let _checkoutSubmitLikely = false;\n    let _checkoutSubmitLikelyReason = null;\n    let _checkoutProcessingVisible = false;\n    let _checkoutInteractionCount = 0;\n    let _checkoutLastInteractionAt = 0;\n    let _checkoutTraceId = null;\n    let _checkoutTrace = [];\n    let _checkoutCleanupFns = [];\n    let _checkoutActiveLink = null;\n    let _checkoutTraceLastPersistAt = 0;\n    let _checkoutIframeFocusedSince = 0; \/\/ timestamp when focus first entered iframe\n    let _checkoutRunPoll = null;\n    let _checkoutVerifyUserId = 0;\n    let _checkoutVerifyUserToken = '';\n    let _checkoutLastVerifyAt = 0;\n    let _checkoutMaxVerifyAttempts = 3;\n\n    function checkoutLog(label, data) {\n        var safeData = null;\n        if (typeof data !== 'undefined') {\n            try {\n                safeData = JSON.parse(JSON.stringify(data));\n            } catch (e) {\n                safeData = String(data);\n            }\n        }\n\n        try {\n            const entry = {\n                ts: Date.now(),\n                iso: new Date().toISOString(),\n                label: String(label || ''),\n                data: safeData,\n            };\n            _checkoutTrace.push(entry);\n            if (_checkoutTrace.length > 400) {\n                _checkoutTrace = _checkoutTrace.slice(-400);\n            }\n\n            if (_checkoutTraceId && window.sessionStorage) {\n                var nowMs = Date.now();\n                var labelStr = String(label || '');\n                var importantPersist = \/checkout\\.success|checkout\\.error|reconcile\\.poll\\.maxAttempts|checkout\\.submit_likely|postMessage\\.received|reconcile\\.poll\\.applied_detected|modal\\.close\/.test(labelStr);\n                if ((nowMs - _checkoutTraceLastPersistAt) >= 2500 || importantPersist) {\n                    const payload = {\n                        trace_id: _checkoutTraceId,\n                        updated_at: entry.iso,\n                        events: _checkoutTrace,\n                    };\n                    sessionStorage.setItem('buddyup_checkout_trace_active', JSON.stringify(payload));\n                    sessionStorage.setItem('buddyup_checkout_trace_last', JSON.stringify(payload));\n                    _checkoutTraceLastPersistAt = nowMs;\n                }\n            }\n        } catch (e) {}\n\n        try {\n            if (window.BUDDYUP_CHECKOUT_DEBUG === true) {\n                if (typeof data !== 'undefined') console.log('[BuddyUpCheckout]', label, data);\n                else console.log('[BuddyUpCheckout]', label);\n            }\n        } catch (e) {}\n    }\n\n    function beginCheckoutTrace(context) {\n        _checkoutTraceId = 'chk_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);\n        _checkoutTrace = [];\n        _checkoutTraceLastPersistAt = 0;\n        try {\n            window.__buddyUpCheckoutTraceId = _checkoutTraceId;\n            window.__buddyUpCheckoutGetTrace = function() {\n                return {\n                    trace_id: _checkoutTraceId,\n                    events: _checkoutTrace.slice(0),\n                };\n            };\n        } catch (e) {}\n        checkoutLog('trace.begin', context || {});\n    }\n\n    function markCheckoutSubmitLikely(reason, meta) {\n        if (_checkoutComplete || _checkoutSubmitLikely) return;\n\n        _checkoutSubmitLikely = true;\n        _checkoutSubmitLikelyReason = reason || 'unknown';\n        showCheckoutProcessing('Payment submitted. Finalizing your subscription...');\n        checkoutLog('checkout.submit_likely', {\n            reason: _checkoutSubmitLikelyReason,\n            meta: meta || null,\n        });\n\n        \/\/ Submit-like signals are strong; trigger a near-immediate verification pass.\n        scheduleCheckoutVerificationSoon(350, 'submit_likely');\n    }\n\n    function scheduleCheckoutVerificationSoon(delayMs, reason) {\n        if (_checkoutComplete || typeof _checkoutRunPoll !== 'function') return;\n\n        var ms = parseInt(delayMs, 10);\n        if (!Number.isFinite(ms) || ms < 0) ms = 0;\n\n        if (_checkoutPollTimer) {\n            clearTimeout(_checkoutPollTimer);\n            _checkoutPollTimer = null;\n        }\n\n        _checkoutPollTimer = setTimeout(function() {\n            if (typeof _checkoutRunPoll === 'function') {\n                _checkoutRunPoll(reason || 'scheduled');\n            }\n        }, ms);\n    }\n\n    function summarizeMessagePayload(payload) {\n        if (payload === null || typeof payload === 'undefined') return { kind: String(payload) };\n        if (typeof payload === 'string') {\n            return {\n                kind: 'string',\n                length: payload.length,\n                preview: payload.slice(0, 180),\n            };\n        }\n        if (typeof payload === 'number' || typeof payload === 'boolean') {\n            return { kind: typeof payload, value: payload };\n        }\n        if (Array.isArray(payload)) {\n            return { kind: 'array', length: payload.length };\n        }\n        if (typeof payload === 'object') {\n            const keys = Object.keys(payload).slice(0, 12);\n            const out = { kind: 'object', keys: keys };\n            if (Object.prototype.hasOwnProperty.call(payload, 'hyfin_return')) out.hyfin_return = payload.hyfin_return;\n            if (Object.prototype.hasOwnProperty.call(payload, 'status')) out.status = payload.status;\n            if (Object.prototype.hasOwnProperty.call(payload, 'event')) out.event = payload.event;\n            if (Object.prototype.hasOwnProperty.call(payload, 'type')) out.type = payload.type;\n            return out;\n        }\n        return { kind: typeof payload };\n    }\n\n    function canInferSubmitNow() {\n        if (_checkoutComplete) return false;\n        if (_checkoutIframeLoadCount < 1) return false;\n        if (_checkoutInteractionCount < 1) return false;\n        if (!_checkoutLastIframeLoadAt) return false;\n\n        \/\/ Allow iframe scripts to finish booting before interpreting message noise.\n        return (Date.now() - _checkoutLastIframeLoadAt) >= 1200;\n    }\n\n    function installCheckoutObservers(iframe) {\n        if (!iframe) return;\n\n        const overlay = document.getElementById('buddyUpCheckoutOverlay');\n        if (!overlay) return;\n\n        const onPointerDown = function(evt) {\n            if (_checkoutComplete) return;\n            try {\n                const rect = iframe.getBoundingClientRect();\n                const cx = typeof evt.clientX === 'number' ? evt.clientX : -1;\n                const cy = typeof evt.clientY === 'number' ? evt.clientY : -1;\n                const inside = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom;\n                if (!inside) return;\n\n                _checkoutInteractionCount += 1;\n                _checkoutLastInteractionAt = Date.now();\n                checkoutLog('checkout.iframe.interaction', {\n                    count: _checkoutInteractionCount,\n                    x: cx,\n                    y: cy,\n                });\n            } catch (e) {}\n        };\n\n        const onWindowBlur = function() {\n            if (_checkoutComplete) return;\n            const now = Date.now();\n            const prevInteractionAt = _checkoutLastInteractionAt;\n            const sinceInteraction = prevInteractionAt ? (now - prevInteractionAt) : null;\n            var activeIsCheckoutIframe = false;\n            try {\n                activeIsCheckoutIframe = document.activeElement === iframe;\n            } catch (e) {}\n\n            if (_checkoutIframeLoadCount >= 1 && activeIsCheckoutIframe) {\n                                \/\/ Track when the user first focused into the iframe (form engagement start).\n                                if (!_checkoutIframeFocusedSince) _checkoutIframeFocusedSince = now;\n                \/\/ Cross-origin iframe clicks do not bubble into parent; infer interaction from focus transfer.\n                if (!prevInteractionAt || (now - prevInteractionAt) > 450) {\n                    _checkoutInteractionCount += 1;\n                    _checkoutLastInteractionAt = now;\n                    checkoutLog('checkout.iframe.focus_interaction', {\n                        count: _checkoutInteractionCount,\n                        source: 'window.blur.activeElement',\n                    });\n                }\n            }\n\n            checkoutLog('checkout.window.blur', {\n                sinceInteractionMs: sinceInteraction,\n                interactionCount: _checkoutInteractionCount,\n                activeIsCheckoutIframe: activeIsCheckoutIframe,\n            });\n\n            \/\/ Strong signal for submit\/challenge transitions: quick blur after meaningful iframe interaction.\n            \/\/ After 5s of iframe focus (user was filling the form) 1 interaction is enough.\n            \/\/ Otherwise require 3 to avoid false positives from an opening click.\n            var blurInteractionThreshold = (_checkoutIframeFocusedSince > 0 && (now - _checkoutIframeFocusedSince) >= 5000) ? 1 : 3;\n            if (_checkoutIframeLoadCount >= 1 && _checkoutInteractionCount >= blurInteractionThreshold && sinceInteraction !== null && sinceInteraction >= 250 && sinceInteraction <= 1600) {\n                markCheckoutSubmitLikely('window_blur_after_iframe_interaction', {\n                    sinceInteractionMs: sinceInteraction,\n                    interactionCount: _checkoutInteractionCount,\n                });\n            }\n        };\n\n        const onWindowFocus = function() {\n            if (_checkoutComplete) return;\n            checkoutLog('checkout.window.focus', {\n                interactionCount: _checkoutInteractionCount,\n                submitLikely: _checkoutSubmitLikely,\n            });\n\n            if (_checkoutSubmitLikely) {\n                scheduleCheckoutVerificationSoon(250, 'window_focus');\n            }\n        };\n\n        const onVisibilityChange = function() {\n            if (_checkoutComplete) return;\n            const state = document.visibilityState;\n            checkoutLog('checkout.visibility', { state: state });\n            if (_checkoutIframeLoadCount >= 1 && state === 'hidden' && _checkoutInteractionCount > 0) {\n                markCheckoutSubmitLikely('document_hidden_after_interaction', {\n                    interactionCount: _checkoutInteractionCount,\n                });\n            }\n            if (state === 'visible' && _checkoutSubmitLikely) {\n                scheduleCheckoutVerificationSoon(200, 'visibility_visible');\n            }\n        };\n\n        overlay.addEventListener('pointerdown', onPointerDown, true);\n        window.addEventListener('blur', onWindowBlur);\n        window.addEventListener('focus', onWindowFocus);\n        document.addEventListener('visibilitychange', onVisibilityChange);\n\n        _checkoutCleanupFns.push(function() {\n            try { overlay.removeEventListener('pointerdown', onPointerDown, true); } catch (e) {}\n            try { window.removeEventListener('blur', onWindowBlur); } catch (e) {}\n            try { window.removeEventListener('focus', onWindowFocus); } catch (e) {}\n            try { document.removeEventListener('visibilitychange', onVisibilityChange); } catch (e) {}\n        });\n\n        checkoutLog('checkout.observers.installed', { traceId: _checkoutTraceId });\n    }\n\n    function getCurrentUser() {\n        try {\n            const raw = window.localStorage ? localStorage.getItem('buddyup_user') : null;\n            return raw ? JSON.parse(raw) : null;\n        } catch(e) { return null; }\n    }\n\n    function normalizeTier(value) {\n        value = parseInt(value);\n        if (isNaN(value)) value = 0;\n        if (![0, 1, 2].includes(value)) value = 0;\n        return value;\n    }\n\n    function accountDataFromResponse(res) {\n        if (!res) return null;\n        if (res && res.data && typeof res.data === 'object') return res.data;\n        if (res && res.user && typeof res.user === 'object') return res.user;\n        if (res && typeof res === 'object') return res;\n        return null;\n    }\n\n    function readFirstString(source, keys) {\n        if (!source || typeof source !== 'object') return '';\n        for (var i = 0; i < keys.length; i++) {\n            var val = source[keys[i]];\n            if (typeof val === 'string' && val.trim() !== '') return val.trim();\n        }\n        return '';\n    }\n\n    function hasEverSubscribed(accountRes, currentTier, user) {\n        if (normalizeTier(currentTier) > 0) return true;\n\n        var account = accountDataFromResponse(accountRes);\n        var candidates = [];\n\n        if (account) {\n            candidates.push(account.payment_ref);\n            candidates.push(account.subscription_started_at);\n            candidates.push(account.subscription_expires_at);\n            candidates.push(account.hyfin_customer_id);\n            candidates.push(account.hyfin_customer_external_id);\n            candidates.push(account.confirmation_number);\n        }\n\n        if (user && typeof user === 'object') {\n            candidates.push(user.payment_ref);\n            candidates.push(user.subscription_started_at);\n            candidates.push(user.subscription_expires_at);\n            candidates.push(user.hyfin_customer_id);\n            candidates.push(user.hyfin_customer_external_id);\n            candidates.push(user.confirmation_number);\n        }\n\n        for (var i = 0; i < candidates.length; i++) {\n            if (typeof candidates[i] === 'string' && candidates[i].trim() !== '') return true;\n        }\n\n        return false;\n    }\n\n    function seedCheckoutCompletion(accountRes, user) {\n        var account = accountDataFromResponse(accountRes) || {};\n        var fallback = user && typeof user === 'object' ? user : {};\n\n        var seed = {};\n        seed.first_name = readFirstString(account, ['first_name']) || readFirstString(fallback, ['first_name']) || '';\n        seed.last_name = readFirstString(account, ['last_name']) || readFirstString(fallback, ['last_name']) || '';\n        seed.mobile_phone = readFirstString(account, ['mobile_phone']) || readFirstString(fallback, ['mobile_phone']) || '';\n        seed.city = readFirstString(account, ['city']) || readFirstString(fallback, ['city']) || '';\n        seed.state = readFirstString(account, ['state']) || readFirstString(fallback, ['state']) || '';\n        seed.postal_code = readFirstString(account, ['postal_code']) || readFirstString(fallback, ['postal_code']) || '';\n        seed.about_me = readFirstString(account, ['about_me']) || '';\n        seed.email = readFirstString(account, ['email_address', 'email']) || readFirstString(fallback, ['email_address', 'email']) || '';\n        return seed;\n    }\n\n    function buildCheckoutCustomerPayload(user, accountRes, completionData) {\n        var account = accountDataFromResponse(accountRes) || {};\n        var completion = completionData && typeof completionData === 'object' ? completionData : {};\n        var fallback = user && typeof user === 'object' ? user : {};\n\n        var payload = {\n            external_id: String(fallback.id || account.id || ''),\n            first_name: completion.first_name || readFirstString(account, ['first_name']) || readFirstString(fallback, ['first_name']) || readFirstString(fallback, ['full_name']) || 'User',\n            last_name: completion.last_name || readFirstString(account, ['last_name']) || readFirstString(fallback, ['last_name']) || '',\n            email: completion.email || readFirstString(account, ['email_address', 'email']) || readFirstString(fallback, ['email_address', 'email']) || '',\n            mobile_phone: completion.mobile_phone || readFirstString(account, ['mobile_phone']) || readFirstString(fallback, ['mobile_phone']) || '',\n            address_line_1: completion.address_line_1 || '',\n            address_line_2: completion.address_line_2 || '',\n            city: completion.city || readFirstString(account, ['city']) || readFirstString(fallback, ['city']) || '',\n            state: completion.state || readFirstString(account, ['state']) || readFirstString(fallback, ['state']) || '',\n            postal_code: completion.postal_code || readFirstString(account, ['postal_code']) || readFirstString(fallback, ['postal_code']) || '',\n            active: true,\n        };\n\n        payload.account_name = (payload.first_name + ' ' + payload.last_name).trim();\n        if (payload.account_name === '') {\n            payload.account_name = 'BuddyUp User ' + String(fallback.id || account.id || '');\n        }\n\n        return payload;\n    }\n\n    async function requestCheckoutProfileCompletion(accountRes, user, tier) {\n        if (typeof BUDDYUP === 'undefined' || typeof BUDDYUP.openModal !== 'function') {\n            return null;\n        }\n\n        var seed = seedCheckoutCompletion(accountRes, user);\n\n        return await new Promise(function(resolve) {\n            var settled = false;\n            var finish = function(value) {\n                if (settled) return;\n                settled = true;\n                resolve(value);\n            };\n\n            var body = '' +\n                '<form id=\"buddyUpCheckoutProfileForm\">' +\n                    '<p style=\"margin-top:0;\">Complete your profile details before checkout. Returning subscribers can skip this step automatically.<\/p>' +\n                    '<div class=\"buddyUpCheckoutProfileGrid\">' +\n                        '<div class=\"buddyUpInputWrapper\">' +\n                            '<label for=\"checkoutFirstName\">First Name <span class=\"requiredMessage\">*<\/span><\/label>' +\n                            '<input type=\"text\" id=\"checkoutFirstName\" value=\"' + BUDDYUP.escapeHtml(seed.first_name) + '\" required \/>' +\n                        '<\/div>' +\n                        '<div class=\"buddyUpInputWrapper\">' +\n                            '<label for=\"checkoutLastName\">Last Name <span class=\"requiredMessage\">*<\/span><\/label>' +\n                            '<input type=\"text\" id=\"checkoutLastName\" value=\"' + BUDDYUP.escapeHtml(seed.last_name) + '\" required \/>' +\n                        '<\/div>' +\n                        '<div class=\"buddyUpInputWrapper\">' +\n                            '<label for=\"checkoutPhone\">Phone Number <span class=\"requiredMessage\">*<\/span><\/label>' +\n                            '<input type=\"tel\" id=\"checkoutPhone\" value=\"' + BUDDYUP.escapeHtml(seed.mobile_phone) + '\" required \/>' +\n                        '<\/div>' +\n                        '<div class=\"buddyUpInputWrapper\">' +\n                            '<label for=\"checkoutZip\">Zip Code <span class=\"requiredMessage\">*<\/span><\/label>' +\n                            '<input type=\"text\" id=\"checkoutZip\" value=\"' + BUDDYUP.escapeHtml(seed.postal_code) + '\" required \/>' +\n                        '<\/div>' +\n                        '<div class=\"buddyUpInputWrapper fullWidth\">' +\n                            '<label for=\"checkoutAddress1\">Street Address <span class=\"requiredMessage\">*<\/span><\/label>' +\n                            '<input type=\"text\" id=\"checkoutAddress1\" required \/>' +\n                        '<\/div>' +\n                        '<div class=\"buddyUpInputWrapper fullWidth\">' +\n                            '<label for=\"checkoutAddress2\">Address Line 2<\/label>' +\n                            '<input type=\"text\" id=\"checkoutAddress2\" \/>' +\n                        '<\/div>' +\n                        '<div class=\"buddyUpInputWrapper\">' +\n                            '<label for=\"checkoutCity\">City <span class=\"requiredMessage\">*<\/span><\/label>' +\n                            '<input type=\"text\" id=\"checkoutCity\" value=\"' + BUDDYUP.escapeHtml(seed.city) + '\" required \/>' +\n                        '<\/div>' +\n                        '<div class=\"buddyUpInputWrapper\">' +\n                            '<label for=\"checkoutState\">State <span class=\"requiredMessage\">*<\/span><\/label>' +\n                            '<input type=\"text\" id=\"checkoutState\" value=\"' + BUDDYUP.escapeHtml(seed.state) + '\" required \/>' +\n                        '<\/div>' +\n                        '<div class=\"buddyUpInputWrapper fullWidth\">' +\n                            '<label for=\"checkoutAboutMe\">About Me<\/label>' +\n                            '<textarea id=\"checkoutAboutMe\" rows=\"3\" placeholder=\"Tell us a little about what you want to do on BuddyUpGo.\">' + BUDDYUP.escapeHtml(seed.about_me) + '<\/textarea>' +\n                        '<\/div>' +\n                    '<\/div>' +\n                    '<div id=\"buddyUpCheckoutProfileError\" class=\"buddyUpErrorMessage hidden\"><\/div>' +\n                    '<div class=\"buddyUpCheckoutProfileActions\">' +\n                        '<button type=\"button\" id=\"buddyUpCheckoutProfileCancel\" class=\"buddyUpButton3\">Cancel<\/button>' +\n                        '<button type=\"submit\" id=\"buddyUpCheckoutProfileSubmit\" class=\"buddyUpButton1\">Checkout<\/button>' +\n                    '<\/div>' +\n                '<\/form>';\n\n            BUDDYUP.openModal(body, 'Complete Your Subscription Profile', 'buddyUpCheckoutProfileModal');\n\n            var closeBtn = document.getElementById('buddyUpCloseModalButton');\n            if (closeBtn) {\n                closeBtn.addEventListener('click', function() { finish(null); }, { once: true });\n            }\n\n            var overlay = document.querySelector('#buddyUpModalWrapper .buddyUpModalOverlay');\n            if (overlay) {\n                overlay.addEventListener('click', function() { finish(null); }, { once: true });\n            }\n\n            var form = document.getElementById('buddyUpCheckoutProfileForm');\n            var cancelBtn = document.getElementById('buddyUpCheckoutProfileCancel');\n            var saveBtn = document.getElementById('buddyUpCheckoutProfileSubmit');\n            var errEl = document.getElementById('buddyUpCheckoutProfileError');\n\n            if (cancelBtn) {\n                cancelBtn.addEventListener('click', function() {\n                    BUDDYUP.closeModal();\n                    finish(null);\n                });\n            }\n\n            if (!form || !saveBtn || !errEl) {\n                finish(null);\n                return;\n            }\n\n            form.addEventListener('submit', async function(e) {\n                e.preventDefault();\n\n                var payload = {\n                    first_name: String((document.getElementById('checkoutFirstName') || {}).value || '').trim(),\n                    last_name: String((document.getElementById('checkoutLastName') || {}).value || '').trim(),\n                    mobile_phone: String((document.getElementById('checkoutPhone') || {}).value || '').trim(),\n                    postal_code: String((document.getElementById('checkoutZip') || {}).value || '').trim(),\n                    address_line_1: String((document.getElementById('checkoutAddress1') || {}).value || '').trim(),\n                    address_line_2: String((document.getElementById('checkoutAddress2') || {}).value || '').trim(),\n                    city: String((document.getElementById('checkoutCity') || {}).value || '').trim(),\n                    state: String((document.getElementById('checkoutState') || {}).value || '').trim(),\n                    about_me: String((document.getElementById('checkoutAboutMe') || {}).value || '').trim(),\n                    email: seed.email || readFirstString(user || {}, ['email_address', 'email'])\n                };\n\n                if (!payload.first_name || !payload.last_name || !payload.mobile_phone || !payload.postal_code || !payload.address_line_1 || !payload.city || !payload.state) {\n                    errEl.textContent = 'Please complete all required fields before checkout.';\n                    errEl.classList.remove('hidden');\n                    return;\n                }\n\n                errEl.classList.add('hidden');\n                saveBtn.setAttribute('disabled', 'disabled');\n                saveBtn.textContent = 'Saving...';\n\n                try {\n                    var editPayload = {\n                        id: user.id,\n                        user_token: user.user_token,\n                        first_name: payload.first_name,\n                        last_name: payload.last_name,\n                        mobile_phone: payload.mobile_phone,\n                        city: payload.city,\n                        state: payload.state,\n                        postal_code: payload.postal_code\n                    };\n                    if (payload.about_me) {\n                        editPayload.about_me = payload.about_me;\n                    }\n\n                    var editRes = await BUDDYUP.apiRequest('account-edit', editPayload);\n                    if (!editRes || editRes.status !== 'success') {\n                        var msg = (editRes && (editRes.message || editRes.data)) ? (editRes.message || editRes.data) : 'Could not save your profile details.';\n                        throw new Error(String(msg));\n                    }\n\n                    var updated = accountDataFromResponse(editRes);\n                    if (updated && typeof updated === 'object') {\n                        var mergedUser = Object.assign({}, user, updated);\n                        if (typeof BUDDYUP.setUser === 'function') {\n                            BUDDYUP.setUser(mergedUser);\n                        } else if (window.localStorage) {\n                            localStorage.setItem('buddyup_user', JSON.stringify(mergedUser));\n                        }\n                    }\n\n                    BUDDYUP.closeModal();\n                    finish(payload);\n                } catch (saveErr) {\n                    errEl.textContent = saveErr && saveErr.message ? saveErr.message : 'Could not save your profile details.';\n                    errEl.classList.remove('hidden');\n                    saveBtn.removeAttribute('disabled');\n                    saveBtn.textContent = 'Checkout';\n                }\n            });\n        });\n    }\n\n    function persistCheckoutTierHint(tierHint, accountRes) {\n        try {\n            var tier = normalizeTier(tierHint);\n            if (tier <= 0) return;\n\n            var user = (typeof BUDDYUP !== 'undefined' && typeof BUDDYUP.getCurrentUser === 'function')\n                ? BUDDYUP.getCurrentUser()\n                : getCurrentUser();\n            if (!user || typeof user !== 'object') return;\n\n            var existing = normalizeTier(\n                user.user_subscription ?? user.subscription ?? user.subscription_tier ?? user.tier ?? 0\n            );\n            var nextTier = Math.max(existing, tier);\n\n            user.user_subscription = nextTier;\n            user.subscription = nextTier;\n            user.subscription_tier = nextTier;\n            user.tier = nextTier;\n\n            if (accountRes && (accountRes.subscription_expires_at || accountRes.expires_at)) {\n                user.subscription_expires_at = accountRes.subscription_expires_at || accountRes.expires_at;\n            }\n\n            if (typeof BUDDYUP !== 'undefined' && typeof BUDDYUP.setUser === 'function') {\n                BUDDYUP.setUser(user);\n            } else if (window.localStorage) {\n                localStorage.setItem('buddyup_user', JSON.stringify(user));\n            }\n        } catch (e) {}\n    }\n\n    function showCheckoutProcessing(message) {\n        var processing = document.getElementById('buddyUpCheckoutProcessing');\n        var messageEl = document.getElementById('buddyUpCheckoutProcessingMessage');\n        if (!processing) return;\n\n        if (messageEl && message) messageEl.textContent = message;\n        processing.style.display = 'flex';\n        _checkoutProcessingVisible = true;\n        checkoutLog('checkout.processing.show', { message: message || null });\n    }\n\n    function hideCheckoutProcessing() {\n        var processing = document.getElementById('buddyUpCheckoutProcessing');\n        if (!processing) return;\n        processing.style.display = 'none';\n        _checkoutProcessingVisible = false;\n    }\n\n    function closeCheckoutModal() {\n        checkoutLog('modal.close');\n        const overlay = document.getElementById('buddyUpCheckoutOverlay');\n        if (overlay) overlay.remove();\n\n        if (_checkoutCleanupFns.length > 0) {\n            _checkoutCleanupFns.forEach(function(fn) {\n                try { fn(); } catch (e) {}\n            });\n            _checkoutCleanupFns = [];\n        }\n\n        document.querySelector('html').style.overflow = '';\n        if (_checkoutPollTimer) {\n            clearTimeout(_checkoutPollTimer);\n            _checkoutPollTimer = null;\n        }\n        _checkoutPollInFlight = false;\n        _checkoutPollAttempts = 0;\n        _checkoutToken = null;\n        _checkoutTargetTier = 0;\n        _checkoutIframeLoadCount = 0;\n        _checkoutLastIframeLoadAt = 0;\n        _checkoutSubmitLikely = false;\n        _checkoutSubmitLikelyReason = null;\n        _checkoutProcessingVisible = false;\n        _checkoutInteractionCount = 0;\n        _checkoutLastInteractionAt = 0;\n        _checkoutIframeFocusedSince = 0;\n        _checkoutActiveLink = null;\n        _checkoutTraceLastPersistAt = 0;\n        _checkoutLastVerifyAt = 0;\n        _checkoutVerifyUserId = 0;\n        _checkoutVerifyUserToken = '';\n        _checkoutRunPoll = null;\n    }\n\n    function showCheckoutError(msg) {\n        checkoutLog('checkout.error', { message: msg });\n        closeCheckoutModal();\n        if (typeof BUDDYUP !== 'undefined' && BUDDYUP.openModal) {\n            BUDDYUP.openModal('<p>' + msg + '<\/p>', 'Checkout Error', 'buddyUpCheckoutErrorModal');\n        } else {\n            alert(msg);\n        }\n    }\n\n    function showCheckoutSuccess(tierHint) {\n        if (_checkoutComplete) return;\n        _checkoutComplete = true;\n        tierHint = normalizeTier(typeof tierHint === 'undefined' ? _checkoutTargetTier : tierHint);\n        checkoutLog('checkout.success');\n\n        if (_checkoutPollTimer) {\n            clearTimeout(_checkoutPollTimer);\n            _checkoutPollTimer = null;\n        }\n\n        hideCheckoutProcessing();\n        persistCheckoutTierHint(tierHint);\n\n        const body = document.getElementById('buddyUpCheckoutBody');\n        const success = document.getElementById('buddyUpCheckoutSuccess');\n        if (body) body.style.display = 'none';\n        if (success) success.style.display = 'block';\n\n        \/\/ Refresh subscription data so the page reflects the new tier\n        if (typeof BUDDYUP !== 'undefined' && BUDDYUP.apiRequest) {\n            var currentUser = getCurrentUser();\n            var userId = currentUser && currentUser.id ? parseInt(currentUser.id, 10) : 0;\n            var userToken = currentUser && currentUser.user_token ? String(currentUser.user_token) : '';\n\n            if (Number.isFinite(userId) && userId > 0) {\n                BUDDYUP.apiRequest('hyfin-fetch-payments', {\n                    apply_for_user: String(userId),\n                    updated_after: _checkoutStartedAt,\n                    checkout_started_at: _checkoutStartedAt,\n                    expected_tier: tierHint,\n                    only_successful: true,\n                    force_remote: true,\n                    id: userId,\n                    user_token: userToken,\n                    _cb: Date.now(),\n                    _force: true,\n                }).then(function(fetchRes) {\n                    checkoutLog('checkout.success.fetch_payments', {\n                        rid: fetchRes && fetchRes.rid ? String(fetchRes.rid) : null,\n                        servedFrom: fetchRes && fetchRes.served_from ? String(fetchRes.served_from) : null,\n                    });\n                }).catch(function(syncErr) {\n                    checkoutLog('checkout.success.fetch_payments_error', {\n                        message: syncErr && syncErr.message ? syncErr.message : String(syncErr),\n                    });\n                });\n            }\n\n            BUDDYUP.apiRequest('account-get', { _force: true }).then(function(res) {\n                var confirmedTier = parseTierFromAccount(res);\n                if (confirmedTier > 0 || tierHint > 0) {\n                    persistCheckoutTierHint(Math.max(confirmedTier, tierHint), res);\n                }\n                if (res && typeof BUDDYUP.resolveSubscriptionTier === 'function') {\n                    BUDDYUP.resolveSubscriptionTier(true);\n                }\n            }).catch(function(){});\n        }\n\n        var accountLink = (typeof buddyUpVariables !== 'undefined' && buddyUpVariables.account_link)\n            ? String(buddyUpVariables.account_link)\n            : '\/buddyup\/account';\n        var redirectUrl = accountLink + (accountLink.indexOf('?') === -1 ? '?' : '&') + 'subscription_success=1';\n        if (tierHint > 0) {\n            redirectUrl += '&subscription_tier=' + encodeURIComponent(String(tierHint));\n        }\n\n        var continueBtn = document.getElementById('buddyUpCheckoutSuccessContinue');\n        if (continueBtn) {\n            continueBtn.setAttribute('href', redirectUrl);\n        }\n\n        \/\/ Auto-redirect to account so users are not stranded on iframe receipt screens.\n        setTimeout(function() {\n            window.location.href = redirectUrl;\n        }, 900);\n    }\n\n    function parseTierFromAccount(res) {\n        var sources = [\n            res,\n            res && res.data,\n            res && res.user,\n            res && res.data && res.data.user\n        ];\n\n        for (var i = 0; i < sources.length; i++) {\n            var src = sources[i];\n            if (!src || typeof src !== 'object') continue;\n            var raw = src.user_subscription;\n            if (typeof raw === 'undefined') raw = src.subscription;\n            if (typeof raw === 'undefined') raw = src.subscription_tier;\n            if (typeof raw === 'undefined') raw = src.tier;\n            if (typeof raw === 'undefined') continue;\n\n            var parsed = parseInt(raw);\n            if (!isNaN(parsed) && [0, 1, 2].includes(parsed)) {\n                return parsed;\n            }\n        }\n\n        return 0;\n    }\n\n    function extractRecordsArray(res) {\n        if (!res) return [];\n        if (Array.isArray(res)) return res;\n        if (Array.isArray(res.records)) return res.records;\n        if (res.data && Array.isArray(res.data.records)) return res.data.records;\n        if (res.result && Array.isArray(res.result.records)) return res.result.records;\n        return [];\n    }\n\n    function parseRecordTimestamp(rec) {\n        if (!rec || typeof rec !== 'object') return null;\n\n        var candidates = ['updated_on', 'updated_at', 'paid_on', 'created_on', 'created_at', 'date'];\n        for (var i = 0; i < candidates.length; i++) {\n            var raw = rec[candidates[i]];\n            if (!raw && rec.subscription && typeof rec.subscription === 'object') {\n                raw = rec.subscription[candidates[i]];\n            }\n            if (!raw) continue;\n\n            var ts = Date.parse(String(raw));\n            if (Number.isFinite(ts)) return ts;\n        }\n\n        return null;\n    }\n\n    function isSubscriptionRecordActive(rec) {\n        if (!rec || typeof rec !== 'object') return false;\n\n        if (typeof rec.active !== 'undefined') {\n            if (rec.active === true) return true;\n            if (typeof rec.active === 'string') {\n                var v = rec.active.toLowerCase().trim();\n                return v === 'true' || v === '1' || v === 'yes' || v === 'on';\n            }\n            return false;\n        }\n\n        var status = (typeof rec.status === 'string' ? rec.status : '').toLowerCase().trim();\n        if (!status) return true;\n        return ['closed', 'cancelled', 'canceled', 'inactive', 'expired', 'failed'].indexOf(status) === -1;\n    }\n\n    function doesSubscriptionRecordMatchUser(rec, userId) {\n        if (!rec || typeof rec !== 'object') return false;\n        var uid = String(userId || '').trim();\n        if (!uid) return false;\n\n        var candidates = [];\n        candidates.push(rec.customer_external_id);\n        candidates.push(rec.external_id);\n        if (rec.customer && typeof rec.customer === 'object') {\n            candidates.push(rec.customer.external_id);\n            candidates.push(rec.customer.reference_number);\n        }\n\n        for (var i = 0; i < candidates.length; i++) {\n            var raw = candidates[i];\n            if (raw === null || typeof raw === 'undefined') continue;\n            var s = String(raw).trim();\n            if (!s) continue;\n            if (s === uid) return true;\n            var m = s.match(\/\\d+\/);\n            if (m && m[0] === uid) return true;\n        }\n\n        return false;\n    }\n\n    function inferTierFromSubscriptionRecord(rec) {\n        if (!rec || typeof rec !== 'object') return 0;\n\n        var textBits = [];\n        if (typeof rec.product_external_id === 'string') textBits.push(rec.product_external_id);\n        if (typeof rec.external_id === 'string') textBits.push(rec.external_id);\n        if (typeof rec.description === 'string') textBits.push(rec.description);\n        if (rec.product && typeof rec.product === 'object') {\n            if (typeof rec.product.external_id === 'string') textBits.push(rec.product.external_id);\n            if (typeof rec.product.name === 'string') textBits.push(rec.product.name);\n        }\n        var text = textBits.join(' ').toLowerCase();\n        if (text.indexOf('enthusiast') !== -1) return 2;\n        if (text.indexOf('adventurer') !== -1) return 1;\n\n        var amount = parseFloat(rec.amount);\n        if (Number.isFinite(amount)) {\n            if (Math.abs(amount - 8.99) < 0.06) return 2;\n            if (Math.abs(amount - 6.99) < 0.06) return 1;\n        }\n\n        return 0;\n    }\n\n    function expectedAmountForTier(tier) {\n        var t = normalizeTier(tier);\n        if (t === 1) return 6.99;\n        if (t === 2) return 8.99;\n        return null;\n    }\n\n    function shouldFetchPaymentsForReason(reason) {\n        if (_checkoutSubmitLikely) return true;\n        var r = String(reason || '').toLowerCase();\n        return \/(return|postmessage|focus|visible|manual|submit|iframe)\/.test(r);\n    }\n\n    async function runCheckoutVerification(reason) {\n        if (_checkoutComplete || _checkoutPollInFlight) return;\n\n        var nowMs = Date.now();\n        if (_checkoutLastVerifyAt > 0 && (nowMs - _checkoutLastVerifyAt) < 900) {\n            scheduleCheckoutVerificationSoon(950, 'verify_cooldown');\n            return;\n        }\n\n        _checkoutPollInFlight = true;\n        _checkoutPollAttempts += 1;\n        _checkoutLastVerifyAt = nowMs;\n\n        var userId = _checkoutVerifyUserId > 0 ? _checkoutVerifyUserId : 0;\n        var userToken = _checkoutVerifyUserToken || '';\n        var targetTier = normalizeTier(_checkoutTargetTier);\n        var expectedAmount = expectedAmountForTier(targetTier);\n\n        checkoutLog('reconcile.verify.start', {\n            reason: reason || null,\n            attempt: _checkoutPollAttempts,\n            userId: userId,\n            targetTier: targetTier,\n        });\n\n        try {\n            if (_checkoutSubmitLikely && !_checkoutProcessingVisible) {\n                showCheckoutProcessing('Payment submitted. Finalizing your subscription...');\n            }\n\n            if (_checkoutToken || userId) {\n                var checkPayload = {\n                    token: _checkoutToken || null,\n                    user_id: userId ? String(userId) : null,\n                    tier: targetTier > 0 ? targetTier : null,\n                    checkout_started_at: _checkoutStartedAt,\n                    _cb: Date.now(),\n                    _force: true\n                };\n\n                var checkStartMs = Date.now();\n                var checkRes = await BUDDYUP.apiRequest('hyfin-check-return', checkPayload);\n                var checkDurationMs = Date.now() - checkStartMs;\n\n                var checkEntry = checkRes && checkRes.entry && typeof checkRes.entry === 'object' ? checkRes.entry : {};\n                var checkReconcile = checkRes && checkRes.reconcile && typeof checkRes.reconcile === 'object' ? checkRes.reconcile : {};\n                var checkStatus = String(checkEntry.status || '').toLowerCase();\n                var checkApplied = parseInt(checkReconcile.applied || 0, 10);\n                if (!Number.isFinite(checkApplied)) checkApplied = 0;\n\n                var lastReconcile = checkEntry.last_reconcile && typeof checkEntry.last_reconcile === 'object'\n                    ? checkEntry.last_reconcile\n                    : null;\n                var lastApplied = parseInt(lastReconcile && lastReconcile.applied ? lastReconcile.applied : 0, 10);\n                if (!Number.isFinite(lastApplied)) lastApplied = 0;\n\n                checkoutLog('reconcile.verify.check_return', {\n                    rid: checkRes && checkRes.rid ? String(checkRes.rid) : null,\n                    fallback: checkRes && checkRes.fallback ? String(checkRes.fallback) : null,\n                    status: checkStatus || null,\n                    applied: checkApplied,\n                    lastApplied: lastApplied,\n                    reason: checkReconcile && checkReconcile.reason ? String(checkReconcile.reason) : null,\n                    waitSec: checkReconcile && !isNaN(parseInt(checkReconcile.wait_sec, 10)) ? parseInt(checkReconcile.wait_sec, 10) : null,\n                    durationMs: checkDurationMs,\n                });\n\n                if (checkApplied > 0 || lastApplied > 0 || checkStatus === 'reconciled') {\n                    showCheckoutSuccess(targetTier);\n                    return;\n                }\n            }\n\n            if (userId > 0 && shouldFetchPaymentsForReason(reason)) {\n                var fetchStartMs = Date.now();\n                var reconcileRes = await BUDDYUP.apiRequest('hyfin-fetch-payments', {\n                    apply_for_user: String(userId),\n                    updated_after: _checkoutStartedAt,\n                    checkout_started_at: _checkoutStartedAt,\n                    expected_tier: targetTier,\n                    expected_amount: expectedAmount,\n                    only_successful: true,\n                    force_remote: true,\n                    id: userId,\n                    user_token: userToken,\n                    _cb: Date.now(),\n                    _force: true\n                });\n                var fetchDurationMs = Date.now() - fetchStartMs;\n\n                checkoutLog('reconcile.verify.fetchPayments', {\n                    rid: reconcileRes && reconcileRes.rid ? String(reconcileRes.rid) : null,\n                    servedFrom: reconcileRes && reconcileRes.served_from ? String(reconcileRes.served_from) : null,\n                    count: reconcileRes && !isNaN(parseInt(reconcileRes.count, 10)) ? parseInt(reconcileRes.count, 10) : null,\n                    reconciled: reconcileRes && reconcileRes.reconciled ? reconcileRes.reconciled : null,\n                    diag: reconcileRes && reconcileRes.diag ? reconcileRes.diag : null,\n                    durationMs: fetchDurationMs,\n                });\n\n                var recMeta = reconcileRes && reconcileRes.reconciled\n                    ? reconcileRes.reconciled\n                    : (reconcileRes && reconcileRes.data && reconcileRes.data.reconciled ? reconcileRes.data.reconciled : null);\n                var appliedCount = recMeta && !isNaN(parseInt(recMeta.applied, 10)) ? parseInt(recMeta.applied, 10) : 0;\n                if (appliedCount > 0) {\n                    showCheckoutSuccess(targetTier);\n                    return;\n                }\n            }\n\n            if (userId > 0) {\n                var accountStartMs = Date.now();\n                var acct = await BUDDYUP.apiRequest('account-get', {\n                    _force: true,\n                    _cb: Date.now()\n                });\n                var accountDurationMs = Date.now() - accountStartMs;\n                var dbTier = parseTierFromAccount(acct);\n\n                checkoutLog('reconcile.verify.account', {\n                    dbTier: dbTier,\n                    targetTier: targetTier,\n                    durationMs: accountDurationMs,\n                });\n\n                if (dbTier >= targetTier) {\n                    showCheckoutSuccess(targetTier);\n                    return;\n                }\n            }\n\n            var canRetry = _checkoutSubmitLikely && _checkoutPollAttempts < _checkoutMaxVerifyAttempts;\n            if (canRetry) {\n                var retryMs = (_checkoutPollAttempts <= 1) ? 3500 : 7000;\n                checkoutLog('reconcile.verify.retry_scheduled', {\n                    waitMs: retryMs,\n                    attempt: _checkoutPollAttempts,\n                });\n                scheduleCheckoutVerificationSoon(retryMs, 'bounded_retry');\n            } else {\n                checkoutLog('reconcile.verify.pending', {\n                    attempt: _checkoutPollAttempts,\n                    reason: reason || null,\n                });\n            }\n        } catch (verifyErr) {\n            console.warn('[BuddyUp] Checkout verification failed:', verifyErr);\n            checkoutLog('reconcile.verify.error', {\n                message: verifyErr && verifyErr.message ? verifyErr.message : String(verifyErr),\n                attempt: _checkoutPollAttempts,\n                reason: reason || null,\n            });\n\n            if (_checkoutSubmitLikely && _checkoutPollAttempts < _checkoutMaxVerifyAttempts) {\n                scheduleCheckoutVerificationSoon(7000, 'verify_error_retry');\n            }\n        } finally {\n            _checkoutPollInFlight = false;\n        }\n    }\n\n    function startCheckoutReconciliation(userId, targetTier) {\n        checkoutLog('reconcile.start', { userId: userId, targetTier: targetTier, mode: 'event_driven' });\n\n        if (_checkoutPollTimer) {\n            clearTimeout(_checkoutPollTimer);\n            _checkoutPollTimer = null;\n        }\n\n        _checkoutPollAttempts = 0;\n        _checkoutPollInFlight = false;\n        _checkoutLastVerifyAt = 0;\n\n        _checkoutVerifyUserId = parseInt(userId, 10);\n        if (!Number.isFinite(_checkoutVerifyUserId) || _checkoutVerifyUserId <= 0) _checkoutVerifyUserId = 0;\n\n        var currentUser = getCurrentUser();\n        _checkoutVerifyUserToken = currentUser && currentUser.user_token ? String(currentUser.user_token) : '';\n\n        _checkoutRunPoll = function(reason) {\n            runCheckoutVerification(reason || 'scheduled');\n        };\n\n        \/\/ Single initial verification pass; subsequent checks are event-triggered with bounded retries only.\n        scheduleCheckoutVerificationSoon(1400, 'initial_verification');\n    }\n\n    \/\/ Listen for postMessage from the Hyfin return endpoint (same-origin iframe)\n    window.addEventListener('message', function(event) {\n        const checkoutIframe = document.querySelector('#buddyUpCheckoutBody iframe');\n        const fromCheckoutIframe = !!(checkoutIframe && checkoutIframe.contentWindow && event.source === checkoutIframe.contentWindow);\n\n        if (fromCheckoutIframe) {\n            const summary = summarizeMessagePayload(event.data);\n            checkoutLog('checkout.postMessage.iframe', {\n                origin: event.origin || null,\n                payload: summary,\n            });\n\n            \/\/ If the user interacted with the iframe at all, ANY postMessage after the boot window\n            \/\/ is a strong signal the checkout SPA changed state (payment result, success screen,\n            \/\/ demo-mode completion, etc.).\n            if (!_checkoutSubmitLikely && !_checkoutComplete && _checkoutInteractionCount >= 1 && canInferSubmitNow()) {\n                markCheckoutSubmitLikely('postMessage_after_interaction', {\n                    origin: event.origin || null,\n                    interactionCount: _checkoutInteractionCount,\n                    iframeLoadCount: _checkoutIframeLoadCount,\n                });\n                scheduleCheckoutVerificationSoon(450, 'postmessage_after_interaction');\n            } else if (!_checkoutSubmitLikely && !_checkoutComplete && canInferSubmitNow()) {\n                \/\/ Keyword-based fallback when no recorded interaction.\n                let blob = '';\n                try {\n                    blob = typeof event.data === 'string'\n                        ? event.data.toLowerCase()\n                        : JSON.stringify(event.data || {}).toLowerCase();\n                } catch (e) { blob = ''; }\n                if (blob && \/(submit|submitted|submitting|success|processing|processed|redirecting|redirected|3ds|challenge|authorize|authorizing|authorized|otp|complete|completed|demomode|demo_mode)\/.test(blob)) {\n                    markCheckoutSubmitLikely('postMessage_keyword', {\n                        origin: event.origin || null,\n                        interactionCount: _checkoutInteractionCount,\n                        iframeLoadCount: _checkoutIframeLoadCount,\n                    });\n                    scheduleCheckoutVerificationSoon(450, 'postmessage_keyword');\n                }\n            }\n        }\n\n        if (!event.data || typeof event.data !== 'object') return;\n        if (!event.data.hyfin_return) return;\n\n        checkoutLog('postMessage.received', event.data);\n\n        if (event.data.hyfin_return === 'ok') {\n            markCheckoutSubmitLikely('return_postmessage', {\n                activated: !!event.data.activated,\n                foundCount: Array.isArray(event.data.found) ? event.data.found.length : 0,\n            });\n            showCheckoutProcessing('Payment submitted. Finalizing your subscription...');\n            scheduleCheckoutVerificationSoon(150, 'return_postmessage');\n        } else {\n            \/\/ Error or unknown token - still close gracefully\n            showCheckoutError('Payment could not be confirmed. Please check your account or try again.');\n        }\n    });\n\n    window.buddyUpStartCheckout = async function(btn) {\n        var user = getCurrentUser();\n        var productId = btn.getAttribute('data-product');\n        var tier = parseInt(btn.getAttribute('data-tier')) || 1;\n\n        beginCheckoutTrace({\n            productId: productId,\n            tier: tier,\n            userId: user && user.id ? user.id : null,\n        });\n\n        _checkoutComplete = false;\n        _checkoutTargetTier = normalizeTier(tier);\n        _checkoutIframeLoadCount = 0;\n        _checkoutLastIframeLoadAt = 0;\n        _checkoutSubmitLikely = false;\n        _checkoutSubmitLikelyReason = null;\n        _checkoutProcessingVisible = false;\n        _checkoutInteractionCount = 0;\n        _checkoutLastInteractionAt = 0;\n        _checkoutIframeFocusedSince = 0;\n        _checkoutStartedAt = new Date(Date.now() - (2 * 60 * 1000)).toISOString();\n        _checkoutLastVerifyAt = 0;\n        _checkoutVerifyUserId = 0;\n        _checkoutVerifyUserToken = '';\n        checkoutLog('checkout.start', { userId: user && user.id, productId: productId, tier: tier, startedAt: _checkoutStartedAt });\n\n        \/\/ 1. Must be logged in\n        if (!user || !user.id) {\n            if (typeof BUDDYUP !== 'undefined' && BUDDYUP.openModal) {\n                var loginLink = (typeof buddyUpVariables !== 'undefined' && buddyUpVariables.login_link) ? buddyUpVariables.login_link : '\/login';\n                BUDDYUP.openModal(\n                    '<div style=\"max-width:350px;margin:0 auto;\"><p>Please log in to purchase a subscription.<\/p><a href=\"' + loginLink + '\" class=\"buddyUpButton1\">Log In<\/a><\/div>',\n                    'Log In Required',\n                    'buddyUpLoginRequiredModal'\n                );\n            } else {\n                alert('Please log in to purchase a subscription.');\n            }\n            return;\n        }\n\n        var accountRes = null;\n        var currentTier = 0;\n\n        \/\/ Preflight guard: never allow same-tier (or lower) checkout while paid tier is active.\n        try {\n            accountRes = await BUDDYUP.apiRequest('account-get', { id: user.id, _force: true });\n            currentTier = parseTierFromAccount(accountRes);\n            if (currentTier >= tier) {\n                var msg = currentTier > tier\n                    ? 'Your account already has a higher active tier. This checkout is disabled.'\n                    : 'You already have this subscription tier active. You cannot purchase the same tier again.';\n                showCheckoutError(msg);\n                return;\n            }\n            if (currentTier > 0 && currentTier < tier) {\n                checkoutLog('checkout.upgrade.mode', { fromTier: currentTier, toTier: tier });\n            }\n        } catch (preflightErr) {\n            checkoutLog('checkout.preflight.error', { message: preflightErr && preflightErr.message ? preflightErr.message : String(preflightErr) });\n        }\n\n        var completionData = null;\n        var skipCompletion = hasEverSubscribed(accountRes, currentTier, user);\n        if (!skipCompletion) {\n            completionData = await requestCheckoutProfileCompletion(accountRes, user, tier);\n            if (!completionData) {\n                checkoutLog('checkout.profile_completion.cancelled', { tier: tier });\n                return;\n            }\n\n            \/\/ Refresh account after completion save so subsequent checks use fresh values.\n            try {\n                accountRes = await BUDDYUP.apiRequest('account-get', { id: user.id, _force: true });\n            } catch (refreshErr) {\n                checkoutLog('checkout.profile_completion.refresh_error', { message: refreshErr && refreshErr.message ? refreshErr.message : String(refreshErr) });\n            }\n        } else {\n            checkoutLog('checkout.profile_completion.skipped', { reason: 'prior_subscription_history' });\n        }\n\n        \/\/ 2. Build the checkout modal with loading state\n        document.querySelector('html').style.overflow = 'hidden';\n        var tierName = tier === 2 ? 'Enthusiast' : 'Adventurer';\n        var modalHtml = '<div id=\"buddyUpCheckoutOverlay\">' +\n            '<div id=\"buddyUpCheckoutModal\">' +\n                '<div id=\"buddyUpCheckoutHeader\">' +\n                    '<h3>Subscribe \u2014 ' + tierName + '<\/h3>' +\n                    '<button id=\"buddyUpCheckoutClose\" aria-label=\"Close checkout\">&times;<\/button>' +\n                '<\/div>' +\n                '<div id=\"buddyUpCheckoutBody\">' +\n                    '<div id=\"buddyUpCheckoutLoading\">Preparing checkout&hellip;<\/div>' +\n                    '<div id=\"buddyUpCheckoutFallbackWrap\" style=\"display:none; margin:10px 0 0; text-align:center;\">' +\n                        '<button type=\"button\" id=\"buddyUpCheckoutOpenExternal\" class=\"buddyUpButton3\">Having trouble? Open checkout in a new tab<\/button>' +\n                    '<\/div>' +\n                    '<div id=\"buddyUpCheckoutProcessing\">' +\n                        '<div class=\"buddyUpCheckoutProcessingInner\">' +\n                            '<div class=\"buddyUpCheckoutSpinner\"><\/div>' +\n                            '<p id=\"buddyUpCheckoutProcessingMessage\" style=\"margin:0 0 8px 0; font-weight:600;\">Processing payment...<\/p>' +\n                            '<p class=\"textSmall\" style=\"margin:0; opacity:.85;\">Please keep this window open while we activate your subscription.<\/p>' +\n                        '<\/div>' +\n                    '<\/div>' +\n                '<\/div>' +\n                '<div id=\"buddyUpCheckoutSuccess\">' +\n                    '<i class=\"fas fa-check-circle\"><\/i>' +\n                    '<h3>Payment Received!<\/h3>' +\n                    '<p>Your subscription is active. Redirecting you to your account now.<\/p>' +\n                    '<p style=\"margin-top:12px;\"><a id=\"buddyUpCheckoutSuccessContinue\" class=\"buddyUpButton1\" href=\"#\">Go to Account<\/a><\/p>' +\n                '<\/div>' +\n            '<\/div>' +\n        '<\/div>';\n        document.body.insertAdjacentHTML('beforeend', modalHtml);\n        document.getElementById('buddyUpCheckoutClose').addEventListener('click', closeCheckoutModal);\n        document.getElementById('buddyUpCheckoutOverlay').addEventListener('click', function(e) {\n            if (e.target === this) closeCheckoutModal();\n        });\n\n        try {\n            \/\/ 3. Build the customer payload from account data + completion form values.\n            var customerPayload = buildCheckoutCustomerPayload(user, accountRes, completionData);\n\n            \/\/ Do not pre-create customer via API here; Rapid Checkout will create\/update\n            \/\/ the payer customer and this avoids duplicate customer records.\n\n            \/\/ 4. Store tier mapping so webhook\/return can infer which tier the user is purchasing\n            try {\n                var mappingRes = await BUDDYUP.apiRequest('hyfin-add-subscription', {\n                    external_id: 'buddyup_' + user.id + '_' + productId + '_' + Date.now(),\n                    tier: tier,\n                    customer: { external_id: String(user.id) },\n                    _store_mapping_only: true\n                });\n                checkoutLog('checkout.mapping.saved', mappingRes);\n            } catch(mapErr) {\n                console.warn('[BuddyUp] Tier mapping store failed (non-fatal):', mapErr);\n            }\n\n            \/\/ 5. Fetch payment link with user context (backend creates a correlation token)\n            var linkRes = await BUDDYUP.apiRequest('hyfin-get-payment-link', {\n                product_id: productId,\n                customer: {\n                    external_id: String(user.id),\n                    first_name: customerPayload.first_name,\n                    last_name: customerPayload.last_name,\n                    email: customerPayload.email,\n                    mobile_phone: customerPayload.mobile_phone,\n                    address_line_1: customerPayload.address_line_1,\n                    address_line_2: customerPayload.address_line_2,\n                    city: customerPayload.city,\n                    state: customerPayload.state,\n                    postal_code: customerPayload.postal_code\n                },\n                user_wp_id: user.id,\n                tier: tier,\n                _force: true\n            });\n\n            if (!linkRes || linkRes.status === 'error' || !linkRes.link) {\n                throw new Error(linkRes && linkRes.message ? linkRes.message : 'Could not load payment link. Please try again.');\n            }\n\n            checkoutLog('checkout.link.ready', {\n                token: linkRes.token || null,\n                return_url: linkRes.return_url || null,\n                hasLink: !!linkRes.link\n            });\n\n            _checkoutActiveLink = String(linkRes.link || '');\n            _checkoutToken = linkRes.token || null;\n\n            var fallbackWrap = document.getElementById('buddyUpCheckoutFallbackWrap');\n            var openExternalBtn = document.getElementById('buddyUpCheckoutOpenExternal');\n            if (fallbackWrap) fallbackWrap.style.display = _checkoutActiveLink ? 'block' : 'none';\n            if (openExternalBtn && _checkoutActiveLink) {\n                openExternalBtn.addEventListener('click', function() {\n                    checkoutLog('checkout.external_open.requested', { hasLink: !!_checkoutActiveLink });\n                    var opened = null;\n                    try {\n                        opened = window.open(_checkoutActiveLink, '_blank', 'noopener,noreferrer');\n                    } catch (e) {\n                        opened = null;\n                    }\n\n                    if (!opened) {\n                        checkoutLog('checkout.external_open.popup_blocked', {});\n                        window.location.href = _checkoutActiveLink;\n                        return;\n                    }\n\n                    showCheckoutProcessing('Checkout opened in a new tab. Complete payment there and we will finish here automatically.');\n                    checkoutLog('checkout.external_open.opened', {});\n                });\n            }\n\n            \/\/ 6. Load Hyfin Rapid Checkout in modal iframe\n            var loading = document.getElementById('buddyUpCheckoutLoading');\n            if (loading) loading.innerHTML = 'Loading secure checkout&hellip;';\n\n            var iframe = document.createElement('iframe');\n            iframe.src = linkRes.link;\n            iframe.title = tierName + ' checkout';\n            iframe.setAttribute('allow', 'payment');\n            iframe.setAttribute('autocomplete', 'off');\n            iframe.setAttribute('data-1p-ignore', 'true');\n            iframe.addEventListener('load', function() {\n                _checkoutIframeLoadCount += 1;\n                _checkoutLastIframeLoadAt = Date.now();\n                checkoutLog('checkout.iframe.load', { src: iframe.src, count: _checkoutIframeLoadCount });\n\n                if (_checkoutIframeLoadCount === 1) {\n                    if (loading) loading.remove();\n                    return;\n                }\n\n                \/\/ Second load = Hyfin internal navigation (product \u2192 payment form), NOT a submit signal.\n                if (_checkoutIframeLoadCount >= 2) {\n                    checkoutLog('checkout.iframe.second_load', { count: _checkoutIframeLoadCount });\n                }\n            });\n            document.getElementById('buddyUpCheckoutBody').appendChild(iframe);\n            installCheckoutObservers(iframe);\n\n            \/\/ Rapid checkout does not always redirect to return_url in iframe mode.\n            \/\/ Start event-driven backend verification so successful payment webhooks activate DB subscription.\n            startCheckoutReconciliation(user.id, tier);\n\n        } catch(err) {\n            showCheckoutError(err.message || 'An error occurred starting checkout. Please try again.');\n        }\n    };\n})();\n<\/script>\n\n<section id=\"tiersOverview\">\n    <h2>Overview of Subscription Tiers<\/h2>\n    <table id=\"desktopViewTable\">\n        <thead>\n            <tr>\n                <td>\n                    <strong>Features<\/strong>\n                <\/td>\n                                    <td>\n                        <strong>Explorer(Free)<\/strong>\n                    <\/td>\n                                    <td>\n                        <strong>Adventurer<\/strong>\n                    <\/td>\n                                    <td>\n                        <strong>Enthusiast<\/strong>\n                    <\/td>\n                            <\/tr>\n        <\/thead>\n        <tbody>\n            \n                            <tr>\n                    <td>MatchUp: 10 right-swipes per day, limited info on matches, 3 reveals per week<\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Groups: Can join but can't interact, create up to 2 free groups<\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Events: Can view and join, create 3 free events per month (up to 25 attendees per event)<\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Event Comments: Can comment with 1 image\/video<\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Buddies: Up to 25<\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Messaging: 5 messages per day<\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Profile Videos: No videos<\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Profile Images: BuddyUp stock images only<\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Privacy: All or nothing<\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Ads: Display ads<\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>MatchUp: Unlimited right-swipes, see matches but not who swiped right<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Groups: Can fully interact, create up to 3 premium groups<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Events: Create up to 4 events per month<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Event Comments: Can comment with up to 3 images\/videos<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Buddies: Up to 75<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Messaging: Unlimited<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Profile Videos: 3 videos (30 sec total)<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Profile Images: Custom uploads unlocked (up to 5 changes)<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Planned perks: advanced planning\/filter tools<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>MatchUp: Unlimited right-swipes, see who swiped right<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Groups: Can fully interact, create up to 15 premium groups<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Events: Unlimited events<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Event Comments: Can comment with up to 8 images\/videos<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Buddies: Up to 1,500<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Profile Videos: 10 videos<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Profile Images: Custom uploads unlocked (up to 10 changes)<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Privacy: Granular control<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Ads: Ad free<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                            <tr>\n                    <td>Planned perks: premium spotlight placement in Explore\/MatchUp<\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u274c                        <\/td>\n                                            <td>\n                            \u2714\ufe0f                        <\/td>\n                                    <\/tr>\n                    <\/tbody>\n    <\/table>\n    <div class=\"tierCardsOverview\">\n                    <table id=\"explorer(free)_overview_table\">\n                <thead class=\"tierNameTableMobile\">\n                    <tr>\n                        <td>Explorer(Free)<\/td>\n                    <\/tr>\n                <\/thead>\n                <tbody class=\"featureNameTableMobile\">\n                                        <tr>\n                        <td>MatchUp: 10 right-swipes per day, limited info on matches, 3 reveals per week<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Groups: Can join but can't interact, create up to 2 free groups<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Events: Can view and join, create 3 free events per month (up to 25 attendees per event)<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Event Comments: Can comment with 1 image\/video<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Buddies: Up to 25<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Messaging: 5 messages per day<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Profile Videos: No videos<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Profile Images: BuddyUp stock images only<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Privacy: All or nothing<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Ads: Display ads<\/td>\n                    <\/tr>\n                                    <\/tbody>\n            <\/table>\n                    <table id=\"adventurer_overview_table\">\n                <thead class=\"tierNameTableMobile\">\n                    <tr>\n                        <td>Adventurer<\/td>\n                    <\/tr>\n                <\/thead>\n                <tbody class=\"featureNameTableMobile\">\n                                        <tr>\n                        <td>MatchUp: Unlimited right-swipes, see matches but not who swiped right<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Groups: Can fully interact, create up to 3 premium groups<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Events: Create up to 4 events per month<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Event Comments: Can comment with up to 3 images\/videos<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Buddies: Up to 75<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Messaging: Unlimited<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Profile Videos: 3 videos (30 sec total)<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Profile Images: Custom uploads unlocked (up to 5 changes)<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Privacy: All or nothing<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Ads: Display ads<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Planned perks: advanced planning\/filter tools<\/td>\n                    <\/tr>\n                                    <\/tbody>\n            <\/table>\n                    <table id=\"enthusiast_overview_table\">\n                <thead class=\"tierNameTableMobile\">\n                    <tr>\n                        <td>Enthusiast<\/td>\n                    <\/tr>\n                <\/thead>\n                <tbody class=\"featureNameTableMobile\">\n                                        <tr>\n                        <td>MatchUp: Unlimited right-swipes, see who swiped right<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Groups: Can fully interact, create up to 15 premium groups<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Events: Unlimited events<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Event Comments: Can comment with up to 8 images\/videos<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Buddies: Up to 1,500<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Messaging: Unlimited<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Profile Videos: 10 videos<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Profile Images: Custom uploads unlocked (up to 10 changes)<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Privacy: Granular control<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Ads: Ad free<\/td>\n                    <\/tr>\n                                        <tr>\n                        <td>Planned perks: premium spotlight placement in Explore\/MatchUp<\/td>\n                    <\/tr>\n                                    <\/tbody>\n            <\/table>\n            <\/div>\n<\/section>\n\n<section id=\"tiersFootNotes\">\n    <span>\n        Explorer: 10 right swipes per day, 3 reveals per week, up to 5 messages per day, up to 2 groups, and up to 3 events per month.\n        Adventurer: unlimited swipes and messaging, up to 3 premium groups, and up to 4 events per month.\n        Enthusiast: unlimited swipes, reveals, messaging, and events, plus up to 15 premium groups.\n    <\/span>\n<\/section><\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"templates\/full-width-no-header.php","meta":{"footnotes":""},"class_list":["post-478","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/buddyupapi-staging.us35.cdn-alpha.com\/api\/wp\/v2\/pages\/478","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/buddyupapi-staging.us35.cdn-alpha.com\/api\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/buddyupapi-staging.us35.cdn-alpha.com\/api\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/buddyupapi-staging.us35.cdn-alpha.com\/api\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/buddyupapi-staging.us35.cdn-alpha.com\/api\/wp\/v2\/comments?post=478"}],"version-history":[{"count":1,"href":"https:\/\/buddyupapi-staging.us35.cdn-alpha.com\/api\/wp\/v2\/pages\/478\/revisions"}],"predecessor-version":[{"id":479,"href":"https:\/\/buddyupapi-staging.us35.cdn-alpha.com\/api\/wp\/v2\/pages\/478\/revisions\/479"}],"wp:attachment":[{"href":"https:\/\/buddyupapi-staging.us35.cdn-alpha.com\/api\/wp\/v2\/media?parent=478"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}