wickes-css2 2.103.0-gift-cards.1 → 2.103.0-gift-cards.4

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 (41) 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 +204 -2
  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 +505 -238
  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/checkout-data.js +2 -2
  32. package/src/js/emulation/gift-cards.js +201 -0
  33. package/src/js/page/components/gift-cards.js +505 -238
  34. package/src/js/page/components/order-summary.js +42 -25
  35. package/src/js/page/utils/gift-cards-utils.js +117 -16
  36. package/src/js/page/utils/input-handling.js +92 -0
  37. package/src/js/page/utils/validation.js +46 -1
  38. package/src/layouts/checkout.hbs +1 -5
  39. package/src/page_payment-details-with-gift-card.html +5 -3
  40. package/src/scss/components/_gift-cards.scss +5 -1
  41. 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,13 +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',
105
+ btnEnterDetails: '.btn-enter-details',
106
+ checkoutPaymentFields: '.billing-address .checkout-payment-details__descr',
69
107
  pageLoader: '.loader-wrapper.page-loader',
70
- iframe: '#hopIframe',
71
- payBtnSelector: '#btnSubmit,input[type="submit"].submit-PayNow,.submitButton.btn.primary.submit-PayNow',
72
- btnEnterDetails: '.billing-address .btn-enter-details'
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',
73
115
  },
74
116
 
75
117
  messages: {
@@ -80,29 +122,11 @@ Wick.GiftCard = {
80
122
  },
81
123
 
82
124
  maxChips: 3,
83
- lastChipData: null,
84
125
  successTimer: null,
85
-
86
- showError($input, message) {
87
- const { field, errorText, fieldError, ariaInvalidAttr } = this.el;
88
- const $field = $input.closest(field);
89
- $field.addClass(fieldError);
90
- $input.attr(ariaInvalidAttr, 'true');
91
-
92
- let $err = $field.find(errorText);
93
- if (!$err.length) {
94
- $err = $('<div class="giftcard__error-text" />').appendTo($field);
95
- }
96
- $err.text(message);
97
- },
98
-
99
- clearError($input) {
100
- const { field, errorText, fieldError, ariaInvalidAttr } = this.el;
101
- const $field = $input.closest(field);
102
- $field.removeClass(fieldError);
103
- $input.removeAttr(ariaInvalidAttr);
104
- $field.find(errorText).remove();
105
- },
126
+ pendingServerGiftCards: null,
127
+ altPaymentInfosLocked: false,
128
+ altPaymentPanelsLocked: false,
129
+ summaryTotal: null,
106
130
 
107
131
  resetPinToggle($root) {
108
132
  const {
@@ -147,15 +171,17 @@ Wick.GiftCard = {
147
171
 
148
172
  $num.val('');
149
173
  $pinInput.val('');
150
- this.clearError($num);
151
- 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);
152
176
  $root.find(note).remove();
153
177
 
178
+ this.hideBanner($root);
154
179
  this.resetPinToggle($root);
155
180
  },
156
181
 
157
182
  open($root) {
158
183
  const { toggle, panel, hint, pin, expandedAttr, passwordToggleEvent } = this.el;
184
+ this.hideBanner($root);
159
185
  $root.show();
160
186
 
161
187
  $root.find(panel).prop('hidden', false);
@@ -182,66 +208,11 @@ Wick.GiftCard = {
182
208
  },
183
209
 
184
210
  showSubmitSpinner($root) {
185
- const { loader, panel, toggle, hint, expandedAttr } = this.el;
186
- const $loader = $root.find(loader);
187
-
188
- showLoader($loader);
189
-
190
- setTimeout(() => {
191
- hideLoader($loader);
192
-
193
- $root.find(panel).prop('hidden', true);
194
- $root.find(toggle).attr(expandedAttr, 'false').show().trigger('focus');
195
- $root.find(hint).show();
196
- this.showSuccess($root);
197
- }, 1000);
198
- },
199
-
200
- handleNumberInput(e) {
201
- const { field, fieldError } = this.el;
202
- const input = e.currentTarget;
203
- const $input = $(input);
204
-
205
- const atEnd = input.selectionStart === input.value.length;
206
- const hadErr = $input.closest(field).hasClass(fieldError);
207
-
208
- input.value = formatGc16(input.value);
209
- if (hadErr) this.clearError($input);
210
- if (atEnd) input.setSelectionRange(input.value.length, input.value.length);
211
- },
212
-
213
- handleNumberKeypress(e) {
214
- if (!/\d/.test(String.fromCharCode(e.which || e.keyCode))) e.preventDefault();
215
- },
216
-
217
- handleNumberBlur(e) {
218
- const $input = $(e.currentTarget);
219
- const len = String($input.val() || '').replace(/\D/g, '').length;
220
- this.clearError($input);
221
- if (len < 16) this.showError($input, this.messages.numberMessage);
222
- },
223
-
224
- handlePinInput(e) {
225
- const { field, fieldError } = this.el;
226
- const input = e.currentTarget;
227
- const $input = $(input);
228
-
229
- const hadErr = $input.closest(field).hasClass(fieldError);
230
- input.value = String(input.value || '').replace(/\D/g, '').slice(0, 8);
231
- if (hadErr) this.clearError($input);
232
- },
233
-
234
- handlePinKeypress(e) {
235
- if (!/\d/.test(String.fromCharCode(e.which || e.keyCode))) e.preventDefault();
236
- },
237
-
238
- handlePinBlur(e) {
239
- const $input = $(e.currentTarget);
240
- const length = String($input.val() || '').replace(/\D/g, '').length;
241
- this.clearError($input);
242
- if (length < 8) {
243
- this.showError($input, this.messages.pinMessage);
244
- }
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);
245
216
  },
246
217
 
247
218
  handleToggleClick(e) {
@@ -257,7 +228,7 @@ Wick.GiftCard = {
257
228
  },
258
229
 
259
230
  finalizeSuccess($root, $success) {
260
- let { panel, toggle, hint, expandedAttr, chipList } = this.el;
231
+ let { panel, toggle, hint, expandedAttr } = this.el;
261
232
 
262
233
  if ($success && $success.length) {
263
234
  $success.attr('hidden', 'hidden').removeClass('fade show').off('transitionend');
@@ -268,28 +239,15 @@ Wick.GiftCard = {
268
239
  $root.find(toggle).attr(expandedAttr, 'false').show();
269
240
  $root.find(hint).show();
270
241
 
271
- if (this.lastChipData) {
272
- const $summary = this.ensureSummaryContainer($root);
273
- const html = this.renderChipHTML(this.lastChipData);
274
- $summary.find(chipList).append(html);
275
- $summary.show();
276
- this.lastChipData = null;
277
- this.updateCtaButton($root);
242
+ if (this.pendingServerGiftCards) {
243
+ this.renderGiftCardsFromList($root, this.pendingServerGiftCards);
244
+ this.pendingServerGiftCards = null;
278
245
  }
279
246
 
280
- const data = Wick.CheckoutData.successGiftCardData;
281
- updateOrderSummary(
282
- data,
283
- this.getEqualMapForGiftCardUpdate(data),
284
- true,
285
- );
286
-
287
247
  this.updateHintVisibility($root);
288
- this.updateCtaButton($root);
289
248
  this.successTimer = null;
290
249
  },
291
250
 
292
-
293
251
  showSuccess($root) {
294
252
  const { success} = this.el;
295
253
 
@@ -313,62 +271,126 @@ Wick.GiftCard = {
313
271
  }, 3000);
314
272
  },
315
273
 
316
- isValid($root) {
317
- const {number, pin} = this.el;
318
- const $numInput = $root.find(number);
319
- const $pinInput = $root.find(pin);
320
- const numField = String($numInput.val() || '').replace(/\D/g, '');
321
- const pinField = String($pinInput.val() || '').replace(/\D/g, '');
322
- let valid = true;
274
+ handleFormSubmit(e) {
275
+ const { number, block, giftcardRow, loaderChip } = this.el;
276
+ e.preventDefault();
323
277
 
324
- if (numField.length !== 16) {
325
- this.showError($numInput, this.messages.numberMessage);
326
- valid = false;
278
+ const $root = $(e.currentTarget).closest(block);
279
+ this.hideBanner($root);
280
+
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;
327
311
  }
328
312
 
329
- if (pinField.length !== 8) {
330
- this.showError($pinInput, this.messages.pinMessage);
331
- 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();
332
322
  }
333
323
 
334
- 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
+ });
335
331
  },
336
332
 
337
- handleFormSubmit(e) {
338
- const { number, block } = this.el;
339
- e.preventDefault();
340
- 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
+ }
341
343
 
342
- const $num = $root.find(number);
343
- $num.val(formatGc16($num.val()));
344
+ this.pendingServerGiftCards = res.giftCards.slice();
345
+
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
+ }
344
353
 
345
- if (!this.isValid($root)) return;
354
+ this.resetForm($root);
355
+ this.showSubmitSpinner($root);
356
+ return;
357
+ }
346
358
 
347
- this.lastChipData = buildChipData($root, number);
348
- this.resetForm($root);
349
- this.showSubmitSpinner($root);
359
+ const msg = (res && MESSAGE_BY_CODE[res.code]) || MESSAGE_BY_CODE[-1];
360
+ this.showBannerError($root, msg);
350
361
  },
351
362
 
352
363
  updateHintVisibility($root) {
353
364
  const {
354
- chipList,
355
- giftcardRow,
356
- hint,
357
- panel,
358
- toggle,
359
- iconInfo,
360
- expandedAttr,
361
- summaryHint,
362
- chip,
363
- block,
364
- body
365
+ chipList, giftcardRow, hint, panel, toggle, iconInfo, expandedAttr,
366
+ summaryHint, chip, block, body
365
367
  } = this.el;
368
+
366
369
  const $row = $root.closest(giftcardRow);
367
370
  const $giftcard = $row.find(block).first();
368
371
  const count = $row.find(`${chipList} ${chip}`).length;
369
- const limit= this.maxChips || 3;
372
+ const limit = this.maxChips || 3;
373
+
374
+ const zeroTotal = this.summaryTotal != null ? isZeroAmount(this.summaryTotal) : false;
370
375
 
371
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
+ }
372
394
 
373
395
  const $summary = this.ensureSummaryContainer($giftcard);
374
396
 
@@ -378,16 +400,17 @@ Wick.GiftCard = {
378
400
  this.resetForm($giftcard);
379
401
  $giftcard.find(panel).prop('hidden', true);
380
402
  $giftcard.find(toggle).attr(expandedAttr, 'false').show();
381
- $giftcard.find(hint).show();
382
- this.updateCtaButton($giftcard);
403
+ $giftcard.find(hint)[zeroTotal ? 'hide' : 'show']();
404
+ this.updateCtaButton($giftcard, { total: this.summaryTotal });
383
405
  return;
384
406
  }
385
407
 
408
+ this.setBillingLabelToCard($giftcard);
386
409
  $summary.show();
387
410
  $giftcard.hide();
388
411
  $giftcard.find(hint).hide();
389
412
 
390
- const ctx= buildHintContext(count, limit);
413
+ const ctx = buildHintContext(count, limit, zeroTotal);
391
414
  const html = giftCardsHint(ctx);
392
415
  const $old = $summary.find(summaryHint).first();
393
416
  const $new = $(html);
@@ -395,31 +418,61 @@ Wick.GiftCard = {
395
418
  if ($old.length) $old.replaceWith($new);
396
419
  else $summary.append($new);
397
420
 
421
+
422
+ if (zeroTotal) {
423
+ $summary.find(summaryHint).hide();
424
+ }
425
+
426
+ this.toggleCardDetailsForTotal($giftcard);
427
+
398
428
  if ($.fn.tooltip) {
399
- $new.find(iconInfo).tooltip({ container: body });
429
+ $summary.find(summaryHint).find(iconInfo).tooltip({ container: body });
400
430
  }
401
431
  },
402
432
 
403
- renderChipHTML({ id, last5, currency, amount }) {
404
- 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;
405
452
  },
406
453
 
407
454
  toggleAltPayments($root, show) {
408
455
  const {
409
456
  paymentForm,
410
457
  billieField,
458
+ klarnaField,
411
459
  clearpayField,
412
460
  appleField,
413
461
  googleField,
414
462
  paypalField,
415
463
  checkoutContainer,
416
464
  formRow,
465
+ cardRadio,
466
+ cardField,
417
467
  } = this.el;
418
468
 
469
+ const isTotalZero = this.summaryTotal != null ? isZeroAmount(this.summaryTotal) : false;
470
+
419
471
  const $paymentField = $root.closest(checkoutContainer).find(paymentForm);
420
472
 
421
473
  const paymentSelectors = [
422
474
  billieField,
475
+ klarnaField,
423
476
  clearpayField,
424
477
  appleField,
425
478
  googleField,
@@ -432,6 +485,19 @@ Wick.GiftCard = {
432
485
  $row[show ? 'show' : 'hide']();
433
486
  }
434
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
+ }
435
501
  },
436
502
 
437
503
  handleAddAnotherClick(e) {
@@ -456,115 +522,274 @@ Wick.GiftCard = {
456
522
  handleChipCloseClick(e) {
457
523
  e.preventDefault();
458
524
  const {
459
- chip, giftcardRow, block,
460
- giftcardChipClose, chipListLoader
525
+ chip,
526
+ giftcardRow,
527
+ block,
528
+ giftcardChipClose,
529
+ chipList,
530
+ loaderChip,
461
531
  } = this.el;
462
532
 
463
533
  const $clickedChip = $(e.currentTarget).closest(chip);
464
- const $row= $clickedChip.closest(giftcardRow);
465
- const $giftcard = $row.find(block).first();
534
+ const $row = $clickedChip.closest(giftcardRow);
535
+ const $giftcard = $row.find(block).first();
466
536
  const $summary = this.ensureSummaryContainer($giftcard);
467
- const $listLoader = $summary.find(chipListLoader);
468
537
 
469
- showLoader($listLoader);
470
- $row.find(giftcardChipClose)
471
- .css('pointer-events', 'none')
472
- .attr('aria-disabled', 'true');
538
+ const $chips = $summary.find(`${chipList} ${chip}`);
539
+ const index = $chips.index($clickedChip);
540
+ if (index < 0) return;
473
541
 
474
- setTimeout(() => {
475
- $clickedChip.remove();
476
- hideLoader($listLoader);
477
- $row.find(giftcardChipClose)
478
- .css('pointer-events', '')
479
- .removeAttr('aria-disabled');
542
+ let $loader = $row.children(loaderChip);
543
+ if (!$loader.length) {
544
+ appendLoader({ wrapper: $row });
545
+ $loader = $row.children(loaderChip).last();
546
+ }
480
547
 
481
- this.updateHintVisibility($giftcard);
482
- }, 1000);
483
- },
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
+ }
484
581
 
485
- getEqualMapForGiftCardUpdate(data) {
486
- return buildSummaryEqualMap(data);
582
+ const msg = (res && MESSAGE_BY_CODE[res.code]) || MESSAGE_BY_CODE[-1];
583
+ this.showBannerError($giftcard, msg);
487
584
  },
488
585
 
489
586
  ensureSummaryContainer($root) {
490
- const {
491
- chipList, summary, giftcardRow, loaderChip
492
- } = this.el;
587
+ const { summary, giftcardRow } = this.el;
493
588
  const $row = $root.closest(giftcardRow);
494
589
  let $summary = $row.find(summary).first();
495
590
 
496
591
  if (!$summary.length) {
497
- $summary = $(`
498
- <div class="giftcard-summary" style="display:none">
499
- <div class="giftcard-chip-list"></div>
500
- <p class="giftcard-summary__hint"></p>
501
- </div>
502
- `);
592
+ $summary = $(giftcardSummary({}));
503
593
  $row.append($summary);
504
594
  }
505
595
 
506
- const $list = $summary.find(chipList);
507
- if (!$list.children(loaderChip).length) {
508
- const html = renderLoader({ hidden: true });
509
- $list.append(html);
510
- }
511
-
512
596
  return $summary;
513
597
  },
514
598
 
515
- updateCtaButton($scope) {
516
- const { chip, ctaSelector, btnDetails, btnText, giftcardRow } = this.el;
517
- const hasChips = $scope.closest(giftcardRow).find(chip).length > 0;
599
+ getPageLoader() {
600
+ const $pl = $(this.el.pageLoader);
601
+ if (!$pl.parent().is('body')) $pl.appendTo('body');
602
+ return $pl;
603
+ },
518
604
 
519
- const $cta = $(ctaSelector).closest(btnDetails);
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
+ }
520
616
  if (!$cta.length) return;
521
617
 
522
- $cta.find(btnText).text(hasChips ? this.messages.ctaGiftcardText : this.messages.ctaDefaultText);
523
- $cta.attr('data-mode', hasChips ? 'giftcards' : 'card');
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);
622
+
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
+ }
524
631
  },
525
632
 
633
+ toggleBillingDescrForZero($scope, summaryData) {
634
+ const $container = $scope.closest(this.el.checkoutContainer);
635
+ const $descr = $container.find(this.el.checkoutPaymentFields);
636
+ if (!$descr.length) return;
526
637
 
527
- getPageLoader() {
528
- const $pageLoader = $(this.el.pageLoader);
529
- if (!$pageLoader.parent().is('body')) $pageLoader.appendTo('body');
530
- return $pageLoader;
638
+ const shouldHide = summaryData ? isZeroAmount(summaryData.total) : false;
639
+ $descr[shouldHide ? 'hide' : 'show']();
531
640
  },
532
641
 
533
- showPageLoader() {
534
- const $pageLoader = this.getPageLoader();
535
- showLoader($pageLoader);
642
+ getNumberValue($root) {
643
+ const { number } = this.el;
644
+ return String($root.find(number).val() || '').replace(/\D/g, '');
645
+ },
646
+
647
+ getPinValue($root) {
648
+ const { pin } = this.el;
649
+ return String($root.find(pin).val() || '').replace(/\D/g, '');
536
650
  },
537
651
 
538
- bindIframePaypageLoader() {
539
- const { iframe, payBtnSelector } = this.el;
652
+ bindGiftcardCtaHandler() {
540
653
  const self = this;
541
- $(iframe).off('load.giftcard').on('load.giftcard', (ev) => {
542
- const iframeEl = ev.currentTarget;
543
654
 
544
- let doc;
545
- try {
546
- doc = iframeEl.contentDocument || iframeEl.contentWindow?.document;
547
- } catch (e) {
548
- return;
549
- }
550
- 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
+ },
698
+
699
+ showBannerError($root, message, title = '') {
700
+ const { notificationRoot, giftCardInline, inlineHeader } = this.el;
701
+ const $inline = $root.find(giftCardInline);
551
702
 
552
- const $doc = $(doc);
553
- $doc.off('.giftcard');
703
+ $inline.find(notificationRoot).remove();
554
704
 
555
- $doc
556
- .on('click.giftcard', payBtnSelector, () => self.showPageLoader())
557
- .on('submit.giftcard', 'form', () => self.showPageLoader());
705
+ const html = notificationTpl({
706
+ classModifier: 'notification_error',
707
+ error: true,
708
+ withCloseBtn: true,
709
+ title: title || undefined,
710
+ text: linkifyClickHere(message)
558
711
  });
712
+
713
+ $inline.find(inlineHeader).after(html);
559
714
  },
560
715
 
561
- bindCtaGiftcardLoader() {
562
- const self = this;
563
- $(document).on('click.giftcard', this.el.btnEnterDetails, function () {
564
- if ($(this).attr('data-mode') === 'giftcards') {
565
- self.showPageLoader();
566
- }
567
- });
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
+ }
568
793
  },
569
794
 
570
795
  bindEvents() {
@@ -572,15 +797,49 @@ Wick.GiftCard = {
572
797
 
573
798
  $(document).off('.giftcard');
574
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
+
575
834
  $(document)
576
- .on('input.giftcard', number, this.handleNumberInput.bind(this))
577
- .on('keypress.giftcard', number, this.handleNumberKeypress.bind(this))
578
- .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);
579
838
 
580
839
  $(document)
581
- .on('input.giftcard', pin, this.handlePinInput.bind(this))
582
- .on('keypress.giftcard', pin, this.handlePinKeypress.bind(this))
583
- .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);
584
843
 
585
844
  $(document)
586
845
  .on('click.giftcard', toggle, this.handleToggleClick.bind(this))
@@ -589,13 +848,25 @@ Wick.GiftCard = {
589
848
  .on('click.giftcard', jsGiftcardAdd, this.handleAddAnotherClick.bind(this))
590
849
  .on('click.giftcard', giftcardChipClose, this.handleChipCloseClick.bind(this))
591
850
 
592
- this.bindIframePaypageLoader();
593
- this.bindCtaGiftcardLoader();
851
+ this.bindGiftcardCtaHandler();
594
852
  },
595
853
 
596
- init() {
854
+ init(payload = {}) {
597
855
  const { field, pin, $blocks } = this.el;
598
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
+
599
870
  $blocks.each((index, block) => {
600
871
  const $pinField = $(block).find(pin).closest(field);
601
872
  if ($pinField.length) {
@@ -604,7 +875,3 @@ init() {
604
875
  });
605
876
  }
606
877
  };
607
-
608
- $(function () {
609
- Wick.GiftCard.init();
610
- });