@maytes/checkout-button 1.1.1 → 1.2.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.
- package/CHANGELOG.md +49 -0
- package/README.md +97 -53
- package/dist/checkout-button.c640180e.js +156 -0
- package/dist/checkout-button.c640180e.js.map +1 -0
- package/dist/checkout-button.cjs +271 -145
- 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.aed57a66.mjs → checkout-button.d6e444ee.mjs} +271 -145
- package/dist/checkout-button.d6e444ee.mjs.map +1 -0
- package/dist/{checkout-button.2aad12c3.cjs → checkout-button.d7bc9aea.cjs} +271 -145
- package/dist/checkout-button.d7bc9aea.cjs.map +1 -0
- package/dist/checkout-button.element.6c530c24.js +155 -0
- package/dist/checkout-button.element.6c530c24.js.map +1 -0
- package/dist/checkout-button.element.js +155 -0
- package/dist/checkout-button.element.js.map +1 -0
- package/dist/checkout-button.js +19 -14
- package/dist/checkout-button.js.map +1 -1
- package/dist/checkout-button.mjs +271 -145
- 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.0";
|
|
349
390
|
|
|
350
391
|
// src/styles.ts
|
|
351
392
|
var STYLE_ATTR = "data-maytes-checkout-button";
|
|
@@ -358,9 +399,10 @@ 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
404
|
border: none;
|
|
363
|
-
border-radius: 999px;
|
|
405
|
+
border-radius: var(--maytes-button-radius, 999px);
|
|
364
406
|
background: ${foundation.brand.primary};
|
|
365
407
|
color: ${foundation.text.primaryInverse};
|
|
366
408
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
@@ -380,6 +422,10 @@ var BUTTON_CSS = `
|
|
|
380
422
|
cursor: progress;
|
|
381
423
|
opacity: 0.7;
|
|
382
424
|
}
|
|
425
|
+
.maytes-checkout-button--sized {
|
|
426
|
+
box-sizing: border-box;
|
|
427
|
+
white-space: nowrap;
|
|
428
|
+
}
|
|
383
429
|
.maytes-checkout-button--block {
|
|
384
430
|
display: flex;
|
|
385
431
|
width: 100%;
|
|
@@ -528,24 +574,12 @@ function releaseStyles() {
|
|
|
528
574
|
if (el !== null) el.remove();
|
|
529
575
|
}
|
|
530
576
|
|
|
531
|
-
// src/
|
|
577
|
+
// src/launch.ts
|
|
532
578
|
var POPUP_WIDTH = 500;
|
|
533
579
|
var POPUP_HEIGHT = 800;
|
|
534
580
|
var POPUP_POLL_INTERVAL_MS = 500;
|
|
535
581
|
var MOBILE_MAX_WIDTH = 600;
|
|
536
582
|
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
583
|
function isValidCheckoutResult(value) {
|
|
550
584
|
if (typeof value !== "object" || value === null) return false;
|
|
551
585
|
const obj = value;
|
|
@@ -644,6 +678,148 @@ function paintPopupLoadingScreen(popup, cspNonce) {
|
|
|
644
678
|
slow.hidden = false;
|
|
645
679
|
}, SLOW_NETWORK_DELAY_MS);
|
|
646
680
|
}
|
|
681
|
+
function eventDetail(state, source) {
|
|
682
|
+
return { instanceId: state.instanceId, source };
|
|
683
|
+
}
|
|
684
|
+
function dispatchFailed(origin, reason, cause) {
|
|
685
|
+
const detail = cause === void 0 ? { ...origin, reason } : { ...origin, reason, cause };
|
|
686
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-failed", { detail }));
|
|
687
|
+
}
|
|
688
|
+
function dispatchRedirected(origin, url, target) {
|
|
689
|
+
const detail = { ...origin, url, target };
|
|
690
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-redirected", { detail }));
|
|
691
|
+
}
|
|
692
|
+
function assertLaunchMode(mode, caller = "renderButton") {
|
|
693
|
+
if (mode !== "redirect" && mode !== "popup") {
|
|
694
|
+
throw new MaytesError(MaytesErrorCode.Config, `${caller}({ mode }) must be 'redirect' or 'popup'`);
|
|
695
|
+
}
|
|
696
|
+
return mode;
|
|
697
|
+
}
|
|
698
|
+
function assertRedirectOverlay(value, caller = "renderButton") {
|
|
699
|
+
if (value === void 0) return false;
|
|
700
|
+
if (typeof value !== "boolean") {
|
|
701
|
+
throw new MaytesError(MaytesErrorCode.Config, `${caller}({ redirectOverlay }) must be true or false`);
|
|
702
|
+
}
|
|
703
|
+
return value;
|
|
704
|
+
}
|
|
705
|
+
function setInstanceBusy(state, busy) {
|
|
706
|
+
state.busy = busy;
|
|
707
|
+
for (const view of [...state.busyViews]) view(busy);
|
|
708
|
+
}
|
|
709
|
+
function endLaunch(state) {
|
|
710
|
+
hideOverlay(state);
|
|
711
|
+
setInstanceBusy(state, false);
|
|
712
|
+
}
|
|
713
|
+
function closeOrphanPopup(state, popup) {
|
|
714
|
+
closePopupWindow(popup);
|
|
715
|
+
stopPopupPoll(state);
|
|
716
|
+
if (state.popupWindow === popup) state.popupWindow = null;
|
|
717
|
+
endLaunch(state);
|
|
718
|
+
}
|
|
719
|
+
function clearOverlayOnRestore(state) {
|
|
720
|
+
if (state.restoreDetach !== null) return;
|
|
721
|
+
const onPageShow = (event) => {
|
|
722
|
+
if (!event.persisted) return;
|
|
723
|
+
detach();
|
|
724
|
+
endLaunch(state);
|
|
725
|
+
};
|
|
726
|
+
const detach = () => {
|
|
727
|
+
window.removeEventListener("pageshow", onPageShow);
|
|
728
|
+
state.restoreDetach = null;
|
|
729
|
+
};
|
|
730
|
+
window.addEventListener("pageshow", onPageShow);
|
|
731
|
+
state.restoreDetach = detach;
|
|
732
|
+
}
|
|
733
|
+
function navigateAway(state, origin, url, keepOverlay) {
|
|
734
|
+
const navigation = navigateTopLevel(url, false);
|
|
735
|
+
if (keepOverlay && (navigation.target === "self" || navigation.target === "top")) {
|
|
736
|
+
setInstanceBusy(state, false);
|
|
737
|
+
clearOverlayOnRestore(state);
|
|
738
|
+
} else {
|
|
739
|
+
endLaunch(state);
|
|
740
|
+
}
|
|
741
|
+
if (navigation.target === null) {
|
|
742
|
+
console.error("[maytes/checkout-button] could not leave the embedding frame:", navigation.cause);
|
|
743
|
+
dispatchFailed(origin, "navigation-blocked", navigation.cause);
|
|
744
|
+
return { outcome: "failed", reason: "navigation-blocked" };
|
|
745
|
+
}
|
|
746
|
+
dispatchRedirected(origin, url, navigation.target);
|
|
747
|
+
return { outcome: "redirected", target: navigation.target };
|
|
748
|
+
}
|
|
749
|
+
function openPopupLoader(state, origin) {
|
|
750
|
+
const popup = openBlankPopup(state);
|
|
751
|
+
if (popup === null) return null;
|
|
752
|
+
state.popupWindow = popup;
|
|
753
|
+
state.popupNavigated = false;
|
|
754
|
+
refocusPopup(popup);
|
|
755
|
+
paintPopupLoadingScreen(popup, state.config.cspNonce);
|
|
756
|
+
showOverlay(state);
|
|
757
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-opened", { detail: origin }));
|
|
758
|
+
startPopupPoll(state, () => {
|
|
759
|
+
endLaunch(state);
|
|
760
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-closed", { detail: origin }));
|
|
761
|
+
});
|
|
762
|
+
return popup;
|
|
763
|
+
}
|
|
764
|
+
async function launchCheckout(state, mode, source, redirectOverlay = false) {
|
|
765
|
+
if (state.busy || state.destroyed) return { outcome: "ignored" };
|
|
766
|
+
setInstanceBusy(state, true);
|
|
767
|
+
const origin = eventDetail(state, source);
|
|
768
|
+
const shouldUsePopup = mode === "popup" && !isMobileViewport();
|
|
769
|
+
const popup = shouldUsePopup ? openPopupLoader(state, origin) : null;
|
|
770
|
+
if (popup === null && redirectOverlay) showOverlay(state, "redirect");
|
|
771
|
+
try {
|
|
772
|
+
const result = await state.config.createCheckout();
|
|
773
|
+
if (state.destroyed || popup !== null && popup.closed) {
|
|
774
|
+
closePopupWindow(popup);
|
|
775
|
+
return { outcome: "closed" };
|
|
776
|
+
}
|
|
777
|
+
if (!isValidCheckoutResult(result)) {
|
|
778
|
+
closeOrphanPopup(state, popup);
|
|
779
|
+
const shapeError = new MaytesError(
|
|
780
|
+
MaytesErrorCode.Config,
|
|
781
|
+
"createCheckout must resolve to { checkoutId: string, checkoutUrl?: http(s) URL }"
|
|
782
|
+
);
|
|
783
|
+
console.error(shapeError);
|
|
784
|
+
dispatchFailed(origin, "invalid-shape", shapeError);
|
|
785
|
+
return { outcome: "failed", reason: "invalid-shape" };
|
|
786
|
+
}
|
|
787
|
+
const url = result.checkoutUrl ?? buildCheckoutUrl(
|
|
788
|
+
state.config.environment,
|
|
789
|
+
state.config.baseUrl,
|
|
790
|
+
result.checkoutId
|
|
791
|
+
);
|
|
792
|
+
if (popup !== null) {
|
|
793
|
+
state.popupNavigated = true;
|
|
794
|
+
navigatePopup(popup, url);
|
|
795
|
+
return { outcome: "popup" };
|
|
796
|
+
}
|
|
797
|
+
if (shouldUsePopup) {
|
|
798
|
+
console.warn("[maytes/checkout-button] popup was blocked; redirecting instead");
|
|
799
|
+
}
|
|
800
|
+
return navigateAway(state, origin, url, redirectOverlay);
|
|
801
|
+
} catch (err) {
|
|
802
|
+
if (popup !== null && popup.closed) return { outcome: "closed" };
|
|
803
|
+
closeOrphanPopup(state, popup);
|
|
804
|
+
console.error("[maytes/checkout-button] createCheckout failed:", err);
|
|
805
|
+
dispatchFailed(origin, "create-checkout-rejected", err);
|
|
806
|
+
return { outcome: "failed", reason: "create-checkout-rejected" };
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/button.ts
|
|
811
|
+
function buildLogo() {
|
|
812
|
+
const svg = buildMaytesLogo("1em", "currentColor");
|
|
813
|
+
svg.setAttribute("class", "maytes-checkout-button__logo");
|
|
814
|
+
svg.setAttribute("aria-hidden", "true");
|
|
815
|
+
return svg;
|
|
816
|
+
}
|
|
817
|
+
function buildSpinner() {
|
|
818
|
+
const spinner = document.createElement("span");
|
|
819
|
+
spinner.className = "maytes-checkout-button__spinner";
|
|
820
|
+
spinner.setAttribute("aria-hidden", "true");
|
|
821
|
+
return spinner;
|
|
822
|
+
}
|
|
647
823
|
function renderButton(state, container, options = {}) {
|
|
648
824
|
if (state.destroyed) {
|
|
649
825
|
throw new MaytesError(
|
|
@@ -654,120 +830,47 @@ function renderButton(state, container, options = {}) {
|
|
|
654
830
|
if (!(container instanceof HTMLElement)) {
|
|
655
831
|
throw new MaytesError(MaytesErrorCode.Config, "renderButton(container) requires an HTMLElement");
|
|
656
832
|
}
|
|
833
|
+
const appearance = assertAppearance(options);
|
|
834
|
+
const redirectOverlay = assertRedirectOverlay(options.redirectOverlay);
|
|
657
835
|
ensureStylesInjected(state.config.cspNonce);
|
|
658
836
|
const label = options.label ?? "Split with";
|
|
659
837
|
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
|
-
}
|
|
838
|
+
const mode = assertLaunchMode(options.mode ?? "popup");
|
|
667
839
|
const button = document.createElement("button");
|
|
668
840
|
button.type = "button";
|
|
669
841
|
button.className = block ? "maytes-checkout-button maytes-checkout-button--block" : "maytes-checkout-button";
|
|
842
|
+
applyAppearance(button, appearance);
|
|
670
843
|
button.setAttribute("aria-label", `${label} Maytes`);
|
|
671
844
|
const labelNode = document.createTextNode(`${label} `);
|
|
672
|
-
|
|
845
|
+
let icon = buildLogo();
|
|
673
846
|
button.appendChild(labelNode);
|
|
674
|
-
button.appendChild(
|
|
675
|
-
const
|
|
676
|
-
state.busy = busy;
|
|
847
|
+
button.appendChild(icon);
|
|
848
|
+
const showBusy = (busy) => {
|
|
677
849
|
if (busy) {
|
|
678
850
|
button.setAttribute("aria-disabled", "true");
|
|
679
851
|
button.setAttribute("aria-busy", "true");
|
|
680
|
-
|
|
852
|
+
const spinner = buildSpinner();
|
|
853
|
+
icon.replaceWith(spinner);
|
|
854
|
+
icon = spinner;
|
|
681
855
|
} else {
|
|
682
856
|
button.removeAttribute("aria-disabled");
|
|
683
857
|
button.removeAttribute("aria-busy");
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
};
|
|
688
|
-
const teardownActiveCheckout = () => {
|
|
689
|
-
hideOverlay(state);
|
|
690
|
-
setBusy(false);
|
|
691
|
-
};
|
|
692
|
-
const onPopupClosed = () => {
|
|
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 }));
|
|
704
|
-
};
|
|
705
|
-
const navigateAway = (url) => {
|
|
706
|
-
const outcome = navigateTopLevel(url, false);
|
|
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;
|
|
858
|
+
const nextLogo = buildLogo();
|
|
859
|
+
icon.replaceWith(nextLogo);
|
|
860
|
+
icon = nextLogo;
|
|
712
861
|
}
|
|
713
|
-
dispatchRedirected(url, outcome.target);
|
|
714
862
|
};
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
863
|
+
let launchedHere = false;
|
|
864
|
+
const followOwnLaunch = (busy) => {
|
|
865
|
+
if (!launchedHere) return;
|
|
866
|
+
showBusy(busy);
|
|
867
|
+
if (!busy) launchedHere = false;
|
|
720
868
|
};
|
|
721
|
-
|
|
869
|
+
state.busyViews.add(followOwnLaunch);
|
|
870
|
+
const handleClick = () => {
|
|
722
871
|
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
|
-
}
|
|
872
|
+
launchedHere = true;
|
|
873
|
+
void launchCheckout(state, mode, "button", redirectOverlay);
|
|
771
874
|
};
|
|
772
875
|
button.addEventListener("click", handleClick);
|
|
773
876
|
container.appendChild(button);
|
|
@@ -777,6 +880,7 @@ function renderButton(state, container, options = {}) {
|
|
|
777
880
|
cleaned = true;
|
|
778
881
|
button.removeEventListener("click", handleClick);
|
|
779
882
|
button.remove();
|
|
883
|
+
state.busyViews.delete(followOwnLaunch);
|
|
780
884
|
state.teardowns.delete(teardown);
|
|
781
885
|
releaseStyles();
|
|
782
886
|
};
|
|
@@ -785,18 +889,18 @@ function renderButton(state, container, options = {}) {
|
|
|
785
889
|
}
|
|
786
890
|
|
|
787
891
|
// src/state.ts
|
|
788
|
-
function
|
|
892
|
+
function randomSuffix() {
|
|
789
893
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
790
894
|
}
|
|
791
|
-
function
|
|
895
|
+
function randomId() {
|
|
792
896
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
793
897
|
try {
|
|
794
|
-
return
|
|
898
|
+
return crypto.randomUUID();
|
|
795
899
|
} catch {
|
|
796
|
-
return
|
|
900
|
+
return randomSuffix();
|
|
797
901
|
}
|
|
798
902
|
}
|
|
799
|
-
return
|
|
903
|
+
return randomSuffix();
|
|
800
904
|
}
|
|
801
905
|
function createInstanceState(options, internal) {
|
|
802
906
|
const config = {
|
|
@@ -808,11 +912,14 @@ function createInstanceState(options, internal) {
|
|
|
808
912
|
return {
|
|
809
913
|
config,
|
|
810
914
|
busy: false,
|
|
915
|
+
busyViews: /* @__PURE__ */ new Set(),
|
|
916
|
+
instanceId: randomId(),
|
|
811
917
|
overlayEl: null,
|
|
812
918
|
overlayDetach: null,
|
|
919
|
+
restoreDetach: null,
|
|
813
920
|
popupWindow: null,
|
|
814
921
|
popupNavigated: false,
|
|
815
|
-
popupName:
|
|
922
|
+
popupName: `maytes-checkout-${randomId()}`,
|
|
816
923
|
popupPollHandle: null,
|
|
817
924
|
destroyed: false,
|
|
818
925
|
teardowns: /* @__PURE__ */ new Set()
|
|
@@ -850,9 +957,27 @@ var Maytes = (options, internal) => {
|
|
|
850
957
|
state.config.baseUrl
|
|
851
958
|
);
|
|
852
959
|
const sdk = {
|
|
960
|
+
instanceId: state.instanceId,
|
|
853
961
|
renderButton(container, opts) {
|
|
854
962
|
return renderButton(state, container, opts);
|
|
855
963
|
},
|
|
964
|
+
openCheckout(opts) {
|
|
965
|
+
if (state.destroyed) {
|
|
966
|
+
throw new MaytesError(MaytesErrorCode.Config, "openCheckout() called on a destroyed Maytes instance");
|
|
967
|
+
}
|
|
968
|
+
const mode = assertLaunchMode(opts?.mode ?? "popup", "openCheckout");
|
|
969
|
+
const redirectOverlay = assertRedirectOverlay(opts?.redirectOverlay, "openCheckout");
|
|
970
|
+
return launchCheckout(state, mode, "api", redirectOverlay);
|
|
971
|
+
},
|
|
972
|
+
onBusyChange(listener) {
|
|
973
|
+
if (state.destroyed) {
|
|
974
|
+
throw new MaytesError(MaytesErrorCode.Config, "onBusyChange() called on a destroyed Maytes instance");
|
|
975
|
+
}
|
|
976
|
+
state.busyViews.add(listener);
|
|
977
|
+
return () => {
|
|
978
|
+
state.busyViews.delete(listener);
|
|
979
|
+
};
|
|
980
|
+
},
|
|
856
981
|
redirectToCheckout(opts) {
|
|
857
982
|
redirectToCheckout(opts);
|
|
858
983
|
},
|
|
@@ -863,6 +988,7 @@ var Maytes = (options, internal) => {
|
|
|
863
988
|
if (state.destroyed) return;
|
|
864
989
|
state.destroyed = true;
|
|
865
990
|
stopPopupPoll(state);
|
|
991
|
+
state.restoreDetach?.();
|
|
866
992
|
hideOverlay(state);
|
|
867
993
|
if (!state.popupNavigated) closePopupWindow(state.popupWindow);
|
|
868
994
|
state.popupWindow = null;
|