@4site/engrid-scripts 0.22.20 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DonationAmount, DonationFrequency, EnForm, ProcessingFees, Country, } from "./events";
2
- import { AmountLabel, Loader, ProgressBar, UpsellLightbox, ENGrid, OptionsDefaults, setRecurrFreq, PageBackground, MediaAttribution, ApplePay, A11y, CapitalizeFields, Ecard, ClickToExpand, Advocacy, DataAttributes, LiveVariables, iFrame, InputPlaceholders, InputHasValueAndFocus, ShowHideRadioCheckboxes, AutoCountrySelect, SkipToMainContentLink, SrcDefer, NeverBounce, AutoYear, Autocomplete, RememberMe, TranslateFields, ShowIfAmount, EngridLogger, OtherAmount, MinMaxAmount, Ticker, DataReplace, DataHide, AddNameToMessage, ExpandRegionName, AppVersion, UrlToForm, RequiredIfVisible, TidyContact, DataLayer, LiveCurrency, Autosubmit, EventTickets, SwapAmounts, DebugPanel, DebugHiddenFields, FreshAddress, BrandingHtml, CountryDisable, PremiumGift, DigitalWallets, MobileCTA, LiveFrequency, UniversalOptIn, Plaid, GiveBySelect, UrlParamsToBodyAttrs, ExitIntentLightbox, SupporterHub, FastFormFill, SetAttr, ShowIfPresent, ENValidators, CustomCurrency, VGS, PostalCodeValidator, CountryRedirect, WelcomeBack, EcardToTarget, UsOnlyForm, ThankYouPageConditionalContent, EmbeddedEcard, CheckboxLabel, UpsellCheckbox, PostDonationEmbed, FrequencyUpsell, CustomPremium, } from ".";
2
+ import { AmountLabel, Loader, ProgressBar, UpsellLightbox, ENGrid, OptionsDefaults, setRecurrFreq, PageBackground, MediaAttribution, ApplePay, A11y, CapitalizeFields, Ecard, ClickToExpand, Advocacy, DataAttributes, LiveVariables, iFrame, InputPlaceholders, InputHasValueAndFocus, ShowHideRadioCheckboxes, AutoCountrySelect, SkipToMainContentLink, SrcDefer, NeverBounce, AutoYear, Autocomplete, RememberMe, TranslateFields, ShowIfAmount, EngridLogger, OtherAmount, MinMaxAmount, Ticker, DataReplace, DataHide, AddNameToMessage, ExpandRegionName, AppVersion, UrlToForm, RequiredIfVisible, TidyContact, DataLayer, LiveCurrency, Autosubmit, EventTickets, SwapAmounts, DebugPanel, DebugHiddenFields, FreshAddress, BrandingHtml, CountryDisable, PremiumGift, DigitalWallets, MobileCTA, LiveFrequency, UniversalOptIn, Plaid, GiveBySelect, UrlParamsToBodyAttrs, ExitIntentLightbox, SupporterHub, FastFormFill, SetAttr, ShowIfPresent, ENValidators, CustomCurrency, VGS, PostalCodeValidator, CountryRedirect, WelcomeBack, EcardToTarget, UsOnlyForm, ThankYouPageConditionalContent, EmbeddedEcard, CheckboxLabel, UpsellCheckbox, PostDonationEmbed, FrequencyUpsell, CustomPremium, PreferredPaymentMethod, } from ".";
3
3
  export class App extends ENGrid {
4
4
  constructor(options) {
5
5
  super();
@@ -253,6 +253,7 @@ export class App extends ENGrid {
253
253
  // Digital Wallets Features
254
254
  if (ENGrid.getPageType() === "DONATION") {
255
255
  new DigitalWallets();
256
+ new PreferredPaymentMethod();
256
257
  }
257
258
  // Mobile CTA
258
259
  new MobileCTA();
@@ -1,4 +1,5 @@
1
1
  export declare class DigitalWallets {
2
+ private static isApplePayAvailable;
2
3
  constructor();
3
4
  private addStripeDigitalWallets;
4
5
  private addPaypalTouchDigitalWallets;
@@ -3,6 +3,7 @@ export class DigitalWallets {
3
3
  constructor() {
4
4
  //digital wallets not enabled.
5
5
  if (!document.getElementById("en__digitalWallet")) {
6
+ ENGrid.setBodyData("payment-type-option-stripedigitalwallet", "false");
6
7
  ENGrid.setBodyData("payment-type-option-apple-pay", "false");
7
8
  ENGrid.setBodyData("payment-type-option-google-pay", "false");
8
9
  ENGrid.setBodyData("payment-type-option-paypal-one-touch", "false");
@@ -41,6 +42,7 @@ export class DigitalWallets {
41
42
  else {
42
43
  ENGrid.setBodyData("payment-type-option-apple-pay", "false");
43
44
  ENGrid.setBodyData("payment-type-option-google-pay", "false");
45
+ ENGrid.setBodyData("payment-type-option-stripedigitalwallet", "false");
44
46
  const stripeContainer = document.getElementById("en__digitalWallet__stripeButtons__container");
45
47
  if (stripeContainer) {
46
48
  this.checkForWalletsBeingAdded(stripeContainer, "stripe");
@@ -85,8 +87,9 @@ export class DigitalWallets {
85
87
  }
86
88
  addStripeDigitalWallets() {
87
89
  this.addOptionToPaymentTypeField("stripedigitalwallet", "GooglePay / ApplePay");
88
- ENGrid.setBodyData("payment-type-option-apple-pay", "true");
89
- ENGrid.setBodyData("payment-type-option-google-pay", "true");
90
+ ENGrid.setBodyData("payment-type-option-apple-pay", DigitalWallets.isApplePayAvailable.toString());
91
+ ENGrid.setBodyData("payment-type-option-google-pay", !DigitalWallets.isApplePayAvailable.toString());
92
+ ENGrid.setBodyData("payment-type-option-stripedigitalwallet", "true");
90
93
  }
91
94
  addPaypalTouchDigitalWallets() {
92
95
  this.addOptionToPaymentTypeField("paypaltouch", "Paypal / Venmo");
@@ -141,3 +144,4 @@ export class DigitalWallets {
141
144
  observer.observe(node, { childList: true, subtree: true });
142
145
  }
143
146
  }
147
+ DigitalWallets.isApplePayAvailable = !!window.hasOwnProperty("ApplePaySession");
package/dist/index.d.ts CHANGED
@@ -86,5 +86,6 @@ export * from "./optin-ladder";
86
86
  export * from "./post-donation-embed";
87
87
  export * from "./frequency-upsell-modal";
88
88
  export * from "./frequency-upsell";
89
+ export * from "./preferred-payment-method";
89
90
  export * from "./events";
90
91
  export * from "./version";
package/dist/index.js CHANGED
@@ -86,6 +86,7 @@ export * from "./optin-ladder";
86
86
  export * from "./post-donation-embed";
87
87
  export * from "./frequency-upsell-modal";
88
88
  export * from "./frequency-upsell";
89
+ export * from "./preferred-payment-method";
89
90
  // Events
90
91
  export * from "./events";
91
92
  // Version
@@ -152,6 +152,10 @@ export interface Options {
152
152
  placementQuerySelector?: string | null;
153
153
  excludePageIDs?: string[];
154
154
  };
155
+ PreferredPaymentMethod?: false | {
156
+ preferredPaymentMethodField?: string;
157
+ defaultPaymentMethod?: string[];
158
+ };
155
159
  CustomPremium?: false | {
156
160
  [frequency: string]: ({
157
161
  products?: {
@@ -42,6 +42,7 @@ export const OptionsDefaults = {
42
42
  CountryRedirect: false,
43
43
  WelcomeBack: false,
44
44
  OptInLadder: false,
45
+ PreferredPaymentMethod: false,
45
46
  PageLayouts: [
46
47
  "leftleft1col",
47
48
  "centerleft1col",
@@ -0,0 +1,31 @@
1
+ export declare class PreferredPaymentMethod {
2
+ private logger;
3
+ private readonly availabilityTimeoutMs;
4
+ private cleanupHandlers;
5
+ private selectionFinalized;
6
+ private listenersAttached;
7
+ private config;
8
+ private preferredFieldName;
9
+ constructor();
10
+ private shouldRun;
11
+ private resolveConfig;
12
+ private buildCandidateList;
13
+ private hasPreferredField;
14
+ private attachGiveBySelectListeners;
15
+ private syncPreferredField;
16
+ private getFieldPreference;
17
+ private getUrlPreference;
18
+ private tryCandidateAtIndex;
19
+ private waitForAvailability;
20
+ private applySelection;
21
+ private paymentMethodExists;
22
+ private isPaymentMethodAvailable;
23
+ private findPaymentInput;
24
+ private getGiveBySelectInputs;
25
+ private getGiveBySelectContainer;
26
+ private getInputContainer;
27
+ private findLabelForInput;
28
+ private normalizePaymentValue;
29
+ private getAvailabilityAttributeFilters;
30
+ private cleanupAllObservers;
31
+ }
@@ -0,0 +1,288 @@
1
+ import { ENGrid, EngridLogger } from ".";
2
+ export class PreferredPaymentMethod {
3
+ constructor() {
4
+ var _a;
5
+ this.logger = new EngridLogger("PreferredPaymentMethod", "#ffffff", "#1f2933", "⭐️");
6
+ this.availabilityTimeoutMs = 4000;
7
+ this.cleanupHandlers = [];
8
+ this.selectionFinalized = false;
9
+ this.listenersAttached = false;
10
+ this.config = this.resolveConfig();
11
+ this.preferredFieldName = ((_a = this.config.preferredPaymentMethodField) === null || _a === void 0 ? void 0 : _a.trim()) || "";
12
+ if (!this.shouldRun()) {
13
+ return;
14
+ }
15
+ this.attachGiveBySelectListeners();
16
+ const candidates = this.buildCandidateList();
17
+ if (candidates.length === 0) {
18
+ this.logger.log("No payment methods to evaluate. Skipping.");
19
+ return;
20
+ }
21
+ this.logger.log(`Evaluating preferred payment methods in order: ${candidates.join(", ")}`);
22
+ this.tryCandidateAtIndex(0, candidates);
23
+ }
24
+ shouldRun() {
25
+ if (ENGrid.getPageType() !== "DONATION") {
26
+ this.logger.log("Not a donation page. Skipping preferred payment selection.");
27
+ return false;
28
+ }
29
+ if (!this.getGiveBySelectInputs().length) {
30
+ this.logger.log("No give-by-select inputs found. Skipping.");
31
+ return false;
32
+ }
33
+ const config = ENGrid.getOption("PreferredPaymentMethod");
34
+ if (config === false) {
35
+ this.logger.log("PreferredPaymentMethod option disabled.");
36
+ return false;
37
+ }
38
+ return true;
39
+ }
40
+ resolveConfig() {
41
+ const option = ENGrid.getOption("PreferredPaymentMethod");
42
+ if (option && typeof option === "object") {
43
+ const preferredPaymentMethodField = option.preferredPaymentMethodField || "";
44
+ const defaultPaymentMethod = Array.isArray(option.defaultPaymentMethod)
45
+ ? option.defaultPaymentMethod.filter((item) => !!item)
46
+ : [];
47
+ return {
48
+ preferredPaymentMethodField,
49
+ defaultPaymentMethod: defaultPaymentMethod.length > 0 ? defaultPaymentMethod : ["card"],
50
+ };
51
+ }
52
+ return {
53
+ preferredPaymentMethodField: "",
54
+ defaultPaymentMethod: ["card"],
55
+ };
56
+ }
57
+ buildCandidateList() {
58
+ const candidates = [];
59
+ const seen = new Set();
60
+ const pushCandidate = (value) => {
61
+ if (!value)
62
+ return;
63
+ const normalized = this.normalizePaymentValue(value);
64
+ if (!normalized || seen.has(normalized))
65
+ return;
66
+ seen.add(normalized);
67
+ candidates.push(normalized);
68
+ };
69
+ pushCandidate(this.getFieldPreference());
70
+ pushCandidate(this.getUrlPreference());
71
+ this.config.defaultPaymentMethod.forEach(pushCandidate);
72
+ return candidates;
73
+ }
74
+ hasPreferredField() {
75
+ if (!this.preferredFieldName)
76
+ return false;
77
+ const field = ENGrid.getField(this.preferredFieldName);
78
+ return !!field;
79
+ }
80
+ attachGiveBySelectListeners() {
81
+ if (this.listenersAttached)
82
+ return;
83
+ if (!this.preferredFieldName)
84
+ return;
85
+ if (!this.hasPreferredField()) {
86
+ this.logger.log(`Preferred payment field "${this.preferredFieldName}" not found. Field sync disabled.`);
87
+ return;
88
+ }
89
+ const inputs = this.getGiveBySelectInputs();
90
+ inputs.forEach((input) => {
91
+ input.addEventListener("change", () => {
92
+ if (input.checked) {
93
+ this.syncPreferredField(input.value);
94
+ }
95
+ });
96
+ });
97
+ this.listenersAttached = true;
98
+ }
99
+ syncPreferredField(value) {
100
+ if (!this.preferredFieldName)
101
+ return;
102
+ if (!this.hasPreferredField())
103
+ return;
104
+ ENGrid.setFieldValue(this.preferredFieldName, value, false, true);
105
+ }
106
+ getFieldPreference() {
107
+ if (!this.preferredFieldName) {
108
+ return null;
109
+ }
110
+ const fieldValue = ENGrid.getFieldValue(this.preferredFieldName);
111
+ if (!fieldValue) {
112
+ this.logger.log(`Preferred payment field "${this.preferredFieldName}" is empty. Moving on.`);
113
+ return null;
114
+ }
115
+ this.logger.log(`Preferred payment from field "${this.preferredFieldName}" resolved to "${fieldValue}".`);
116
+ return fieldValue;
117
+ }
118
+ getUrlPreference() {
119
+ const urlValue = ENGrid.getUrlParameter("payment");
120
+ if (typeof urlValue === "string" && urlValue.trim() !== "") {
121
+ this.logger.log(`Preferred payment from URL parameter: "${urlValue}".`);
122
+ return urlValue;
123
+ }
124
+ return null;
125
+ }
126
+ tryCandidateAtIndex(index, candidates) {
127
+ if (this.selectionFinalized) {
128
+ return;
129
+ }
130
+ if (index >= candidates.length) {
131
+ this.logger.log("No preferred payment method was applied.");
132
+ return;
133
+ }
134
+ const method = candidates[index];
135
+ if (!this.paymentMethodExists(method)) {
136
+ this.logger.log(`Payment method "${method}" not found. Skipping.`);
137
+ this.tryCandidateAtIndex(index + 1, candidates);
138
+ return;
139
+ }
140
+ if (this.isPaymentMethodAvailable(method)) {
141
+ this.logger.success(`Selecting available payment method "${method}".`);
142
+ this.applySelection(method);
143
+ return;
144
+ }
145
+ this.logger.log(`Payment method "${method}" exists but is not available yet. Waiting up to ${this.availabilityTimeoutMs}ms.`);
146
+ this.waitForAvailability(method, () => {
147
+ if (this.selectionFinalized)
148
+ return;
149
+ if (this.isPaymentMethodAvailable(method)) {
150
+ this.logger.success(`Selecting payment method "${method}" once it became available.`);
151
+ this.applySelection(method);
152
+ }
153
+ }, () => {
154
+ if (this.selectionFinalized)
155
+ return;
156
+ this.logger.log(`Payment method "${method}" still unavailable after waiting. Trying next option.`);
157
+ this.tryCandidateAtIndex(index + 1, candidates);
158
+ });
159
+ }
160
+ waitForAvailability(method, onAvailable, onTimeout) {
161
+ const observers = [];
162
+ const cleanup = () => {
163
+ observers.forEach((observer) => observer.disconnect());
164
+ observers.length = 0;
165
+ this.cleanupHandlers = this.cleanupHandlers.filter((fn) => fn !== cleanup);
166
+ window.clearTimeout(timeoutId);
167
+ };
168
+ this.cleanupHandlers.push(cleanup);
169
+ const checkAvailability = () => {
170
+ if (this.selectionFinalized) {
171
+ cleanup();
172
+ return;
173
+ }
174
+ if (this.isPaymentMethodAvailable(method)) {
175
+ cleanup();
176
+ onAvailable();
177
+ }
178
+ };
179
+ const fieldContainer = this.getGiveBySelectContainer() || document.body;
180
+ const domObserver = new MutationObserver(() => checkAvailability());
181
+ domObserver.observe(fieldContainer, {
182
+ attributes: true,
183
+ attributeFilter: ["class", "style"],
184
+ childList: true,
185
+ subtree: true,
186
+ });
187
+ observers.push(domObserver);
188
+ const attributeFilters = this.getAvailabilityAttributeFilters(method);
189
+ if (attributeFilters.length > 0) {
190
+ const attrObserver = new MutationObserver(() => checkAvailability());
191
+ attrObserver.observe(document.body, {
192
+ attributes: true,
193
+ attributeFilter: attributeFilters,
194
+ });
195
+ observers.push(attrObserver);
196
+ }
197
+ const timeoutId = window.setTimeout(() => {
198
+ cleanup();
199
+ onTimeout();
200
+ }, this.availabilityTimeoutMs);
201
+ }
202
+ applySelection(method) {
203
+ if (this.selectionFinalized) {
204
+ return;
205
+ }
206
+ const input = this.findPaymentInput(method);
207
+ if (!input) {
208
+ this.logger.log(`Unable to locate give-by-select input for "${method}" during selection.`);
209
+ return;
210
+ }
211
+ if (!this.isPaymentMethodAvailable(method)) {
212
+ this.logger.log(`Payment method "${method}" is not available to select.`);
213
+ return;
214
+ }
215
+ const label = this.findLabelForInput(input);
216
+ if (label) {
217
+ label.click();
218
+ }
219
+ else {
220
+ input.checked = true;
221
+ input.dispatchEvent(new Event("change", { bubbles: true, cancelable: true }));
222
+ }
223
+ ENGrid.setPaymentType(method);
224
+ this.syncPreferredField(input.value);
225
+ this.selectionFinalized = true;
226
+ this.cleanupAllObservers();
227
+ }
228
+ paymentMethodExists(method) {
229
+ return !!this.findPaymentInput(method);
230
+ }
231
+ isPaymentMethodAvailable(method) {
232
+ const input = this.findPaymentInput(method);
233
+ if (!input || input.disabled) {
234
+ return false;
235
+ }
236
+ const container = this.getInputContainer(input);
237
+ return container ? ENGrid.isVisible(container) : ENGrid.isVisible(input);
238
+ }
239
+ findPaymentInput(method) {
240
+ const normalized = this.normalizePaymentValue(method);
241
+ if (!normalized) {
242
+ return null;
243
+ }
244
+ const inputs = this.getGiveBySelectInputs();
245
+ return (Array.from(inputs).find((input) => input.value && this.normalizePaymentValue(input.value) === normalized) || null);
246
+ }
247
+ getGiveBySelectInputs() {
248
+ return document.getElementsByName("transaction.giveBySelect");
249
+ }
250
+ getGiveBySelectContainer() {
251
+ return document.querySelector(".en__field--give-by-select, .give-by-select");
252
+ }
253
+ getInputContainer(input) {
254
+ return (input.closest(".en__field__item") ||
255
+ input.closest(".en__field__element") ||
256
+ input.parentElement);
257
+ }
258
+ findLabelForInput(input) {
259
+ if (input.id) {
260
+ const externalLabel = document.querySelector(`label[for="${input.id}"]`);
261
+ if (externalLabel) {
262
+ return externalLabel;
263
+ }
264
+ }
265
+ return input.closest("label");
266
+ }
267
+ normalizePaymentValue(value) {
268
+ return value.trim().toLowerCase();
269
+ }
270
+ getAvailabilityAttributeFilters(method) {
271
+ const map = {
272
+ stripedigitalwallet: [
273
+ "data-engrid-payment-type-option-apple-pay",
274
+ "data-engrid-payment-type-option-google-pay",
275
+ ],
276
+ paypaltouch: [
277
+ "data-engrid-payment-type-option-paypal-one-touch",
278
+ "data-engrid-payment-type-option-venmo",
279
+ ],
280
+ daf: ["data-engrid-payment-type-option-daf"],
281
+ };
282
+ return map[method] || [];
283
+ }
284
+ cleanupAllObservers() {
285
+ this.cleanupHandlers.forEach((cleanup) => cleanup());
286
+ this.cleanupHandlers = [];
287
+ }
288
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const AppVersion = "0.22.20";
1
+ export declare const AppVersion = "0.23.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const AppVersion = "0.22.20";
1
+ export const AppVersion = "0.23.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4site/engrid-scripts",
3
- "version": "0.22.20",
3
+ "version": "0.23.0",
4
4
  "description": "Scripts for the ENGRID Project",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  "strongly-typed-events": "^2.0.9"
26
26
  },
27
27
  "homepage": "https://github.com/4site-interactive-studios/engrid#readme",
28
- "gitHead": "ff21b4a608e3275322905d25fbb256522d9c7bf1",
28
+ "gitHead": "78e80f4a337ab108967c9c5467b95d74e307d93b",
29
29
  "devDependencies": {
30
30
  "nodemon": "^2.0.20"
31
31
  }