@cshah18/sdk 4.12.0 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -455,6 +455,8 @@ var CoBuySDK = (function (exports) {
455
455
  debug,
456
456
  events: options.events,
457
457
  performance,
458
+ geo: options.geo,
459
+ device: options.device,
458
460
  };
459
461
  return this.config;
460
462
  }
@@ -822,6 +824,7 @@ var CoBuySDK = (function (exports) {
822
824
  this.currentJoinedGroupId = null;
823
825
  this.currentProductId = null;
824
826
  this.currentSessionId = null;
827
+ this.analyticsClient = null;
825
828
  this.onGroupJoined = null;
826
829
  this.onViewProgress = null;
827
830
  this.socketListenerRegistered = false;
@@ -836,6 +839,7 @@ var CoBuySDK = (function (exports) {
836
839
  /** Handle group member joined socket event and update groups list */
837
840
  this.onGroupMemberJoined = (event) => {
838
841
  const detail = event.detail || {};
842
+ console.log("eventttttt", detail);
839
843
  const productId = detail.product_id;
840
844
  const groupData = detail.group;
841
845
  // Only process if this is for the current product
@@ -868,6 +872,10 @@ var CoBuySDK = (function (exports) {
868
872
  this.apiClient = apiClient;
869
873
  this.injectStyles();
870
874
  }
875
+ /** Set the analytics client for event tracking */
876
+ setAnalyticsClient(client) {
877
+ this.analyticsClient = client;
878
+ }
871
879
  /** Set callback for when a group is joined successfully */
872
880
  setOnGroupJoined(callback) {
873
881
  this.onGroupJoined = callback;
@@ -1125,10 +1133,21 @@ var CoBuySDK = (function (exports) {
1125
1133
  buttonElement.disabled = true;
1126
1134
  const originalText = button.textContent;
1127
1135
  button.textContent = "Joining...";
1136
+ const productId = this.currentProductId || "";
1137
+ if (this.analyticsClient) {
1138
+ this.analyticsClient
1139
+ .trackJoinAttempt(productId, groupId)
1140
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1141
+ }
1128
1142
  try {
1129
1143
  const response = await this.apiClient.joinGroup(groupId);
1130
1144
  if (response.success && response.data) {
1131
1145
  this.logger.info(`[handleJoinGroup] Successfully joined group: ${groupId}`, response.data);
1146
+ if (this.analyticsClient) {
1147
+ this.analyticsClient
1148
+ .trackJoinSuccess(productId, groupId)
1149
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1150
+ }
1132
1151
  // Track the joined group
1133
1152
  this.currentJoinedGroupId = groupId;
1134
1153
  // Close the modal and trigger callback with the full join response data
@@ -1139,12 +1158,22 @@ var CoBuySDK = (function (exports) {
1139
1158
  }
1140
1159
  else {
1141
1160
  this.logger.error("Failed to join group: API response unsuccessful");
1161
+ if (this.analyticsClient) {
1162
+ this.analyticsClient
1163
+ .trackJoinFailure(productId, groupId)
1164
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1165
+ }
1142
1166
  button.textContent = originalText;
1143
1167
  buttonElement.disabled = false;
1144
1168
  }
1145
1169
  }
1146
1170
  catch (error) {
1147
1171
  this.logger.error("Error joining group", error);
1172
+ if (this.analyticsClient) {
1173
+ this.analyticsClient
1174
+ .trackJoinFailure(productId, groupId, "EXCEPTION", error instanceof Error ? error.message : String(error))
1175
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1176
+ }
1148
1177
  button.textContent = originalText;
1149
1178
  buttonElement.disabled = false;
1150
1179
  }
@@ -1165,10 +1194,21 @@ var CoBuySDK = (function (exports) {
1165
1194
  this.setJoinButtonsDisabled(true);
1166
1195
  const originalText = button.textContent;
1167
1196
  button.textContent = "Creating...";
1197
+ const productId = this.currentProductId;
1198
+ if (this.analyticsClient) {
1199
+ this.analyticsClient
1200
+ .trackGroupCreateAttempt(productId)
1201
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1202
+ }
1168
1203
  try {
1169
1204
  const response = await this.apiClient.createAndJoinGroup(this.currentProductId);
1170
1205
  if (response.success && response.data) {
1171
1206
  this.logger.info("Successfully created and joined new group", response.data);
1207
+ if (this.analyticsClient) {
1208
+ this.analyticsClient
1209
+ .trackGroupCreateSuccess(productId, response.data.group.id)
1210
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1211
+ }
1172
1212
  this.currentJoinedGroupId = response.data.group.id;
1173
1213
  this.close();
1174
1214
  if (this.onGroupJoined) {
@@ -1177,6 +1217,11 @@ var CoBuySDK = (function (exports) {
1177
1217
  }
1178
1218
  else {
1179
1219
  this.logger.error("Failed to create group: API response unsuccessful");
1220
+ if (this.analyticsClient) {
1221
+ this.analyticsClient
1222
+ .trackGroupCreateFailure(productId)
1223
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1224
+ }
1180
1225
  button.textContent = originalText;
1181
1226
  buttonElement.disabled = false;
1182
1227
  this.setJoinButtonsDisabled(false);
@@ -1184,6 +1229,11 @@ var CoBuySDK = (function (exports) {
1184
1229
  }
1185
1230
  catch (error) {
1186
1231
  this.logger.error("Error creating group", error);
1232
+ if (this.analyticsClient) {
1233
+ this.analyticsClient
1234
+ .trackGroupCreateFailure(productId, "EXCEPTION", error instanceof Error ? error.message : String(error))
1235
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1236
+ }
1187
1237
  button.textContent = originalText;
1188
1238
  buttonElement.disabled = false;
1189
1239
  this.setJoinButtonsDisabled(false);
@@ -3581,14 +3631,13 @@ var CoBuySDK = (function (exports) {
3581
3631
  document.body.appendChild(this.modalElement);
3582
3632
  // Subscribe to realtime socket events
3583
3633
  this.subscribeToSocketEvents();
3584
- // If socket is available and connected, subscribe to this group
3585
- if (this.socketManager && this.socketManager.isConnected() && this.currentGroupId) {
3586
- this.logger.info(`[LobbyModal] Socket connected, subscribing to group ${this.currentGroupId}`);
3634
+ // Subscribe to the group room for targeted events.
3635
+ // Socket.IO client buffers emits until connected, so this is safe even
3636
+ // if the handshake hasn't completed yet.
3637
+ if (this.socketManager && this.currentGroupId) {
3638
+ this.logger.info(`[LobbyModal] Subscribing to group room ${this.currentGroupId}`);
3587
3639
  this.socketManager.subscribeToGroup(this.currentGroupId);
3588
3640
  }
3589
- else if (this.socketManager && !this.socketManager.isConnected()) {
3590
- this.logger.warn("[LobbyModal] Socket manager not connected yet");
3591
- }
3592
3641
  // Start timers and animations
3593
3642
  this.startTimer();
3594
3643
  this.startActivityAnimation();
@@ -4389,10 +4438,18 @@ var CoBuySDK = (function (exports) {
4389
4438
  /** Handle backend fulfillment notifications */
4390
4439
  this.handleGroupFulfilledEvent = (event) => {
4391
4440
  const detail = event.detail;
4392
- if (!detail || !detail.productId) {
4441
+ if (!detail || !detail.product_id) {
4442
+ return;
4443
+ }
4444
+ if (this.currentProductId && detail.product_id !== this.currentProductId) {
4393
4445
  return;
4394
4446
  }
4395
- if (this.currentProductId && detail.productId !== this.currentProductId) {
4447
+ // Only enter the fulfilled/checkout flow for users who have actively joined a group this
4448
+ // session. currentSessionId is set exclusively in the setOnGroupJoined callback (when the
4449
+ // user completes a join API call). Observers who never joined should re-fetch the primary
4450
+ // group API to display the updated group state instead.
4451
+ if (!this.currentSessionId) {
4452
+ void this.refreshGroupDataFromRealtime();
4396
4453
  return;
4397
4454
  }
4398
4455
  this.processGroupFulfilled(detail);
@@ -4449,12 +4506,21 @@ var CoBuySDK = (function (exports) {
4449
4506
  const groupData = await this.fetchPrimaryGroup(this.currentProductId);
4450
4507
  this.currentGroupData = groupData;
4451
4508
  this.currentGroupId = (groupData === null || groupData === void 0 ? void 0 : groupData.id) || this.currentGroupId;
4452
- // If backend signals completion via counts, reflect it
4509
+ // If backend signals completion via counts, reflect it — but only for actual members.
4510
+ // Observers may see a full group returned by fetchPrimaryGroup; they should NOT enter
4511
+ // the checkout flow. currentSessionId is the membership signal (non-null = joined).
4453
4512
  if (groupData) {
4454
4513
  const participants = Number(groupData.participants_count || 0);
4455
4514
  const max = Number(groupData.max_participants || 0);
4456
- if (max > 0 && participants >= max) {
4515
+ if (max > 0 && participants >= max && this.currentSessionId) {
4457
4516
  this.groupFulfilled = true;
4517
+ // The primary group API returns offline_redemption for members of fulfilled groups.
4518
+ // Extract it here so renderFulfilledSummary shows the "Redeem In-store" link
4519
+ // (matching the UI users see on a full page reload).
4520
+ if (groupData.offline_redemption &&
4521
+ isValidOfflineRedemption(groupData.offline_redemption)) {
4522
+ this.offlineRedemption = groupData.offline_redemption;
4523
+ }
4458
4524
  }
4459
4525
  }
4460
4526
  else {
@@ -4799,6 +4865,9 @@ var CoBuySDK = (function (exports) {
4799
4865
  getGroupListModal() {
4800
4866
  if (!this.groupListModal) {
4801
4867
  this.groupListModal = new GroupListModal(undefined, 8, this.config.debug, this.apiClient);
4868
+ if (this.analyticsClient) {
4869
+ this.groupListModal.setAnalyticsClient(this.analyticsClient);
4870
+ }
4802
4871
  // Set callback to open lobby when a group is successfully joined
4803
4872
  this.groupListModal.setOnGroupJoined((joinData) => {
4804
4873
  var _a;
@@ -5024,6 +5093,11 @@ var CoBuySDK = (function (exports) {
5024
5093
  if (max > 0 && participants >= max) {
5025
5094
  this.groupFulfilled = true;
5026
5095
  this.logger.info("Group is already fulfilled on initial load", { participants, max });
5096
+ if (this.analyticsClient) {
5097
+ this.analyticsClient
5098
+ .trackGroupFullView(options.productId, groupData.id, max)
5099
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5100
+ }
5027
5101
  }
5028
5102
  }
5029
5103
  }
@@ -5089,6 +5163,11 @@ var CoBuySDK = (function (exports) {
5089
5163
  // LOADED state - render widget only if we have group data
5090
5164
  this.createWidget(rewardData, container, options);
5091
5165
  this.logger.info(`Widget rendered for product: ${options.productId}`);
5166
+ if (this.analyticsClient) {
5167
+ this.analyticsClient
5168
+ .trackCreativeView(options.productId)
5169
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5170
+ }
5092
5171
  }
5093
5172
  else {
5094
5173
  // No group data available - hide widget
@@ -5740,7 +5819,7 @@ var CoBuySDK = (function (exports) {
5740
5819
  * Handle CTA button click with analytics and modal opening
5741
5820
  */
5742
5821
  async handleCTAClick(productId) {
5743
- var _a, _b, _c, _d;
5822
+ var _a, _b, _c, _d, _e, _f, _g;
5744
5823
  this.logger.info(`CTA clicked for product: ${productId}`);
5745
5824
  // Track analytics event asynchronously (fire-and-forget)
5746
5825
  if (this.analyticsClient) {
@@ -5758,14 +5837,31 @@ var CoBuySDK = (function (exports) {
5758
5837
  if (this.apiClient && this.currentGroupId) {
5759
5838
  try {
5760
5839
  this.logger.info(`Joining group: ${this.currentGroupId}`);
5840
+ if (this.analyticsClient) {
5841
+ this.analyticsClient
5842
+ .trackJoinAttempt(productId, this.currentGroupId)
5843
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5844
+ }
5761
5845
  const joinResponse = await this.apiClient.joinGroup(this.currentGroupId);
5762
5846
  if (!joinResponse.success) {
5763
5847
  this.logger.error("Failed to join group, modal will not open", joinResponse.error);
5848
+ if (this.analyticsClient) {
5849
+ const errCode = (_c = joinResponse.error) === null || _c === void 0 ? void 0 : _c.code;
5850
+ const errMsg = (_d = joinResponse.error) === null || _d === void 0 ? void 0 : _d.message;
5851
+ this.analyticsClient
5852
+ .trackJoinFailure(productId, this.currentGroupId, errCode, errMsg)
5853
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5854
+ }
5764
5855
  this.setButtonLoadingState(false);
5765
5856
  return; // Don't open modal on error
5766
5857
  }
5767
5858
  groupJoinData = joinResponse.data;
5768
5859
  this.logger.info("Successfully joined group", groupJoinData);
5860
+ if (this.analyticsClient && groupJoinData) {
5861
+ this.analyticsClient
5862
+ .trackJoinSuccess(productId, groupJoinData.group.id)
5863
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5864
+ }
5769
5865
  this.offlineRedemption =
5770
5866
  groupJoinData &&
5771
5867
  groupJoinData.offline_redemption &&
@@ -5789,6 +5885,11 @@ var CoBuySDK = (function (exports) {
5789
5885
  }
5790
5886
  catch (error) {
5791
5887
  this.logger.error("Group join failed, modal will not open", error);
5888
+ if (this.analyticsClient) {
5889
+ this.analyticsClient
5890
+ .trackJoinFailure(productId, (_e = this.currentGroupId) !== null && _e !== void 0 ? _e : undefined, "EXCEPTION", error instanceof Error ? error.message : String(error))
5891
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5892
+ }
5792
5893
  this.setButtonLoadingState(false);
5793
5894
  return; // Don't open modal on error
5794
5895
  }
@@ -5803,7 +5904,7 @@ var CoBuySDK = (function (exports) {
5803
5904
  const progress = Math.round((groupJoinData.group.participants_count / groupJoinData.group.max_participants) * 100);
5804
5905
  // Format discount based on reward type
5805
5906
  let discountText = "";
5806
- if ((_d = (_c = this.currentRewardData) === null || _c === void 0 ? void 0 : _c.reward) === null || _d === void 0 ? void 0 : _d.value) {
5907
+ if ((_g = (_f = this.currentRewardData) === null || _f === void 0 ? void 0 : _f.reward) === null || _g === void 0 ? void 0 : _g.value) {
5807
5908
  const rewardType = this.currentRewardData.reward.type;
5808
5909
  const rewardValue = this.currentRewardData.reward.value;
5809
5910
  if (rewardType === "percentage" || rewardType === "cashback") {
@@ -5843,6 +5944,11 @@ var CoBuySDK = (function (exports) {
5843
5944
  shareMessage: shareMessageFromInvite,
5844
5945
  isLocked: !isGroupFulfilled,
5845
5946
  offlineRedemption: offlineRedemptionFromJoin,
5947
+ onShare: this.analyticsClient
5948
+ ? () => {
5949
+ this.analyticsClient.trackShareClick(productId, groupJoinData.group.id, "other").catch((e) => this.logger.warn("Analytics tracking failed", e));
5950
+ }
5951
+ : undefined,
5846
5952
  activities: [
5847
5953
  {
5848
5954
  emoji: "👤",
@@ -5878,6 +5984,12 @@ var CoBuySDK = (function (exports) {
5878
5984
  },
5879
5985
  ], // Will be populated from real-time data later
5880
5986
  });
5987
+ // Track popup open after modal is launched
5988
+ if (this.analyticsClient) {
5989
+ this.analyticsClient
5990
+ .trackPopupOpen(productId, groupJoinData.group.id)
5991
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5992
+ }
5881
5993
  // Remove loading state after modal opens
5882
5994
  this.setButtonLoadingState(false);
5883
5995
  }
@@ -5984,6 +6096,14 @@ var CoBuySDK = (function (exports) {
5984
6096
  getProductId() {
5985
6097
  return this.currentProductId;
5986
6098
  }
6099
+ /**
6100
+ * Returns the group ID that the current user has joined via this widget, or null if the user
6101
+ * is only an observer (has not actively joined a group this session).
6102
+ * Used by the CoBuy host class to check membership before preparing checkout.
6103
+ */
6104
+ getJoinedGroupId() {
6105
+ return this.currentSessionId ? this.currentGroupId : null;
6106
+ }
5987
6107
  }
5988
6108
 
5989
6109
  /**
@@ -5991,6 +6111,7 @@ var CoBuySDK = (function (exports) {
5991
6111
  */
5992
6112
  const API_ENDPOINTS = {
5993
6113
  // Product endpoints
6114
+ PRODUCT_CONTEXT: "/v1/sdk/products/:productId/context",
5994
6115
  PRODUCT_REWARD: "/v1/sdk/products/:productId/reward",
5995
6116
  PRODUCT_PRIMARY_GROUP: "/v1/sdk/products/:productId/group/primary",
5996
6117
  // Group endpoints
@@ -6001,6 +6122,8 @@ var CoBuySDK = (function (exports) {
6001
6122
  GROUP_CHECKOUT_PREPARE: "/v1/sdk/groups/:groupId/checkout/prepare",
6002
6123
  GROUP_CHECKOUT_VALIDATE: "/v1/sdk/groups/:groupId/checkout/validate",
6003
6124
  GROUP_CHECKOUT_CONFIRM: "/v1/sdk/groups/:groupId/checkout/confirm",
6125
+ // Invite endpoints
6126
+ INVITE_RESOLVE: "/v1/sdk/invite/resolve",
6004
6127
  };
6005
6128
  /**
6006
6129
  * Build full API URL from base URL and endpoint path
@@ -6039,8 +6162,11 @@ var CoBuySDK = (function (exports) {
6039
6162
  var _a;
6040
6163
  this.traceId = null;
6041
6164
  this.rewardCache = new Map();
6165
+ this.productContextCache = new Map();
6042
6166
  this.REWARD_CACHE_TTL = 60000; // 1 minute
6043
- this.pendingRequests = new Map();
6167
+ this.PRODUCT_CONTEXT_CACHE_TTL = 30000; // 30 seconds
6168
+ this.pendingRewardRequests = new Map();
6169
+ this.pendingContextRequests = new Map();
6044
6170
  this.baseUrl = config.baseUrl;
6045
6171
  this.authStrategy = config.authStrategy;
6046
6172
  this.sessionId = config.sessionId;
@@ -6436,6 +6562,106 @@ var CoBuySDK = (function (exports) {
6436
6562
  getTraceId() {
6437
6563
  return this.traceId;
6438
6564
  }
6565
+ getCachedProductContext(productId) {
6566
+ const cached = this.productContextCache.get(productId);
6567
+ if (cached && cached.expires > Date.now()) {
6568
+ return cached.data;
6569
+ }
6570
+ if (cached) {
6571
+ this.productContextCache.delete(productId);
6572
+ }
6573
+ return null;
6574
+ }
6575
+ normalizePrimaryGroup(groupData) {
6576
+ if (!groupData) {
6577
+ return null;
6578
+ }
6579
+ const nestedGroup = groupData.group;
6580
+ if (nestedGroup && typeof nestedGroup === "object" && "id" in nestedGroup) {
6581
+ return nestedGroup;
6582
+ }
6583
+ return groupData;
6584
+ }
6585
+ buildRewardDataFromContext(productId, context) {
6586
+ var _a, _b, _c, _d, _e;
6587
+ const primaryGroup = this.normalizePrimaryGroup(context.primary_group);
6588
+ return {
6589
+ productId,
6590
+ reward: (_a = context.reward) !== null && _a !== void 0 ? _a : null,
6591
+ campaign_mode: (_c = (_b = context.campaign) === null || _b === void 0 ? void 0 : _b.campaign_mode) !== null && _c !== void 0 ? _c : "group",
6592
+ campaign_creative_id: (_d = primaryGroup === null || primaryGroup === void 0 ? void 0 : primaryGroup.campaign_creative_id) !== null && _d !== void 0 ? _d : null,
6593
+ eligibility: (_e = context.eligibility) !== null && _e !== void 0 ? _e : { isEligible: false },
6594
+ };
6595
+ }
6596
+ setProductContextCache(productId, context) {
6597
+ this.productContextCache.set(productId, {
6598
+ data: context,
6599
+ expires: Date.now() + this.PRODUCT_CONTEXT_CACHE_TTL,
6600
+ });
6601
+ this.rewardCache.set(productId, {
6602
+ data: this.buildRewardDataFromContext(productId, context),
6603
+ expires: Date.now() + this.REWARD_CACHE_TTL,
6604
+ });
6605
+ }
6606
+ /**
6607
+ * Get product context information
6608
+ *
6609
+ * Uses the consolidated bootstrap endpoint so the SDK can resolve campaign,
6610
+ * reward, primary group, and active groups in a single request.
6611
+ */
6612
+ async getProductContext(productId) {
6613
+ if (!validateProductId(productId)) {
6614
+ return {
6615
+ success: false,
6616
+ error: {
6617
+ message: "Invalid productId format. Must be a non-empty string (max 200 chars).",
6618
+ code: "INVALID_PRODUCT_ID",
6619
+ },
6620
+ };
6621
+ }
6622
+ // const cached = this.getCachedProductContext(productId);
6623
+ // if (cached) {
6624
+ // this.logger.info(`Using cached product context for product: ${productId}`);
6625
+ // return {
6626
+ // success: true,
6627
+ // data: cached,
6628
+ // };
6629
+ // }
6630
+ const cacheKey = `context:${productId}`;
6631
+ const pending = this.pendingContextRequests.get(cacheKey);
6632
+ if (pending) {
6633
+ this.logger.info(`Deduplicating product context request for: ${productId}`);
6634
+ return pending;
6635
+ }
6636
+ const promise = this.fetchProductContext(productId).then((response) => {
6637
+ if (response.success && response.data) {
6638
+ this.setProductContextCache(productId, response.data);
6639
+ }
6640
+ return response;
6641
+ });
6642
+ this.pendingContextRequests.set(cacheKey, promise);
6643
+ promise.then(() => this.pendingContextRequests.delete(cacheKey), () => this.pendingContextRequests.delete(cacheKey));
6644
+ return promise;
6645
+ }
6646
+ async fetchProductContext(productId) {
6647
+ var _a;
6648
+ const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_CONTEXT, { productId });
6649
+ this.logger.info(`Fetching product context for product: ${productId}`);
6650
+ const response = await this.get(endpoint);
6651
+ if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.data)) {
6652
+ return {
6653
+ success: true,
6654
+ data: response.data.data,
6655
+ };
6656
+ }
6657
+ return {
6658
+ success: false,
6659
+ error: response.error || {
6660
+ message: "Failed to fetch product context data",
6661
+ code: "PRODUCT_CONTEXT_FETCH_ERROR",
6662
+ },
6663
+ };
6664
+ }
6439
6665
  /**
6440
6666
  * Get product reward information
6441
6667
  *
@@ -6480,13 +6706,31 @@ var CoBuySDK = (function (exports) {
6480
6706
  data: cached.data,
6481
6707
  };
6482
6708
  }
6709
+ const cachedContext = this.getCachedProductContext(productId);
6710
+ if (cachedContext) {
6711
+ this.logger.info(`Using cached product context reward for product: ${productId}`);
6712
+ return {
6713
+ success: true,
6714
+ data: this.buildRewardDataFromContext(productId, cachedContext),
6715
+ };
6716
+ }
6483
6717
  const cacheKey = `reward:${productId}`;
6484
- const pending = this.pendingRequests.get(cacheKey);
6718
+ const pending = this.pendingRewardRequests.get(cacheKey);
6485
6719
  if (pending) {
6486
6720
  this.logger.info(`Deduplicating request for product: ${productId}`);
6487
6721
  return pending;
6488
6722
  }
6489
- const promise = this.fetchProductReward(productId).then((response) => {
6723
+ const promise = (async () => {
6724
+ const contextResponse = await this.getProductContext(productId);
6725
+ if (contextResponse.success && contextResponse.data) {
6726
+ return {
6727
+ success: true,
6728
+ data: this.buildRewardDataFromContext(productId, contextResponse.data),
6729
+ };
6730
+ }
6731
+ this.logger.info(`Falling back to legacy reward endpoint for product: ${productId}`);
6732
+ return this.fetchProductReward(productId);
6733
+ })().then((response) => {
6490
6734
  if (response.success && response.data) {
6491
6735
  this.rewardCache.set(productId, {
6492
6736
  data: response.data,
@@ -6495,8 +6739,8 @@ var CoBuySDK = (function (exports) {
6495
6739
  }
6496
6740
  return response;
6497
6741
  });
6498
- this.pendingRequests.set(cacheKey, promise);
6499
- promise.then(() => this.pendingRequests.delete(cacheKey), () => this.pendingRequests.delete(cacheKey));
6742
+ this.pendingRewardRequests.set(cacheKey, promise);
6743
+ promise.then(() => this.pendingRewardRequests.delete(cacheKey), () => this.pendingRewardRequests.delete(cacheKey));
6500
6744
  return promise;
6501
6745
  }
6502
6746
  /**
@@ -6512,6 +6756,7 @@ var CoBuySDK = (function (exports) {
6512
6756
  */
6513
6757
  clearRewardCache() {
6514
6758
  this.rewardCache.clear();
6759
+ this.productContextCache.clear();
6515
6760
  }
6516
6761
  async fetchProductReward(productId) {
6517
6762
  var _a;
@@ -6559,7 +6804,7 @@ var CoBuySDK = (function (exports) {
6559
6804
  * ```
6560
6805
  */
6561
6806
  async getProductPrimaryGroup(productId) {
6562
- var _a;
6807
+ var _a, _b;
6563
6808
  if (!validateProductId(productId)) {
6564
6809
  return {
6565
6810
  success: false,
@@ -6569,15 +6814,38 @@ var CoBuySDK = (function (exports) {
6569
6814
  },
6570
6815
  };
6571
6816
  }
6572
- const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_PRIMARY_GROUP, { productId }, { allowAutoCreate: true });
6573
- this.logger.info(`Fetching primary group for product: ${productId}`);
6574
- const response = await this.get(endpoint);
6575
- if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.data)) {
6817
+ // const cachedContext = this.getCachedProductContext(productId);
6818
+ // const cachedGroup = this.normalizePrimaryGroup(cachedContext?.primary_group);
6819
+ // if (cachedGroup) {
6820
+ // this.logger.info(`Using cached primary group from product context for product: ${productId}`);
6821
+ // return {
6822
+ // success: true,
6823
+ // data: {
6824
+ // ...cachedGroup,
6825
+ // group: cachedGroup,
6826
+ // },
6827
+ // };
6828
+ // }
6829
+ const contextResponse = await this.getProductContext(productId);
6830
+ const contextGroup = this.normalizePrimaryGroup((_a = contextResponse.data) === null || _a === void 0 ? void 0 : _a.primary_group);
6831
+ if (contextResponse.success && contextGroup) {
6576
6832
  return {
6577
6833
  success: true,
6578
- data: response.data.data,
6834
+ data: Object.assign(Object.assign({}, contextGroup), { group: contextGroup }),
6579
6835
  };
6580
6836
  }
6837
+ const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_PRIMARY_GROUP, { productId }, { allowAutoCreate: true });
6838
+ this.logger.info(`Fetching primary group for product: ${productId}`);
6839
+ const response = await this.get(endpoint);
6840
+ if (response.success && ((_b = response.data) === null || _b === void 0 ? void 0 : _b.data)) {
6841
+ const normalizedGroup = this.normalizePrimaryGroup(response.data.data);
6842
+ if (normalizedGroup) {
6843
+ return {
6844
+ success: true,
6845
+ data: Object.assign(Object.assign({}, normalizedGroup), { group: normalizedGroup }),
6846
+ };
6847
+ }
6848
+ }
6581
6849
  return {
6582
6850
  success: false,
6583
6851
  error: response.error || {
@@ -6625,6 +6893,25 @@ var CoBuySDK = (function (exports) {
6625
6893
  },
6626
6894
  };
6627
6895
  }
6896
+ const cachedContext = this.getCachedProductContext(productId);
6897
+ if (cachedContext) {
6898
+ this.logger.info(`Using cached active groups from product context for product: ${productId}`);
6899
+ return {
6900
+ success: true,
6901
+ data: {
6902
+ groups: (cachedContext.active_groups || []),
6903
+ },
6904
+ };
6905
+ }
6906
+ const contextResponse = await this.getProductContext(productId);
6907
+ if (contextResponse.success && contextResponse.data) {
6908
+ return {
6909
+ success: true,
6910
+ data: {
6911
+ groups: (contextResponse.data.active_groups || []),
6912
+ },
6913
+ };
6914
+ }
6628
6915
  const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_ACTIVE_GROUPS, { productId });
6629
6916
  this.logger.info(`Fetching active groups for product: ${productId}`);
6630
6917
  const response = await this.get(endpoint);
@@ -6811,6 +7098,58 @@ var CoBuySDK = (function (exports) {
6811
7098
  },
6812
7099
  };
6813
7100
  }
7101
+ /**
7102
+ * Resolve an invite token to get product and group information
7103
+ *
7104
+ * Resolves an invite link token (e.g., from https://brand.cobuyza.co.za/i/QMeHD7ew)
7105
+ * to get the associated product, group, and invite metadata.
7106
+ *
7107
+ * @param {string} inviteToken - The invite token from the URL
7108
+ * @returns {Promise<ApiResponse<InviteResolveResponseData>>} Product and group information
7109
+ *
7110
+ * @description
7111
+ * Features:
7112
+ * - Resolves invite tokens to product/group details
7113
+ * - Tracks invite click counts
7114
+ * - Returns invite metadata (shared_via, click_count)
7115
+ * - Used for invite link handling in applications
7116
+ *
7117
+ * @example
7118
+ * ```typescript
7119
+ * // Resolve invite token from URL
7120
+ * const response = await client.resolveInvite('QMeHD7ew');
7121
+ *
7122
+ * if (response.success) {
7123
+ * const productId = response.data.product.id;
7124
+ * const groupId = response.data.group.id;
7125
+ * // Navigate to product page
7126
+ * window.location.href = `/product/${productId}`;
7127
+ * }
7128
+ * ```
7129
+ */
7130
+ async resolveInvite(inviteToken) {
7131
+ const endpoint = buildApiUrl("", API_ENDPOINTS.INVITE_RESOLVE, undefined, {
7132
+ invite_token: inviteToken,
7133
+ });
7134
+ this.logger.info(`Resolving invite token: ${inviteToken}`);
7135
+ const response = await this.get(endpoint);
7136
+ if (response.success) {
7137
+ const payload = response.data;
7138
+ const inviteData = ((payload === null || payload === void 0 ? void 0 : payload.data) || response.data);
7139
+ this.logger.info(`Invite resolved successfully for product: ${inviteData.product.id}`);
7140
+ return {
7141
+ success: true,
7142
+ data: inviteData,
7143
+ };
7144
+ }
7145
+ return {
7146
+ success: false,
7147
+ error: response.error || {
7148
+ message: "Failed to resolve invite",
7149
+ code: "INVITE_RESOLVE_ERROR",
7150
+ },
7151
+ };
7152
+ }
6814
7153
  /**
6815
7154
  * Set or update contact information for the current session
6816
7155
  *
@@ -7077,6 +7416,7 @@ var CoBuySDK = (function (exports) {
7077
7416
  const event = {
7078
7417
  event: "CTA_CLICKED",
7079
7418
  productId,
7419
+ sessionId: this.sessionId,
7080
7420
  timestamp: new Date().toISOString(),
7081
7421
  context: {
7082
7422
  pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
@@ -7129,12 +7469,32 @@ var CoBuySDK = (function (exports) {
7129
7469
  throw new CoBuyApiError(error instanceof Error ? error.message : "Unknown analytics error", "ANALYTICS_ERROR", { originalError: error });
7130
7470
  }
7131
7471
  }
7472
+ /**
7473
+ * Track session init event fired when the SDK initializes
7474
+ */
7475
+ async trackSessionInit(geo, device) {
7476
+ const event = {
7477
+ event: "SESSION_INIT",
7478
+ sessionId: this.sessionId,
7479
+ timestamp: new Date().toISOString(),
7480
+ context: Object.assign(Object.assign({ pageUrl: typeof window !== "undefined" ? window.location.href : undefined, userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined, sdkVersion: this.sdkVersion }, (geo ? { geo } : {})), (device ? { device } : {})),
7481
+ };
7482
+ try {
7483
+ await this.sendEvent(event);
7484
+ this.logger.info("[Analytics] Session init tracked");
7485
+ }
7486
+ catch (error) {
7487
+ // Non-blocking
7488
+ this.logger.error("[Analytics] Failed to track session init", error);
7489
+ }
7490
+ }
7132
7491
  /**
7133
7492
  * Track page view event
7134
7493
  */
7135
7494
  async trackPageView() {
7136
7495
  const event = {
7137
7496
  event: "PAGE_VIEW",
7497
+ sessionId: this.sessionId,
7138
7498
  timestamp: new Date().toISOString(),
7139
7499
  context: {
7140
7500
  pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
@@ -7151,6 +7511,273 @@ var CoBuySDK = (function (exports) {
7151
7511
  this.logger.error("[Analytics] Failed to track page view", error);
7152
7512
  }
7153
7513
  }
7514
+ /**
7515
+ * Track creative view event (widget rendered and visible)
7516
+ */
7517
+ async trackCreativeView(productId) {
7518
+ const event = {
7519
+ event: "CREATIVE_VIEW",
7520
+ productId,
7521
+ sessionId: this.sessionId,
7522
+ timestamp: new Date().toISOString(),
7523
+ context: {
7524
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7525
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7526
+ sdkVersion: this.sdkVersion,
7527
+ },
7528
+ };
7529
+ try {
7530
+ await this.sendEvent(event);
7531
+ this.logger.info(`[Analytics] Creative view tracked for product: ${productId}`);
7532
+ }
7533
+ catch (error) {
7534
+ this.logger.error("[Analytics] Failed to track creative view", error);
7535
+ }
7536
+ }
7537
+ /**
7538
+ * Track popup/lobby modal open event
7539
+ */
7540
+ async trackPopupOpen(productId, groupId) {
7541
+ const event = {
7542
+ event: "POPUP_OPEN",
7543
+ productId,
7544
+ sessionId: this.sessionId,
7545
+ timestamp: new Date().toISOString(),
7546
+ context: {
7547
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7548
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7549
+ sdkVersion: this.sdkVersion,
7550
+ groupId,
7551
+ },
7552
+ };
7553
+ try {
7554
+ await this.sendEvent(event);
7555
+ this.logger.info(`[Analytics] Popup open tracked for product: ${productId}`);
7556
+ }
7557
+ catch (error) {
7558
+ this.logger.error("[Analytics] Failed to track popup open", error);
7559
+ }
7560
+ }
7561
+ /**
7562
+ * Track join attempt event (before API call)
7563
+ */
7564
+ async trackJoinAttempt(productId, groupId) {
7565
+ const event = {
7566
+ event: "JOIN_ATTEMPT",
7567
+ productId,
7568
+ sessionId: this.sessionId,
7569
+ timestamp: new Date().toISOString(),
7570
+ context: {
7571
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7572
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7573
+ sdkVersion: this.sdkVersion,
7574
+ groupId,
7575
+ },
7576
+ };
7577
+ try {
7578
+ await this.sendEvent(event);
7579
+ this.logger.info(`[Analytics] Join attempt tracked for product: ${productId}`);
7580
+ }
7581
+ catch (error) {
7582
+ this.logger.error("[Analytics] Failed to track join attempt", error);
7583
+ }
7584
+ }
7585
+ /**
7586
+ * Track successful group join event
7587
+ */
7588
+ async trackJoinSuccess(productId, groupId) {
7589
+ const event = {
7590
+ event: "JOIN_SUCCESS",
7591
+ productId,
7592
+ sessionId: this.sessionId,
7593
+ timestamp: new Date().toISOString(),
7594
+ context: {
7595
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7596
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7597
+ sdkVersion: this.sdkVersion,
7598
+ groupId,
7599
+ },
7600
+ };
7601
+ try {
7602
+ await this.sendEvent(event);
7603
+ this.logger.info(`[Analytics] Join success tracked for product: ${productId}`);
7604
+ }
7605
+ catch (error) {
7606
+ this.logger.error("[Analytics] Failed to track join success", error);
7607
+ }
7608
+ }
7609
+ /**
7610
+ * Track join failure event
7611
+ */
7612
+ async trackJoinFailure(productId, groupId, errorCode, message) {
7613
+ const event = {
7614
+ event: "JOIN_FAILURE",
7615
+ productId,
7616
+ sessionId: this.sessionId,
7617
+ timestamp: new Date().toISOString(),
7618
+ context: {
7619
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7620
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7621
+ sdkVersion: this.sdkVersion,
7622
+ groupId,
7623
+ errorCode,
7624
+ errorMessage: message,
7625
+ },
7626
+ };
7627
+ try {
7628
+ await this.sendEvent(event);
7629
+ this.logger.info(`[Analytics] Join failure tracked for product: ${productId}`);
7630
+ }
7631
+ catch (error) {
7632
+ this.logger.error("[Analytics] Failed to track join failure", error);
7633
+ }
7634
+ }
7635
+ /**
7636
+ * Track already joined event (user attempts to join a group they're already in)
7637
+ */
7638
+ async trackAlreadyJoined(productId, groupId) {
7639
+ const event = {
7640
+ event: "ALREADY_JOINED",
7641
+ productId,
7642
+ sessionId: this.sessionId,
7643
+ timestamp: new Date().toISOString(),
7644
+ context: {
7645
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7646
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7647
+ sdkVersion: this.sdkVersion,
7648
+ groupId,
7649
+ },
7650
+ };
7651
+ try {
7652
+ await this.sendEvent(event);
7653
+ this.logger.info(`[Analytics] Already joined tracked for product: ${productId}`);
7654
+ }
7655
+ catch (error) {
7656
+ this.logger.error("[Analytics] Failed to track already joined", error);
7657
+ }
7658
+ }
7659
+ /**
7660
+ * Track group full view event (user sees a fulfilled/full group)
7661
+ */
7662
+ async trackGroupFullView(productId, groupId, totalMembers) {
7663
+ const event = {
7664
+ event: "GROUP_FULL_VIEW",
7665
+ productId,
7666
+ sessionId: this.sessionId,
7667
+ timestamp: new Date().toISOString(),
7668
+ context: {
7669
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7670
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7671
+ sdkVersion: this.sdkVersion,
7672
+ groupId,
7673
+ totalMembers,
7674
+ },
7675
+ };
7676
+ try {
7677
+ await this.sendEvent(event);
7678
+ this.logger.info(`[Analytics] Group full view tracked for product: ${productId}`);
7679
+ }
7680
+ catch (error) {
7681
+ this.logger.error("[Analytics] Failed to track group full view", error);
7682
+ }
7683
+ }
7684
+ /**
7685
+ * Track share click event
7686
+ */
7687
+ async trackShareClick(productId, groupId, channel) {
7688
+ const event = {
7689
+ event: "SHARE_CLICK",
7690
+ productId,
7691
+ sessionId: this.sessionId,
7692
+ timestamp: new Date().toISOString(),
7693
+ context: {
7694
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7695
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7696
+ sdkVersion: this.sdkVersion,
7697
+ groupId,
7698
+ channel,
7699
+ },
7700
+ };
7701
+ try {
7702
+ await this.sendEvent(event);
7703
+ this.logger.info(`[Analytics] Share click tracked for product: ${productId}`);
7704
+ }
7705
+ catch (error) {
7706
+ this.logger.error("[Analytics] Failed to track share click", error);
7707
+ }
7708
+ }
7709
+ /**
7710
+ * Track group creation attempt event
7711
+ */
7712
+ async trackGroupCreateAttempt(productId) {
7713
+ const event = {
7714
+ event: "GROUP_CREATE_ATTEMPT",
7715
+ productId,
7716
+ sessionId: this.sessionId,
7717
+ timestamp: new Date().toISOString(),
7718
+ context: {
7719
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7720
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7721
+ sdkVersion: this.sdkVersion,
7722
+ },
7723
+ };
7724
+ try {
7725
+ await this.sendEvent(event);
7726
+ this.logger.info(`[Analytics] Group create attempt tracked for product: ${productId}`);
7727
+ }
7728
+ catch (error) {
7729
+ this.logger.error("[Analytics] Failed to track group create attempt", error);
7730
+ }
7731
+ }
7732
+ /**
7733
+ * Track successful group creation event
7734
+ */
7735
+ async trackGroupCreateSuccess(productId, groupId) {
7736
+ const event = {
7737
+ event: "GROUP_CREATE_SUCCESS",
7738
+ productId,
7739
+ sessionId: this.sessionId,
7740
+ timestamp: new Date().toISOString(),
7741
+ context: {
7742
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7743
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7744
+ sdkVersion: this.sdkVersion,
7745
+ groupId,
7746
+ },
7747
+ };
7748
+ try {
7749
+ await this.sendEvent(event);
7750
+ this.logger.info(`[Analytics] Group create success tracked for product: ${productId}`);
7751
+ }
7752
+ catch (error) {
7753
+ this.logger.error("[Analytics] Failed to track group create success", error);
7754
+ }
7755
+ }
7756
+ /**
7757
+ * Track group creation failure event
7758
+ */
7759
+ async trackGroupCreateFailure(productId, errorCode, message) {
7760
+ const event = {
7761
+ event: "GROUP_CREATE_FAILURE",
7762
+ productId,
7763
+ sessionId: this.sessionId,
7764
+ timestamp: new Date().toISOString(),
7765
+ context: {
7766
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7767
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7768
+ sdkVersion: this.sdkVersion,
7769
+ errorCode,
7770
+ errorMessage: message,
7771
+ },
7772
+ };
7773
+ try {
7774
+ await this.sendEvent(event);
7775
+ this.logger.info(`[Analytics] Group create failure tracked for product: ${productId}`);
7776
+ }
7777
+ catch (error) {
7778
+ this.logger.error("[Analytics] Failed to track group create failure", error);
7779
+ }
7780
+ }
7154
7781
  /**
7155
7782
  * Track custom event (extensible for future use)
7156
7783
  */
@@ -12596,8 +13223,14 @@ var CoBuySDK = (function (exports) {
12596
13223
  participantsCount !== undefined &&
12597
13224
  participantsCount >= maxParticipants);
12598
13225
  if (isFulfilled && productId && groupId) {
12599
- // Prepare checkout when group is fulfilled (if not already prepared)
12600
- this.prepareCheckoutIfNotDone(productId, groupId);
13226
+ // Only prepare checkout if the current user is actually a member of this group.
13227
+ // this.widgets tracks all WidgetRoot instances; getJoinedGroupId() returns the
13228
+ // joined group ID only when the user has completed a join this session (not for
13229
+ // observers). Calling prepareCheckout for non-members would generate spurious 403s.
13230
+ const userIsInGroup = [...this.widgets].some((w) => w.getJoinedGroupId() === groupId);
13231
+ if (userIsInGroup) {
13232
+ this.prepareCheckoutIfNotDone(productId, groupId);
13233
+ }
12601
13234
  }
12602
13235
  // Emit user-defined callback if provided
12603
13236
  if ((_a = config.events) === null || _a === void 0 ? void 0 : _a.onGroupMemberJoined) {
@@ -12610,8 +13243,11 @@ var CoBuySDK = (function (exports) {
12610
13243
  this.logger.warn("[SDK] Failed to initialize sockets", e);
12611
13244
  }
12612
13245
  }
12613
- // Track page view event after successful initialization
13246
+ // Track session init + page view events after successful initialization
12614
13247
  if (this.analyticsClient) {
13248
+ this.analyticsClient.trackSessionInit(config.geo, config.device).catch((error) => {
13249
+ this.logger.warn("[SDK] Failed to track session init", error);
13250
+ });
12615
13251
  this.analyticsClient.trackPageView().catch((error) => {
12616
13252
  // Non-blocking: Analytics failure should not affect SDK initialization
12617
13253
  this.logger.warn("[SDK] Failed to track page view", error);
@@ -12917,6 +13553,63 @@ var CoBuySDK = (function (exports) {
12917
13553
  this.logger.error("Error setting contact information", error);
12918
13554
  }
12919
13555
  }
13556
+ /**
13557
+ * Resolve an invite token to get product and group information
13558
+ *
13559
+ * Resolves an invite link token (e.g., from https://brand.cobuyza.co.za/i/QMeHD7ew)
13560
+ * to get the associated product, group, and invite metadata. Use this to handle
13561
+ * invite links in your application routing.
13562
+ *
13563
+ * @param inviteToken - The invite token from the URL
13564
+ * @returns Promise with invite resolution data or null if failed
13565
+ *
13566
+ * @example
13567
+ * ```typescript
13568
+ * // Extract token from URL path like /i/QMeHD7ew
13569
+ * const pathParts = window.location.pathname.split('/i/');
13570
+ * if (pathParts.length > 1) {
13571
+ * const inviteToken = pathParts[1];
13572
+ * const inviteData = await CoBuy.resolveInvite(inviteToken);
13573
+ *
13574
+ * if (inviteData) {
13575
+ * // Navigate to product page
13576
+ * window.location.href = `/product/${inviteData.product.id}`;
13577
+ * }
13578
+ * }
13579
+ * ```
13580
+ */
13581
+ async resolveInvite(inviteToken) {
13582
+ if (!this.configManager.isInitialized()) {
13583
+ this.logger.warn("SDK not initialized, cannot resolve invite");
13584
+ return null;
13585
+ }
13586
+ if (!this.apiClient) {
13587
+ this.logger.error("API client not available");
13588
+ return null;
13589
+ }
13590
+ if (!inviteToken || typeof inviteToken !== "string" || inviteToken.trim() === "") {
13591
+ this.logger.error("Invalid invite token provided");
13592
+ return null;
13593
+ }
13594
+ try {
13595
+ const response = await this.apiClient.resolveInvite(inviteToken.trim());
13596
+ if (response.success && response.data) {
13597
+ this.logger.info(`Invite resolved successfully: product=${response.data.product.id}, group=${response.data.group.id}`);
13598
+ return response.data;
13599
+ }
13600
+ else {
13601
+ if (this.handleFraudError(response.error, "resolveInvite")) {
13602
+ return null;
13603
+ }
13604
+ this.logger.error("Failed to resolve invite", response.error);
13605
+ return null;
13606
+ }
13607
+ }
13608
+ catch (error) {
13609
+ this.logger.error("Error resolving invite", error);
13610
+ return null;
13611
+ }
13612
+ }
12920
13613
  /**
12921
13614
  * Validate checkout for a group
12922
13615
  *