@dintero/checkout-web-sdk 0.5.10 → 0.6.1
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.
- package/README.md +4 -2
- package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/checkout.ts.html +91 -4
- package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/createIframeAsync.ts.html +15 -15
- package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/index.html +88 -43
- package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/index.ts.html +1085 -95
- package/coverage/Chrome Headless 117.0.5938.149 (Linux x86_64)/html/popOut.ts.html +377 -0
- package/coverage/Chrome Headless 117.0.5938.149 (Linux x86_64)/html/popOutBackdrop.ts.html +1046 -0
- package/coverage/Chrome Headless 117.0.5938.149 (Linux x86_64)/html/popOutButton.ts.html +374 -0
- package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/subscribe.ts.html +218 -29
- package/coverage/Chrome Headless 117.0.5938.149 (Linux x86_64)/html/url.ts.html +284 -0
- package/dist/declarations/src/checkout.d.ts +29 -1
- package/dist/declarations/src/index.d.ts +17 -1
- package/dist/declarations/src/subscribe.d.ts +62 -0
- package/dist/dintero-checkout-web-sdk.cjs.dev.js +917 -53
- package/dist/dintero-checkout-web-sdk.cjs.prod.js +917 -53
- package/dist/dintero-checkout-web-sdk.esm.js +917 -53
- package/dist/dintero-checkout-web-sdk.umd.min.js +1 -1
- package/dist/dintero-checkout-web-sdk.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/coverage/Chrome Headless 117.0.5938.92 (Linux x86_64)/html/url.ts.html +0 -185
- /package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/base.css +0 -0
- /package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/block-navigation.js +0 -0
- /package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/favicon.png +0 -0
- /package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/prettify.css +0 -0
- /package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/prettify.js +0 -0
- /package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (Linux x86_64)}/html/sort-arrow-sprite.png +0 -0
- /package/coverage/{Chrome Headless 117.0.5938.92 (Linux x86_64) → Chrome Headless 117.0.5938.149 (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
|
+
version: "0.6.1",
|
|
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",
|
|
@@ -70,6 +70,8 @@ let InternalCheckoutEvents = /*#__PURE__*/function (InternalCheckoutEvents) {
|
|
|
70
70
|
InternalCheckoutEvents["HeightChanged"] = "HeightChanged";
|
|
71
71
|
InternalCheckoutEvents["LanguageChanged"] = "LanguageChanged";
|
|
72
72
|
InternalCheckoutEvents["ScrollToTop"] = "ScrollToTop";
|
|
73
|
+
InternalCheckoutEvents["ShowPopOutButton"] = "ShowPopOutButton";
|
|
74
|
+
InternalCheckoutEvents["HidePopOutButton"] = "HidePopOutButton";
|
|
73
75
|
return InternalCheckoutEvents;
|
|
74
76
|
}({});
|
|
75
77
|
|
|
@@ -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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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 = (
|
|
172
|
-
if (event.data.mid &&
|
|
173
|
-
|
|
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
|
|
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 ===
|
|
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
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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:
|
|
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
|
-
}
|
|
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
|
});
|