@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
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var MaytesErrorCode = {
|
|
3
|
+
Config: "CONFIG"
|
|
4
|
+
};
|
|
5
|
+
var MaytesError = class extends Error {
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "MaytesError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/appearance.ts
|
|
14
|
+
var MIN_BUTTON_HEIGHT = 40;
|
|
15
|
+
var MAX_BUTTON_HEIGHT = 55;
|
|
16
|
+
var MAX_BUTTON_RADIUS = 999;
|
|
17
|
+
function isWholeNumber(value) {
|
|
18
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
19
|
+
}
|
|
20
|
+
function assertAppearance(options) {
|
|
21
|
+
const { radius, height } = options;
|
|
22
|
+
const appearance = {};
|
|
23
|
+
if (height !== void 0) {
|
|
24
|
+
if (!isWholeNumber(height) || height < MIN_BUTTON_HEIGHT || height > MAX_BUTTON_HEIGHT) {
|
|
25
|
+
throw new MaytesError(
|
|
26
|
+
MaytesErrorCode.Config,
|
|
27
|
+
`renderButton({ height }) must be a whole number of pixels from ${MIN_BUTTON_HEIGHT} to ${MAX_BUTTON_HEIGHT}`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
appearance.height = height;
|
|
31
|
+
}
|
|
32
|
+
if (radius !== void 0) {
|
|
33
|
+
const maxRadius = height === void 0 ? MAX_BUTTON_RADIUS : Math.floor(height / 2);
|
|
34
|
+
if (!isWholeNumber(radius) || radius < 0 || radius > maxRadius) {
|
|
35
|
+
throw new MaytesError(
|
|
36
|
+
MaytesErrorCode.Config,
|
|
37
|
+
`renderButton({ radius }) must be a whole number of pixels from 0 to ${maxRadius}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
appearance.radius = radius;
|
|
41
|
+
}
|
|
42
|
+
return appearance;
|
|
43
|
+
}
|
|
44
|
+
function applyAppearance(button, appearance) {
|
|
45
|
+
if (appearance.radius !== void 0) button.style.setProperty("--maytes-button-radius", `${appearance.radius}px`);
|
|
46
|
+
if (appearance.height !== void 0) {
|
|
47
|
+
button.style.setProperty("--maytes-button-height", `${appearance.height}px`);
|
|
48
|
+
button.classList.add("maytes-checkout-button--sized");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
1
52
|
// src/foundation/brand.generated.ts
|
|
2
53
|
var foundation = {
|
|
3
54
|
brand: {
|
|
@@ -45,18 +96,6 @@ function buildMaytesLogo(height, color) {
|
|
|
45
96
|
return svg;
|
|
46
97
|
}
|
|
47
98
|
|
|
48
|
-
// src/errors.ts
|
|
49
|
-
var MaytesErrorCode = {
|
|
50
|
-
Config: "CONFIG"
|
|
51
|
-
};
|
|
52
|
-
var MaytesError = class extends Error {
|
|
53
|
-
constructor(code, message) {
|
|
54
|
-
super(message);
|
|
55
|
-
this.code = code;
|
|
56
|
-
this.name = "MaytesError";
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
99
|
// src/framing.ts
|
|
61
100
|
function isSecurityError(err) {
|
|
62
101
|
return typeof err === "object" && err !== null && err.name === "SecurityError";
|
|
@@ -146,7 +185,7 @@ function ensureOverlayStylesInTopDocument(host) {
|
|
|
146
185
|
copy.textContent = localStyle.textContent ?? "";
|
|
147
186
|
host.head.appendChild(copy);
|
|
148
187
|
}
|
|
149
|
-
function buildOverlay(host, state) {
|
|
188
|
+
function buildOverlay(host, state, variant) {
|
|
150
189
|
const dialog = host.createElement("dialog");
|
|
151
190
|
dialog.className = "maytes-checkout-overlay";
|
|
152
191
|
dialog.setAttribute("aria-labelledby", TITLE_ID);
|
|
@@ -166,22 +205,24 @@ function buildOverlay(host, state) {
|
|
|
166
205
|
const text = host.createElement("p");
|
|
167
206
|
text.id = TITLE_ID;
|
|
168
207
|
text.className = "maytes-checkout-overlay__text";
|
|
169
|
-
text.textContent = "Completing checkout with Maytes\u2026";
|
|
208
|
+
text.textContent = variant === "popup" ? "Completing checkout with Maytes\u2026" : "Taking you to Maytes\u2026";
|
|
170
209
|
content.appendChild(text);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
210
|
+
if (variant === "popup") {
|
|
211
|
+
const link = host.createElement("button");
|
|
212
|
+
link.className = "maytes-checkout-overlay__link";
|
|
213
|
+
link.type = "button";
|
|
214
|
+
link.textContent = "Return to Maytes";
|
|
215
|
+
link.addEventListener("click", () => {
|
|
216
|
+
if (state.popupWindow !== null && !state.popupWindow.closed) {
|
|
217
|
+
try {
|
|
218
|
+
state.popupWindow.focus();
|
|
219
|
+
} catch (err) {
|
|
220
|
+
if (!isSecurityError(err)) throw err;
|
|
221
|
+
}
|
|
181
222
|
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
223
|
+
});
|
|
224
|
+
content.appendChild(link);
|
|
225
|
+
}
|
|
185
226
|
dialog.appendChild(content);
|
|
186
227
|
dialog.addEventListener("cancel", (event) => {
|
|
187
228
|
event.preventDefault();
|
|
@@ -193,11 +234,11 @@ function buildOverlay(host, state) {
|
|
|
193
234
|
function applyNativeBackdropFallback(dialog) {
|
|
194
235
|
dialog.style.background = NATIVE_BACKDROP_FALLBACK;
|
|
195
236
|
}
|
|
196
|
-
function showOverlay(state) {
|
|
237
|
+
function showOverlay(state, variant = "popup") {
|
|
197
238
|
if (state.overlayEl !== null) return;
|
|
198
239
|
const host = resolveOverlayHost();
|
|
199
240
|
ensureOverlayStylesInTopDocument(host);
|
|
200
|
-
const dialog = buildOverlay(host, state);
|
|
241
|
+
const dialog = buildOverlay(host, state, variant);
|
|
201
242
|
state.overlayEl = dialog;
|
|
202
243
|
host.body.appendChild(dialog);
|
|
203
244
|
if (host !== document) detachOverlayWhenThisPageHides(state);
|
|
@@ -316,7 +357,7 @@ function makeRedirect(environment, baseUrlOverride) {
|
|
|
316
357
|
}
|
|
317
358
|
|
|
318
359
|
// src/version.ts
|
|
319
|
-
var SDK_VERSION = "1.
|
|
360
|
+
var SDK_VERSION = "1.2.0";
|
|
320
361
|
|
|
321
362
|
// src/styles.ts
|
|
322
363
|
var STYLE_ATTR = "data-maytes-checkout-button";
|
|
@@ -329,9 +370,10 @@ var BUTTON_CSS = `
|
|
|
329
370
|
align-items: center;
|
|
330
371
|
justify-content: center;
|
|
331
372
|
gap: 0.57em;
|
|
373
|
+
min-height: var(--maytes-button-height, auto);
|
|
332
374
|
padding: 0.71em 1.29em;
|
|
333
375
|
border: none;
|
|
334
|
-
border-radius: 999px;
|
|
376
|
+
border-radius: var(--maytes-button-radius, 999px);
|
|
335
377
|
background: ${foundation.brand.primary};
|
|
336
378
|
color: ${foundation.text.primaryInverse};
|
|
337
379
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
@@ -351,6 +393,10 @@ var BUTTON_CSS = `
|
|
|
351
393
|
cursor: progress;
|
|
352
394
|
opacity: 0.7;
|
|
353
395
|
}
|
|
396
|
+
.maytes-checkout-button--sized {
|
|
397
|
+
box-sizing: border-box;
|
|
398
|
+
white-space: nowrap;
|
|
399
|
+
}
|
|
354
400
|
.maytes-checkout-button--block {
|
|
355
401
|
display: flex;
|
|
356
402
|
width: 100%;
|
|
@@ -499,24 +545,12 @@ function releaseStyles() {
|
|
|
499
545
|
if (el !== null) el.remove();
|
|
500
546
|
}
|
|
501
547
|
|
|
502
|
-
// src/
|
|
548
|
+
// src/launch.ts
|
|
503
549
|
var POPUP_WIDTH = 500;
|
|
504
550
|
var POPUP_HEIGHT = 800;
|
|
505
551
|
var POPUP_POLL_INTERVAL_MS = 500;
|
|
506
552
|
var MOBILE_MAX_WIDTH = 600;
|
|
507
553
|
var SLOW_NETWORK_DELAY_MS = 8e3;
|
|
508
|
-
function buildLogo() {
|
|
509
|
-
const svg = buildMaytesLogo("1em", "currentColor");
|
|
510
|
-
svg.setAttribute("class", "maytes-checkout-button__logo");
|
|
511
|
-
svg.setAttribute("aria-hidden", "true");
|
|
512
|
-
return svg;
|
|
513
|
-
}
|
|
514
|
-
function buildSpinner() {
|
|
515
|
-
const spinner = document.createElement("span");
|
|
516
|
-
spinner.className = "maytes-checkout-button__spinner";
|
|
517
|
-
spinner.setAttribute("aria-hidden", "true");
|
|
518
|
-
return spinner;
|
|
519
|
-
}
|
|
520
554
|
function isValidCheckoutResult(value) {
|
|
521
555
|
if (typeof value !== "object" || value === null) return false;
|
|
522
556
|
const obj = value;
|
|
@@ -615,6 +649,148 @@ function paintPopupLoadingScreen(popup, cspNonce) {
|
|
|
615
649
|
slow.hidden = false;
|
|
616
650
|
}, SLOW_NETWORK_DELAY_MS);
|
|
617
651
|
}
|
|
652
|
+
function eventDetail(state, source) {
|
|
653
|
+
return { instanceId: state.instanceId, source };
|
|
654
|
+
}
|
|
655
|
+
function dispatchFailed(origin, reason, cause) {
|
|
656
|
+
const detail = cause === void 0 ? { ...origin, reason } : { ...origin, reason, cause };
|
|
657
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-failed", { detail }));
|
|
658
|
+
}
|
|
659
|
+
function dispatchRedirected(origin, url, target) {
|
|
660
|
+
const detail = { ...origin, url, target };
|
|
661
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-redirected", { detail }));
|
|
662
|
+
}
|
|
663
|
+
function assertLaunchMode(mode, caller = "renderButton") {
|
|
664
|
+
if (mode !== "redirect" && mode !== "popup") {
|
|
665
|
+
throw new MaytesError(MaytesErrorCode.Config, `${caller}({ mode }) must be 'redirect' or 'popup'`);
|
|
666
|
+
}
|
|
667
|
+
return mode;
|
|
668
|
+
}
|
|
669
|
+
function assertRedirectOverlay(value, caller = "renderButton") {
|
|
670
|
+
if (value === void 0) return false;
|
|
671
|
+
if (typeof value !== "boolean") {
|
|
672
|
+
throw new MaytesError(MaytesErrorCode.Config, `${caller}({ redirectOverlay }) must be true or false`);
|
|
673
|
+
}
|
|
674
|
+
return value;
|
|
675
|
+
}
|
|
676
|
+
function setInstanceBusy(state, busy) {
|
|
677
|
+
state.busy = busy;
|
|
678
|
+
for (const view of [...state.busyViews]) view(busy);
|
|
679
|
+
}
|
|
680
|
+
function endLaunch(state) {
|
|
681
|
+
hideOverlay(state);
|
|
682
|
+
setInstanceBusy(state, false);
|
|
683
|
+
}
|
|
684
|
+
function closeOrphanPopup(state, popup) {
|
|
685
|
+
closePopupWindow(popup);
|
|
686
|
+
stopPopupPoll(state);
|
|
687
|
+
if (state.popupWindow === popup) state.popupWindow = null;
|
|
688
|
+
endLaunch(state);
|
|
689
|
+
}
|
|
690
|
+
function clearOverlayOnRestore(state) {
|
|
691
|
+
if (state.restoreDetach !== null) return;
|
|
692
|
+
const onPageShow = (event) => {
|
|
693
|
+
if (!event.persisted) return;
|
|
694
|
+
detach();
|
|
695
|
+
endLaunch(state);
|
|
696
|
+
};
|
|
697
|
+
const detach = () => {
|
|
698
|
+
window.removeEventListener("pageshow", onPageShow);
|
|
699
|
+
state.restoreDetach = null;
|
|
700
|
+
};
|
|
701
|
+
window.addEventListener("pageshow", onPageShow);
|
|
702
|
+
state.restoreDetach = detach;
|
|
703
|
+
}
|
|
704
|
+
function navigateAway(state, origin, url, keepOverlay) {
|
|
705
|
+
const navigation = navigateTopLevel(url, false);
|
|
706
|
+
if (keepOverlay && (navigation.target === "self" || navigation.target === "top")) {
|
|
707
|
+
setInstanceBusy(state, false);
|
|
708
|
+
clearOverlayOnRestore(state);
|
|
709
|
+
} else {
|
|
710
|
+
endLaunch(state);
|
|
711
|
+
}
|
|
712
|
+
if (navigation.target === null) {
|
|
713
|
+
console.error("[maytes/checkout-button] could not leave the embedding frame:", navigation.cause);
|
|
714
|
+
dispatchFailed(origin, "navigation-blocked", navigation.cause);
|
|
715
|
+
return { outcome: "failed", reason: "navigation-blocked" };
|
|
716
|
+
}
|
|
717
|
+
dispatchRedirected(origin, url, navigation.target);
|
|
718
|
+
return { outcome: "redirected", target: navigation.target };
|
|
719
|
+
}
|
|
720
|
+
function openPopupLoader(state, origin) {
|
|
721
|
+
const popup = openBlankPopup(state);
|
|
722
|
+
if (popup === null) return null;
|
|
723
|
+
state.popupWindow = popup;
|
|
724
|
+
state.popupNavigated = false;
|
|
725
|
+
refocusPopup(popup);
|
|
726
|
+
paintPopupLoadingScreen(popup, state.config.cspNonce);
|
|
727
|
+
showOverlay(state);
|
|
728
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-opened", { detail: origin }));
|
|
729
|
+
startPopupPoll(state, () => {
|
|
730
|
+
endLaunch(state);
|
|
731
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-closed", { detail: origin }));
|
|
732
|
+
});
|
|
733
|
+
return popup;
|
|
734
|
+
}
|
|
735
|
+
async function launchCheckout(state, mode, source, redirectOverlay = false) {
|
|
736
|
+
if (state.busy || state.destroyed) return { outcome: "ignored" };
|
|
737
|
+
setInstanceBusy(state, true);
|
|
738
|
+
const origin = eventDetail(state, source);
|
|
739
|
+
const shouldUsePopup = mode === "popup" && !isMobileViewport();
|
|
740
|
+
const popup = shouldUsePopup ? openPopupLoader(state, origin) : null;
|
|
741
|
+
if (popup === null && redirectOverlay) showOverlay(state, "redirect");
|
|
742
|
+
try {
|
|
743
|
+
const result = await state.config.createCheckout();
|
|
744
|
+
if (state.destroyed || popup !== null && popup.closed) {
|
|
745
|
+
closePopupWindow(popup);
|
|
746
|
+
return { outcome: "closed" };
|
|
747
|
+
}
|
|
748
|
+
if (!isValidCheckoutResult(result)) {
|
|
749
|
+
closeOrphanPopup(state, popup);
|
|
750
|
+
const shapeError = new MaytesError(
|
|
751
|
+
MaytesErrorCode.Config,
|
|
752
|
+
"createCheckout must resolve to { checkoutId: string, checkoutUrl?: http(s) URL }"
|
|
753
|
+
);
|
|
754
|
+
console.error(shapeError);
|
|
755
|
+
dispatchFailed(origin, "invalid-shape", shapeError);
|
|
756
|
+
return { outcome: "failed", reason: "invalid-shape" };
|
|
757
|
+
}
|
|
758
|
+
const url = result.checkoutUrl ?? buildCheckoutUrl(
|
|
759
|
+
state.config.environment,
|
|
760
|
+
state.config.baseUrl,
|
|
761
|
+
result.checkoutId
|
|
762
|
+
);
|
|
763
|
+
if (popup !== null) {
|
|
764
|
+
state.popupNavigated = true;
|
|
765
|
+
navigatePopup(popup, url);
|
|
766
|
+
return { outcome: "popup" };
|
|
767
|
+
}
|
|
768
|
+
if (shouldUsePopup) {
|
|
769
|
+
console.warn("[maytes/checkout-button] popup was blocked; redirecting instead");
|
|
770
|
+
}
|
|
771
|
+
return navigateAway(state, origin, url, redirectOverlay);
|
|
772
|
+
} catch (err) {
|
|
773
|
+
if (popup !== null && popup.closed) return { outcome: "closed" };
|
|
774
|
+
closeOrphanPopup(state, popup);
|
|
775
|
+
console.error("[maytes/checkout-button] createCheckout failed:", err);
|
|
776
|
+
dispatchFailed(origin, "create-checkout-rejected", err);
|
|
777
|
+
return { outcome: "failed", reason: "create-checkout-rejected" };
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/button.ts
|
|
782
|
+
function buildLogo() {
|
|
783
|
+
const svg = buildMaytesLogo("1em", "currentColor");
|
|
784
|
+
svg.setAttribute("class", "maytes-checkout-button__logo");
|
|
785
|
+
svg.setAttribute("aria-hidden", "true");
|
|
786
|
+
return svg;
|
|
787
|
+
}
|
|
788
|
+
function buildSpinner() {
|
|
789
|
+
const spinner = document.createElement("span");
|
|
790
|
+
spinner.className = "maytes-checkout-button__spinner";
|
|
791
|
+
spinner.setAttribute("aria-hidden", "true");
|
|
792
|
+
return spinner;
|
|
793
|
+
}
|
|
618
794
|
function renderButton(state, container, options = {}) {
|
|
619
795
|
if (state.destroyed) {
|
|
620
796
|
throw new MaytesError(
|
|
@@ -625,120 +801,47 @@ function renderButton(state, container, options = {}) {
|
|
|
625
801
|
if (!(container instanceof HTMLElement)) {
|
|
626
802
|
throw new MaytesError(MaytesErrorCode.Config, "renderButton(container) requires an HTMLElement");
|
|
627
803
|
}
|
|
804
|
+
const appearance = assertAppearance(options);
|
|
805
|
+
const redirectOverlay = assertRedirectOverlay(options.redirectOverlay);
|
|
628
806
|
ensureStylesInjected(state.config.cspNonce);
|
|
629
807
|
const label = options.label ?? "Split with";
|
|
630
808
|
const block = options.block === true;
|
|
631
|
-
const mode = options.mode ?? "popup";
|
|
632
|
-
if (mode !== "redirect" && mode !== "popup") {
|
|
633
|
-
throw new MaytesError(
|
|
634
|
-
MaytesErrorCode.Config,
|
|
635
|
-
"renderButton({ mode }) must be 'redirect' or 'popup'"
|
|
636
|
-
);
|
|
637
|
-
}
|
|
809
|
+
const mode = assertLaunchMode(options.mode ?? "popup");
|
|
638
810
|
const button = document.createElement("button");
|
|
639
811
|
button.type = "button";
|
|
640
812
|
button.className = block ? "maytes-checkout-button maytes-checkout-button--block" : "maytes-checkout-button";
|
|
813
|
+
applyAppearance(button, appearance);
|
|
641
814
|
button.setAttribute("aria-label", `${label} Maytes`);
|
|
642
815
|
const labelNode = document.createTextNode(`${label} `);
|
|
643
|
-
|
|
816
|
+
let icon = buildLogo();
|
|
644
817
|
button.appendChild(labelNode);
|
|
645
|
-
button.appendChild(
|
|
646
|
-
const
|
|
647
|
-
state.busy = busy;
|
|
818
|
+
button.appendChild(icon);
|
|
819
|
+
const showBusy = (busy) => {
|
|
648
820
|
if (busy) {
|
|
649
821
|
button.setAttribute("aria-disabled", "true");
|
|
650
822
|
button.setAttribute("aria-busy", "true");
|
|
651
|
-
|
|
823
|
+
const spinner = buildSpinner();
|
|
824
|
+
icon.replaceWith(spinner);
|
|
825
|
+
icon = spinner;
|
|
652
826
|
} else {
|
|
653
827
|
button.removeAttribute("aria-disabled");
|
|
654
828
|
button.removeAttribute("aria-busy");
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
};
|
|
659
|
-
const teardownActiveCheckout = () => {
|
|
660
|
-
hideOverlay(state);
|
|
661
|
-
setBusy(false);
|
|
662
|
-
};
|
|
663
|
-
const onPopupClosed = () => {
|
|
664
|
-
teardownActiveCheckout();
|
|
665
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-closed"));
|
|
666
|
-
};
|
|
667
|
-
const dispatchFailed = (reason, cause) => {
|
|
668
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-failed", {
|
|
669
|
-
detail: cause === void 0 ? { reason } : { reason, cause }
|
|
670
|
-
}));
|
|
671
|
-
};
|
|
672
|
-
const dispatchRedirected = (url, target) => {
|
|
673
|
-
const detail = { url, target };
|
|
674
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-redirected", { detail }));
|
|
675
|
-
};
|
|
676
|
-
const navigateAway = (url) => {
|
|
677
|
-
const outcome = navigateTopLevel(url, false);
|
|
678
|
-
teardownActiveCheckout();
|
|
679
|
-
if (outcome.target === null) {
|
|
680
|
-
console.error("[maytes/checkout-button] could not leave the embedding frame:", outcome.cause);
|
|
681
|
-
dispatchFailed("navigation-blocked", outcome.cause);
|
|
682
|
-
return;
|
|
829
|
+
const nextLogo = buildLogo();
|
|
830
|
+
icon.replaceWith(nextLogo);
|
|
831
|
+
icon = nextLogo;
|
|
683
832
|
}
|
|
684
|
-
dispatchRedirected(url, outcome.target);
|
|
685
833
|
};
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
834
|
+
let launchedHere = false;
|
|
835
|
+
const followOwnLaunch = (busy) => {
|
|
836
|
+
if (!launchedHere) return;
|
|
837
|
+
showBusy(busy);
|
|
838
|
+
if (!busy) launchedHere = false;
|
|
691
839
|
};
|
|
692
|
-
|
|
840
|
+
state.busyViews.add(followOwnLaunch);
|
|
841
|
+
const handleClick = () => {
|
|
693
842
|
if (state.busy || state.destroyed) return;
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const popup = shouldUsePopup ? openBlankPopup(state) : null;
|
|
697
|
-
if (popup !== null) {
|
|
698
|
-
state.popupWindow = popup;
|
|
699
|
-
state.popupNavigated = false;
|
|
700
|
-
refocusPopup(popup);
|
|
701
|
-
paintPopupLoadingScreen(popup, state.config.cspNonce);
|
|
702
|
-
showOverlay(state);
|
|
703
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-opened"));
|
|
704
|
-
startPopupPoll(state, onPopupClosed);
|
|
705
|
-
}
|
|
706
|
-
try {
|
|
707
|
-
const result = await state.config.createCheckout();
|
|
708
|
-
if (state.destroyed || popup !== null && popup.closed) {
|
|
709
|
-
closePopupWindow(popup);
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
if (!isValidCheckoutResult(result)) {
|
|
713
|
-
closeOrphanPopup(popup);
|
|
714
|
-
const shapeError = new MaytesError(
|
|
715
|
-
MaytesErrorCode.Config,
|
|
716
|
-
"createCheckout must resolve to { checkoutId: string, checkoutUrl?: http(s) URL }"
|
|
717
|
-
);
|
|
718
|
-
console.error(shapeError);
|
|
719
|
-
dispatchFailed("invalid-shape", shapeError);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
const url = result.checkoutUrl ?? buildCheckoutUrl(
|
|
723
|
-
state.config.environment,
|
|
724
|
-
state.config.baseUrl,
|
|
725
|
-
result.checkoutId
|
|
726
|
-
);
|
|
727
|
-
if (popup !== null) {
|
|
728
|
-
state.popupNavigated = true;
|
|
729
|
-
navigatePopup(popup, url);
|
|
730
|
-
} else {
|
|
731
|
-
if (shouldUsePopup) {
|
|
732
|
-
console.warn("[maytes/checkout-button] popup was blocked; redirecting instead");
|
|
733
|
-
}
|
|
734
|
-
navigateAway(url);
|
|
735
|
-
}
|
|
736
|
-
} catch (err) {
|
|
737
|
-
if (popup !== null && popup.closed) return;
|
|
738
|
-
closeOrphanPopup(popup);
|
|
739
|
-
console.error("[maytes/checkout-button] createCheckout failed:", err);
|
|
740
|
-
dispatchFailed("create-checkout-rejected", err);
|
|
741
|
-
}
|
|
843
|
+
launchedHere = true;
|
|
844
|
+
void launchCheckout(state, mode, "button", redirectOverlay);
|
|
742
845
|
};
|
|
743
846
|
button.addEventListener("click", handleClick);
|
|
744
847
|
container.appendChild(button);
|
|
@@ -748,6 +851,7 @@ function renderButton(state, container, options = {}) {
|
|
|
748
851
|
cleaned = true;
|
|
749
852
|
button.removeEventListener("click", handleClick);
|
|
750
853
|
button.remove();
|
|
854
|
+
state.busyViews.delete(followOwnLaunch);
|
|
751
855
|
state.teardowns.delete(teardown);
|
|
752
856
|
releaseStyles();
|
|
753
857
|
};
|
|
@@ -756,18 +860,18 @@ function renderButton(state, container, options = {}) {
|
|
|
756
860
|
}
|
|
757
861
|
|
|
758
862
|
// src/state.ts
|
|
759
|
-
function
|
|
863
|
+
function randomSuffix() {
|
|
760
864
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
761
865
|
}
|
|
762
|
-
function
|
|
866
|
+
function randomId() {
|
|
763
867
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
764
868
|
try {
|
|
765
|
-
return
|
|
869
|
+
return crypto.randomUUID();
|
|
766
870
|
} catch {
|
|
767
|
-
return
|
|
871
|
+
return randomSuffix();
|
|
768
872
|
}
|
|
769
873
|
}
|
|
770
|
-
return
|
|
874
|
+
return randomSuffix();
|
|
771
875
|
}
|
|
772
876
|
function createInstanceState(options, internal) {
|
|
773
877
|
const config = {
|
|
@@ -779,11 +883,14 @@ function createInstanceState(options, internal) {
|
|
|
779
883
|
return {
|
|
780
884
|
config,
|
|
781
885
|
busy: false,
|
|
886
|
+
busyViews: /* @__PURE__ */ new Set(),
|
|
887
|
+
instanceId: randomId(),
|
|
782
888
|
overlayEl: null,
|
|
783
889
|
overlayDetach: null,
|
|
890
|
+
restoreDetach: null,
|
|
784
891
|
popupWindow: null,
|
|
785
892
|
popupNavigated: false,
|
|
786
|
-
popupName:
|
|
893
|
+
popupName: `maytes-checkout-${randomId()}`,
|
|
787
894
|
popupPollHandle: null,
|
|
788
895
|
destroyed: false,
|
|
789
896
|
teardowns: /* @__PURE__ */ new Set()
|
|
@@ -821,9 +928,27 @@ var Maytes = (options, internal) => {
|
|
|
821
928
|
state.config.baseUrl
|
|
822
929
|
);
|
|
823
930
|
const sdk = {
|
|
931
|
+
instanceId: state.instanceId,
|
|
824
932
|
renderButton(container, opts) {
|
|
825
933
|
return renderButton(state, container, opts);
|
|
826
934
|
},
|
|
935
|
+
openCheckout(opts) {
|
|
936
|
+
if (state.destroyed) {
|
|
937
|
+
throw new MaytesError(MaytesErrorCode.Config, "openCheckout() called on a destroyed Maytes instance");
|
|
938
|
+
}
|
|
939
|
+
const mode = assertLaunchMode(opts?.mode ?? "popup", "openCheckout");
|
|
940
|
+
const redirectOverlay = assertRedirectOverlay(opts?.redirectOverlay, "openCheckout");
|
|
941
|
+
return launchCheckout(state, mode, "api", redirectOverlay);
|
|
942
|
+
},
|
|
943
|
+
onBusyChange(listener) {
|
|
944
|
+
if (state.destroyed) {
|
|
945
|
+
throw new MaytesError(MaytesErrorCode.Config, "onBusyChange() called on a destroyed Maytes instance");
|
|
946
|
+
}
|
|
947
|
+
state.busyViews.add(listener);
|
|
948
|
+
return () => {
|
|
949
|
+
state.busyViews.delete(listener);
|
|
950
|
+
};
|
|
951
|
+
},
|
|
827
952
|
redirectToCheckout(opts) {
|
|
828
953
|
redirectToCheckout(opts);
|
|
829
954
|
},
|
|
@@ -834,6 +959,7 @@ var Maytes = (options, internal) => {
|
|
|
834
959
|
if (state.destroyed) return;
|
|
835
960
|
state.destroyed = true;
|
|
836
961
|
stopPopupPoll(state);
|
|
962
|
+
state.restoreDetach?.();
|
|
837
963
|
hideOverlay(state);
|
|
838
964
|
if (!state.popupNavigated) closePopupWindow(state.popupWindow);
|
|
839
965
|
state.popupWindow = null;
|