@youidian/sdk 3.3.7 → 3.4.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/dist/client.cjs CHANGED
@@ -26,6 +26,7 @@ __export(client_exports, {
26
26
  PaymentUI: () => PaymentUI,
27
27
  createLoginUI: () => createLoginUI,
28
28
  createPaymentUI: () => createPaymentUI,
29
+ detectLoginEnvironment: () => detectLoginEnvironment,
29
30
  handleLoginCallbackIfPresent: () => handleLoginCallbackIfPresent
30
31
  });
31
32
  module.exports = __toCommonJS(client_exports);
@@ -232,6 +233,91 @@ var HostedFrameModal = class {
232
233
  }
233
234
  };
234
235
 
236
+ // src/environment.ts
237
+ function normalizeText(value) {
238
+ return value || "";
239
+ }
240
+ function getRuntimeInput() {
241
+ if (typeof navigator === "undefined") {
242
+ return {};
243
+ }
244
+ const nav = navigator;
245
+ const coarsePointer = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)").matches : void 0;
246
+ return {
247
+ coarsePointer,
248
+ maxTouchPoints: nav.maxTouchPoints || 0,
249
+ platform: nav.platform || "",
250
+ standalone: nav.standalone,
251
+ userAgent: nav.userAgent || "",
252
+ userAgentDataMobile: nav.userAgentData?.mobile
253
+ };
254
+ }
255
+ function includesAny(value, patterns) {
256
+ return patterns.some((pattern) => pattern.test(value));
257
+ }
258
+ function detectLoginEnvironment(input = getRuntimeInput()) {
259
+ const userAgent = normalizeText(input.userAgent);
260
+ const platform = normalizeText(input.platform);
261
+ const rawMaxTouchPoints = input.maxTouchPoints ?? 0;
262
+ const maxTouchPoints = Number.isFinite(rawMaxTouchPoints) ? Math.max(0, rawMaxTouchPoints) : 0;
263
+ const isCoarsePointer = input.coarsePointer === true;
264
+ const isTouch = maxTouchPoints > 0 || isCoarsePointer;
265
+ const isWeChat = /micromessenger/i.test(userAgent);
266
+ const isAndroid = /\bandroid\b/i.test(userAgent);
267
+ const hasExplicitIOSDevice = /\b(iPad|iPhone|iPod)\b/i.test(userAgent);
268
+ const hasMacintoshUserAgent = /\bMacintosh\b/i.test(userAgent);
269
+ const hasMacPlatform = /^Mac/i.test(platform);
270
+ const isIPadOS = !hasExplicitIOSDevice && hasMacintoshUserAgent && hasMacPlatform && typeof input.standalone !== "undefined" && maxTouchPoints > 2;
271
+ const isIOS = hasExplicitIOSDevice || isIPadOS;
272
+ const isTabletByUa = includesAny(userAgent, [
273
+ /\biPad\b/i,
274
+ /\btablet\b/i,
275
+ /\bplaybook\b/i,
276
+ /\bsilk\b(?!.*\bmobile\b)/i,
277
+ /\bkindle\b/i,
278
+ /\bnexus 7\b/i,
279
+ /\bnexus 9\b/i,
280
+ /\bxoom\b/i,
281
+ /\bsm-t\d+/i,
282
+ /\bgt-p\d+/i,
283
+ /\bmi pad\b/i
284
+ ]);
285
+ const isMobileByUa = includesAny(userAgent, [
286
+ /\bMobile\b/i,
287
+ /\biPhone\b/i,
288
+ /\biPod\b/i,
289
+ /\bWindows Phone\b/i,
290
+ /\bIEMobile\b/i,
291
+ /\bBlackBerry\b/i,
292
+ /\bBB10\b/i,
293
+ /\bOpera Mini\b/i,
294
+ /\bOpera Mobi\b/i,
295
+ /\bwebOS\b/i
296
+ ]);
297
+ const isAndroidTablet = isAndroid && !/\bMobile\b/i.test(userAgent);
298
+ const isTablet = isIPadOS || isTabletByUa || isAndroidTablet;
299
+ const isMobile = input.userAgentDataMobile === true || isMobileByUa || isAndroid && !isTablet || isIOS && !isTablet;
300
+ const deviceType = isTablet ? "tablet" : isMobile ? "mobile" : userAgent || platform || isTouch ? "desktop" : "unknown";
301
+ const shouldUseRedirectLogin = deviceType === "mobile" || deviceType === "tablet" || isWeChat;
302
+ return {
303
+ deviceType,
304
+ isAndroid,
305
+ isCoarsePointer,
306
+ isDesktop: deviceType === "desktop",
307
+ isIOS,
308
+ isIPadOS,
309
+ isMobile,
310
+ isStandalone: input.standalone === true,
311
+ isTablet,
312
+ isTouch,
313
+ isWeChat,
314
+ maxTouchPoints,
315
+ platform,
316
+ shouldUseRedirectLogin,
317
+ userAgent
318
+ };
319
+ }
320
+
235
321
  // src/login.ts
236
322
  function getOrigin(value) {
237
323
  if (!value) return null;
@@ -241,6 +327,15 @@ function getOrigin(value) {
241
327
  return null;
242
328
  }
243
329
  }
330
+ function isValidLoginRedirectUrl(value) {
331
+ if (!value) return false;
332
+ try {
333
+ const url = new URL(value);
334
+ return url.protocol === "https:" || url.protocol === "http:";
335
+ } catch {
336
+ return false;
337
+ }
338
+ }
244
339
  function createLoginCallbackState() {
245
340
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
246
341
  return crypto.randomUUID().replace(/-/g, "");
@@ -587,6 +682,24 @@ function applyLoginPanelStyle(panel) {
587
682
  backdropFilter: "blur(16px)"
588
683
  });
589
684
  }
685
+ function applyLoginLoadingPanelStyle(panel) {
686
+ Object.assign(panel.style, {
687
+ position: "absolute",
688
+ inset: "0",
689
+ display: "flex",
690
+ alignItems: "center",
691
+ justifyContent: "center",
692
+ border: "0",
693
+ borderRadius: "28px",
694
+ background: "rgba(255,255,255,0.94)",
695
+ color: "#0f172a",
696
+ padding: "24px",
697
+ textAlign: "center",
698
+ zIndex: "1",
699
+ boxShadow: "none",
700
+ backdropFilter: "blur(16px)"
701
+ });
702
+ }
590
703
  function createLoginPanelContent() {
591
704
  const content = document.createElement("div");
592
705
  Object.assign(content.style, {
@@ -611,6 +724,71 @@ function createLoginPanelContent() {
611
724
  content.appendChild(badge);
612
725
  return content;
613
726
  }
727
+ function createBrandLoadingMark() {
728
+ const logo = document.createElementNS("http://www.w3.org/2000/svg", "svg");
729
+ logo.setAttribute("viewBox", "0 0 1024 1024");
730
+ logo.setAttribute("fill", "none");
731
+ logo.setAttribute("aria-hidden", "true");
732
+ Object.assign(logo.style, {
733
+ width: "112px",
734
+ height: "112px",
735
+ color: "#22c55e",
736
+ display: "block",
737
+ overflow: "visible"
738
+ });
739
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
740
+ group.setAttribute("fill", "currentColor");
741
+ const ring = document.createElementNS("http://www.w3.org/2000/svg", "path");
742
+ ring.setAttribute(
743
+ "d",
744
+ "M704.298 679.54A264 264 0 1 1 704.298 309.46A49 49 0 0 1 634.4 378.149A166 166 0 1 0 634.4 610.851A49 49 0 0 1 704.298 679.54Z"
745
+ );
746
+ ring.setAttribute("class", "youidian-sdk-brand-loading__ring");
747
+ const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
748
+ dot.setAttribute("class", "youidian-sdk-brand-loading__dot");
749
+ dot.setAttribute("cx", "714.5");
750
+ dot.setAttribute("cy", "490.5");
751
+ dot.setAttribute("r", "68");
752
+ group.appendChild(ring);
753
+ group.appendChild(dot);
754
+ logo.appendChild(group);
755
+ return logo;
756
+ }
757
+ function createBrandLoadingCopy() {
758
+ const brand = document.createElement("div");
759
+ brand.className = "youidian-sdk-brand-loading__copy";
760
+ Object.assign(brand.style, {
761
+ display: "flex",
762
+ flexDirection: "column",
763
+ alignItems: "flex-start",
764
+ justifyContent: "center",
765
+ lineHeight: "1"
766
+ });
767
+ const name = document.createElement("div");
768
+ name.className = "youidian-sdk-brand-loading__name";
769
+ name.textContent = "\u4F18\u6613\u70B9";
770
+ Object.assign(name.style, {
771
+ color: "#0f172a",
772
+ fontSize: "34px",
773
+ fontWeight: "900",
774
+ letterSpacing: "0",
775
+ lineHeight: "1"
776
+ });
777
+ const tagline = document.createElement("div");
778
+ tagline.className = "youidian-sdk-brand-loading__tagline";
779
+ tagline.textContent = "\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0";
780
+ Object.assign(tagline.style, {
781
+ color: "#64748b",
782
+ fontSize: "16px",
783
+ fontWeight: "700",
784
+ letterSpacing: "0.24em",
785
+ lineHeight: "1",
786
+ marginTop: "6px"
787
+ });
788
+ brand.appendChild(name);
789
+ brand.appendChild(tagline);
790
+ return brand;
791
+ }
614
792
  function createLoginPanelTitle(value) {
615
793
  const title = document.createElement("div");
616
794
  title.textContent = value;
@@ -640,32 +818,111 @@ function ensureLoginLoadingStyles() {
640
818
  }
641
819
  const style = document.createElement("style");
642
820
  style.id = "youidian-login-loading-style";
643
- style.textContent = "@keyframes youidian-login-spin{to{transform:rotate(360deg)}}";
821
+ style.textContent = `
822
+ .youidian-sdk-brand-loading__ring,
823
+ .youidian-sdk-brand-loading__dot {
824
+ transform-box: fill-box;
825
+ transform-origin: center;
826
+ will-change: opacity, transform;
827
+ }
828
+
829
+ .youidian-sdk-brand-loading__ring {
830
+ animation: youidian-sdk-brand-loading-ring 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
831
+ }
832
+
833
+ .youidian-sdk-brand-loading__dot {
834
+ animation: youidian-sdk-brand-loading-dot 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
835
+ }
836
+
837
+ .youidian-sdk-brand-loading__copy {
838
+ will-change: opacity, transform;
839
+ animation: youidian-sdk-brand-loading-copy 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
840
+ }
841
+
842
+ .youidian-sdk-brand-loading__tagline {
843
+ will-change: opacity;
844
+ animation: youidian-sdk-brand-loading-tagline 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
845
+ }
846
+
847
+ @keyframes youidian-sdk-brand-loading-ring {
848
+ 0%,
849
+ 100% {
850
+ opacity: 0.72;
851
+ transform: rotate(0deg) scale(0.98);
852
+ }
853
+ 50% {
854
+ opacity: 1;
855
+ transform: rotate(8deg) scale(1.02);
856
+ }
857
+ }
858
+
859
+ @keyframes youidian-sdk-brand-loading-dot {
860
+ 0%,
861
+ 100% {
862
+ opacity: 0.55;
863
+ transform: scale(0.9);
864
+ }
865
+ 50% {
866
+ opacity: 1;
867
+ transform: scale(1.08);
868
+ }
869
+ }
870
+
871
+ @keyframes youidian-sdk-brand-loading-copy {
872
+ 0%,
873
+ 100% {
874
+ opacity: 0.86;
875
+ transform: translateY(0);
876
+ }
877
+ 50% {
878
+ opacity: 1;
879
+ transform: translateY(-1px);
880
+ }
881
+ }
882
+
883
+ @keyframes youidian-sdk-brand-loading-tagline {
884
+ 0%,
885
+ 100% {
886
+ opacity: 0.64;
887
+ }
888
+ 50% {
889
+ opacity: 0.92;
890
+ }
891
+ }
892
+
893
+ @media (prefers-reduced-motion: reduce) {
894
+ .youidian-sdk-brand-loading__ring,
895
+ .youidian-sdk-brand-loading__dot,
896
+ .youidian-sdk-brand-loading__copy,
897
+ .youidian-sdk-brand-loading__tagline {
898
+ animation: none;
899
+ opacity: 1;
900
+ transform: none;
901
+ }
902
+ }
903
+ `;
644
904
  document.head.appendChild(style);
645
905
  }
646
- function createLoginLoadingPanel(options) {
906
+ function createLoginLoadingPanel() {
647
907
  ensureLoginLoadingStyles();
648
908
  const panel = document.createElement("div");
649
- applyLoginPanelStyle(panel);
650
- const content = createLoginPanelContent();
651
- const spinner = document.createElement("div");
652
- Object.assign(spinner.style, {
653
- width: "22px",
654
- height: "22px",
655
- borderRadius: "9999px",
656
- border: "2px solid rgba(23,23,23,0.16)",
657
- borderTopColor: "#171717",
658
- margin: "0 auto 18px",
659
- animation: "youidian-login-spin 780ms linear infinite"
909
+ applyLoginLoadingPanelStyle(panel);
910
+ panel.setAttribute("role", "status");
911
+ panel.setAttribute("aria-label", "\u4F18\u6613\u70B9\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0");
912
+ const content = document.createElement("div");
913
+ Object.assign(content.style, {
914
+ display: "flex",
915
+ alignItems: "center",
916
+ justifyContent: "center",
917
+ gap: "12px"
660
918
  });
661
- content.appendChild(spinner);
662
- content.appendChild(createLoginPanelTitle(options.title));
663
- content.appendChild(createLoginPanelDescription(options.description));
919
+ content.appendChild(createBrandLoadingMark());
920
+ content.appendChild(createBrandLoadingCopy());
664
921
  panel.appendChild(content);
665
922
  return panel;
666
923
  }
667
- function createLoginRedirectingPanel(options) {
668
- return createLoginLoadingPanel(options);
924
+ function createLoginRedirectingPanel(_options) {
925
+ return createLoginLoadingPanel();
669
926
  }
670
927
  function createLoginFallbackPanel(options) {
671
928
  const panel = document.createElement("div");
@@ -765,14 +1022,11 @@ var LoginUI = class extends HostedFrameModal {
765
1022
  this.iframeFallbackPanel = null;
766
1023
  this.iframeLoadingPanel = null;
767
1024
  }
768
- showIframeLoading(container, options) {
1025
+ showIframeLoading(container) {
769
1026
  if (this.iframeLoadingPanel || this.iframeReady) {
770
1027
  return;
771
1028
  }
772
- const panel = createLoginLoadingPanel({
773
- description: options?.loadingDescription || "Please wait while the secure login page opens.",
774
- title: options?.loadingTitle || "Opening login page"
775
- });
1029
+ const panel = createLoginLoadingPanel();
776
1030
  this.iframeLoadingPanel = panel;
777
1031
  container.appendChild(panel);
778
1032
  }
@@ -860,6 +1114,20 @@ var LoginUI = class extends HostedFrameModal {
860
1114
  break;
861
1115
  case "LOGIN_RESIZE":
862
1116
  break;
1117
+ case "LOGIN_REDIRECT_REQUIRED":
1118
+ if (!isValidLoginRedirectUrl(data.authUrl)) {
1119
+ logLoginWarn("Login redirect requested with invalid authUrl");
1120
+ options?.onError?.("Login redirect URL is invalid", data);
1121
+ break;
1122
+ }
1123
+ logLoginInfo("Login redirect requested by hosted page", {
1124
+ attemptId: data.attemptId || null,
1125
+ channel: data.channel || null,
1126
+ authUrl: data.authUrl
1127
+ });
1128
+ this.close();
1129
+ window.location.assign(data.authUrl);
1130
+ break;
863
1131
  case "LOGIN_ERROR":
864
1132
  if (this.completed) {
865
1133
  break;
@@ -883,6 +1151,25 @@ var LoginUI = class extends HostedFrameModal {
883
1151
  break;
884
1152
  }
885
1153
  }
1154
+ openLoginRedirect(params, options) {
1155
+ if (typeof window === "undefined") return;
1156
+ const { callbackState, finalUrl, launchPayload } = this.buildOpenContext(
1157
+ params,
1158
+ options
1159
+ );
1160
+ logLoginInfo("Redirecting to hosted login page", {
1161
+ appId: params.appId,
1162
+ callbackState,
1163
+ callbackUrl: launchPayload.callbackUrl || null,
1164
+ hasCallbackUrl: Boolean(launchPayload.callbackUrl),
1165
+ loginUrl: finalUrl,
1166
+ autoClose: options?.autoClose ?? true,
1167
+ displayMode: "redirect",
1168
+ pageUrl: window.location.href,
1169
+ parentOrigin: launchPayload.origin || null
1170
+ });
1171
+ window.location.assign(finalUrl);
1172
+ }
886
1173
  listenForLoginCallback(callbackState, options) {
887
1174
  try {
888
1175
  this.callbackChannel = new BroadcastChannel(
@@ -959,21 +1246,17 @@ var LoginUI = class extends HostedFrameModal {
959
1246
  options?.onCancel?.();
960
1247
  options?.onClose?.();
961
1248
  },
962
- onMessage: (data, container, event) => {
1249
+ onMessage: (data, _container, event) => {
963
1250
  const isIframeEvent = event.source === this.iframe?.contentWindow;
964
1251
  if (isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE") || data.type === "LOGIN_SUCCESS" || data.type === "LOGIN_ERROR") {
965
1252
  this.markIframeReady();
966
1253
  }
967
- if (data.type === "LOGIN_RESIZE" && data.height) {
968
- const maxHeight = window.innerHeight * 0.94;
969
- container.style.height = `${Math.min(data.height, maxHeight)}px`;
970
- }
971
1254
  this.handleLoginEvent(data, options);
972
1255
  },
973
1256
  width: "min(520px, 100%)"
974
1257
  });
975
1258
  if (hostedFrame) {
976
- this.showIframeLoading(hostedFrame.container, options);
1259
+ this.showIframeLoading(hostedFrame.container);
977
1260
  this.startIframeWatchdog({
978
1261
  container: hostedFrame.container,
979
1262
  finalUrl,
@@ -1077,11 +1360,19 @@ var LoginUI = class extends HostedFrameModal {
1077
1360
  }
1078
1361
  openLogin(params, options) {
1079
1362
  if (typeof window === "undefined") return;
1080
- const displayMode = options?.displayMode ?? "modal";
1363
+ const displayMode = options?.displayMode ?? "auto";
1364
+ if (displayMode === "redirect") {
1365
+ this.openLoginRedirect(params, options);
1366
+ return;
1367
+ }
1081
1368
  if (displayMode === "popup") {
1082
1369
  this.openLoginPopup(params, options);
1083
1370
  return;
1084
1371
  }
1372
+ if (displayMode === "auto" && detectLoginEnvironment().shouldUseRedirectLogin) {
1373
+ this.openLoginRedirect(params, options);
1374
+ return;
1375
+ }
1085
1376
  this.openLoginModal(params, options);
1086
1377
  }
1087
1378
  };
@@ -1092,6 +1383,43 @@ handleLoginCallbackIfPresent({ autoClose: false });
1092
1383
 
1093
1384
  // src/client.ts
1094
1385
  var PaymentUI = class extends HostedFrameModal {
1386
+ constructor() {
1387
+ super(...arguments);
1388
+ __publicField(this, "activeCheckoutAppId", null);
1389
+ __publicField(this, "activeCheckoutBaseUrl", null);
1390
+ __publicField(this, "activeOrderId", null);
1391
+ __publicField(this, "paymentCompleted", false);
1392
+ __publicField(this, "cancelRequestedOrderId", null);
1393
+ }
1394
+ cancelHostedOrder(reason = "user_cancel", orderId) {
1395
+ const targetOrderId = orderId || this.activeOrderId;
1396
+ if (!targetOrderId || !this.activeCheckoutAppId || !this.activeCheckoutBaseUrl || this.paymentCompleted || this.cancelRequestedOrderId === targetOrderId) {
1397
+ return;
1398
+ }
1399
+ this.cancelRequestedOrderId = targetOrderId;
1400
+ const url = `${this.activeCheckoutBaseUrl}/api/internal/checkout/cancel-order`;
1401
+ const payload = JSON.stringify({
1402
+ appId: this.activeCheckoutAppId,
1403
+ orderId: targetOrderId,
1404
+ reason
1405
+ });
1406
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
1407
+ const sent = navigator.sendBeacon(
1408
+ url,
1409
+ new Blob([payload], { type: "application/json" })
1410
+ );
1411
+ if (sent) return;
1412
+ }
1413
+ void fetch(url, {
1414
+ method: "POST",
1415
+ body: payload,
1416
+ headers: { "Content-Type": "text/plain;charset=UTF-8" },
1417
+ keepalive: true,
1418
+ mode: "no-cors"
1419
+ }).catch(() => {
1420
+ this.cancelRequestedOrderId = null;
1421
+ });
1422
+ }
1095
1423
  /**
1096
1424
  * Opens the payment checkout page in an iframe modal.
1097
1425
  * @param urlOrParams - The checkout page URL or payment parameters
@@ -1101,11 +1429,25 @@ var PaymentUI = class extends HostedFrameModal {
1101
1429
  if (typeof document === "undefined") return;
1102
1430
  if (this.modal) return;
1103
1431
  let checkoutUrl;
1432
+ this.activeOrderId = null;
1433
+ this.paymentCompleted = false;
1434
+ this.cancelRequestedOrderId = null;
1104
1435
  if (typeof urlOrParams === "string") {
1105
1436
  checkoutUrl = urlOrParams;
1437
+ try {
1438
+ const parsedUrl = new URL(checkoutUrl);
1439
+ this.activeCheckoutBaseUrl = parsedUrl.origin;
1440
+ const parts = parsedUrl.pathname.split("/").filter(Boolean);
1441
+ const checkoutIndex = parts.indexOf("checkout");
1442
+ this.activeCheckoutAppId = checkoutIndex >= 0 ? parts[checkoutIndex + 1] || null : null;
1443
+ } catch {
1444
+ this.activeCheckoutAppId = null;
1445
+ this.activeCheckoutBaseUrl = null;
1446
+ }
1106
1447
  } else {
1107
1448
  const {
1108
1449
  appId,
1450
+ orderId,
1109
1451
  productId,
1110
1452
  priceId,
1111
1453
  productCode,
@@ -1115,9 +1457,10 @@ var PaymentUI = class extends HostedFrameModal {
1115
1457
  baseUrl = "https://pay.imgto.link"
1116
1458
  } = urlOrParams;
1117
1459
  const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
1118
- const query = new URLSearchParams({
1119
- userId
1120
- });
1460
+ this.activeCheckoutAppId = appId;
1461
+ this.activeCheckoutBaseUrl = base;
1462
+ const query = new URLSearchParams();
1463
+ if (userId) query.set("userId", userId);
1121
1464
  if (customAmount) {
1122
1465
  const amount = Number(customAmount.amount);
1123
1466
  const currency = customAmount.currency.trim().toUpperCase();
@@ -1134,13 +1477,22 @@ var PaymentUI = class extends HostedFrameModal {
1134
1477
  query.set("customAmount", String(amount));
1135
1478
  query.set("customCurrency", currency);
1136
1479
  }
1137
- if (productCode) {
1480
+ if (orderId) {
1481
+ const cleanOrderId = orderId.trim();
1482
+ if (!cleanOrderId) {
1483
+ throw new Error("orderId is required");
1484
+ }
1485
+ this.activeOrderId = cleanOrderId;
1486
+ checkoutUrl = `${base}/checkout/${appId}/orders/${cleanOrderId}`;
1487
+ const queryString = query.toString();
1488
+ if (queryString) checkoutUrl += `?${queryString}`;
1489
+ } else if (productCode) {
1138
1490
  checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?${query.toString()}`;
1139
1491
  } else if (productId && priceId) {
1140
1492
  checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?${query.toString()}`;
1141
1493
  } else {
1142
1494
  throw new Error(
1143
- "Either productCode or both productId and priceId are required"
1495
+ "Either orderId, productCode, or both productId and priceId are required"
1144
1496
  );
1145
1497
  }
1146
1498
  }
@@ -1150,13 +1502,23 @@ var PaymentUI = class extends HostedFrameModal {
1150
1502
  );
1151
1503
  this.openHostedFrame(finalUrl, {
1152
1504
  allowedOrigin: options?.allowedOrigin,
1153
- onCloseButton: () => options?.onCancel?.(),
1505
+ onCloseButton: () => {
1506
+ this.cancelHostedOrder("modal_close");
1507
+ options?.onCancel?.(this.activeOrderId || void 0);
1508
+ },
1154
1509
  onMessage: (data, container) => {
1155
1510
  switch (data.type) {
1511
+ case "PAYMENT_STARTED":
1512
+ if (data.orderId) {
1513
+ this.activeOrderId = data.orderId;
1514
+ }
1515
+ break;
1156
1516
  case "PAYMENT_SUCCESS":
1517
+ this.paymentCompleted = true;
1157
1518
  options?.onSuccess?.(data.orderId);
1158
1519
  break;
1159
1520
  case "PAYMENT_CANCELLED":
1521
+ this.cancelHostedOrder("payment_cancelled", data.orderId);
1160
1522
  options?.onCancel?.(data.orderId);
1161
1523
  break;
1162
1524
  case "PAYMENT_RESIZE":
@@ -1167,6 +1529,7 @@ var PaymentUI = class extends HostedFrameModal {
1167
1529
  }
1168
1530
  break;
1169
1531
  case "PAYMENT_CLOSE":
1532
+ this.cancelHostedOrder("payment_close", data.orderId);
1170
1533
  this.close();
1171
1534
  options?.onClose?.();
1172
1535
  break;
@@ -1227,6 +1590,7 @@ function createPaymentUI() {
1227
1590
  PaymentUI,
1228
1591
  createLoginUI,
1229
1592
  createPaymentUI,
1593
+ detectLoginEnvironment,
1230
1594
  handleLoginCallbackIfPresent
1231
1595
  });
1232
1596
  //# sourceMappingURL=client.cjs.map