@01.software/sdk 0.49.0 → 0.56.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +257 -1
  2. package/MIGRATION.md +56 -0
  3. package/README.md +269 -56
  4. package/dist/client.cjs +608 -190
  5. package/dist/client.cjs.map +1 -1
  6. package/dist/client.d.cts +6 -6
  7. package/dist/client.d.ts +6 -6
  8. package/dist/client.js +608 -190
  9. package/dist/client.js.map +1 -1
  10. package/dist/{collection-client-DnDt6EQz.d.ts → collection-client-Bmr53saa.d.ts} +6 -4
  11. package/dist/{collection-client-XfGLPuTf.d.cts → collection-client-DxuBDso_.d.cts} +6 -4
  12. package/dist/{const-CBba4lDo.d.cts → const-BKUqg96Q.d.ts} +5 -5
  13. package/dist/{const-DCNV5Kq7.d.ts → const-Cv1DCsp6.d.cts} +5 -5
  14. package/dist/embedded-admin/server.cjs +306 -0
  15. package/dist/embedded-admin/server.cjs.map +1 -0
  16. package/dist/embedded-admin/server.d.cts +33 -0
  17. package/dist/embedded-admin/server.d.ts +33 -0
  18. package/dist/embedded-admin/server.js +288 -0
  19. package/dist/embedded-admin/server.js.map +1 -0
  20. package/dist/embedded-admin.cjs +400 -0
  21. package/dist/embedded-admin.cjs.map +1 -0
  22. package/dist/embedded-admin.d.cts +61 -0
  23. package/dist/embedded-admin.d.ts +61 -0
  24. package/dist/embedded-admin.js +377 -0
  25. package/dist/embedded-admin.js.map +1 -0
  26. package/dist/errors.cjs +1 -0
  27. package/dist/errors.cjs.map +1 -1
  28. package/dist/errors.d.cts +3 -3
  29. package/dist/errors.d.ts +3 -3
  30. package/dist/errors.js +1 -0
  31. package/dist/errors.js.map +1 -1
  32. package/dist/{index-DUuIqUS8.d.ts → index-CGsT4AQ8.d.cts} +89 -10
  33. package/dist/{index-CbKyoEzy.d.cts → index-DFHLLqYu.d.ts} +89 -10
  34. package/dist/index.cjs +752 -436
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +12 -69
  37. package/dist/index.d.ts +12 -69
  38. package/dist/index.js +752 -436
  39. package/dist/index.js.map +1 -1
  40. package/dist/{payload-types-MKbx5MuJ.d.cts → payload-types-Bk-duc5k.d.cts} +545 -141
  41. package/dist/{payload-types-MKbx5MuJ.d.ts → payload-types-Bk-duc5k.d.ts} +545 -141
  42. package/dist/query.cjs +108 -71
  43. package/dist/query.cjs.map +1 -1
  44. package/dist/query.d.cts +5 -5
  45. package/dist/query.d.ts +5 -5
  46. package/dist/query.js +108 -71
  47. package/dist/query.js.map +1 -1
  48. package/dist/realtime.d.cts +2 -2
  49. package/dist/realtime.d.ts +2 -2
  50. package/dist/server.cjs +502 -175
  51. package/dist/server.cjs.map +1 -1
  52. package/dist/server.d.cts +9 -8
  53. package/dist/server.d.ts +9 -8
  54. package/dist/server.js +502 -175
  55. package/dist/server.js.map +1 -1
  56. package/dist/storefront-cache.cjs +1 -0
  57. package/dist/storefront-cache.cjs.map +1 -1
  58. package/dist/storefront-cache.js +1 -0
  59. package/dist/storefront-cache.js.map +1 -1
  60. package/dist/{types-1UeOk1Uy.d.cts → types-CEK8uTvU.d.ts} +287 -30
  61. package/dist/{types-BKr_2avj.d.cts → types-CNdpj2fI.d.cts} +2 -2
  62. package/dist/{types-L_nh-ED7.d.ts → types-D4mjGFj4.d.ts} +2 -2
  63. package/dist/{types-Du4s5anw.d.ts → types-Dhk5NX7Z.d.cts} +287 -30
  64. package/dist/ui/form.d.cts +1 -1
  65. package/dist/ui/form.d.ts +1 -1
  66. package/dist/ui/video.d.cts +1 -1
  67. package/dist/ui/video.d.ts +1 -1
  68. package/dist/webhook.cjs +135 -80
  69. package/dist/webhook.cjs.map +1 -1
  70. package/dist/webhook.d.cts +21 -5
  71. package/dist/webhook.d.ts +21 -5
  72. package/dist/webhook.js +135 -80
  73. package/dist/webhook.js.map +1 -1
  74. package/package.json +28 -6
package/dist/index.cjs CHANGED
@@ -130,8 +130,6 @@ __export(src_exports, {
130
130
  projectProductToListingShape: () => projectProductToListingShape,
131
131
  resolveFeaturedImagePointer: () => resolveFeaturedImagePointer,
132
132
  resolveProductDisplayMedia: () => resolveProductDisplayMedia,
133
- resolveProductGallery: () => resolveProductGallery,
134
- resolveProductMedia: () => resolveProductMedia,
135
133
  resolveProductSelection: () => resolveProductSelection,
136
134
  resolveProductSelectionFromMatrix: () => resolveProductSelectionFromMatrix,
137
135
  resolveProductSelectionMedia: () => resolveProductSelectionMedia,
@@ -243,6 +241,7 @@ var COMMERCE_ERROR_CODES = [
243
241
  "payment_session_not_cancelable",
244
242
  "payment_session_conflict",
245
243
  "provider_identity_mismatch",
244
+ "managed_refund_attestation_forbidden",
246
245
  "payment_method_unavailable",
247
246
  "provider_unavailable",
248
247
  "lock_timeout",
@@ -785,11 +784,11 @@ function parseRetryAfter(response) {
785
784
  permitsAutomaticRetry: seconds <= MAX_AUTOMATIC_RETRY_AFTER_SECONDS
786
785
  };
787
786
  }
788
- function mapResponseToError(status, parsed, details, requestId, retryAfter, errorFamily) {
787
+ function mapResponseToError(status, parsed, details, requestId, retryAfter, canonicalCommerceError) {
789
788
  const errorDetails = {
790
789
  ...details,
791
790
  ...parsed.errors && { errors: parsed.errors },
792
- ...errorFamily !== "commerce" && parsed.body && { body: parsed.body }
791
+ ...!canonicalCommerceError && parsed.body && { body: parsed.body }
793
792
  };
794
793
  const suggestion = getParsedErrorSuggestion(status, parsed);
795
794
  const finalize = (error) => {
@@ -797,7 +796,7 @@ function mapResponseToError(status, parsed, details, requestId, retryAfter, erro
797
796
  if (retryAfter !== void 0) {
798
797
  Object.assign(withRequestId, { retryAfter });
799
798
  }
800
- return errorFamily === "commerce" ? enhanceCommerceError(withRequestId, parsed.body, requestId) : enhanceNonCommerceError(withRequestId, parsed.reason);
799
+ return canonicalCommerceError ? enhanceCommerceError(withRequestId, parsed.body, requestId) : enhanceNonCommerceError(withRequestId, parsed.reason);
801
800
  };
802
801
  if (status === 400 || status === 422) {
803
802
  return finalize(
@@ -903,6 +902,7 @@ async function httpFetch(url, options) {
903
902
  retry,
904
903
  onUnauthorized,
905
904
  errorFamily,
905
+ typedCommerceErrorReasons,
906
906
  ...requestInit
907
907
  } = options || {};
908
908
  const baseUrl = resolveApiUrl(apiUrl);
@@ -956,7 +956,8 @@ async function httpFetch(url, options) {
956
956
  });
957
957
  if (!response.ok) {
958
958
  const parsed = await parseErrorBody(response);
959
- const commerceEnvelope = errorFamily === "commerce" ? parseCommerceErrorEnvelope(parsed.body, requestId) : void 0;
959
+ const canonicalCommerceError = errorFamily === "commerce" || typeof parsed.reason === "string" && typedCommerceErrorReasons?.includes(parsed.reason) === true;
960
+ const commerceEnvelope = canonicalCommerceError ? parseCommerceErrorEnvelope(parsed.body, requestId) : void 0;
960
961
  if (isUsageLimitExceededResponse(response)) {
961
962
  const limit = parseInt(
962
963
  response.headers.get("X-Usage-Limit") || "0",
@@ -984,7 +985,7 @@ async function httpFetch(url, options) {
984
985
  ),
985
986
  requestId
986
987
  );
987
- throw errorFamily === "commerce" ? enhanceCommerceError(usageError, parsed.body, requestId) : enhanceNonCommerceError(usageError, parsed.reason);
988
+ throw canonicalCommerceError ? enhanceCommerceError(usageError, parsed.body, requestId) : enhanceNonCommerceError(usageError, parsed.reason);
988
989
  }
989
990
  if (response.status === 401 && onUnauthorized && customerToken && !hasRetried401 && (commerceEnvelope?.reason ?? parsed.reason) === "token_expired") {
990
991
  hasRetried401 = true;
@@ -1009,7 +1010,7 @@ async function httpFetch(url, options) {
1009
1010
  details,
1010
1011
  requestId,
1011
1012
  retryAfter.seconds,
1012
- errorFamily
1013
+ canonicalCommerceError
1013
1014
  );
1014
1015
  const method2 = (requestInit.method || "GET").toUpperCase();
1015
1016
  const isTerminal = error instanceof RateLimitError || error instanceof UsageLimitError;
@@ -1187,15 +1188,15 @@ var HttpClient = class {
1187
1188
  throw err;
1188
1189
  }
1189
1190
  }
1190
- buildUrl(endpoint2, options) {
1191
- if (!options) return endpoint2;
1191
+ buildUrl(endpoint, options) {
1192
+ if (!options) return endpoint;
1192
1193
  const { fallbackLocale, ...rest } = options;
1193
1194
  const queryOptions = fallbackLocale === void 0 ? rest : {
1194
1195
  ...rest,
1195
1196
  "fallback-locale": fallbackLocale === false ? "none" : fallbackLocale
1196
1197
  };
1197
1198
  const queryString = (0, import_qs_esm.stringify)(queryOptions, { addQueryPrefix: true });
1198
- return queryString ? `${endpoint2}${queryString}` : endpoint2;
1199
+ return queryString ? `${endpoint}${queryString}` : endpoint;
1199
1200
  }
1200
1201
  assertJsonResponse(response) {
1201
1202
  const contentType = response.headers.get("content-type");
@@ -1344,39 +1345,39 @@ var ReadOnlyCollectionClient = class extends HttpClient {
1344
1345
  from(collection) {
1345
1346
  return new ReadOnlyCollectionQueryBuilder(this, collection);
1346
1347
  }
1347
- async requestFind(endpoint2, options) {
1348
- const url = this.buildUrl(endpoint2, options);
1348
+ async requestFind(endpoint, options) {
1349
+ const url = this.buildUrl(endpoint, options);
1349
1350
  const response = await this.fetchWithTracking(url, {
1350
1351
  ...this.defaultOptions,
1351
1352
  method: "GET"
1352
1353
  });
1353
1354
  return this.parseFindResponse(response);
1354
1355
  }
1355
- async requestFindEndpoint(endpoint2, data) {
1356
- const response = await this.fetchWithTracking(endpoint2, {
1356
+ async requestFindEndpoint(endpoint, data) {
1357
+ const response = await this.fetchWithTracking(endpoint, {
1357
1358
  ...this.defaultOptions,
1358
1359
  method: "POST",
1359
1360
  body: data ? JSON.stringify(data) : void 0
1360
1361
  });
1361
1362
  return this.parseFindResponse(response);
1362
1363
  }
1363
- async requestFindEndpointGet(endpoint2) {
1364
- const response = await this.fetchWithTracking(endpoint2, {
1364
+ async requestFindEndpointGet(endpoint) {
1365
+ const response = await this.fetchWithTracking(endpoint, {
1365
1366
  ...this.defaultOptions,
1366
1367
  method: "GET"
1367
1368
  });
1368
1369
  return this.parseFindResponse(response);
1369
1370
  }
1370
- async requestFindById(endpoint2, options) {
1371
- const url = this.buildUrl(endpoint2, options);
1371
+ async requestFindById(endpoint, options) {
1372
+ const url = this.buildUrl(endpoint, options);
1372
1373
  const response = await this.fetchWithTracking(url, {
1373
1374
  ...this.defaultOptions,
1374
1375
  method: "GET"
1375
1376
  });
1376
1377
  return this.parseDocumentResponse(response);
1377
1378
  }
1378
- async requestCount(endpoint2, options) {
1379
- const url = this.buildUrl(endpoint2, options);
1379
+ async requestCount(endpoint, options) {
1380
+ const url = this.buildUrl(endpoint, options);
1380
1381
  const response = await this.fetchWithTracking(url, {
1381
1382
  ...this.defaultOptions,
1382
1383
  method: "GET"
@@ -1386,7 +1387,7 @@ var ReadOnlyCollectionClient = class extends HttpClient {
1386
1387
  };
1387
1388
 
1388
1389
  // src/core/api/parse-response.ts
1389
- async function parseApiResponse(response, endpoint2, options = {}) {
1390
+ async function parseApiResponse(response, endpoint, options = {}) {
1390
1391
  if (response.status === 204 || response.headers.get("content-length") === "0") {
1391
1392
  return void 0;
1392
1393
  }
@@ -1395,7 +1396,7 @@ async function parseApiResponse(response, endpoint2, options = {}) {
1395
1396
  parsed = await response.json();
1396
1397
  } catch {
1397
1398
  throw createApiError(
1398
- `Invalid JSON response from ${endpoint2}`,
1399
+ `Invalid JSON response from ${endpoint}`,
1399
1400
  response.status,
1400
1401
  void 0,
1401
1402
  "Server returned an invalid response.",
@@ -1445,6 +1446,7 @@ async function parseApiResponse(response, endpoint2, options = {}) {
1445
1446
 
1446
1447
  // ../../internal/http-contracts/src/index.ts
1447
1448
  var operationBrand = /* @__PURE__ */ Symbol("sdk-http-operation");
1449
+ var PORTONE_STANDARD_ACTION_CAPABILITIES_MEDIA_TYPE = "application/vnd.01software.portone-action-capabilities.v1+json";
1448
1450
  var operation = (collection, method, path, errorFamily) => Object.freeze({
1449
1451
  [operationBrand]: true,
1450
1452
  collection,
@@ -1453,6 +1455,10 @@ var operation = (collection, method, path, errorFamily) => Object.freeze({
1453
1455
  ...errorFamily && { errorFamily }
1454
1456
  });
1455
1457
  var strictCommerceOperation = (collection, method, path) => operation(collection, method, path, "commerce");
1458
+ var selectiveCommerceOperation = (collection, method, path, typedCommerceErrorReasons) => Object.freeze({
1459
+ ...operation(collection, method, path),
1460
+ typedCommerceErrorReasons: Object.freeze([...typedCommerceErrorReasons])
1461
+ });
1456
1462
  var COMMERCE_ENDPOINT_OPERATIONS = {
1457
1463
  products: {
1458
1464
  detail: operation("products", "get", "/detail"),
@@ -1484,7 +1490,12 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
1484
1490
  confirmPayment: operation("orders", "post", "/confirm-payment"),
1485
1491
  byPayment: operation("orders", "get", "/by-payment"),
1486
1492
  cancel: operation("orders", "post", "/cancel"),
1487
- resolveCancelRefund: operation("orders", "post", "/resolve-cancel-refund"),
1493
+ resolveCancelRefund: selectiveCommerceOperation(
1494
+ "orders",
1495
+ "post",
1496
+ "/resolve-cancel-refund",
1497
+ ["managed_refund_attestation_forbidden"]
1498
+ ),
1488
1499
  checkout: strictCommerceOperation("orders", "post", "/checkout"),
1489
1500
  createFulfillment: operation("orders", "post", "/create-fulfillment"),
1490
1501
  updateFulfillment: operation("orders", "post", "/update-fulfillment")
@@ -1493,6 +1504,11 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
1493
1504
  detail: strictCommerceOperation("checkouts", "post", "/detail"),
1494
1505
  query: strictCommerceOperation("checkouts", "post", "/query")
1495
1506
  },
1507
+ draftOrders: {
1508
+ calculate: operation("draft-orders", "post", "/:id/calculate"),
1509
+ complete: operation("draft-orders", "post", "/:id/complete"),
1510
+ finalize: operation("draft-orders", "post", "/:id/finalize")
1511
+ },
1496
1512
  transactions: {
1497
1513
  update: operation("transactions", "post", "/update")
1498
1514
  },
@@ -1555,6 +1571,16 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
1555
1571
  "payment-sessions",
1556
1572
  "post",
1557
1573
  "/server/confirm"
1574
+ ),
1575
+ findPortOne: strictCommerceOperation(
1576
+ "payment-sessions",
1577
+ "post",
1578
+ "/server/portone/find"
1579
+ ),
1580
+ reconcilePortOne: strictCommerceOperation(
1581
+ "payment-sessions",
1582
+ "post",
1583
+ "/server/portone/reconcile"
1558
1584
  )
1559
1585
  },
1560
1586
  custom: {
@@ -1574,12 +1600,34 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
1574
1600
  validate: operation("discounts", "post", "/validate")
1575
1601
  }
1576
1602
  };
1603
+ var portOneStandardActionCapabilityOperations = /* @__PURE__ */ new Set([
1604
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.create,
1605
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.retrieve,
1606
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.create,
1607
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.retrieve
1608
+ ]);
1577
1609
  var MEMBERSHIP_ENDPOINT_OPERATIONS = {
1578
1610
  subscribe: operation(null, "post", "/memberships/subscribe"),
1579
1611
  cancel: operation(null, "post", "/memberships/:id/cancel"),
1580
- me: operation(null, "get", "/memberships/me")
1612
+ me: operation(null, "get", "/memberships/me"),
1613
+ chargeResult: operation(null, "post", "/memberships/:id/charge-result"),
1614
+ refundResult: operation(null, "post", "/memberships/:id/refund-result"),
1615
+ revoke: operation(null, "post", "/memberships/:id/revoke")
1616
+ };
1617
+ var CUSTOMER_GROUP_ENDPOINT_OPERATIONS = {
1618
+ members: operation("customer-groups", "get", "/:id/members"),
1619
+ grant: operation("customer-groups", "post", "/:id/members/grant"),
1620
+ revoke: operation("customer-groups", "post", "/:id/members/revoke")
1621
+ };
1622
+ var EMBEDDED_ADMIN_ENDPOINT_OPERATIONS = {
1623
+ show: operation("tenants", "get", "/embedded-admin-app"),
1624
+ configure: operation("tenants", "patch", "/embedded-admin-app"),
1625
+ disable: operation("tenants", "delete", "/embedded-admin-app")
1581
1626
  };
1582
1627
  var EVENTS_ENDPOINT_OPERATIONS = {
1628
+ events: {
1629
+ convertSchedule: operation("events", "post", "/:id/schedule")
1630
+ },
1583
1631
  occurrences: {
1584
1632
  range: operation("event-occurrences", "get", "/range"),
1585
1633
  admissionAvailability: operation(
@@ -1620,46 +1668,51 @@ function resolveOperationMethod(descriptor) {
1620
1668
  }
1621
1669
 
1622
1670
  // src/core/api/endpoints.ts
1623
- var endpoint = (method, path) => ({ method, path });
1624
1671
  var sharedEndpoint = (descriptor) => descriptor;
1625
- function resolveOperationMethod2(descriptor) {
1626
- return "collection" in descriptor ? resolveOperationMethod(descriptor) : descriptor.method;
1627
- }
1628
1672
  function resolveOperationErrorFamily(descriptor) {
1629
- return "collection" in descriptor ? descriptor.errorFamily : void 0;
1630
- }
1631
- function resolveOperationPath2(descriptor, options) {
1632
- if ("collection" in descriptor) {
1633
- return resolveOperationPath(descriptor, options);
1634
- }
1635
- const query = options?.query?.toString().replace(/^\?/, "");
1636
- return query ? `${descriptor.path}?${query}` : descriptor.path;
1673
+ return descriptor.errorFamily;
1637
1674
  }
1638
1675
  var COMMERCE_ENDPOINTS = {
1639
1676
  products: {
1640
- detail: endpoint("GET", "/api/products/detail"),
1641
- detailCatalog: endpoint("GET", "/api/products/detail/catalog"),
1642
- listingGroups: endpoint("GET", "/api/products/listing-groups"),
1643
- listingGroupsCatalog: endpoint(
1644
- "GET",
1645
- "/api/products/listing-groups/catalog"
1677
+ detail: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.products.detail),
1678
+ detailCatalog: sharedEndpoint(
1679
+ COMMERCE_ENDPOINT_OPERATIONS.products.detailCatalog
1680
+ ),
1681
+ listingGroups: sharedEndpoint(
1682
+ COMMERCE_ENDPOINT_OPERATIONS.products.listingGroups
1683
+ ),
1684
+ listingGroupsCatalog: sharedEndpoint(
1685
+ COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsCatalog
1686
+ ),
1687
+ // The manifest keeps the GET read and the POST body-query apart; this
1688
+ // semantic name has always resolved to the GET one.
1689
+ listingGroupsQuery: sharedEndpoint(
1690
+ COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsQueryGet
1691
+ ),
1692
+ listingGroupsQueryCatalog: sharedEndpoint(
1693
+ COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsQueryCatalog
1646
1694
  ),
1647
- listingGroupsQuery: endpoint("GET", "/api/products/listing-groups/query"),
1648
- listingGroupsQueryCatalog: endpoint(
1649
- "GET",
1650
- "/api/products/listing-groups/query/catalog"
1695
+ stockCheck: sharedEndpoint(
1696
+ COMMERCE_ENDPOINT_OPERATIONS.products.stockCheck
1651
1697
  ),
1652
- stockCheck: endpoint("POST", "/api/products/stock-check"),
1653
- stockSnapshot: endpoint("GET", "/api/products/stock"),
1654
- upsert: endpoint("POST", "/api/products/upsert")
1698
+ stockSnapshot: sharedEndpoint(
1699
+ COMMERCE_ENDPOINT_OPERATIONS.products.stockSnapshot
1700
+ ),
1701
+ upsert: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.products.upsert)
1655
1702
  },
1656
1703
  orders: {
1657
- update: endpoint("POST", "/api/orders/update"),
1658
- byPayment: endpoint("GET", "/api/orders/by-payment"),
1659
- cancel: endpoint("POST", "/api/orders/cancel"),
1660
- resolveCancelRefund: endpoint("POST", "/api/orders/resolve-cancel-refund"),
1661
- createFulfillment: endpoint("POST", "/api/orders/create-fulfillment"),
1662
- updateFulfillment: endpoint("POST", "/api/orders/update-fulfillment")
1704
+ update: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.update),
1705
+ byPayment: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.byPayment),
1706
+ cancel: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.cancel),
1707
+ resolveCancelRefund: sharedEndpoint(
1708
+ COMMERCE_ENDPOINT_OPERATIONS.orders.resolveCancelRefund
1709
+ ),
1710
+ createFulfillment: sharedEndpoint(
1711
+ COMMERCE_ENDPOINT_OPERATIONS.orders.createFulfillment
1712
+ ),
1713
+ updateFulfillment: sharedEndpoint(
1714
+ COMMERCE_ENDPOINT_OPERATIONS.orders.updateFulfillment
1715
+ )
1663
1716
  },
1664
1717
  checkouts: {
1665
1718
  create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.checkout),
@@ -1671,18 +1724,21 @@ var COMMERCE_ENDPOINTS = {
1671
1724
  }
1672
1725
  },
1673
1726
  returns: {
1674
- create: endpoint("POST", "/api/returns/create"),
1675
- update: endpoint("POST", "/api/returns/update"),
1676
- returnRefund: endpoint("POST", "/api/returns/return-refund")
1727
+ create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.returns.create),
1728
+ update: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.returns.update),
1729
+ returnRefund: sharedEndpoint(
1730
+ COMMERCE_ENDPOINT_OPERATIONS.returns.returnRefund
1731
+ )
1677
1732
  },
1678
1733
  fulfillmentOrders: {
1679
- prepare: endpoint(
1680
- "POST",
1681
- "/api/fulfillment-orders/prepare-fulfillment-order"
1734
+ prepare: sharedEndpoint(
1735
+ COMMERCE_ENDPOINT_OPERATIONS.fulfillmentOrders.prepare
1682
1736
  )
1683
1737
  },
1684
1738
  fulfillments: {
1685
- bulkImport: endpoint("POST", "/api/fulfillments/bulk-import")
1739
+ bulkImport: sharedEndpoint(
1740
+ COMMERCE_ENDPOINT_OPERATIONS.fulfillments.bulkImport
1741
+ )
1686
1742
  },
1687
1743
  carts: {
1688
1744
  create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.carts.create),
@@ -1747,42 +1803,21 @@ var COMMERCE_ENDPOINTS = {
1747
1803
  ),
1748
1804
  confirm: sharedEndpoint(
1749
1805
  COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.confirm
1806
+ ),
1807
+ findPortOne: sharedEndpoint(
1808
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.findPortOne
1809
+ ),
1810
+ reconcilePortOne: sharedEndpoint(
1811
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.reconcilePortOne
1750
1812
  )
1751
1813
  }
1752
1814
  },
1753
1815
  shippingPolicies: {
1754
- calculate: endpoint("POST", "/api/shipping-policies/calculate")
1816
+ calculate: sharedEndpoint(
1817
+ COMMERCE_ENDPOINT_OPERATIONS.shippingPolicies.calculate
1818
+ )
1755
1819
  }
1756
1820
  };
1757
- function isCommerceEndpointDescriptor(value) {
1758
- if (!value || typeof value !== "object") return false;
1759
- const candidate = value;
1760
- return typeof candidate.method === "string" && typeof candidate.path === "string";
1761
- }
1762
- function flattenEndpointPaths(value) {
1763
- if (isCommerceEndpointDescriptor(value)) return [resolveOperationPath2(value)];
1764
- if (!value || typeof value !== "object") return [];
1765
- return Object.values(value).flatMap((entry) => flattenEndpointPaths(entry));
1766
- }
1767
- function flattenEndpointDescriptors(value) {
1768
- if (isCommerceEndpointDescriptor(value)) return [value];
1769
- if (!value || typeof value !== "object") return [];
1770
- return Object.values(value).flatMap(
1771
- (entry) => flattenEndpointDescriptors(entry)
1772
- );
1773
- }
1774
- var COMMERCE_ENDPOINT_PATHS = flattenEndpointPaths(COMMERCE_ENDPOINTS);
1775
- var COMMERCE_ENDPOINT_DESCRIPTORS = flattenEndpointDescriptors(COMMERCE_ENDPOINTS);
1776
- var MEMBERSHIP_ENDPOINTS = {
1777
- subscribe: "/api/memberships/subscribe",
1778
- cancel: (id) => `/api/memberships/${id}/cancel`,
1779
- me: "/api/memberships/me"
1780
- };
1781
- var MEMBERSHIP_ENDPOINT_PATHS = [
1782
- MEMBERSHIP_ENDPOINTS.subscribe,
1783
- MEMBERSHIP_ENDPOINTS.cancel(":id"),
1784
- MEMBERSHIP_ENDPOINTS.me
1785
- ];
1786
1821
 
1787
1822
  // src/core/api/customer-scoped-api.ts
1788
1823
  var CustomerScopedTransport = class {
@@ -1803,10 +1838,10 @@ var CustomerScopedTransport = class {
1803
1838
  this.onUnauthorized = options.onUnauthorized;
1804
1839
  this.onRequestId = options.onRequestId;
1805
1840
  }
1806
- async request(endpoint2, options) {
1841
+ async request(endpoint, options) {
1807
1842
  const token = typeof this.customerToken === "function" ? this.customerToken() : this.customerToken;
1808
1843
  try {
1809
- const response = await httpFetch(endpoint2, {
1844
+ const response = await httpFetch(endpoint, {
1810
1845
  method: options.method,
1811
1846
  apiUrl: this.apiUrl,
1812
1847
  publishableKey: this.publishableKey,
@@ -1820,7 +1855,7 @@ var CustomerScopedTransport = class {
1820
1855
  ...options.errorFamily && { errorFamily: options.errorFamily }
1821
1856
  });
1822
1857
  this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1823
- return parseApiResponse(response, endpoint2, {
1858
+ return parseApiResponse(response, endpoint, {
1824
1859
  errorFamily: options.errorFamily
1825
1860
  });
1826
1861
  } catch (err) {
@@ -1834,12 +1869,12 @@ var CustomerScopedApi = class {
1834
1869
  constructor(apiName, options) {
1835
1870
  this.transport = new CustomerScopedTransport(apiName, options);
1836
1871
  }
1837
- requestEndpoint(endpoint2, body, options) {
1838
- return this.transport.request(resolveOperationPath2(endpoint2, options), {
1839
- method: resolveOperationMethod2(endpoint2),
1872
+ requestEndpoint(endpoint, body, options) {
1873
+ return this.transport.request(resolveOperationPath(endpoint, options), {
1874
+ method: resolveOperationMethod(endpoint),
1840
1875
  body,
1841
1876
  headers: options?.headers,
1842
- errorFamily: resolveOperationErrorFamily(endpoint2)
1877
+ errorFamily: resolveOperationErrorFamily(endpoint)
1843
1878
  });
1844
1879
  }
1845
1880
  };
@@ -1847,8 +1882,8 @@ var RawCustomerScopedApi = class {
1847
1882
  constructor(apiName, options) {
1848
1883
  this.transport = new CustomerScopedTransport(apiName, options);
1849
1884
  }
1850
- request(endpoint2, options) {
1851
- return this.transport.request(endpoint2, options);
1885
+ request(endpoint, options) {
1886
+ return this.transport.request(endpoint, options);
1852
1887
  }
1853
1888
  };
1854
1889
 
@@ -1923,11 +1958,11 @@ var CommunityClient = class extends RawCustomerScopedApi {
1923
1958
  if (params.page !== void 0) urlParams.set("page", String(params.page));
1924
1959
  return `/api/comments/public?${urlParams.toString()}`;
1925
1960
  }
1926
- async execute(endpoint2, method, body) {
1927
- return this.request(endpoint2, { method, body });
1961
+ async execute(endpoint, method, body) {
1962
+ return this.request(endpoint, { method, body });
1928
1963
  }
1929
- async executeDoc(endpoint2, method, body) {
1930
- const response = await this.execute(endpoint2, method, body);
1964
+ async executeDoc(endpoint, method, body) {
1965
+ const response = await this.execute(endpoint, method, body);
1931
1966
  return unwrapPayloadDoc(response);
1932
1967
  }
1933
1968
  createPost(params) {
@@ -2193,8 +2228,8 @@ var CommunityClient = class extends RawCustomerScopedApi {
2193
2228
 
2194
2229
  // src/core/content/content-client.ts
2195
2230
  var PublicContentApi = class extends RawCustomerScopedApi {
2196
- get(endpoint2) {
2197
- return this.request(endpoint2, { method: "GET" });
2231
+ get(endpoint) {
2232
+ return this.request(endpoint, { method: "GET" });
2198
2233
  }
2199
2234
  };
2200
2235
  function appendCommonParams(search, options, defaultSort) {
@@ -2292,7 +2327,10 @@ var ORDER_PRIMARY_DISPLAY_STATUSES = [
2292
2327
  ];
2293
2328
  var CHECKOUT_STATUSES = ["open", "placed", "abandoned"];
2294
2329
  var ABANDONED_REASONS = ["expired", "customer", "operator"];
2295
- var PAYMENT_PROVIDERS = ["toss", "portone"];
2330
+ function managedPaymentProviders(providers) {
2331
+ return providers;
2332
+ }
2333
+ var PAYMENT_PROVIDERS = managedPaymentProviders(["toss", "portone"]);
2296
2334
  var PAYMENT_PROVIDER_STATUSES = [
2297
2335
  "requires_payment_method",
2298
2336
  "requires_action",
@@ -2340,6 +2378,26 @@ function invalidCanonicalResponse(resource, details) {
2340
2378
  function fail(resource, path, expected) {
2341
2379
  invalidCanonicalResponse(resource, { path, expected });
2342
2380
  }
2381
+ function providerMismatch(resource, expected, actual, operation2) {
2382
+ if (operation2 === "cancel") {
2383
+ const method = {
2384
+ toss: "cancel()",
2385
+ portone: "cancelPortOne()"
2386
+ }[actual];
2387
+ throw createApiError(
2388
+ `Canonical ${resource} response provider mismatch: expected ${expected}, received ${actual}.`,
2389
+ 502,
2390
+ { resource, path: "$.provider", expected, actual },
2391
+ `The server applied the cancellation to a ${actual} PaymentSession, but this SDK method expects ${expected}.`,
2392
+ `Re-invoke ${method} with the same idempotencyKey; do not create a new key.`
2393
+ );
2394
+ }
2395
+ invalidCanonicalResponse(resource, {
2396
+ path: "$.provider",
2397
+ expected,
2398
+ actual
2399
+ });
2400
+ }
2343
2401
  function record(value, resource, path) {
2344
2402
  if (!value || typeof value !== "object" || Array.isArray(value)) {
2345
2403
  fail(resource, path, "object");
@@ -2375,6 +2433,9 @@ function nullableString(value, resource, path) {
2375
2433
  fail(resource, path, "string or null");
2376
2434
  }
2377
2435
  }
2436
+ function nullableNonEmptyString(value, resource, path) {
2437
+ if (value !== null) string(value, resource, path);
2438
+ }
2378
2439
  function cartLineQuantity(value, resource, path) {
2379
2440
  const quantity = positiveInteger(value, resource, path);
2380
2441
  if (quantity > MAX_CART_LINE_QUANTITY) {
@@ -2884,8 +2945,8 @@ function validatePublicOrderItem(value, resource, path) {
2884
2945
  validateReference(item.variant, resource, `${path}.variant`);
2885
2946
  }
2886
2947
  string(item.productTitle, resource, `${path}.productTitle`);
2887
- nullableString(item.variantTitle, resource, `${path}.variantTitle`);
2888
- nullableString(item.sku, resource, `${path}.sku`);
2948
+ nullableNonEmptyString(item.variantTitle, resource, `${path}.variantTitle`);
2949
+ nullableNonEmptyString(item.sku, resource, `${path}.sku`);
2889
2950
  validateAdmissions(item.admissions, resource, `${path}.admissions`);
2890
2951
  const quantity = positiveInteger(item.quantity, resource, `${path}.quantity`);
2891
2952
  const unitPrice = safeMinor(
@@ -3136,8 +3197,8 @@ function validateCheckoutItem(value, resource, path) {
3136
3197
  validateReference(item.product, resource, `${path}.product`);
3137
3198
  validateReference(item.variant, resource, `${path}.variant`);
3138
3199
  string(item.productTitle, resource, `${path}.productTitle`);
3139
- nullableString(item.variantTitle, resource, `${path}.variantTitle`);
3140
- nullableString(item.sku, resource, `${path}.sku`);
3200
+ nullableNonEmptyString(item.variantTitle, resource, `${path}.variantTitle`);
3201
+ nullableNonEmptyString(item.sku, resource, `${path}.sku`);
3141
3202
  validateAdmissions(item.admissions, resource, `${path}.admissions`);
3142
3203
  cartLineQuantity(item.quantity, resource, `${path}.quantity`);
3143
3204
  const unitPrice = safeMinor(
@@ -3469,11 +3530,21 @@ function validateNextAction(value, provider, resource, path) {
3469
3530
  const action = record(value, resource, path);
3470
3531
  if (provider === "portone") {
3471
3532
  if (action.type === "portone_sdk") {
3533
+ const hasCancelable = "cancelable" in action;
3534
+ const hasRetryPolicy = "retryPolicy" in action;
3535
+ if (hasCancelable !== hasRetryPolicy) {
3536
+ fail(
3537
+ resource,
3538
+ path,
3539
+ "exact released action or exact negotiated capability action"
3540
+ );
3541
+ }
3472
3542
  exact(
3473
3543
  action,
3474
3544
  [
3475
3545
  "type",
3476
3546
  "version",
3547
+ ...hasCancelable ? ["cancelable", "retryPolicy"] : [],
3477
3548
  "storeId",
3478
3549
  "channelKey",
3479
3550
  "paymentId",
@@ -3488,6 +3559,11 @@ function validateNextAction(value, provider, resource, path) {
3488
3559
  path
3489
3560
  );
3490
3561
  if (action.version !== "v2") fail(resource, `${path}.version`, "v2");
3562
+ if (hasCancelable && action.cancelable !== false)
3563
+ fail(resource, `${path}.cancelable`, "false");
3564
+ if (hasRetryPolicy && action.retryPolicy !== "same_action_only") {
3565
+ fail(resource, `${path}.retryPolicy`, "same_action_only");
3566
+ }
3491
3567
  for (const key of ["storeId", "channelKey"]) {
3492
3568
  boundedString(action[key], 255, resource, `${path}.${key}`);
3493
3569
  }
@@ -3534,6 +3610,10 @@ function validateNextAction(value, provider, resource, path) {
3534
3610
  "HTTPS or loopback URL without embedded credentials"
3535
3611
  );
3536
3612
  }
3613
+ if (!hasCancelable) {
3614
+ action.cancelable = false;
3615
+ action.retryPolicy = "same_action_only";
3616
+ }
3537
3617
  return;
3538
3618
  }
3539
3619
  exact(action, ["type", "checkoutUrl"], [], resource, path);
@@ -3610,7 +3690,7 @@ function validateNextAction(value, provider, resource, path) {
3610
3690
  if (action.customerEmail !== void 0)
3611
3691
  validateCustomer({ email: action.customerEmail }, resource, path);
3612
3692
  }
3613
- function validatePublicPaymentSession(value, expectedProvider) {
3693
+ function validatePublicPaymentSession(value, expectedProvider, operation2) {
3614
3694
  const resource = "PaymentSession";
3615
3695
  const session = record(value, resource, "$");
3616
3696
  const provider = enumValue(
@@ -3619,9 +3699,6 @@ function validatePublicPaymentSession(value, expectedProvider) {
3619
3699
  resource,
3620
3700
  "$.provider"
3621
3701
  );
3622
- if (expectedProvider !== void 0 && provider !== expectedProvider) {
3623
- fail(resource, "$.provider", expectedProvider);
3624
- }
3625
3702
  const status = enumValue(
3626
3703
  session.providerStatus,
3627
3704
  PAYMENT_PROVIDER_STATUSES,
@@ -3825,44 +3902,128 @@ function validatePublicPaymentSession(value, expectedProvider) {
3825
3902
  "$.cancellationReason"
3826
3903
  );
3827
3904
  }
3905
+ if (expectedProvider !== void 0 && provider !== expectedProvider) {
3906
+ if (operation2 === "cancel" && status !== "canceling" && status !== "canceled") {
3907
+ fail(
3908
+ resource,
3909
+ "$.providerStatus",
3910
+ "canceling or canceled after a successful cancellation"
3911
+ );
3912
+ }
3913
+ providerMismatch(resource, expectedProvider, provider, operation2);
3914
+ }
3828
3915
  return value;
3829
3916
  }
3830
3917
 
3831
3918
  // src/core/customer/customer-auth.ts
3832
3919
  var DEFAULT_TIMEOUT2 = 15e3;
3833
- function safeGetItem(key) {
3920
+ var REFRESH_LOCK_TIMEOUT = DEFAULT_TIMEOUT2 * 3;
3921
+ var SESSION_RECORD_VERSION = 1;
3922
+ var STORAGE_UNUSABLE = /* @__PURE__ */ Symbol("storage-unusable");
3923
+ function canonicalApiIdentity(apiUrl) {
3834
3924
  try {
3835
- return localStorage.getItem(key);
3925
+ const url = new URL(apiUrl);
3926
+ return `${url.protocol.toLowerCase()}//${url.host.toLowerCase()}${url.pathname.replace(/\/+$/, "")}`;
3927
+ } catch {
3928
+ return apiUrl.trim().toLowerCase().replace(/\/+$/, "");
3929
+ }
3930
+ }
3931
+ function buildRefreshLockName(apiUrl, publishableKey, storageKey) {
3932
+ return `01software:customer-auth-refresh:${canonicalApiIdentity(apiUrl)}:${publishableKey}:${storageKey}`;
3933
+ }
3934
+ function getRefreshLockRequester() {
3935
+ try {
3936
+ if (typeof navigator === "undefined") return null;
3937
+ const locks = navigator.locks;
3938
+ if (!locks || typeof locks.request !== "function") return null;
3939
+ return (name, options, callback) => locks.request(
3940
+ name,
3941
+ options,
3942
+ () => callback()
3943
+ );
3836
3944
  } catch {
3837
3945
  return null;
3838
3946
  }
3839
3947
  }
3948
+ function parseSessionRecord(raw) {
3949
+ try {
3950
+ const parsed = JSON.parse(raw);
3951
+ if (!parsed || typeof parsed !== "object") return null;
3952
+ const candidate = parsed;
3953
+ if (candidate.v !== SESSION_RECORD_VERSION) return null;
3954
+ if (typeof candidate.rev !== "number" || !Number.isFinite(candidate.rev)) {
3955
+ return null;
3956
+ }
3957
+ const token = candidate.token;
3958
+ const refreshToken = candidate.refreshToken;
3959
+ if (token !== null && typeof token !== "string") return null;
3960
+ if (refreshToken !== null && typeof refreshToken !== "string") return null;
3961
+ return {
3962
+ v: SESSION_RECORD_VERSION,
3963
+ rev: candidate.rev,
3964
+ token,
3965
+ refreshToken
3966
+ };
3967
+ } catch {
3968
+ return null;
3969
+ }
3970
+ }
3971
+ function readSessionRecord(recordKey, mirrorKey, mirrorRefreshKey) {
3972
+ let raw;
3973
+ try {
3974
+ raw = localStorage.getItem(recordKey);
3975
+ } catch {
3976
+ return STORAGE_UNUSABLE;
3977
+ }
3978
+ if (raw !== null) {
3979
+ const parsed = parseSessionRecord(raw);
3980
+ return parsed ? { record: parsed, stored: true } : STORAGE_UNUSABLE;
3981
+ }
3982
+ try {
3983
+ return {
3984
+ record: {
3985
+ v: SESSION_RECORD_VERSION,
3986
+ rev: 0,
3987
+ token: localStorage.getItem(mirrorKey),
3988
+ refreshToken: localStorage.getItem(mirrorRefreshKey)
3989
+ },
3990
+ stored: false
3991
+ };
3992
+ } catch {
3993
+ return STORAGE_UNUSABLE;
3994
+ }
3995
+ }
3840
3996
  var CustomerAuth = class {
3841
3997
  constructor(publishableKey, options, apiUrl) {
3842
3998
  this.refreshPromise = null;
3999
+ /** Canonical record key; null unless persistence is active in a browser. */
4000
+ this.persistRecordKey = null;
4001
+ /** Advisory single-value access mirror, plus the retired refresh mirror. */
4002
+ this.persistMirrorKey = null;
4003
+ this.persistMirrorRefreshKey = null;
4004
+ /** Cross-context refresh lock name; null when there is no shared storage. */
4005
+ this.refreshLockName = null;
4006
+ /** Revision of the canonical record this instance last observed. */
4007
+ this.sessionRev = 0;
3843
4008
  this.publishableKey = publishableKey;
3844
4009
  this.baseUrl = resolveApiUrl(apiUrl);
3845
4010
  const persist = options?.persist ?? true;
3846
4011
  if (persist) {
3847
4012
  const key = typeof persist === "string" ? persist : "customer-token";
3848
- const refreshKey = `${key}:refresh`;
3849
4013
  const isBrowser = typeof window !== "undefined";
3850
- this.token = isBrowser ? safeGetItem(key) : null;
3851
- this.refreshTokenValue = isBrowser ? safeGetItem(refreshKey) : null;
3852
- this.onTokenChange = isBrowser ? (token) => {
3853
- try {
3854
- if (token) localStorage.setItem(key, token);
3855
- else localStorage.removeItem(key);
3856
- } catch {
3857
- }
3858
- } : void 0;
3859
- this.onRefreshTokenChange = isBrowser ? (refreshToken) => {
3860
- try {
3861
- if (refreshToken) localStorage.setItem(refreshKey, refreshToken);
3862
- else localStorage.removeItem(refreshKey);
3863
- } catch {
3864
- }
3865
- } : void 0;
4014
+ this.token = null;
4015
+ this.refreshTokenValue = null;
4016
+ if (isBrowser) {
4017
+ this.persistRecordKey = `${key}:session`;
4018
+ this.persistMirrorKey = key;
4019
+ this.persistMirrorRefreshKey = `${key}:refresh`;
4020
+ this.refreshLockName = buildRefreshLockName(
4021
+ this.baseUrl,
4022
+ publishableKey,
4023
+ key
4024
+ );
4025
+ this.syncFromPersistedStorage();
4026
+ }
3866
4027
  } else {
3867
4028
  this.token = options?.token ?? null;
3868
4029
  this.refreshTokenValue = options?.refreshToken ?? null;
@@ -3890,66 +4051,299 @@ var CustomerAuth = class {
3890
4051
  body: JSON.stringify(data)
3891
4052
  }
3892
4053
  );
3893
- this.setToken(result.token);
3894
- if (result.refreshToken) this.setRefreshToken(result.refreshToken);
4054
+ if (result.refreshToken) this.setSession(result.token, result.refreshToken);
4055
+ else this.setToken(result.token);
3895
4056
  return result;
3896
4057
  }
3897
4058
  /**
3898
4059
  * Refresh the current token. Uses a refresh token when available, otherwise
3899
4060
  * falls back to the legacy bearer-token compatibility path.
4061
+ *
4062
+ * Refresh tokens are single-use, so a persisted session shared by several
4063
+ * tabs or SDK instances must rotate at most once: concurrent rotations of the
4064
+ * same token are replay from the server's point of view and revoke the whole
4065
+ * family. Persisted browser sessions therefore serialize rotation across
4066
+ * contexts and re-read the canonical persisted credentials inside the lock.
3900
4067
  */
3901
4068
  async refreshToken() {
4069
+ this.syncFromPersistedStorage();
3902
4070
  if (!this.refreshTokenValue && !this.token) {
3903
4071
  throw new ApiError("Not authenticated", 401);
3904
4072
  }
3905
4073
  if (this.refreshPromise) return this.refreshPromise;
3906
- this.refreshPromise = this._doRefreshToken();
4074
+ const intended = this.currentFence();
4075
+ this.refreshPromise = this.rotateAcrossContexts(intended);
3907
4076
  try {
3908
4077
  return await this.refreshPromise;
3909
4078
  } finally {
3910
4079
  this.refreshPromise = null;
3911
4080
  }
3912
4081
  }
3913
- async _doRefreshToken() {
3914
- const init = this.refreshTokenValue ? {
4082
+ /**
4083
+ * Re-read the canonical persisted session. In persisted mode the shared
4084
+ * record — not this instance's memory — is the source of truth, so another
4085
+ * context's rotation, login, or logout is observed before the next credential
4086
+ * use. Change callbacks are deliberately not invoked: this is a read.
4087
+ */
4088
+ syncFromPersistedStorage() {
4089
+ const canonical = this.readCanonicalSession();
4090
+ if (canonical === STORAGE_UNUSABLE) {
4091
+ this.disablePersistence();
4092
+ return;
4093
+ }
4094
+ if (!canonical) return;
4095
+ this.sessionRev = canonical.record.rev;
4096
+ this.token = canonical.record.token;
4097
+ this.refreshTokenValue = canonical.record.refreshToken;
4098
+ }
4099
+ /** `null` when this instance does not persist. */
4100
+ readCanonicalSession() {
4101
+ if (!this.persistRecordKey || !this.persistMirrorKey || !this.persistMirrorRefreshKey) {
4102
+ return null;
4103
+ }
4104
+ return readSessionRecord(
4105
+ this.persistRecordKey,
4106
+ this.persistMirrorKey,
4107
+ this.persistMirrorRefreshKey
4108
+ );
4109
+ }
4110
+ /**
4111
+ * Publish a change to canonical storage.
4112
+ *
4113
+ * The canonical pair is re-read immediately before the single record write so
4114
+ * that a field this call does not change is taken from that read rather than
4115
+ * from this instance's possibly stale copy. That merge is BEST-EFFORT: it
4116
+ * removes the stale counterpart an instance would otherwise carry, but it is
4117
+ * not a compare-and-swap. Web Storage offers none, and the synchronous public
4118
+ * mutators cannot await the refresh lease, so a commit that lands between
4119
+ * this read and the store operation is lost by ordering — including the
4120
+ * counterpart just read, which is then republished stale. The window is one
4121
+ * store operation wide and the server remains authoritative; see
4122
+ * `CUST-AUTH-033` for the stated limit.
4123
+ *
4124
+ * Returns `false` only when `requireLiveSession` refused a write because the
4125
+ * canonical session had already been cleared.
4126
+ */
4127
+ commitPersistedSession(change, options) {
4128
+ const recordKey = this.persistRecordKey;
4129
+ const mirrorKey = this.persistMirrorKey;
4130
+ const mirrorRefreshKey = this.persistMirrorRefreshKey;
4131
+ const canonical = this.readCanonicalSession();
4132
+ if (canonical === null || !recordKey || !mirrorKey || !mirrorRefreshKey) {
4133
+ return true;
4134
+ }
4135
+ if (canonical === STORAGE_UNUSABLE) {
4136
+ this.disablePersistence();
4137
+ return true;
4138
+ }
4139
+ const current = canonical.record;
4140
+ if (options?.requireLiveSession && !current.token && !current.refreshToken) {
4141
+ return false;
4142
+ }
4143
+ const next = {
4144
+ v: SESSION_RECORD_VERSION,
4145
+ rev: current.rev + 1,
4146
+ token: "token" in change ? change.token ?? null : current.token,
4147
+ refreshToken: "refreshToken" in change ? change.refreshToken ?? null : current.refreshToken
4148
+ };
4149
+ if (
4150
+ // Only a state that is genuinely persisted can already say what this
4151
+ // write would say. An absent record reads as an empty pair, and treating
4152
+ // that as a no-op would drop the very tombstone that stops a later
4153
+ // migration from resurrecting the session out of the earlier layout.
4154
+ canonical.stored && next.token === current.token && next.refreshToken === current.refreshToken
4155
+ ) {
4156
+ this.sessionRev = current.rev;
4157
+ this.token = current.token;
4158
+ this.refreshTokenValue = current.refreshToken;
4159
+ return true;
4160
+ }
4161
+ try {
4162
+ localStorage.setItem(recordKey, JSON.stringify(next));
4163
+ } catch {
4164
+ this.disablePersistence();
4165
+ return true;
4166
+ }
4167
+ this.sessionRev = next.rev;
4168
+ this.token = next.token;
4169
+ this.refreshTokenValue = next.refreshToken;
4170
+ try {
4171
+ localStorage.removeItem(mirrorRefreshKey);
4172
+ } catch {
4173
+ return true;
4174
+ }
4175
+ try {
4176
+ if (next.token) localStorage.setItem(mirrorKey, next.token);
4177
+ else localStorage.removeItem(mirrorKey);
4178
+ } catch {
4179
+ }
4180
+ return true;
4181
+ }
4182
+ /**
4183
+ * Storage rejected a write or a read, so it is not a canonical store for this
4184
+ * instance. Fall back to the memory-only session and take no lease: there is
4185
+ * no shared state left to coordinate. Nothing is purged — the record commit
4186
+ * is atomic, so no inconsistent state was published, and another context's
4187
+ * valid session must not be destroyed by this instance's local failure.
4188
+ */
4189
+ disablePersistence() {
4190
+ this.persistRecordKey = null;
4191
+ this.persistMirrorKey = null;
4192
+ this.persistMirrorRefreshKey = null;
4193
+ this.refreshLockName = null;
4194
+ }
4195
+ /**
4196
+ * Compare the canonical record with the fence this call captured before it
4197
+ * left. Every persisted decision — rotate, converge, or fail closed — goes
4198
+ * through this one comparison. The revision catches a logout, a fresh login,
4199
+ * or a manual setter even when token values coincide; the value comparison
4200
+ * additionally catches a mirror-only writer that cannot bump a revision.
4201
+ */
4202
+ classifyCanonicalState(intended) {
4203
+ if (!this.persistRecordKey) return "current";
4204
+ this.syncFromPersistedStorage();
4205
+ if (!this.persistRecordKey) return "current";
4206
+ if (!this.token && !this.refreshTokenValue) return "cleared";
4207
+ if (intended.refreshToken !== null) {
4208
+ return this.refreshTokenValue === intended.refreshToken ? "current" : "superseded";
4209
+ }
4210
+ return this.token === intended.token && !this.refreshTokenValue ? "current" : "superseded";
4211
+ }
4212
+ /** The canonical persisted pair, as a refresh result. */
4213
+ canonicalPair(token) {
4214
+ return {
4215
+ token,
4216
+ ...this.refreshTokenValue ? { refreshToken: this.refreshTokenValue } : {}
4217
+ };
4218
+ }
4219
+ /** Current credentials plus the revision they were observed at. */
4220
+ currentFence() {
4221
+ return { token: this.token, refreshToken: this.refreshTokenValue };
4222
+ }
4223
+ /**
4224
+ * Clear an access credential a 401 just proved unusable — but only while it
4225
+ * is still the canonical one. A rotation committed by another context while
4226
+ * the request was in flight is newer than this 401, so it must survive; the
4227
+ * instance adopts it instead. The refresh entry is never touched here: a
4228
+ * rejected access token says nothing about the refresh family.
4229
+ */
4230
+ invalidateAccessToken(spentToken) {
4231
+ this.syncFromPersistedStorage();
4232
+ if (this.token !== spentToken) return;
4233
+ this.setToken(null);
4234
+ }
4235
+ /** Commit both credentials in one canonical write. */
4236
+ setSession(token, refreshToken, options) {
4237
+ this.token = token;
4238
+ this.refreshTokenValue = refreshToken;
4239
+ const committed = this.commitPersistedSession(
4240
+ { token, refreshToken },
4241
+ options
4242
+ );
4243
+ if (!committed) return false;
4244
+ this.onTokenChange?.(this.token);
4245
+ this.onRefreshTokenChange?.(this.refreshTokenValue);
4246
+ return true;
4247
+ }
4248
+ /**
4249
+ * Serialize rotation against every other context sharing the same storage
4250
+ * key and API/tenant identity. Coordination is best-effort: when it is
4251
+ * unavailable or times out the rotation still happens, so server-side reuse
4252
+ * detection is never bypassed by a client that failed to coordinate.
4253
+ */
4254
+ async rotateAcrossContexts(intended) {
4255
+ const lockName = this.refreshLockName;
4256
+ const requestLock = lockName ? getRefreshLockRequester() : null;
4257
+ if (!lockName || !requestLock) {
4258
+ return this.rotateWithCanonicalState(intended);
4259
+ }
4260
+ const controller = new AbortController();
4261
+ const timer = setTimeout(() => controller.abort(), REFRESH_LOCK_TIMEOUT);
4262
+ let entered = false;
4263
+ try {
4264
+ return await requestLock(
4265
+ lockName,
4266
+ { signal: controller.signal },
4267
+ async () => {
4268
+ entered = true;
4269
+ return this.rotateWithCanonicalState(intended);
4270
+ }
4271
+ );
4272
+ } catch (error) {
4273
+ if (entered) throw error;
4274
+ return this.rotateWithCanonicalState(intended);
4275
+ } finally {
4276
+ clearTimeout(timer);
4277
+ }
4278
+ }
4279
+ /**
4280
+ * Decide, against freshly re-read canonical state, whether this context still
4281
+ * owns the rotation.
4282
+ */
4283
+ async rotateWithCanonicalState(intended) {
4284
+ let fence = intended;
4285
+ for (let attempt = 0; attempt < 2; attempt += 1) {
4286
+ const state = this.classifyCanonicalState(fence);
4287
+ if (state === "cleared") throw new ApiError("Not authenticated", 401);
4288
+ if (state === "current") return this._doRefreshToken(fence);
4289
+ if (this.token) return this.canonicalPair(this.token);
4290
+ fence = this.currentFence();
4291
+ }
4292
+ throw new ApiError("Not authenticated", 401);
4293
+ }
4294
+ async _doRefreshToken(intended) {
4295
+ const init = intended.refreshToken ? {
3915
4296
  method: "POST",
3916
- body: JSON.stringify({ refreshToken: this.refreshTokenValue })
4297
+ body: JSON.stringify({ refreshToken: intended.refreshToken })
3917
4298
  } : {
3918
4299
  method: "POST",
3919
- headers: { Authorization: `Bearer ${this.token}` }
4300
+ headers: { Authorization: `Bearer ${intended.token}` }
3920
4301
  };
3921
4302
  const result = await this.requestJson(
3922
4303
  "/api/customers/refresh",
3923
4304
  init
3924
4305
  );
3925
- this.setToken(result.token);
3926
- if (result.refreshToken) this.setRefreshToken(result.refreshToken);
3927
- return result;
4306
+ const state = this.classifyCanonicalState(intended);
4307
+ if (state === "cleared") throw new ApiError("Not authenticated", 401);
4308
+ if (state === "superseded") {
4309
+ if (this.token) return this.canonicalPair(this.token);
4310
+ throw new ApiError("Not authenticated", 401);
4311
+ }
4312
+ const replacedAccessToken = this.token !== null && this.token !== intended.token ? this.token : null;
4313
+ const nextToken = replacedAccessToken ?? result.token;
4314
+ const nextRefresh = result.refreshToken ?? null;
4315
+ if (!this.setSession(nextToken, nextRefresh, { requireLiveSession: true })) {
4316
+ throw new ApiError("Not authenticated", 401);
4317
+ }
4318
+ return {
4319
+ token: nextToken,
4320
+ ...nextRefresh ? { refreshToken: nextRefresh } : {}
4321
+ };
3928
4322
  }
3929
4323
  /**
3930
4324
  * Clear the stored token
3931
4325
  */
3932
4326
  logout() {
3933
- this.setToken(null);
3934
- this.setRefreshToken(null);
4327
+ this.setSession(null, null);
3935
4328
  }
3936
4329
  /**
3937
4330
  * Get the current authenticated customer's profile
3938
4331
  */
3939
4332
  async me() {
3940
- if (!this.token) return null;
4333
+ const token = this.getToken();
4334
+ if (!token) return null;
3941
4335
  try {
3942
4336
  const data = await this.requestJson(
3943
4337
  "/api/customers/me",
3944
4338
  {
3945
4339
  method: "GET",
3946
- headers: { Authorization: `Bearer ${this.token}` }
4340
+ headers: { Authorization: `Bearer ${token}` }
3947
4341
  }
3948
4342
  );
3949
4343
  return data.customer ?? null;
3950
4344
  } catch (error) {
3951
4345
  if (error instanceof SDKError && error.status === 401) {
3952
- this.setToken(null);
4346
+ this.invalidateAccessToken(token);
3953
4347
  return null;
3954
4348
  }
3955
4349
  throw error;
@@ -3977,12 +4371,13 @@ var CustomerAuth = class {
3977
4371
  * Update the authenticated customer's profile (name, phone, marketingConsent)
3978
4372
  */
3979
4373
  async updateProfile(data) {
3980
- if (!this.token) throw new ApiError("Not authenticated", 401);
4374
+ const token = this.getToken();
4375
+ if (!token) throw new ApiError("Not authenticated", 401);
3981
4376
  const result = await this.requestJson(
3982
4377
  "/api/customers/me",
3983
4378
  {
3984
4379
  method: "PATCH",
3985
- headers: { Authorization: `Bearer ${this.token}` },
4380
+ headers: { Authorization: `Bearer ${token}` },
3986
4381
  body: JSON.stringify(data)
3987
4382
  }
3988
4383
  );
@@ -3992,10 +4387,11 @@ var CustomerAuth = class {
3992
4387
  * Change the password of the currently authenticated customer
3993
4388
  */
3994
4389
  async changePassword(currentPassword, newPassword) {
3995
- if (!this.token) throw new ApiError("Not authenticated", 401);
4390
+ const token = this.getToken();
4391
+ if (!token) throw new ApiError("Not authenticated", 401);
3996
4392
  await this.requestJson("/api/customers/change-password", {
3997
4393
  method: "POST",
3998
- headers: { Authorization: `Bearer ${this.token}` },
4394
+ headers: { Authorization: `Bearer ${token}` },
3999
4395
  body: JSON.stringify({ currentPassword, newPassword })
4000
4396
  });
4001
4397
  }
@@ -4003,7 +4399,8 @@ var CustomerAuth = class {
4003
4399
  * Get the authenticated customer's orders with pagination and optional status filter
4004
4400
  */
4005
4401
  async getMyOrders(options) {
4006
- if (!this.token) throw new ApiError("Not authenticated", 401);
4402
+ const token = this.getToken();
4403
+ if (!token) throw new ApiError("Not authenticated", 401);
4007
4404
  const params = new URLSearchParams();
4008
4405
  if (options?.page) params.set("page", String(options.page));
4009
4406
  if (options?.limit) params.set("limit", String(options.limit));
@@ -4025,21 +4422,27 @@ var CustomerAuth = class {
4025
4422
  `/api/customers/me/orders${qs ? `?${qs}` : ""}`,
4026
4423
  {
4027
4424
  method: "GET",
4028
- headers: { Authorization: `Bearer ${this.token}` }
4425
+ headers: { Authorization: `Bearer ${token}` }
4029
4426
  }
4030
4427
  );
4031
4428
  return validatePublicOrderList(result);
4032
4429
  }
4033
4430
  /**
4034
- * Get the current token (or null if not authenticated)
4431
+ * Get the current token (or null if not authenticated).
4432
+ *
4433
+ * In persisted mode this re-reads shared storage first, so a rotation or
4434
+ * logout performed by another tab is observed before the token is used.
4035
4435
  */
4036
4436
  getToken() {
4437
+ this.syncFromPersistedStorage();
4037
4438
  return this.token;
4038
4439
  }
4039
4440
  /**
4040
- * Get the current refresh token (or null if unavailable)
4441
+ * Get the current refresh token (or null if unavailable). Persisted mode
4442
+ * re-reads shared storage first, like {@link CustomerAuth.getToken}.
4041
4443
  */
4042
4444
  getRefreshToken() {
4445
+ this.syncFromPersistedStorage();
4043
4446
  return this.refreshTokenValue;
4044
4447
  }
4045
4448
  /**
@@ -4047,6 +4450,7 @@ var CustomerAuth = class {
4047
4450
  */
4048
4451
  setToken(token) {
4049
4452
  this.token = token;
4453
+ this.commitPersistedSession({ token });
4050
4454
  this.onTokenChange?.(token);
4051
4455
  }
4052
4456
  /**
@@ -4054,13 +4458,14 @@ var CustomerAuth = class {
4054
4458
  */
4055
4459
  setRefreshToken(refreshToken) {
4056
4460
  this.refreshTokenValue = refreshToken;
4461
+ this.commitPersistedSession({ refreshToken });
4057
4462
  this.onRefreshTokenChange?.(refreshToken);
4058
4463
  }
4059
4464
  /**
4060
4465
  * Check if the customer is currently authenticated
4061
4466
  */
4062
4467
  isAuthenticated() {
4063
- return this.token !== null;
4468
+ return this.getToken() !== null;
4064
4469
  }
4065
4470
  /**
4066
4471
  * Internal: make a request with timeout and error handling.
@@ -4268,8 +4673,8 @@ var CartApi = class extends CustomerScopedApi {
4268
4673
  endpoint(key) {
4269
4674
  return this.serverTransport ? COMMERCE_ENDPOINTS.carts.server[key] : COMMERCE_ENDPOINTS.carts[key];
4270
4675
  }
4271
- execute(endpoint2, body, idempotencyKey) {
4272
- return this.requestEndpoint(endpoint2, body, {
4676
+ execute(endpoint, body, idempotencyKey) {
4677
+ return this.requestEndpoint(endpoint, body, {
4273
4678
  ...idempotencyKey && {
4274
4679
  headers: { "X-Idempotency-Key": idempotencyKey }
4275
4680
  }
@@ -4286,26 +4691,26 @@ var CartApi = class extends CustomerScopedApi {
4286
4691
  );
4287
4692
  }
4288
4693
  mine(params = {}) {
4289
- const endpoint2 = this.endpoint("mine");
4290
- return this.execute(endpoint2, params).then(
4694
+ const endpoint = this.endpoint("mine");
4695
+ return this.execute(endpoint, params).then(
4291
4696
  (value) => validateCartMineResult(value)
4292
4697
  );
4293
4698
  }
4294
4699
  merge(params) {
4295
4700
  const { idempotencyKey, ...body } = params;
4296
4701
  assertCommerceIdempotencyKey(idempotencyKey);
4297
- const endpoint2 = this.endpoint("merge");
4298
- return this.execute(endpoint2, body, idempotencyKey).then(
4702
+ const endpoint = this.endpoint("merge");
4703
+ return this.execute(endpoint, body, idempotencyKey).then(
4299
4704
  (value) => validateCartMergeResult(value)
4300
4705
  );
4301
4706
  }
4302
4707
  addItem(params) {
4303
4708
  return this.mutate(this.endpoint("addItem"), params);
4304
4709
  }
4305
- mutate(endpoint2, params) {
4710
+ mutate(endpoint, params) {
4306
4711
  const { idempotencyKey, ...body } = params;
4307
4712
  assertCommerceIdempotencyKey(idempotencyKey);
4308
- return this.execute(endpoint2, body, idempotencyKey).then(
4713
+ return this.execute(endpoint, body, idempotencyKey).then(
4309
4714
  (value) => validateCartMutationResult(value)
4310
4715
  );
4311
4716
  }
@@ -4331,8 +4736,8 @@ var CheckoutApi = class extends CustomerScopedApi {
4331
4736
  constructor(options) {
4332
4737
  super("CheckoutApi", { ...options, requiresCredential: false });
4333
4738
  }
4334
- post(endpoint2, body, idempotencyKey) {
4335
- return this.requestEndpoint(endpoint2, body, {
4739
+ post(endpoint, body, idempotencyKey) {
4740
+ return this.requestEndpoint(endpoint, body, {
4336
4741
  ...idempotencyKey && {
4337
4742
  headers: { "X-Idempotency-Key": idempotencyKey }
4338
4743
  }
@@ -4399,15 +4804,23 @@ var PaymentSessionApi = class extends CustomerScopedApi {
4399
4804
  super("PaymentSessionApi", { ...options, requiresCredential: false });
4400
4805
  this.serverTransport = Boolean(options.secretKey);
4401
4806
  }
4402
- post(endpoint2, body, idempotencyKey) {
4403
- return this.requestEndpoint(endpoint2, body, {
4404
- ...idempotencyKey && {
4405
- headers: { "X-Idempotency-Key": idempotencyKey }
4807
+ post(endpoint, body, idempotencyKey, negotiateStandardActionCapabilities = false) {
4808
+ const headers = {
4809
+ ...idempotencyKey && { "X-Idempotency-Key": idempotencyKey },
4810
+ ...negotiateStandardActionCapabilities && {
4811
+ Accept: PORTONE_STANDARD_ACTION_CAPABILITIES_MEDIA_TYPE
4406
4812
  }
4813
+ };
4814
+ return this.requestEndpoint(endpoint, body, {
4815
+ ...Object.keys(headers).length > 0 && { headers }
4407
4816
  });
4408
4817
  }
4409
- async paymentSession(result, expectedProvider) {
4410
- return validatePublicPaymentSession(await result, expectedProvider);
4818
+ async paymentSession(result, expectedProvider, operation2) {
4819
+ return validatePublicPaymentSession(
4820
+ await result,
4821
+ expectedProvider,
4822
+ operation2
4823
+ );
4411
4824
  }
4412
4825
  create(params) {
4413
4826
  const { idempotencyKey, ...body } = params;
@@ -4428,41 +4841,44 @@ var PaymentSessionApi = class extends CustomerScopedApi {
4428
4841
  this.post(
4429
4842
  this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.create : COMMERCE_ENDPOINTS.paymentSessions.create,
4430
4843
  body,
4431
- idempotencyKey
4844
+ idempotencyKey,
4845
+ body.providerOptions.profile === "standard_v2"
4432
4846
  ),
4433
4847
  "portone"
4434
4848
  );
4435
4849
  }
4436
4850
  retrieve(params) {
4437
- const endpoint2 = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
4851
+ const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
4438
4852
  return this.paymentSession(
4439
- this.post(endpoint2, params),
4853
+ this.post(endpoint, params),
4440
4854
  "toss"
4441
4855
  );
4442
4856
  }
4443
4857
  retrievePortOne(params) {
4444
- const endpoint2 = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
4858
+ const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
4445
4859
  return this.paymentSession(
4446
- this.post(endpoint2, params),
4860
+ this.post(endpoint, params, void 0, true),
4447
4861
  "portone"
4448
4862
  );
4449
4863
  }
4450
4864
  cancel(params) {
4451
4865
  const { idempotencyKey, ...body } = params;
4452
4866
  assertCommerceIdempotencyKey(idempotencyKey);
4453
- const endpoint2 = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
4867
+ const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
4454
4868
  return this.paymentSession(
4455
- this.post(endpoint2, body, idempotencyKey),
4456
- "toss"
4869
+ this.post(endpoint, body, idempotencyKey),
4870
+ "toss",
4871
+ "cancel"
4457
4872
  );
4458
4873
  }
4459
4874
  cancelPortOne(params) {
4460
4875
  const { idempotencyKey, ...body } = params;
4461
4876
  assertCommerceIdempotencyKey(idempotencyKey);
4462
- const endpoint2 = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
4877
+ const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
4463
4878
  return this.paymentSession(
4464
- this.post(endpoint2, body, idempotencyKey),
4465
- "portone"
4879
+ this.post(endpoint, body, idempotencyKey),
4880
+ "portone",
4881
+ "cancel"
4466
4882
  );
4467
4883
  }
4468
4884
  confirm(params) {
@@ -4540,9 +4956,9 @@ function resolveVariantImageItems(variant, poolById) {
4540
4956
  if (pooled) resolved.push(pooled);
4541
4957
  continue;
4542
4958
  }
4543
- const mediaId2 = getMediaId(entry);
4544
- if (mediaId2) {
4545
- const pooled = poolById.get(mediaId2);
4959
+ const mediaId = getMediaId(entry);
4960
+ if (mediaId) {
4961
+ const pooled = poolById.get(mediaId);
4546
4962
  if (pooled) {
4547
4963
  resolved.push(pooled);
4548
4964
  }
@@ -4733,11 +5149,11 @@ function getFirstMediaId(values) {
4733
5149
  }
4734
5150
  return null;
4735
5151
  }
4736
- function inProductMediaPool(product, mediaId2) {
4737
- if (mediaId2 == null) return null;
5152
+ function inProductMediaPool(product, mediaId) {
5153
+ if (mediaId == null) return null;
4738
5154
  const images = Array.isArray(product?.images) ? product.images : [];
4739
5155
  for (const image of images) {
4740
- if (extractEntityId(image) === mediaId2) return mediaId2;
5156
+ if (extractEntityId(image) === mediaId) return mediaId;
4741
5157
  }
4742
5158
  return null;
4743
5159
  }
@@ -6318,8 +6734,8 @@ function isProductUpsertFieldValidationErrorBody(value) {
6318
6734
 
6319
6735
  // src/core/commerce/commerce-client.ts
6320
6736
  var BrowserCommerceApi = class extends CustomerScopedApi {
6321
- requestEndpoint(endpoint2, body, options) {
6322
- return super.requestEndpoint(endpoint2, body, options);
6737
+ requestEndpoint(endpoint, body, options) {
6738
+ return super.requestEndpoint(endpoint, body, options);
6323
6739
  }
6324
6740
  };
6325
6741
  var CommerceClient = class {
@@ -6519,11 +6935,11 @@ var EventsClient = class {
6519
6935
  { useCustomerAuth: false }
6520
6936
  );
6521
6937
  }
6522
- async execute(endpoint2, method, body, options = {}) {
6938
+ async execute(endpoint, method, body, options = {}) {
6523
6939
  const useCustomerAuth = options.useCustomerAuth === true;
6524
6940
  const token = useCustomerAuth ? typeof this.customerToken === "function" ? this.customerToken() : this.customerToken : void 0;
6525
6941
  try {
6526
- const response = await httpFetch(endpoint2, {
6942
+ const response = await httpFetch(endpoint, {
6527
6943
  method,
6528
6944
  apiUrl: this.apiUrl,
6529
6945
  publishableKey: this.publishableKey,
@@ -6532,7 +6948,7 @@ var EventsClient = class {
6532
6948
  ...body !== void 0 && { body: JSON.stringify(body) }
6533
6949
  });
6534
6950
  this.onRequestId?.(response.headers.get("x-request-id") ?? null);
6535
- return parseApiResponse(response, endpoint2);
6951
+ return parseApiResponse(response, endpoint);
6536
6952
  } catch (err) {
6537
6953
  const id = err instanceof SDKError ? err.requestId ?? null : null;
6538
6954
  this.onRequestId?.(id);
@@ -6790,13 +7206,106 @@ function mergeProductDetailWithStock(catalog, snapshot) {
6790
7206
  };
6791
7207
  }
6792
7208
 
7209
+ // src/core/webhook/validation.ts
7210
+ function isValidWebhookEventData(data) {
7211
+ if (typeof data !== "object" || data === null) return false;
7212
+ const event = data;
7213
+ return typeof event.collection === "string" && typeof event.operation === "string" && event.operation.length > 0 && typeof event.data === "object" && event.data !== null;
7214
+ }
7215
+
7216
+ // src/core/webhook/receiver.ts
7217
+ async function verifySignature(rawBody, secret, signature, timestamp2, deliveryId) {
7218
+ const encoder = new TextEncoder();
7219
+ const signedPrefix = encoder.encode(`${timestamp2}.${deliveryId}.`);
7220
+ const signedPayload = new Uint8Array(signedPrefix.length + rawBody.length);
7221
+ signedPayload.set(signedPrefix);
7222
+ signedPayload.set(rawBody, signedPrefix.length);
7223
+ const key = await crypto.subtle.importKey(
7224
+ "raw",
7225
+ encoder.encode(secret),
7226
+ { name: "HMAC", hash: "SHA-256" },
7227
+ false,
7228
+ ["verify"]
7229
+ );
7230
+ if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
7231
+ return false;
7232
+ }
7233
+ const sigBytes = new Uint8Array(
7234
+ (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
7235
+ );
7236
+ return crypto.subtle.verify("HMAC", key, sigBytes, signedPayload);
7237
+ }
7238
+ function timestampIsFresh(timestamp2, toleranceSeconds) {
7239
+ if (!/^\d+$/.test(timestamp2)) return false;
7240
+ const timestampMs = Number(timestamp2);
7241
+ if (!Number.isFinite(timestampMs)) return false;
7242
+ const skewMs = Math.abs(Date.now() - timestampMs);
7243
+ return skewMs <= toleranceSeconds * 1e3;
7244
+ }
7245
+ var MAX_TOLERANCE_SECONDS = 86400;
7246
+ function toleranceIsValid(toleranceSeconds) {
7247
+ return typeof toleranceSeconds === "number" && Number.isFinite(toleranceSeconds) && toleranceSeconds >= 0 && toleranceSeconds <= MAX_TOLERANCE_SECONDS;
7248
+ }
7249
+ async function processWebhookRequest(request, handler, verify) {
7250
+ try {
7251
+ const rawBody = new Uint8Array(await request.arrayBuffer());
7252
+ const verificationFailure = await verify?.(rawBody);
7253
+ if (verificationFailure) return verificationFailure;
7254
+ const body = JSON.parse(new TextDecoder().decode(rawBody));
7255
+ if (!isValidWebhookEventData(body)) {
7256
+ return new Response(
7257
+ JSON.stringify({ error: "Invalid webhook event format" }),
7258
+ { status: 400, headers: { "Content-Type": "application/json" } }
7259
+ );
7260
+ }
7261
+ await handler(body);
7262
+ return new Response(
7263
+ JSON.stringify({ success: true, message: "Webhook processed" }),
7264
+ { status: 200, headers: { "Content-Type": "application/json" } }
7265
+ );
7266
+ } catch (error) {
7267
+ console.error("Webhook processing error:", error);
7268
+ return new Response(JSON.stringify({ error: "Internal server error" }), {
7269
+ status: 500,
7270
+ headers: { "Content-Type": "application/json" }
7271
+ });
7272
+ }
7273
+ }
7274
+ async function handleSignedWebhook(request, handler, options) {
7275
+ const secret = options?.secret;
7276
+ const configuredTolerance = options?.toleranceSeconds;
7277
+ const toleranceSeconds = configuredTolerance ?? 300;
7278
+ if (typeof secret !== "string" || secret.trim().length === 0) {
7279
+ return new Response(
7280
+ JSON.stringify({ error: "Webhook signing secret is not configured" }),
7281
+ { status: 500, headers: { "Content-Type": "application/json" } }
7282
+ );
7283
+ }
7284
+ if (!toleranceIsValid(toleranceSeconds)) {
7285
+ return new Response(
7286
+ JSON.stringify({ error: "Webhook signature tolerance is not valid" }),
7287
+ { status: 500, headers: { "Content-Type": "application/json" } }
7288
+ );
7289
+ }
7290
+ return processWebhookRequest(request, handler, async (rawBody) => {
7291
+ const signature = request.headers.get("x-webhook-signature") || "";
7292
+ const timestamp2 = request.headers.get("x-webhook-timestamp") || "";
7293
+ const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
7294
+ const valid = Boolean(timestamp2 && deliveryId) && timestampIsFresh(timestamp2, toleranceSeconds) && await verifySignature(rawBody, secret, signature, timestamp2, deliveryId);
7295
+ if (!valid) {
7296
+ return new Response(
7297
+ JSON.stringify({ error: "Invalid webhook signature" }),
7298
+ { status: 401, headers: { "Content-Type": "application/json" } }
7299
+ );
7300
+ }
7301
+ });
7302
+ }
7303
+
6793
7304
  // src/core/webhook/index.ts
6794
7305
  var ORDER_CHANGED_EVENT_TYPE = "collection.orderChanged";
6795
7306
  var COMMERCE_NOTIFICATION_EVENT_TYPE = "commerce.notification";
6796
7307
  function isValidWebhookEvent(data) {
6797
- if (typeof data !== "object" || data === null) return false;
6798
- const obj = data;
6799
- return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
7308
+ return isValidWebhookEventData(data);
6800
7309
  }
6801
7310
  var COMMERCE_NOTIFICATION_OPERATION = "notification";
6802
7311
  var COMMERCE_NOTIFICATION_EVENTS = [
@@ -6808,7 +7317,13 @@ var COMMERCE_NOTIFICATION_EVENTS = [
6808
7317
  "returnApproved",
6809
7318
  "returnRejected",
6810
7319
  "returnCanceled",
6811
- "returnCompleted"
7320
+ "returnCompleted",
7321
+ "paymentReconciliationRequired",
7322
+ "paymentRefundRequiresAttention",
7323
+ "membershipActivated",
7324
+ "membershipPastDue",
7325
+ "membershipExpired",
7326
+ "membershipCancelled"
6812
7327
  ];
6813
7328
  function isStringOrNumber(value) {
6814
7329
  return typeof value === "string" || typeof value === "number";
@@ -6838,18 +7353,29 @@ function isCommerceNotificationEventName(value) {
6838
7353
  return typeof value === "string" && COMMERCE_NOTIFICATION_EVENTS.includes(value);
6839
7354
  }
6840
7355
  function isCommerceNotificationSourceCollection(value) {
6841
- return value === "orders" || value === "fulfillments" || value === "returns";
7356
+ return value === "orders" || value === "fulfillments" || value === "returns" || value === "payment-sessions" || value === "payment-refund-attempts" || value === "customer-memberships";
6842
7357
  }
6843
7358
  function isCommerceNotificationData(value) {
6844
7359
  if (!isRecord(value)) return false;
6845
7360
  if (value.source !== void 0 && (!isRecord(value.source) || !isCommerceNotificationSourceCollection(value.source.collection) || !isStringOrNumber(value.source.id))) {
6846
7361
  return false;
6847
7362
  }
6848
- return (value.orderId === void 0 || isStringOrNumber(value.orderId)) && (value.orderNumber === void 0 || typeof value.orderNumber === "string") && (value.status === void 0 || typeof value.status === "string") && (value.totalAmount === void 0 || typeof value.totalAmount === "number") && (value.currency === void 0 || typeof value.currency === "string") && (value.fulfillmentId === void 0 || isStringOrNumber(value.fulfillmentId)) && (value.fulfillmentStatus === void 0 || typeof value.fulfillmentStatus === "string") && (value.shippedAt === void 0 || typeof value.shippedAt === "string") && (value.returnId === void 0 || isStringOrNumber(value.returnId)) && (value.returnStatus === void 0 || typeof value.returnStatus === "string") && (value.refundAmount === void 0 || typeof value.refundAmount === "number") && (value.completedAt === void 0 || typeof value.completedAt === "string");
7363
+ return (value.orderId === void 0 || isStringOrNumber(value.orderId)) && (value.orderNumber === void 0 || typeof value.orderNumber === "string") && (value.status === void 0 || typeof value.status === "string") && (value.totalAmount === void 0 || typeof value.totalAmount === "number") && (value.currency === void 0 || typeof value.currency === "string") && (value.fulfillmentId === void 0 || isStringOrNumber(value.fulfillmentId)) && (value.fulfillmentStatus === void 0 || typeof value.fulfillmentStatus === "string") && (value.shippedAt === void 0 || typeof value.shippedAt === "string") && (value.returnId === void 0 || isStringOrNumber(value.returnId)) && (value.returnStatus === void 0 || typeof value.returnStatus === "string") && (value.refundAmount === void 0 || typeof value.refundAmount === "number") && (value.completedAt === void 0 || typeof value.completedAt === "string") && (value.paymentSessionId === void 0 || typeof value.paymentSessionId === "string") && (value.checkoutId === void 0 || typeof value.checkoutId === "string") && (value.refundAttemptId === void 0 || typeof value.refundAttemptId === "string") && (value.reason === void 0 || typeof value.reason === "string") && (value.observedAt === void 0 || typeof value.observedAt === "string") && (value.providerObject === void 0 || isRecord(value.providerObject) && typeof value.providerObject.provider === "string" && (value.providerObject.kind === "payment" || value.providerObject.kind === "refund") && typeof value.providerObject.id === "string") && (value.money === void 0 || isRecord(value.money) && Number.isSafeInteger(value.money.amountMinor) && Number(value.money.amountMinor) > 0 && typeof value.money.currency === "string");
6849
7364
  }
6850
7365
  function isCommerceNotification(value) {
6851
7366
  if (!isRecord(value)) return false;
6852
- return isCommerceNotificationEventName(value.event) && typeof value.intentId === "string" && value.intentId.length > 0 && typeof value.dedupeKey === "string" && value.dedupeKey.length > 0 && (value.orderId === void 0 || typeof value.orderId === "string") && (value.fulfillmentId === void 0 || typeof value.fulfillmentId === "string") && (value.returnId === void 0 || typeof value.returnId === "string");
7367
+ return isCommerceNotificationEventName(value.event) && typeof value.intentId === "string" && value.intentId.length > 0 && typeof value.dedupeKey === "string" && value.dedupeKey.length > 0 && (value.orderId === void 0 || typeof value.orderId === "string") && (value.fulfillmentId === void 0 || typeof value.fulfillmentId === "string") && (value.returnId === void 0 || typeof value.returnId === "string") && (value.paymentSessionId === void 0 || typeof value.paymentSessionId === "string") && (value.refundAttemptId === void 0 || typeof value.refundAttemptId === "string");
7368
+ }
7369
+ function isPaymentIncidentNotificationData(value, event) {
7370
+ const reconciliation = event === "paymentReconciliationRequired";
7371
+ const expectedCollection = reconciliation ? "payment-sessions" : "payment-refund-attempts";
7372
+ const expectedStatus = reconciliation ? "reconciliation_required" : "requires_attention";
7373
+ const expectedReason = reconciliation ? "orderless_capture" : "local_commit_ambiguous";
7374
+ return value.source?.collection === expectedCollection && typeof value.source.id === "string" && value.source.id.length > 0 && typeof value.paymentSessionId === "string" && value.paymentSessionId.length > 0 && isRecord(value.providerObject) && typeof value.providerObject.provider === "string" && value.providerObject.provider.length > 0 && (value.providerObject.kind === "payment" || value.providerObject.kind === "refund") && typeof value.providerObject.id === "string" && value.providerObject.id.length > 0 && isRecord(value.money) && Number.isSafeInteger(value.money.amountMinor) && Number(value.money.amountMinor) > 0 && typeof value.money.currency === "string" && value.money.currency.length > 0 && value.status === expectedStatus && value.reason === expectedReason && typeof value.observedAt === "string" && value.observedAt.length > 0 && !Number.isNaN(Date.parse(value.observedAt)) && (value.checkoutId === void 0 || typeof value.checkoutId === "string" && value.checkoutId.length > 0) && (value.orderId === void 0 || typeof value.orderId === "string" && value.orderId.length > 0) && (reconciliation ? value.source.id === value.paymentSessionId && value.providerObject.kind === "payment" && value.refundAttemptId === void 0 : typeof value.refundAttemptId === "string" && value.refundAttemptId.length > 0 && value.source.id === value.refundAttemptId);
7375
+ }
7376
+ function isMembershipNotificationData(value, event) {
7377
+ const expectedStatus = event === "membershipActivated" ? "active" : event === "membershipPastDue" ? "past_due" : event === "membershipExpired" ? "expired" : "cancelled";
7378
+ return typeof value.membershipId === "string" && value.membershipId.length > 0 && typeof value.planId === "string" && value.planId.length > 0 && typeof value.customerId === "string" && value.customerId.length > 0 && typeof value.fromStatus === "string" && value.fromStatus.length > 0 && value.fromStatus !== value.toStatus && value.toStatus === expectedStatus && typeof value.occurredAt === "string" && !Number.isNaN(Date.parse(value.occurredAt));
6853
7379
  }
6854
7380
  function isWebhookCommerceNotificationChange(value) {
6855
7381
  if (!isRecord(value)) return false;
@@ -6890,6 +7416,18 @@ function isCommerceNotificationWebhookEvent(event) {
6890
7416
  if (!matchesOptionalId(change.sourceId, sourceId)) return false;
6891
7417
  }
6892
7418
  const changeSourceId = isWebhookCommerceNotificationChange(change) ? change.sourceId : void 0;
7419
+ if (notification.event === "paymentReconciliationRequired" || notification.event === "paymentRefundRequiresAttention") {
7420
+ if (!isPaymentIncidentNotificationData(data, notification.event)) {
7421
+ return false;
7422
+ }
7423
+ const incidentData = data;
7424
+ const expectedCollection = notification.event === "paymentReconciliationRequired" ? "payment-sessions" : "payment-refund-attempts";
7425
+ const notificationSourceId = notification.event === "paymentReconciliationRequired" ? notification.paymentSessionId : notification.refundAttemptId;
7426
+ return sourceCollection === expectedCollection && typeof notification.paymentSessionId === "string" && notification.paymentSessionId === incidentData.paymentSessionId && typeof notificationSourceId === "string" && notificationSourceId === incidentData.source.id && matchesOptionalId(changeSourceId, incidentData.source.id);
7427
+ }
7428
+ if (notification.event === "membershipActivated" || notification.event === "membershipPastDue" || notification.event === "membershipExpired" || notification.event === "membershipCancelled") {
7429
+ return sourceCollection === "customer-memberships" && isMembershipNotificationData(data, notification.event) && typeof notification.membershipId === "string" && notification.membershipId === data.membershipId && matchesOptionalId(changeSourceId, data.membershipId);
7430
+ }
6893
7431
  if (notification.event === "orderPaid" || notification.event === "orderCanceled" || notification.event === "orderDelivered") {
6894
7432
  return sourceCollection === "orders" && typeof notification.orderId === "string" && notification.orderId.length > 0 && matchesOptionalId(data.orderId, sourceId) && matchesOptionalId(notification.orderId, sourceId) && matchesOptionalId(data.orderId, notification.orderId) && matchesOptionalId(changeSourceId, data.orderId ?? notification.orderId);
6895
7433
  }
@@ -6958,80 +7496,8 @@ function createCustomerAuthWebhookHandler(handlers) {
6958
7496
  await handlers.unhandled?.(event);
6959
7497
  };
6960
7498
  }
6961
- async function verifySignature(payload, secret, signature, timestamp2, deliveryId) {
6962
- const encoder = new TextEncoder();
6963
- const key = await crypto.subtle.importKey(
6964
- "raw",
6965
- encoder.encode(secret),
6966
- { name: "HMAC", hash: "SHA-256" },
6967
- false,
6968
- ["verify"]
6969
- );
6970
- if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
6971
- return false;
6972
- }
6973
- const sigBytes = new Uint8Array(
6974
- (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
6975
- );
6976
- return crypto.subtle.verify(
6977
- "HMAC",
6978
- key,
6979
- sigBytes,
6980
- encoder.encode(`${timestamp2}.${deliveryId}.${payload}`)
6981
- );
6982
- }
6983
- function timestampIsFresh(timestamp2, toleranceSeconds) {
6984
- if (!/^\d+$/.test(timestamp2)) return false;
6985
- const timestampMs = Number(timestamp2);
6986
- if (!Number.isFinite(timestampMs)) return false;
6987
- const skewMs = Math.abs(Date.now() - timestampMs);
6988
- return skewMs <= toleranceSeconds * 1e3;
6989
- }
6990
7499
  async function handleWebhook(request, handler, options) {
6991
- try {
6992
- const rawBody = await request.text();
6993
- if (options?.secret) {
6994
- const signature = request.headers.get("x-webhook-signature") || "";
6995
- const timestamp2 = request.headers.get("x-webhook-timestamp") || "";
6996
- const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
6997
- const toleranceSeconds = options.toleranceSeconds ?? 300;
6998
- const valid = Boolean(timestamp2 && deliveryId) && timestampIsFresh(timestamp2, toleranceSeconds) && await verifySignature(
6999
- rawBody,
7000
- options.secret,
7001
- signature,
7002
- timestamp2,
7003
- deliveryId
7004
- );
7005
- if (!valid) {
7006
- return new Response(
7007
- JSON.stringify({ error: "Invalid webhook signature" }),
7008
- { status: 401, headers: { "Content-Type": "application/json" } }
7009
- );
7010
- }
7011
- } else {
7012
- console.warn(
7013
- "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
7014
- );
7015
- }
7016
- const body = JSON.parse(rawBody);
7017
- if (!isValidWebhookEvent(body)) {
7018
- return new Response(
7019
- JSON.stringify({ error: "Invalid webhook event format" }),
7020
- { status: 400, headers: { "Content-Type": "application/json" } }
7021
- );
7022
- }
7023
- await handler(body);
7024
- return new Response(
7025
- JSON.stringify({ success: true, message: "Webhook processed" }),
7026
- { status: 200, headers: { "Content-Type": "application/json" } }
7027
- );
7028
- } catch (error) {
7029
- console.error("Webhook processing error:", error);
7030
- return new Response(JSON.stringify({ error: "Internal server error" }), {
7031
- status: 500,
7032
- headers: { "Content-Type": "application/json" }
7033
- });
7034
- }
7500
+ return handleSignedWebhook(request, handler, options);
7035
7501
  }
7036
7502
  function createTypedWebhookHandler(collection, handler) {
7037
7503
  return async (event) => {
@@ -7071,6 +7537,7 @@ var INTERNAL_COLLECTIONS = [
7071
7537
  "payment-event-inbox",
7072
7538
  "payment-refund-attempts",
7073
7539
  "portone-payment-interactions",
7540
+ "commerce-payment-webhook-routes",
7074
7541
  "commerce-notification-intents",
7075
7542
  "commerce-idempotency-records",
7076
7543
  "product-collection-items",
@@ -7078,6 +7545,7 @@ var INTERNAL_COLLECTIONS = [
7078
7545
  "fulfillment-events",
7079
7546
  "tenant-commerce-config",
7080
7547
  "customer-oauth-clients",
7548
+ "customer-group-grants",
7081
7549
  "api-keys",
7082
7550
  "personal-access-tokens",
7083
7551
  "tenant-entitlements",
@@ -7111,7 +7579,6 @@ var COLLECTIONS = [
7111
7579
  "brands",
7112
7580
  "customer-profiles",
7113
7581
  "customer-profile-stats",
7114
- "discounts",
7115
7582
  "shipping-policies",
7116
7583
  "shipping-zones",
7117
7584
  "documents",
@@ -7187,6 +7654,9 @@ var SERVER_ONLY_COLLECTIONS = [
7187
7654
  "cart-items",
7188
7655
  "checkouts",
7189
7656
  "checkout-items",
7657
+ // Raw discount definitions require a server credential. Browser discount
7658
+ // validation is exposed through the shaped POST /api/discounts/validate path.
7659
+ "discounts",
7190
7660
  "draft-orders",
7191
7661
  "draft-order-items",
7192
7662
  "posts",
@@ -7352,160 +7822,6 @@ var RealtimeConnection = class {
7352
7822
  }
7353
7823
  };
7354
7824
 
7355
- // src/utils/product-media.ts
7356
- function mediaPointerOptionValues(values) {
7357
- return (values ?? []).filter((value) => value != null && value !== "").map((value) => ({
7358
- swatch: {
7359
- type: "media",
7360
- mediaItemId: value
7361
- }
7362
- }));
7363
- }
7364
- function resolveProductMedia(input) {
7365
- const resolved = resolveProductSelectionMedia({
7366
- ...input,
7367
- selectedVariant: input.selectedVariant ?? (input.variantFeaturedMediaItemId ? { images: [input.variantFeaturedMediaItemId] } : null),
7368
- selectedOptionValues: input.selectedOptionValues ?? mediaPointerOptionValues(input.selectedOptionValueMediaItemIds)
7369
- });
7370
- if (resolved.source !== "none") {
7371
- return {
7372
- ...resolved,
7373
- source: resolved.source === "variant_media_selected" ? "variant_featured" : resolved.source
7374
- };
7375
- }
7376
- const display = resolveProductDisplayMedia({
7377
- productMediaPool: input.productMediaPool
7378
- });
7379
- return {
7380
- primaryImage: display.primaryImage,
7381
- images: display.images,
7382
- source: display.source
7383
- };
7384
- }
7385
-
7386
- // src/utils/product-gallery.ts
7387
- function mediaId(value) {
7388
- if (typeof value === "string" || typeof value === "number") {
7389
- return String(value);
7390
- }
7391
- if (typeof value === "object" && value !== null && "id" in value) {
7392
- const id = value.id;
7393
- if (typeof id === "string" || typeof id === "number") return String(id);
7394
- }
7395
- return null;
7396
- }
7397
- function relationshipIds(values) {
7398
- if (!Array.isArray(values)) return [];
7399
- return values.map((value) => mediaId(value)).filter((value) => Boolean(value));
7400
- }
7401
- function buildPoolById2(pool) {
7402
- const poolById = /* @__PURE__ */ new Map();
7403
- for (const item of pool) {
7404
- const id = mediaId(item);
7405
- if (id) poolById.set(id, item);
7406
- }
7407
- return poolById;
7408
- }
7409
- function mediaSetItemsInPool(mediaSet, poolById) {
7410
- const seen = /* @__PURE__ */ new Set();
7411
- const items = [];
7412
- const primaryId = mediaId(mediaSet.primaryMediaItemId);
7413
- if (primaryId) {
7414
- const primary = poolById.get(primaryId);
7415
- if (primary) {
7416
- seen.add(primaryId);
7417
- items.push(primary);
7418
- }
7419
- }
7420
- for (const itemId of relationshipIds(mediaSet.items)) {
7421
- if (seen.has(itemId)) continue;
7422
- const item = poolById.get(itemId);
7423
- if (!item) continue;
7424
- seen.add(itemId);
7425
- items.push(item);
7426
- }
7427
- return items;
7428
- }
7429
- function resolveLegacyMediaSet(input) {
7430
- if (!Array.isArray(input.mediaSets)) return null;
7431
- const pool = input.productMediaPool ?? input.pool ?? [];
7432
- const poolById = buildPoolById2(pool);
7433
- const selectedVariantId = mediaId(input.selectedVariantId);
7434
- if (selectedVariantId) {
7435
- const exactSet = input.mediaSets.find(
7436
- (set) => set.matchType === "exact-variant" && relationshipIds(set.variantIds).includes(selectedVariantId)
7437
- );
7438
- if (exactSet) {
7439
- const images = mediaSetItemsInPool(exactSet, poolById);
7440
- if (images.length > 0) {
7441
- return {
7442
- primaryImage: images[0] ?? null,
7443
- images,
7444
- source: "exact_variant_set"
7445
- };
7446
- }
7447
- }
7448
- }
7449
- const selectedOptionValueIds = new Set(
7450
- relationshipIds(input.selectedOptionValueIds)
7451
- );
7452
- const optionSetMatches = input.mediaSets.filter((set) => set.matchType === "option-values").map((set) => {
7453
- const optionValueIds = relationshipIds(set.optionValueIds);
7454
- if (optionValueIds.length === 0) return null;
7455
- if (!optionValueIds.every((id) => selectedOptionValueIds.has(id))) {
7456
- return null;
7457
- }
7458
- const images = mediaSetItemsInPool(set, poolById);
7459
- if (images.length === 0) return null;
7460
- return { optionValueCount: optionValueIds.length, images };
7461
- }).filter(
7462
- (match) => Boolean(match)
7463
- ).sort((a, b) => b.optionValueCount - a.optionValueCount);
7464
- if (optionSetMatches.length > 0) {
7465
- const images = optionSetMatches[0].images;
7466
- return {
7467
- primaryImage: images[0] ?? null,
7468
- images,
7469
- source: "option_value_set"
7470
- };
7471
- }
7472
- const defaultSet = input.mediaSets.find((set) => set.matchType === "default");
7473
- if (defaultSet) {
7474
- const images = mediaSetItemsInPool(defaultSet, poolById);
7475
- if (images.length > 0) {
7476
- return {
7477
- primaryImage: images[0] ?? null,
7478
- images,
7479
- source: "default_set"
7480
- };
7481
- }
7482
- }
7483
- return {
7484
- primaryImage: null,
7485
- images: [],
7486
- source: "none"
7487
- };
7488
- }
7489
- function resolveProductGallery(input) {
7490
- const selectedVariantId = mediaId(input.selectedVariantId);
7491
- const selectedOptionValueIds = relationshipIds(input.selectedOptionValueIds);
7492
- const selection = resolveProductSelectionMedia({
7493
- ...input,
7494
- productMediaPool: input.productMediaPool ?? input.pool ?? null,
7495
- selectedVariant: input.selectedVariant ?? (selectedVariantId ? { id: selectedVariantId } : null),
7496
- selectedOptionValues: input.selectedOptionValues ?? selectedOptionValueIds.map((id) => ({ id }))
7497
- });
7498
- if (selection.source !== "none") {
7499
- return selection;
7500
- }
7501
- const legacyResolved = resolveLegacyMediaSet(input);
7502
- if (legacyResolved && legacyResolved.source !== "none") return legacyResolved;
7503
- return resolveProductDisplayMedia({
7504
- productMediaPool: input.productMediaPool ?? input.pool ?? null,
7505
- featuredImage: input.featuredImage
7506
- });
7507
- }
7508
-
7509
7825
  // src/utils/image.ts
7510
7826
  var IMAGE_SIZES = [200, 400, 800, 1200, 1600];
7511
7827
  function imageSizeCandidates(sizes) {
@@ -7637,7 +7953,7 @@ function createAnalytics(config) {
7637
7953
  }, destroy() {
7638
7954
  } };
7639
7955
  }
7640
- const endpoint2 = config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`;
7956
+ const endpoint = config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`;
7641
7957
  const respectDnt = config.respectDnt !== false;
7642
7958
  function isDntActive() {
7643
7959
  if (!respectDnt) return false;
@@ -7678,10 +7994,10 @@ function createAnalytics(config) {
7678
7994
  try {
7679
7995
  if (typeof navigator.sendBeacon === "function") {
7680
7996
  const blob = new Blob([body], { type: "text/plain" });
7681
- const sent = navigator.sendBeacon(endpoint2, blob);
7997
+ const sent = navigator.sendBeacon(endpoint, blob);
7682
7998
  if (sent) return;
7683
7999
  }
7684
- fetch(endpoint2, {
8000
+ fetch(endpoint, {
7685
8001
  method: "POST",
7686
8002
  keepalive: true,
7687
8003
  headers: { "Content-Type": "application/json" },