#227 - Show a Reactivation Banner to Cancelled Member

Show a "Welcome back! Reactivate your plan" banner to cancelled members with a one-click reactivation checkout link.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

336 lines
Paste this into Webflow
<!-- 💙 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>

Script Info

Versionv0.1
PublishedApr 30, 2026
Last UpdatedApr 30, 2026

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in Conditional Visibility