PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.3.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.3.0
2.4.0 2.3.0 2.2.0 2.1.0 2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 All 65 releases
subscription / assets / js / frontend / cancellation-feedback.js

cancellation-feedback.js in Subscriptions for WooCommerce with Stripe Recurring Payments 2.3.0, at assets/js/frontend/cancellation-feedback.js

184 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Cancellation feedback modal.
3 *
4 * Intercepts the Cancel action link on the single-subscription page, shows a
5 * feedback modal, records the customer's reason + comment via AJAX, then follows
6 * the original secure cancel URL. Recording is best-effort: if the request fails,
7 * the cancellation still proceeds.
8 *
9 * Backing out is reported too. Dismissing the modal - Keep subscription, the X,
10 * the overlay or Escape - is a retention event the store owner wants to hear
11 * about, so it posts a save before hiding. Also fire-and-forget: a failed report
12 * must never trap the customer in a modal they asked to close.
13 */
14 (function () {
15 "use strict";
16
17 document.addEventListener("DOMContentLoaded", function () {
18 var modal = document.getElementById("subscrpt-feedback-modal");
19 if (!modal) {
20 return;
21 }
22
23 var cancelLink = document.querySelector(".subscrpt_action_buttons a.cancel");
24 if (!cancelLink) {
25 return;
26 }
27
28 var cancelUrl = cancelLink.getAttribute("href");
29 var confirmBtn = document.getElementById("subscrpt-feedback-confirm");
30 var reported = false;
31
32 /**
33 * Open the modal.
34 *
35 * @param {Event} e Click event.
36 */
37 function openModal(e) {
38 e.preventDefault();
39 modal.hidden = false;
40 document.body.style.overflow = "hidden";
41 var firstRadio = modal.querySelector('input[name="subscrpt_feedback_reason"]');
42 if (firstRadio) firstRadio.focus();
43 }
44
45 /**
46 * Post the modal's current state to a wp-ajax action.
47 *
48 * @param {string} action The wp_ajax action name.
49 * @param {Object} [extra] Extra key/value pairs to send.
50 * @return {Promise} Resolves when the request settles.
51 */
52 function post(action, extra) {
53 var checked = modal.querySelector('input[name="subscrpt_feedback_reason"]:checked');
54 var commentEl = document.getElementById("subscrpt-feedback-comment");
55
56 var params = new URLSearchParams();
57 params.append("action", action);
58 params.append("nonce", subscrptCancellationFeedback.nonce);
59 params.append("subscription_id", modal.getAttribute("data-subscription") || "");
60 params.append("reason_key", checked ? checked.value : "");
61 params.append("comment", commentEl ? commentEl.value : "");
62
63 if (extra) {
64 Object.keys(extra).forEach(function (key) {
65 params.append(key, extra[key]);
66 });
67 }
68
69 return fetch(subscrptCancellationFeedback.ajaxUrl, {
70 method: "POST",
71 credentials: "same-origin",
72 headers: { "Content-Type": "application/x-www-form-urlencoded" },
73 body: params.toString(),
74 });
75 }
76
77 /**
78 * Close the modal without cancelling, reporting the save on the way out.
79 *
80 * Reported once per page view: a customer who opens and closes the modal
81 * repeatedly is one save, not several.
82 */
83 function closeModal() {
84 if (!reported) {
85 reported = true;
86 post("subscrpt_record_cancellation_save").catch(function () {});
87 }
88 modal.hidden = true;
89 document.body.style.overflow = "";
90 }
91
92 /**
93 * Navigate to the original cancel URL.
94 */
95 function proceed() {
96 window.location.href = cancelUrl;
97 }
98
99 /**
100 * Swap the offer step for the reasons step.
101 */
102 function showReasons() {
103 modal.querySelectorAll("[data-subscrpt-offer-step]").forEach(function (el) {
104 el.hidden = true;
105 });
106 modal.querySelectorAll("[data-subscrpt-reason-step]").forEach(function (el) {
107 el.hidden = false;
108 });
109 }
110
111 var claimBtn = modal.querySelector("[data-subscrpt-offer-claim]");
112 var declineBtn = modal.querySelector("[data-subscrpt-offer-decline]");
113 var offerResult = modal.querySelector("[data-subscrpt-offer-result]");
114
115 if (declineBtn) {
116 declineBtn.addEventListener("click", showReasons);
117 }
118
119 if (claimBtn) {
120 claimBtn.addEventListener("click", function () {
121 claimBtn.disabled = true;
122
123 // A claimed offer is a save, reported by the endpoint itself.
124 reported = true;
125
126 post("subscrpt_claim_cancellation_offer")
127 .then(function (res) {
128 return res.json();
129 })
130 .then(function (data) {
131 if (!data || !data.success || !data.data || !data.data.code) {
132 throw new Error("no offer");
133 }
134 if (offerResult) {
135 offerResult.textContent = data.data.code;
136 offerResult.hidden = false;
137 }
138 claimBtn.hidden = true;
139 if (declineBtn) {
140 declineBtn.textContent = subscrptCancellationFeedback.doneLabel || "Done";
141 declineBtn.removeEventListener("click", showReasons);
142 declineBtn.addEventListener("click", function () {
143 window.location.reload();
144 });
145 }
146 })
147 .catch(function () {
148 // No offer could be issued - never strand the customer on a step
149 // that cannot complete.
150 claimBtn.disabled = false;
151 reported = false;
152 showReasons();
153 });
154 });
155 }
156
157 cancelLink.addEventListener("click", openModal);
158
159 var dismissEls = modal.querySelectorAll("[data-subscrpt-feedback-dismiss]");
160 for (var i = 0; i < dismissEls.length; i++) {
161 dismissEls[i].addEventListener("click", closeModal);
162 }
163
164 // Close on Escape, but only while this modal is open.
165 document.addEventListener("keydown", function (e) {
166 if (e.key === "Escape" && !modal.hidden) {
167 closeModal();
168 }
169 });
170
171 if (confirmBtn) {
172 confirmBtn.addEventListener("click", function () {
173 confirmBtn.disabled = true;
174
175 // Cancelling is not a save: stop closeModal reporting one if the page
176 // tears down mid-navigation.
177 reported = true;
178
179 post("subscrpt_record_cancellation_feedback").then(proceed).catch(proceed);
180 });
181 }
182 });
183 })();
184