v0.1

Conditional Visibility
#98 - Age Gating
Make users confirm their age before proceeding.
Show a "Welcome back! Reactivate your plan" banner to cancelled members with a one-click reactivation checkout link.
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #227 v0.1 💙 REACTIVATION PROMPT BANNER AFTER CANCEL -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
var CONFIG = {
jsonKey: "reactivation_banner",
dismissDays: 7,
requireNoActive: true,
includeExpired: true,
includeScheduledCancel: true,
bannerDisplay: "flex",
dateLocale: "en-US"
};
var memberstack = window.$memberstackDom;
if (!memberstack) {
console.warn("Memberscript # number227: Memberstack not found");
return;
}
// ─── DOM REFERENCES ───
var banner = document.querySelector('[data-ms-reactivate="banner"]');
if (!banner) return;
banner.style.display = "none";
var reactivateBtns = banner.querySelectorAll('[data-ms-reactivate="button"]');
var dismissBtns = banner.querySelectorAll('[data-ms-reactivate="dismiss"]');
var planNameEls = banner.querySelectorAll('[data-ms-reactivate="plan-name"]');
var cancelledEls = banner.querySelectorAll('[data-ms-reactivate="cancelled-date"]');
var firstNameEls = banner.querySelectorAll('[data-ms-reactivate="first-name"]');
// ─── READ CONFIG FROM ATTRIBUTES ───
var jsonKey = banner.getAttribute("ms-reactivate-field") || CONFIG.jsonKey;
var dismissDays = parseInt(banner.getAttribute("ms-reactivate-dismiss-days")) || CONFIG.dismissDays;
var bannerDisplay = banner.getAttribute("ms-reactivate-display") || CONFIG.bannerDisplay;
var overridePlanId = banner.getAttribute("ms-reactivate-plan") || "";
var overridePriceId = banner.getAttribute("ms-reactivate-price") || "";
var firstNameField = banner.getAttribute("ms-reactivate-first-name-field") || "first-name";
var requireNoActiveAttr = banner.getAttribute("ms-reactivate-require-no-active");
var requireNoActive = requireNoActiveAttr !== null
? requireNoActiveAttr !== " keywordfalse"
: CONFIG.requireNoActive;
var includeScheduledAttr = banner.getAttribute("ms-reactivate-include-scheduled");
var includeScheduledCancel = includeScheduledAttr !== null
? includeScheduledAttr !== " keywordfalse"
: CONFIG.includeScheduledCancel;
// ─── GET MEMBER ───
var member;
try {
var memberResult = await memberstack.getCurrentMember();
member = memberResult?.data || memberResult;
} catch (err) {
console.warn("Memberscript # number227: Could not get member", err);
return;
}
if (!member || !member.id) return;
var memberJSON = {};
try {
var jsonResult = await memberstack.getMemberJSON();
memberJSON = jsonResult?.data || {};
} catch (e) {}
// ─── DETECT CANCELLED PLAN ───
var planConnections = member.planConnections || [];
function hasScheduledCancel(pc) {
if (!pc) return false;
if (pc.cancelAtPeriodEnd === true) return true;
if (pc.payment && (pc.payment.cancelAtDate || pc.payment.cancel_at || pc.payment.cancelAt)) return true;
if (pc.cancelAtDate || pc.cancel_at || pc.cancelAt) return true;
return false;
}
function isCancelledConnection(pc) {
if (!pc) return false;
var status = (pc.status || "").toString().toUpperCase();
if (status === "CANCELED" || status === "CANCELLED") return true;
if (CONFIG.includeExpired && (status === "EXPIRED" || status === "PAST_DUE" || status === "UNPAID")) return true;
if (includeScheduledCancel && hasScheduledCancel(pc)) return true;
return false;
}
function isActiveSubscription(pc) {
if (!pc) return false;
var status = (pc.status || "").toString().toUpperCase();
if (status !== "ACTIVE" && status !== "TRIALING") return false;
if (pc.type === "ONETIME" || pc.type === "FREE") return false;
if (includeScheduledCancel && hasScheduledCancel(pc)) return false;
return true;
}
var cancelledPlans = planConnections.filter(isCancelledConnection);
var activePlans = planConnections.filter(isActiveSubscription);
if (cancelledPlans.length === 0) return;
if (requireNoActive && activePlans.length > 0) return;
function toDate(raw) {
if (raw === null || raw === undefined || raw === "") return null;
var d;
if (typeof raw === "number") {
d = new Date(raw < 1e12 ? raw * 1000 : raw);
} else {
d = new Date(raw);
}
return (d && !isNaN(d)) ? d : null;
}
function getEndDate(pc) {
var p = pc.payment || {};
var candidates = [
p.cancelAtDate, p.cancel_at, p.cancelAt,
pc.cancelAtDate, pc.cancel_at, pc.cancelAt,
pc.canceledAt, pc.cancelledAt, pc.endedAt,
p.endedAt, p.canceledAt, p.cancelledAt,
pc.currentPeriodEnd, p.nextBillingDate, p.next_billing_date,
p.lastBillingDate, p.last_billing_date,
pc.updatedAt, pc.createdAt
];
for (var i = 0; i < candidates.length; i++) {
var d = toDate(candidates[i]);
if (d) return d;
}
return null;
}
cancelledPlans.sort(function(a, b) {
var da = getEndDate(a);
var db = getEndDate(b);
return (db ? db.getTime() : 0) - (da ? da.getTime() : 0);
});
var targetPlan = cancelledPlans[0];
var cancelledPlanId = targetPlan.planId || targetPlan.plan?.id || "";
var cancelledPriceId = targetPlan.payment?.priceId || targetPlan.priceId || "";
var cancelledEndDate = getEndDate(targetPlan);
var cancelledPlanName = targetPlan.planName || targetPlan.plan?.name || targetPlan.name || "";
if (!cancelledPlanName && cancelledPlanId && typeof memberstack.getPlan === " keywordfunction") {
try {
var planResult = await memberstack.getPlan({ planId: cancelledPlanId });
var planData = planResult?.data || planResult || {};
cancelledPlanName = planData.name || planData.planName || planData.label || "";
} catch (e) {}
}
if (!cancelledPlanName) cancelledPlanName = "your plan";
// ─── DISMISS TRACKING ───
function getDismiss() {
var d = memberJSON[jsonKey];
if (!d || typeof d !== "object") return { dismissed_until: null, dismiss_count: 0, last_plan_id: null };
return d;
}
async function saveDismiss(data) {
memberJSON[jsonKey] = data;
try {
await memberstack.updateMemberJSON({ json: memberJSON });
} catch (e) {
console.warn("Memberscript # number227: Could not save dismiss state", e);
}
}
var dismiss = getDismiss();
if (dismiss.last_plan_id && dismiss.last_plan_id !== cancelledPlanId) {
dismiss = { dismissed_until: null, dismiss_count: 0, last_plan_id: null };
}
if (dismiss.dismissed_until) {
var until = new Date(dismiss.dismissed_until);
if (!isNaN(until) && new Date() < until) return;
}
// ─── POPULATE BANNER CONTENT ───
planNameEls.forEach(function(el) {
el.textContent = cancelledPlanName;
});
if (cancelledEndDate) {
var formatted = cancelledEndDate.toLocaleDateString(CONFIG.dateLocale, {
month: "short", day: "numeric", year: "numeric"
});
cancelledEls.forEach(function(el) {
el.textContent = formatted;
});
} else {
cancelledEls.forEach(function(el) { el.style.display = "none"; });
}
var firstName = (member.customFields && member.customFields[firstNameField]) || "";
firstName = (firstName || "").toString().trim();
firstNameEls.forEach(function(el) {
if (firstName) {
el.textContent = firstName;
} else {
el.style.display = "none";
}
});
// ─── REACTIVATE BUTTON WIRING ───
var targetPriceId = overridePriceId || cancelledPriceId;
var targetPlanIdForCheckout = overridePlanId || cancelledPlanId;
var isScheduledCancel = hasScheduledCancel(targetPlan);
function hasMemberstackAction(el) {
return el.hasAttribute("data-ms-price:add") ||
el.hasAttribute("data-ms-plan:add") ||
el.hasAttribute("data-ms-price:update") ||
el.hasAttribute("data-ms-plan:update") ||
el.hasAttribute("data-ms-action") ||
el.hasAttribute("data-ms-modal");
}
function getButtonState(btn) {
var whenAttr = btn.getAttribute("ms-reactivate-when");
if (whenAttr) return whenAttr.toLowerCase();
if (btn.hasAttribute("data-ms-action") &&
btn.getAttribute("data-ms-action") === "customer-portal") {
return "scheduled";
}
if (btn.hasAttribute("data-ms-price:add") ||
btn.hasAttribute("data-ms-plan:add") ||
btn.hasAttribute("data-ms-price:update") ||
btn.hasAttribute("data-ms-plan:update")) {
return "cancelled";
}
return "any";
}
reactivateBtns.forEach(function(btn) {
var state = getButtonState(btn);
var shouldShow = state === "any" ||
(state === "scheduled" && isScheduledCancel) ||
(state === "cancelled" && !isScheduledCancel);
if (!shouldShow) {
btn.style.display = "none";
return;
}
if (!hasMemberstackAction(btn)) {
if (isScheduledCancel) {
btn.setAttribute("data-ms-action", "customer-portal");
} else if (targetPriceId) {
btn.setAttribute("data-ms-price:add", targetPriceId);
} else if (targetPlanIdForCheckout) {
btn.setAttribute("data-ms-plan:add", targetPlanIdForCheckout);
}
}
btn.addEventListener("click", async function(e) {
if (hasMemberstackAction(btn)) return;
e.preventDefault();
if (isScheduledCancel) {
try {
var result = await memberstack.launchStripeCustomerPortal({
returnUrl: window.location.href
});
if (result?.data?.url) window.location.href = result.data.url;
} catch (err) {
console.error("Memberscript # number227: Could not open Stripe portal", err);
}
return;
}
if (!targetPriceId && !targetPlanIdForCheckout) {
console.warn("Memberscript # number227: No priceId or planId available for checkout");
return;
}
try {
if (typeof memberstack.purchasePlansWithCheckout === " keywordfunction" && targetPriceId) {
await memberstack.purchasePlansWithCheckout({
priceId: targetPriceId,
successUrl: window.location.href,
cancelUrl: window.location.href
});
} else if (typeof memberstack.checkout === " keywordfunction" && targetPriceId) {
await memberstack.checkout({ priceId: targetPriceId });
} else {
await memberstack.openModal("SIGNUP", { planId: targetPlanIdForCheckout });
}
} catch (err) {
console.error("Memberscript # number227: Checkout failed", err);
}
});
});
// ─── DISMISS BUTTON WIRING ───
dismissBtns.forEach(function(btn) {
btn.addEventListener("click", async function(e) {
e.preventDefault();
var until = new Date();
until.setDate(until.getDate() + dismissDays);
await saveDismiss({
dismissed_until: until.toISOString(),
dismiss_count: (dismiss.dismiss_count || 0) + 1,
last_plan_id: cancelledPlanId || null
});
banner.style.display = "none";
});
});
// ─── SHOW BANNER ───
banner.style.display = bannerDisplay;
});
</script>
<script>
document.getElementById("copyButton227").addEventListener("click", function() {
var code = document.getElementById("scriptCode227");
var text = code.textContent || code.innerText;
navigator.clipboard.writeText(text.trim()).then(function() {
var btn = document.getElementById("copyButton227");
btn.textContent = "Copied!";
setTimeout(function() { btn.textContent = "Copy Script"; }, 2000);
});
});
</script>More scripts in Conditional Visibility