@maytes/checkout-button 1.1.1 → 1.2.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/CHANGELOG.md +82 -0
- package/README.md +93 -74
- package/dist/checkout-button.299a037b.js +188 -0
- package/dist/checkout-button.299a037b.js.map +1 -0
- package/dist/{checkout-button.2aad12c3.cjs → checkout-button.332d26cc.cjs} +314 -156
- package/dist/checkout-button.332d26cc.cjs.map +1 -0
- package/dist/{checkout-button.aed57a66.mjs → checkout-button.67fdb2e1.mjs} +314 -156
- package/dist/checkout-button.67fdb2e1.mjs.map +1 -0
- package/dist/checkout-button.cjs +314 -156
- package/dist/checkout-button.cjs.map +1 -1
- package/dist/checkout-button.d.cts +40 -4
- package/dist/checkout-button.d.ts +40 -4
- package/dist/checkout-button.element.afe71cd5.js +187 -0
- package/dist/checkout-button.element.afe71cd5.js.map +1 -0
- package/dist/checkout-button.element.js +187 -0
- package/dist/checkout-button.element.js.map +1 -0
- package/dist/checkout-button.js +58 -21
- package/dist/checkout-button.js.map +1 -1
- package/dist/checkout-button.mjs +314 -156
- package/dist/checkout-button.mjs.map +1 -1
- package/dist/element.cjs +201 -0
- package/dist/element.cjs.map +1 -0
- package/dist/element.d.cts +44 -0
- package/dist/element.d.ts +44 -0
- package/dist/element.mjs +180 -0
- package/dist/element.mjs.map +1 -0
- package/dist/integrity.json +37 -26
- package/dist/react.cjs +139 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +30 -0
- package/dist/react.d.ts +30 -0
- package/dist/react.mjs +117 -0
- package/dist/react.mjs.map +1 -0
- package/docs/guides/angular.md +56 -0
- package/docs/guides/api.md +108 -0
- package/docs/guides/events.md +48 -0
- package/docs/guides/launch-behaviour.md +41 -0
- package/docs/guides/nextjs.md +49 -0
- package/docs/guides/own-button.md +41 -0
- package/docs/guides/quickstart-html.md +31 -0
- package/docs/guides/quickstart-npm.md +34 -0
- package/docs/guides/react.md +88 -0
- package/docs/guides/security-csp.md +32 -0
- package/docs/guides/server.md +76 -0
- package/docs/guides/solid.md +44 -0
- package/docs/guides/stripe-payment-element.md +14 -0
- package/docs/guides/svelte.md +46 -0
- package/docs/guides/troubleshooting.md +17 -0
- package/docs/guides/vue-nuxt.md +64 -0
- package/docs/guides/web-component.md +109 -0
- package/package.json +40 -3
- package/dist/checkout-button.2aad12c3.cjs.map +0 -1
- package/dist/checkout-button.4b780d52.js +0 -151
- package/dist/checkout-button.4b780d52.js.map +0 -1
- package/dist/checkout-button.aed57a66.mjs.map +0 -1
|
@@ -27,6 +27,57 @@ __export(src_exports, {
|
|
|
27
27
|
});
|
|
28
28
|
module.exports = __toCommonJS(src_exports);
|
|
29
29
|
|
|
30
|
+
// src/errors.ts
|
|
31
|
+
var MaytesErrorCode = {
|
|
32
|
+
Config: "CONFIG"
|
|
33
|
+
};
|
|
34
|
+
var MaytesError = class extends Error {
|
|
35
|
+
constructor(code, message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.code = code;
|
|
38
|
+
this.name = "MaytesError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/appearance.ts
|
|
43
|
+
var MIN_BUTTON_HEIGHT = 40;
|
|
44
|
+
var MAX_BUTTON_HEIGHT = 55;
|
|
45
|
+
var MAX_BUTTON_RADIUS = 999;
|
|
46
|
+
function isWholeNumber(value) {
|
|
47
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
48
|
+
}
|
|
49
|
+
function assertAppearance(options) {
|
|
50
|
+
const { radius, height } = options;
|
|
51
|
+
const appearance = {};
|
|
52
|
+
if (height !== void 0) {
|
|
53
|
+
if (!isWholeNumber(height) || height < MIN_BUTTON_HEIGHT || height > MAX_BUTTON_HEIGHT) {
|
|
54
|
+
throw new MaytesError(
|
|
55
|
+
MaytesErrorCode.Config,
|
|
56
|
+
`renderButton({ height }) must be a whole number of pixels from ${MIN_BUTTON_HEIGHT} to ${MAX_BUTTON_HEIGHT}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
appearance.height = height;
|
|
60
|
+
}
|
|
61
|
+
if (radius !== void 0) {
|
|
62
|
+
const maxRadius = height === void 0 ? MAX_BUTTON_RADIUS : Math.floor(height / 2);
|
|
63
|
+
if (!isWholeNumber(radius) || radius < 0 || radius > maxRadius) {
|
|
64
|
+
throw new MaytesError(
|
|
65
|
+
MaytesErrorCode.Config,
|
|
66
|
+
`renderButton({ radius }) must be a whole number of pixels from 0 to ${maxRadius}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
appearance.radius = radius;
|
|
70
|
+
}
|
|
71
|
+
return appearance;
|
|
72
|
+
}
|
|
73
|
+
function applyAppearance(button, appearance) {
|
|
74
|
+
if (appearance.radius !== void 0) button.style.setProperty("--maytes-button-radius", `${appearance.radius}px`);
|
|
75
|
+
if (appearance.height !== void 0) {
|
|
76
|
+
button.style.setProperty("--maytes-button-height", `${appearance.height}px`);
|
|
77
|
+
button.classList.add("maytes-checkout-button--sized");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
30
81
|
// src/foundation/brand.generated.ts
|
|
31
82
|
var foundation = {
|
|
32
83
|
brand: {
|
|
@@ -74,18 +125,6 @@ function buildMaytesLogo(height, color) {
|
|
|
74
125
|
return svg;
|
|
75
126
|
}
|
|
76
127
|
|
|
77
|
-
// src/errors.ts
|
|
78
|
-
var MaytesErrorCode = {
|
|
79
|
-
Config: "CONFIG"
|
|
80
|
-
};
|
|
81
|
-
var MaytesError = class extends Error {
|
|
82
|
-
constructor(code, message) {
|
|
83
|
-
super(message);
|
|
84
|
-
this.code = code;
|
|
85
|
-
this.name = "MaytesError";
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
128
|
// src/framing.ts
|
|
90
129
|
function isSecurityError(err) {
|
|
91
130
|
return typeof err === "object" && err !== null && err.name === "SecurityError";
|
|
@@ -175,7 +214,7 @@ function ensureOverlayStylesInTopDocument(host) {
|
|
|
175
214
|
copy.textContent = localStyle.textContent ?? "";
|
|
176
215
|
host.head.appendChild(copy);
|
|
177
216
|
}
|
|
178
|
-
function buildOverlay(host, state) {
|
|
217
|
+
function buildOverlay(host, state, variant) {
|
|
179
218
|
const dialog = host.createElement("dialog");
|
|
180
219
|
dialog.className = "maytes-checkout-overlay";
|
|
181
220
|
dialog.setAttribute("aria-labelledby", TITLE_ID);
|
|
@@ -195,22 +234,24 @@ function buildOverlay(host, state) {
|
|
|
195
234
|
const text = host.createElement("p");
|
|
196
235
|
text.id = TITLE_ID;
|
|
197
236
|
text.className = "maytes-checkout-overlay__text";
|
|
198
|
-
text.textContent = "Completing checkout with Maytes\u2026";
|
|
237
|
+
text.textContent = variant === "popup" ? "Completing checkout with Maytes\u2026" : "Taking you to Maytes\u2026";
|
|
199
238
|
content.appendChild(text);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
239
|
+
if (variant === "popup") {
|
|
240
|
+
const link = host.createElement("button");
|
|
241
|
+
link.className = "maytes-checkout-overlay__link";
|
|
242
|
+
link.type = "button";
|
|
243
|
+
link.textContent = "Return to Maytes";
|
|
244
|
+
link.addEventListener("click", () => {
|
|
245
|
+
if (state.popupWindow !== null && !state.popupWindow.closed) {
|
|
246
|
+
try {
|
|
247
|
+
state.popupWindow.focus();
|
|
248
|
+
} catch (err) {
|
|
249
|
+
if (!isSecurityError(err)) throw err;
|
|
250
|
+
}
|
|
210
251
|
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
252
|
+
});
|
|
253
|
+
content.appendChild(link);
|
|
254
|
+
}
|
|
214
255
|
dialog.appendChild(content);
|
|
215
256
|
dialog.addEventListener("cancel", (event) => {
|
|
216
257
|
event.preventDefault();
|
|
@@ -222,11 +263,11 @@ function buildOverlay(host, state) {
|
|
|
222
263
|
function applyNativeBackdropFallback(dialog) {
|
|
223
264
|
dialog.style.background = NATIVE_BACKDROP_FALLBACK;
|
|
224
265
|
}
|
|
225
|
-
function showOverlay(state) {
|
|
266
|
+
function showOverlay(state, variant = "popup") {
|
|
226
267
|
if (state.overlayEl !== null) return;
|
|
227
268
|
const host = resolveOverlayHost();
|
|
228
269
|
ensureOverlayStylesInTopDocument(host);
|
|
229
|
-
const dialog = buildOverlay(host, state);
|
|
270
|
+
const dialog = buildOverlay(host, state, variant);
|
|
230
271
|
state.overlayEl = dialog;
|
|
231
272
|
host.body.appendChild(dialog);
|
|
232
273
|
if (host !== document) detachOverlayWhenThisPageHides(state);
|
|
@@ -345,7 +386,7 @@ function makeRedirect(environment, baseUrlOverride) {
|
|
|
345
386
|
}
|
|
346
387
|
|
|
347
388
|
// src/version.ts
|
|
348
|
-
var SDK_VERSION = "1.
|
|
389
|
+
var SDK_VERSION = "1.2.1";
|
|
349
390
|
|
|
350
391
|
// src/styles.ts
|
|
351
392
|
var STYLE_ATTR = "data-maytes-checkout-button";
|
|
@@ -358,28 +399,51 @@ var BUTTON_CSS = `
|
|
|
358
399
|
align-items: center;
|
|
359
400
|
justify-content: center;
|
|
360
401
|
gap: 0.57em;
|
|
402
|
+
min-height: var(--maytes-button-height, auto);
|
|
361
403
|
padding: 0.71em 1.29em;
|
|
362
|
-
border:
|
|
363
|
-
border-radius: 999px;
|
|
364
|
-
background: ${foundation.brand.primary};
|
|
365
|
-
color: ${foundation.text.primaryInverse};
|
|
404
|
+
border-radius: var(--maytes-button-radius, 999px);
|
|
366
405
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
367
406
|
font-size: var(--maytes-button-font-size, 14px);
|
|
368
407
|
font-weight: 600;
|
|
369
408
|
line-height: 1;
|
|
370
|
-
cursor: pointer;
|
|
371
409
|
user-select: none;
|
|
410
|
+
}
|
|
411
|
+
.maytes-checkout-button.maytes-checkout-button,
|
|
412
|
+
.maytes-checkout-button.maytes-checkout-button:hover,
|
|
413
|
+
.maytes-checkout-button.maytes-checkout-button:focus,
|
|
414
|
+
.maytes-checkout-button.maytes-checkout-button:active {
|
|
415
|
+
background: ${foundation.brand.primary};
|
|
416
|
+
border: none;
|
|
417
|
+
color: ${foundation.text.primaryInverse};
|
|
418
|
+
box-shadow: none;
|
|
419
|
+
text-decoration: none;
|
|
420
|
+
text-shadow: none;
|
|
421
|
+
filter: none;
|
|
422
|
+
transform: none;
|
|
423
|
+
opacity: 1;
|
|
424
|
+
cursor: pointer;
|
|
372
425
|
transition: background-color 120ms ease, opacity 120ms ease;
|
|
373
426
|
}
|
|
374
|
-
.maytes-checkout-button:hover { background: ${foundation.palette.burgundy[700]}; }
|
|
375
|
-
.maytes-checkout-button:focus-visible {
|
|
427
|
+
.maytes-checkout-button.maytes-checkout-button:hover { background: ${foundation.palette.burgundy[700]}; }
|
|
428
|
+
.maytes-checkout-button.maytes-checkout-button:focus:not(:focus-visible) { outline: none; }
|
|
429
|
+
.maytes-checkout-button.maytes-checkout-button:focus-visible {
|
|
376
430
|
outline: 2px solid ${FOCUS_RING};
|
|
377
431
|
outline-offset: 2px;
|
|
378
432
|
}
|
|
379
|
-
.maytes-checkout-button[aria-disabled='true'] {
|
|
433
|
+
.maytes-checkout-button.maytes-checkout-button[aria-disabled='true'] {
|
|
380
434
|
cursor: progress;
|
|
381
435
|
opacity: 0.7;
|
|
382
436
|
}
|
|
437
|
+
.maytes-checkout-button.maytes-checkout-button * { transition: none; }
|
|
438
|
+
.maytes-checkout-button.maytes-checkout-button .maytes-checkout-button__logo,
|
|
439
|
+
.maytes-checkout-button.maytes-checkout-button .maytes-checkout-button__logo * {
|
|
440
|
+
animation: none;
|
|
441
|
+
fill: currentColor;
|
|
442
|
+
}
|
|
443
|
+
.maytes-checkout-button--sized {
|
|
444
|
+
box-sizing: border-box;
|
|
445
|
+
white-space: nowrap;
|
|
446
|
+
}
|
|
383
447
|
.maytes-checkout-button--block {
|
|
384
448
|
display: flex;
|
|
385
449
|
width: 100%;
|
|
@@ -441,17 +505,31 @@ var BUTTON_CSS = `
|
|
|
441
505
|
margin: 0;
|
|
442
506
|
}
|
|
443
507
|
.maytes-checkout-overlay__link {
|
|
444
|
-
color: rgba(255, 255, 255, 0.7);
|
|
445
508
|
font-size: 0.875rem;
|
|
446
|
-
text-decoration: underline;
|
|
447
509
|
text-underline-offset: 4px;
|
|
510
|
+
font-family: inherit;
|
|
511
|
+
}
|
|
512
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link,
|
|
513
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:hover,
|
|
514
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:focus,
|
|
515
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:active {
|
|
448
516
|
background: none;
|
|
449
517
|
border: none;
|
|
518
|
+
color: rgba(255, 255, 255, 0.7);
|
|
519
|
+
box-shadow: none;
|
|
520
|
+
text-decoration: underline;
|
|
521
|
+
text-shadow: none;
|
|
522
|
+
transform: none;
|
|
523
|
+
opacity: 1;
|
|
450
524
|
cursor: pointer;
|
|
451
|
-
font-family: inherit;
|
|
452
525
|
transition: color 0.15s;
|
|
453
526
|
}
|
|
454
|
-
.maytes-checkout-overlay__link:hover { color: ${foundation.text.primaryInverse}; }
|
|
527
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:hover { color: ${foundation.text.primaryInverse}; }
|
|
528
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:focus:not(:focus-visible) { outline: none; }
|
|
529
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:focus-visible {
|
|
530
|
+
outline: 2px solid ${FOCUS_RING};
|
|
531
|
+
outline-offset: 2px;
|
|
532
|
+
}
|
|
455
533
|
@media (prefers-reduced-motion: reduce) {
|
|
456
534
|
.maytes-checkout-overlay__spinner,
|
|
457
535
|
.maytes-checkout-button__spinner { animation: none; }
|
|
@@ -528,24 +606,12 @@ function releaseStyles() {
|
|
|
528
606
|
if (el !== null) el.remove();
|
|
529
607
|
}
|
|
530
608
|
|
|
531
|
-
// src/
|
|
609
|
+
// src/launch.ts
|
|
532
610
|
var POPUP_WIDTH = 500;
|
|
533
611
|
var POPUP_HEIGHT = 800;
|
|
534
612
|
var POPUP_POLL_INTERVAL_MS = 500;
|
|
535
613
|
var MOBILE_MAX_WIDTH = 600;
|
|
536
614
|
var SLOW_NETWORK_DELAY_MS = 8e3;
|
|
537
|
-
function buildLogo() {
|
|
538
|
-
const svg = buildMaytesLogo("1em", "currentColor");
|
|
539
|
-
svg.setAttribute("class", "maytes-checkout-button__logo");
|
|
540
|
-
svg.setAttribute("aria-hidden", "true");
|
|
541
|
-
return svg;
|
|
542
|
-
}
|
|
543
|
-
function buildSpinner() {
|
|
544
|
-
const spinner = document.createElement("span");
|
|
545
|
-
spinner.className = "maytes-checkout-button__spinner";
|
|
546
|
-
spinner.setAttribute("aria-hidden", "true");
|
|
547
|
-
return spinner;
|
|
548
|
-
}
|
|
549
615
|
function isValidCheckoutResult(value) {
|
|
550
616
|
if (typeof value !== "object" || value === null) return false;
|
|
551
617
|
const obj = value;
|
|
@@ -644,6 +710,148 @@ function paintPopupLoadingScreen(popup, cspNonce) {
|
|
|
644
710
|
slow.hidden = false;
|
|
645
711
|
}, SLOW_NETWORK_DELAY_MS);
|
|
646
712
|
}
|
|
713
|
+
function eventDetail(state, source) {
|
|
714
|
+
return { instanceId: state.instanceId, source };
|
|
715
|
+
}
|
|
716
|
+
function dispatchFailed(origin, reason, cause) {
|
|
717
|
+
const detail = cause === void 0 ? { ...origin, reason } : { ...origin, reason, cause };
|
|
718
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-failed", { detail }));
|
|
719
|
+
}
|
|
720
|
+
function dispatchRedirected(origin, url, target) {
|
|
721
|
+
const detail = { ...origin, url, target };
|
|
722
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-redirected", { detail }));
|
|
723
|
+
}
|
|
724
|
+
function assertLaunchMode(mode, caller = "renderButton") {
|
|
725
|
+
if (mode !== "redirect" && mode !== "popup") {
|
|
726
|
+
throw new MaytesError(MaytesErrorCode.Config, `${caller}({ mode }) must be 'redirect' or 'popup'`);
|
|
727
|
+
}
|
|
728
|
+
return mode;
|
|
729
|
+
}
|
|
730
|
+
function assertRedirectOverlay(value, caller = "renderButton") {
|
|
731
|
+
if (value === void 0) return false;
|
|
732
|
+
if (typeof value !== "boolean") {
|
|
733
|
+
throw new MaytesError(MaytesErrorCode.Config, `${caller}({ redirectOverlay }) must be true or false`);
|
|
734
|
+
}
|
|
735
|
+
return value;
|
|
736
|
+
}
|
|
737
|
+
function setInstanceBusy(state, busy) {
|
|
738
|
+
state.busy = busy;
|
|
739
|
+
for (const view of [...state.busyViews]) view(busy);
|
|
740
|
+
}
|
|
741
|
+
function endLaunch(state) {
|
|
742
|
+
hideOverlay(state);
|
|
743
|
+
setInstanceBusy(state, false);
|
|
744
|
+
}
|
|
745
|
+
function closeOrphanPopup(state, popup) {
|
|
746
|
+
closePopupWindow(popup);
|
|
747
|
+
stopPopupPoll(state);
|
|
748
|
+
if (state.popupWindow === popup) state.popupWindow = null;
|
|
749
|
+
endLaunch(state);
|
|
750
|
+
}
|
|
751
|
+
function clearOverlayOnRestore(state) {
|
|
752
|
+
if (state.restoreDetach !== null) return;
|
|
753
|
+
const onPageShow = (event) => {
|
|
754
|
+
if (!event.persisted) return;
|
|
755
|
+
detach();
|
|
756
|
+
endLaunch(state);
|
|
757
|
+
};
|
|
758
|
+
const detach = () => {
|
|
759
|
+
window.removeEventListener("pageshow", onPageShow);
|
|
760
|
+
state.restoreDetach = null;
|
|
761
|
+
};
|
|
762
|
+
window.addEventListener("pageshow", onPageShow);
|
|
763
|
+
state.restoreDetach = detach;
|
|
764
|
+
}
|
|
765
|
+
function navigateAway(state, origin, url, keepOverlay) {
|
|
766
|
+
const navigation = navigateTopLevel(url, false);
|
|
767
|
+
if (keepOverlay && (navigation.target === "self" || navigation.target === "top")) {
|
|
768
|
+
setInstanceBusy(state, false);
|
|
769
|
+
clearOverlayOnRestore(state);
|
|
770
|
+
} else {
|
|
771
|
+
endLaunch(state);
|
|
772
|
+
}
|
|
773
|
+
if (navigation.target === null) {
|
|
774
|
+
console.error("[maytes/checkout-button] could not leave the embedding frame:", navigation.cause);
|
|
775
|
+
dispatchFailed(origin, "navigation-blocked", navigation.cause);
|
|
776
|
+
return { outcome: "failed", reason: "navigation-blocked" };
|
|
777
|
+
}
|
|
778
|
+
dispatchRedirected(origin, url, navigation.target);
|
|
779
|
+
return { outcome: "redirected", target: navigation.target };
|
|
780
|
+
}
|
|
781
|
+
function openPopupLoader(state, origin) {
|
|
782
|
+
const popup = openBlankPopup(state);
|
|
783
|
+
if (popup === null) return null;
|
|
784
|
+
state.popupWindow = popup;
|
|
785
|
+
state.popupNavigated = false;
|
|
786
|
+
refocusPopup(popup);
|
|
787
|
+
paintPopupLoadingScreen(popup, state.config.cspNonce);
|
|
788
|
+
showOverlay(state);
|
|
789
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-opened", { detail: origin }));
|
|
790
|
+
startPopupPoll(state, () => {
|
|
791
|
+
endLaunch(state);
|
|
792
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-closed", { detail: origin }));
|
|
793
|
+
});
|
|
794
|
+
return popup;
|
|
795
|
+
}
|
|
796
|
+
async function launchCheckout(state, mode, source, redirectOverlay = false) {
|
|
797
|
+
if (state.busy || state.destroyed) return { outcome: "ignored" };
|
|
798
|
+
setInstanceBusy(state, true);
|
|
799
|
+
const origin = eventDetail(state, source);
|
|
800
|
+
const shouldUsePopup = mode === "popup" && !isMobileViewport();
|
|
801
|
+
const popup = shouldUsePopup ? openPopupLoader(state, origin) : null;
|
|
802
|
+
if (popup === null && redirectOverlay) showOverlay(state, "redirect");
|
|
803
|
+
try {
|
|
804
|
+
const result = await state.config.createCheckout();
|
|
805
|
+
if (state.destroyed || popup !== null && popup.closed) {
|
|
806
|
+
closePopupWindow(popup);
|
|
807
|
+
return { outcome: "closed" };
|
|
808
|
+
}
|
|
809
|
+
if (!isValidCheckoutResult(result)) {
|
|
810
|
+
closeOrphanPopup(state, popup);
|
|
811
|
+
const shapeError = new MaytesError(
|
|
812
|
+
MaytesErrorCode.Config,
|
|
813
|
+
"createCheckout must resolve to { checkoutId: string, checkoutUrl?: http(s) URL }"
|
|
814
|
+
);
|
|
815
|
+
console.error(shapeError);
|
|
816
|
+
dispatchFailed(origin, "invalid-shape", shapeError);
|
|
817
|
+
return { outcome: "failed", reason: "invalid-shape" };
|
|
818
|
+
}
|
|
819
|
+
const url = result.checkoutUrl ?? buildCheckoutUrl(
|
|
820
|
+
state.config.environment,
|
|
821
|
+
state.config.baseUrl,
|
|
822
|
+
result.checkoutId
|
|
823
|
+
);
|
|
824
|
+
if (popup !== null) {
|
|
825
|
+
state.popupNavigated = true;
|
|
826
|
+
navigatePopup(popup, url);
|
|
827
|
+
return { outcome: "popup" };
|
|
828
|
+
}
|
|
829
|
+
if (shouldUsePopup) {
|
|
830
|
+
console.warn("[maytes/checkout-button] popup was blocked; redirecting instead");
|
|
831
|
+
}
|
|
832
|
+
return navigateAway(state, origin, url, redirectOverlay);
|
|
833
|
+
} catch (err) {
|
|
834
|
+
if (popup !== null && popup.closed) return { outcome: "closed" };
|
|
835
|
+
closeOrphanPopup(state, popup);
|
|
836
|
+
console.error("[maytes/checkout-button] createCheckout failed:", err);
|
|
837
|
+
dispatchFailed(origin, "create-checkout-rejected", err);
|
|
838
|
+
return { outcome: "failed", reason: "create-checkout-rejected" };
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// src/button.ts
|
|
843
|
+
function buildLogo() {
|
|
844
|
+
const svg = buildMaytesLogo("1em", "currentColor");
|
|
845
|
+
svg.setAttribute("class", "maytes-checkout-button__logo");
|
|
846
|
+
svg.setAttribute("aria-hidden", "true");
|
|
847
|
+
return svg;
|
|
848
|
+
}
|
|
849
|
+
function buildSpinner() {
|
|
850
|
+
const spinner = document.createElement("span");
|
|
851
|
+
spinner.className = "maytes-checkout-button__spinner";
|
|
852
|
+
spinner.setAttribute("aria-hidden", "true");
|
|
853
|
+
return spinner;
|
|
854
|
+
}
|
|
647
855
|
function renderButton(state, container, options = {}) {
|
|
648
856
|
if (state.destroyed) {
|
|
649
857
|
throw new MaytesError(
|
|
@@ -654,120 +862,47 @@ function renderButton(state, container, options = {}) {
|
|
|
654
862
|
if (!(container instanceof HTMLElement)) {
|
|
655
863
|
throw new MaytesError(MaytesErrorCode.Config, "renderButton(container) requires an HTMLElement");
|
|
656
864
|
}
|
|
865
|
+
const appearance = assertAppearance(options);
|
|
866
|
+
const redirectOverlay = assertRedirectOverlay(options.redirectOverlay);
|
|
657
867
|
ensureStylesInjected(state.config.cspNonce);
|
|
658
868
|
const label = options.label ?? "Split with";
|
|
659
869
|
const block = options.block === true;
|
|
660
|
-
const mode = options.mode ?? "popup";
|
|
661
|
-
if (mode !== "redirect" && mode !== "popup") {
|
|
662
|
-
throw new MaytesError(
|
|
663
|
-
MaytesErrorCode.Config,
|
|
664
|
-
"renderButton({ mode }) must be 'redirect' or 'popup'"
|
|
665
|
-
);
|
|
666
|
-
}
|
|
870
|
+
const mode = assertLaunchMode(options.mode ?? "popup");
|
|
667
871
|
const button = document.createElement("button");
|
|
668
872
|
button.type = "button";
|
|
669
873
|
button.className = block ? "maytes-checkout-button maytes-checkout-button--block" : "maytes-checkout-button";
|
|
874
|
+
applyAppearance(button, appearance);
|
|
670
875
|
button.setAttribute("aria-label", `${label} Maytes`);
|
|
671
876
|
const labelNode = document.createTextNode(`${label} `);
|
|
672
|
-
|
|
877
|
+
let icon = buildLogo();
|
|
673
878
|
button.appendChild(labelNode);
|
|
674
|
-
button.appendChild(
|
|
675
|
-
const
|
|
676
|
-
state.busy = busy;
|
|
879
|
+
button.appendChild(icon);
|
|
880
|
+
const showBusy = (busy) => {
|
|
677
881
|
if (busy) {
|
|
678
882
|
button.setAttribute("aria-disabled", "true");
|
|
679
883
|
button.setAttribute("aria-busy", "true");
|
|
680
|
-
|
|
884
|
+
const spinner = buildSpinner();
|
|
885
|
+
icon.replaceWith(spinner);
|
|
886
|
+
icon = spinner;
|
|
681
887
|
} else {
|
|
682
888
|
button.removeAttribute("aria-disabled");
|
|
683
889
|
button.removeAttribute("aria-busy");
|
|
684
|
-
const
|
|
685
|
-
|
|
890
|
+
const nextLogo = buildLogo();
|
|
891
|
+
icon.replaceWith(nextLogo);
|
|
892
|
+
icon = nextLogo;
|
|
686
893
|
}
|
|
687
894
|
};
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
teardownActiveCheckout();
|
|
694
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-closed"));
|
|
695
|
-
};
|
|
696
|
-
const dispatchFailed = (reason, cause) => {
|
|
697
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-failed", {
|
|
698
|
-
detail: cause === void 0 ? { reason } : { reason, cause }
|
|
699
|
-
}));
|
|
700
|
-
};
|
|
701
|
-
const dispatchRedirected = (url, target) => {
|
|
702
|
-
const detail = { url, target };
|
|
703
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-redirected", { detail }));
|
|
895
|
+
let launchedHere = false;
|
|
896
|
+
const followOwnLaunch = (busy) => {
|
|
897
|
+
if (!launchedHere) return;
|
|
898
|
+
showBusy(busy);
|
|
899
|
+
if (!busy) launchedHere = false;
|
|
704
900
|
};
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
teardownActiveCheckout();
|
|
708
|
-
if (outcome.target === null) {
|
|
709
|
-
console.error("[maytes/checkout-button] could not leave the embedding frame:", outcome.cause);
|
|
710
|
-
dispatchFailed("navigation-blocked", outcome.cause);
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
dispatchRedirected(url, outcome.target);
|
|
714
|
-
};
|
|
715
|
-
const closeOrphanPopup = (popup) => {
|
|
716
|
-
closePopupWindow(popup);
|
|
717
|
-
stopPopupPoll(state);
|
|
718
|
-
if (state.popupWindow === popup) state.popupWindow = null;
|
|
719
|
-
teardownActiveCheckout();
|
|
720
|
-
};
|
|
721
|
-
const handleClick = async () => {
|
|
901
|
+
state.busyViews.add(followOwnLaunch);
|
|
902
|
+
const handleClick = () => {
|
|
722
903
|
if (state.busy || state.destroyed) return;
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const popup = shouldUsePopup ? openBlankPopup(state) : null;
|
|
726
|
-
if (popup !== null) {
|
|
727
|
-
state.popupWindow = popup;
|
|
728
|
-
state.popupNavigated = false;
|
|
729
|
-
refocusPopup(popup);
|
|
730
|
-
paintPopupLoadingScreen(popup, state.config.cspNonce);
|
|
731
|
-
showOverlay(state);
|
|
732
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-opened"));
|
|
733
|
-
startPopupPoll(state, onPopupClosed);
|
|
734
|
-
}
|
|
735
|
-
try {
|
|
736
|
-
const result = await state.config.createCheckout();
|
|
737
|
-
if (state.destroyed || popup !== null && popup.closed) {
|
|
738
|
-
closePopupWindow(popup);
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
if (!isValidCheckoutResult(result)) {
|
|
742
|
-
closeOrphanPopup(popup);
|
|
743
|
-
const shapeError = new MaytesError(
|
|
744
|
-
MaytesErrorCode.Config,
|
|
745
|
-
"createCheckout must resolve to { checkoutId: string, checkoutUrl?: http(s) URL }"
|
|
746
|
-
);
|
|
747
|
-
console.error(shapeError);
|
|
748
|
-
dispatchFailed("invalid-shape", shapeError);
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
const url = result.checkoutUrl ?? buildCheckoutUrl(
|
|
752
|
-
state.config.environment,
|
|
753
|
-
state.config.baseUrl,
|
|
754
|
-
result.checkoutId
|
|
755
|
-
);
|
|
756
|
-
if (popup !== null) {
|
|
757
|
-
state.popupNavigated = true;
|
|
758
|
-
navigatePopup(popup, url);
|
|
759
|
-
} else {
|
|
760
|
-
if (shouldUsePopup) {
|
|
761
|
-
console.warn("[maytes/checkout-button] popup was blocked; redirecting instead");
|
|
762
|
-
}
|
|
763
|
-
navigateAway(url);
|
|
764
|
-
}
|
|
765
|
-
} catch (err) {
|
|
766
|
-
if (popup !== null && popup.closed) return;
|
|
767
|
-
closeOrphanPopup(popup);
|
|
768
|
-
console.error("[maytes/checkout-button] createCheckout failed:", err);
|
|
769
|
-
dispatchFailed("create-checkout-rejected", err);
|
|
770
|
-
}
|
|
904
|
+
launchedHere = true;
|
|
905
|
+
void launchCheckout(state, mode, "button", redirectOverlay);
|
|
771
906
|
};
|
|
772
907
|
button.addEventListener("click", handleClick);
|
|
773
908
|
container.appendChild(button);
|
|
@@ -777,6 +912,7 @@ function renderButton(state, container, options = {}) {
|
|
|
777
912
|
cleaned = true;
|
|
778
913
|
button.removeEventListener("click", handleClick);
|
|
779
914
|
button.remove();
|
|
915
|
+
state.busyViews.delete(followOwnLaunch);
|
|
780
916
|
state.teardowns.delete(teardown);
|
|
781
917
|
releaseStyles();
|
|
782
918
|
};
|
|
@@ -785,18 +921,18 @@ function renderButton(state, container, options = {}) {
|
|
|
785
921
|
}
|
|
786
922
|
|
|
787
923
|
// src/state.ts
|
|
788
|
-
function
|
|
924
|
+
function randomSuffix() {
|
|
789
925
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
790
926
|
}
|
|
791
|
-
function
|
|
927
|
+
function randomId() {
|
|
792
928
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
793
929
|
try {
|
|
794
|
-
return
|
|
930
|
+
return crypto.randomUUID();
|
|
795
931
|
} catch {
|
|
796
|
-
return
|
|
932
|
+
return randomSuffix();
|
|
797
933
|
}
|
|
798
934
|
}
|
|
799
|
-
return
|
|
935
|
+
return randomSuffix();
|
|
800
936
|
}
|
|
801
937
|
function createInstanceState(options, internal) {
|
|
802
938
|
const config = {
|
|
@@ -808,11 +944,14 @@ function createInstanceState(options, internal) {
|
|
|
808
944
|
return {
|
|
809
945
|
config,
|
|
810
946
|
busy: false,
|
|
947
|
+
busyViews: /* @__PURE__ */ new Set(),
|
|
948
|
+
instanceId: randomId(),
|
|
811
949
|
overlayEl: null,
|
|
812
950
|
overlayDetach: null,
|
|
951
|
+
restoreDetach: null,
|
|
813
952
|
popupWindow: null,
|
|
814
953
|
popupNavigated: false,
|
|
815
|
-
popupName:
|
|
954
|
+
popupName: `maytes-checkout-${randomId()}`,
|
|
816
955
|
popupPollHandle: null,
|
|
817
956
|
destroyed: false,
|
|
818
957
|
teardowns: /* @__PURE__ */ new Set()
|
|
@@ -850,9 +989,27 @@ var Maytes = (options, internal) => {
|
|
|
850
989
|
state.config.baseUrl
|
|
851
990
|
);
|
|
852
991
|
const sdk = {
|
|
992
|
+
instanceId: state.instanceId,
|
|
853
993
|
renderButton(container, opts) {
|
|
854
994
|
return renderButton(state, container, opts);
|
|
855
995
|
},
|
|
996
|
+
openCheckout(opts) {
|
|
997
|
+
if (state.destroyed) {
|
|
998
|
+
throw new MaytesError(MaytesErrorCode.Config, "openCheckout() called on a destroyed Maytes instance");
|
|
999
|
+
}
|
|
1000
|
+
const mode = assertLaunchMode(opts?.mode ?? "popup", "openCheckout");
|
|
1001
|
+
const redirectOverlay = assertRedirectOverlay(opts?.redirectOverlay, "openCheckout");
|
|
1002
|
+
return launchCheckout(state, mode, "api", redirectOverlay);
|
|
1003
|
+
},
|
|
1004
|
+
onBusyChange(listener) {
|
|
1005
|
+
if (state.destroyed) {
|
|
1006
|
+
throw new MaytesError(MaytesErrorCode.Config, "onBusyChange() called on a destroyed Maytes instance");
|
|
1007
|
+
}
|
|
1008
|
+
state.busyViews.add(listener);
|
|
1009
|
+
return () => {
|
|
1010
|
+
state.busyViews.delete(listener);
|
|
1011
|
+
};
|
|
1012
|
+
},
|
|
856
1013
|
redirectToCheckout(opts) {
|
|
857
1014
|
redirectToCheckout(opts);
|
|
858
1015
|
},
|
|
@@ -863,6 +1020,7 @@ var Maytes = (options, internal) => {
|
|
|
863
1020
|
if (state.destroyed) return;
|
|
864
1021
|
state.destroyed = true;
|
|
865
1022
|
stopPopupPoll(state);
|
|
1023
|
+
state.restoreDetach?.();
|
|
866
1024
|
hideOverlay(state);
|
|
867
1025
|
if (!state.popupNavigated) closePopupWindow(state.popupWindow);
|
|
868
1026
|
state.popupWindow = null;
|