wickes-css2 2.103.0-RG-599-place-order-with-gift-card-spinner.1 → 2.103.0-RG-599-place-order-with-gift-card-spinner.2

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.
Files changed (40) hide show
  1. package/build/css/components/checkout-payment-details-v2.css +1 -1
  2. package/build/css/pages/page_checkout_delivery-new.css +1 -1
  3. package/build/js/account-members.min.js +1 -1
  4. package/build/js/add-project-list-id.min.js +1 -1
  5. package/build/js/address-book.min.js +1 -1
  6. package/build/js/basket.min.js +2 -2
  7. package/build/js/bundle.min.js +1 -1
  8. package/build/js/change-password.min.js +1 -1
  9. package/build/js/checkout.min.js +2 -2
  10. package/build/js/emulation.min.js +202 -0
  11. package/build/js/general.bundle.min.js +1 -1
  12. package/build/js/gift-cards.min.js +1 -1
  13. package/build/js/merged-checkout.min.js +2 -2
  14. package/build/js/page/components/gift-cards.js +500 -257
  15. package/build/js/page/components/order-summary.js +42 -25
  16. package/build/js/page/utils/gift-cards-utils.js +117 -16
  17. package/build/js/page/utils/input-handling.js +92 -0
  18. package/build/js/page/utils/validation.js +46 -1
  19. package/build/js/pdp.bundle.min.js +1 -1
  20. package/build/js/personal-details.min.js +1 -1
  21. package/build/js/plp.bundle.min.js +1 -1
  22. package/build/js/project-list.min.js +44 -1
  23. package/build/js/quiz.min.js +1 -1
  24. package/build/js/track-my-order.min.js +1 -1
  25. package/package.json +1 -1
  26. package/src/components/checkout-payment-details-v2.hbs +1 -1
  27. package/src/components/gift-cards.hbs +2 -1
  28. package/src/components/giftcard-chip.hbs +3 -3
  29. package/src/components/giftcard-summary.hbs +6 -0
  30. package/src/js/components/general/notification.js +2 -1
  31. package/src/js/emulation/gift-cards.js +201 -0
  32. package/src/js/page/components/gift-cards.js +500 -257
  33. package/src/js/page/components/order-summary.js +42 -25
  34. package/src/js/page/utils/gift-cards-utils.js +117 -16
  35. package/src/js/page/utils/input-handling.js +92 -0
  36. package/src/js/page/utils/validation.js +46 -1
  37. package/src/layouts/checkout.hbs +1 -5
  38. package/src/page_payment-details-with-gift-card.html +5 -3
  39. package/src/scss/components/_gift-cards.scss +5 -1
  40. package/src/scss/pages/page_checkout_delivery-new.scss +7 -0
@@ -1,7 +1,34 @@
1
- import { initializeInputToggle } from "../utils/show-hide-input";
2
- import { updateOrderSummary } from './order-summary';
3
- import { showLoader, hideLoader } from "../utils/loader";
4
- import { formatGc16, buildSummaryEqualMap, buildChipData, buildHintContext } from "../utils/gift-cards-utils";
1
+ import {initializeInputToggle} from "../utils/show-hide-input";
2
+ import {updateOrderSummary} from './order-summary';
3
+ import {hideLoader, showLoader, appendLoader} from "../utils/loader";
4
+ import {
5
+ ADD_GIFT_CARD,
6
+ buildHintContext,
7
+ buildSummaryEqualMap,
8
+ formatGc16,
9
+ isZeroAmount,
10
+ linkifyClickHere,
11
+ MESSAGE_BY_CODE,
12
+ OK_CODE,
13
+ PAY_WITH_GIFT_CARD,
14
+ payWithGiftCard,
15
+ REMOVE_GIFT_CARD,
16
+ removeGiftCard,
17
+ verifyGiftCard,
18
+ ZERO_BALANCE_MESSAGE,
19
+ PAY_MESSAGE_BY_CODE
20
+ } from "../utils/gift-cards-utils";
21
+
22
+ import { clearErrorText, validateExactDigits } from "../utils/validation";
23
+ import {
24
+ createGcNumberInputHandler,
25
+ digitsOnlyKeypress,
26
+ handleGcNumberBlur,
27
+ handleGcPinInput,
28
+ handleGcPinKeypress,
29
+ handleGcPinBlur,
30
+ } from '../utils/input-handling';
31
+
5
32
  const Handlebars = require('hbsfy/runtime');
6
33
 
7
34
  Handlebars.registerPartial('info-icon', require("../../../components/info-icon.hbs"));
@@ -9,11 +36,15 @@ Handlebars.registerPartial('loader', require("../../../elements/loader.hbs"));
9
36
 
10
37
  const giftCardsHint = require("../../../components/gift-cards-hint.hbs");
11
38
  const giftCardChip = require("../../../components/giftcard-chip.hbs");
12
- const renderLoader = require("../../../elements/loader.hbs");
39
+ const giftcardSummary = require("../../../components/giftcard-summary.hbs");
40
+ const notificationTpl = require("../../../elements/notifications.hbs");
13
41
 
14
42
  var Wick = window.Wick || {};
15
43
 
16
44
  Wick.GiftCard = {
45
+ ADD_GIFT_CARD,
46
+ REMOVE_GIFT_CARD,
47
+ PAY_WITH_GIFT_CARD,
17
48
  el: {
18
49
  $blocks: $('.giftcard'),
19
50
  block: '.giftcard',
@@ -30,15 +61,16 @@ Wick.GiftCard = {
30
61
  fieldError: 'giftcard-field--error',
31
62
  showToggle: '.toggle-show',
32
63
  loader: '.giftcard__container .loader-wrapper',
33
- wrapper: '.giftcard__wrapper',
34
64
  success: '#giftcard-applied',
35
65
  container: '.giftcard__container',
36
66
  paymentForm: '.checkout-payment-details__payment-method',
37
67
  billieField: '.form-row__field.form-row__field-billie',
68
+ klarnaField: '.form-row__field.form-row__field-klarna',
38
69
  clearpayField: '.form-row__field.form-row__field-clearpay',
39
70
  appleField: '.form-row__field.form-row__field-apple',
40
71
  googleField: '.form-row__field.form-row__field-google',
41
72
  paypalField: '.form-row__field.form-row__field-paypal',
73
+ cardField: '.form-row__field.form-row__card-payment',
42
74
  summaryHint: '.giftcard-summary__hint',
43
75
  chipList: '.giftcard-chip-list',
44
76
  summary: '.giftcard-summary',
@@ -63,14 +95,23 @@ Wick.GiftCard = {
63
95
  body: 'body',
64
96
  loaderChip: '.loader-wrapper',
65
97
  chipListLoader: '.giftcard-chip-list > .loader-wrapper',
66
- ctaSelector: '.billing-address .btn-enter-details .btn__text',
67
- btnDetails: '.btn-enter-details',
98
+ notification: '.giftcard-inline .notification',
99
+ giftCardInline: '.giftcard-inline',
100
+ notificationRoot: '.notification',
101
+ inlineHeader: '.giftcard-inline__header',
102
+ ctaSelector: '.btn-enter-details',
103
+ btnDetails: 'button',
68
104
  btnText: '.btn__text',
69
- pageLoader: '.loader-wrapper.page-loader',
70
- btnEnterDetails: '.billing-address .btn-enter-details',
105
+ btnEnterDetails: '.btn-enter-details',
71
106
  checkoutPaymentFields: '.billing-address .checkout-payment-details__descr',
72
- iframe: '#hopIframe',
73
- payBtnSelector: '#btnSubmit,input[type="submit"].submit-PayNow,.submitButton.btn.primary.submit-PayNow',
107
+ pageLoader: '.loader-wrapper.page-loader',
108
+ cardRadio: '#checkout-payment-details-card',
109
+ billieInfo: '.checkout-payment-details__billie',
110
+ klarnaInfo: '.checkout-payment-details__klarna',
111
+ billingAddress: '.billing-address',
112
+ altPaymentRowsAttr: '[data-apple],[data-google],[data-paypal],[data-klarna],[data-billie],[data-clearpay],[data-existing-card]',
113
+ hiddenCard: 'checkout-payment-details__card-details_hidden',
114
+ cardDetails: '.checkout-payment-details__card-details',
74
115
  },
75
116
 
76
117
  messages: {
@@ -81,29 +122,11 @@ Wick.GiftCard = {
81
122
  },
82
123
 
83
124
  maxChips: 3,
84
- lastChipData: null,
85
125
  successTimer: null,
86
-
87
- showError($input, message) {
88
- const { field, errorText, fieldError, ariaInvalidAttr } = this.el;
89
- const $field = $input.closest(field);
90
- $field.addClass(fieldError);
91
- $input.attr(ariaInvalidAttr, 'true');
92
-
93
- let $err = $field.find(errorText);
94
- if (!$err.length) {
95
- $err = $('<div class="giftcard__error-text" />').appendTo($field);
96
- }
97
- $err.text(message);
98
- },
99
-
100
- clearError($input) {
101
- const { field, errorText, fieldError, ariaInvalidAttr } = this.el;
102
- const $field = $input.closest(field);
103
- $field.removeClass(fieldError);
104
- $input.removeAttr(ariaInvalidAttr);
105
- $field.find(errorText).remove();
106
- },
126
+ pendingServerGiftCards: null,
127
+ altPaymentInfosLocked: false,
128
+ altPaymentPanelsLocked: false,
129
+ summaryTotal: null,
107
130
 
108
131
  resetPinToggle($root) {
109
132
  const {
@@ -148,15 +171,17 @@ Wick.GiftCard = {
148
171
 
149
172
  $num.val('');
150
173
  $pinInput.val('');
151
- this.clearError($num);
152
- this.clearError($pinInput);
174
+ clearErrorText($num, this.el.field, this.el.errorText, this.el.fieldError, this.el.ariaInvalidAttr);
175
+ clearErrorText($pinInput, this.el.field, this.el.errorText, this.el.fieldError, this.el.ariaInvalidAttr);
153
176
  $root.find(note).remove();
154
177
 
178
+ this.hideBanner($root);
155
179
  this.resetPinToggle($root);
156
180
  },
157
181
 
158
182
  open($root) {
159
183
  const { toggle, panel, hint, pin, expandedAttr, passwordToggleEvent } = this.el;
184
+ this.hideBanner($root);
160
185
  $root.show();
161
186
 
162
187
  $root.find(panel).prop('hidden', false);
@@ -183,66 +208,11 @@ Wick.GiftCard = {
183
208
  },
184
209
 
185
210
  showSubmitSpinner($root) {
186
- const { loader, panel, toggle, hint, expandedAttr } = this.el;
187
- const $loader = $root.find(loader);
188
-
189
- showLoader($loader);
190
-
191
- setTimeout(() => {
192
- hideLoader($loader);
193
-
194
- $root.find(panel).prop('hidden', true);
195
- $root.find(toggle).attr(expandedAttr, 'false').show().trigger('focus');
196
- $root.find(hint).show();
197
- this.showSuccess($root);
198
- }, 1000);
199
- },
200
-
201
- handleNumberInput(e) {
202
- const { field, fieldError } = this.el;
203
- const input = e.currentTarget;
204
- const $input = $(input);
205
-
206
- const atEnd = input.selectionStart === input.value.length;
207
- const hadErr = $input.closest(field).hasClass(fieldError);
208
-
209
- input.value = formatGc16(input.value);
210
- if (hadErr) this.clearError($input);
211
- if (atEnd) input.setSelectionRange(input.value.length, input.value.length);
212
- },
213
-
214
- handleNumberKeypress(e) {
215
- if (!/\d/.test(String.fromCharCode(e.which || e.keyCode))) e.preventDefault();
216
- },
217
-
218
- handleNumberBlur(e) {
219
- const $input = $(e.currentTarget);
220
- const len = String($input.val() || '').replace(/\D/g, '').length;
221
- this.clearError($input);
222
- if (len < 16) this.showError($input, this.messages.numberMessage);
223
- },
224
-
225
- handlePinInput(e) {
226
- const { field, fieldError } = this.el;
227
- const input = e.currentTarget;
228
- const $input = $(input);
229
-
230
- const hadErr = $input.closest(field).hasClass(fieldError);
231
- input.value = String(input.value || '').replace(/\D/g, '').slice(0, 8);
232
- if (hadErr) this.clearError($input);
233
- },
234
-
235
- handlePinKeypress(e) {
236
- if (!/\d/.test(String.fromCharCode(e.which || e.keyCode))) e.preventDefault();
237
- },
238
-
239
- handlePinBlur(e) {
240
- const $input = $(e.currentTarget);
241
- const length = String($input.val() || '').replace(/\D/g, '').length;
242
- this.clearError($input);
243
- if (length < 8) {
244
- this.showError($input, this.messages.pinMessage);
245
- }
211
+ const { panel, toggle, hint, expandedAttr } = this.el;
212
+ $root.find(panel).prop('hidden', true);
213
+ $root.find(toggle).attr(expandedAttr, 'false').show().trigger('focus');
214
+ $root.find(hint).show();
215
+ this.showSuccess($root);
246
216
  },
247
217
 
248
218
  handleToggleClick(e) {
@@ -258,7 +228,7 @@ Wick.GiftCard = {
258
228
  },
259
229
 
260
230
  finalizeSuccess($root, $success) {
261
- let { panel, toggle, hint, expandedAttr, chipList } = this.el;
231
+ let { panel, toggle, hint, expandedAttr } = this.el;
262
232
 
263
233
  if ($success && $success.length) {
264
234
  $success.attr('hidden', 'hidden').removeClass('fade show').off('transitionend');
@@ -269,28 +239,15 @@ Wick.GiftCard = {
269
239
  $root.find(toggle).attr(expandedAttr, 'false').show();
270
240
  $root.find(hint).show();
271
241
 
272
- if (this.lastChipData) {
273
- const $summary = this.ensureSummaryContainer($root);
274
- const html = this.renderChipHTML(this.lastChipData);
275
- $summary.find(chipList).append(html);
276
- $summary.show();
277
- this.lastChipData = null;
278
- this.updateCtaButton($root);
242
+ if (this.pendingServerGiftCards) {
243
+ this.renderGiftCardsFromList($root, this.pendingServerGiftCards);
244
+ this.pendingServerGiftCards = null;
279
245
  }
280
246
 
281
- const data = Wick.CheckoutData.successGiftCardData;
282
- updateOrderSummary(
283
- data,
284
- this.getEqualMapForGiftCardUpdate(data),
285
- true,
286
- );
287
-
288
- this.toggleBillingDescrForZero($root, data);
289
- this.updateCtaButton($root, data);
290
- this.updateHintVisibility($root, data);
247
+ this.updateHintVisibility($root);
248
+ this.successTimer = null;
291
249
  },
292
250
 
293
-
294
251
  showSuccess($root) {
295
252
  const { success} = this.el;
296
253
 
@@ -314,62 +271,126 @@ Wick.GiftCard = {
314
271
  }, 3000);
315
272
  },
316
273
 
317
- isValid($root) {
318
- const {number, pin} = this.el;
319
- const $numInput = $root.find(number);
320
- const $pinInput = $root.find(pin);
321
- const numField = String($numInput.val() || '').replace(/\D/g, '');
322
- const pinField = String($pinInput.val() || '').replace(/\D/g, '');
323
- let valid = true;
274
+ handleFormSubmit(e) {
275
+ const { number, block, giftcardRow, loaderChip } = this.el;
276
+ e.preventDefault();
277
+
278
+ const $root = $(e.currentTarget).closest(block);
279
+ this.hideBanner($root);
324
280
 
325
- if (numField.length !== 16) {
326
- this.showError($numInput, this.messages.numberMessage);
327
- valid = false;
281
+ const $num = $root.find(number);
282
+ $num.val(formatGc16($num.val()));
283
+
284
+ const $numInput = $root.find(this.el.number);
285
+ const $pinInput = $root.find(this.el.pin);
286
+
287
+ const numberBlurOpts = {
288
+ fieldSelector: this.el.field,
289
+ errorTextSelector: this.el.errorText,
290
+ errorClass: this.el.fieldError,
291
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
292
+ numberMessage: this.messages.numberMessage,
293
+ };
294
+
295
+ const pinBlurOpts = {
296
+ fieldSelector: this.el.field,
297
+ errorTextSelector: this.el.errorText,
298
+ errorClass: this.el.fieldError,
299
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
300
+ pinMessage: this.messages.pinMessage,
301
+ };
302
+
303
+ const okNumber = validateExactDigits($numInput, 16, this.messages.numberMessage, this.el);
304
+ const okPin = validateExactDigits($pinInput, 8, this.messages.pinMessage, this.el);
305
+
306
+ if (!(okNumber && okPin)) {
307
+ handleGcNumberBlur({ currentTarget: $numInput[0] }, numberBlurOpts);
308
+ handleGcPinBlur({ currentTarget: $pinInput[0] }, pinBlurOpts);
309
+ (!okNumber ? $numInput : $pinInput).trigger('focus');
310
+ return;
328
311
  }
329
312
 
330
- if (pinField.length !== 8) {
331
- this.showError($pinInput, this.messages.pinMessage);
332
- valid = false;
313
+ const giftCardNumber = this.getNumberValue($root);
314
+ const pin = this.getPinValue($root);
315
+ const $row = $root.closest(giftcardRow);
316
+
317
+ let $loader = $row.children(loaderChip);
318
+
319
+ if (!$loader.length) {
320
+ appendLoader({ wrapper: $row });
321
+ $loader = $row.children(loaderChip).last();
333
322
  }
334
323
 
335
- return valid;
324
+ verifyGiftCard($loader, giftCardNumber, pin)
325
+ .then((res) => this.handleVerifyResponse($root, res))
326
+ .catch((err) => {
327
+ const code = err && typeof err.code === 'number' ? err.code : undefined;
328
+ const msg = MESSAGE_BY_CODE[code] || MESSAGE_BY_CODE[-1];
329
+ this.showBannerError($root, msg);
330
+ });
336
331
  },
337
332
 
338
- handleFormSubmit(e) {
339
- const { number, block } = this.el;
340
- e.preventDefault();
341
- const $root = $(e.currentTarget).closest(block);
333
+ handleVerifyResponse($root, res) {
334
+ if (res && res.code === OK_CODE) {
335
+ if (!Array.isArray(res.giftCards)) {
336
+ this.showBannerError($root, MESSAGE_BY_CODE[-1]);
337
+ return;
338
+ }
339
+ if (Number(res.giftCardTotal) === 0) {
340
+ this.showBannerError($root, ZERO_BALANCE_MESSAGE);
341
+ return;
342
+ }
342
343
 
343
- const $num = $root.find(number);
344
- $num.val(formatGc16($num.val()));
344
+ this.pendingServerGiftCards = res.giftCards.slice();
345
345
 
346
- if (!this.isValid($root)) return;
346
+ if (res.orderTotal != null || res.giftCardTotal != null) {
347
+ const data = { total: res.orderTotal, giftCardApplied: res.giftCardTotal };
348
+ updateOrderSummary(data, buildSummaryEqualMap(data), true);
349
+ this.updateCtaButton($root, { total: res.orderTotal });
350
+ this.toggleBillingDescrForZero($root, { total: res.orderTotal });
351
+ this.summaryTotal = res.orderTotal;
352
+ }
347
353
 
348
- this.lastChipData = buildChipData($root, number);
349
- this.resetForm($root);
350
- this.showSubmitSpinner($root);
354
+ this.resetForm($root);
355
+ this.showSubmitSpinner($root);
356
+ return;
357
+ }
358
+
359
+ const msg = (res && MESSAGE_BY_CODE[res.code]) || MESSAGE_BY_CODE[-1];
360
+ this.showBannerError($root, msg);
351
361
  },
352
362
 
353
- updateHintVisibility($root, summaryData) {
363
+ updateHintVisibility($root) {
354
364
  const {
355
- chipList,
356
- giftcardRow,
357
- hint,
358
- panel,
359
- toggle,
360
- iconInfo,
361
- expandedAttr,
362
- summaryHint,
363
- chip,
364
- block,
365
- body
365
+ chipList, giftcardRow, hint, panel, toggle, iconInfo, expandedAttr,
366
+ summaryHint, chip, block, body
366
367
  } = this.el;
368
+
367
369
  const $row = $root.closest(giftcardRow);
368
370
  const $giftcard = $row.find(block).first();
369
371
  const count = $row.find(`${chipList} ${chip}`).length;
370
- const limit= this.maxChips || 3;
372
+ const limit = this.maxChips || 3;
373
+
374
+ const zeroTotal = this.summaryTotal != null ? isZeroAmount(this.summaryTotal) : false;
371
375
 
372
376
  this.toggleAltPayments($giftcard, count === 0);
377
+ this.toggleAltPaymentInfos($giftcard, count === 0);
378
+
379
+ if (count > 0) {
380
+ if (!this.altPaymentPanelsLocked) {
381
+ this.showOnlyBillingAddress($giftcard);
382
+ this.altPaymentPanelsLocked = true;
383
+ } else {
384
+ this.showOnlyBillingAddress($giftcard);
385
+ }
386
+ } else {
387
+ if (!this.altPaymentPanelsLocked) {
388
+ this.toggleAltPayments($giftcard, true);
389
+ this.toggleAltPaymentInfos($giftcard, true);
390
+ } else {
391
+ this.showOnlyBillingAddress($giftcard);
392
+ }
393
+ }
373
394
 
374
395
  const $summary = this.ensureSummaryContainer($giftcard);
375
396
 
@@ -379,55 +400,79 @@ Wick.GiftCard = {
379
400
  this.resetForm($giftcard);
380
401
  $giftcard.find(panel).prop('hidden', true);
381
402
  $giftcard.find(toggle).attr(expandedAttr, 'false').show();
382
- $giftcard.find(hint).show();
383
- this.updateCtaButton($giftcard);
403
+ $giftcard.find(hint)[zeroTotal ? 'hide' : 'show']();
404
+ this.updateCtaButton($giftcard, { total: this.summaryTotal });
384
405
  return;
385
406
  }
386
407
 
408
+ this.setBillingLabelToCard($giftcard);
387
409
  $summary.show();
388
410
  $giftcard.hide();
389
411
  $giftcard.find(hint).hide();
390
412
 
391
- const ctx= buildHintContext(count, limit);
413
+ const ctx = buildHintContext(count, limit, zeroTotal);
392
414
  const html = giftCardsHint(ctx);
393
415
  const $old = $summary.find(summaryHint).first();
394
416
  const $new = $(html);
395
417
 
396
- if ($old.length) {
397
- $old.replaceWith($new);
398
- } else {
399
- $summary.append($new);
400
- }
418
+ if ($old.length) $old.replaceWith($new);
419
+ else $summary.append($new);
401
420
 
402
- const shouldHideHint = summaryData ? this.isZeroAmount(summaryData.total) : false;
403
- $new.toggle(!shouldHideHint);
404
421
 
422
+ if (zeroTotal) {
423
+ $summary.find(summaryHint).hide();
424
+ }
425
+
426
+ this.toggleCardDetailsForTotal($giftcard);
405
427
 
406
428
  if ($.fn.tooltip) {
407
- $new.find(iconInfo).tooltip({ container: body });
429
+ $summary.find(summaryHint).find(iconInfo).tooltip({ container: body });
408
430
  }
409
431
  },
410
432
 
411
- renderChipHTML({ id, last5, currency, amount }) {
412
- return giftCardChip({ id, last5, currency, amount });
433
+ renderChipHTML({ id, maskedNumber, currency, amount }) {
434
+ return giftCardChip({ id, maskedNumber, currency, amount });
435
+ },
436
+
437
+ toggleAltPaymentInfos($root, show) {
438
+ const { checkoutContainer, billieInfo, klarnaInfo } = this.el;
439
+ const $container = $root.closest(checkoutContainer);
440
+
441
+ if (this.altPaymentInfosLocked) {
442
+ show = false;
443
+ }
444
+
445
+ [$container.find(billieInfo), $container.find(klarnaInfo)].forEach(($panel) => {
446
+ if ($panel && $panel.length) {
447
+ $panel[show ? 'show' : 'hide']();
448
+ }
449
+ });
450
+
451
+ if (!show) this.altPaymentInfosLocked = true;
413
452
  },
414
453
 
415
454
  toggleAltPayments($root, show) {
416
455
  const {
417
456
  paymentForm,
418
457
  billieField,
458
+ klarnaField,
419
459
  clearpayField,
420
460
  appleField,
421
461
  googleField,
422
462
  paypalField,
423
463
  checkoutContainer,
424
464
  formRow,
465
+ cardRadio,
466
+ cardField,
425
467
  } = this.el;
426
468
 
469
+ const isTotalZero = this.summaryTotal != null ? isZeroAmount(this.summaryTotal) : false;
470
+
427
471
  const $paymentField = $root.closest(checkoutContainer).find(paymentForm);
428
472
 
429
473
  const paymentSelectors = [
430
474
  billieField,
475
+ klarnaField,
431
476
  clearpayField,
432
477
  appleField,
433
478
  googleField,
@@ -440,6 +485,19 @@ Wick.GiftCard = {
440
485
  $row[show ? 'show' : 'hide']();
441
486
  }
442
487
  });
488
+
489
+ const $cardRow = $paymentField.find(cardField).closest(formRow);
490
+ if ($cardRow.length) {
491
+ $cardRow[isTotalZero ? 'hide' : 'show']();
492
+ }
493
+
494
+ if (!show && !isTotalZero) {
495
+ let $radio = $root.closest(checkoutContainer).find(cardRadio);
496
+ if (!$radio.length) $radio = $(cardRadio);
497
+ if ($radio.length) {
498
+ $radio.prop('checked', true);
499
+ }
500
+ }
443
501
  },
444
502
 
445
503
  handleAddAnotherClick(e) {
@@ -464,78 +522,112 @@ Wick.GiftCard = {
464
522
  handleChipCloseClick(e) {
465
523
  e.preventDefault();
466
524
  const {
467
- chip, giftcardRow, block,
468
- giftcardChipClose, chipListLoader
525
+ chip,
526
+ giftcardRow,
527
+ block,
528
+ giftcardChipClose,
529
+ chipList,
530
+ loaderChip,
469
531
  } = this.el;
470
532
 
471
533
  const $clickedChip = $(e.currentTarget).closest(chip);
472
- const $row= $clickedChip.closest(giftcardRow);
473
- const $giftcard = $row.find(block).first();
534
+ const $row = $clickedChip.closest(giftcardRow);
535
+ const $giftcard = $row.find(block).first();
474
536
  const $summary = this.ensureSummaryContainer($giftcard);
475
- const $listLoader = $summary.find(chipListLoader);
476
537
 
477
- showLoader($listLoader);
478
- $row.find(giftcardChipClose)
479
- .css('pointer-events', 'none')
480
- .attr('aria-disabled', 'true');
538
+ const $chips = $summary.find(`${chipList} ${chip}`);
539
+ const index = $chips.index($clickedChip);
540
+ if (index < 0) return;
481
541
 
482
- setTimeout(() => {
483
- $clickedChip.remove();
484
- hideLoader($listLoader);
485
- $row.find(giftcardChipClose)
486
- .css('pointer-events', '')
487
- .removeAttr('aria-disabled');
542
+ let $loader = $row.children(loaderChip);
543
+ if (!$loader.length) {
544
+ appendLoader({ wrapper: $row });
545
+ $loader = $row.children(loaderChip).last();
546
+ }
488
547
 
489
- this.updateHintVisibility($giftcard);
490
- }, 1000);
491
- },
548
+ $row
549
+ .find(giftcardChipClose)
550
+ .css('pointer-events', 'none')
551
+ .attr('aria-disabled', 'true');
552
+
553
+ removeGiftCard($loader, index)
554
+ .then((res) => this.handleRemoveResponse($giftcard, res))
555
+ .catch((err) => {
556
+ const code = err && typeof err.code === 'number' ? err.code : undefined;
557
+ const msg = MESSAGE_BY_CODE[code] || MESSAGE_BY_CODE[-1];
558
+ this.showBannerError($giftcard, msg);
559
+ })
560
+ .finally(() => {
561
+ hideLoader($loader);
562
+ $row.find(giftcardChipClose).css('pointer-events', '').removeAttr('aria-disabled');
563
+ });
564
+ },
565
+
566
+ handleRemoveResponse($giftcard, res) {
567
+ if (res && res.code === OK_CODE) {
568
+ const giftCardsList = Array.isArray(res.giftCards) ? res.giftCards : [];
569
+ const data = { total: res.orderTotal, giftCardApplied: res.giftCardTotal };
570
+ this.summaryTotal = res.orderTotal;
571
+
572
+ this.renderGiftCardsFromList($giftcard, giftCardsList, data);
573
+
574
+ if (res.orderTotal != null || res.giftCardTotal != null) {
575
+ updateOrderSummary(data, buildSummaryEqualMap(data), true);
576
+ }
577
+ this.updateCtaButton && this.updateCtaButton($giftcard, { total: res.orderTotal });
578
+ this.toggleBillingDescrForZero && this.toggleBillingDescrForZero($giftcard, { total: res.orderTotal });
579
+ return;
580
+ }
492
581
 
493
- getEqualMapForGiftCardUpdate(data) {
494
- return buildSummaryEqualMap(data);
582
+ const msg = (res && MESSAGE_BY_CODE[res.code]) || MESSAGE_BY_CODE[-1];
583
+ this.showBannerError($giftcard, msg);
495
584
  },
496
585
 
497
586
  ensureSummaryContainer($root) {
498
- const {
499
- chipList, summary, giftcardRow, loaderChip
500
- } = this.el;
587
+ const { summary, giftcardRow } = this.el;
501
588
  const $row = $root.closest(giftcardRow);
502
589
  let $summary = $row.find(summary).first();
503
590
 
504
591
  if (!$summary.length) {
505
- $summary = $(`
506
- <div class="giftcard-summary" style="display:none">
507
- <div class="giftcard-chip-list"></div>
508
- <p class="giftcard-summary__hint"></p>
509
- </div>
510
- `);
592
+ $summary = $(giftcardSummary({}));
511
593
  $row.append($summary);
512
594
  }
513
595
 
514
- const $list = $summary.find(chipList);
515
- if (!$list.children(loaderChip).length) {
516
- const html = renderLoader({ hidden: true });
517
- $list.append(html);
518
- }
519
-
520
596
  return $summary;
521
597
  },
522
598
 
523
- updateCtaButton($scope, summaryData) {
524
- const { chip, ctaSelector, btnDetails, btnText, giftcardRow } = this.el;
525
- const hasChips = $scope.closest(giftcardRow).find(chip).length > 0;
526
- const $cta = $(ctaSelector).closest(btnDetails);
599
+ getPageLoader() {
600
+ const $pl = $(this.el.pageLoader);
601
+ if (!$pl.parent().is('body')) $pl.appendTo('body');
602
+ return $pl;
603
+ },
527
604
 
605
+ showPageLoader() {
606
+ const $pl = this.getPageLoader();
607
+ showLoader($pl);
608
+ },
609
+
610
+ updateCtaButton($scope, summaryData) {
611
+ const { ctaSelector, btnDetails, btnText, checkoutContainer } = this.el;
612
+ let $cta = $(ctaSelector);
613
+ if (!$cta.length) {
614
+ $cta = $scope.closest(checkoutContainer).find(ctaSelector);
615
+ }
528
616
  if (!$cta.length) return;
529
617
 
530
- const forceGiftcard = summaryData ? this.isZeroAmount(summaryData.total) : false;
531
- const useGiftcard = forceGiftcard || hasChips;
532
- $cta.find(btnText).text(useGiftcard ? this.messages.ctaGiftcardText : this.messages.ctaDefaultText);
533
- $cta.attr('data-mode', useGiftcard ? 'giftcards' : 'card');
534
- },
618
+ const $wrap = $cta.closest(btnDetails).length ? $cta.closest(btnDetails) : $cta;
619
+ const $text = $wrap.find(btnText).first().length ? $wrap.find(btnText).first() : $wrap;
620
+ const totalLike = summaryData?.total;
621
+ const useGiftcard = isZeroAmount(totalLike);
535
622
 
536
- isZeroAmount(val) {
537
- const num = Number(String(val ?? '').replace(/[^\d.]/g, ''));
538
- return Number.isFinite(num) && num === 0;
623
+ $text.text(useGiftcard ? this.messages.ctaGiftcardText : this.messages.ctaDefaultText);
624
+ $wrap.attr('data-mode', useGiftcard ? 'giftcards' : 'card');
625
+
626
+ if (useGiftcard) {
627
+ $wrap.attr('type', 'button').removeAttr('form');
628
+ } else {
629
+ $wrap.attr('type', 'submit').attr('form', 'hopPaymentAddressPostForm');
630
+ }
539
631
  },
540
632
 
541
633
  toggleBillingDescrForZero($scope, summaryData) {
@@ -543,52 +635,161 @@ Wick.GiftCard = {
543
635
  const $descr = $container.find(this.el.checkoutPaymentFields);
544
636
  if (!$descr.length) return;
545
637
 
546
- const shouldHide = summaryData ? this.isZeroAmount(summaryData.total) : false;
638
+ const shouldHide = summaryData ? isZeroAmount(summaryData.total) : false;
547
639
  $descr[shouldHide ? 'hide' : 'show']();
548
640
  },
549
641
 
550
- getPageLoader() {
551
- const $pageLoader = $(this.el.pageLoader);
552
- if (!$pageLoader.parent().is('body')) $pageLoader.appendTo('body');
553
- return $pageLoader;
642
+ getNumberValue($root) {
643
+ const { number } = this.el;
644
+ return String($root.find(number).val() || '').replace(/\D/g, '');
554
645
  },
555
646
 
556
- showPageLoader() {
557
- const $pageLoader = this.getPageLoader();
558
- showLoader($pageLoader);
647
+ getPinValue($root) {
648
+ const { pin } = this.el;
649
+ return String($root.find(pin).val() || '').replace(/\D/g, '');
559
650
  },
560
651
 
561
-
562
- bindIframePaypageLoader() {
563
- const { iframe, payBtnSelector } = this.el;
652
+ bindGiftcardCtaHandler() {
564
653
  const self = this;
565
- $(iframe).off('load.giftcard').on('load.giftcard', (ev) => {
566
- const iframeEl = ev.currentTarget;
567
654
 
568
- let doc;
569
- try {
570
- doc = iframeEl.contentDocument || iframeEl.contentWindow?.document;
571
- } catch (e) {
572
- return;
573
- }
574
- if (!doc) return;
655
+ $(document).off('click.giftcard', this.el.btnEnterDetails);
656
+ $(document).on('click.giftcard', this.el.btnEnterDetails, function (e) {
657
+ const $btn = $(this);
658
+ const mode = $btn.attr('data-mode');
659
+
660
+ if (mode !== 'giftcards') return;
661
+
662
+ e.preventDefault();
663
+ e.stopPropagation();
664
+ e.stopImmediatePropagation();
665
+
666
+ const $scope = $btn.closest(self.el.checkoutContainer).find(self.el.block).first();
667
+ const $root = $scope.length ? $scope : $(self.el.block).first();
668
+
669
+ self.showPageLoader();
670
+
671
+ payWithGiftCard()
672
+ .then(({ url }) => {
673
+ if (typeof url === 'string' && url) {
674
+ window.location.href = url;
675
+ }
676
+ })
677
+ .catch((error) => {
678
+ if (Array.isArray(error?.giftCards)) {
679
+ self.renderGiftCardsFromList($root, error.giftCards);
680
+ }
681
+
682
+ const numberCode = Number(error?.errorCode);
683
+
684
+ if (Number.isFinite(numberCode) && PAY_MESSAGE_BY_CODE[numberCode]) {
685
+ Wick.Notification.show({
686
+ text: linkifyClickHere(PAY_MESSAGE_BY_CODE[numberCode]),
687
+ container: '.globalMessages .container',
688
+ type: 'error',
689
+ });
690
+ }
691
+ })
692
+ .finally(() => {
693
+ const $pl = self.getPageLoader();
694
+ hideLoader($pl);
695
+ });
696
+ });
697
+ },
575
698
 
576
- const $doc = $(doc);
577
- $doc.off('.giftcard');
699
+ showBannerError($root, message, title = '') {
700
+ const { notificationRoot, giftCardInline, inlineHeader } = this.el;
701
+ const $inline = $root.find(giftCardInline);
578
702
 
579
- $doc
580
- .on('click.giftcard', payBtnSelector, () => self.showPageLoader())
581
- .on('submit.giftcard', 'form', () => self.showPageLoader());
703
+ $inline.find(notificationRoot).remove();
704
+
705
+ const html = notificationTpl({
706
+ classModifier: 'notification_error',
707
+ error: true,
708
+ withCloseBtn: true,
709
+ title: title || undefined,
710
+ text: linkifyClickHere(message)
582
711
  });
712
+
713
+ $inline.find(inlineHeader).after(html);
583
714
  },
584
715
 
585
- bindCtaGiftcardLoader() {
586
- const self = this;
587
- $(document).on('click.giftcard', this.el.btnEnterDetails, function () {
588
- if ($(this).attr('data-mode') === 'giftcards') {
589
- self.showPageLoader();
590
- }
591
- });
716
+
717
+ hideBanner($root) {
718
+ const { notification } = this.el;
719
+ $root.find(notification).remove();
720
+ },
721
+
722
+ mapGiftToChipData($root, gift) {
723
+ const $giftcard = $root.closest(this.el.block);
724
+ const currency = $giftcard.data('currency') || '£';
725
+ return {
726
+ id: gift.id,
727
+ maskedNumber: String(gift.maskedNumber || ''),
728
+ currency,
729
+ amount: Number(gift.amountToRedeem || 0),
730
+ };
731
+ },
732
+
733
+ renderGiftCardsFromList($root, giftCards, summaryData) {
734
+ const { chipList, loaderChip, chip } = this.el;
735
+ const $summary = this.ensureSummaryContainer($root);
736
+ const $list = $summary.find(chipList);
737
+ $list.children(`${chip}:not(${loaderChip})`).remove();
738
+
739
+ const html = (giftCards || [])
740
+ .map((g) => this.renderChipHTML(this.mapGiftToChipData($root, g)))
741
+ .join('');
742
+
743
+ if (html) {
744
+ $list.append(html);
745
+ $summary.show();
746
+ }
747
+
748
+ this.updateHintVisibility($root, summaryData);
749
+
750
+ if (Array.isArray(giftCards) && giftCards.length > 0) {
751
+ this.setBillingLabelToCard($root);
752
+ }
753
+ },
754
+
755
+ toggleCardDetailsForTotal($scope) {
756
+ const { checkoutContainer, cardDetails, hiddenCard } = this.el;
757
+ const $container = $scope.closest(checkoutContainer);
758
+ const $cardDetails = $container.find(cardDetails);
759
+ if (!$cardDetails.length) return;
760
+
761
+ const zero = this.summaryTotal != null ? isZeroAmount(this.summaryTotal) : false;
762
+ if (zero) $cardDetails.addClass(hiddenCard);
763
+ },
764
+
765
+ setBillingLabelToCard($scope) {
766
+ const $container = $scope.closest(this.el.checkoutContainer);
767
+ const $billing = $container.find(this.el.billingAddress);
768
+ if (!$billing.length) return;
769
+
770
+ const $label = $billing.find('[for="card-name"]');
771
+ if ($label.length) {
772
+ $label.text('Name on card');
773
+ }
774
+ },
775
+
776
+ showOnlyBillingAddress($root) {
777
+ const { checkoutContainer, billingAddress, altPaymentRowsAttr, hiddenCard } = this.el;
778
+ const $container = $root.closest(checkoutContainer);
779
+ $container.find(billingAddress).removeClass(hiddenCard);
780
+ $container.find(altPaymentRowsAttr).addClass(hiddenCard);
781
+ this.toggleAltPaymentInfos($root, false);
782
+ },
783
+
784
+ useGlobalError($root, message) {
785
+ if (window.Wick && Wick.Notification && typeof Wick.Notification.show === 'function') {
786
+ const text = linkifyClickHere ? linkifyClickHere(message) : String(message || '');
787
+ Wick.Notification.show({
788
+ text,
789
+ container: '.globalMessages .container',
790
+ type: 'error',
791
+ });
792
+ }
592
793
  },
593
794
 
594
795
  bindEvents() {
@@ -596,15 +797,49 @@ Wick.GiftCard = {
596
797
 
597
798
  $(document).off('.giftcard');
598
799
 
800
+ const handleNumberInput = createGcNumberInputHandler({
801
+ fieldSelector: this.el.field,
802
+ fieldErrorClass: this.el.fieldError,
803
+ errorTextSelector: this.el.errorText,
804
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
805
+ });
806
+
807
+ const onNumberBlur = (e) =>
808
+ handleGcNumberBlur(e, {
809
+ fieldSelector: this.el.field,
810
+ errorTextSelector: this.el.errorText,
811
+ errorClass: this.el.fieldError,
812
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
813
+ numberMessage: this.messages.numberMessage,
814
+ });
815
+
816
+ const onPinInput = (e) =>
817
+ handleGcPinInput(e, {
818
+ fieldSelector: this.el.field,
819
+ errorTextSelector: this.el.errorText,
820
+ errorClass: this.el.fieldError,
821
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
822
+ });
823
+
824
+ const onPinBlur = (e) =>
825
+ handleGcPinBlur(e, {
826
+ fieldSelector: this.el.field,
827
+ errorTextSelector: this.el.errorText,
828
+ errorClass: this.el.fieldError,
829
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
830
+ pinMessage: this.messages.pinMessage,
831
+ });
832
+
833
+
599
834
  $(document)
600
- .on('input.giftcard', number, this.handleNumberInput.bind(this))
601
- .on('keypress.giftcard', number, this.handleNumberKeypress.bind(this))
602
- .on('blur.giftcard', number, this.handleNumberBlur.bind(this));
835
+ .on('input.giftcard', number, handleNumberInput)
836
+ .on('keypress.giftcard', number, digitsOnlyKeypress)
837
+ .on('blur.giftcard', number, onNumberBlur);
603
838
 
604
839
  $(document)
605
- .on('input.giftcard', pin, this.handlePinInput.bind(this))
606
- .on('keypress.giftcard', pin, this.handlePinKeypress.bind(this))
607
- .on('blur.giftcard', pin, this.handlePinBlur.bind(this));
840
+ .on('input.giftcard', pin, onPinInput)
841
+ .on('keypress.giftcard', pin, handleGcPinKeypress)
842
+ .on('blur.giftcard', pin, onPinBlur);
608
843
 
609
844
  $(document)
610
845
  .on('click.giftcard', toggle, this.handleToggleClick.bind(this))
@@ -613,13 +848,25 @@ Wick.GiftCard = {
613
848
  .on('click.giftcard', jsGiftcardAdd, this.handleAddAnotherClick.bind(this))
614
849
  .on('click.giftcard', giftcardChipClose, this.handleChipCloseClick.bind(this))
615
850
 
616
- this.bindIframePaypageLoader();
617
- this.bindCtaGiftcardLoader();
851
+ this.bindGiftcardCtaHandler();
618
852
  },
619
853
 
620
- init() {
854
+ init(payload = {}) {
621
855
  const { field, pin, $blocks } = this.el;
622
856
  this.bindEvents();
857
+ if ($blocks && $blocks.length) {
858
+ const $firstBlock = $blocks.first();
859
+ if (Array.isArray(payload.giftCards) && payload.giftCards.length) {
860
+ this.renderGiftCardsFromList($firstBlock, payload.giftCards);
861
+ }
862
+
863
+ const code = payload.errorCode;
864
+
865
+ if (Number.isFinite(code) && PAY_MESSAGE_BY_CODE[code]) {
866
+ this.useGlobalError($firstBlock, PAY_MESSAGE_BY_CODE[code]);
867
+ }
868
+ }
869
+
623
870
  $blocks.each((index, block) => {
624
871
  const $pinField = $(block).find(pin).closest(field);
625
872
  if ($pinField.length) {
@@ -628,7 +875,3 @@ init() {
628
875
  });
629
876
  }
630
877
  };
631
-
632
- $(function () {
633
- Wick.GiftCard.init();
634
- });