@maytes/checkout-button 1.1.0 → 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 +79 -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 +280 -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.f96c49ee.mjs → checkout-button.d6e444ee.mjs} +280 -145
- package/dist/checkout-button.d6e444ee.mjs.map +1 -0
- package/dist/{checkout-button.178d8394.cjs → checkout-button.d7bc9aea.cjs} +280 -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 +280 -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.178d8394.cjs.map +0 -1
- package/dist/checkout-button.811263f9.js +0 -151
- package/dist/checkout-button.811263f9.js.map +0 -1
- package/dist/checkout-button.f96c49ee.mjs.map +0 -1
package/dist/checkout-button.mjs
CHANGED
|
@@ -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,13 +234,14 @@ 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);
|
|
244
|
+
if (host !== document) detachOverlayWhenThisPageHides(state);
|
|
203
245
|
if (typeof dialog.showModal === "function") {
|
|
204
246
|
try {
|
|
205
247
|
dialog.showModal();
|
|
@@ -213,8 +255,15 @@ function showOverlay(state) {
|
|
|
213
255
|
applyNativeBackdropFallback(dialog);
|
|
214
256
|
}
|
|
215
257
|
}
|
|
258
|
+
function detachOverlayWhenThisPageHides(state) {
|
|
259
|
+
const onPageHide = () => hideOverlay(state);
|
|
260
|
+
window.addEventListener("pagehide", onPageHide);
|
|
261
|
+
state.overlayDetach = () => window.removeEventListener("pagehide", onPageHide);
|
|
262
|
+
}
|
|
216
263
|
function hideOverlay(state) {
|
|
217
264
|
if (state.overlayEl === null) return;
|
|
265
|
+
state.overlayDetach?.();
|
|
266
|
+
state.overlayDetach = null;
|
|
218
267
|
const dialog = state.overlayEl;
|
|
219
268
|
const host = dialog.ownerDocument;
|
|
220
269
|
if (typeof dialog.close === "function" && dialog.open) dialog.close();
|
|
@@ -308,7 +357,7 @@ function makeRedirect(environment, baseUrlOverride) {
|
|
|
308
357
|
}
|
|
309
358
|
|
|
310
359
|
// src/version.ts
|
|
311
|
-
var SDK_VERSION = "1.
|
|
360
|
+
var SDK_VERSION = "1.2.0";
|
|
312
361
|
|
|
313
362
|
// src/styles.ts
|
|
314
363
|
var STYLE_ATTR = "data-maytes-checkout-button";
|
|
@@ -321,9 +370,10 @@ var BUTTON_CSS = `
|
|
|
321
370
|
align-items: center;
|
|
322
371
|
justify-content: center;
|
|
323
372
|
gap: 0.57em;
|
|
373
|
+
min-height: var(--maytes-button-height, auto);
|
|
324
374
|
padding: 0.71em 1.29em;
|
|
325
375
|
border: none;
|
|
326
|
-
border-radius: 999px;
|
|
376
|
+
border-radius: var(--maytes-button-radius, 999px);
|
|
327
377
|
background: ${foundation.brand.primary};
|
|
328
378
|
color: ${foundation.text.primaryInverse};
|
|
329
379
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
@@ -343,6 +393,10 @@ var BUTTON_CSS = `
|
|
|
343
393
|
cursor: progress;
|
|
344
394
|
opacity: 0.7;
|
|
345
395
|
}
|
|
396
|
+
.maytes-checkout-button--sized {
|
|
397
|
+
box-sizing: border-box;
|
|
398
|
+
white-space: nowrap;
|
|
399
|
+
}
|
|
346
400
|
.maytes-checkout-button--block {
|
|
347
401
|
display: flex;
|
|
348
402
|
width: 100%;
|
|
@@ -491,24 +545,12 @@ function releaseStyles() {
|
|
|
491
545
|
if (el !== null) el.remove();
|
|
492
546
|
}
|
|
493
547
|
|
|
494
|
-
// src/
|
|
548
|
+
// src/launch.ts
|
|
495
549
|
var POPUP_WIDTH = 500;
|
|
496
550
|
var POPUP_HEIGHT = 800;
|
|
497
551
|
var POPUP_POLL_INTERVAL_MS = 500;
|
|
498
552
|
var MOBILE_MAX_WIDTH = 600;
|
|
499
553
|
var SLOW_NETWORK_DELAY_MS = 8e3;
|
|
500
|
-
function buildLogo() {
|
|
501
|
-
const svg = buildMaytesLogo("1em", "currentColor");
|
|
502
|
-
svg.setAttribute("class", "maytes-checkout-button__logo");
|
|
503
|
-
svg.setAttribute("aria-hidden", "true");
|
|
504
|
-
return svg;
|
|
505
|
-
}
|
|
506
|
-
function buildSpinner() {
|
|
507
|
-
const spinner = document.createElement("span");
|
|
508
|
-
spinner.className = "maytes-checkout-button__spinner";
|
|
509
|
-
spinner.setAttribute("aria-hidden", "true");
|
|
510
|
-
return spinner;
|
|
511
|
-
}
|
|
512
554
|
function isValidCheckoutResult(value) {
|
|
513
555
|
if (typeof value !== "object" || value === null) return false;
|
|
514
556
|
const obj = value;
|
|
@@ -607,6 +649,148 @@ function paintPopupLoadingScreen(popup, cspNonce) {
|
|
|
607
649
|
slow.hidden = false;
|
|
608
650
|
}, SLOW_NETWORK_DELAY_MS);
|
|
609
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
|
+
}
|
|
610
794
|
function renderButton(state, container, options = {}) {
|
|
611
795
|
if (state.destroyed) {
|
|
612
796
|
throw new MaytesError(
|
|
@@ -617,120 +801,47 @@ function renderButton(state, container, options = {}) {
|
|
|
617
801
|
if (!(container instanceof HTMLElement)) {
|
|
618
802
|
throw new MaytesError(MaytesErrorCode.Config, "renderButton(container) requires an HTMLElement");
|
|
619
803
|
}
|
|
804
|
+
const appearance = assertAppearance(options);
|
|
805
|
+
const redirectOverlay = assertRedirectOverlay(options.redirectOverlay);
|
|
620
806
|
ensureStylesInjected(state.config.cspNonce);
|
|
621
807
|
const label = options.label ?? "Split with";
|
|
622
808
|
const block = options.block === true;
|
|
623
|
-
const mode = options.mode ?? "popup";
|
|
624
|
-
if (mode !== "redirect" && mode !== "popup") {
|
|
625
|
-
throw new MaytesError(
|
|
626
|
-
MaytesErrorCode.Config,
|
|
627
|
-
"renderButton({ mode }) must be 'redirect' or 'popup'"
|
|
628
|
-
);
|
|
629
|
-
}
|
|
809
|
+
const mode = assertLaunchMode(options.mode ?? "popup");
|
|
630
810
|
const button = document.createElement("button");
|
|
631
811
|
button.type = "button";
|
|
632
812
|
button.className = block ? "maytes-checkout-button maytes-checkout-button--block" : "maytes-checkout-button";
|
|
813
|
+
applyAppearance(button, appearance);
|
|
633
814
|
button.setAttribute("aria-label", `${label} Maytes`);
|
|
634
815
|
const labelNode = document.createTextNode(`${label} `);
|
|
635
|
-
|
|
816
|
+
let icon = buildLogo();
|
|
636
817
|
button.appendChild(labelNode);
|
|
637
|
-
button.appendChild(
|
|
638
|
-
const
|
|
639
|
-
state.busy = busy;
|
|
818
|
+
button.appendChild(icon);
|
|
819
|
+
const showBusy = (busy) => {
|
|
640
820
|
if (busy) {
|
|
641
821
|
button.setAttribute("aria-disabled", "true");
|
|
642
822
|
button.setAttribute("aria-busy", "true");
|
|
643
|
-
|
|
823
|
+
const spinner = buildSpinner();
|
|
824
|
+
icon.replaceWith(spinner);
|
|
825
|
+
icon = spinner;
|
|
644
826
|
} else {
|
|
645
827
|
button.removeAttribute("aria-disabled");
|
|
646
828
|
button.removeAttribute("aria-busy");
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
};
|
|
651
|
-
const teardownActiveCheckout = () => {
|
|
652
|
-
hideOverlay(state);
|
|
653
|
-
setBusy(false);
|
|
654
|
-
};
|
|
655
|
-
const onPopupClosed = () => {
|
|
656
|
-
teardownActiveCheckout();
|
|
657
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-closed"));
|
|
658
|
-
};
|
|
659
|
-
const dispatchFailed = (reason, cause) => {
|
|
660
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-failed", {
|
|
661
|
-
detail: cause === void 0 ? { reason } : { reason, cause }
|
|
662
|
-
}));
|
|
663
|
-
};
|
|
664
|
-
const dispatchRedirected = (url, target) => {
|
|
665
|
-
const detail = { url, target };
|
|
666
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-redirected", { detail }));
|
|
667
|
-
};
|
|
668
|
-
const navigateAway = (url) => {
|
|
669
|
-
const outcome = navigateTopLevel(url, false);
|
|
670
|
-
teardownActiveCheckout();
|
|
671
|
-
if (outcome.target === null) {
|
|
672
|
-
console.error("[maytes/checkout-button] could not leave the embedding frame:", outcome.cause);
|
|
673
|
-
dispatchFailed("navigation-blocked", outcome.cause);
|
|
674
|
-
return;
|
|
829
|
+
const nextLogo = buildLogo();
|
|
830
|
+
icon.replaceWith(nextLogo);
|
|
831
|
+
icon = nextLogo;
|
|
675
832
|
}
|
|
676
|
-
dispatchRedirected(url, outcome.target);
|
|
677
833
|
};
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
834
|
+
let launchedHere = false;
|
|
835
|
+
const followOwnLaunch = (busy) => {
|
|
836
|
+
if (!launchedHere) return;
|
|
837
|
+
showBusy(busy);
|
|
838
|
+
if (!busy) launchedHere = false;
|
|
683
839
|
};
|
|
684
|
-
|
|
840
|
+
state.busyViews.add(followOwnLaunch);
|
|
841
|
+
const handleClick = () => {
|
|
685
842
|
if (state.busy || state.destroyed) return;
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const popup = shouldUsePopup ? openBlankPopup(state) : null;
|
|
689
|
-
if (popup !== null) {
|
|
690
|
-
state.popupWindow = popup;
|
|
691
|
-
state.popupNavigated = false;
|
|
692
|
-
refocusPopup(popup);
|
|
693
|
-
paintPopupLoadingScreen(popup, state.config.cspNonce);
|
|
694
|
-
showOverlay(state);
|
|
695
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-opened"));
|
|
696
|
-
startPopupPoll(state, onPopupClosed);
|
|
697
|
-
}
|
|
698
|
-
try {
|
|
699
|
-
const result = await state.config.createCheckout();
|
|
700
|
-
if (state.destroyed || popup !== null && popup.closed) {
|
|
701
|
-
closePopupWindow(popup);
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
if (!isValidCheckoutResult(result)) {
|
|
705
|
-
closeOrphanPopup(popup);
|
|
706
|
-
const shapeError = new MaytesError(
|
|
707
|
-
MaytesErrorCode.Config,
|
|
708
|
-
"createCheckout must resolve to { checkoutId: string, checkoutUrl?: http(s) URL }"
|
|
709
|
-
);
|
|
710
|
-
console.error(shapeError);
|
|
711
|
-
dispatchFailed("invalid-shape", shapeError);
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
const url = result.checkoutUrl ?? buildCheckoutUrl(
|
|
715
|
-
state.config.environment,
|
|
716
|
-
state.config.baseUrl,
|
|
717
|
-
result.checkoutId
|
|
718
|
-
);
|
|
719
|
-
if (popup !== null) {
|
|
720
|
-
state.popupNavigated = true;
|
|
721
|
-
navigatePopup(popup, url);
|
|
722
|
-
} else {
|
|
723
|
-
if (shouldUsePopup) {
|
|
724
|
-
console.warn("[maytes/checkout-button] popup was blocked; redirecting instead");
|
|
725
|
-
}
|
|
726
|
-
navigateAway(url);
|
|
727
|
-
}
|
|
728
|
-
} catch (err) {
|
|
729
|
-
if (popup !== null && popup.closed) return;
|
|
730
|
-
closeOrphanPopup(popup);
|
|
731
|
-
console.error("[maytes/checkout-button] createCheckout failed:", err);
|
|
732
|
-
dispatchFailed("create-checkout-rejected", err);
|
|
733
|
-
}
|
|
843
|
+
launchedHere = true;
|
|
844
|
+
void launchCheckout(state, mode, "button", redirectOverlay);
|
|
734
845
|
};
|
|
735
846
|
button.addEventListener("click", handleClick);
|
|
736
847
|
container.appendChild(button);
|
|
@@ -740,6 +851,7 @@ function renderButton(state, container, options = {}) {
|
|
|
740
851
|
cleaned = true;
|
|
741
852
|
button.removeEventListener("click", handleClick);
|
|
742
853
|
button.remove();
|
|
854
|
+
state.busyViews.delete(followOwnLaunch);
|
|
743
855
|
state.teardowns.delete(teardown);
|
|
744
856
|
releaseStyles();
|
|
745
857
|
};
|
|
@@ -748,18 +860,18 @@ function renderButton(state, container, options = {}) {
|
|
|
748
860
|
}
|
|
749
861
|
|
|
750
862
|
// src/state.ts
|
|
751
|
-
function
|
|
863
|
+
function randomSuffix() {
|
|
752
864
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
753
865
|
}
|
|
754
|
-
function
|
|
866
|
+
function randomId() {
|
|
755
867
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
756
868
|
try {
|
|
757
|
-
return
|
|
869
|
+
return crypto.randomUUID();
|
|
758
870
|
} catch {
|
|
759
|
-
return
|
|
871
|
+
return randomSuffix();
|
|
760
872
|
}
|
|
761
873
|
}
|
|
762
|
-
return
|
|
874
|
+
return randomSuffix();
|
|
763
875
|
}
|
|
764
876
|
function createInstanceState(options, internal) {
|
|
765
877
|
const config = {
|
|
@@ -771,10 +883,14 @@ function createInstanceState(options, internal) {
|
|
|
771
883
|
return {
|
|
772
884
|
config,
|
|
773
885
|
busy: false,
|
|
886
|
+
busyViews: /* @__PURE__ */ new Set(),
|
|
887
|
+
instanceId: randomId(),
|
|
774
888
|
overlayEl: null,
|
|
889
|
+
overlayDetach: null,
|
|
890
|
+
restoreDetach: null,
|
|
775
891
|
popupWindow: null,
|
|
776
892
|
popupNavigated: false,
|
|
777
|
-
popupName:
|
|
893
|
+
popupName: `maytes-checkout-${randomId()}`,
|
|
778
894
|
popupPollHandle: null,
|
|
779
895
|
destroyed: false,
|
|
780
896
|
teardowns: /* @__PURE__ */ new Set()
|
|
@@ -812,9 +928,27 @@ var Maytes = (options, internal) => {
|
|
|
812
928
|
state.config.baseUrl
|
|
813
929
|
);
|
|
814
930
|
const sdk = {
|
|
931
|
+
instanceId: state.instanceId,
|
|
815
932
|
renderButton(container, opts) {
|
|
816
933
|
return renderButton(state, container, opts);
|
|
817
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
|
+
},
|
|
818
952
|
redirectToCheckout(opts) {
|
|
819
953
|
redirectToCheckout(opts);
|
|
820
954
|
},
|
|
@@ -825,6 +959,7 @@ var Maytes = (options, internal) => {
|
|
|
825
959
|
if (state.destroyed) return;
|
|
826
960
|
state.destroyed = true;
|
|
827
961
|
stopPopupPoll(state);
|
|
962
|
+
state.restoreDetach?.();
|
|
828
963
|
hideOverlay(state);
|
|
829
964
|
if (!state.popupNavigated) closePopupWindow(state.popupWindow);
|
|
830
965
|
state.popupWindow = null;
|