@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
|
@@ -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,13 +263,14 @@ 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);
|
|
273
|
+
if (host !== document) detachOverlayWhenThisPageHides(state);
|
|
232
274
|
if (typeof dialog.showModal === "function") {
|
|
233
275
|
try {
|
|
234
276
|
dialog.showModal();
|
|
@@ -242,8 +284,15 @@ function showOverlay(state) {
|
|
|
242
284
|
applyNativeBackdropFallback(dialog);
|
|
243
285
|
}
|
|
244
286
|
}
|
|
287
|
+
function detachOverlayWhenThisPageHides(state) {
|
|
288
|
+
const onPageHide = () => hideOverlay(state);
|
|
289
|
+
window.addEventListener("pagehide", onPageHide);
|
|
290
|
+
state.overlayDetach = () => window.removeEventListener("pagehide", onPageHide);
|
|
291
|
+
}
|
|
245
292
|
function hideOverlay(state) {
|
|
246
293
|
if (state.overlayEl === null) return;
|
|
294
|
+
state.overlayDetach?.();
|
|
295
|
+
state.overlayDetach = null;
|
|
247
296
|
const dialog = state.overlayEl;
|
|
248
297
|
const host = dialog.ownerDocument;
|
|
249
298
|
if (typeof dialog.close === "function" && dialog.open) dialog.close();
|
|
@@ -337,7 +386,7 @@ function makeRedirect(environment, baseUrlOverride) {
|
|
|
337
386
|
}
|
|
338
387
|
|
|
339
388
|
// src/version.ts
|
|
340
|
-
var SDK_VERSION = "1.
|
|
389
|
+
var SDK_VERSION = "1.2.0";
|
|
341
390
|
|
|
342
391
|
// src/styles.ts
|
|
343
392
|
var STYLE_ATTR = "data-maytes-checkout-button";
|
|
@@ -350,9 +399,10 @@ var BUTTON_CSS = `
|
|
|
350
399
|
align-items: center;
|
|
351
400
|
justify-content: center;
|
|
352
401
|
gap: 0.57em;
|
|
402
|
+
min-height: var(--maytes-button-height, auto);
|
|
353
403
|
padding: 0.71em 1.29em;
|
|
354
404
|
border: none;
|
|
355
|
-
border-radius: 999px;
|
|
405
|
+
border-radius: var(--maytes-button-radius, 999px);
|
|
356
406
|
background: ${foundation.brand.primary};
|
|
357
407
|
color: ${foundation.text.primaryInverse};
|
|
358
408
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
@@ -372,6 +422,10 @@ var BUTTON_CSS = `
|
|
|
372
422
|
cursor: progress;
|
|
373
423
|
opacity: 0.7;
|
|
374
424
|
}
|
|
425
|
+
.maytes-checkout-button--sized {
|
|
426
|
+
box-sizing: border-box;
|
|
427
|
+
white-space: nowrap;
|
|
428
|
+
}
|
|
375
429
|
.maytes-checkout-button--block {
|
|
376
430
|
display: flex;
|
|
377
431
|
width: 100%;
|
|
@@ -520,24 +574,12 @@ function releaseStyles() {
|
|
|
520
574
|
if (el !== null) el.remove();
|
|
521
575
|
}
|
|
522
576
|
|
|
523
|
-
// src/
|
|
577
|
+
// src/launch.ts
|
|
524
578
|
var POPUP_WIDTH = 500;
|
|
525
579
|
var POPUP_HEIGHT = 800;
|
|
526
580
|
var POPUP_POLL_INTERVAL_MS = 500;
|
|
527
581
|
var MOBILE_MAX_WIDTH = 600;
|
|
528
582
|
var SLOW_NETWORK_DELAY_MS = 8e3;
|
|
529
|
-
function buildLogo() {
|
|
530
|
-
const svg = buildMaytesLogo("1em", "currentColor");
|
|
531
|
-
svg.setAttribute("class", "maytes-checkout-button__logo");
|
|
532
|
-
svg.setAttribute("aria-hidden", "true");
|
|
533
|
-
return svg;
|
|
534
|
-
}
|
|
535
|
-
function buildSpinner() {
|
|
536
|
-
const spinner = document.createElement("span");
|
|
537
|
-
spinner.className = "maytes-checkout-button__spinner";
|
|
538
|
-
spinner.setAttribute("aria-hidden", "true");
|
|
539
|
-
return spinner;
|
|
540
|
-
}
|
|
541
583
|
function isValidCheckoutResult(value) {
|
|
542
584
|
if (typeof value !== "object" || value === null) return false;
|
|
543
585
|
const obj = value;
|
|
@@ -636,6 +678,148 @@ function paintPopupLoadingScreen(popup, cspNonce) {
|
|
|
636
678
|
slow.hidden = false;
|
|
637
679
|
}, SLOW_NETWORK_DELAY_MS);
|
|
638
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
|
+
}
|
|
639
823
|
function renderButton(state, container, options = {}) {
|
|
640
824
|
if (state.destroyed) {
|
|
641
825
|
throw new MaytesError(
|
|
@@ -646,120 +830,47 @@ function renderButton(state, container, options = {}) {
|
|
|
646
830
|
if (!(container instanceof HTMLElement)) {
|
|
647
831
|
throw new MaytesError(MaytesErrorCode.Config, "renderButton(container) requires an HTMLElement");
|
|
648
832
|
}
|
|
833
|
+
const appearance = assertAppearance(options);
|
|
834
|
+
const redirectOverlay = assertRedirectOverlay(options.redirectOverlay);
|
|
649
835
|
ensureStylesInjected(state.config.cspNonce);
|
|
650
836
|
const label = options.label ?? "Split with";
|
|
651
837
|
const block = options.block === true;
|
|
652
|
-
const mode = options.mode ?? "popup";
|
|
653
|
-
if (mode !== "redirect" && mode !== "popup") {
|
|
654
|
-
throw new MaytesError(
|
|
655
|
-
MaytesErrorCode.Config,
|
|
656
|
-
"renderButton({ mode }) must be 'redirect' or 'popup'"
|
|
657
|
-
);
|
|
658
|
-
}
|
|
838
|
+
const mode = assertLaunchMode(options.mode ?? "popup");
|
|
659
839
|
const button = document.createElement("button");
|
|
660
840
|
button.type = "button";
|
|
661
841
|
button.className = block ? "maytes-checkout-button maytes-checkout-button--block" : "maytes-checkout-button";
|
|
842
|
+
applyAppearance(button, appearance);
|
|
662
843
|
button.setAttribute("aria-label", `${label} Maytes`);
|
|
663
844
|
const labelNode = document.createTextNode(`${label} `);
|
|
664
|
-
|
|
845
|
+
let icon = buildLogo();
|
|
665
846
|
button.appendChild(labelNode);
|
|
666
|
-
button.appendChild(
|
|
667
|
-
const
|
|
668
|
-
state.busy = busy;
|
|
847
|
+
button.appendChild(icon);
|
|
848
|
+
const showBusy = (busy) => {
|
|
669
849
|
if (busy) {
|
|
670
850
|
button.setAttribute("aria-disabled", "true");
|
|
671
851
|
button.setAttribute("aria-busy", "true");
|
|
672
|
-
|
|
852
|
+
const spinner = buildSpinner();
|
|
853
|
+
icon.replaceWith(spinner);
|
|
854
|
+
icon = spinner;
|
|
673
855
|
} else {
|
|
674
856
|
button.removeAttribute("aria-disabled");
|
|
675
857
|
button.removeAttribute("aria-busy");
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
};
|
|
680
|
-
const teardownActiveCheckout = () => {
|
|
681
|
-
hideOverlay(state);
|
|
682
|
-
setBusy(false);
|
|
683
|
-
};
|
|
684
|
-
const onPopupClosed = () => {
|
|
685
|
-
teardownActiveCheckout();
|
|
686
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-closed"));
|
|
687
|
-
};
|
|
688
|
-
const dispatchFailed = (reason, cause) => {
|
|
689
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-failed", {
|
|
690
|
-
detail: cause === void 0 ? { reason } : { reason, cause }
|
|
691
|
-
}));
|
|
692
|
-
};
|
|
693
|
-
const dispatchRedirected = (url, target) => {
|
|
694
|
-
const detail = { url, target };
|
|
695
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-redirected", { detail }));
|
|
696
|
-
};
|
|
697
|
-
const navigateAway = (url) => {
|
|
698
|
-
const outcome = navigateTopLevel(url, false);
|
|
699
|
-
teardownActiveCheckout();
|
|
700
|
-
if (outcome.target === null) {
|
|
701
|
-
console.error("[maytes/checkout-button] could not leave the embedding frame:", outcome.cause);
|
|
702
|
-
dispatchFailed("navigation-blocked", outcome.cause);
|
|
703
|
-
return;
|
|
858
|
+
const nextLogo = buildLogo();
|
|
859
|
+
icon.replaceWith(nextLogo);
|
|
860
|
+
icon = nextLogo;
|
|
704
861
|
}
|
|
705
|
-
dispatchRedirected(url, outcome.target);
|
|
706
862
|
};
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
863
|
+
let launchedHere = false;
|
|
864
|
+
const followOwnLaunch = (busy) => {
|
|
865
|
+
if (!launchedHere) return;
|
|
866
|
+
showBusy(busy);
|
|
867
|
+
if (!busy) launchedHere = false;
|
|
712
868
|
};
|
|
713
|
-
|
|
869
|
+
state.busyViews.add(followOwnLaunch);
|
|
870
|
+
const handleClick = () => {
|
|
714
871
|
if (state.busy || state.destroyed) return;
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
const popup = shouldUsePopup ? openBlankPopup(state) : null;
|
|
718
|
-
if (popup !== null) {
|
|
719
|
-
state.popupWindow = popup;
|
|
720
|
-
state.popupNavigated = false;
|
|
721
|
-
refocusPopup(popup);
|
|
722
|
-
paintPopupLoadingScreen(popup, state.config.cspNonce);
|
|
723
|
-
showOverlay(state);
|
|
724
|
-
document.dispatchEvent(new CustomEvent("maytes:checkout-opened"));
|
|
725
|
-
startPopupPoll(state, onPopupClosed);
|
|
726
|
-
}
|
|
727
|
-
try {
|
|
728
|
-
const result = await state.config.createCheckout();
|
|
729
|
-
if (state.destroyed || popup !== null && popup.closed) {
|
|
730
|
-
closePopupWindow(popup);
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
if (!isValidCheckoutResult(result)) {
|
|
734
|
-
closeOrphanPopup(popup);
|
|
735
|
-
const shapeError = new MaytesError(
|
|
736
|
-
MaytesErrorCode.Config,
|
|
737
|
-
"createCheckout must resolve to { checkoutId: string, checkoutUrl?: http(s) URL }"
|
|
738
|
-
);
|
|
739
|
-
console.error(shapeError);
|
|
740
|
-
dispatchFailed("invalid-shape", shapeError);
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
const url = result.checkoutUrl ?? buildCheckoutUrl(
|
|
744
|
-
state.config.environment,
|
|
745
|
-
state.config.baseUrl,
|
|
746
|
-
result.checkoutId
|
|
747
|
-
);
|
|
748
|
-
if (popup !== null) {
|
|
749
|
-
state.popupNavigated = true;
|
|
750
|
-
navigatePopup(popup, url);
|
|
751
|
-
} else {
|
|
752
|
-
if (shouldUsePopup) {
|
|
753
|
-
console.warn("[maytes/checkout-button] popup was blocked; redirecting instead");
|
|
754
|
-
}
|
|
755
|
-
navigateAway(url);
|
|
756
|
-
}
|
|
757
|
-
} catch (err) {
|
|
758
|
-
if (popup !== null && popup.closed) return;
|
|
759
|
-
closeOrphanPopup(popup);
|
|
760
|
-
console.error("[maytes/checkout-button] createCheckout failed:", err);
|
|
761
|
-
dispatchFailed("create-checkout-rejected", err);
|
|
762
|
-
}
|
|
872
|
+
launchedHere = true;
|
|
873
|
+
void launchCheckout(state, mode, "button", redirectOverlay);
|
|
763
874
|
};
|
|
764
875
|
button.addEventListener("click", handleClick);
|
|
765
876
|
container.appendChild(button);
|
|
@@ -769,6 +880,7 @@ function renderButton(state, container, options = {}) {
|
|
|
769
880
|
cleaned = true;
|
|
770
881
|
button.removeEventListener("click", handleClick);
|
|
771
882
|
button.remove();
|
|
883
|
+
state.busyViews.delete(followOwnLaunch);
|
|
772
884
|
state.teardowns.delete(teardown);
|
|
773
885
|
releaseStyles();
|
|
774
886
|
};
|
|
@@ -777,18 +889,18 @@ function renderButton(state, container, options = {}) {
|
|
|
777
889
|
}
|
|
778
890
|
|
|
779
891
|
// src/state.ts
|
|
780
|
-
function
|
|
892
|
+
function randomSuffix() {
|
|
781
893
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
782
894
|
}
|
|
783
|
-
function
|
|
895
|
+
function randomId() {
|
|
784
896
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
785
897
|
try {
|
|
786
|
-
return
|
|
898
|
+
return crypto.randomUUID();
|
|
787
899
|
} catch {
|
|
788
|
-
return
|
|
900
|
+
return randomSuffix();
|
|
789
901
|
}
|
|
790
902
|
}
|
|
791
|
-
return
|
|
903
|
+
return randomSuffix();
|
|
792
904
|
}
|
|
793
905
|
function createInstanceState(options, internal) {
|
|
794
906
|
const config = {
|
|
@@ -800,10 +912,14 @@ function createInstanceState(options, internal) {
|
|
|
800
912
|
return {
|
|
801
913
|
config,
|
|
802
914
|
busy: false,
|
|
915
|
+
busyViews: /* @__PURE__ */ new Set(),
|
|
916
|
+
instanceId: randomId(),
|
|
803
917
|
overlayEl: null,
|
|
918
|
+
overlayDetach: null,
|
|
919
|
+
restoreDetach: null,
|
|
804
920
|
popupWindow: null,
|
|
805
921
|
popupNavigated: false,
|
|
806
|
-
popupName:
|
|
922
|
+
popupName: `maytes-checkout-${randomId()}`,
|
|
807
923
|
popupPollHandle: null,
|
|
808
924
|
destroyed: false,
|
|
809
925
|
teardowns: /* @__PURE__ */ new Set()
|
|
@@ -841,9 +957,27 @@ var Maytes = (options, internal) => {
|
|
|
841
957
|
state.config.baseUrl
|
|
842
958
|
);
|
|
843
959
|
const sdk = {
|
|
960
|
+
instanceId: state.instanceId,
|
|
844
961
|
renderButton(container, opts) {
|
|
845
962
|
return renderButton(state, container, opts);
|
|
846
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
|
+
},
|
|
847
981
|
redirectToCheckout(opts) {
|
|
848
982
|
redirectToCheckout(opts);
|
|
849
983
|
},
|
|
@@ -854,6 +988,7 @@ var Maytes = (options, internal) => {
|
|
|
854
988
|
if (state.destroyed) return;
|
|
855
989
|
state.destroyed = true;
|
|
856
990
|
stopPopupPoll(state);
|
|
991
|
+
state.restoreDetach?.();
|
|
857
992
|
hideOverlay(state);
|
|
858
993
|
if (!state.popupNavigated) closePopupWindow(state.popupWindow);
|
|
859
994
|
state.popupWindow = null;
|