@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.js CHANGED
@@ -97,6 +97,7 @@ var COMMERCE_ERROR_CODES = [
97
97
  "payment_session_not_cancelable",
98
98
  "payment_session_conflict",
99
99
  "provider_identity_mismatch",
100
+ "managed_refund_attestation_forbidden",
100
101
  "payment_method_unavailable",
101
102
  "provider_unavailable",
102
103
  "lock_timeout",
@@ -639,11 +640,11 @@ function parseRetryAfter(response) {
639
640
  permitsAutomaticRetry: seconds <= MAX_AUTOMATIC_RETRY_AFTER_SECONDS
640
641
  };
641
642
  }
642
- function mapResponseToError(status, parsed, details, requestId, retryAfter, errorFamily) {
643
+ function mapResponseToError(status, parsed, details, requestId, retryAfter, canonicalCommerceError) {
643
644
  const errorDetails = {
644
645
  ...details,
645
646
  ...parsed.errors && { errors: parsed.errors },
646
- ...errorFamily !== "commerce" && parsed.body && { body: parsed.body }
647
+ ...!canonicalCommerceError && parsed.body && { body: parsed.body }
647
648
  };
648
649
  const suggestion = getParsedErrorSuggestion(status, parsed);
649
650
  const finalize = (error) => {
@@ -651,7 +652,7 @@ function mapResponseToError(status, parsed, details, requestId, retryAfter, erro
651
652
  if (retryAfter !== void 0) {
652
653
  Object.assign(withRequestId, { retryAfter });
653
654
  }
654
- return errorFamily === "commerce" ? enhanceCommerceError(withRequestId, parsed.body, requestId) : enhanceNonCommerceError(withRequestId, parsed.reason);
655
+ return canonicalCommerceError ? enhanceCommerceError(withRequestId, parsed.body, requestId) : enhanceNonCommerceError(withRequestId, parsed.reason);
655
656
  };
656
657
  if (status === 400 || status === 422) {
657
658
  return finalize(
@@ -757,6 +758,7 @@ async function httpFetch(url, options) {
757
758
  retry,
758
759
  onUnauthorized,
759
760
  errorFamily,
761
+ typedCommerceErrorReasons,
760
762
  ...requestInit
761
763
  } = options || {};
762
764
  const baseUrl = resolveApiUrl(apiUrl);
@@ -810,7 +812,8 @@ async function httpFetch(url, options) {
810
812
  });
811
813
  if (!response.ok) {
812
814
  const parsed = await parseErrorBody(response);
813
- const commerceEnvelope = errorFamily === "commerce" ? parseCommerceErrorEnvelope(parsed.body, requestId) : void 0;
815
+ const canonicalCommerceError = errorFamily === "commerce" || typeof parsed.reason === "string" && typedCommerceErrorReasons?.includes(parsed.reason) === true;
816
+ const commerceEnvelope = canonicalCommerceError ? parseCommerceErrorEnvelope(parsed.body, requestId) : void 0;
814
817
  if (isUsageLimitExceededResponse(response)) {
815
818
  const limit = parseInt(
816
819
  response.headers.get("X-Usage-Limit") || "0",
@@ -838,7 +841,7 @@ async function httpFetch(url, options) {
838
841
  ),
839
842
  requestId
840
843
  );
841
- throw errorFamily === "commerce" ? enhanceCommerceError(usageError, parsed.body, requestId) : enhanceNonCommerceError(usageError, parsed.reason);
844
+ throw canonicalCommerceError ? enhanceCommerceError(usageError, parsed.body, requestId) : enhanceNonCommerceError(usageError, parsed.reason);
842
845
  }
843
846
  if (response.status === 401 && onUnauthorized && customerToken && !hasRetried401 && (commerceEnvelope?.reason ?? parsed.reason) === "token_expired") {
844
847
  hasRetried401 = true;
@@ -863,7 +866,7 @@ async function httpFetch(url, options) {
863
866
  details,
864
867
  requestId,
865
868
  retryAfter.seconds,
866
- errorFamily
869
+ canonicalCommerceError
867
870
  );
868
871
  const method2 = (requestInit.method || "GET").toUpperCase();
869
872
  const isTerminal = error instanceof RateLimitError || error instanceof UsageLimitError;
@@ -1041,15 +1044,15 @@ var HttpClient = class {
1041
1044
  throw err;
1042
1045
  }
1043
1046
  }
1044
- buildUrl(endpoint2, options) {
1045
- if (!options) return endpoint2;
1047
+ buildUrl(endpoint, options) {
1048
+ if (!options) return endpoint;
1046
1049
  const { fallbackLocale, ...rest } = options;
1047
1050
  const queryOptions = fallbackLocale === void 0 ? rest : {
1048
1051
  ...rest,
1049
1052
  "fallback-locale": fallbackLocale === false ? "none" : fallbackLocale
1050
1053
  };
1051
1054
  const queryString = stringify(queryOptions, { addQueryPrefix: true });
1052
- return queryString ? `${endpoint2}${queryString}` : endpoint2;
1055
+ return queryString ? `${endpoint}${queryString}` : endpoint;
1053
1056
  }
1054
1057
  assertJsonResponse(response) {
1055
1058
  const contentType = response.headers.get("content-type");
@@ -1198,39 +1201,39 @@ var ReadOnlyCollectionClient = class extends HttpClient {
1198
1201
  from(collection) {
1199
1202
  return new ReadOnlyCollectionQueryBuilder(this, collection);
1200
1203
  }
1201
- async requestFind(endpoint2, options) {
1202
- const url = this.buildUrl(endpoint2, options);
1204
+ async requestFind(endpoint, options) {
1205
+ const url = this.buildUrl(endpoint, options);
1203
1206
  const response = await this.fetchWithTracking(url, {
1204
1207
  ...this.defaultOptions,
1205
1208
  method: "GET"
1206
1209
  });
1207
1210
  return this.parseFindResponse(response);
1208
1211
  }
1209
- async requestFindEndpoint(endpoint2, data) {
1210
- const response = await this.fetchWithTracking(endpoint2, {
1212
+ async requestFindEndpoint(endpoint, data) {
1213
+ const response = await this.fetchWithTracking(endpoint, {
1211
1214
  ...this.defaultOptions,
1212
1215
  method: "POST",
1213
1216
  body: data ? JSON.stringify(data) : void 0
1214
1217
  });
1215
1218
  return this.parseFindResponse(response);
1216
1219
  }
1217
- async requestFindEndpointGet(endpoint2) {
1218
- const response = await this.fetchWithTracking(endpoint2, {
1220
+ async requestFindEndpointGet(endpoint) {
1221
+ const response = await this.fetchWithTracking(endpoint, {
1219
1222
  ...this.defaultOptions,
1220
1223
  method: "GET"
1221
1224
  });
1222
1225
  return this.parseFindResponse(response);
1223
1226
  }
1224
- async requestFindById(endpoint2, options) {
1225
- const url = this.buildUrl(endpoint2, options);
1227
+ async requestFindById(endpoint, options) {
1228
+ const url = this.buildUrl(endpoint, options);
1226
1229
  const response = await this.fetchWithTracking(url, {
1227
1230
  ...this.defaultOptions,
1228
1231
  method: "GET"
1229
1232
  });
1230
1233
  return this.parseDocumentResponse(response);
1231
1234
  }
1232
- async requestCount(endpoint2, options) {
1233
- const url = this.buildUrl(endpoint2, options);
1235
+ async requestCount(endpoint, options) {
1236
+ const url = this.buildUrl(endpoint, options);
1234
1237
  const response = await this.fetchWithTracking(url, {
1235
1238
  ...this.defaultOptions,
1236
1239
  method: "GET"
@@ -1240,7 +1243,7 @@ var ReadOnlyCollectionClient = class extends HttpClient {
1240
1243
  };
1241
1244
 
1242
1245
  // src/core/api/parse-response.ts
1243
- async function parseApiResponse(response, endpoint2, options = {}) {
1246
+ async function parseApiResponse(response, endpoint, options = {}) {
1244
1247
  if (response.status === 204 || response.headers.get("content-length") === "0") {
1245
1248
  return void 0;
1246
1249
  }
@@ -1249,7 +1252,7 @@ async function parseApiResponse(response, endpoint2, options = {}) {
1249
1252
  parsed = await response.json();
1250
1253
  } catch {
1251
1254
  throw createApiError(
1252
- `Invalid JSON response from ${endpoint2}`,
1255
+ `Invalid JSON response from ${endpoint}`,
1253
1256
  response.status,
1254
1257
  void 0,
1255
1258
  "Server returned an invalid response.",
@@ -1299,6 +1302,7 @@ async function parseApiResponse(response, endpoint2, options = {}) {
1299
1302
 
1300
1303
  // ../../internal/http-contracts/src/index.ts
1301
1304
  var operationBrand = /* @__PURE__ */ Symbol("sdk-http-operation");
1305
+ var PORTONE_STANDARD_ACTION_CAPABILITIES_MEDIA_TYPE = "application/vnd.01software.portone-action-capabilities.v1+json";
1302
1306
  var operation = (collection, method, path, errorFamily) => Object.freeze({
1303
1307
  [operationBrand]: true,
1304
1308
  collection,
@@ -1307,6 +1311,10 @@ var operation = (collection, method, path, errorFamily) => Object.freeze({
1307
1311
  ...errorFamily && { errorFamily }
1308
1312
  });
1309
1313
  var strictCommerceOperation = (collection, method, path) => operation(collection, method, path, "commerce");
1314
+ var selectiveCommerceOperation = (collection, method, path, typedCommerceErrorReasons) => Object.freeze({
1315
+ ...operation(collection, method, path),
1316
+ typedCommerceErrorReasons: Object.freeze([...typedCommerceErrorReasons])
1317
+ });
1310
1318
  var COMMERCE_ENDPOINT_OPERATIONS = {
1311
1319
  products: {
1312
1320
  detail: operation("products", "get", "/detail"),
@@ -1338,7 +1346,12 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
1338
1346
  confirmPayment: operation("orders", "post", "/confirm-payment"),
1339
1347
  byPayment: operation("orders", "get", "/by-payment"),
1340
1348
  cancel: operation("orders", "post", "/cancel"),
1341
- resolveCancelRefund: operation("orders", "post", "/resolve-cancel-refund"),
1349
+ resolveCancelRefund: selectiveCommerceOperation(
1350
+ "orders",
1351
+ "post",
1352
+ "/resolve-cancel-refund",
1353
+ ["managed_refund_attestation_forbidden"]
1354
+ ),
1342
1355
  checkout: strictCommerceOperation("orders", "post", "/checkout"),
1343
1356
  createFulfillment: operation("orders", "post", "/create-fulfillment"),
1344
1357
  updateFulfillment: operation("orders", "post", "/update-fulfillment")
@@ -1347,6 +1360,11 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
1347
1360
  detail: strictCommerceOperation("checkouts", "post", "/detail"),
1348
1361
  query: strictCommerceOperation("checkouts", "post", "/query")
1349
1362
  },
1363
+ draftOrders: {
1364
+ calculate: operation("draft-orders", "post", "/:id/calculate"),
1365
+ complete: operation("draft-orders", "post", "/:id/complete"),
1366
+ finalize: operation("draft-orders", "post", "/:id/finalize")
1367
+ },
1350
1368
  transactions: {
1351
1369
  update: operation("transactions", "post", "/update")
1352
1370
  },
@@ -1409,6 +1427,16 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
1409
1427
  "payment-sessions",
1410
1428
  "post",
1411
1429
  "/server/confirm"
1430
+ ),
1431
+ findPortOne: strictCommerceOperation(
1432
+ "payment-sessions",
1433
+ "post",
1434
+ "/server/portone/find"
1435
+ ),
1436
+ reconcilePortOne: strictCommerceOperation(
1437
+ "payment-sessions",
1438
+ "post",
1439
+ "/server/portone/reconcile"
1412
1440
  )
1413
1441
  },
1414
1442
  custom: {
@@ -1428,12 +1456,34 @@ var COMMERCE_ENDPOINT_OPERATIONS = {
1428
1456
  validate: operation("discounts", "post", "/validate")
1429
1457
  }
1430
1458
  };
1459
+ var portOneStandardActionCapabilityOperations = /* @__PURE__ */ new Set([
1460
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.create,
1461
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.retrieve,
1462
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.create,
1463
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.retrieve
1464
+ ]);
1431
1465
  var MEMBERSHIP_ENDPOINT_OPERATIONS = {
1432
1466
  subscribe: operation(null, "post", "/memberships/subscribe"),
1433
1467
  cancel: operation(null, "post", "/memberships/:id/cancel"),
1434
- me: operation(null, "get", "/memberships/me")
1468
+ me: operation(null, "get", "/memberships/me"),
1469
+ chargeResult: operation(null, "post", "/memberships/:id/charge-result"),
1470
+ refundResult: operation(null, "post", "/memberships/:id/refund-result"),
1471
+ revoke: operation(null, "post", "/memberships/:id/revoke")
1472
+ };
1473
+ var CUSTOMER_GROUP_ENDPOINT_OPERATIONS = {
1474
+ members: operation("customer-groups", "get", "/:id/members"),
1475
+ grant: operation("customer-groups", "post", "/:id/members/grant"),
1476
+ revoke: operation("customer-groups", "post", "/:id/members/revoke")
1477
+ };
1478
+ var EMBEDDED_ADMIN_ENDPOINT_OPERATIONS = {
1479
+ show: operation("tenants", "get", "/embedded-admin-app"),
1480
+ configure: operation("tenants", "patch", "/embedded-admin-app"),
1481
+ disable: operation("tenants", "delete", "/embedded-admin-app")
1435
1482
  };
1436
1483
  var EVENTS_ENDPOINT_OPERATIONS = {
1484
+ events: {
1485
+ convertSchedule: operation("events", "post", "/:id/schedule")
1486
+ },
1437
1487
  occurrences: {
1438
1488
  range: operation("event-occurrences", "get", "/range"),
1439
1489
  admissionAvailability: operation(
@@ -1474,46 +1524,51 @@ function resolveOperationMethod(descriptor) {
1474
1524
  }
1475
1525
 
1476
1526
  // src/core/api/endpoints.ts
1477
- var endpoint = (method, path) => ({ method, path });
1478
1527
  var sharedEndpoint = (descriptor) => descriptor;
1479
- function resolveOperationMethod2(descriptor) {
1480
- return "collection" in descriptor ? resolveOperationMethod(descriptor) : descriptor.method;
1481
- }
1482
1528
  function resolveOperationErrorFamily(descriptor) {
1483
- return "collection" in descriptor ? descriptor.errorFamily : void 0;
1484
- }
1485
- function resolveOperationPath2(descriptor, options) {
1486
- if ("collection" in descriptor) {
1487
- return resolveOperationPath(descriptor, options);
1488
- }
1489
- const query = options?.query?.toString().replace(/^\?/, "");
1490
- return query ? `${descriptor.path}?${query}` : descriptor.path;
1529
+ return descriptor.errorFamily;
1491
1530
  }
1492
1531
  var COMMERCE_ENDPOINTS = {
1493
1532
  products: {
1494
- detail: endpoint("GET", "/api/products/detail"),
1495
- detailCatalog: endpoint("GET", "/api/products/detail/catalog"),
1496
- listingGroups: endpoint("GET", "/api/products/listing-groups"),
1497
- listingGroupsCatalog: endpoint(
1498
- "GET",
1499
- "/api/products/listing-groups/catalog"
1533
+ detail: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.products.detail),
1534
+ detailCatalog: sharedEndpoint(
1535
+ COMMERCE_ENDPOINT_OPERATIONS.products.detailCatalog
1536
+ ),
1537
+ listingGroups: sharedEndpoint(
1538
+ COMMERCE_ENDPOINT_OPERATIONS.products.listingGroups
1539
+ ),
1540
+ listingGroupsCatalog: sharedEndpoint(
1541
+ COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsCatalog
1542
+ ),
1543
+ // The manifest keeps the GET read and the POST body-query apart; this
1544
+ // semantic name has always resolved to the GET one.
1545
+ listingGroupsQuery: sharedEndpoint(
1546
+ COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsQueryGet
1547
+ ),
1548
+ listingGroupsQueryCatalog: sharedEndpoint(
1549
+ COMMERCE_ENDPOINT_OPERATIONS.products.listingGroupsQueryCatalog
1500
1550
  ),
1501
- listingGroupsQuery: endpoint("GET", "/api/products/listing-groups/query"),
1502
- listingGroupsQueryCatalog: endpoint(
1503
- "GET",
1504
- "/api/products/listing-groups/query/catalog"
1551
+ stockCheck: sharedEndpoint(
1552
+ COMMERCE_ENDPOINT_OPERATIONS.products.stockCheck
1505
1553
  ),
1506
- stockCheck: endpoint("POST", "/api/products/stock-check"),
1507
- stockSnapshot: endpoint("GET", "/api/products/stock"),
1508
- upsert: endpoint("POST", "/api/products/upsert")
1554
+ stockSnapshot: sharedEndpoint(
1555
+ COMMERCE_ENDPOINT_OPERATIONS.products.stockSnapshot
1556
+ ),
1557
+ upsert: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.products.upsert)
1509
1558
  },
1510
1559
  orders: {
1511
- update: endpoint("POST", "/api/orders/update"),
1512
- byPayment: endpoint("GET", "/api/orders/by-payment"),
1513
- cancel: endpoint("POST", "/api/orders/cancel"),
1514
- resolveCancelRefund: endpoint("POST", "/api/orders/resolve-cancel-refund"),
1515
- createFulfillment: endpoint("POST", "/api/orders/create-fulfillment"),
1516
- updateFulfillment: endpoint("POST", "/api/orders/update-fulfillment")
1560
+ update: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.update),
1561
+ byPayment: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.byPayment),
1562
+ cancel: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.cancel),
1563
+ resolveCancelRefund: sharedEndpoint(
1564
+ COMMERCE_ENDPOINT_OPERATIONS.orders.resolveCancelRefund
1565
+ ),
1566
+ createFulfillment: sharedEndpoint(
1567
+ COMMERCE_ENDPOINT_OPERATIONS.orders.createFulfillment
1568
+ ),
1569
+ updateFulfillment: sharedEndpoint(
1570
+ COMMERCE_ENDPOINT_OPERATIONS.orders.updateFulfillment
1571
+ )
1517
1572
  },
1518
1573
  checkouts: {
1519
1574
  create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.orders.checkout),
@@ -1525,18 +1580,21 @@ var COMMERCE_ENDPOINTS = {
1525
1580
  }
1526
1581
  },
1527
1582
  returns: {
1528
- create: endpoint("POST", "/api/returns/create"),
1529
- update: endpoint("POST", "/api/returns/update"),
1530
- returnRefund: endpoint("POST", "/api/returns/return-refund")
1583
+ create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.returns.create),
1584
+ update: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.returns.update),
1585
+ returnRefund: sharedEndpoint(
1586
+ COMMERCE_ENDPOINT_OPERATIONS.returns.returnRefund
1587
+ )
1531
1588
  },
1532
1589
  fulfillmentOrders: {
1533
- prepare: endpoint(
1534
- "POST",
1535
- "/api/fulfillment-orders/prepare-fulfillment-order"
1590
+ prepare: sharedEndpoint(
1591
+ COMMERCE_ENDPOINT_OPERATIONS.fulfillmentOrders.prepare
1536
1592
  )
1537
1593
  },
1538
1594
  fulfillments: {
1539
- bulkImport: endpoint("POST", "/api/fulfillments/bulk-import")
1595
+ bulkImport: sharedEndpoint(
1596
+ COMMERCE_ENDPOINT_OPERATIONS.fulfillments.bulkImport
1597
+ )
1540
1598
  },
1541
1599
  carts: {
1542
1600
  create: sharedEndpoint(COMMERCE_ENDPOINT_OPERATIONS.carts.create),
@@ -1601,42 +1659,21 @@ var COMMERCE_ENDPOINTS = {
1601
1659
  ),
1602
1660
  confirm: sharedEndpoint(
1603
1661
  COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.confirm
1662
+ ),
1663
+ findPortOne: sharedEndpoint(
1664
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.findPortOne
1665
+ ),
1666
+ reconcilePortOne: sharedEndpoint(
1667
+ COMMERCE_ENDPOINT_OPERATIONS.paymentSessions.server.reconcilePortOne
1604
1668
  )
1605
1669
  }
1606
1670
  },
1607
1671
  shippingPolicies: {
1608
- calculate: endpoint("POST", "/api/shipping-policies/calculate")
1672
+ calculate: sharedEndpoint(
1673
+ COMMERCE_ENDPOINT_OPERATIONS.shippingPolicies.calculate
1674
+ )
1609
1675
  }
1610
1676
  };
1611
- function isCommerceEndpointDescriptor(value) {
1612
- if (!value || typeof value !== "object") return false;
1613
- const candidate = value;
1614
- return typeof candidate.method === "string" && typeof candidate.path === "string";
1615
- }
1616
- function flattenEndpointPaths(value) {
1617
- if (isCommerceEndpointDescriptor(value)) return [resolveOperationPath2(value)];
1618
- if (!value || typeof value !== "object") return [];
1619
- return Object.values(value).flatMap((entry) => flattenEndpointPaths(entry));
1620
- }
1621
- function flattenEndpointDescriptors(value) {
1622
- if (isCommerceEndpointDescriptor(value)) return [value];
1623
- if (!value || typeof value !== "object") return [];
1624
- return Object.values(value).flatMap(
1625
- (entry) => flattenEndpointDescriptors(entry)
1626
- );
1627
- }
1628
- var COMMERCE_ENDPOINT_PATHS = flattenEndpointPaths(COMMERCE_ENDPOINTS);
1629
- var COMMERCE_ENDPOINT_DESCRIPTORS = flattenEndpointDescriptors(COMMERCE_ENDPOINTS);
1630
- var MEMBERSHIP_ENDPOINTS = {
1631
- subscribe: "/api/memberships/subscribe",
1632
- cancel: (id) => `/api/memberships/${id}/cancel`,
1633
- me: "/api/memberships/me"
1634
- };
1635
- var MEMBERSHIP_ENDPOINT_PATHS = [
1636
- MEMBERSHIP_ENDPOINTS.subscribe,
1637
- MEMBERSHIP_ENDPOINTS.cancel(":id"),
1638
- MEMBERSHIP_ENDPOINTS.me
1639
- ];
1640
1677
 
1641
1678
  // src/core/api/customer-scoped-api.ts
1642
1679
  var CustomerScopedTransport = class {
@@ -1657,10 +1694,10 @@ var CustomerScopedTransport = class {
1657
1694
  this.onUnauthorized = options.onUnauthorized;
1658
1695
  this.onRequestId = options.onRequestId;
1659
1696
  }
1660
- async request(endpoint2, options) {
1697
+ async request(endpoint, options) {
1661
1698
  const token = typeof this.customerToken === "function" ? this.customerToken() : this.customerToken;
1662
1699
  try {
1663
- const response = await httpFetch(endpoint2, {
1700
+ const response = await httpFetch(endpoint, {
1664
1701
  method: options.method,
1665
1702
  apiUrl: this.apiUrl,
1666
1703
  publishableKey: this.publishableKey,
@@ -1674,7 +1711,7 @@ var CustomerScopedTransport = class {
1674
1711
  ...options.errorFamily && { errorFamily: options.errorFamily }
1675
1712
  });
1676
1713
  this.onRequestId?.(response.headers.get("x-request-id") ?? null);
1677
- return parseApiResponse(response, endpoint2, {
1714
+ return parseApiResponse(response, endpoint, {
1678
1715
  errorFamily: options.errorFamily
1679
1716
  });
1680
1717
  } catch (err) {
@@ -1688,12 +1725,12 @@ var CustomerScopedApi = class {
1688
1725
  constructor(apiName, options) {
1689
1726
  this.transport = new CustomerScopedTransport(apiName, options);
1690
1727
  }
1691
- requestEndpoint(endpoint2, body, options) {
1692
- return this.transport.request(resolveOperationPath2(endpoint2, options), {
1693
- method: resolveOperationMethod2(endpoint2),
1728
+ requestEndpoint(endpoint, body, options) {
1729
+ return this.transport.request(resolveOperationPath(endpoint, options), {
1730
+ method: resolveOperationMethod(endpoint),
1694
1731
  body,
1695
1732
  headers: options?.headers,
1696
- errorFamily: resolveOperationErrorFamily(endpoint2)
1733
+ errorFamily: resolveOperationErrorFamily(endpoint)
1697
1734
  });
1698
1735
  }
1699
1736
  };
@@ -1701,8 +1738,8 @@ var RawCustomerScopedApi = class {
1701
1738
  constructor(apiName, options) {
1702
1739
  this.transport = new CustomerScopedTransport(apiName, options);
1703
1740
  }
1704
- request(endpoint2, options) {
1705
- return this.transport.request(endpoint2, options);
1741
+ request(endpoint, options) {
1742
+ return this.transport.request(endpoint, options);
1706
1743
  }
1707
1744
  };
1708
1745
 
@@ -1777,11 +1814,11 @@ var CommunityClient = class extends RawCustomerScopedApi {
1777
1814
  if (params.page !== void 0) urlParams.set("page", String(params.page));
1778
1815
  return `/api/comments/public?${urlParams.toString()}`;
1779
1816
  }
1780
- async execute(endpoint2, method, body) {
1781
- return this.request(endpoint2, { method, body });
1817
+ async execute(endpoint, method, body) {
1818
+ return this.request(endpoint, { method, body });
1782
1819
  }
1783
- async executeDoc(endpoint2, method, body) {
1784
- const response = await this.execute(endpoint2, method, body);
1820
+ async executeDoc(endpoint, method, body) {
1821
+ const response = await this.execute(endpoint, method, body);
1785
1822
  return unwrapPayloadDoc(response);
1786
1823
  }
1787
1824
  createPost(params) {
@@ -2047,8 +2084,8 @@ var CommunityClient = class extends RawCustomerScopedApi {
2047
2084
 
2048
2085
  // src/core/content/content-client.ts
2049
2086
  var PublicContentApi = class extends RawCustomerScopedApi {
2050
- get(endpoint2) {
2051
- return this.request(endpoint2, { method: "GET" });
2087
+ get(endpoint) {
2088
+ return this.request(endpoint, { method: "GET" });
2052
2089
  }
2053
2090
  };
2054
2091
  function appendCommonParams(search, options, defaultSort) {
@@ -2146,7 +2183,10 @@ var ORDER_PRIMARY_DISPLAY_STATUSES = [
2146
2183
  ];
2147
2184
  var CHECKOUT_STATUSES = ["open", "placed", "abandoned"];
2148
2185
  var ABANDONED_REASONS = ["expired", "customer", "operator"];
2149
- var PAYMENT_PROVIDERS = ["toss", "portone"];
2186
+ function managedPaymentProviders(providers) {
2187
+ return providers;
2188
+ }
2189
+ var PAYMENT_PROVIDERS = managedPaymentProviders(["toss", "portone"]);
2150
2190
  var PAYMENT_PROVIDER_STATUSES = [
2151
2191
  "requires_payment_method",
2152
2192
  "requires_action",
@@ -2194,6 +2234,26 @@ function invalidCanonicalResponse(resource, details) {
2194
2234
  function fail(resource, path, expected) {
2195
2235
  invalidCanonicalResponse(resource, { path, expected });
2196
2236
  }
2237
+ function providerMismatch(resource, expected, actual, operation2) {
2238
+ if (operation2 === "cancel") {
2239
+ const method = {
2240
+ toss: "cancel()",
2241
+ portone: "cancelPortOne()"
2242
+ }[actual];
2243
+ throw createApiError(
2244
+ `Canonical ${resource} response provider mismatch: expected ${expected}, received ${actual}.`,
2245
+ 502,
2246
+ { resource, path: "$.provider", expected, actual },
2247
+ `The server applied the cancellation to a ${actual} PaymentSession, but this SDK method expects ${expected}.`,
2248
+ `Re-invoke ${method} with the same idempotencyKey; do not create a new key.`
2249
+ );
2250
+ }
2251
+ invalidCanonicalResponse(resource, {
2252
+ path: "$.provider",
2253
+ expected,
2254
+ actual
2255
+ });
2256
+ }
2197
2257
  function record(value, resource, path) {
2198
2258
  if (!value || typeof value !== "object" || Array.isArray(value)) {
2199
2259
  fail(resource, path, "object");
@@ -2229,6 +2289,9 @@ function nullableString(value, resource, path) {
2229
2289
  fail(resource, path, "string or null");
2230
2290
  }
2231
2291
  }
2292
+ function nullableNonEmptyString(value, resource, path) {
2293
+ if (value !== null) string(value, resource, path);
2294
+ }
2232
2295
  function cartLineQuantity(value, resource, path) {
2233
2296
  const quantity = positiveInteger(value, resource, path);
2234
2297
  if (quantity > MAX_CART_LINE_QUANTITY) {
@@ -2738,8 +2801,8 @@ function validatePublicOrderItem(value, resource, path) {
2738
2801
  validateReference(item.variant, resource, `${path}.variant`);
2739
2802
  }
2740
2803
  string(item.productTitle, resource, `${path}.productTitle`);
2741
- nullableString(item.variantTitle, resource, `${path}.variantTitle`);
2742
- nullableString(item.sku, resource, `${path}.sku`);
2804
+ nullableNonEmptyString(item.variantTitle, resource, `${path}.variantTitle`);
2805
+ nullableNonEmptyString(item.sku, resource, `${path}.sku`);
2743
2806
  validateAdmissions(item.admissions, resource, `${path}.admissions`);
2744
2807
  const quantity = positiveInteger(item.quantity, resource, `${path}.quantity`);
2745
2808
  const unitPrice = safeMinor(
@@ -2990,8 +3053,8 @@ function validateCheckoutItem(value, resource, path) {
2990
3053
  validateReference(item.product, resource, `${path}.product`);
2991
3054
  validateReference(item.variant, resource, `${path}.variant`);
2992
3055
  string(item.productTitle, resource, `${path}.productTitle`);
2993
- nullableString(item.variantTitle, resource, `${path}.variantTitle`);
2994
- nullableString(item.sku, resource, `${path}.sku`);
3056
+ nullableNonEmptyString(item.variantTitle, resource, `${path}.variantTitle`);
3057
+ nullableNonEmptyString(item.sku, resource, `${path}.sku`);
2995
3058
  validateAdmissions(item.admissions, resource, `${path}.admissions`);
2996
3059
  cartLineQuantity(item.quantity, resource, `${path}.quantity`);
2997
3060
  const unitPrice = safeMinor(
@@ -3323,11 +3386,21 @@ function validateNextAction(value, provider, resource, path) {
3323
3386
  const action = record(value, resource, path);
3324
3387
  if (provider === "portone") {
3325
3388
  if (action.type === "portone_sdk") {
3389
+ const hasCancelable = "cancelable" in action;
3390
+ const hasRetryPolicy = "retryPolicy" in action;
3391
+ if (hasCancelable !== hasRetryPolicy) {
3392
+ fail(
3393
+ resource,
3394
+ path,
3395
+ "exact released action or exact negotiated capability action"
3396
+ );
3397
+ }
3326
3398
  exact(
3327
3399
  action,
3328
3400
  [
3329
3401
  "type",
3330
3402
  "version",
3403
+ ...hasCancelable ? ["cancelable", "retryPolicy"] : [],
3331
3404
  "storeId",
3332
3405
  "channelKey",
3333
3406
  "paymentId",
@@ -3342,6 +3415,11 @@ function validateNextAction(value, provider, resource, path) {
3342
3415
  path
3343
3416
  );
3344
3417
  if (action.version !== "v2") fail(resource, `${path}.version`, "v2");
3418
+ if (hasCancelable && action.cancelable !== false)
3419
+ fail(resource, `${path}.cancelable`, "false");
3420
+ if (hasRetryPolicy && action.retryPolicy !== "same_action_only") {
3421
+ fail(resource, `${path}.retryPolicy`, "same_action_only");
3422
+ }
3345
3423
  for (const key of ["storeId", "channelKey"]) {
3346
3424
  boundedString(action[key], 255, resource, `${path}.${key}`);
3347
3425
  }
@@ -3388,6 +3466,10 @@ function validateNextAction(value, provider, resource, path) {
3388
3466
  "HTTPS or loopback URL without embedded credentials"
3389
3467
  );
3390
3468
  }
3469
+ if (!hasCancelable) {
3470
+ action.cancelable = false;
3471
+ action.retryPolicy = "same_action_only";
3472
+ }
3391
3473
  return;
3392
3474
  }
3393
3475
  exact(action, ["type", "checkoutUrl"], [], resource, path);
@@ -3464,7 +3546,7 @@ function validateNextAction(value, provider, resource, path) {
3464
3546
  if (action.customerEmail !== void 0)
3465
3547
  validateCustomer({ email: action.customerEmail }, resource, path);
3466
3548
  }
3467
- function validatePublicPaymentSession(value, expectedProvider) {
3549
+ function validatePublicPaymentSession(value, expectedProvider, operation2) {
3468
3550
  const resource = "PaymentSession";
3469
3551
  const session = record(value, resource, "$");
3470
3552
  const provider = enumValue(
@@ -3473,9 +3555,6 @@ function validatePublicPaymentSession(value, expectedProvider) {
3473
3555
  resource,
3474
3556
  "$.provider"
3475
3557
  );
3476
- if (expectedProvider !== void 0 && provider !== expectedProvider) {
3477
- fail(resource, "$.provider", expectedProvider);
3478
- }
3479
3558
  const status = enumValue(
3480
3559
  session.providerStatus,
3481
3560
  PAYMENT_PROVIDER_STATUSES,
@@ -3679,44 +3758,128 @@ function validatePublicPaymentSession(value, expectedProvider) {
3679
3758
  "$.cancellationReason"
3680
3759
  );
3681
3760
  }
3761
+ if (expectedProvider !== void 0 && provider !== expectedProvider) {
3762
+ if (operation2 === "cancel" && status !== "canceling" && status !== "canceled") {
3763
+ fail(
3764
+ resource,
3765
+ "$.providerStatus",
3766
+ "canceling or canceled after a successful cancellation"
3767
+ );
3768
+ }
3769
+ providerMismatch(resource, expectedProvider, provider, operation2);
3770
+ }
3682
3771
  return value;
3683
3772
  }
3684
3773
 
3685
3774
  // src/core/customer/customer-auth.ts
3686
3775
  var DEFAULT_TIMEOUT2 = 15e3;
3687
- function safeGetItem(key) {
3776
+ var REFRESH_LOCK_TIMEOUT = DEFAULT_TIMEOUT2 * 3;
3777
+ var SESSION_RECORD_VERSION = 1;
3778
+ var STORAGE_UNUSABLE = /* @__PURE__ */ Symbol("storage-unusable");
3779
+ function canonicalApiIdentity(apiUrl) {
3688
3780
  try {
3689
- return localStorage.getItem(key);
3781
+ const url = new URL(apiUrl);
3782
+ return `${url.protocol.toLowerCase()}//${url.host.toLowerCase()}${url.pathname.replace(/\/+$/, "")}`;
3783
+ } catch {
3784
+ return apiUrl.trim().toLowerCase().replace(/\/+$/, "");
3785
+ }
3786
+ }
3787
+ function buildRefreshLockName(apiUrl, publishableKey, storageKey) {
3788
+ return `01software:customer-auth-refresh:${canonicalApiIdentity(apiUrl)}:${publishableKey}:${storageKey}`;
3789
+ }
3790
+ function getRefreshLockRequester() {
3791
+ try {
3792
+ if (typeof navigator === "undefined") return null;
3793
+ const locks = navigator.locks;
3794
+ if (!locks || typeof locks.request !== "function") return null;
3795
+ return (name, options, callback) => locks.request(
3796
+ name,
3797
+ options,
3798
+ () => callback()
3799
+ );
3690
3800
  } catch {
3691
3801
  return null;
3692
3802
  }
3693
3803
  }
3804
+ function parseSessionRecord(raw) {
3805
+ try {
3806
+ const parsed = JSON.parse(raw);
3807
+ if (!parsed || typeof parsed !== "object") return null;
3808
+ const candidate = parsed;
3809
+ if (candidate.v !== SESSION_RECORD_VERSION) return null;
3810
+ if (typeof candidate.rev !== "number" || !Number.isFinite(candidate.rev)) {
3811
+ return null;
3812
+ }
3813
+ const token = candidate.token;
3814
+ const refreshToken = candidate.refreshToken;
3815
+ if (token !== null && typeof token !== "string") return null;
3816
+ if (refreshToken !== null && typeof refreshToken !== "string") return null;
3817
+ return {
3818
+ v: SESSION_RECORD_VERSION,
3819
+ rev: candidate.rev,
3820
+ token,
3821
+ refreshToken
3822
+ };
3823
+ } catch {
3824
+ return null;
3825
+ }
3826
+ }
3827
+ function readSessionRecord(recordKey, mirrorKey, mirrorRefreshKey) {
3828
+ let raw;
3829
+ try {
3830
+ raw = localStorage.getItem(recordKey);
3831
+ } catch {
3832
+ return STORAGE_UNUSABLE;
3833
+ }
3834
+ if (raw !== null) {
3835
+ const parsed = parseSessionRecord(raw);
3836
+ return parsed ? { record: parsed, stored: true } : STORAGE_UNUSABLE;
3837
+ }
3838
+ try {
3839
+ return {
3840
+ record: {
3841
+ v: SESSION_RECORD_VERSION,
3842
+ rev: 0,
3843
+ token: localStorage.getItem(mirrorKey),
3844
+ refreshToken: localStorage.getItem(mirrorRefreshKey)
3845
+ },
3846
+ stored: false
3847
+ };
3848
+ } catch {
3849
+ return STORAGE_UNUSABLE;
3850
+ }
3851
+ }
3694
3852
  var CustomerAuth = class {
3695
3853
  constructor(publishableKey, options, apiUrl) {
3696
3854
  this.refreshPromise = null;
3855
+ /** Canonical record key; null unless persistence is active in a browser. */
3856
+ this.persistRecordKey = null;
3857
+ /** Advisory single-value access mirror, plus the retired refresh mirror. */
3858
+ this.persistMirrorKey = null;
3859
+ this.persistMirrorRefreshKey = null;
3860
+ /** Cross-context refresh lock name; null when there is no shared storage. */
3861
+ this.refreshLockName = null;
3862
+ /** Revision of the canonical record this instance last observed. */
3863
+ this.sessionRev = 0;
3697
3864
  this.publishableKey = publishableKey;
3698
3865
  this.baseUrl = resolveApiUrl(apiUrl);
3699
3866
  const persist = options?.persist ?? true;
3700
3867
  if (persist) {
3701
3868
  const key = typeof persist === "string" ? persist : "customer-token";
3702
- const refreshKey = `${key}:refresh`;
3703
3869
  const isBrowser = typeof window !== "undefined";
3704
- this.token = isBrowser ? safeGetItem(key) : null;
3705
- this.refreshTokenValue = isBrowser ? safeGetItem(refreshKey) : null;
3706
- this.onTokenChange = isBrowser ? (token) => {
3707
- try {
3708
- if (token) localStorage.setItem(key, token);
3709
- else localStorage.removeItem(key);
3710
- } catch {
3711
- }
3712
- } : void 0;
3713
- this.onRefreshTokenChange = isBrowser ? (refreshToken) => {
3714
- try {
3715
- if (refreshToken) localStorage.setItem(refreshKey, refreshToken);
3716
- else localStorage.removeItem(refreshKey);
3717
- } catch {
3718
- }
3719
- } : void 0;
3870
+ this.token = null;
3871
+ this.refreshTokenValue = null;
3872
+ if (isBrowser) {
3873
+ this.persistRecordKey = `${key}:session`;
3874
+ this.persistMirrorKey = key;
3875
+ this.persistMirrorRefreshKey = `${key}:refresh`;
3876
+ this.refreshLockName = buildRefreshLockName(
3877
+ this.baseUrl,
3878
+ publishableKey,
3879
+ key
3880
+ );
3881
+ this.syncFromPersistedStorage();
3882
+ }
3720
3883
  } else {
3721
3884
  this.token = options?.token ?? null;
3722
3885
  this.refreshTokenValue = options?.refreshToken ?? null;
@@ -3744,66 +3907,299 @@ var CustomerAuth = class {
3744
3907
  body: JSON.stringify(data)
3745
3908
  }
3746
3909
  );
3747
- this.setToken(result.token);
3748
- if (result.refreshToken) this.setRefreshToken(result.refreshToken);
3910
+ if (result.refreshToken) this.setSession(result.token, result.refreshToken);
3911
+ else this.setToken(result.token);
3749
3912
  return result;
3750
3913
  }
3751
3914
  /**
3752
3915
  * Refresh the current token. Uses a refresh token when available, otherwise
3753
3916
  * falls back to the legacy bearer-token compatibility path.
3917
+ *
3918
+ * Refresh tokens are single-use, so a persisted session shared by several
3919
+ * tabs or SDK instances must rotate at most once: concurrent rotations of the
3920
+ * same token are replay from the server's point of view and revoke the whole
3921
+ * family. Persisted browser sessions therefore serialize rotation across
3922
+ * contexts and re-read the canonical persisted credentials inside the lock.
3754
3923
  */
3755
3924
  async refreshToken() {
3925
+ this.syncFromPersistedStorage();
3756
3926
  if (!this.refreshTokenValue && !this.token) {
3757
3927
  throw new ApiError("Not authenticated", 401);
3758
3928
  }
3759
3929
  if (this.refreshPromise) return this.refreshPromise;
3760
- this.refreshPromise = this._doRefreshToken();
3930
+ const intended = this.currentFence();
3931
+ this.refreshPromise = this.rotateAcrossContexts(intended);
3761
3932
  try {
3762
3933
  return await this.refreshPromise;
3763
3934
  } finally {
3764
3935
  this.refreshPromise = null;
3765
3936
  }
3766
3937
  }
3767
- async _doRefreshToken() {
3768
- const init = this.refreshTokenValue ? {
3938
+ /**
3939
+ * Re-read the canonical persisted session. In persisted mode the shared
3940
+ * record — not this instance's memory — is the source of truth, so another
3941
+ * context's rotation, login, or logout is observed before the next credential
3942
+ * use. Change callbacks are deliberately not invoked: this is a read.
3943
+ */
3944
+ syncFromPersistedStorage() {
3945
+ const canonical = this.readCanonicalSession();
3946
+ if (canonical === STORAGE_UNUSABLE) {
3947
+ this.disablePersistence();
3948
+ return;
3949
+ }
3950
+ if (!canonical) return;
3951
+ this.sessionRev = canonical.record.rev;
3952
+ this.token = canonical.record.token;
3953
+ this.refreshTokenValue = canonical.record.refreshToken;
3954
+ }
3955
+ /** `null` when this instance does not persist. */
3956
+ readCanonicalSession() {
3957
+ if (!this.persistRecordKey || !this.persistMirrorKey || !this.persistMirrorRefreshKey) {
3958
+ return null;
3959
+ }
3960
+ return readSessionRecord(
3961
+ this.persistRecordKey,
3962
+ this.persistMirrorKey,
3963
+ this.persistMirrorRefreshKey
3964
+ );
3965
+ }
3966
+ /**
3967
+ * Publish a change to canonical storage.
3968
+ *
3969
+ * The canonical pair is re-read immediately before the single record write so
3970
+ * that a field this call does not change is taken from that read rather than
3971
+ * from this instance's possibly stale copy. That merge is BEST-EFFORT: it
3972
+ * removes the stale counterpart an instance would otherwise carry, but it is
3973
+ * not a compare-and-swap. Web Storage offers none, and the synchronous public
3974
+ * mutators cannot await the refresh lease, so a commit that lands between
3975
+ * this read and the store operation is lost by ordering — including the
3976
+ * counterpart just read, which is then republished stale. The window is one
3977
+ * store operation wide and the server remains authoritative; see
3978
+ * `CUST-AUTH-033` for the stated limit.
3979
+ *
3980
+ * Returns `false` only when `requireLiveSession` refused a write because the
3981
+ * canonical session had already been cleared.
3982
+ */
3983
+ commitPersistedSession(change, options) {
3984
+ const recordKey = this.persistRecordKey;
3985
+ const mirrorKey = this.persistMirrorKey;
3986
+ const mirrorRefreshKey = this.persistMirrorRefreshKey;
3987
+ const canonical = this.readCanonicalSession();
3988
+ if (canonical === null || !recordKey || !mirrorKey || !mirrorRefreshKey) {
3989
+ return true;
3990
+ }
3991
+ if (canonical === STORAGE_UNUSABLE) {
3992
+ this.disablePersistence();
3993
+ return true;
3994
+ }
3995
+ const current = canonical.record;
3996
+ if (options?.requireLiveSession && !current.token && !current.refreshToken) {
3997
+ return false;
3998
+ }
3999
+ const next = {
4000
+ v: SESSION_RECORD_VERSION,
4001
+ rev: current.rev + 1,
4002
+ token: "token" in change ? change.token ?? null : current.token,
4003
+ refreshToken: "refreshToken" in change ? change.refreshToken ?? null : current.refreshToken
4004
+ };
4005
+ if (
4006
+ // Only a state that is genuinely persisted can already say what this
4007
+ // write would say. An absent record reads as an empty pair, and treating
4008
+ // that as a no-op would drop the very tombstone that stops a later
4009
+ // migration from resurrecting the session out of the earlier layout.
4010
+ canonical.stored && next.token === current.token && next.refreshToken === current.refreshToken
4011
+ ) {
4012
+ this.sessionRev = current.rev;
4013
+ this.token = current.token;
4014
+ this.refreshTokenValue = current.refreshToken;
4015
+ return true;
4016
+ }
4017
+ try {
4018
+ localStorage.setItem(recordKey, JSON.stringify(next));
4019
+ } catch {
4020
+ this.disablePersistence();
4021
+ return true;
4022
+ }
4023
+ this.sessionRev = next.rev;
4024
+ this.token = next.token;
4025
+ this.refreshTokenValue = next.refreshToken;
4026
+ try {
4027
+ localStorage.removeItem(mirrorRefreshKey);
4028
+ } catch {
4029
+ return true;
4030
+ }
4031
+ try {
4032
+ if (next.token) localStorage.setItem(mirrorKey, next.token);
4033
+ else localStorage.removeItem(mirrorKey);
4034
+ } catch {
4035
+ }
4036
+ return true;
4037
+ }
4038
+ /**
4039
+ * Storage rejected a write or a read, so it is not a canonical store for this
4040
+ * instance. Fall back to the memory-only session and take no lease: there is
4041
+ * no shared state left to coordinate. Nothing is purged — the record commit
4042
+ * is atomic, so no inconsistent state was published, and another context's
4043
+ * valid session must not be destroyed by this instance's local failure.
4044
+ */
4045
+ disablePersistence() {
4046
+ this.persistRecordKey = null;
4047
+ this.persistMirrorKey = null;
4048
+ this.persistMirrorRefreshKey = null;
4049
+ this.refreshLockName = null;
4050
+ }
4051
+ /**
4052
+ * Compare the canonical record with the fence this call captured before it
4053
+ * left. Every persisted decision — rotate, converge, or fail closed — goes
4054
+ * through this one comparison. The revision catches a logout, a fresh login,
4055
+ * or a manual setter even when token values coincide; the value comparison
4056
+ * additionally catches a mirror-only writer that cannot bump a revision.
4057
+ */
4058
+ classifyCanonicalState(intended) {
4059
+ if (!this.persistRecordKey) return "current";
4060
+ this.syncFromPersistedStorage();
4061
+ if (!this.persistRecordKey) return "current";
4062
+ if (!this.token && !this.refreshTokenValue) return "cleared";
4063
+ if (intended.refreshToken !== null) {
4064
+ return this.refreshTokenValue === intended.refreshToken ? "current" : "superseded";
4065
+ }
4066
+ return this.token === intended.token && !this.refreshTokenValue ? "current" : "superseded";
4067
+ }
4068
+ /** The canonical persisted pair, as a refresh result. */
4069
+ canonicalPair(token) {
4070
+ return {
4071
+ token,
4072
+ ...this.refreshTokenValue ? { refreshToken: this.refreshTokenValue } : {}
4073
+ };
4074
+ }
4075
+ /** Current credentials plus the revision they were observed at. */
4076
+ currentFence() {
4077
+ return { token: this.token, refreshToken: this.refreshTokenValue };
4078
+ }
4079
+ /**
4080
+ * Clear an access credential a 401 just proved unusable — but only while it
4081
+ * is still the canonical one. A rotation committed by another context while
4082
+ * the request was in flight is newer than this 401, so it must survive; the
4083
+ * instance adopts it instead. The refresh entry is never touched here: a
4084
+ * rejected access token says nothing about the refresh family.
4085
+ */
4086
+ invalidateAccessToken(spentToken) {
4087
+ this.syncFromPersistedStorage();
4088
+ if (this.token !== spentToken) return;
4089
+ this.setToken(null);
4090
+ }
4091
+ /** Commit both credentials in one canonical write. */
4092
+ setSession(token, refreshToken, options) {
4093
+ this.token = token;
4094
+ this.refreshTokenValue = refreshToken;
4095
+ const committed = this.commitPersistedSession(
4096
+ { token, refreshToken },
4097
+ options
4098
+ );
4099
+ if (!committed) return false;
4100
+ this.onTokenChange?.(this.token);
4101
+ this.onRefreshTokenChange?.(this.refreshTokenValue);
4102
+ return true;
4103
+ }
4104
+ /**
4105
+ * Serialize rotation against every other context sharing the same storage
4106
+ * key and API/tenant identity. Coordination is best-effort: when it is
4107
+ * unavailable or times out the rotation still happens, so server-side reuse
4108
+ * detection is never bypassed by a client that failed to coordinate.
4109
+ */
4110
+ async rotateAcrossContexts(intended) {
4111
+ const lockName = this.refreshLockName;
4112
+ const requestLock = lockName ? getRefreshLockRequester() : null;
4113
+ if (!lockName || !requestLock) {
4114
+ return this.rotateWithCanonicalState(intended);
4115
+ }
4116
+ const controller = new AbortController();
4117
+ const timer = setTimeout(() => controller.abort(), REFRESH_LOCK_TIMEOUT);
4118
+ let entered = false;
4119
+ try {
4120
+ return await requestLock(
4121
+ lockName,
4122
+ { signal: controller.signal },
4123
+ async () => {
4124
+ entered = true;
4125
+ return this.rotateWithCanonicalState(intended);
4126
+ }
4127
+ );
4128
+ } catch (error) {
4129
+ if (entered) throw error;
4130
+ return this.rotateWithCanonicalState(intended);
4131
+ } finally {
4132
+ clearTimeout(timer);
4133
+ }
4134
+ }
4135
+ /**
4136
+ * Decide, against freshly re-read canonical state, whether this context still
4137
+ * owns the rotation.
4138
+ */
4139
+ async rotateWithCanonicalState(intended) {
4140
+ let fence = intended;
4141
+ for (let attempt = 0; attempt < 2; attempt += 1) {
4142
+ const state = this.classifyCanonicalState(fence);
4143
+ if (state === "cleared") throw new ApiError("Not authenticated", 401);
4144
+ if (state === "current") return this._doRefreshToken(fence);
4145
+ if (this.token) return this.canonicalPair(this.token);
4146
+ fence = this.currentFence();
4147
+ }
4148
+ throw new ApiError("Not authenticated", 401);
4149
+ }
4150
+ async _doRefreshToken(intended) {
4151
+ const init = intended.refreshToken ? {
3769
4152
  method: "POST",
3770
- body: JSON.stringify({ refreshToken: this.refreshTokenValue })
4153
+ body: JSON.stringify({ refreshToken: intended.refreshToken })
3771
4154
  } : {
3772
4155
  method: "POST",
3773
- headers: { Authorization: `Bearer ${this.token}` }
4156
+ headers: { Authorization: `Bearer ${intended.token}` }
3774
4157
  };
3775
4158
  const result = await this.requestJson(
3776
4159
  "/api/customers/refresh",
3777
4160
  init
3778
4161
  );
3779
- this.setToken(result.token);
3780
- if (result.refreshToken) this.setRefreshToken(result.refreshToken);
3781
- return result;
4162
+ const state = this.classifyCanonicalState(intended);
4163
+ if (state === "cleared") throw new ApiError("Not authenticated", 401);
4164
+ if (state === "superseded") {
4165
+ if (this.token) return this.canonicalPair(this.token);
4166
+ throw new ApiError("Not authenticated", 401);
4167
+ }
4168
+ const replacedAccessToken = this.token !== null && this.token !== intended.token ? this.token : null;
4169
+ const nextToken = replacedAccessToken ?? result.token;
4170
+ const nextRefresh = result.refreshToken ?? null;
4171
+ if (!this.setSession(nextToken, nextRefresh, { requireLiveSession: true })) {
4172
+ throw new ApiError("Not authenticated", 401);
4173
+ }
4174
+ return {
4175
+ token: nextToken,
4176
+ ...nextRefresh ? { refreshToken: nextRefresh } : {}
4177
+ };
3782
4178
  }
3783
4179
  /**
3784
4180
  * Clear the stored token
3785
4181
  */
3786
4182
  logout() {
3787
- this.setToken(null);
3788
- this.setRefreshToken(null);
4183
+ this.setSession(null, null);
3789
4184
  }
3790
4185
  /**
3791
4186
  * Get the current authenticated customer's profile
3792
4187
  */
3793
4188
  async me() {
3794
- if (!this.token) return null;
4189
+ const token = this.getToken();
4190
+ if (!token) return null;
3795
4191
  try {
3796
4192
  const data = await this.requestJson(
3797
4193
  "/api/customers/me",
3798
4194
  {
3799
4195
  method: "GET",
3800
- headers: { Authorization: `Bearer ${this.token}` }
4196
+ headers: { Authorization: `Bearer ${token}` }
3801
4197
  }
3802
4198
  );
3803
4199
  return data.customer ?? null;
3804
4200
  } catch (error) {
3805
4201
  if (error instanceof SDKError && error.status === 401) {
3806
- this.setToken(null);
4202
+ this.invalidateAccessToken(token);
3807
4203
  return null;
3808
4204
  }
3809
4205
  throw error;
@@ -3831,12 +4227,13 @@ var CustomerAuth = class {
3831
4227
  * Update the authenticated customer's profile (name, phone, marketingConsent)
3832
4228
  */
3833
4229
  async updateProfile(data) {
3834
- if (!this.token) throw new ApiError("Not authenticated", 401);
4230
+ const token = this.getToken();
4231
+ if (!token) throw new ApiError("Not authenticated", 401);
3835
4232
  const result = await this.requestJson(
3836
4233
  "/api/customers/me",
3837
4234
  {
3838
4235
  method: "PATCH",
3839
- headers: { Authorization: `Bearer ${this.token}` },
4236
+ headers: { Authorization: `Bearer ${token}` },
3840
4237
  body: JSON.stringify(data)
3841
4238
  }
3842
4239
  );
@@ -3846,10 +4243,11 @@ var CustomerAuth = class {
3846
4243
  * Change the password of the currently authenticated customer
3847
4244
  */
3848
4245
  async changePassword(currentPassword, newPassword) {
3849
- if (!this.token) throw new ApiError("Not authenticated", 401);
4246
+ const token = this.getToken();
4247
+ if (!token) throw new ApiError("Not authenticated", 401);
3850
4248
  await this.requestJson("/api/customers/change-password", {
3851
4249
  method: "POST",
3852
- headers: { Authorization: `Bearer ${this.token}` },
4250
+ headers: { Authorization: `Bearer ${token}` },
3853
4251
  body: JSON.stringify({ currentPassword, newPassword })
3854
4252
  });
3855
4253
  }
@@ -3857,7 +4255,8 @@ var CustomerAuth = class {
3857
4255
  * Get the authenticated customer's orders with pagination and optional status filter
3858
4256
  */
3859
4257
  async getMyOrders(options) {
3860
- if (!this.token) throw new ApiError("Not authenticated", 401);
4258
+ const token = this.getToken();
4259
+ if (!token) throw new ApiError("Not authenticated", 401);
3861
4260
  const params = new URLSearchParams();
3862
4261
  if (options?.page) params.set("page", String(options.page));
3863
4262
  if (options?.limit) params.set("limit", String(options.limit));
@@ -3879,21 +4278,27 @@ var CustomerAuth = class {
3879
4278
  `/api/customers/me/orders${qs ? `?${qs}` : ""}`,
3880
4279
  {
3881
4280
  method: "GET",
3882
- headers: { Authorization: `Bearer ${this.token}` }
4281
+ headers: { Authorization: `Bearer ${token}` }
3883
4282
  }
3884
4283
  );
3885
4284
  return validatePublicOrderList(result);
3886
4285
  }
3887
4286
  /**
3888
- * Get the current token (or null if not authenticated)
4287
+ * Get the current token (or null if not authenticated).
4288
+ *
4289
+ * In persisted mode this re-reads shared storage first, so a rotation or
4290
+ * logout performed by another tab is observed before the token is used.
3889
4291
  */
3890
4292
  getToken() {
4293
+ this.syncFromPersistedStorage();
3891
4294
  return this.token;
3892
4295
  }
3893
4296
  /**
3894
- * Get the current refresh token (or null if unavailable)
4297
+ * Get the current refresh token (or null if unavailable). Persisted mode
4298
+ * re-reads shared storage first, like {@link CustomerAuth.getToken}.
3895
4299
  */
3896
4300
  getRefreshToken() {
4301
+ this.syncFromPersistedStorage();
3897
4302
  return this.refreshTokenValue;
3898
4303
  }
3899
4304
  /**
@@ -3901,6 +4306,7 @@ var CustomerAuth = class {
3901
4306
  */
3902
4307
  setToken(token) {
3903
4308
  this.token = token;
4309
+ this.commitPersistedSession({ token });
3904
4310
  this.onTokenChange?.(token);
3905
4311
  }
3906
4312
  /**
@@ -3908,13 +4314,14 @@ var CustomerAuth = class {
3908
4314
  */
3909
4315
  setRefreshToken(refreshToken) {
3910
4316
  this.refreshTokenValue = refreshToken;
4317
+ this.commitPersistedSession({ refreshToken });
3911
4318
  this.onRefreshTokenChange?.(refreshToken);
3912
4319
  }
3913
4320
  /**
3914
4321
  * Check if the customer is currently authenticated
3915
4322
  */
3916
4323
  isAuthenticated() {
3917
- return this.token !== null;
4324
+ return this.getToken() !== null;
3918
4325
  }
3919
4326
  /**
3920
4327
  * Internal: make a request with timeout and error handling.
@@ -4122,8 +4529,8 @@ var CartApi = class extends CustomerScopedApi {
4122
4529
  endpoint(key) {
4123
4530
  return this.serverTransport ? COMMERCE_ENDPOINTS.carts.server[key] : COMMERCE_ENDPOINTS.carts[key];
4124
4531
  }
4125
- execute(endpoint2, body, idempotencyKey) {
4126
- return this.requestEndpoint(endpoint2, body, {
4532
+ execute(endpoint, body, idempotencyKey) {
4533
+ return this.requestEndpoint(endpoint, body, {
4127
4534
  ...idempotencyKey && {
4128
4535
  headers: { "X-Idempotency-Key": idempotencyKey }
4129
4536
  }
@@ -4140,26 +4547,26 @@ var CartApi = class extends CustomerScopedApi {
4140
4547
  );
4141
4548
  }
4142
4549
  mine(params = {}) {
4143
- const endpoint2 = this.endpoint("mine");
4144
- return this.execute(endpoint2, params).then(
4550
+ const endpoint = this.endpoint("mine");
4551
+ return this.execute(endpoint, params).then(
4145
4552
  (value) => validateCartMineResult(value)
4146
4553
  );
4147
4554
  }
4148
4555
  merge(params) {
4149
4556
  const { idempotencyKey, ...body } = params;
4150
4557
  assertCommerceIdempotencyKey(idempotencyKey);
4151
- const endpoint2 = this.endpoint("merge");
4152
- return this.execute(endpoint2, body, idempotencyKey).then(
4558
+ const endpoint = this.endpoint("merge");
4559
+ return this.execute(endpoint, body, idempotencyKey).then(
4153
4560
  (value) => validateCartMergeResult(value)
4154
4561
  );
4155
4562
  }
4156
4563
  addItem(params) {
4157
4564
  return this.mutate(this.endpoint("addItem"), params);
4158
4565
  }
4159
- mutate(endpoint2, params) {
4566
+ mutate(endpoint, params) {
4160
4567
  const { idempotencyKey, ...body } = params;
4161
4568
  assertCommerceIdempotencyKey(idempotencyKey);
4162
- return this.execute(endpoint2, body, idempotencyKey).then(
4569
+ return this.execute(endpoint, body, idempotencyKey).then(
4163
4570
  (value) => validateCartMutationResult(value)
4164
4571
  );
4165
4572
  }
@@ -4185,8 +4592,8 @@ var CheckoutApi = class extends CustomerScopedApi {
4185
4592
  constructor(options) {
4186
4593
  super("CheckoutApi", { ...options, requiresCredential: false });
4187
4594
  }
4188
- post(endpoint2, body, idempotencyKey) {
4189
- return this.requestEndpoint(endpoint2, body, {
4595
+ post(endpoint, body, idempotencyKey) {
4596
+ return this.requestEndpoint(endpoint, body, {
4190
4597
  ...idempotencyKey && {
4191
4598
  headers: { "X-Idempotency-Key": idempotencyKey }
4192
4599
  }
@@ -4253,15 +4660,23 @@ var PaymentSessionApi = class extends CustomerScopedApi {
4253
4660
  super("PaymentSessionApi", { ...options, requiresCredential: false });
4254
4661
  this.serverTransport = Boolean(options.secretKey);
4255
4662
  }
4256
- post(endpoint2, body, idempotencyKey) {
4257
- return this.requestEndpoint(endpoint2, body, {
4258
- ...idempotencyKey && {
4259
- headers: { "X-Idempotency-Key": idempotencyKey }
4663
+ post(endpoint, body, idempotencyKey, negotiateStandardActionCapabilities = false) {
4664
+ const headers = {
4665
+ ...idempotencyKey && { "X-Idempotency-Key": idempotencyKey },
4666
+ ...negotiateStandardActionCapabilities && {
4667
+ Accept: PORTONE_STANDARD_ACTION_CAPABILITIES_MEDIA_TYPE
4260
4668
  }
4669
+ };
4670
+ return this.requestEndpoint(endpoint, body, {
4671
+ ...Object.keys(headers).length > 0 && { headers }
4261
4672
  });
4262
4673
  }
4263
- async paymentSession(result, expectedProvider) {
4264
- return validatePublicPaymentSession(await result, expectedProvider);
4674
+ async paymentSession(result, expectedProvider, operation2) {
4675
+ return validatePublicPaymentSession(
4676
+ await result,
4677
+ expectedProvider,
4678
+ operation2
4679
+ );
4265
4680
  }
4266
4681
  create(params) {
4267
4682
  const { idempotencyKey, ...body } = params;
@@ -4282,41 +4697,44 @@ var PaymentSessionApi = class extends CustomerScopedApi {
4282
4697
  this.post(
4283
4698
  this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.create : COMMERCE_ENDPOINTS.paymentSessions.create,
4284
4699
  body,
4285
- idempotencyKey
4700
+ idempotencyKey,
4701
+ body.providerOptions.profile === "standard_v2"
4286
4702
  ),
4287
4703
  "portone"
4288
4704
  );
4289
4705
  }
4290
4706
  retrieve(params) {
4291
- const endpoint2 = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
4707
+ const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
4292
4708
  return this.paymentSession(
4293
- this.post(endpoint2, params),
4709
+ this.post(endpoint, params),
4294
4710
  "toss"
4295
4711
  );
4296
4712
  }
4297
4713
  retrievePortOne(params) {
4298
- const endpoint2 = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
4714
+ const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.retrieve : COMMERCE_ENDPOINTS.paymentSessions.retrieve;
4299
4715
  return this.paymentSession(
4300
- this.post(endpoint2, params),
4716
+ this.post(endpoint, params, void 0, true),
4301
4717
  "portone"
4302
4718
  );
4303
4719
  }
4304
4720
  cancel(params) {
4305
4721
  const { idempotencyKey, ...body } = params;
4306
4722
  assertCommerceIdempotencyKey(idempotencyKey);
4307
- const endpoint2 = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
4723
+ const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
4308
4724
  return this.paymentSession(
4309
- this.post(endpoint2, body, idempotencyKey),
4310
- "toss"
4725
+ this.post(endpoint, body, idempotencyKey),
4726
+ "toss",
4727
+ "cancel"
4311
4728
  );
4312
4729
  }
4313
4730
  cancelPortOne(params) {
4314
4731
  const { idempotencyKey, ...body } = params;
4315
4732
  assertCommerceIdempotencyKey(idempotencyKey);
4316
- const endpoint2 = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
4733
+ const endpoint = this.serverTransport ? COMMERCE_ENDPOINTS.paymentSessions.server.cancel : COMMERCE_ENDPOINTS.paymentSessions.cancel;
4317
4734
  return this.paymentSession(
4318
- this.post(endpoint2, body, idempotencyKey),
4319
- "portone"
4735
+ this.post(endpoint, body, idempotencyKey),
4736
+ "portone",
4737
+ "cancel"
4320
4738
  );
4321
4739
  }
4322
4740
  confirm(params) {
@@ -4394,9 +4812,9 @@ function resolveVariantImageItems(variant, poolById) {
4394
4812
  if (pooled) resolved.push(pooled);
4395
4813
  continue;
4396
4814
  }
4397
- const mediaId2 = getMediaId(entry);
4398
- if (mediaId2) {
4399
- const pooled = poolById.get(mediaId2);
4815
+ const mediaId = getMediaId(entry);
4816
+ if (mediaId) {
4817
+ const pooled = poolById.get(mediaId);
4400
4818
  if (pooled) {
4401
4819
  resolved.push(pooled);
4402
4820
  }
@@ -4587,11 +5005,11 @@ function getFirstMediaId(values) {
4587
5005
  }
4588
5006
  return null;
4589
5007
  }
4590
- function inProductMediaPool(product, mediaId2) {
4591
- if (mediaId2 == null) return null;
5008
+ function inProductMediaPool(product, mediaId) {
5009
+ if (mediaId == null) return null;
4592
5010
  const images = Array.isArray(product?.images) ? product.images : [];
4593
5011
  for (const image of images) {
4594
- if (extractEntityId(image) === mediaId2) return mediaId2;
5012
+ if (extractEntityId(image) === mediaId) return mediaId;
4595
5013
  }
4596
5014
  return null;
4597
5015
  }
@@ -6172,8 +6590,8 @@ function isProductUpsertFieldValidationErrorBody(value) {
6172
6590
 
6173
6591
  // src/core/commerce/commerce-client.ts
6174
6592
  var BrowserCommerceApi = class extends CustomerScopedApi {
6175
- requestEndpoint(endpoint2, body, options) {
6176
- return super.requestEndpoint(endpoint2, body, options);
6593
+ requestEndpoint(endpoint, body, options) {
6594
+ return super.requestEndpoint(endpoint, body, options);
6177
6595
  }
6178
6596
  };
6179
6597
  var CommerceClient = class {
@@ -6373,11 +6791,11 @@ var EventsClient = class {
6373
6791
  { useCustomerAuth: false }
6374
6792
  );
6375
6793
  }
6376
- async execute(endpoint2, method, body, options = {}) {
6794
+ async execute(endpoint, method, body, options = {}) {
6377
6795
  const useCustomerAuth = options.useCustomerAuth === true;
6378
6796
  const token = useCustomerAuth ? typeof this.customerToken === "function" ? this.customerToken() : this.customerToken : void 0;
6379
6797
  try {
6380
- const response = await httpFetch(endpoint2, {
6798
+ const response = await httpFetch(endpoint, {
6381
6799
  method,
6382
6800
  apiUrl: this.apiUrl,
6383
6801
  publishableKey: this.publishableKey,
@@ -6386,7 +6804,7 @@ var EventsClient = class {
6386
6804
  ...body !== void 0 && { body: JSON.stringify(body) }
6387
6805
  });
6388
6806
  this.onRequestId?.(response.headers.get("x-request-id") ?? null);
6389
- return parseApiResponse(response, endpoint2);
6807
+ return parseApiResponse(response, endpoint);
6390
6808
  } catch (err) {
6391
6809
  const id = err instanceof SDKError ? err.requestId ?? null : null;
6392
6810
  this.onRequestId?.(id);
@@ -6644,13 +7062,106 @@ function mergeProductDetailWithStock(catalog, snapshot) {
6644
7062
  };
6645
7063
  }
6646
7064
 
7065
+ // src/core/webhook/validation.ts
7066
+ function isValidWebhookEventData(data) {
7067
+ if (typeof data !== "object" || data === null) return false;
7068
+ const event = data;
7069
+ return typeof event.collection === "string" && typeof event.operation === "string" && event.operation.length > 0 && typeof event.data === "object" && event.data !== null;
7070
+ }
7071
+
7072
+ // src/core/webhook/receiver.ts
7073
+ async function verifySignature(rawBody, secret, signature, timestamp2, deliveryId) {
7074
+ const encoder = new TextEncoder();
7075
+ const signedPrefix = encoder.encode(`${timestamp2}.${deliveryId}.`);
7076
+ const signedPayload = new Uint8Array(signedPrefix.length + rawBody.length);
7077
+ signedPayload.set(signedPrefix);
7078
+ signedPayload.set(rawBody, signedPrefix.length);
7079
+ const key = await crypto.subtle.importKey(
7080
+ "raw",
7081
+ encoder.encode(secret),
7082
+ { name: "HMAC", hash: "SHA-256" },
7083
+ false,
7084
+ ["verify"]
7085
+ );
7086
+ if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
7087
+ return false;
7088
+ }
7089
+ const sigBytes = new Uint8Array(
7090
+ (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
7091
+ );
7092
+ return crypto.subtle.verify("HMAC", key, sigBytes, signedPayload);
7093
+ }
7094
+ function timestampIsFresh(timestamp2, toleranceSeconds) {
7095
+ if (!/^\d+$/.test(timestamp2)) return false;
7096
+ const timestampMs = Number(timestamp2);
7097
+ if (!Number.isFinite(timestampMs)) return false;
7098
+ const skewMs = Math.abs(Date.now() - timestampMs);
7099
+ return skewMs <= toleranceSeconds * 1e3;
7100
+ }
7101
+ var MAX_TOLERANCE_SECONDS = 86400;
7102
+ function toleranceIsValid(toleranceSeconds) {
7103
+ return typeof toleranceSeconds === "number" && Number.isFinite(toleranceSeconds) && toleranceSeconds >= 0 && toleranceSeconds <= MAX_TOLERANCE_SECONDS;
7104
+ }
7105
+ async function processWebhookRequest(request, handler, verify) {
7106
+ try {
7107
+ const rawBody = new Uint8Array(await request.arrayBuffer());
7108
+ const verificationFailure = await verify?.(rawBody);
7109
+ if (verificationFailure) return verificationFailure;
7110
+ const body = JSON.parse(new TextDecoder().decode(rawBody));
7111
+ if (!isValidWebhookEventData(body)) {
7112
+ return new Response(
7113
+ JSON.stringify({ error: "Invalid webhook event format" }),
7114
+ { status: 400, headers: { "Content-Type": "application/json" } }
7115
+ );
7116
+ }
7117
+ await handler(body);
7118
+ return new Response(
7119
+ JSON.stringify({ success: true, message: "Webhook processed" }),
7120
+ { status: 200, headers: { "Content-Type": "application/json" } }
7121
+ );
7122
+ } catch (error) {
7123
+ console.error("Webhook processing error:", error);
7124
+ return new Response(JSON.stringify({ error: "Internal server error" }), {
7125
+ status: 500,
7126
+ headers: { "Content-Type": "application/json" }
7127
+ });
7128
+ }
7129
+ }
7130
+ async function handleSignedWebhook(request, handler, options) {
7131
+ const secret = options?.secret;
7132
+ const configuredTolerance = options?.toleranceSeconds;
7133
+ const toleranceSeconds = configuredTolerance ?? 300;
7134
+ if (typeof secret !== "string" || secret.trim().length === 0) {
7135
+ return new Response(
7136
+ JSON.stringify({ error: "Webhook signing secret is not configured" }),
7137
+ { status: 500, headers: { "Content-Type": "application/json" } }
7138
+ );
7139
+ }
7140
+ if (!toleranceIsValid(toleranceSeconds)) {
7141
+ return new Response(
7142
+ JSON.stringify({ error: "Webhook signature tolerance is not valid" }),
7143
+ { status: 500, headers: { "Content-Type": "application/json" } }
7144
+ );
7145
+ }
7146
+ return processWebhookRequest(request, handler, async (rawBody) => {
7147
+ const signature = request.headers.get("x-webhook-signature") || "";
7148
+ const timestamp2 = request.headers.get("x-webhook-timestamp") || "";
7149
+ const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
7150
+ const valid = Boolean(timestamp2 && deliveryId) && timestampIsFresh(timestamp2, toleranceSeconds) && await verifySignature(rawBody, secret, signature, timestamp2, deliveryId);
7151
+ if (!valid) {
7152
+ return new Response(
7153
+ JSON.stringify({ error: "Invalid webhook signature" }),
7154
+ { status: 401, headers: { "Content-Type": "application/json" } }
7155
+ );
7156
+ }
7157
+ });
7158
+ }
7159
+
6647
7160
  // src/core/webhook/index.ts
6648
7161
  var ORDER_CHANGED_EVENT_TYPE = "collection.orderChanged";
6649
7162
  var COMMERCE_NOTIFICATION_EVENT_TYPE = "commerce.notification";
6650
7163
  function isValidWebhookEvent(data) {
6651
- if (typeof data !== "object" || data === null) return false;
6652
- const obj = data;
6653
- return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
7164
+ return isValidWebhookEventData(data);
6654
7165
  }
6655
7166
  var COMMERCE_NOTIFICATION_OPERATION = "notification";
6656
7167
  var COMMERCE_NOTIFICATION_EVENTS = [
@@ -6662,7 +7173,13 @@ var COMMERCE_NOTIFICATION_EVENTS = [
6662
7173
  "returnApproved",
6663
7174
  "returnRejected",
6664
7175
  "returnCanceled",
6665
- "returnCompleted"
7176
+ "returnCompleted",
7177
+ "paymentReconciliationRequired",
7178
+ "paymentRefundRequiresAttention",
7179
+ "membershipActivated",
7180
+ "membershipPastDue",
7181
+ "membershipExpired",
7182
+ "membershipCancelled"
6666
7183
  ];
6667
7184
  function isStringOrNumber(value) {
6668
7185
  return typeof value === "string" || typeof value === "number";
@@ -6692,18 +7209,29 @@ function isCommerceNotificationEventName(value) {
6692
7209
  return typeof value === "string" && COMMERCE_NOTIFICATION_EVENTS.includes(value);
6693
7210
  }
6694
7211
  function isCommerceNotificationSourceCollection(value) {
6695
- return value === "orders" || value === "fulfillments" || value === "returns";
7212
+ return value === "orders" || value === "fulfillments" || value === "returns" || value === "payment-sessions" || value === "payment-refund-attempts" || value === "customer-memberships";
6696
7213
  }
6697
7214
  function isCommerceNotificationData(value) {
6698
7215
  if (!isRecord(value)) return false;
6699
7216
  if (value.source !== void 0 && (!isRecord(value.source) || !isCommerceNotificationSourceCollection(value.source.collection) || !isStringOrNumber(value.source.id))) {
6700
7217
  return false;
6701
7218
  }
6702
- 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");
7219
+ 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");
6703
7220
  }
6704
7221
  function isCommerceNotification(value) {
6705
7222
  if (!isRecord(value)) return false;
6706
- 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");
7223
+ 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");
7224
+ }
7225
+ function isPaymentIncidentNotificationData(value, event) {
7226
+ const reconciliation = event === "paymentReconciliationRequired";
7227
+ const expectedCollection = reconciliation ? "payment-sessions" : "payment-refund-attempts";
7228
+ const expectedStatus = reconciliation ? "reconciliation_required" : "requires_attention";
7229
+ const expectedReason = reconciliation ? "orderless_capture" : "local_commit_ambiguous";
7230
+ 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);
7231
+ }
7232
+ function isMembershipNotificationData(value, event) {
7233
+ const expectedStatus = event === "membershipActivated" ? "active" : event === "membershipPastDue" ? "past_due" : event === "membershipExpired" ? "expired" : "cancelled";
7234
+ 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));
6707
7235
  }
6708
7236
  function isWebhookCommerceNotificationChange(value) {
6709
7237
  if (!isRecord(value)) return false;
@@ -6744,6 +7272,18 @@ function isCommerceNotificationWebhookEvent(event) {
6744
7272
  if (!matchesOptionalId(change.sourceId, sourceId)) return false;
6745
7273
  }
6746
7274
  const changeSourceId = isWebhookCommerceNotificationChange(change) ? change.sourceId : void 0;
7275
+ if (notification.event === "paymentReconciliationRequired" || notification.event === "paymentRefundRequiresAttention") {
7276
+ if (!isPaymentIncidentNotificationData(data, notification.event)) {
7277
+ return false;
7278
+ }
7279
+ const incidentData = data;
7280
+ const expectedCollection = notification.event === "paymentReconciliationRequired" ? "payment-sessions" : "payment-refund-attempts";
7281
+ const notificationSourceId = notification.event === "paymentReconciliationRequired" ? notification.paymentSessionId : notification.refundAttemptId;
7282
+ return sourceCollection === expectedCollection && typeof notification.paymentSessionId === "string" && notification.paymentSessionId === incidentData.paymentSessionId && typeof notificationSourceId === "string" && notificationSourceId === incidentData.source.id && matchesOptionalId(changeSourceId, incidentData.source.id);
7283
+ }
7284
+ if (notification.event === "membershipActivated" || notification.event === "membershipPastDue" || notification.event === "membershipExpired" || notification.event === "membershipCancelled") {
7285
+ return sourceCollection === "customer-memberships" && isMembershipNotificationData(data, notification.event) && typeof notification.membershipId === "string" && notification.membershipId === data.membershipId && matchesOptionalId(changeSourceId, data.membershipId);
7286
+ }
6747
7287
  if (notification.event === "orderPaid" || notification.event === "orderCanceled" || notification.event === "orderDelivered") {
6748
7288
  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);
6749
7289
  }
@@ -6812,80 +7352,8 @@ function createCustomerAuthWebhookHandler(handlers) {
6812
7352
  await handlers.unhandled?.(event);
6813
7353
  };
6814
7354
  }
6815
- async function verifySignature(payload, secret, signature, timestamp2, deliveryId) {
6816
- const encoder = new TextEncoder();
6817
- const key = await crypto.subtle.importKey(
6818
- "raw",
6819
- encoder.encode(secret),
6820
- { name: "HMAC", hash: "SHA-256" },
6821
- false,
6822
- ["verify"]
6823
- );
6824
- if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
6825
- return false;
6826
- }
6827
- const sigBytes = new Uint8Array(
6828
- (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
6829
- );
6830
- return crypto.subtle.verify(
6831
- "HMAC",
6832
- key,
6833
- sigBytes,
6834
- encoder.encode(`${timestamp2}.${deliveryId}.${payload}`)
6835
- );
6836
- }
6837
- function timestampIsFresh(timestamp2, toleranceSeconds) {
6838
- if (!/^\d+$/.test(timestamp2)) return false;
6839
- const timestampMs = Number(timestamp2);
6840
- if (!Number.isFinite(timestampMs)) return false;
6841
- const skewMs = Math.abs(Date.now() - timestampMs);
6842
- return skewMs <= toleranceSeconds * 1e3;
6843
- }
6844
7355
  async function handleWebhook(request, handler, options) {
6845
- try {
6846
- const rawBody = await request.text();
6847
- if (options?.secret) {
6848
- const signature = request.headers.get("x-webhook-signature") || "";
6849
- const timestamp2 = request.headers.get("x-webhook-timestamp") || "";
6850
- const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
6851
- const toleranceSeconds = options.toleranceSeconds ?? 300;
6852
- const valid = Boolean(timestamp2 && deliveryId) && timestampIsFresh(timestamp2, toleranceSeconds) && await verifySignature(
6853
- rawBody,
6854
- options.secret,
6855
- signature,
6856
- timestamp2,
6857
- deliveryId
6858
- );
6859
- if (!valid) {
6860
- return new Response(
6861
- JSON.stringify({ error: "Invalid webhook signature" }),
6862
- { status: 401, headers: { "Content-Type": "application/json" } }
6863
- );
6864
- }
6865
- } else {
6866
- console.warn(
6867
- "[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
6868
- );
6869
- }
6870
- const body = JSON.parse(rawBody);
6871
- if (!isValidWebhookEvent(body)) {
6872
- return new Response(
6873
- JSON.stringify({ error: "Invalid webhook event format" }),
6874
- { status: 400, headers: { "Content-Type": "application/json" } }
6875
- );
6876
- }
6877
- await handler(body);
6878
- return new Response(
6879
- JSON.stringify({ success: true, message: "Webhook processed" }),
6880
- { status: 200, headers: { "Content-Type": "application/json" } }
6881
- );
6882
- } catch (error) {
6883
- console.error("Webhook processing error:", error);
6884
- return new Response(JSON.stringify({ error: "Internal server error" }), {
6885
- status: 500,
6886
- headers: { "Content-Type": "application/json" }
6887
- });
6888
- }
7356
+ return handleSignedWebhook(request, handler, options);
6889
7357
  }
6890
7358
  function createTypedWebhookHandler(collection, handler) {
6891
7359
  return async (event) => {
@@ -6925,6 +7393,7 @@ var INTERNAL_COLLECTIONS = [
6925
7393
  "payment-event-inbox",
6926
7394
  "payment-refund-attempts",
6927
7395
  "portone-payment-interactions",
7396
+ "commerce-payment-webhook-routes",
6928
7397
  "commerce-notification-intents",
6929
7398
  "commerce-idempotency-records",
6930
7399
  "product-collection-items",
@@ -6932,6 +7401,7 @@ var INTERNAL_COLLECTIONS = [
6932
7401
  "fulfillment-events",
6933
7402
  "tenant-commerce-config",
6934
7403
  "customer-oauth-clients",
7404
+ "customer-group-grants",
6935
7405
  "api-keys",
6936
7406
  "personal-access-tokens",
6937
7407
  "tenant-entitlements",
@@ -6965,7 +7435,6 @@ var COLLECTIONS = [
6965
7435
  "brands",
6966
7436
  "customer-profiles",
6967
7437
  "customer-profile-stats",
6968
- "discounts",
6969
7438
  "shipping-policies",
6970
7439
  "shipping-zones",
6971
7440
  "documents",
@@ -7041,6 +7510,9 @@ var SERVER_ONLY_COLLECTIONS = [
7041
7510
  "cart-items",
7042
7511
  "checkouts",
7043
7512
  "checkout-items",
7513
+ // Raw discount definitions require a server credential. Browser discount
7514
+ // validation is exposed through the shaped POST /api/discounts/validate path.
7515
+ "discounts",
7044
7516
  "draft-orders",
7045
7517
  "draft-order-items",
7046
7518
  "posts",
@@ -7206,160 +7678,6 @@ var RealtimeConnection = class {
7206
7678
  }
7207
7679
  };
7208
7680
 
7209
- // src/utils/product-media.ts
7210
- function mediaPointerOptionValues(values) {
7211
- return (values ?? []).filter((value) => value != null && value !== "").map((value) => ({
7212
- swatch: {
7213
- type: "media",
7214
- mediaItemId: value
7215
- }
7216
- }));
7217
- }
7218
- function resolveProductMedia(input) {
7219
- const resolved = resolveProductSelectionMedia({
7220
- ...input,
7221
- selectedVariant: input.selectedVariant ?? (input.variantFeaturedMediaItemId ? { images: [input.variantFeaturedMediaItemId] } : null),
7222
- selectedOptionValues: input.selectedOptionValues ?? mediaPointerOptionValues(input.selectedOptionValueMediaItemIds)
7223
- });
7224
- if (resolved.source !== "none") {
7225
- return {
7226
- ...resolved,
7227
- source: resolved.source === "variant_media_selected" ? "variant_featured" : resolved.source
7228
- };
7229
- }
7230
- const display = resolveProductDisplayMedia({
7231
- productMediaPool: input.productMediaPool
7232
- });
7233
- return {
7234
- primaryImage: display.primaryImage,
7235
- images: display.images,
7236
- source: display.source
7237
- };
7238
- }
7239
-
7240
- // src/utils/product-gallery.ts
7241
- function mediaId(value) {
7242
- if (typeof value === "string" || typeof value === "number") {
7243
- return String(value);
7244
- }
7245
- if (typeof value === "object" && value !== null && "id" in value) {
7246
- const id = value.id;
7247
- if (typeof id === "string" || typeof id === "number") return String(id);
7248
- }
7249
- return null;
7250
- }
7251
- function relationshipIds(values) {
7252
- if (!Array.isArray(values)) return [];
7253
- return values.map((value) => mediaId(value)).filter((value) => Boolean(value));
7254
- }
7255
- function buildPoolById2(pool) {
7256
- const poolById = /* @__PURE__ */ new Map();
7257
- for (const item of pool) {
7258
- const id = mediaId(item);
7259
- if (id) poolById.set(id, item);
7260
- }
7261
- return poolById;
7262
- }
7263
- function mediaSetItemsInPool(mediaSet, poolById) {
7264
- const seen = /* @__PURE__ */ new Set();
7265
- const items = [];
7266
- const primaryId = mediaId(mediaSet.primaryMediaItemId);
7267
- if (primaryId) {
7268
- const primary = poolById.get(primaryId);
7269
- if (primary) {
7270
- seen.add(primaryId);
7271
- items.push(primary);
7272
- }
7273
- }
7274
- for (const itemId of relationshipIds(mediaSet.items)) {
7275
- if (seen.has(itemId)) continue;
7276
- const item = poolById.get(itemId);
7277
- if (!item) continue;
7278
- seen.add(itemId);
7279
- items.push(item);
7280
- }
7281
- return items;
7282
- }
7283
- function resolveLegacyMediaSet(input) {
7284
- if (!Array.isArray(input.mediaSets)) return null;
7285
- const pool = input.productMediaPool ?? input.pool ?? [];
7286
- const poolById = buildPoolById2(pool);
7287
- const selectedVariantId = mediaId(input.selectedVariantId);
7288
- if (selectedVariantId) {
7289
- const exactSet = input.mediaSets.find(
7290
- (set) => set.matchType === "exact-variant" && relationshipIds(set.variantIds).includes(selectedVariantId)
7291
- );
7292
- if (exactSet) {
7293
- const images = mediaSetItemsInPool(exactSet, poolById);
7294
- if (images.length > 0) {
7295
- return {
7296
- primaryImage: images[0] ?? null,
7297
- images,
7298
- source: "exact_variant_set"
7299
- };
7300
- }
7301
- }
7302
- }
7303
- const selectedOptionValueIds = new Set(
7304
- relationshipIds(input.selectedOptionValueIds)
7305
- );
7306
- const optionSetMatches = input.mediaSets.filter((set) => set.matchType === "option-values").map((set) => {
7307
- const optionValueIds = relationshipIds(set.optionValueIds);
7308
- if (optionValueIds.length === 0) return null;
7309
- if (!optionValueIds.every((id) => selectedOptionValueIds.has(id))) {
7310
- return null;
7311
- }
7312
- const images = mediaSetItemsInPool(set, poolById);
7313
- if (images.length === 0) return null;
7314
- return { optionValueCount: optionValueIds.length, images };
7315
- }).filter(
7316
- (match) => Boolean(match)
7317
- ).sort((a, b) => b.optionValueCount - a.optionValueCount);
7318
- if (optionSetMatches.length > 0) {
7319
- const images = optionSetMatches[0].images;
7320
- return {
7321
- primaryImage: images[0] ?? null,
7322
- images,
7323
- source: "option_value_set"
7324
- };
7325
- }
7326
- const defaultSet = input.mediaSets.find((set) => set.matchType === "default");
7327
- if (defaultSet) {
7328
- const images = mediaSetItemsInPool(defaultSet, poolById);
7329
- if (images.length > 0) {
7330
- return {
7331
- primaryImage: images[0] ?? null,
7332
- images,
7333
- source: "default_set"
7334
- };
7335
- }
7336
- }
7337
- return {
7338
- primaryImage: null,
7339
- images: [],
7340
- source: "none"
7341
- };
7342
- }
7343
- function resolveProductGallery(input) {
7344
- const selectedVariantId = mediaId(input.selectedVariantId);
7345
- const selectedOptionValueIds = relationshipIds(input.selectedOptionValueIds);
7346
- const selection = resolveProductSelectionMedia({
7347
- ...input,
7348
- productMediaPool: input.productMediaPool ?? input.pool ?? null,
7349
- selectedVariant: input.selectedVariant ?? (selectedVariantId ? { id: selectedVariantId } : null),
7350
- selectedOptionValues: input.selectedOptionValues ?? selectedOptionValueIds.map((id) => ({ id }))
7351
- });
7352
- if (selection.source !== "none") {
7353
- return selection;
7354
- }
7355
- const legacyResolved = resolveLegacyMediaSet(input);
7356
- if (legacyResolved && legacyResolved.source !== "none") return legacyResolved;
7357
- return resolveProductDisplayMedia({
7358
- productMediaPool: input.productMediaPool ?? input.pool ?? null,
7359
- featuredImage: input.featuredImage
7360
- });
7361
- }
7362
-
7363
7681
  // src/utils/image.ts
7364
7682
  var IMAGE_SIZES = [200, 400, 800, 1200, 1600];
7365
7683
  function imageSizeCandidates(sizes) {
@@ -7491,7 +7809,7 @@ function createAnalytics(config) {
7491
7809
  }, destroy() {
7492
7810
  } };
7493
7811
  }
7494
- const endpoint2 = config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`;
7812
+ const endpoint = config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`;
7495
7813
  const respectDnt = config.respectDnt !== false;
7496
7814
  function isDntActive() {
7497
7815
  if (!respectDnt) return false;
@@ -7532,10 +7850,10 @@ function createAnalytics(config) {
7532
7850
  try {
7533
7851
  if (typeof navigator.sendBeacon === "function") {
7534
7852
  const blob = new Blob([body], { type: "text/plain" });
7535
- const sent = navigator.sendBeacon(endpoint2, blob);
7853
+ const sent = navigator.sendBeacon(endpoint, blob);
7536
7854
  if (sent) return;
7537
7855
  }
7538
- fetch(endpoint2, {
7856
+ fetch(endpoint, {
7539
7857
  method: "POST",
7540
7858
  keepalive: true,
7541
7859
  headers: { "Content-Type": "application/json" },
@@ -7792,8 +8110,6 @@ export {
7792
8110
  projectProductToListingShape,
7793
8111
  resolveFeaturedImagePointer,
7794
8112
  resolveProductDisplayMedia,
7795
- resolveProductGallery,
7796
- resolveProductMedia,
7797
8113
  resolveProductSelection,
7798
8114
  resolveProductSelectionFromMatrix,
7799
8115
  resolveProductSelectionMedia,