#221 - Stripe Customer Purchase History

Show members their full purchase history, stats, and receipt links.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

351 lines
Paste this into Webflow
<!-- πŸ’™ MEMBERSCRIPT #221 v0.1 πŸ’™ PURCHASE HISTORY WITH STATS, FILTERS & PAGINATION -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
  var CONFIG = {
    tableName: "purchases",
    ownerField: "member",
    perPage: 10,
    currency: "$",
    dateLocale: "en-US",
    statusClasses: {
      paid: "is-paid",
      refunded: "is-refunded",
      failed: "is-failed",
      pending: "is-pending",
      cancelled: "is-cancelled",
      canceled: "is-cancelled",
      active: "is-active"
    }
  };

  var memberstack = window.$memberstackDom;
  if (!memberstack) {
    console.warn("Memberscript #number221: Memberstack not found");
    return;
  }

  function show(el, type) { if (el) el.style.display = type || ""; }
  function hide(el) { if (el) el.style.display = "none"; }

  function formatDate(iso) {
    var d = new Date(iso);
    if (isNaN(d)) return iso || "";
    return d.toLocaleDateString(CONFIG.dateLocale, {
      month: "short", day: "numeric", year: "numeric"
    });
  }

  function formatAmount(value) {
    var num = parseFloat(value);
    if (isNaN(num)) return value || "";
    return CONFIG.currency + num.toFixed(2);
  }

  function daysBetween(dateStr) {
    var d = new Date(dateStr);
    if (isNaN(d)) return Infinity;
    var now = new Date();
    return Math.floor((now - d) / 86400000);
  }

  // ─── GET MEMBER ───

  var memberResult;
  try {
    memberResult = await memberstack.getCurrentMember();
  } catch (err) {
    console.warn("Memberscript #number221: Could not get member", err);
    return;
  }

  var member = memberResult?.data || memberResult;
  if (!member || !member.id) return;

  var planConnections = member.planConnections || [];

  // ─── DOM REFERENCES ───

  var listContainer = document.querySelector('[data-ms-code="list-container"]');
  if (!listContainer) return;

  var tableName = listContainer.getAttribute("ms-code-table") || CONFIG.tableName;
  var template = listContainer.querySelector('[data-ms-code="list-template"]');
  var loadingEl = listContainer.querySelector('[data-ms-code="list-loading"]');
  var errorEl = listContainer.querySelector('[data-ms-code="list-error"]');
  var emptyEl = listContainer.querySelector('[data-ms-code="list-empty"]');
  var paginationEl = listContainer.querySelector('[data-ms-code="list-pagination"]');
  var prevBtn = listContainer.querySelector('[data-ms-code="list-prev"]');
  var nextBtn = listContainer.querySelector('[data-ms-code="list-next"]');
  var pageInfo = listContainer.querySelector('[data-ms-code="list-page-info"]');
  var tableHeader = listContainer.querySelector(".proplist_table-header");
  var templateWrapper = template ? template.parentElement : null;

  var statTotal = document.querySelector('[data-ms-field="total_spent"]');
  var statActive = document.querySelector('[data-ms-field="active_count"]');
  var statLastDate = document.querySelector('[data-ms-field="last_payment_date"]');

  var filterSelects = document.querySelectorAll(".propfilter_select");
  var statusFilter = filterSelects[0] || null;
  var dateFilter = filterSelects[1] || null;

  if (!template) {
    console.warn("Memberscript #number221: list-template not found");
    return;
  }

  var templateClone = template.cloneNode(true);
  templateClone.removeAttribute("data-ms-code");
  hide(template);
  hide(listContainer);
  if (loadingEl) {
    listContainer.parentNode.insertBefore(loadingEl, listContainer);
    show(loadingEl);
  }

  // ─── FETCH PURCHASES ───

  var allRecords = [];

  async function fetchPurchases() {
    var all = [];
    var skip = 0;
    var page;

    do {
      var result = await memberstack.queryDataRecords({
        table: tableName,
        query: {
          where: { [CONFIG.ownerField]: { equals: member.id } },
          orderBy: { date: "desc" },
          take: 100,
          skip: skip
        }
      });

      page = result.data?.records || result.data || [];
      all.push.apply(all, page);
      skip += page.length;
    } while (page.length === 100);

    return all;
  }

  try {
    allRecords = await fetchPurchases();
  } catch (err) {
    console.error("Memberscript #number221: Failed to load purchases", err);
    hide(loadingEl);
    show(listContainer, "block");
    show(errorEl);
    return;
  }

  hide(loadingEl);
  hide(errorEl);
  hide(emptyEl);
  show(listContainer, "block");

  // ─── COMPUTE STATS ───

  function computeStats(records) {
    var totalSpent = 0;
    var activeCount = 0;
    var lastDate = null;

    records.forEach(function(r) {
      var d = r.data || {};
      var amount = parseFloat(d.amount) || 0;
      var status = (d.status || "").toLowerCase();

      if (status === "paid" || status === "active") {
        totalSpent += amount;
      }
      if ((status === "paid" || status === "active") && (d.type || "").toLowerCase() === "subscription") {
        activeCount++;
      }
      if (d.date) {
        var dt = new Date(d.date);
        if (!isNaN(dt) && (!lastDate || dt > lastDate)) {
          lastDate = dt;
        }
      }
    });

    if (statTotal) statTotal.textContent = formatAmount(totalSpent);
    if (statActive) statActive.textContent = activeCount;
    if (statLastDate) {
      statLastDate.textContent = lastDate
        ? formatDate(lastDate.toISOString())
        : "N/A";
    }
  }

  var activePlans = planConnections.filter(function(pc) {
    return pc.status === "ACTIVE" && pc.type !== "ONETIME" && pc.type !== "FREE";
  });

  computeStats(allRecords);

  if (statActive && activePlans.length > 0) {
    var tableActiveCount = parseInt(statActive.textContent) || 0;
    var mergedCount = Math.max(tableActiveCount, activePlans.length);
    statActive.textContent = mergedCount;
  }

  // ─── FILTERING ───

  var filteredRecords = allRecords.slice();

  function applyFilters() {
    var statusVal = statusFilter ? statusFilter.value : "all";
    var dateVal = dateFilter ? dateFilter.value : "all";

    filteredRecords = allRecords.filter(function(r) {
      var d = r.data || {};
      var status = (d.status || "").toLowerCase();

      if (statusVal !== "all" && status !== statusVal) return false;

      if (dateVal !== "all") {
        var days = daysBetween(d.date);
        if (dateVal === "number30" && days > 30) return false;
        if (dateVal === "number90" && days > 90) return false;
        if (dateVal === "year" && days > 365) return false;
      }

      return true;
    });

    currentPage = 1;
    render();
  }

  if (statusFilter) statusFilter.addEventListener("change", applyFilters);
  if (dateFilter) dateFilter.addEventListener("change", applyFilters);

  // ─── PAGINATION & RENDER ───

  var currentPage = 1;

  function getTotalPages() {
    return Math.max(1, Math.ceil(filteredRecords.length / CONFIG.perPage));
  }

  function render() {
    var wrapper = templateWrapper || listContainer;
    var existing = wrapper.querySelectorAll(".proplist_row:not([data-ms-code])");
    existing.forEach(function(el) { el.remove(); });

    var total = filteredRecords.length;

    if (total === 0) {
      show(emptyEl);
      hide(paginationEl);
      if (tableHeader) hide(tableHeader);
      return;
    }

    hide(emptyEl);
    if (tableHeader) show(tableHeader);

    var totalPages = getTotalPages();
    if (currentPage > totalPages) currentPage = totalPages;

    var start = (currentPage - 1) * CONFIG.perPage;
    var end = Math.min(start + CONFIG.perPage, total);
    var pageRecords = filteredRecords.slice(start, end);

    pageRecords.forEach(function(record) {
      var row = templateClone.cloneNode(true);
      var data = record.data || {};

      var fields = row.querySelectorAll("[data-ms-field]");
      fields.forEach(function(el) {
        var name = el.getAttribute("data-ms-field");
        var type = el.getAttribute("ms-field") || "text";
        var value = data[name];

        if (name === "status") {
          var statusText = (value || "").toString();
          el.textContent = statusText.charAt(0).toUpperCase() + statusText.slice(1);
          Object.keys(CONFIG.statusClasses).forEach(function(key) {
            el.classList.remove(CONFIG.statusClasses[key]);
          });
          var cls = CONFIG.statusClasses[statusText.toLowerCase()];
          if (cls) el.classList.add(cls);
          return;
        }

        if (type === "link" || name === "receipt_url") {
          if (value) {
            el.href = value;
            el.setAttribute("target", "_blank");
            el.setAttribute("rel", "noopener noreferrer");
            show(el);
          } else {
            hide(el);
          }
          return;
        }

        if (value === undefined || value === null || value === "") {
          el.textContent = "";
          return;
        }

        if (type === "date") {
          el.textContent = formatDate(value);
        } else if (name === "amount") {
          el.textContent = formatAmount(value);
        } else {
          el.textContent = value;
        }
      });

      wrapper.appendChild(row);
    });

    if (totalPages > 1) {
      show(paginationEl);
      if (pageInfo) pageInfo.textContent = "Page " + currentPage + " keywordof " + totalPages;
      if (prevBtn) prevBtn.disabled = currentPage <= 1;
      if (nextBtn) nextBtn.disabled = currentPage >= totalPages;
    } else {
      hide(paginationEl);
    }
  }

  if (prevBtn) {
    prevBtn.addEventListener("click", function() {
      if (currentPage > 1) {
        currentPage--;
        render();
      }
    });
  }

  if (nextBtn) {
    nextBtn.addEventListener("click", function() {
      if (currentPage < getTotalPages()) {
        currentPage++;
        render();
      }
    });
  }

  render();
});
</script>


<script>
document.getElementById("copyButton221").addEventListener("click", function() {
  var code = document.getElementById("scriptCode221");
  var text = code.textContent || code.innerText;
  navigator.clipboard.writeText(text.trim()).then(function() {
    var btn = document.getElementById("copyButton221");
    btn.textContent = "Copied!";
    setTimeout(function() { btn.textContent = "Copy Script"; }, 2000);
  });
});
</script>

Tutorial

Download the second Make Blueprint here: https://drive.google.com/file/d/1cPtBnsYo7AUdhJflD7V0fRBDEFoFPcL1/view?usp=sharing

Make.com Blueprint

Download Blueprint

Import this into Make.com to get started

Download File
How to use:
  1. Download the JSON blueprint above
  2. Navigate to Make.com and create a new scenario
  3. Click the 3-dot menu and select Import Blueprint
  4. Upload your file and connect your accounts

Script Info

Versionv0.1
PublishedMar 31, 2026
Last UpdatedMar 31, 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 Data Tables