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