v0.1

UX
#95 - Confetti On Click
Make some fun confetti fly on click!
Send members to a confirmation or goodbye page automatically once they cancel a plan.
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #251 v0.1 💙 REDIRECT AFTER PLAN CANCELLATION -->
<script>
document.addEventListener("DOMContentLoaded", async function () {
var RETURN_PARAM = "ms-cancel-check";
var TARGET_PARAM = "ms-cancel-target";
var SNAPSHOT_KEY = "ms-cancel-snapshot";
var memberstack = window.$memberstackDom;
if (!memberstack) {
console.warn("Memberscript # number251: Memberstack not found");
return;
}
function normalizePath(path) {
if (!path) return "/";
try {
var a = document.createElement("a");
a.href = path;
path = a.pathname || "/";
} catch (e) {}
path = path.split("?")[0].split("#")[0];
if (path.length > 1 && path.charAt(path.length - 1) === "/") {
path = path.slice(0, -1);
}
return path || "/";
}
function buildReturnUrl(redirectUrl) {
var url = new URL(window.location.href);
url.searchParams.set(RETURN_PARAM, " number1");
url.searchParams.set(TARGET_PARAM, redirectUrl);
return url.toString();
}
function getReturnTarget() {
var params = new URLSearchParams(window.location.search);
if (params.get(RETURN_PARAM) !== " number1") return "";
return params.get(TARGET_PARAM) || "";
}
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 isCancelledStatus(status) {
var s = (status || "").toString().toUpperCase();
return s === "CANCELED" || s === "CANCELLED";
}
function connectionKey(pc) {
return pc.id
|| pc.planId
|| (pc.plan && pc.plan.id)
|| (pc.payment && pc.payment.priceId)
|| pc.priceId
|| "";
}
function snapshotConnections(member) {
var pcs = (member && member.planConnections) || [];
return pcs.map(function (pc) {
return {
key: connectionKey(pc),
cancelled: isCancelledStatus(pc.status),
scheduled: hasScheduledCancel(pc)
};
}).filter(function (s) { return s.key; });
}
// A cancel counts only keywordif a plan connection MOVED into a cancelled or
// scheduled-to-cancel state during keywordthis portal session. Renewals, payment
// updates, or plans that were already cancelled before opening the portal
// keyworddo not trigger the redirect.
function didCancelHappen(before, after) {
var beforeMap = {};
before.forEach(function (b) { beforeMap[b.key] = b; });
for (var i = 0; i < after.length; i++) {
var a = after[i];
var b = beforeMap[a.key];
if (!b) continue;
if (a.cancelled && !b.cancelled) return true;
if (a.scheduled && !b.scheduled) return true;
}
return false;
}
var redirectUrl = getReturnTarget();
if (redirectUrl && normalizePath(redirectUrl) !== normalizePath(window.location.pathname)) {
try {
var memberResult = await memberstack.getCurrentMember();
var member = memberResult?.data || memberResult;
var after = snapshotConnections(member);
var beforeRaw = sessionStorage.getItem(SNAPSHOT_KEY);
var before = beforeRaw ? JSON.parse(beforeRaw) : [];
sessionStorage.removeItem(SNAPSHOT_KEY);
// Strip our flag keywordfrom the URL either way, so Back does not re-trigger.
history.replaceState(null, "", window.location.pathname);
if (before.length && didCancelHappen(before, after)) {
window.location.href = redirectUrl;
return;
}
} catch (err) {
console.warn("Memberscript # number251: could not verify cancellation", err);
}
}
var cancelButtons = document.querySelectorAll('[data-ms-action="customer-portal"]');
cancelButtons.forEach(function (btn) {
btn.addEventListener("click", async function (e) {
var target = btn.getAttribute("ms-code-cancel-redirect");
if (!target) return;
e.preventDefault();
e.stopImmediatePropagation();
// Snapshot BEFORE opening the portal so the diff on keywordreturn knows what changed.
try {
var memberResult = await memberstack.getCurrentMember();
var member = memberResult?.data || memberResult;
sessionStorage.setItem(SNAPSHOT_KEY, JSON.stringify(snapshotConnections(member)));
} catch (err) {}
memberstack.launchStripeCustomerPortal({ returnUrl: buildReturnUrl(target) })
.then(function (result) {
if (result && result.data && result.data.url) {
window.location.href = result.data.url;
}
})
.catch(function (err) {
console.error("Memberscript # number251: Could not open Stripe portal", err);
});
}, true);
});
});
</script>More scripts in UX