@dintero/checkout-web-sdk 0.5.9 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +4 -2
  2. package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/checkout.ts.html +92 -5
  3. package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/createIframeAsync.ts.html +15 -15
  4. package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/index.html +88 -43
  5. package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/index.ts.html +1085 -95
  6. package/coverage/Chrome Headless 117.0.5938.92 (Linux x86_64)/html/popOut.ts.html +377 -0
  7. package/coverage/Chrome Headless 117.0.5938.92 (Linux x86_64)/html/popOutBackdrop.ts.html +1046 -0
  8. package/coverage/Chrome Headless 117.0.5938.92 (Linux x86_64)/html/popOutButton.ts.html +374 -0
  9. package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/subscribe.ts.html +218 -29
  10. package/coverage/Chrome Headless 117.0.5938.92 (Linux x86_64)/html/url.ts.html +284 -0
  11. package/dist/declarations/src/checkout.d.ts +45 -17
  12. package/dist/declarations/src/index.d.ts +17 -1
  13. package/dist/declarations/src/session.d.ts +19 -35
  14. package/dist/declarations/src/subscribe.d.ts +62 -0
  15. package/dist/dintero-checkout-web-sdk.cjs.dev.js +927 -63
  16. package/dist/dintero-checkout-web-sdk.cjs.prod.js +927 -63
  17. package/dist/dintero-checkout-web-sdk.esm.js +927 -63
  18. package/dist/dintero-checkout-web-sdk.umd.min.js +1 -1
  19. package/dist/dintero-checkout-web-sdk.umd.min.js.map +1 -1
  20. package/package.json +5 -5
  21. package/coverage/Chrome Headless 115.0.5790.98 (Linux x86_64)/html/url.ts.html +0 -185
  22. /package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/base.css +0 -0
  23. /package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/block-navigation.js +0 -0
  24. /package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/favicon.png +0 -0
  25. /package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/prettify.css +0 -0
  26. /package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/prettify.js +0 -0
  27. /package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/sort-arrow-sprite.png +0 -0
  28. /package/coverage/{Chrome Headless 115.0.5790.98 (Linux x86_64) → Chrome Headless 117.0.5938.92 (Linux x86_64)}/html/sorter.js +0 -0
@@ -6,7 +6,7 @@ require('native-promise-only');
6
6
 
7
7
  var pkg = {
8
8
  name: "@dintero/checkout-web-sdk",
9
- version: "0.5.9",
9
+ version: "0.6.0",
10
10
  description: "Dintero Checkout SDK for web frontends",
11
11
  main: "dist/dintero-checkout-web-sdk.cjs.js",
12
12
  module: "dist/dintero-checkout-web-sdk.esm.js",
@@ -45,19 +45,18 @@ var pkg = {
45
45
  "karma-mocha": "^2.0.1",
46
46
  "karma-typescript": "^5.0.3",
47
47
  mocha: "^8.1.1",
48
- prettier: "^2.6.2",
49
- puppeteer: "^20.9.0",
50
- "semantic-release": "^21.0.2",
48
+ prettier: "^3.0.3",
49
+ puppeteer: "^21.1.0",
50
+ "semantic-release": "^22.0.1",
51
51
  sinon: "^15.0.0",
52
- typescript: "^4.2.4"
52
+ typescript: "^5.2.2"
53
53
  },
54
54
  dependencies: {
55
55
  "native-promise-only": "^0.8.1"
56
56
  }
57
57
  };
58
58
 
59
- let CheckoutEvents;
60
- (function (CheckoutEvents) {
59
+ let CheckoutEvents = /*#__PURE__*/function (CheckoutEvents) {
61
60
  CheckoutEvents["SessionNotFound"] = "SessionNotFound";
62
61
  CheckoutEvents["SessionLoaded"] = "SessionLoaded";
63
62
  CheckoutEvents["SessionUpdated"] = "SessionUpdated";
@@ -69,13 +68,16 @@ let CheckoutEvents;
69
68
  CheckoutEvents["SessionLockFailed"] = "SessionLockFailed";
70
69
  CheckoutEvents["ActivePaymentProductType"] = "ActivePaymentProductType";
71
70
  CheckoutEvents["ValidateSession"] = "ValidateSession";
72
- })(CheckoutEvents || (CheckoutEvents = {}));
73
- let InternalCheckoutEvents;
74
- (function (InternalCheckoutEvents) {
71
+ return CheckoutEvents;
72
+ }({});
73
+ let InternalCheckoutEvents = /*#__PURE__*/function (InternalCheckoutEvents) {
75
74
  InternalCheckoutEvents["HeightChanged"] = "HeightChanged";
76
75
  InternalCheckoutEvents["LanguageChanged"] = "LanguageChanged";
77
76
  InternalCheckoutEvents["ScrollToTop"] = "ScrollToTop";
78
- })(InternalCheckoutEvents || (InternalCheckoutEvents = {}));
77
+ InternalCheckoutEvents["ShowPopOutButton"] = "ShowPopOutButton";
78
+ InternalCheckoutEvents["HidePopOutButton"] = "HidePopOutButton";
79
+ return InternalCheckoutEvents;
80
+ }({});
79
81
 
80
82
  /**
81
83
  * Wraps window.location.assign()
@@ -95,19 +97,55 @@ const getSessionUrl = options => {
95
97
  endpoint,
96
98
  language,
97
99
  ui,
98
- shouldCallValidateSession
100
+ shouldCallValidateSession,
101
+ popOut
99
102
  } = options;
100
103
  if (!endpoint) {
101
104
  throw new Error("Invalid endpoint");
102
105
  }
103
-
104
- // Compose url for view session endpoint with optional language parameter.
105
- let languageParam = language ? `language=${language}` : "";
106
- let uiParam = ui ? `ui=${ui}` : "";
107
- let sdk = `sdk=${pkg.version}`;
108
- let validate = shouldCallValidateSession ? `client_side_validation=true` : undefined;
109
- const params = [languageParam, uiParam, sdk, validate].filter(x => x).join("&");
110
- return `${endpoint}/v1/view/${sid}${params ? "?" + params : ""}`;
106
+ const params = new URLSearchParams();
107
+ params.append('sdk', pkg.version);
108
+ if (ui) {
109
+ params.append('ui', ui);
110
+ }
111
+ if (language) {
112
+ params.append('language', language);
113
+ }
114
+ if (shouldCallValidateSession) {
115
+ params.append('client_side_validation', 'true');
116
+ }
117
+ if (popOut) {
118
+ params.append('role', 'pop_out_launcher');
119
+ }
120
+ if (endpoint === "https://checkout.dintero.com") {
121
+ // Default endpoint will redirect via the view endpoint
122
+ return `${endpoint}/v1/view/${sid}?${params.toString()}`;
123
+ }
124
+ // When a custom endpoint is set skip the view redirect endpoint since
125
+ // custom endpoints like localhost and PR builds does not support the
126
+ // serverside view flow.
127
+ params.append('sid', sid);
128
+ return `${endpoint}/?${params.toString()}`;
129
+ };
130
+ const padTralingSlash = endpoint => endpoint.endsWith('/') ? endpoint : `${endpoint}/`;
131
+ const getPopOutUrl = ({
132
+ sid,
133
+ endpoint,
134
+ language,
135
+ shouldCallValidateSession
136
+ }) => {
137
+ if (shouldCallValidateSession) {
138
+ return `${padTralingSlash(endpoint)}?loader=true`;
139
+ }
140
+ const params = new URLSearchParams();
141
+ params.append('ui', 'fullscreen');
142
+ params.append('role', 'pop_out_payment');
143
+ params.append('sid', sid);
144
+ params.append('sdk', pkg.version);
145
+ if (language) {
146
+ params.append('language', language);
147
+ }
148
+ return `${endpoint}/?${params.toString()}`;
111
149
  };
112
150
 
113
151
  /**
@@ -172,9 +210,9 @@ const createIframeAsync = (container, endpoint, url) => {
172
210
  /**
173
211
  * Post a message acknowledgement to the checkout iframe.
174
212
  */
175
- const postAck = (iframe, event) => {
176
- if (event.data.mid && iframe.contentWindow) {
177
- iframe.contentWindow.postMessage({
213
+ const postAck = (source, event) => {
214
+ if (event.data.mid && source) {
215
+ source.postMessage({
178
216
  ack: event.data.mid
179
217
  }, event.origin || "*");
180
218
  }
@@ -218,7 +256,7 @@ const postSessionRefresh = (iframe, sid) => {
218
256
  };
219
257
 
220
258
  /**
221
- * Post setActivePaymentProductType-event to the checkout iframe.
259
+ * Post SetActivePaymentProductType-event to the checkout iframe.
222
260
  */
223
261
  const postActivePaymentProductType = (iframe, sid, paymentProductType) => {
224
262
  if (iframe.contentWindow) {
@@ -230,6 +268,67 @@ const postActivePaymentProductType = (iframe, sid, paymentProductType) => {
230
268
  }
231
269
  };
232
270
 
271
+ /**
272
+ * Post ClosePopOut-event to the checkout iframe.
273
+ */
274
+ const postValidatePopOutEvent = (iframe, sid) => {
275
+ if (iframe.contentWindow) {
276
+ iframe.contentWindow.postMessage({
277
+ type: "ValidatingPopOut",
278
+ sid
279
+ }, "*");
280
+ }
281
+ };
282
+
283
+ /**
284
+ * Post OpenPopOutFailed-event to the checkout iframe.
285
+ */
286
+ const postOpenPopOutFailedEvent = (iframe, sid) => {
287
+ if (iframe.contentWindow) {
288
+ iframe.contentWindow.postMessage({
289
+ type: "OpenPopOutFailed",
290
+ sid
291
+ }, "*");
292
+ }
293
+ };
294
+
295
+ /**
296
+ * Post OpenedPopOut-event to the checkout iframe.
297
+ */
298
+ const postOpenPopOutEvent = (iframe, sid) => {
299
+ if (iframe.contentWindow) {
300
+ iframe.contentWindow.postMessage({
301
+ type: "OpenedPopOut",
302
+ sid
303
+ }, "*");
304
+ }
305
+ };
306
+
307
+ /**
308
+ * Post ClosePopOut-event to the checkout iframe.
309
+ */
310
+ const postClosePopOutEvent = (iframe, sid) => {
311
+ if (iframe.contentWindow) {
312
+ iframe.contentWindow.postMessage({
313
+ type: "ClosedPopOut",
314
+ sid
315
+ }, "*");
316
+ }
317
+ };
318
+
319
+ /**
320
+ * Post SetLanguage-event to the checkout iframe.
321
+ */
322
+ const postSetLanguage = (iframe, sid, language) => {
323
+ if (iframe.contentWindow) {
324
+ iframe.contentWindow.postMessage({
325
+ type: "SetLanguage",
326
+ sid,
327
+ language
328
+ }, "*");
329
+ }
330
+ };
331
+
233
332
  /**
234
333
  * Subscribe to events from an iframe given a handler and a set
235
334
  * of event types.
@@ -245,13 +344,14 @@ const subscribe = options => {
245
344
 
246
345
  // Wrap event handler in a function that checks for correct origin and
247
346
  // filters on event type(s) in the event data.
347
+ const endpointUrl = new URL(endpoint);
248
348
  const wrappedHandler = event => {
249
- const correctOrigin = event.origin === endpoint;
349
+ const correctOrigin = event.origin === endpointUrl.origin;
250
350
  const correctWindow = event.source === checkout.iframe.contentWindow;
251
351
  const correctSid = event.data && event.data.sid === sid;
252
352
  const correctMessageType = eventTypes.indexOf(event.data && event.data.type) !== -1;
253
353
  if (correctOrigin && correctWindow && correctSid && correctMessageType) {
254
- postAck(checkout.iframe, event);
354
+ postAck(checkout.iframe.contentWindow, event);
255
355
  handler(event.data, checkout);
256
356
  }
257
357
  };
@@ -270,10 +370,490 @@ const subscribe = options => {
270
370
  };
271
371
  };
272
372
 
373
+ const getBackdropZIndex = () => {
374
+ // Iterate all DOM items to get current highest element.
375
+ const elements = document.getElementsByTagName('*');
376
+ const highest = Array.from(elements).reduce((acc, element) => {
377
+ try {
378
+ const zIndexStr = document.defaultView.getComputedStyle(element, null).getPropertyValue("z-index");
379
+ const zIndex = parseInt(zIndexStr || '0');
380
+ if (!isNaN(zIndex) && zIndex > acc) {
381
+ return zIndex;
382
+ }
383
+ } catch (e) {
384
+ // Ignore errors when getting z-index
385
+ console.error(e);
386
+ }
387
+ return acc;
388
+ }, 0);
389
+ if (highest < 9999) {
390
+ return '9999';
391
+ }
392
+ return (highest + 1).toString();
393
+ };
394
+ const STYLE_ID = 'dintero-checkout-sdk-style';
395
+ const BACKDROP_ID = 'dintero-checkout-sdk-backdrop';
396
+ const BACKDROP_DESCRIPTION = 'dintero-checkout-sdk-backdrop-description';
397
+ const FOCUS_CHECKOUT_BUTTON_ID = 'dintero-checkout-sdk-backdrop-focus';
398
+ const CLOSE_BACKDROP_BUTTON_ID = 'dintero-checkout-sdk-backdrop-close';
399
+ const wrapPreventDefault = fn => {
400
+ // Creates a wrapped function that will invoke preventDefault() to stop
401
+ // the event from bubbling up the DOM tree.
402
+ return e => {
403
+ e.preventDefault();
404
+ e.stopPropagation();
405
+ fn();
406
+ return false;
407
+ };
408
+ };
409
+ const appendBackdropStyles = () => {
410
+ // Check if exists before appending to DOM
411
+ if (document.getElementById(STYLE_ID)) {
412
+ return;
413
+ }
414
+ // Add style to DOM
415
+ const style = document.createElement('style');
416
+ style.setAttribute('id', STYLE_ID);
417
+ style.innerHTML = `
418
+ @keyframes ${BACKDROP_ID}-fade-in {
419
+ from {opacity: 0;}
420
+ to {opacity: 1;}
421
+ }
422
+
423
+ #${BACKDROP_ID} {
424
+ position: fixed;
425
+ top: 0;
426
+ bottom: 0;
427
+ left: 0;
428
+ right: 0;
429
+ height: 100vh;
430
+ width: 100vw;
431
+ background-color: rgba(0,0,0,0.9);
432
+ background: radial-gradient(rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.8) 100%);
433
+ cursor: pointer;
434
+ animation: 20ms ease-out ${BACKDROP_ID}-fade-in;
435
+ display: flex;
436
+ flex-direction: column;
437
+ justify-content: center;
438
+ align-items: center;
439
+ gap: 20px;
440
+ color: #ffffff;
441
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
442
+ font-size: 18px;
443
+ font-weight: 400;
444
+ line-height: normal;
445
+ text-rendering: geometricPrecision;
446
+ margin: 0;
447
+ padding: 0;
448
+ border: 0;
449
+ vertical-align: baseline;
450
+ line-height: normal;
451
+ }
452
+
453
+ #${BACKDROP_ID} p {
454
+ padding: 0;
455
+ margin: 0;
456
+ border: 0;
457
+ user-select: none;
458
+ }
459
+
460
+ #${FOCUS_CHECKOUT_BUTTON_ID} {
461
+ background-color: #efefef !important;
462
+ color: #000000 !important;
463
+ font-size: 16px !important;
464
+ font-weight: 600 !important;
465
+ border-radius: 200px !important;
466
+ margin: 0 !important;
467
+ line-height: normal !important;
468
+ border: none !important;
469
+ padding: 10px 20px !important;
470
+ user-select: none !important;
471
+ cursor: pointer !important;
472
+ }
473
+ #${FOCUS_CHECKOUT_BUTTON_ID}:hover,
474
+ #${FOCUS_CHECKOUT_BUTTON_ID}:focus {
475
+ outline: none !important;
476
+ background-color: #ffffff !important;
477
+ border: none !important;
478
+ color: #000000 !important;
479
+ padding: 10px 20px !important;
480
+ margin: 0 !important;
481
+ }
482
+ #${FOCUS_CHECKOUT_BUTTON_ID}:focus{
483
+ outline-offset: 2px;
484
+ outline: 1px #ffffff solid !important;
485
+ }
486
+
487
+ #${CLOSE_BACKDROP_BUTTON_ID} {
488
+ background: transparent !important;
489
+ padding: 0 !important;
490
+ margin: 0 !important;
491
+ border: none !important;
492
+ border-radius: 4px !important;
493
+ height: 24px !important;
494
+ width: 24px !important;
495
+ color: #efefef !important;
496
+ position: absolute !important;
497
+ top: 16px !important;
498
+ right: 24px !important;
499
+ transition: all 200ms ease-out !important;
500
+ cursor: pointer !important;
501
+ }
502
+
503
+ #${CLOSE_BACKDROP_BUTTON_ID}:hover,
504
+ #${CLOSE_BACKDROP_BUTTON_ID}:focus {
505
+ outline: none !important;
506
+ color: #ffffff !important;
507
+ border: none !important;
508
+ background: transparent !important;
509
+ padding: 0 !important;
510
+ margin: 0 !important;
511
+ position: absolute;
512
+ top: 16px;
513
+ right: 24px;
514
+ }
515
+ #${CLOSE_BACKDROP_BUTTON_ID}:focus{
516
+ outline: 1px #ffffff solid !important;
517
+ }
518
+
519
+ #${BACKDROP_ID}:before,
520
+ #${BACKDROP_ID}:after,
521
+ #${BACKDROP_ID} > *:before,
522
+ #${BACKDROP_ID} > *:after {
523
+ content: '';
524
+ content: none;
525
+ }
526
+ `;
527
+ document.head.appendChild(style);
528
+ };
529
+ const createBackdropDOM = () => {
530
+ // Dark translucent backdrop element
531
+ const backdrop = document.createElement('div');
532
+ backdrop.setAttribute("id", BACKDROP_ID);
533
+ backdrop.setAttribute("role", "dialog");
534
+ backdrop.style.zIndex = getBackdropZIndex();
535
+ return backdrop;
536
+ };
537
+ const createCloseButtonDOM = label => {
538
+ // Close button for the top right corner
539
+ const button = document.createElement('button');
540
+ button.setAttribute("id", CLOSE_BACKDROP_BUTTON_ID);
541
+ button.setAttribute("type", "button");
542
+ button.setAttribute("aria-label", label);
543
+ button.innerHTML = `
544
+ <svg
545
+ xmlns="http://www.w3.org/2000/svg"
546
+ width="24"
547
+ height="24"
548
+ viewBox="0 0 24 24"
549
+ fill="none"
550
+ stroke="currentColor"
551
+ stroke-width="2"
552
+ stroke-linecap="round"
553
+ stroke-linejoin="round"
554
+ alt="close icon"
555
+ >
556
+ <line x1="18" y1="6" x2="6" y2="18"></line>
557
+ <line x1="6" y1="6" x2="18" y2="18"></line>
558
+ </svg>`;
559
+ return button;
560
+ };
561
+ const createDinteroLogoDOM = () => {
562
+ // Close button for the top right corner
563
+ const div = document.createElement('div');
564
+ div.innerHTML = `
565
+ <svg width="120px" height="22px" viewBox="0 0 630 111" version="1.1" >
566
+ <g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
567
+ <g id="Dintero" fill="#ffffff" fillRule="nonzero">
568
+ <path d="M376.23,60.48 L376.23,73.54 L454.13,73.54 C456.31,41.55 435.85,23.71 410.61,23.71 C385.37,23.71 367.09,41.77 367.09,66.79 C367.09,92.03 386.02,110.31 411.91,110.31 C433.02,110.31 448.9,97.25 453.25,82.24 L436.5,82.24 C432.37,89.42 423.88,95.51 411.91,95.51 C395.16,95.51 382.75,83.11 382.75,66.79 C382.75,50.69 394.72,38.5 410.6,38.5 C426.48,38.5 438.45,50.68 438.45,66.79 L444.54,60.48 L376.23,60.48 Z M154.29,17.83 L171.7,17.83 L171.7,0.42 L154.29,0.42 L154.29,17.83 Z M120.34,108.13 L191.27,108.13 L191.27,93.77 L120.34,93.77 L120.34,108.13 Z M156.46,40.24 L156.46,108.13 L171.69,108.13 L171.69,45.47 C171.69,32.85 165.82,25.89 151.89,25.89 L120.34,25.89 L120.34,40.25 L156.46,40.25 L156.46,40.24 Z M499.17,25.88 L464.36,25.88 L464.36,40.24 L483.94,40.24 L484.16,108.13 L499.39,108.13 L499.17,62.44 C499.17,48.51 508.53,40.25 521.58,40.25 L535.29,40.25 L535.29,25.89 L524.41,25.89 C509.18,25.89 501.78,31.33 497.65,41.56 L495.47,47 L499.17,47.65 L499.17,25.88 Z M288.76,25.88 L310.52,25.88 L310.52,6.3 L325.75,6.3 L325.75,25.88 L359.69,25.88 L359.69,40.24 L325.75,40.24 L325.75,93.77 L359.69,93.77 L359.69,108.13 L332.49,108.13 C318.56,108.13 310.51,98.99 310.51,86.37 L310.51,40.24 L288.75,40.24 L288.75,25.88 L288.76,25.88 Z M464.35,108.13 L535.28,108.13 L535.28,93.77 L464.35,93.77 L464.35,108.13 Z M108.6,54.17 C108.6,23.06 85.54,0.43 53.77,0.43 L0.9,0.43 L0.9,108.14 L53.77,108.14 C85.53,108.13 108.6,85.5 108.6,54.17 M248.07,23.71 C234.58,23.71 223.92,31.98 220,41.55 L220,25.88 L204.77,25.88 L204.77,108.13 L220,108.13 L220,66.35 C220,53.08 224.79,38.93 243.72,38.93 C259.39,38.93 267.44,48.07 267.44,67.43 L267.44,108.12 L282.67,108.12 L282.67,64.6 C282.67,35.02 265.91,23.71 248.07,23.71 M586.2,110.31 C611.22,110.31 629.72,92.03 629.72,67.01 C629.72,41.99 611.23,23.71 586.2,23.71 C560.96,23.71 542.68,41.99 542.68,67.01 C542.68,92.03 560.96,110.31 586.2,110.31 M586.2,95.51 C570.32,95.51 558.35,83.33 558.35,67.01 C558.35,50.69 570.32,38.51 586.2,38.51 C602.08,38.51 614.05,50.69 614.05,67.01 C614.05,83.33 602.08,95.51 586.2,95.51 M16.99,92.9 L16.99,15.66 L51.8,15.66 C75.3,15.66 92.05,31.98 92.05,54.61 C92.05,76.8 75.3,92.91 51.8,92.91 L16.99,92.91 L16.99,92.9 Z" id="Shape"></path>
569
+ </g>
570
+ </g>
571
+ </svg>`;
572
+ return div;
573
+ };
574
+ const createLabelDOM = text => {
575
+ // Text about the pop out
576
+ const p = document.createElement('p');
577
+ p.setAttribute('id', BACKDROP_DESCRIPTION);
578
+ p.innerText = text;
579
+ return p;
580
+ };
581
+ const createFocusButtonDOM = text => {
582
+ // Mock button to give the user a call to action element to click, even
583
+ // though the entire backdrop (except the close button) returns focus to the
584
+ // checkout.
585
+ const button = document.createElement('button');
586
+ button.setAttribute("id", FOCUS_CHECKOUT_BUTTON_ID);
587
+ button.setAttribute("type", "button");
588
+ button.innerText = text;
589
+ return button;
590
+ };
591
+ const focusTrap = e => {
592
+ // Prevent the user focusing outside of the backdrop while it is visible
593
+ const focusButton = document.getElementById(FOCUS_CHECKOUT_BUTTON_ID);
594
+ const closeButton = document.getElementById(CLOSE_BACKDROP_BUTTON_ID);
595
+ if (e.key === 'Tab' || e.code === "Tab") {
596
+ if (document.activeElement === focusButton) {
597
+ closeButton.focus();
598
+ e.preventDefault();
599
+ } else {
600
+ // Tab
601
+ focusButton.focus();
602
+ e.preventDefault();
603
+ }
604
+ }
605
+ };
606
+ const createBackdropView = options => {
607
+ // Add styles needed for the backdrop;
608
+ appendBackdropStyles();
609
+ // Create DOM nodes
610
+ const backdrop = createBackdropDOM();
611
+ const closeButton = createCloseButtonDOM(options.event.closeLabel);
612
+ const dinteroLogo = createDinteroLogoDOM();
613
+ const label = createLabelDOM(options.event.descriptionLabel);
614
+ const focusButton = createFocusButtonDOM(options.event.focusLabel);
615
+
616
+ // Add click handlers
617
+ backdrop.onclick = wrapPreventDefault(options.focus);
618
+ focusButton.onclick = wrapPreventDefault(options.focus);
619
+ closeButton.onclick = wrapPreventDefault(options.close);
620
+
621
+ // Add focus trap when backdrop is visible
622
+ document.addEventListener('keydown', focusTrap);
623
+
624
+ // Append to document
625
+ backdrop.appendChild(closeButton);
626
+ backdrop.appendChild(dinteroLogo);
627
+ backdrop.appendChild(label);
628
+ backdrop.appendChild(focusButton);
629
+ document.body.appendChild(backdrop);
630
+ backdrop.focus();
631
+ return backdrop;
632
+ };
633
+ const setBackdropLabels = event => {
634
+ const focusButton = document.getElementById(FOCUS_CHECKOUT_BUTTON_ID);
635
+ if (focusButton) {
636
+ focusButton.innerText = event.focusLabel;
637
+ }
638
+ const description = document.getElementById(BACKDROP_DESCRIPTION);
639
+ if (description) {
640
+ description.innerText = event.descriptionLabel;
641
+ }
642
+ const closeButton = document.getElementById(CLOSE_BACKDROP_BUTTON_ID);
643
+ if (closeButton) {
644
+ closeButton.setAttribute('aria-label', event.descriptionLabel);
645
+ }
646
+ };
647
+ const createBackdrop = options => {
648
+ try {
649
+ // Check if backdrop already exists
650
+ const backdrop = document.getElementById(BACKDROP_ID);
651
+ if (backdrop) {
652
+ return;
653
+ }
654
+ return createBackdropView(options);
655
+ } catch (e) {
656
+ // Ignore errors when creating backdrop. If it fails we should not
657
+ // block the payment.
658
+ console.error(e);
659
+ }
660
+ };
661
+ const removeBackdrop = () => {
662
+ try {
663
+ const backdrop = document.getElementById(BACKDROP_ID);
664
+ if (backdrop) {
665
+ document.body.removeChild(backdrop);
666
+ }
667
+ document.removeEventListener('keydown', focusTrap);
668
+ } catch (e) {
669
+ // Ignore errors when closing backdrop. If it fails we should not stop
670
+ // the rest of the application from working.
671
+ console.error(e);
672
+ }
673
+ };
674
+
675
+ const OPEN_POP_OUT_BUTTON_ID = "dintero-checkout-sdk-launch-pop-out";
676
+ const configureButton = (button, {
677
+ label,
678
+ disabled,
679
+ top,
680
+ left,
681
+ right,
682
+ styles,
683
+ onClick
684
+ }) => {
685
+ button.setAttribute('id', OPEN_POP_OUT_BUTTON_ID);
686
+ button.setAttribute('type', 'button');
687
+
688
+ // Is clickable
689
+ if (disabled === 'true') {
690
+ button.setAttribute('disabled', disabled);
691
+ } else {
692
+ button.removeAttribute('disabled');
693
+ }
694
+
695
+ // Click handler
696
+ button.onclick = event => {
697
+ // Do not submit any form on the page using the SDK
698
+ event.preventDefault();
699
+ event.stopPropagation();
700
+
701
+ // Update look
702
+ button.style.boxShadow = 'inset 0 0 10px rgba(34, 84, 65, 0.9)';
703
+
704
+ // Invoke handler
705
+ onClick();
706
+
707
+ // Reset look
708
+ window.setTimeout(() => {
709
+ button.style.boxShadow = 'none';
710
+ }, 200);
711
+ };
712
+
713
+ // Label
714
+ button.innerText = label;
715
+
716
+ // Position
717
+ button.style.position = 'absolute';
718
+ button.style.top = top + 'px';
719
+ button.style.left = left + 'px';
720
+ button.style.right = right + 'px';
721
+
722
+ // Appearance from checkout
723
+ for (const [key, value] of Object.entries(styles)) {
724
+ button.style[key] = value;
725
+ }
726
+ };
727
+ const addPopOutButton = options => {
728
+ // Will add or update existing button
729
+ const {
730
+ container
731
+ } = options;
732
+ const exists = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
733
+ const button = exists || document.createElement('button');
734
+ configureButton(button, options);
735
+ if (!exists) {
736
+ container.appendChild(button);
737
+ }
738
+ };
739
+ const setPopOutButtonDisabled = disabled => {
740
+ try {
741
+ const button = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
742
+ if (button) {
743
+ if (disabled) {
744
+ button.setAttribute('disabled', disabled.toString());
745
+ } else {
746
+ button.removeAttribute('disabled');
747
+ }
748
+ }
749
+ } catch (e) {
750
+ // Ignore error and continue
751
+ console.error(e);
752
+ }
753
+ };
754
+ const removePopOutButton = () => {
755
+ try {
756
+ const button = document.getElementById(OPEN_POP_OUT_BUTTON_ID);
757
+ if (button) {
758
+ button.remove();
759
+ }
760
+ } catch (e) {
761
+ // Ignore error and continue
762
+ console.error(e);
763
+ }
764
+ };
765
+
766
+ const WIDTH = Math.min(480, window.screen.width);
767
+ const HEIGHT = Math.min(840, window.screen.height);
768
+ let popOutWindow;
769
+ const createPopOutWindow = (url, width, height) => {
770
+ return new Promise(resolve => {
771
+ try {
772
+ // Opens a centered pop up window
773
+ const left = window.screenX + (window.outerWidth - width) / 2;
774
+ const top = window.screenY + (window.outerHeight - height) / 2;
775
+ const features = `width=${width},height=${height},left=${left},top=${top},location=no,menubar=no,toolbar=no,status=no`;
776
+ const popOut = window.open(url, 'dintero-checkout', features);
777
+ if (!popOut) {
778
+ console.log('createPopOutWindow no popOut');
779
+ resolve(undefined);
780
+ return;
781
+ }
782
+ const timeout = window.setTimeout(() => {
783
+ console.log('createPopOutWindow timeout');
784
+ resolve(undefined);
785
+ }, 3000);
786
+ popOut.addEventListener('load', event => {
787
+ console.log('createPopOutWindow loaded', {
788
+ popOut,
789
+ event
790
+ });
791
+ clearTimeout(timeout);
792
+ resolve(popOut);
793
+ });
794
+ } catch (err) {
795
+ resolve(undefined);
796
+ }
797
+ });
798
+ };
799
+ const openPopOut = async options => {
800
+ let unsubscribe;
801
+ let intervalId = -1;
802
+ if (popOutWindow && !popOutWindow.closed) {
803
+ // Skip if already open.
804
+ return;
805
+ }
806
+
807
+ // Open popup window
808
+ const url = getPopOutUrl(options);
809
+ popOutWindow = await createPopOutWindow(url, WIDTH, HEIGHT);
810
+ const focusPopOut = () => {
811
+ if (popOutWindow) {
812
+ popOutWindow.focus();
813
+ }
814
+ };
815
+ const cleanUpClosed = () => {
816
+ window.clearInterval(intervalId);
817
+ intervalId = -1;
818
+ window.removeEventListener('beforeunload', closePopOut);
819
+ popOutWindow = undefined;
820
+ options.onClose();
821
+ if (unsubscribe) {
822
+ unsubscribe();
823
+ }
824
+ };
825
+ const closePopOut = () => {
826
+ if (popOutWindow) {
827
+ popOutWindow.close();
828
+ }
829
+ cleanUpClosed();
830
+ };
831
+ const checkIfPopupClosed = () => {
832
+ if (popOutWindow && popOutWindow.closed) {
833
+ cleanUpClosed();
834
+ }
835
+ };
836
+
837
+ // Close pop out if current window is closed
838
+ window.addEventListener('beforeunload', closePopOut);
839
+
840
+ // Check if checkout is still open
841
+ intervalId = window.setInterval(checkIfPopupClosed, 200);
842
+
843
+ // Set up pub/sub of messages from pop out to SDK
844
+ unsubscribe = options.onOpen(popOutWindow);
845
+ return {
846
+ close: closePopOut,
847
+ focus: focusPopOut,
848
+ popOutWindow
849
+ };
850
+ };
851
+
273
852
  /**
274
853
  * An event handler that navigates to the href in the event.
275
854
  */
276
- const followHref = event => {
855
+ const followHref = (event, checkout) => {
856
+ cleanUpPopOut(checkout);
277
857
  if (event.href) {
278
858
  windowLocationAssign(event.href);
279
859
  }
@@ -299,7 +879,7 @@ const scrollToIframeTop = (event, checkout) => {
299
879
  behavior: 'smooth'
300
880
  });
301
881
  } catch (e) {
302
- // Ignore erorr silenty bug log it to the console.
882
+ // Ignore error silently bug log it to the console.
303
883
  console.error(e);
304
884
  }
305
885
  };
@@ -312,30 +892,247 @@ const setLanguage = (event, checkout) => {
312
892
  checkout.language = event.language;
313
893
  }
314
894
  };
315
- const handleWithResult = (sid, endpoint, handler) => {
316
- return (event, checkout) => {
317
- const eventKeys = ["sid", "merchant_reference", "transaction_id", "error"];
318
- const pairs = eventKeys.map(key => [key, event[key]]);
319
- if (event.type === CheckoutEvents.SessionCancel && !event.error) {
320
- pairs.push(["error", "cancelled"]);
895
+
896
+ /**
897
+ * Wrap function with try catch so an error in a single function won't short circuit other code in the current context.
898
+ */
899
+ const safelyInvoke = fn => {
900
+ try {
901
+ fn();
902
+ } catch (e) {
903
+ console.error(e);
904
+ }
905
+ };
906
+
907
+ /**
908
+ * Handle messages sendt to the SDK from the pop out.
909
+ */
910
+ const createPopOutMessageHandler = (source, checkout) => {
911
+ // Change language in embed if changed in pop out
912
+ const popOutChangedLanguageHandler = {
913
+ internalPopOutHandler: true,
914
+ eventTypes: [InternalCheckoutEvents.LanguageChanged],
915
+ handler: (eventData, checkout) => {
916
+ // Tell the embedded checkout to change language.
917
+ postSetLanguage(checkout.iframe, checkout.options.sid, eventData.language);
918
+ }
919
+ };
920
+
921
+ // Close pop out, and remove SDK rendered button when payment is completed.
922
+ const paymentCompletedEvents = [CheckoutEvents.SessionCancel, CheckoutEvents.SessionPaymentOnHold, CheckoutEvents.SessionPaymentAuthorized, CheckoutEvents.SessionPaymentError];
923
+ const popOutCompletedHandler = {
924
+ internalPopOutHandler: true,
925
+ eventTypes: paymentCompletedEvents,
926
+ handler: (eventData, checkout) => {
927
+ if (eventData.href) {
928
+ // Remove open pop out button rendered by SDK
929
+ removePopOutButton();
930
+
931
+ // Close checkout
932
+ try {
933
+ source.close();
934
+ } catch (e) {
935
+ console.error(e);
936
+ }
937
+ } else {
938
+ console.error('Payment Complete event missing href property');
939
+ }
940
+ }
941
+ };
942
+
943
+ // Listens to messages from pop out window and routes the events to dedicated handlers
944
+ const messageRouter = event => {
945
+ // Check that we should handle the message
946
+ if (event.source === source && event.data.context === 'popOut' && event.data.sid === checkout.options.sid) {
947
+ // Check if handler matches incoming event and trigger the handler if so.
948
+ [
949
+ // SDK events for managing the pop out flow.
950
+ popOutChangedLanguageHandler, popOutCompletedHandler,
951
+ // Events configured when the checkout was embedded.
952
+ ...checkout.handlers].forEach(handlerObject => {
953
+ if (handlerObject.eventTypes.includes(event.data.type) && handlerObject.handler) {
954
+ // Invoking the handler function if the event type matches the handler.
955
+ safelyInvoke(() => {
956
+ handlerObject.handler(event.data, checkout);
957
+ });
958
+ }
959
+ });
960
+ }
961
+ };
962
+ // Add messageRouter event listener to the Pop Out
963
+ window.addEventListener('message', messageRouter);
964
+
965
+ // Return unsubscribe function
966
+ return () => {
967
+ window.removeEventListener('message', messageRouter);
968
+ };
969
+ };
970
+
971
+ /**
972
+ * Configures and shows the pop out with the payment options.
973
+ */
974
+ const showPopOut = async (event, checkout) => {
975
+ const {
976
+ close,
977
+ focus,
978
+ popOutWindow
979
+ } = await openPopOut({
980
+ sid: checkout.options.sid,
981
+ endpoint: checkout.options.endpoint,
982
+ shouldCallValidateSession: Boolean(checkout.options.onValidateSession),
983
+ language: event.language,
984
+ onOpen: popOutWindow => createPopOutMessageHandler(popOutWindow, checkout),
985
+ onClose: () => {
986
+ removeBackdrop();
987
+ postClosePopOutEvent(checkout.iframe, checkout.options.sid);
988
+ setPopOutButtonDisabled(false);
989
+ checkout.popOutWindow = undefined;
990
+ }
991
+ });
992
+ if (popOutWindow) {
993
+ postOpenPopOutEvent(checkout.iframe, checkout.options.sid);
994
+ // Add pop out window to checkout instance
995
+ checkout.popOutWindow = popOutWindow;
996
+ createBackdrop({
997
+ focus,
998
+ close,
999
+ event
1000
+ });
1001
+ return true;
1002
+ } else {
1003
+ postOpenPopOutFailedEvent(checkout.iframe, checkout.options.sid);
1004
+ return false;
1005
+ }
1006
+ };
1007
+
1008
+ /**
1009
+ * Create callback function for the client side validation flow. It allows the
1010
+ * host application to validate the content of the payment session before the
1011
+ * pop out is opened.
1012
+ */
1013
+ const createPopOutValidationCallback = (event, checkout) => {
1014
+ return result => {
1015
+ // Tell the embedded iframe about the validation result so it can show an error message if
1016
+ // the validation failed.
1017
+ postValidationResult(checkout.iframe, checkout.options.sid, result);
1018
+ if (result.success && checkout.popOutWindow) {
1019
+ // Redirect user to session in pop out window
1020
+ checkout.popOutWindow.location.href = getPopOutUrl({
1021
+ sid: checkout.options.sid,
1022
+ endpoint: checkout.options.endpoint,
1023
+ shouldCallValidateSession: false,
1024
+ language: event.language
1025
+ });
1026
+ } else {
1027
+ // Close pop out
1028
+ if (checkout.popOutWindow) {
1029
+ checkout.popOutWindow.close();
1030
+ }
1031
+ // Log validation error to console log.
1032
+ console.error(result.clientValidationError);
321
1033
  }
322
- pairs.push(["language", checkout.language]);
323
- pairs.push(["sdk", pkg.version]);
324
- const urlQuery = pairs.filter(([key, value]) => value).map(([key, value]) => `${key}=${value}`).join("&");
325
- checkout.iframe.setAttribute("src", `${endpoint}/embedResult/?${urlQuery}`);
326
- handler(event, checkout);
327
1034
  };
328
1035
  };
329
1036
 
1037
+ /**
1038
+ * Handle click event on the SDK rendered pop out button
1039
+ */
1040
+ const handlePopOutButtonClick = async (event, checkout) => {
1041
+ // Disable button while pop out is open
1042
+ const opened = await showPopOut(event, checkout);
1043
+ if (opened && checkout.options.onValidateSession) {
1044
+ // Let the host application validate the payment session before opening checkout.
1045
+
1046
+ // Tell the embedded iframe that we are validating the session
1047
+ postValidatePopOutEvent(checkout.iframe, checkout.options.sid);
1048
+
1049
+ // Create callback function added to the SDK event and onValidateSession attributes
1050
+ const callback = createPopOutValidationCallback(event, checkout);
1051
+
1052
+ // Invoke onValidateSession function defined in checkout options
1053
+ try {
1054
+ checkout.options.onValidateSession({
1055
+ type: CheckoutEvents.ValidateSession,
1056
+ session: checkout.session,
1057
+ callback
1058
+ }, checkout, callback);
1059
+ } catch (e) {
1060
+ console.error(e);
1061
+ postValidationResult(checkout.iframe, checkout.options.sid, {
1062
+ success: false,
1063
+ clientValidationError: 'Validation runtime error'
1064
+ });
1065
+ }
1066
+ }
1067
+ };
1068
+
1069
+ /**
1070
+ * Type guard for ShowPopOutButton
1071
+ */
1072
+ const isShowPopOutButton = event => {
1073
+ return event && event.type === InternalCheckoutEvents.ShowPopOutButton;
1074
+ };
1075
+
1076
+ /**
1077
+ * Display the SDK rendered pop out button on top of the embedded iframe
1078
+ */
1079
+ const handleShowButton = (event, checkout) => {
1080
+ if (isShowPopOutButton(event)) {
1081
+ addPopOutButton({
1082
+ container: checkout.options.innerContainer,
1083
+ label: event.openLabel,
1084
+ top: event.top,
1085
+ left: event.left,
1086
+ right: event.right,
1087
+ styles: event.styles,
1088
+ disabled: event.disabled,
1089
+ onClick: () => handlePopOutButtonClick(event, checkout)
1090
+ });
1091
+ setBackdropLabels(event);
1092
+ }
1093
+ };
1094
+
1095
+ /**
1096
+ * Remove the pop out button above the embedded iframe
1097
+ */
1098
+ const handleRemoveButton = (event, checkout) => {
1099
+ if (event.type === InternalCheckoutEvents.HidePopOutButton) {
1100
+ removePopOutButton();
1101
+ }
1102
+ };
1103
+ const cleanUpPopOut = checkout => {
1104
+ // Ensures that the pop out is no longer open when the payment is completed or the checkout is destroyed.
1105
+ removePopOutButton();
1106
+ removeBackdrop();
1107
+ if (checkout.popOutWindow) {
1108
+ try {
1109
+ checkout.popOutWindow.close();
1110
+ // Pop out message handlers will be removed when the pop out window is closed
1111
+ // via the interval created by openPopOut.
1112
+ } catch (e) {
1113
+ console.error(e);
1114
+ }
1115
+ }
1116
+ };
1117
+
330
1118
  /**
331
1119
  * Show a dintero payment session in an embedded iframe.
332
1120
  */
333
1121
  const embed = async options => {
1122
+ // Create inner container to offset any styling on the container.
1123
+ const innerContainer = document.createElement('div');
1124
+ innerContainer.style.position = 'relative';
1125
+ innerContainer.style['box-sizing'] = 'border-box';
1126
+ const internalOptions = {
1127
+ endpoint: "https://checkout.dintero.com",
1128
+ innerContainer: innerContainer,
1129
+ ...options
1130
+ };
334
1131
  const {
335
1132
  container,
336
1133
  sid,
337
1134
  language,
338
- endpoint = "https://checkout.dintero.com",
1135
+ endpoint,
339
1136
  onSession,
340
1137
  onSessionCancel,
341
1138
  onPayment,
@@ -345,33 +1142,45 @@ const embed = async options => {
345
1142
  onSessionLocked,
346
1143
  onSessionLockFailed,
347
1144
  onActivePaymentType,
348
- onValidateSession
349
- } = options;
1145
+ onValidateSession,
1146
+ popOut
1147
+ } = internalOptions;
350
1148
  let checkout;
351
1149
  const subscriptions = [];
1150
+ let has_delivered_final_event = false;
352
1151
 
353
1152
  // Create iframe
1153
+ container.appendChild(innerContainer);
354
1154
  const {
355
1155
  iframe,
356
1156
  initiate
357
- } = createIframeAsync(container, endpoint, getSessionUrl({
1157
+ } = createIframeAsync(innerContainer, endpoint, getSessionUrl({
358
1158
  sid,
359
1159
  endpoint,
360
1160
  language,
361
1161
  ui: "inline",
362
- shouldCallValidateSession: onValidateSession !== undefined
1162
+ shouldCallValidateSession: onValidateSession !== undefined,
1163
+ popOut
363
1164
  }));
364
1165
 
365
1166
  /**
366
- * Function that removes the iframe and all event listeners.
1167
+ * Function that removes the iframe, pop out and all event listeners.
367
1168
  */
368
1169
  const destroy = () => {
1170
+ cleanUpPopOut(checkout);
369
1171
  if (iframe) {
1172
+ if (internalOptions.popOut) {
1173
+ // Try to remove backdrop if it exists
1174
+ removeBackdrop();
1175
+ }
370
1176
  subscriptions.forEach(sub => sub.unsubscribe());
371
1177
  if (iframe.parentElement) {
372
- container.removeChild(iframe);
1178
+ innerContainer.removeChild(iframe);
373
1179
  }
374
1180
  }
1181
+ if (innerContainer.parentElement) {
1182
+ container.removeChild(innerContainer);
1183
+ }
375
1184
  };
376
1185
 
377
1186
  /**
@@ -392,7 +1201,8 @@ const embed = async options => {
392
1201
  resolve(sessionEvent);
393
1202
  },
394
1203
  eventTypes: [resolveEvent],
395
- checkout
1204
+ checkout,
1205
+ source: checkout.iframe.contentWindow
396
1206
  }));
397
1207
  eventSubscriptions.push(subscribe({
398
1208
  sid,
@@ -402,7 +1212,8 @@ const embed = async options => {
402
1212
  reject(`Received unexpected event: ${rejectEvent}`);
403
1213
  },
404
1214
  eventTypes: [rejectEvent],
405
- checkout
1215
+ checkout,
1216
+ source: checkout.iframe.contentWindow
406
1217
  }));
407
1218
  action();
408
1219
  });
@@ -423,9 +1234,44 @@ const embed = async options => {
423
1234
  const submitValidationResult = result => {
424
1235
  postValidationResult(iframe, sid, result);
425
1236
  };
1237
+
1238
+ /**
1239
+ * Internal result event message handler wrapper, to replace the content of the iframe with a success/or
1240
+ * error message. Only used when the embed function in the SDK has a dedicated handler for onPayment, onError etc.
1241
+ * If no custom handler is set the followHref handler is used instead.
1242
+ */
1243
+ const handleWithResult = (sid, endpoint, handler) => {
1244
+ return (event, checkout) => {
1245
+ if (!has_delivered_final_event) {
1246
+ has_delivered_final_event = true;
1247
+ cleanUpPopOut(checkout);
1248
+ const eventKeys = ["sid", "merchant_reference", "transaction_id", "error"];
1249
+ const pairs = eventKeys.map(key => [key, event[key]]);
1250
+ if (event.type === CheckoutEvents.SessionCancel && !event.error) {
1251
+ pairs.push(["error", "cancelled"]);
1252
+ }
1253
+ pairs.push(["language", checkout.language]);
1254
+ pairs.push(["sdk", pkg.version]);
1255
+ const urlQuery = pairs.filter(([key, value]) => value).map(([key, value]) => `${key}=${value}`).join("&");
1256
+ checkout.iframe.setAttribute("src", `${endpoint}/embedResult/?${urlQuery}`);
1257
+ handler(event, checkout);
1258
+ }
1259
+ };
1260
+ };
426
1261
  const wrappedOnValidateSession = (event, checkout) => {
427
1262
  if (onValidateSession) {
428
- onValidateSession(event, checkout, submitValidationResult);
1263
+ try {
1264
+ onValidateSession({
1265
+ ...event,
1266
+ callback: submitValidationResult
1267
+ }, checkout, submitValidationResult);
1268
+ } catch (e) {
1269
+ console.error(e);
1270
+ submitValidationResult({
1271
+ success: false,
1272
+ clientValidationError: "Validation runtime error"
1273
+ });
1274
+ }
429
1275
  }
430
1276
  };
431
1277
  const wrappedOnSessionLocked = (event, checkout) => {
@@ -433,20 +1279,16 @@ const embed = async options => {
433
1279
  onSessionLocked(event, checkout, refreshSession);
434
1280
  }
435
1281
  };
436
-
437
- // Create checkout object that wraps the destroy function.
438
- checkout = {
439
- destroy,
440
- iframe,
441
- language,
442
- lockSession,
443
- refreshSession,
444
- setActivePaymentProductType,
445
- submitValidationResult
1282
+ const wrappedOnLoadedOrUpdated = (event, checkout) => {
1283
+ // Update the checkout instance to include the session object
1284
+ checkout.session = event.session;
1285
+ if (onSession) {
1286
+ onSession(event, checkout);
1287
+ }
446
1288
  };
447
1289
 
448
1290
  // Add event handlers (or in some cases add a fallback href handler).
449
- [{
1291
+ const handlers = [{
450
1292
  handler: setLanguage,
451
1293
  eventTypes: [InternalCheckoutEvents.LanguageChanged]
452
1294
  }, {
@@ -456,7 +1298,7 @@ const embed = async options => {
456
1298
  handler: scrollToIframeTop,
457
1299
  eventTypes: [InternalCheckoutEvents.ScrollToTop]
458
1300
  }, {
459
- handler: onSession,
1301
+ handler: wrappedOnLoadedOrUpdated,
460
1302
  eventTypes: [CheckoutEvents.SessionLoaded, CheckoutEvents.SessionUpdated]
461
1303
  }, {
462
1304
  eventTypes: [CheckoutEvents.SessionPaymentOnHold],
@@ -485,7 +1327,28 @@ const embed = async options => {
485
1327
  }, {
486
1328
  handler: wrappedOnValidateSession,
487
1329
  eventTypes: [CheckoutEvents.ValidateSession]
488
- }].forEach(({
1330
+ }, {
1331
+ handler: handleShowButton,
1332
+ eventTypes: [InternalCheckoutEvents.ShowPopOutButton]
1333
+ }, {
1334
+ handler: handleRemoveButton,
1335
+ eventTypes: [InternalCheckoutEvents.HidePopOutButton]
1336
+ }];
1337
+ // Create checkout object that wraps the destroy function.
1338
+ checkout = {
1339
+ destroy,
1340
+ iframe,
1341
+ language,
1342
+ lockSession,
1343
+ refreshSession,
1344
+ setActivePaymentProductType,
1345
+ submitValidationResult,
1346
+ options: internalOptions,
1347
+ handlers,
1348
+ session: undefined,
1349
+ popOutWindow: undefined
1350
+ };
1351
+ handlers.forEach(({
489
1352
  handler,
490
1353
  eventTypes
491
1354
  }) => {
@@ -495,7 +1358,8 @@ const embed = async options => {
495
1358
  endpoint,
496
1359
  handler,
497
1360
  eventTypes,
498
- checkout
1361
+ checkout,
1362
+ source: checkout.iframe.contentWindow
499
1363
  }));
500
1364
  }
501
1365
  });