PluginProbe
seQura / 3.2.2
seQura v3.2.2
4.3.4 4.3.3 4.3.2 4.3.1 trunk 2.0.0 2.0.10 2.0.11 2.0.12 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0.0 3.0.2 3.0.5 3.0.6 3.0.7 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 4.0.0 All 30 releases
sequra / assets / js / src / core / GeneralSettingsForm.js

GeneralSettingsForm.js in seQura 3.2.2, at assets/js/src/core/GeneralSettingsForm.js

577 lines 22.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 if (!window.SequraFE) {
2 window.SequraFE = {};
3 }
4
5 (function () {
6 /**
7 * @typedef GeneralSettings
8 * @property {boolean} sendOrderReportsPeriodicallyToSeQura
9 * @property {boolean | null} showSeQuraCheckoutAsHostedPage
10 * @property {string[] | null} allowedIPAddresses
11 * @property {string[] | null} excludedCategories
12 * @property {string[] | null} excludedProducts
13 * @property {string | null} replacementPaymentMethod
14 */
15
16 /**
17 * @typedef Category
18 * @property {string} id
19 * @property {string} name
20 */
21
22 /**
23 * @typedef ShopPaymentMethod
24 * @property {string} code
25 * @property {string} name
26 */
27
28 /**
29 * @typedef CountrySettings
30 * @property {string} countryCode
31 * @property {string} merchantId
32 */
33
34 /**
35 * @typedef SellingCountry
36 * @property {string} name
37 * @property {string} code
38 */
39
40 /**
41 * Handles general settings form logic.
42 *
43 * @param {{
44 * generalSettings: GeneralSettings,
45 * countrySettings: CountrySettings[],
46 * shopCategories: Category[],
47 * shopPaymentMethods: ShopPaymentMethod[],
48 * sellingCountries: SellingCountry[],
49 * connectionSettings: ConnectionSettings,
50 * }} data
51 * @param {{
52 * saveGeneralSettingsUrl: string,
53 * saveCountrySettingsUrl: string,
54 * validateConnectionDataUrl: string,
55 * page: string,
56 * appState: string,
57 * }} configuration
58 * @constructor
59 */
60 function GeneralSettingsForm(data, configuration) {
61 const {
62 elementGenerator: generator,
63 validationService: validator,
64 utilities
65 } = SequraFE;
66 /** @type AjaxServiceType */
67 const api = SequraFE.ajaxService;
68 /** @type GeneralSettings */
69 let activeGeneralSettings;
70 /** @type CountrySettings[] */
71 let activeCountryConfiguration;
72 /** @type GeneralSettings */
73 let changedGeneralSettings;
74 /** @type CountrySettings[] */
75 let changedCountryConfiguration;
76 /** @type boolean */
77 let haveGeneralSettingsChanged = false;
78 /** @type boolean */
79 let hasCountryConfigurationChanged = false;
80
81 /** @type boolean */
82 const useHostedPage = SequraFE?.generalSettings?.useHostedPage ?? true
83 /** @type boolean */
84 const useReplacementPaymentMethod = SequraFE?.generalSettings?.useReplacementPaymentMethod ?? false
85 /** @type boolean */
86 const useAllowedIPAddresses = SequraFE?.generalSettings?.useAllowedIPAddresses ?? true
87 /** @type boolean */
88 const useOrderReporting = SequraFE?.generalSettings?.useOrderReporting ?? false;
89
90 /** @type GeneralSettings */
91 const defaultGeneralSettingsData = {
92 sendOrderReportsPeriodicallyToSeQura: false,
93 showSeQuraCheckoutAsHostedPage: useHostedPage ? false : null,
94 allowedIPAddresses: useAllowedIPAddresses ? [] : null,
95 excludedCategories: [],
96 excludedProducts: [],
97 replacementPaymentMethod: useReplacementPaymentMethod ? '' : null,
98 enabledForServices: false,
99 allowFirstServicePaymentDelay: false,
100 allowServiceRegItems: false,
101 defaultServicesEndDate: 'P1Y'
102 };
103
104 /**
105 * Handles form rendering.
106 */
107 this.render = () => {
108 if (!activeCountryConfiguration) {
109 activeCountryConfiguration = data?.countrySettings?.map((utilities.cloneObject))
110 }
111
112 if (!activeGeneralSettings) {
113 activeGeneralSettings = utilities.cloneObject(defaultGeneralSettingsData);
114 for (let key in activeGeneralSettings) {
115 activeGeneralSettings[key] = data?.generalSettings?.[key] ?? defaultGeneralSettingsData[key];
116 }
117 }
118
119 changedCountryConfiguration = activeCountryConfiguration?.map((utilities.cloneObject))
120 changedGeneralSettings = utilities.cloneObject(activeGeneralSettings)
121 initForm();
122
123 if (SequraFE.state.getCredentialsChanged()) {
124 handleSave();
125
126 return;
127 }
128
129 disableFooter(true);
130 utilities.hideLoader();
131 }
132
133 /**
134 * Initializes the form structure.
135 */
136 const initForm = () => {
137 const pageContent = document.querySelector('.sq-content');
138 pageContent?.append(
139 generator.createElement('div', 'sq-content-inner', '', null, [
140 generator.createElement('div', 'sqp-flash-message-wrapper'),
141 generator.createPageHeading({
142 title: configuration.appState === SequraFE.appStates.ONBOARDING ?
143 'countries.title' : 'generalSettings.title',
144 text: configuration.appState === SequraFE.appStates.ONBOARDING ?
145 'countries.description' : 'generalSettings.description'
146 })
147 ])
148 );
149
150 const pageInnerContent = document.querySelector('.sq-content-inner');
151
152 if (data.sellingCountries.length === 0) {
153 pageInnerContent?.append(utilities.createFlashMessage('general.errors.countries.noCountries', "error"));
154
155 return;
156 }
157
158 if (configuration.appState === SequraFE.appStates.SETTINGS && !SequraFE.isPromotional) {
159 pageInnerContent?.append(
160 useHostedPage ? generator.createToggleField({
161 value: changedGeneralSettings.showSeQuraCheckoutAsHostedPage,
162 label: 'generalSettings.showCheckoutAsHostedPage.label',
163 description: 'generalSettings.showCheckoutAsHostedPage.description',
164 onChange: (value) => handleGeneralSettingsChange('showSeQuraCheckoutAsHostedPage', value)
165 }) : [],
166 useOrderReporting ? generator.createToggleField({
167 value: changedGeneralSettings.sendOrderReportsPeriodicallyToSeQura,
168 label: 'generalSettings.sendOrderReports.label',
169 description: 'generalSettings.sendOrderReports.description',
170 onChange: (value) => handleGeneralSettingsChange('sendOrderReportsPeriodicallyToSeQura', value)
171 }) : [],
172 useReplacementPaymentMethod ? generator.createDropdownField({
173 value: changedGeneralSettings.replacementPaymentMethod,
174 label: 'generalSettings.replacementPaymentMethod.label',
175 description: 'generalSettings.replacementPaymentMethod.description',
176 options: getPaymentMethodOptions(),
177 onChange: (value) => handleGeneralSettingsChange('replacementPaymentMethod', value)
178 }) : [],
179 useAllowedIPAddresses ? generator.createMultiItemSelectorField({
180 name: 'allowedIPAddresses-selector',
181 label: 'generalSettings.allowedIPAddresses.label',
182 description: 'generalSettings.allowedIPAddresses.description',
183 value: changedGeneralSettings.allowedIPAddresses?.join(', '),
184 searchable: false,
185 onChange: (value) => handleGeneralSettingsChange('allowedIPAddresses', value)
186 }) : [],
187 generator.createMultiItemSelectorField({
188 label: 'generalSettings.excludedCategories.label',
189 description: 'generalSettings.excludedCategories.description',
190 value: changedGeneralSettings.excludedCategories?.join(','),
191 options: data.shopCategories.map((category) => ({ label: category.name, value: category.id })),
192 onChange: (value) => handleGeneralSettingsChange('excludedCategories', value)
193 }),
194 generator.createMultiItemSelectorField({
195 label: 'generalSettings.excludedProducts.label',
196 description: 'generalSettings.excludedProducts.description',
197 value: changedGeneralSettings.excludedProducts?.join(','),
198 searchable: false,
199 onChange: (value) => handleGeneralSettingsChange('excludedProducts', value)
200 }),
201 // Add more fields here
202 generator.createToggleField({
203 value: changedGeneralSettings.enabledForServices,
204 label: 'generalSettings.enabledForServices.label',
205 description: 'generalSettings.enabledForServices.description',
206 onChange: handleEnabledForServicesChange
207 }),
208 generator.createToggleField({
209 // className: 'sq-log-settings-toggle',
210 className: 'sq-service-related-field',
211 value: changedGeneralSettings.allowFirstServicePaymentDelay,
212 label: 'generalSettings.allowFirstServicePaymentDelay.label',
213 description: 'generalSettings.allowFirstServicePaymentDelay.description',
214 onChange: (value) => handleGeneralSettingsChange('allowFirstServicePaymentDelay', value)
215 }),
216 generator.createToggleField({
217 className: 'sq-service-related-field',
218 value: changedGeneralSettings.allowServiceRegItems,
219 label: 'generalSettings.allowServiceRegItems.label',
220 description: 'generalSettings.allowServiceRegItems.description',
221 onChange: (value) => handleGeneralSettingsChange('allowServiceRegItems', value)
222 }),
223 generator.createTextField({
224 value: changedGeneralSettings.defaultServicesEndDate,
225 className: 'sq-text-input sq-default-services-end-date sq-service-related-field',
226 label: 'generalSettings.defaultServicesEndDate.label',
227 description: 'generalSettings.defaultServicesEndDate.description',
228 onChange: (value) => handleGeneralSettingsChange('defaultServicesEndDate', value)
229 }),
230
231 )
232 }
233
234 pageInnerContent?.append(
235 generator.createMultiItemSelectorField({
236 name: 'countries-selector',
237 label: 'countries.selector.label',
238 description: 'countries.selector.description',
239 value: changedCountryConfiguration.map((country) => country.countryCode).join(','),
240 options: data.sellingCountries.map((country) => ({ label: country.name, value: country.code })),
241 onChange: handleCountryChange
242 })
243 );
244
245 renderCountries();
246 data.sellingCountries.length !== 0 && renderControls();
247
248 maybeShowServiceRelatedFields();
249 }
250
251 /**
252 * Returns shop payment method options.
253 *
254 * @returns {[{label: string, value: string}]}
255 */
256 const getPaymentMethodOptions = () => {
257 const options = [{ label: "None", value: "" }];
258 const paymentMethods = data.shopPaymentMethods ? data.shopPaymentMethods : [];
259 paymentMethods.map((shopPaymentMethod) => {
260 options.push({
261 label: shopPaymentMethod.name.charAt(0).toUpperCase() + shopPaymentMethod.name.slice(1),
262 value: shopPaymentMethod.code
263 })
264 })
265
266 return options;
267 }
268
269 /**
270 * Renders country fields.
271 */
272 const renderCountries = () => {
273 changedCountryConfiguration.map((countryConfig) => {
274 document.querySelector('.sq-content-inner')?.append(generator.createCountryField({
275 countryCode: countryConfig.countryCode,
276 merchantId: countryConfig.merchantId,
277 onChange: (value) => handleMerchantChange(countryConfig.countryCode, value)
278 }))
279 })
280 }
281
282 /**
283 * Renders form controls.
284 */
285 const renderControls = () => {
286 configuration.appState === SequraFE.appStates.ONBOARDING ?
287 document.querySelector('.sq-content-inner')?.append(
288 generator.createButtonField({
289 className: 'sq-continue sqm--block',
290 buttonType: 'primary',
291 buttonLabel: 'general.continue',
292 onClick: handleSave
293 })
294 ) :
295 document.querySelector('.sq-content')?.append(
296 generator.createPageFooter({
297 onSave: handleSave,
298 onCancel: () => {
299 const pageContent = document.querySelector('.sq-content');
300 while (pageContent?.firstChild) {
301 pageContent?.removeChild(pageContent.firstChild);
302 }
303
304 this.render();
305 }
306 })
307 )
308 }
309
310 /**
311 * Validates country configuration.
312 *
313 * @returns {boolean}
314 */
315 const isCountryConfigurationValid = () => {
316 let errorCount = 0;
317
318 if (!changedCountryConfiguration.length) {
319 validator.validateRequiredField(
320 document.querySelector(`[name="countries-selector"]`),
321 'validation.requiredField'
322 );
323
324 errorCount++;
325 }
326
327 changedCountryConfiguration.map((setting) => {
328 !validator.validateRequiredField(
329 document.querySelector(`[name="country_${setting.countryCode}"]`),
330 'validation.requiredField'
331 ) && errorCount++;
332 })
333
334 return errorCount === 0;
335 }
336
337 /**
338 * Handles changes to the merchant ids.
339 *
340 * @param countryCode
341 * @param merchantId
342 */
343 const handleMerchantChange = (countryCode, merchantId) => {
344 validator.validateRequiredField(
345 document.querySelector(`[name="country_${countryCode}"]`),
346 'validation.requiredField'
347 );
348
349 changedCountryConfiguration.find(
350 (setting) => setting.countryCode === countryCode
351 ).merchantId = merchantId;
352
353 hasCountryConfigurationChanged = true;
354 disableFooter(false);
355 }
356
357 /**
358 * Handles configured country changes.
359 *
360 * @param countryCodes
361 */
362 const handleCountryChange = (countryCodes) => {
363 validator.validateRequiredField(
364 document.querySelector(`[name="countries-selector"]`),
365 'validation.requiredField'
366 );
367
368 const countryElements = document.querySelectorAll('.sq-country-field-wrapper');
369 countryElements.forEach((el) => el.remove());
370 changedCountryConfiguration = countryCodes.map((code) => ({
371 countryCode: code,
372 merchantId: changedCountryConfiguration.find((config) => config.countryCode === code)?.merchantId || ''
373 }));
374
375 renderCountries();
376
377 if (configuration.appState === SequraFE.appStates.ONBOARDING) {
378 document.querySelector('.sq-continue').remove();
379 renderControls();
380 }
381
382 hasCountryConfigurationChanged = true;
383 disableFooter(false);
384 }
385
386 /**
387 * Handles general settings changes.
388 *
389 * @param name
390 * @param value
391 */
392 const handleGeneralSettingsChange = (name, value) => {
393 changedGeneralSettings[name] = value;
394 haveGeneralSettingsChanged = true;
395 disableFooter(false);
396 }
397
398
399 const maybeShowServiceRelatedFields = () => {
400 const selector = '.sq-field-wrapper:has(.sq-service-related-field),.sq-field-wrapper.sq-service-related-field'
401 const hiddenClass = 'sqs--hidden';
402 document.querySelectorAll(selector).forEach((el) => {
403 if (changedGeneralSettings.enabledForServices) {
404 el.classList.remove(hiddenClass)
405 } else {
406 el.classList.add(hiddenClass)
407 }
408 });
409 }
410 /**
411 * Handles enabledForServices changes.
412 *
413 * @param value
414 */
415 const handleEnabledForServicesChange = (value) => {
416 // .sqs--hidden
417 handleGeneralSettingsChange('enabledForServices', value)
418 maybeShowServiceRelatedFields();
419 }
420
421 const areIPAddressesValid = () => {
422 const valid = validator.validateIPList(changedGeneralSettings.allowedIPAddresses);
423 validator.validateField(
424 document.querySelector(`[name="allowedIPAddresses-selector"]`),
425 !valid,
426 'validation.invalidIPAddress'
427 );
428 return valid;
429 }
430
431 const isValidTimeDuration = () => {
432 const valid = validator.validateDateOrDuration(changedGeneralSettings.defaultServicesEndDate);
433 validator.validateField(
434 document.querySelector('.sq-default-services-end-date'),
435 !valid,
436 'validation.invalidTimeDuration'
437 );
438
439 return valid;
440 }
441
442 /**
443 * Verifying merchant ids for all selected countries.
444 *
445 * @returns {Promise<Awaited<unknown>[]>}
446 */
447 const validateMerchantIds = () => {
448 const promises = [];
449
450 changedCountryConfiguration.forEach((config) => {
451 promises.push(api.post(
452 configuration.validateConnectionDataUrl,
453 { ...data.connectionSettings, merchantId: config.merchantId },
454 SequraFE.customHeader
455 ))
456 })
457
458 return Promise.all(promises);
459 }
460
461 /**
462 * Handles saving of the form.
463 */
464 const handleSave = () => {
465 if (!isCountryConfigurationValid() || !areIPAddressesValid() || !isValidTimeDuration()) {
466 return;
467 }
468
469 utilities.showLoader();
470
471 areIPAddressesValid()
472 validateMerchantIds()
473 .then((results) => {
474 const hasError = results.some((result) => result.isValid === false);
475 hasError ? handleValidationError(results) : saveChangedData();
476 });
477 }
478
479 /**
480 * Handle merchant id validation error.
481 *
482 * @param {[{isValid: boolean, reason: string|null}]} results
483 */
484 const handleValidationError = (results) => {
485 if (results[0].reason && !results[0].reason.includes('merchantId')) {
486 SequraFE.responseService.errorHandler(
487 { errorCode: 'general.errors.connection.invalidUsernameOrPassword' }
488 ).catch(() => {
489 });
490
491 utilities.hideLoader();
492
493 return;
494 }
495
496 results.forEach((result, index) => {
497 validator.validateField(
498 document.querySelector(`[name="country_${changedCountryConfiguration[index].countryCode}"]`),
499 !result.isValid,
500 'validation.invalidField'
501 );
502 });
503
504 utilities.hideLoader();
505 }
506
507 /**
508 * Save changed data.
509 */
510 const saveChangedData = () => {
511 utilities.showLoader();
512
513 const promises = [];
514
515 haveGeneralSettingsChanged &&
516 promises.push(api.post(configuration.saveGeneralSettingsUrl, changedGeneralSettings, SequraFE.customHeader));
517
518 hasCountryConfigurationChanged &&
519 promises.push(api.post(configuration.saveCountrySettingsUrl, changedCountryConfiguration, SequraFE.customHeader));
520
521 Promise.all(promises)
522 .then(() => {
523 disableFooter(true);
524 activeGeneralSettings = utilities.cloneObject(changedGeneralSettings);
525 activeCountryConfiguration = changedCountryConfiguration.map((utilities.cloneObject))
526
527 if (configuration.appState !== SequraFE.appStates.ONBOARDING && hasCountryConfigurationChanged) {
528 // if countries have been changed, payment methods should be retrieved from API again
529 SequraFE.state.setData('paymentMethods', null);
530 }
531
532 configuration.appState === SequraFE.appStates.SETTINGS &&
533 SequraFE.state.setData('generalSettings', activeGeneralSettings);
534 SequraFE.state.setData('countrySettings', activeCountryConfiguration);
535
536 haveGeneralSettingsChanged = false;
537 hasCountryConfigurationChanged = false;
538
539 let haveCredentialsChanges = SequraFE.state.getCredentialsChanged();
540 if (configuration.appState === SequraFE.appStates.ONBOARDING) {
541 if (haveCredentialsChanges) {
542 SequraFE.state.removeCredentialsChanged();
543 SequraFE.state.goToState(SequraFE.appStates.SETTINGS);
544
545 return;
546 }
547
548 const index = SequraFE.pages.onboarding.indexOf(SequraFE.appPages.ONBOARDING.COUNTRIES)
549 SequraFE.pages.onboarding.length > index + 1 ?
550 window.location.hash = configuration.appState + '-' + SequraFE.pages.onboarding[index + 1] :
551 window.location.hash = SequraFE.appStates.PAYMENT + '-' + SequraFE.appPages.PAYMENT.METHODS;
552 }
553
554 if (haveCredentialsChanges) {
555 SequraFE.state.removeCredentialsChanged();
556 }
557 })
558 .finally(() => {
559 configuration.appState !== SequraFE.appStates.ONBOARDING && utilities.hideLoader();
560 });
561 }
562
563 /**
564 * Disables footer form controls.
565 *
566 * @param disable
567 */
568 const disableFooter = (disable) => {
569 if (configuration.appState !== SequraFE.appStates.ONBOARDING) {
570 utilities.disableFooter(disable);
571 }
572 }
573 }
574
575 SequraFE.GeneralSettingsForm = GeneralSettingsForm;
576 })();
577