v0.1

ModalsUX
#91 - Hide Popup For Set Duration
Hide a popup for X time when a button is clicked.
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #233 v0.1 💙 ABANDONED CHECKOUT RECOVERY BANNER -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
var CONFIG = {
storageKey: "ms_abandoned_checkout",
jsonKey: "abandoned_checkout_banner",
dismissDays: 3,
maxAgeHours: 48,
bannerDisplay: "flex",
requireLoggedIn: true
};
var memberstack = window.$memberstackDom;
if (!memberstack) {
console.warn("Memberscript # number233: Memberstack not found");
return;
}
var banner = document.querySelector('[data-ms-abandoned="banner"]');
if (!banner) return;
banner.style.display = "none";
var continueBtns = banner.querySelectorAll('[data-ms-abandoned="button"]');
var dismissBtns = banner.querySelectorAll('[data-ms-abandoned="dismiss"]');
var planNameEls = banner.querySelectorAll('[data-ms-code="plan-name"]');
var firstNameEls = banner.querySelectorAll('[data-ms-abandoned="first-name"]');
var storageKey = banner.getAttribute("ms-abandoned-storage-key") || CONFIG.storageKey;
var jsonKey = banner.getAttribute("ms-abandoned-json-key") || CONFIG.jsonKey;
var dismissDays = parseInt(banner.getAttribute("ms-abandoned-dismiss-days"), 10);
if (isNaN(dismissDays)) dismissDays = CONFIG.dismissDays;
var maxAgeHours = parseInt(banner.getAttribute("ms-abandoned-max-age-hours"), 10);
if (isNaN(maxAgeHours)) maxAgeHours = CONFIG.maxAgeHours;
var bannerDisplay = banner.getAttribute("ms-abandoned-display") || CONFIG.bannerDisplay;
var overridePlanId = banner.getAttribute("ms-abandoned-plan") || "";
var overridePriceId = banner.getAttribute("ms-abandoned-price") || "";
var firstNameField = banner.getAttribute("ms-abandoned-first-name-field") || "first-name";
var debug = banner.getAttribute("ms-abandoned-debug") === " keywordtrue";
var requireLoggedInAttr = banner.getAttribute("ms-abandoned-require-logged- keywordin");
var requireLoggedIn = requireLoggedInAttr !== null
? requireLoggedInAttr !== " keywordfalse"
: CONFIG.requireLoggedIn;
var checkoutSelector =
'[data-ms-price\\:add], [data-ms-price\\:update], ' +
'[data-ms-plan\\:add], [data-ms-plan\\:update]';
function readStorage() {
try {
var raw = sessionStorage.getItem(storageKey);
if (!raw) return null;
return JSON.parse(raw);
} catch (e) {
return null;
}
}
function clearStorage() {
try {
sessionStorage.removeItem(storageKey);
} catch (e) {}
}
function isExpired(pending) {
if (!pending || !pending.startedAt) return true;
var started = Date.parse(pending.startedAt);
if (!isFinite(started)) return true;
return Date.now() - started > maxAgeHours * 60 * 60 * 1000;
}
function idsFromElement(el) {
if (!el) return { priceId: "", planId: "", priceAction: "add" };
var priceAction = "add";
if (
el.hasAttribute("data-ms-price:update") ||
el.hasAttribute("data-ms-plan:update")
) {
priceAction = "update";
}
return {
priceId:
el.getAttribute("data-ms-price:add") ||
el.getAttribute("data-ms-price:update") ||
"",
planId:
el.getAttribute("data-ms-plan:add") ||
el.getAttribute("data-ms-plan:update") ||
"",
priceAction: priceAction
};
}
function isInsideBanner(el) {
return !!(banner && el && banner.contains(el));
}
function planNameFromElement(el) {
if (!el || isInsideBanner(el)) return "";
var node = el.parentElement;
while (node && node !== document.body) {
if (isInsideBanner(node)) break;
var matches = [];
var all = node.querySelectorAll('[data-ms-code="plan-name"]');
for (var i = 0; i < all.length; i++) {
if (!isInsideBanner(all[i])) matches.push(all[i]);
}
if (matches.length === 1 && matches[0].textContent) {
return matches[0].textContent.trim().slice(0, 80);
}
node = node.parentElement;
}
if (debug) {
console.warn(
"Memberscript # number233: Add data-ms-code=\"plan-name\" to each plan title(one per card, outside the banner)."
);
}
return "";
}
function savePendingFromElement(el) {
var ids = idsFromElement(el);
if (!ids.priceId && !ids.planId) return;
var prev = readStorage() || {};
var inBanner = isInsideBanner(el);
var planName = inBanner ? (prev.planName || "") : (planNameFromElement(el) || "");
if (!planName && prev.planName && prev.priceId === ids.priceId) {
planName = prev.planName;
}
var pending = {
priceId: ids.priceId,
planId: ids.planId,
planName: planName,
priceAction: ids.priceAction || (prev.priceAction || "add"),
startedAt: new Date().toISOString()
};
try {
sessionStorage.setItem(storageKey, JSON.stringify(pending));
} catch (e) {}
if (debug) console.log("Memberscript # number233: saved pending checkout", pending);
if (!pending.planId && pending.priceId) {
enrichPendingPlanMeta(pending.priceId);
}
}
async function lookupPlanByPriceId(priceId) {
if (!priceId || typeof memberstack.getPlans !== " keywordfunction") return null;
try {
var plansResult = await memberstack.getPlans();
var plans = plansResult?.data || plansResult || [];
if (!Array.isArray(plans)) plans = [];
for (var i = 0; i < plans.length; i++) {
var plan = plans[i];
var prices = plan.prices || plan.priceOptions || plan.stripePrices || [];
for (var j = 0; j < prices.length; j++) {
var p = prices[j];
var pid = p.id || p.priceId || "";
if (pid === priceId) {
return {
planId: (plan.id || plan.planId || "").trim(),
planName: (plan.name || plan.planName || plan.label || "").trim()
};
}
}
}
} catch (e) {}
return null;
}
function enrichPendingPlanMeta(priceId) {
lookupPlanByPriceId(priceId).then(function(found) {
if (!found || !found.planId) return;
var current = readStorage();
if (!current || current.priceId !== priceId) return;
var next = {
priceId: current.priceId,
planId: found.planId,
planName: current.planName || found.planName || "",
priceAction: current.priceAction || "add",
startedAt: current.startedAt
};
try {
sessionStorage.setItem(storageKey, JSON.stringify(next));
} catch (e) {}
if (debug) console.log("Memberscript # number233: enriched planId", next);
});
}
document.addEventListener("click", function(e) {
var el = e.target && e.target.closest ? e.target.closest(checkoutSelector) : null;
if (!el) return;
savePendingFromElement(el);
}, true);
function isActiveConnection(pc) {
if (!pc) return false;
var status = (pc.status || "").toString().toUpperCase();
return status === "ACTIVE" || status === "TRIALING";
}
function memberHasTarget(member, priceId, planId) {
var connections = member.planConnections || [];
for (var i = 0; i < connections.length; i++) {
var pc = connections[i];
if (!isActiveConnection(pc)) continue;
var pcPlan = pc.planId || (pc.plan && pc.plan.id) || "";
var pcPrice = (pc.payment && pc.payment.priceId) || pc.priceId || "";
if (priceId && pcPrice === priceId) return true;
if (planId && pcPlan === planId) return true;
}
return false;
}
async function lookupPlanNameFromMemberstack(planId, priceId) {
if (priceId) {
var byPrice = await lookupPlanByPriceId(priceId);
if (byPrice && byPrice.planName) return byPrice.planName;
}
if (planId && typeof memberstack.getPlan === " keywordfunction") {
try {
var planResult = await memberstack.getPlan({ planId: planId });
var planData = planResult?.data || planResult || {};
return (planData.name || planData.planName || planData.label || "").trim();
} catch (e) {}
}
return "";
}
async function resolvePlanName(planId, priceId, fallback) {
var fromApi = await lookupPlanNameFromMemberstack(planId, priceId);
if (fromApi) return fromApi;
var fb = (fallback || "").trim();
return fb || "your selected plan";
}
var pending = readStorage();
if (!pending || isExpired(pending)) {
if (pending && isExpired(pending)) clearStorage();
return;
}
if (new URLSearchParams(window.location.search).get("ms_abandoned") === " number0") {
clearStorage();
return;
}
var targetPriceId = overridePriceId || pending.priceId || "";
var targetPlanId = overridePlanId || pending.planId || "";
if (!targetPriceId && !targetPlanId) {
clearStorage();
return;
}
var member = null;
try {
var memberResult = await memberstack.getCurrentMember();
member = memberResult?.data || memberResult;
} catch (err) {
console.warn("Memberscript # number233: Could not get member", err);
return;
}
if (requireLoggedIn && (!member || !member.id)) return;
var memberJSON = {};
if (member && member.id) {
try {
var jsonResult = await memberstack.getMemberJSON();
memberJSON = jsonResult?.data || {};
} catch (e) {}
}
async function clearDismissRecord() {
if (!member || !member.id || !memberJSON[jsonKey]) return;
delete memberJSON[jsonKey];
try {
await memberstack.updateMemberJSON({ json: memberJSON });
if (debug) console.log("Memberscript # number233: cleared dismiss JSON(plan acquired)");
} catch (e) {
console.warn("Memberscript # number233: Could not clear dismiss state", e);
}
}
if (member && member.id && memberHasTarget(member, targetPriceId, targetPlanId)) {
clearStorage();
await clearDismissRecord();
return;
}
function getDismiss() {
var d = memberJSON[jsonKey];
if (!d || typeof d !== "object") {
return { dismissed_until: null, dismiss_count: 0, last_price_id: null, last_plan_id: null };
}
return d;
}
async function saveDismiss(data) {
if (!member || !member.id) return;
memberJSON[jsonKey] = data;
try {
await memberstack.updateMemberJSON({ json: memberJSON });
} catch (e) {
console.warn("Memberscript # number233: Could not save dismiss state", e);
}
}
var dismiss = getDismiss();
if (
(dismiss.last_price_id && dismiss.last_price_id !== targetPriceId) ||
(dismiss.last_plan_id && dismiss.last_plan_id !== targetPlanId)
) {
dismiss = {
dismissed_until: null,
dismiss_count: 0,
last_price_id: null,
last_plan_id: null
};
}
if (dismiss.dismissed_until) {
var until = new Date(dismiss.dismissed_until);
if (!isNaN(until) && new Date() < until) return;
}
var planLabel = await resolvePlanName(
targetPlanId,
targetPriceId,
pending.planName
);
planNameEls.forEach(function(el) {
el.textContent = planLabel;
});
var firstName = "";
if (member && member.customFields) {
firstName = (member.customFields[firstNameField] || "").toString().trim();
}
firstNameEls.forEach(function(el) {
if (firstName) {
el.textContent = firstName;
} else {
el.style.display = "none";
}
});
async function launchCheckout() {
if (!targetPriceId && !targetPlanId) {
console.warn("Memberscript # number233: No priceId or planId for checkout");
return;
}
var cancelUrl = window.location.href;
if (cancelUrl.indexOf("ms_abandoned=") === -1) {
cancelUrl += (cancelUrl.indexOf("?") === -1 ? "?" : "&") + "ms_abandoned= number1";
}
try {
if (typeof memberstack.purchasePlansWithCheckout === " keywordfunction" && targetPriceId) {
var result = await memberstack.purchasePlansWithCheckout({
priceId: targetPriceId,
successUrl: window.location.href,
cancelUrl: cancelUrl
});
var checkoutUrl = (result && result.data && result.data.url) || (result && result.url);
if (checkoutUrl) window.location.href = checkoutUrl;
return;
}
if (typeof memberstack.checkout === " keywordfunction" && targetPriceId) {
await memberstack.checkout({ priceId: targetPriceId });
return;
}
if (typeof memberstack.openModal === " keywordfunction" && targetPlanId) {
await memberstack.openModal("SIGNUP", { planId: targetPlanId });
return;
}
console.warn("Memberscript # number233: No checkout method available on Memberstack DOM");
} catch (err) {
console.error("Memberscript # number233: Checkout failed", err);
}
}
continueBtns.forEach(function(btn) {
btn.addEventListener("click", async function(e) {
e.preventDefault();
if (e.stopPropagation) e.stopPropagation();
savePendingFromElement(btn);
await launchCheckout();
});
});
dismissBtns.forEach(function(btn) {
btn.addEventListener("click", async function(e) {
e.preventDefault();
clearStorage();
if (member && member.id) {
var until = new Date();
until.setDate(until.getDate() + dismissDays);
await saveDismiss({
dismissed_until: until.toISOString(),
dismiss_count: (dismiss.dismiss_count || 0) + 1,
last_price_id: targetPriceId || null,
last_plan_id: targetPlanId || null
});
}
banner.style.display = "none";
});
});
banner.style.display = bannerDisplay;
if (debug) console.log("Memberscript # number233: banner shown", pending);
});
</script>More scripts in JSON