@maytes/checkout-button 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -0
- package/README.md +93 -74
- package/dist/checkout-button.299a037b.js +188 -0
- package/dist/checkout-button.299a037b.js.map +1 -0
- package/dist/{checkout-button.2aad12c3.cjs → checkout-button.332d26cc.cjs} +314 -156
- package/dist/checkout-button.332d26cc.cjs.map +1 -0
- package/dist/{checkout-button.aed57a66.mjs → checkout-button.67fdb2e1.mjs} +314 -156
- package/dist/checkout-button.67fdb2e1.mjs.map +1 -0
- package/dist/checkout-button.cjs +314 -156
- package/dist/checkout-button.cjs.map +1 -1
- package/dist/checkout-button.d.cts +40 -4
- package/dist/checkout-button.d.ts +40 -4
- package/dist/checkout-button.element.afe71cd5.js +187 -0
- package/dist/checkout-button.element.afe71cd5.js.map +1 -0
- package/dist/checkout-button.element.js +187 -0
- package/dist/checkout-button.element.js.map +1 -0
- package/dist/checkout-button.js +58 -21
- package/dist/checkout-button.js.map +1 -1
- package/dist/checkout-button.mjs +314 -156
- package/dist/checkout-button.mjs.map +1 -1
- package/dist/element.cjs +201 -0
- package/dist/element.cjs.map +1 -0
- package/dist/element.d.cts +44 -0
- package/dist/element.d.ts +44 -0
- package/dist/element.mjs +180 -0
- package/dist/element.mjs.map +1 -0
- package/dist/integrity.json +37 -26
- package/dist/react.cjs +139 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +30 -0
- package/dist/react.d.ts +30 -0
- package/dist/react.mjs +117 -0
- package/dist/react.mjs.map +1 -0
- package/docs/guides/angular.md +56 -0
- package/docs/guides/api.md +108 -0
- package/docs/guides/events.md +48 -0
- package/docs/guides/launch-behaviour.md +41 -0
- package/docs/guides/nextjs.md +49 -0
- package/docs/guides/own-button.md +41 -0
- package/docs/guides/quickstart-html.md +31 -0
- package/docs/guides/quickstart-npm.md +34 -0
- package/docs/guides/react.md +88 -0
- package/docs/guides/security-csp.md +32 -0
- package/docs/guides/server.md +76 -0
- package/docs/guides/solid.md +44 -0
- package/docs/guides/stripe-payment-element.md +14 -0
- package/docs/guides/svelte.md +46 -0
- package/docs/guides/troubleshooting.md +17 -0
- package/docs/guides/vue-nuxt.md +64 -0
- package/docs/guides/web-component.md +109 -0
- package/package.json +40 -3
- package/dist/checkout-button.2aad12c3.cjs.map +0 -1
- package/dist/checkout-button.4b780d52.js +0 -151
- package/dist/checkout-button.4b780d52.js.map +0 -1
- package/dist/checkout-button.aed57a66.mjs.map +0 -1
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,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.1";
|
|
320
361
|
|
|
321
362
|
// src/styles.ts
|
|
322
363
|
var STYLE_ATTR = "data-maytes-checkout-button";
|
|
@@ -329,28 +370,51 @@ 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
|
-
border:
|
|
334
|
-
border-radius: 999px;
|
|
335
|
-
background: ${foundation.brand.primary};
|
|
336
|
-
color: ${foundation.text.primaryInverse};
|
|
375
|
+
border-radius: var(--maytes-button-radius, 999px);
|
|
337
376
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
338
377
|
font-size: var(--maytes-button-font-size, 14px);
|
|
339
378
|
font-weight: 600;
|
|
340
379
|
line-height: 1;
|
|
341
|
-
cursor: pointer;
|
|
342
380
|
user-select: none;
|
|
381
|
+
}
|
|
382
|
+
.maytes-checkout-button.maytes-checkout-button,
|
|
383
|
+
.maytes-checkout-button.maytes-checkout-button:hover,
|
|
384
|
+
.maytes-checkout-button.maytes-checkout-button:focus,
|
|
385
|
+
.maytes-checkout-button.maytes-checkout-button:active {
|
|
386
|
+
background: ${foundation.brand.primary};
|
|
387
|
+
border: none;
|
|
388
|
+
color: ${foundation.text.primaryInverse};
|
|
389
|
+
box-shadow: none;
|
|
390
|
+
text-decoration: none;
|
|
391
|
+
text-shadow: none;
|
|
392
|
+
filter: none;
|
|
393
|
+
transform: none;
|
|
394
|
+
opacity: 1;
|
|
395
|
+
cursor: pointer;
|
|
343
396
|
transition: background-color 120ms ease, opacity 120ms ease;
|
|
344
397
|
}
|
|
345
|
-
.maytes-checkout-button:hover { background: ${foundation.palette.burgundy[700]}; }
|
|
346
|
-
.maytes-checkout-button:focus-visible {
|
|
398
|
+
.maytes-checkout-button.maytes-checkout-button:hover { background: ${foundation.palette.burgundy[700]}; }
|
|
399
|
+
.maytes-checkout-button.maytes-checkout-button:focus:not(:focus-visible) { outline: none; }
|
|
400
|
+
.maytes-checkout-button.maytes-checkout-button:focus-visible {
|
|
347
401
|
outline: 2px solid ${FOCUS_RING};
|
|
348
402
|
outline-offset: 2px;
|
|
349
403
|
}
|
|
350
|
-
.maytes-checkout-button[aria-disabled='true'] {
|
|
404
|
+
.maytes-checkout-button.maytes-checkout-button[aria-disabled='true'] {
|
|
351
405
|
cursor: progress;
|
|
352
406
|
opacity: 0.7;
|
|
353
407
|
}
|
|
408
|
+
.maytes-checkout-button.maytes-checkout-button * { transition: none; }
|
|
409
|
+
.maytes-checkout-button.maytes-checkout-button .maytes-checkout-button__logo,
|
|
410
|
+
.maytes-checkout-button.maytes-checkout-button .maytes-checkout-button__logo * {
|
|
411
|
+
animation: none;
|
|
412
|
+
fill: currentColor;
|
|
413
|
+
}
|
|
414
|
+
.maytes-checkout-button--sized {
|
|
415
|
+
box-sizing: border-box;
|
|
416
|
+
white-space: nowrap;
|
|
417
|
+
}
|
|
354
418
|
.maytes-checkout-button--block {
|
|
355
419
|
display: flex;
|
|
356
420
|
width: 100%;
|
|
@@ -412,17 +476,31 @@ var BUTTON_CSS = `
|
|
|
412
476
|
margin: 0;
|
|
413
477
|
}
|
|
414
478
|
.maytes-checkout-overlay__link {
|
|
415
|
-
color: rgba(255, 255, 255, 0.7);
|
|
416
479
|
font-size: 0.875rem;
|
|
417
|
-
text-decoration: underline;
|
|
418
480
|
text-underline-offset: 4px;
|
|
481
|
+
font-family: inherit;
|
|
482
|
+
}
|
|
483
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link,
|
|
484
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:hover,
|
|
485
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:focus,
|
|
486
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:active {
|
|
419
487
|
background: none;
|
|
420
488
|
border: none;
|
|
489
|
+
color: rgba(255, 255, 255, 0.7);
|
|
490
|
+
box-shadow: none;
|
|
491
|
+
text-decoration: underline;
|
|
492
|
+
text-shadow: none;
|
|
493
|
+
transform: none;
|
|
494
|
+
opacity: 1;
|
|
421
495
|
cursor: pointer;
|
|
422
|
-
font-family: inherit;
|
|
423
496
|
transition: color 0.15s;
|
|
424
497
|
}
|
|
425
|
-
.maytes-checkout-overlay__link:hover { color: ${foundation.text.primaryInverse}; }
|
|
498
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:hover { color: ${foundation.text.primaryInverse}; }
|
|
499
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:focus:not(:focus-visible) { outline: none; }
|
|
500
|
+
.maytes-checkout-overlay__link.maytes-checkout-overlay__link:focus-visible {
|
|
501
|
+
outline: 2px solid ${FOCUS_RING};
|
|
502
|
+
outline-offset: 2px;
|
|
503
|
+
}
|
|
426
504
|
@media (prefers-reduced-motion: reduce) {
|
|
427
505
|
.maytes-checkout-overlay__spinner,
|
|
428
506
|
.maytes-checkout-button__spinner { animation: none; }
|
|
@@ -499,24 +577,12 @@ function releaseStyles() {
|
|
|
499
577
|
if (el !== null) el.remove();
|
|
500
578
|
}
|
|
501
579
|
|
|
502
|
-
// src/
|
|
580
|
+
// src/launch.ts
|
|
503
581
|
var POPUP_WIDTH = 500;
|
|
504
582
|
var POPUP_HEIGHT = 800;
|
|
505
583
|
var POPUP_POLL_INTERVAL_MS = 500;
|
|
506
584
|
var MOBILE_MAX_WIDTH = 600;
|
|
507
585
|
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
586
|
function isValidCheckoutResult(value) {
|
|
521
587
|
if (typeof value !== "object" || value === null) return false;
|
|
522
588
|
const obj = value;
|
|
@@ -615,6 +681,148 @@ function paintPopupLoadingScreen(popup, cspNonce) {
|
|
|
615
681
|
slow.hidden = false;
|
|
616
682
|
}, SLOW_NETWORK_DELAY_MS);
|
|
617
683
|
}
|
|
684
|
+
function eventDetail(state, source) {
|
|
685
|
+
return { instanceId: state.instanceId, source };
|
|
686
|
+
}
|
|
687
|
+
function dispatchFailed(origin, reason, cause) {
|
|
688
|
+
const detail = cause === void 0 ? { ...origin, reason } : { ...origin, reason, cause };
|
|
689
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-failed", { detail }));
|
|
690
|
+
}
|
|
691
|
+
function dispatchRedirected(origin, url, target) {
|
|
692
|
+
const detail = { ...origin, url, target };
|
|
693
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-redirected", { detail }));
|
|
694
|
+
}
|
|
695
|
+
function assertLaunchMode(mode, caller = "renderButton") {
|
|
696
|
+
if (mode !== "redirect" && mode !== "popup") {
|
|
697
|
+
throw new MaytesError(MaytesErrorCode.Config, `${caller}({ mode }) must be 'redirect' or 'popup'`);
|
|
698
|
+
}
|
|
699
|
+
return mode;
|
|
700
|
+
}
|
|
701
|
+
function assertRedirectOverlay(value, caller = "renderButton") {
|
|
702
|
+
if (value === void 0) return false;
|
|
703
|
+
if (typeof value !== "boolean") {
|
|
704
|
+
throw new MaytesError(MaytesErrorCode.Config, `${caller}({ redirectOverlay }) must be true or false`);
|
|
705
|
+
}
|
|
706
|
+
return value;
|
|
707
|
+
}
|
|
708
|
+
function setInstanceBusy(state, busy) {
|
|
709
|
+
state.busy = busy;
|
|
710
|
+
for (const view of [...state.busyViews]) view(busy);
|
|
711
|
+
}
|
|
712
|
+
function endLaunch(state) {
|
|
713
|
+
hideOverlay(state);
|
|
714
|
+
setInstanceBusy(state, false);
|
|
715
|
+
}
|
|
716
|
+
function closeOrphanPopup(state, popup) {
|
|
717
|
+
closePopupWindow(popup);
|
|
718
|
+
stopPopupPoll(state);
|
|
719
|
+
if (state.popupWindow === popup) state.popupWindow = null;
|
|
720
|
+
endLaunch(state);
|
|
721
|
+
}
|
|
722
|
+
function clearOverlayOnRestore(state) {
|
|
723
|
+
if (state.restoreDetach !== null) return;
|
|
724
|
+
const onPageShow = (event) => {
|
|
725
|
+
if (!event.persisted) return;
|
|
726
|
+
detach();
|
|
727
|
+
endLaunch(state);
|
|
728
|
+
};
|
|
729
|
+
const detach = () => {
|
|
730
|
+
window.removeEventListener("pageshow", onPageShow);
|
|
731
|
+
state.restoreDetach = null;
|
|
732
|
+
};
|
|
733
|
+
window.addEventListener("pageshow", onPageShow);
|
|
734
|
+
state.restoreDetach = detach;
|
|
735
|
+
}
|
|
736
|
+
function navigateAway(state, origin, url, keepOverlay) {
|
|
737
|
+
const navigation = navigateTopLevel(url, false);
|
|
738
|
+
if (keepOverlay && (navigation.target === "self" || navigation.target === "top")) {
|
|
739
|
+
setInstanceBusy(state, false);
|
|
740
|
+
clearOverlayOnRestore(state);
|
|
741
|
+
} else {
|
|
742
|
+
endLaunch(state);
|
|
743
|
+
}
|
|
744
|
+
if (navigation.target === null) {
|
|
745
|
+
console.error("[maytes/checkout-button] could not leave the embedding frame:", navigation.cause);
|
|
746
|
+
dispatchFailed(origin, "navigation-blocked", navigation.cause);
|
|
747
|
+
return { outcome: "failed", reason: "navigation-blocked" };
|
|
748
|
+
}
|
|
749
|
+
dispatchRedirected(origin, url, navigation.target);
|
|
750
|
+
return { outcome: "redirected", target: navigation.target };
|
|
751
|
+
}
|
|
752
|
+
function openPopupLoader(state, origin) {
|
|
753
|
+
const popup = openBlankPopup(state);
|
|
754
|
+
if (popup === null) return null;
|
|
755
|
+
state.popupWindow = popup;
|
|
756
|
+
state.popupNavigated = false;
|
|
757
|
+
refocusPopup(popup);
|
|
758
|
+
paintPopupLoadingScreen(popup, state.config.cspNonce);
|
|
759
|
+
showOverlay(state);
|
|
760
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-opened", { detail: origin }));
|
|
761
|
+
startPopupPoll(state, () => {
|
|
762
|
+
endLaunch(state);
|
|
763
|
+
document.dispatchEvent(new CustomEvent("maytes:checkout-closed", { detail: origin }));
|
|
764
|
+
});
|
|
765
|
+
return popup;
|
|
766
|
+
}
|
|
767
|
+
async function launchCheckout(state, mode, source, redirectOverlay = false) {
|
|
768
|
+
if (state.busy || state.destroyed) return { outcome: "ignored" };
|
|
769
|
+
setInstanceBusy(state, true);
|
|
770
|
+
const origin = eventDetail(state, source);
|
|
771
|
+
const shouldUsePopup = mode === "popup" && !isMobileViewport();
|
|
772
|
+
const popup = shouldUsePopup ? openPopupLoader(state, origin) : null;
|
|
773
|
+
if (popup === null && redirectOverlay) showOverlay(state, "redirect");
|
|
774
|
+
try {
|
|
775
|
+
const result = await state.config.createCheckout();
|
|
776
|
+
if (state.destroyed || popup !== null && popup.closed) {
|
|
777
|
+
closePopupWindow(popup);
|
|
778
|
+
return { outcome: "closed" };
|
|
779
|
+
}
|
|
780
|
+
if (!isValidCheckoutResult(result)) {
|
|
781
|
+
closeOrphanPopup(state, popup);
|
|
782
|
+
const shapeError = new MaytesError(
|
|
783
|
+
MaytesErrorCode.Config,
|
|
784
|
+
"createCheckout must resolve to { checkoutId: string, checkoutUrl?: http(s) URL }"
|
|
785
|
+
);
|
|
786
|
+
console.error(shapeError);
|
|
787
|
+
dispatchFailed(origin, "invalid-shape", shapeError);
|
|
788
|
+
return { outcome: "failed", reason: "invalid-shape" };
|
|
789
|
+
}
|
|
790
|
+
const url = result.checkoutUrl ?? buildCheckoutUrl(
|
|
791
|
+
state.config.environment,
|
|
792
|
+
state.config.baseUrl,
|
|
793
|
+
result.checkoutId
|
|
794
|
+
);
|
|
795
|
+
if (popup !== null) {
|
|
796
|
+
state.popupNavigated = true;
|
|
797
|
+
navigatePopup(popup, url);
|
|
798
|
+
return { outcome: "popup" };
|
|
799
|
+
}
|
|
800
|
+
if (shouldUsePopup) {
|
|
801
|
+
console.warn("[maytes/checkout-button] popup was blocked; redirecting instead");
|
|
802
|
+
}
|
|
803
|
+
return navigateAway(state, origin, url, redirectOverlay);
|
|
804
|
+
} catch (err) {
|
|
805
|
+
if (popup !== null && popup.closed) return { outcome: "closed" };
|
|
806
|
+
closeOrphanPopup(state, popup);
|
|
807
|
+
console.error("[maytes/checkout-button] createCheckout failed:", err);
|
|
808
|
+
dispatchFailed(origin, "create-checkout-rejected", err);
|
|
809
|
+
return { outcome: "failed", reason: "create-checkout-rejected" };
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// src/button.ts
|
|
814
|
+
function buildLogo() {
|
|
815
|
+
const svg = buildMaytesLogo("1em", "currentColor");
|
|
816
|
+
svg.setAttribute("class", "maytes-checkout-button__logo");
|
|
817
|
+
svg.setAttribute("aria-hidden", "true");
|
|
818
|
+
return svg;
|
|
819
|
+
}
|
|
820
|
+
function buildSpinner() {
|
|
821
|
+
const spinner = document.createElement("span");
|
|
822
|
+
spinner.className = "maytes-checkout-button__spinner";
|
|
823
|
+
spinner.setAttribute("aria-hidden", "true");
|
|
824
|
+
return spinner;
|
|
825
|
+
}
|
|
618
826
|
function renderButton(state, container, options = {}) {
|
|
619
827
|
if (state.destroyed) {
|
|
620
828
|
throw new MaytesError(
|
|
@@ -625,120 +833,47 @@ function renderButton(state, container, options = {}) {
|
|
|
625
833
|
if (!(container instanceof HTMLElement)) {
|
|
626
834
|
throw new MaytesError(MaytesErrorCode.Config, "renderButton(container) requires an HTMLElement");
|
|
627
835
|
}
|
|
836
|
+
const appearance = assertAppearance(options);
|
|
837
|
+
const redirectOverlay = assertRedirectOverlay(options.redirectOverlay);
|
|
628
838
|
ensureStylesInjected(state.config.cspNonce);
|
|
629
839
|
const label = options.label ?? "Split with";
|
|
630
840
|
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
|
-
}
|
|
841
|
+
const mode = assertLaunchMode(options.mode ?? "popup");
|
|
638
842
|
const button = document.createElement("button");
|
|
639
843
|
button.type = "button";
|
|
640
844
|
button.className = block ? "maytes-checkout-button maytes-checkout-button--block" : "maytes-checkout-button";
|
|
845
|
+
applyAppearance(button, appearance);
|
|
641
846
|
button.setAttribute("aria-label", `${label} Maytes`);
|
|
642
847
|
const labelNode = document.createTextNode(`${label} `);
|
|
643
|
-
|
|
848
|
+
let icon = buildLogo();
|
|
644
849
|
button.appendChild(labelNode);
|
|
645
|
-
button.appendChild(
|
|
646
|
-
const
|
|
647
|
-
state.busy = busy;
|
|
850
|
+
button.appendChild(icon);
|
|
851
|
+
const showBusy = (busy) => {
|
|
648
852
|
if (busy) {
|
|
649
853
|
button.setAttribute("aria-disabled", "true");
|
|
650
854
|
button.setAttribute("aria-busy", "true");
|
|
651
|
-
|
|
855
|
+
const spinner = buildSpinner();
|
|
856
|
+
icon.replaceWith(spinner);
|
|
857
|
+
icon = spinner;
|
|
652
858
|
} else {
|
|
653
859
|
button.removeAttribute("aria-disabled");
|
|
654
860
|
button.removeAttribute("aria-busy");
|
|
655
|
-
const
|
|
656
|
-
|
|
861
|
+
const nextLogo = buildLogo();
|
|
862
|
+
icon.replaceWith(nextLogo);
|
|
863
|
+
icon = nextLogo;
|
|
657
864
|
}
|
|
658
865
|
};
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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 }));
|
|
866
|
+
let launchedHere = false;
|
|
867
|
+
const followOwnLaunch = (busy) => {
|
|
868
|
+
if (!launchedHere) return;
|
|
869
|
+
showBusy(busy);
|
|
870
|
+
if (!busy) launchedHere = false;
|
|
675
871
|
};
|
|
676
|
-
|
|
677
|
-
|
|
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;
|
|
683
|
-
}
|
|
684
|
-
dispatchRedirected(url, outcome.target);
|
|
685
|
-
};
|
|
686
|
-
const closeOrphanPopup = (popup) => {
|
|
687
|
-
closePopupWindow(popup);
|
|
688
|
-
stopPopupPoll(state);
|
|
689
|
-
if (state.popupWindow === popup) state.popupWindow = null;
|
|
690
|
-
teardownActiveCheckout();
|
|
691
|
-
};
|
|
692
|
-
const handleClick = async () => {
|
|
872
|
+
state.busyViews.add(followOwnLaunch);
|
|
873
|
+
const handleClick = () => {
|
|
693
874
|
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
|
-
}
|
|
875
|
+
launchedHere = true;
|
|
876
|
+
void launchCheckout(state, mode, "button", redirectOverlay);
|
|
742
877
|
};
|
|
743
878
|
button.addEventListener("click", handleClick);
|
|
744
879
|
container.appendChild(button);
|
|
@@ -748,6 +883,7 @@ function renderButton(state, container, options = {}) {
|
|
|
748
883
|
cleaned = true;
|
|
749
884
|
button.removeEventListener("click", handleClick);
|
|
750
885
|
button.remove();
|
|
886
|
+
state.busyViews.delete(followOwnLaunch);
|
|
751
887
|
state.teardowns.delete(teardown);
|
|
752
888
|
releaseStyles();
|
|
753
889
|
};
|
|
@@ -756,18 +892,18 @@ function renderButton(state, container, options = {}) {
|
|
|
756
892
|
}
|
|
757
893
|
|
|
758
894
|
// src/state.ts
|
|
759
|
-
function
|
|
895
|
+
function randomSuffix() {
|
|
760
896
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
761
897
|
}
|
|
762
|
-
function
|
|
898
|
+
function randomId() {
|
|
763
899
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
764
900
|
try {
|
|
765
|
-
return
|
|
901
|
+
return crypto.randomUUID();
|
|
766
902
|
} catch {
|
|
767
|
-
return
|
|
903
|
+
return randomSuffix();
|
|
768
904
|
}
|
|
769
905
|
}
|
|
770
|
-
return
|
|
906
|
+
return randomSuffix();
|
|
771
907
|
}
|
|
772
908
|
function createInstanceState(options, internal) {
|
|
773
909
|
const config = {
|
|
@@ -779,11 +915,14 @@ function createInstanceState(options, internal) {
|
|
|
779
915
|
return {
|
|
780
916
|
config,
|
|
781
917
|
busy: false,
|
|
918
|
+
busyViews: /* @__PURE__ */ new Set(),
|
|
919
|
+
instanceId: randomId(),
|
|
782
920
|
overlayEl: null,
|
|
783
921
|
overlayDetach: null,
|
|
922
|
+
restoreDetach: null,
|
|
784
923
|
popupWindow: null,
|
|
785
924
|
popupNavigated: false,
|
|
786
|
-
popupName:
|
|
925
|
+
popupName: `maytes-checkout-${randomId()}`,
|
|
787
926
|
popupPollHandle: null,
|
|
788
927
|
destroyed: false,
|
|
789
928
|
teardowns: /* @__PURE__ */ new Set()
|
|
@@ -821,9 +960,27 @@ var Maytes = (options, internal) => {
|
|
|
821
960
|
state.config.baseUrl
|
|
822
961
|
);
|
|
823
962
|
const sdk = {
|
|
963
|
+
instanceId: state.instanceId,
|
|
824
964
|
renderButton(container, opts) {
|
|
825
965
|
return renderButton(state, container, opts);
|
|
826
966
|
},
|
|
967
|
+
openCheckout(opts) {
|
|
968
|
+
if (state.destroyed) {
|
|
969
|
+
throw new MaytesError(MaytesErrorCode.Config, "openCheckout() called on a destroyed Maytes instance");
|
|
970
|
+
}
|
|
971
|
+
const mode = assertLaunchMode(opts?.mode ?? "popup", "openCheckout");
|
|
972
|
+
const redirectOverlay = assertRedirectOverlay(opts?.redirectOverlay, "openCheckout");
|
|
973
|
+
return launchCheckout(state, mode, "api", redirectOverlay);
|
|
974
|
+
},
|
|
975
|
+
onBusyChange(listener) {
|
|
976
|
+
if (state.destroyed) {
|
|
977
|
+
throw new MaytesError(MaytesErrorCode.Config, "onBusyChange() called on a destroyed Maytes instance");
|
|
978
|
+
}
|
|
979
|
+
state.busyViews.add(listener);
|
|
980
|
+
return () => {
|
|
981
|
+
state.busyViews.delete(listener);
|
|
982
|
+
};
|
|
983
|
+
},
|
|
827
984
|
redirectToCheckout(opts) {
|
|
828
985
|
redirectToCheckout(opts);
|
|
829
986
|
},
|
|
@@ -834,6 +991,7 @@ var Maytes = (options, internal) => {
|
|
|
834
991
|
if (state.destroyed) return;
|
|
835
992
|
state.destroyed = true;
|
|
836
993
|
stopPopupPoll(state);
|
|
994
|
+
state.restoreDetach?.();
|
|
837
995
|
hideOverlay(state);
|
|
838
996
|
if (!state.popupNavigated) closePopupWindow(state.popupWindow);
|
|
839
997
|
state.popupWindow = null;
|