@explorins/pers-sdk-react-native 2.1.3 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2828,6 +2828,28 @@ exports.SortOrder = void 0;
2828
2828
  SortOrder["DESC"] = "DESC";
2829
2829
  })(exports.SortOrder || (exports.SortOrder = {}));
2830
2830
 
2831
+ /**
2832
+ * ProcessRecordStatus - Status for business process records that create blockchain transactions
2833
+ *
2834
+ * Used for tracking the lifecycle of business operations (campaigns, redemptions) that
2835
+ * trigger blockchain transactions. This is distinct from TransactionStatus which tracks
2836
+ * the blockchain transaction itself.
2837
+ *
2838
+ * Usage:
2839
+ * - Campaign Claims (user claims rewards via blockchain transactions)
2840
+ * - Redemption Redeems (user redeems items via blockchain transactions)
2841
+ * - Any business process creating blockchain transactions that need status tracking
2842
+ *
2843
+ * NOT for blockchain transactions themselves - use TransactionStatus instead.
2844
+ */
2845
+ exports.ProcessRecordStatus = void 0;
2846
+ (function (ProcessRecordStatus) {
2847
+ ProcessRecordStatus["PENDING"] = "PENDING";
2848
+ ProcessRecordStatus["PROCESSING"] = "PROCESSING";
2849
+ ProcessRecordStatus["COMPLETED"] = "COMPLETED";
2850
+ ProcessRecordStatus["FAILED"] = "FAILED"; // Transaction processing failed (blockchain or validation error)
2851
+ })(exports.ProcessRecordStatus || (exports.ProcessRecordStatus = {}));
2852
+
2831
2853
  exports.PurchaseStatus = void 0;
2832
2854
  (function (PurchaseStatus) {
2833
2855
  // after creation of payment and before payment is done by user
@@ -2847,35 +2869,30 @@ exports.PurchaseCurrency = void 0;
2847
2869
  PurchaseCurrency["EUR"] = "eur";
2848
2870
  })(exports.PurchaseCurrency || (exports.PurchaseCurrency = {}));
2849
2871
 
2850
- exports.RedemptionRedeemStatus = void 0;
2851
- (function (RedemptionRedeemStatus) {
2852
- RedemptionRedeemStatus["PENDING"] = "PENDING";
2853
- RedemptionRedeemStatus["PROCESSING"] = "PROCESSING";
2854
- RedemptionRedeemStatus["COMPLETED"] = "COMPLETED";
2855
- RedemptionRedeemStatus["FAILED"] = "FAILED"; // Processing failed
2856
- })(exports.RedemptionRedeemStatus || (exports.RedemptionRedeemStatus = {}));
2857
-
2858
2872
  /**
2859
2873
  * Trigger Source Types
2860
2874
  * Defines the different types of triggers that can activate a campaign flow
2861
- */
2862
- exports.TriggerSourceType = void 0;
2863
- (function (TriggerSourceType) {
2864
- TriggerSourceType["QR_CODE"] = "QR_CODE";
2865
- // NFC_TAG = 'NFC_TAG',
2866
- TriggerSourceType["API_WEBHOOK"] = "API_WEBHOOK";
2867
- // TRANSACTION = 'TRANSACTION',
2868
- })(exports.TriggerSourceType || (exports.TriggerSourceType = {}));
2875
+ * Using const object pattern for better extensibility without requiring deploys
2876
+ */
2877
+ const TRIGGER_SOURCE_TYPES = {
2878
+ QR_CODE: 'QR_CODE',
2879
+ NFC_TAG: 'NFC_TAG',
2880
+ API_WEBHOOK: 'API_WEBHOOK',
2881
+ GPS_GEOFENCE: 'GPS_GEOFENCE',
2882
+ TRANSACTION: 'TRANSACTION',
2883
+ };
2884
+ // Export values array for validation
2885
+ const TRIGGER_SOURCE_TYPE_VALUES = Object.values(TRIGGER_SOURCE_TYPES);
2869
2886
  /**
2870
2887
  * Source Logic Types
2871
2888
  * Defines how multiple trigger sources combine to activate a flow
2872
2889
  * Currently only ANY (OR logic) is implemented
2873
2890
  */
2874
- exports.SourceLogicType = void 0;
2875
- (function (SourceLogicType) {
2876
- SourceLogicType["ANY"] = "any";
2877
- // Future: ALL = 'all', SEQUENCE = 'sequence', WEIGHTED = 'weighted'
2878
- })(exports.SourceLogicType || (exports.SourceLogicType = {}));
2891
+ const SOURCE_LOGIC_TYPES = {
2892
+ ANY: 'any'
2893
+ // Future: ALL: 'all', SEQUENCE: 'sequence', WEIGHTED: 'weighted'
2894
+ };
2895
+ const SOURCE_LOGIC_TYPE_VALUES = Object.values(SOURCE_LOGIC_TYPES);
2879
2896
 
2880
2897
  exports.CampaignConditionType = void 0;
2881
2898
  (function (CampaignConditionType) {
@@ -3104,6 +3121,103 @@ const Domains = {
3104
3121
  AUDIT: 'audit',
3105
3122
  };
3106
3123
 
3124
+ /**
3125
+ * SQL operator mapping for query operators
3126
+ * Maps abstract query operators to SQL equivalents
3127
+ */
3128
+ const QUERY_OPERATOR_SQL_MAP = {
3129
+ eq: '=',
3130
+ ne: '!=',
3131
+ gt: '>',
3132
+ gte: '>=',
3133
+ lt: '<',
3134
+ lte: '<=',
3135
+ in: 'IN',
3136
+ nin: 'NOT IN'
3137
+ };
3138
+
3139
+ /**
3140
+ * Helper to validate includes array at runtime
3141
+ */
3142
+ const VALID_RELATIONS = ['user', 'campaign', 'business', 'triggerSource'];
3143
+ function isValidRelation(value) {
3144
+ return VALID_RELATIONS.includes(value);
3145
+ }
3146
+
3147
+ /**
3148
+ * Valid transaction relations for runtime validation
3149
+ */
3150
+ const VALID_TRANSACTION_RELATIONS = [
3151
+ 'sender',
3152
+ 'recipient',
3153
+ 'business'
3154
+ ];
3155
+ /**
3156
+ * Type guard to validate relation strings at runtime
3157
+ */
3158
+ function isValidTransactionRelation(value) {
3159
+ return VALID_TRANSACTION_RELATIONS.includes(value);
3160
+ }
3161
+
3162
+ /**
3163
+ * Array of all valid campaign include relations
3164
+ * Used for runtime validation
3165
+ */
3166
+ const VALID_CAMPAIGN_RELATIONS = [
3167
+ 'triggerSources',
3168
+ 'businesses'
3169
+ ];
3170
+ /**
3171
+ * Type guard to validate campaign include relation at runtime
3172
+ *
3173
+ * @param value - String to validate
3174
+ * @returns True if value is a valid CampaignIncludeRelation
3175
+ */
3176
+ function isValidCampaignRelation(value) {
3177
+ return VALID_CAMPAIGN_RELATIONS.includes(value);
3178
+ }
3179
+
3180
+ /**
3181
+ * Array of all valid campaign claim include relations
3182
+ * Used for runtime validation
3183
+ */
3184
+ const VALID_CAMPAIGN_CLAIM_RELATIONS = [
3185
+ 'campaign',
3186
+ 'user',
3187
+ 'business',
3188
+ 'triggerSource',
3189
+ 'transactions'
3190
+ ];
3191
+ /**
3192
+ * Type guard to validate campaign claim include relation at runtime
3193
+ *
3194
+ * @param value - String to validate
3195
+ * @returns True if value is a valid CampaignClaimIncludeRelation
3196
+ */
3197
+ function isValidCampaignClaimRelation(value) {
3198
+ return VALID_CAMPAIGN_CLAIM_RELATIONS.includes(value);
3199
+ }
3200
+
3201
+ /**
3202
+ * Array of all valid redemption redeem include relations
3203
+ * Used for runtime validation
3204
+ */
3205
+ const VALID_REDEMPTION_REDEEM_RELATIONS = [
3206
+ 'redemption',
3207
+ 'user',
3208
+ 'business',
3209
+ 'transactions'
3210
+ ];
3211
+ /**
3212
+ * Type guard to validate redemption redeem include relation at runtime
3213
+ *
3214
+ * @param value - String to validate
3215
+ * @returns True if value is a valid RedemptionRedeemIncludeRelation
3216
+ */
3217
+ function isValidRedemptionRedeemRelation(value) {
3218
+ return VALID_REDEMPTION_REDEEM_RELATIONS.includes(value);
3219
+ }
3220
+
3107
3221
  /**
3108
3222
  * Transaction format constants for Ethereum and EVM-compatible chains
3109
3223
  * Using const assertions for zero runtime overhead
@@ -3642,10 +3756,30 @@ class UserApi {
3642
3756
  * ADMIN: Get all remote users with query parameters
3643
3757
  * Uses new RESTful /users endpoint with role-based access
3644
3758
  * Note: Admin users get full data, non-admin users get public profiles only
3759
+ *
3760
+ * @param options - Pagination options (page, limit, sortBy, sortOrder)
3761
+ * @param search - Optional search query to filter users
3762
+ *
3763
+ * @example
3764
+ * ```typescript
3765
+ * // Get users sorted by email
3766
+ * const users = await sdk.users.getAllUsers({
3767
+ * page: 1,
3768
+ * limit: 10,
3769
+ * sortBy: 'email',
3770
+ * sortOrder: SortOrder.ASC
3771
+ * });
3772
+ *
3773
+ * // Search for users
3774
+ * const searchResults = await sdk.users.getAllUsers({ page: 1, limit: 10 }, 'john');
3775
+ * ```
3645
3776
  */
3646
- async getAllRemoteUsers(options) {
3777
+ async getAllRemoteUsers(options, search) {
3647
3778
  const params = buildPaginationParams(options);
3648
- const url = `${this.basePath}?merge=soft${params ? '&' + params.toString() : ''}`;
3779
+ if (search) {
3780
+ params.set('search', search);
3781
+ }
3782
+ const url = `${this.basePath}?merge=soft&${params.toString()}`;
3649
3783
  const response = await this.apiClient.get(url);
3650
3784
  return normalizeToPaginated(response);
3651
3785
  }
@@ -3740,9 +3874,11 @@ class UserService {
3740
3874
  /**
3741
3875
  * ADMIN: Get all remote users
3742
3876
  * ✅ FIXED: Matches API method signature (no parameters needed)
3877
+ * @param options - Pagination options
3878
+ * @param search - Optional search query
3743
3879
  */
3744
- async getAllRemoteUsers(options) {
3745
- return this.userApi.getAllRemoteUsers(options);
3880
+ async getAllRemoteUsers(options, search) {
3881
+ return this.userApi.getAllRemoteUsers(options, search);
3746
3882
  }
3747
3883
  /**
3748
3884
  * ADMIN: Update user as admin
@@ -4913,6 +5049,19 @@ class CampaignApi {
4913
5049
  * Get campaigns with pagination support
4914
5050
  * Returns paginated response with metadata (total, page, limit, hasMore)
4915
5051
  * Intelligent access: Public gets active only, Business gets own campaigns, Admin gets all
5052
+ *
5053
+ * @param options - Filter, pagination, and include options
5054
+ * @param options.include - Relations to include: 'triggerSources', 'businesses'
5055
+ *
5056
+ * @example
5057
+ * ```typescript
5058
+ * // Get campaigns with trigger sources included
5059
+ * const campaigns = await api.getCampaigns({ include: ['triggerSources'] });
5060
+ * campaigns.data.forEach(c => console.log(c.included?.triggerSources));
5061
+ *
5062
+ * // Get campaigns with both relations
5063
+ * const full = await api.getCampaigns({ include: ['triggerSources', 'businesses'] });
5064
+ * ```
4916
5065
  */
4917
5066
  async getCampaigns(options) {
4918
5067
  const params = [];
@@ -4929,14 +5078,28 @@ class CampaignApi {
4929
5078
  params.push(`sortBy=${options.sortBy}`);
4930
5079
  if (options?.sortOrder)
4931
5080
  params.push(`sortOrder=${options.sortOrder}`);
5081
+ if (options?.include?.length)
5082
+ params.push(`include=${options.include.join(',')}`);
4932
5083
  return this.apiClient.get(`/campaigns?${params.join('&')}`);
4933
5084
  }
4934
5085
  /**
4935
5086
  * PUBLIC: Get campaign by ID
4936
5087
  * NEW: /campaigns/{id}
5088
+ *
5089
+ * @param id - Campaign UUID
5090
+ * @param include - Relations to include: 'triggerSources', 'businesses'
5091
+ *
5092
+ * @example
5093
+ * ```typescript
5094
+ * // Get campaign with all relations
5095
+ * const campaign = await api.getCampaignById('123', ['triggerSources', 'businesses']);
5096
+ * console.log(campaign.included?.triggerSources);
5097
+ * console.log(campaign.included?.businesses);
5098
+ * ```
4937
5099
  */
4938
- async getCampaignById(id) {
4939
- return this.apiClient.get(`/campaigns/${id}`);
5100
+ async getCampaignById(id, include) {
5101
+ const params = include?.length ? `?include=${include.join(',')}` : '';
5102
+ return this.apiClient.get(`/campaigns/${id}${params}`);
4940
5103
  }
4941
5104
  /**
4942
5105
  * ADMIN: Create campaign
@@ -5055,6 +5218,16 @@ class CampaignApi {
5055
5218
  const response = await this.apiClient.get(`/campaign-triggers?${params.toString()}`);
5056
5219
  return normalizeToPaginated(response);
5057
5220
  }
5221
+ /**
5222
+ * PUBLIC: Get campaign trigger by ID
5223
+ * NEW: GET /campaign-triggers/{id}
5224
+ *
5225
+ * @param triggerId - Campaign trigger UUID
5226
+ * @returns Campaign trigger details
5227
+ */
5228
+ async getCampaignTriggerById(triggerId) {
5229
+ return this.apiClient.get(`/campaign-triggers/${triggerId}`);
5230
+ }
5058
5231
  /**
5059
5232
  * ADMIN: Create campaign trigger
5060
5233
  * NEW: POST /campaign-triggers
@@ -5083,6 +5256,13 @@ class CampaignApi {
5083
5256
  async setCampaignTrigger(campaignId, triggerId) {
5084
5257
  return this.apiClient.put(`/campaign-triggers/${triggerId}/assign/${campaignId}`, {});
5085
5258
  }
5259
+ /**
5260
+ * ADMIN: Remove trigger from campaign (unassign)
5261
+ * NEW: DELETE /campaign-triggers/{triggerId}/assign/{campaignId}
5262
+ */
5263
+ async removeCampaignTrigger(campaignId, triggerId) {
5264
+ return this.apiClient.delete(`/campaign-triggers/${triggerId}/assign/${campaignId}`);
5265
+ }
5086
5266
  /**
5087
5267
  * ADMIN: Add/Remove condition to trigger
5088
5268
  * NEW: PUT /campaign-triggers/{triggerId}/conditions/{conditionId}
@@ -5154,7 +5334,7 @@ class CampaignApi {
5154
5334
  * });
5155
5335
  * ```
5156
5336
  */
5157
- async getClaims(filters) {
5337
+ async getClaims(filters, include) {
5158
5338
  const params = buildPaginationParams(filters);
5159
5339
  // Add filter parameters
5160
5340
  if (filters?.campaignId)
@@ -5163,6 +5343,10 @@ class CampaignApi {
5163
5343
  params.set('userId', filters.userId);
5164
5344
  if (filters?.businessId)
5165
5345
  params.set('businessId', filters.businessId);
5346
+ // Add include parameter
5347
+ if (include && include.length > 0) {
5348
+ include.forEach(relation => params.append('include', relation));
5349
+ }
5166
5350
  const endpoint = `/campaigns/claims?${params.toString()}`;
5167
5351
  const response = await this.apiClient.get(endpoint);
5168
5352
  return normalizeToPaginated(response);
@@ -5172,13 +5356,42 @@ class CampaignApi {
5172
5356
  * Uses /me endpoint for authenticated user context
5173
5357
  *
5174
5358
  * @param options - Pagination options
5359
+ * @param include - Optional relations to include (campaign, user, business)
5175
5360
  * @returns Paginated list of user's claims
5176
5361
  */
5177
- async getClaimsLoggedUser(options) {
5362
+ async getClaimsLoggedUser(options, include) {
5178
5363
  const params = buildPaginationParams(options);
5364
+ // Add include parameter
5365
+ if (include && include.length > 0) {
5366
+ include.forEach(relation => params.append('include', relation));
5367
+ }
5179
5368
  const response = await this.apiClient.get(`/campaigns/claims/me?${params.toString()}`);
5180
5369
  return normalizeToPaginated(response);
5181
5370
  }
5371
+ // ==========================================
5372
+ // TRIGGER SOURCE ASSIGNMENT (/campaigns/{id}/trigger-sources)
5373
+ // Note: TriggerSource CRUD is in TriggerSourceApi
5374
+ // ==========================================
5375
+ /**
5376
+ * ADMIN: Assign a trigger source to a campaign
5377
+ *
5378
+ * @param campaignId - Campaign UUID
5379
+ * @param triggerSourceId - Trigger source UUID
5380
+ * @returns Updated campaign with trigger source assigned
5381
+ */
5382
+ async assignTriggerSourceToCampaign(campaignId, triggerSourceId) {
5383
+ return this.apiClient.put(`/campaigns/${campaignId}/trigger-sources/${triggerSourceId}`, {});
5384
+ }
5385
+ /**
5386
+ * ADMIN: Remove a trigger source from a campaign
5387
+ *
5388
+ * @param campaignId - Campaign UUID
5389
+ * @param triggerSourceId - Trigger source UUID
5390
+ * @returns Updated campaign with trigger source removed
5391
+ */
5392
+ async removeTriggerSourceFromCampaign(campaignId, triggerSourceId) {
5393
+ return this.apiClient.delete(`/campaigns/${campaignId}/trigger-sources/${triggerSourceId}`);
5394
+ }
5182
5395
  }
5183
5396
 
5184
5397
  /**
@@ -5198,9 +5411,11 @@ class CampaignService {
5198
5411
  // ==========================================
5199
5412
  /**
5200
5413
  * PUBLIC: Get campaign by ID
5414
+ * @param id - Campaign UUID
5415
+ * @param include - Relations to include: 'triggerSources', 'businesses'
5201
5416
  */
5202
- async getCampaignById(id) {
5203
- return this.campaignApi.getCampaignById(id);
5417
+ async getCampaignById(id, include) {
5418
+ return this.campaignApi.getCampaignById(id, include);
5204
5419
  }
5205
5420
  // ==========================================
5206
5421
  // AUTHENTICATED OPERATIONS
@@ -5213,9 +5428,11 @@ class CampaignService {
5213
5428
  }
5214
5429
  /**
5215
5430
  * AUTH: Get claims for logged user with pagination
5431
+ * @param options - Pagination options
5432
+ * @param include - Optional relations to include (campaign, user, business)
5216
5433
  */
5217
- async getClaimsForLoggedUser(options) {
5218
- return this.campaignApi.getClaimsLoggedUser(options);
5434
+ async getClaimsForLoggedUser(options, include) {
5435
+ return this.campaignApi.getClaimsLoggedUser(options, include);
5219
5436
  }
5220
5437
  // ==========================================
5221
5438
  // CAMPAIGN OPERATIONS (Paginated)
@@ -5229,6 +5446,7 @@ class CampaignService {
5229
5446
  * @param options.limit - Items per page (default: 50)
5230
5447
  * @param options.sortBy - Sort field
5231
5448
  * @param options.sortOrder - Sort direction
5449
+ * @param options.include - Relations to include: 'triggerSources', 'businesses'
5232
5450
  */
5233
5451
  async getCampaigns(options) {
5234
5452
  return this.campaignApi.getCampaigns(options);
@@ -5239,6 +5457,30 @@ class CampaignService {
5239
5457
  async getCampaignTriggers(options) {
5240
5458
  return this.campaignApi.getCampaignTriggers(options);
5241
5459
  }
5460
+ /**
5461
+ * ADMIN: Get campaign trigger by ID
5462
+ */
5463
+ async getCampaignTriggerById(triggerId) {
5464
+ return this.campaignApi.getCampaignTriggerById(triggerId);
5465
+ }
5466
+ /**
5467
+ * ADMIN: Create campaign trigger
5468
+ */
5469
+ async createCampaignTrigger(data) {
5470
+ return this.campaignApi.createCampaignTrigger(data);
5471
+ }
5472
+ /**
5473
+ * ADMIN: Update campaign trigger
5474
+ */
5475
+ async updateCampaignTrigger(triggerId, data) {
5476
+ return this.campaignApi.updateCampaignTrigger(triggerId, data);
5477
+ }
5478
+ /**
5479
+ * ADMIN: Delete campaign trigger
5480
+ */
5481
+ async deleteCampaignTrigger(triggerId) {
5482
+ return this.campaignApi.deleteCampaignTrigger(triggerId);
5483
+ }
5242
5484
  /**
5243
5485
  * ADMIN: Toggle campaign active status
5244
5486
  */
@@ -5263,6 +5505,12 @@ class CampaignService {
5263
5505
  async setCampaignTrigger(campaignId, triggerId) {
5264
5506
  return this.campaignApi.setCampaignTrigger(campaignId, triggerId);
5265
5507
  }
5508
+ /**
5509
+ * ADMIN: Remove trigger from campaign (unassign)
5510
+ */
5511
+ async removeCampaignTrigger(campaignId, triggerId) {
5512
+ return this.campaignApi.removeCampaignTrigger(campaignId, triggerId);
5513
+ }
5266
5514
  /**
5267
5515
  * ADMIN: Update campaign
5268
5516
  */
@@ -5298,21 +5546,45 @@ class CampaignService {
5298
5546
  }
5299
5547
  /**
5300
5548
  * ADMIN: Get campaign claims with optional filters and pagination
5549
+ * @param filters - Filter and pagination options
5550
+ * @param include - Optional relations to include (campaign, user, business)
5301
5551
  */
5302
- async getCampaignClaims(filters) {
5303
- return this.campaignApi.getClaims(filters);
5552
+ async getCampaignClaims(filters, include) {
5553
+ return this.campaignApi.getClaims(filters, include);
5304
5554
  }
5305
5555
  /**
5306
5556
  * ADMIN: Get campaign claims by user ID with pagination
5557
+ * @param userId - User ID to filter by
5558
+ * @param options - Pagination options
5559
+ * @param include - Optional relations to include (campaign, user, business)
5307
5560
  */
5308
- async getCampaignClaimsByUserId(userId, options) {
5309
- return this.campaignApi.getClaims({ userId, ...options });
5561
+ async getCampaignClaimsByUserId(userId, options, include) {
5562
+ return this.campaignApi.getClaims({ userId, ...options }, include);
5310
5563
  }
5311
5564
  /**
5312
5565
  * ADMIN: Get campaign claims by business ID with pagination
5566
+ * @param businessId - Business ID to filter by
5567
+ * @param options - Pagination options
5568
+ * @param include - Optional relations to include (campaign, user, business)
5569
+ */
5570
+ async getCampaignClaimsByBusinessId(businessId, options, include) {
5571
+ return this.campaignApi.getClaims({ businessId, ...options }, include);
5572
+ }
5573
+ // ==========================================
5574
+ // TRIGGER SOURCE ASSIGNMENT
5575
+ // Note: TriggerSource CRUD is in TriggerSourceService
5576
+ // ==========================================
5577
+ /**
5578
+ * ADMIN: Assign a trigger source to a campaign
5313
5579
  */
5314
- async getCampaignClaimsByBusinessId(businessId, options) {
5315
- return this.campaignApi.getClaims({ businessId, ...options });
5580
+ async assignTriggerSourceToCampaign(campaignId, triggerSourceId) {
5581
+ return this.campaignApi.assignTriggerSourceToCampaign(campaignId, triggerSourceId);
5582
+ }
5583
+ /**
5584
+ * ADMIN: Remove a trigger source from a campaign
5585
+ */
5586
+ async removeTriggerSourceFromCampaign(campaignId, triggerSourceId) {
5587
+ return this.campaignApi.removeTriggerSourceFromCampaign(campaignId, triggerSourceId);
5316
5588
  }
5317
5589
  }
5318
5590
 
@@ -5344,12 +5616,7 @@ class RedemptionApi {
5344
5616
  * - Public users: Get active redemptions only
5345
5617
  * - Admin users: Get all redemptions with optional filtering
5346
5618
  *
5347
- * @param options.active - Filter by active status (undefined = all for admins/active for public)
5348
- * @param options.adminAccess - Force admin access (requires admin auth)
5349
- * @param options.page - Page number (default: 1)
5350
- * @param options.limit - Items per page (default: 50)
5351
- * @param options.sortBy - Sort field
5352
- * @param options.sortOrder - Sort order (asc/desc)
5619
+ * @param options - Filter and pagination options (active, adminAccess, page, limit, sortBy, sortOrder)
5353
5620
  * @returns Paginated response with redemptions
5354
5621
  */
5355
5622
  async getRedemptions(options) {
@@ -5416,17 +5683,11 @@ class RedemptionApi {
5416
5683
  * - Users: See only their own redeems (userId/businessId filters ignored)
5417
5684
  * - Admins: Can filter by userId, businessId, or redemptionId
5418
5685
  *
5419
- * @param filters.redemptionId - Filter by specific redemption
5420
- * @param filters.userId - Admin only: Filter by user ID
5421
- * @param filters.businessId - Admin only: Filter by business ID
5422
- * @param filters.myRedeems - Force user's own redeems (uses /me endpoint)
5423
- * @param filters.page - Page number (default: 1)
5424
- * @param filters.limit - Items per page (default: 50)
5425
- * @param filters.sortBy - Sort field
5426
- * @param filters.sortOrder - Sort order (asc/desc)
5686
+ * @param filters - Filter and pagination options (redemptionId, userId, businessId, myRedeems, page, limit, sortBy, sortOrder)
5687
+ * @param include - Optional relations to include (redemption, user, business)
5427
5688
  * @returns Paginated response with redemption redeems
5428
5689
  */
5429
- async getRedemptionRedeems(filters) {
5690
+ async getRedemptionRedeems(filters, include) {
5430
5691
  let url = `${this.basePath}/redeems`;
5431
5692
  const params = buildPaginationParams(filters);
5432
5693
  // Use convenience endpoint for user's own redeems
@@ -5435,6 +5696,10 @@ class RedemptionApi {
5435
5696
  if (filters?.redemptionId) {
5436
5697
  params.append('redemptionId', filters.redemptionId);
5437
5698
  }
5699
+ // Add include parameter
5700
+ if (include && include.length > 0) {
5701
+ include.forEach(relation => params.append('include', relation));
5702
+ }
5438
5703
  const response = await this.apiClient.get(`${url}?${params.toString()}`);
5439
5704
  return normalizeToPaginated(response);
5440
5705
  }
@@ -5445,14 +5710,26 @@ class RedemptionApi {
5445
5710
  params.append('userId', filters.userId);
5446
5711
  if (filters?.businessId)
5447
5712
  params.append('businessId', filters.businessId);
5713
+ // Add include parameter
5714
+ if (include && include.length > 0) {
5715
+ include.forEach(relation => params.append('include', relation));
5716
+ }
5448
5717
  const response = await this.apiClient.get(`${url}?${params.toString()}`);
5449
5718
  return normalizeToPaginated(response);
5450
5719
  }
5451
5720
  /**
5452
5721
  * UNIFIED: Get specific redemption redeem by ID
5722
+ * @param id - Redemption redeem ID
5723
+ * @param include - Optional relations to include (redemption, user, business)
5453
5724
  */
5454
- async getRedemptionRedeemById(id) {
5455
- return this.apiClient.get(`${this.basePath}/redeems/${id}`);
5725
+ async getRedemptionRedeemById(id, include) {
5726
+ const params = new URLSearchParams();
5727
+ // Add include parameter
5728
+ if (include && include.length > 0) {
5729
+ include.forEach(relation => params.append('include', relation));
5730
+ }
5731
+ const url = `${this.basePath}/redeems/${id}${params.toString() ? `?${params.toString()}` : ''}`;
5732
+ return this.apiClient.get(url);
5456
5733
  }
5457
5734
  // ==========================================
5458
5735
  // USER OPERATIONS (JWT + Project Key)
@@ -5463,10 +5740,11 @@ class RedemptionApi {
5463
5740
  * Uses convenience endpoint /redemption-redeems/me with optional filtering
5464
5741
  * @param redemptionId - Optional filter by specific redemption
5465
5742
  * @param options - Pagination options (page, limit, sortBy, sortOrder)
5743
+ * @param include - Optional relations to include (redemption, user, business)
5466
5744
  * @returns Paginated response with user's redemption redeems
5467
5745
  */
5468
- async getUserRedeems(redemptionId, options) {
5469
- return this.getRedemptionRedeems({ myRedeems: true, redemptionId, ...options });
5746
+ async getUserRedeems(redemptionId, options, include) {
5747
+ return this.getRedemptionRedeems({ myRedeems: true, redemptionId, ...options }, include);
5470
5748
  }
5471
5749
  // ==========================================
5472
5750
  // ADMIN OPERATIONS (Tenant Admin JWT)
@@ -5599,15 +5877,19 @@ class RedemptionService {
5599
5877
  }
5600
5878
  /**
5601
5879
  * UNIFIED: Get redemption redeems with filtering and pagination
5880
+ * @param filters - Filter options
5881
+ * @param include - Optional relations to include (redemption, user, business)
5602
5882
  */
5603
- async getRedemptionRedeems(filters) {
5604
- return this.redemptionApi.getRedemptionRedeems(filters);
5883
+ async getRedemptionRedeems(filters, include) {
5884
+ return this.redemptionApi.getRedemptionRedeems(filters, include);
5605
5885
  }
5606
5886
  /**
5607
5887
  * Convenience: Get user redemptions with pagination
5888
+ * @param options - Pagination options
5889
+ * @param include - Optional relations to include (redemption, user, business)
5608
5890
  */
5609
- async getUserRedeems(options) {
5610
- return this.redemptionApi.getUserRedeems(undefined, options);
5891
+ async getUserRedeems(options, include) {
5892
+ return this.redemptionApi.getUserRedeems(undefined, options, include);
5611
5893
  }
5612
5894
  // ==========================================
5613
5895
  // ADMIN OPERATIONS
@@ -5672,9 +5954,19 @@ class TransactionApi {
5672
5954
  * Get transaction by ID (public endpoint)
5673
5955
  *
5674
5956
  * UPDATED: /transaction/{id} → /transactions/{id}
5957
+ * UPDATED: Added support for include parameter to enrich with sender/recipient/business entities
5958
+ *
5959
+ * @param transactionId - Transaction ID
5960
+ * @param include - Optional relations to include (sender, recipient, business)
5675
5961
  */
5676
- async getTransactionById(transactionId) {
5677
- return this.apiClient.get(`${this.basePath}/${transactionId}`);
5962
+ async getTransactionById(transactionId, include) {
5963
+ let url = `${this.basePath}/${transactionId}`;
5964
+ if (include && include.length > 0) {
5965
+ const params = new URLSearchParams();
5966
+ include.forEach(relation => params.append('include', relation));
5967
+ url += `?${params.toString()}`;
5968
+ }
5969
+ return this.apiClient.get(url);
5678
5970
  }
5679
5971
  /**
5680
5972
  * Unique method to create a transaction
@@ -5688,13 +5980,78 @@ class TransactionApi {
5688
5980
  // ==========================================
5689
5981
  // AUTHENTICATED USER OPERATIONS
5690
5982
  // ==========================================
5983
+ /**
5984
+ * Helper to build query params from TransactionQueryOptions
5985
+ * Handles all filter options and include parameter
5986
+ */
5987
+ buildTransactionQueryParams(options) {
5988
+ const params = buildPaginationParams(options);
5989
+ if (!options)
5990
+ return params;
5991
+ // Search and identification filters
5992
+ if (options.search)
5993
+ params.append('search', options.search);
5994
+ if (options.participantId)
5995
+ params.append('participantId', options.participantId);
5996
+ if (options.participantAddress)
5997
+ params.append('participantAddress', options.participantAddress);
5998
+ // Business and process filters
5999
+ if (options.engagedBusinessId)
6000
+ params.append('engagedBusinessId', options.engagedBusinessId);
6001
+ if (options.businessScopeId)
6002
+ params.append('businessScopeId', options.businessScopeId);
6003
+ if (options.triggerProcessId)
6004
+ params.append('triggerProcessId', options.triggerProcessId);
6005
+ // Token filters
6006
+ if (options.tokenAddress)
6007
+ params.append('tokenAddress', options.tokenAddress);
6008
+ if (options.tokenType) {
6009
+ if (Array.isArray(options.tokenType)) {
6010
+ options.tokenType.forEach(type => params.append('tokenType', type));
6011
+ }
6012
+ else {
6013
+ params.append('tokenType', options.tokenType);
6014
+ }
6015
+ }
6016
+ // Transaction classification filters (arrays or single values)
6017
+ if (options.role) {
6018
+ if (Array.isArray(options.role)) {
6019
+ options.role.forEach(role => params.append('role', role));
6020
+ }
6021
+ else {
6022
+ params.append('role', options.role);
6023
+ }
6024
+ }
6025
+ if (options.status) {
6026
+ if (Array.isArray(options.status)) {
6027
+ options.status.forEach(status => params.append('status', status));
6028
+ }
6029
+ else {
6030
+ params.append('status', options.status);
6031
+ }
6032
+ }
6033
+ if (options.type) {
6034
+ if (Array.isArray(options.type)) {
6035
+ options.type.forEach(type => params.append('type', type));
6036
+ }
6037
+ else {
6038
+ params.append('type', options.type);
6039
+ }
6040
+ }
6041
+ // Include relations for entity enrichment
6042
+ if (options.include && options.include.length > 0) {
6043
+ options.include.forEach(relation => params.append('include', relation));
6044
+ }
6045
+ return params;
6046
+ }
5691
6047
  /**
5692
6048
  * AUTH: Get user transaction history with role-based filtering and pagination
5693
6049
  *
5694
- * UPDATED: Uses consolidated /transactions/me endpoint with role parameter
5695
- * FIXED: Now correctly returns paginated response (was incorrectly unwrapping to array)
6050
+ * UPDATED: Uses consolidated /transactions/me endpoint with all filter options
6051
+ * UPDATED: Added support for include parameter to enrich with sender/recipient/business entities
6052
+ * UPDATED: Now supports all TransactionFiltersDTO filters (triggerProcessId, status, type, etc.)
5696
6053
  *
5697
- * @param options - Pagination and filter options
6054
+ * @param options - Pagination, filter, and include options
5698
6055
  * @returns Paginated list of user's transactions
5699
6056
  *
5700
6057
  * @example
@@ -5702,24 +6059,30 @@ class TransactionApi {
5702
6059
  * // Get first page of all transactions
5703
6060
  * const page1 = await transactionApi.getUserTransactionHistory();
5704
6061
  *
5705
- * // Filter by role
5706
- * const sent = await transactionApi.getUserTransactionHistory({
6062
+ * // Filter by role and status
6063
+ * const completed = await transactionApi.getUserTransactionHistory({
5707
6064
  * role: TransactionRole.SENDER,
6065
+ * status: TransactionStatus.COMPLETED,
5708
6066
  * page: 1,
5709
6067
  * limit: 50
5710
6068
  * });
5711
6069
  *
5712
- * // Access data and pagination
5713
- * console.log(`Showing ${page1.data.length} of ${page1.pagination.total} transactions`);
6070
+ * // Filter by trigger process (e.g., from campaign claim or redemption)
6071
+ * const claimTransactions = await transactionApi.getUserTransactionHistory({
6072
+ * triggerProcessId: 'claim-abc123',
6073
+ * include: ['sender', 'recipient', 'business']
6074
+ * });
6075
+ *
6076
+ * // Access included entities
6077
+ * claimTransactions.data.forEach(tx => {
6078
+ * if (tx.included?.engagedBusiness) {
6079
+ * console.log('Business:', tx.included.engagedBusiness.displayName);
6080
+ * }
6081
+ * });
5714
6082
  * ```
5715
6083
  */
5716
6084
  async getUserTransactionHistory(options) {
5717
- const params = buildPaginationParams(options);
5718
- // Add role parameter if specified
5719
- if (options?.role) {
5720
- params.set('role', options.role);
5721
- }
5722
- // Backend already returns PaginatedResponseDTO - return it directly
6085
+ const params = this.buildTransactionQueryParams(options);
5723
6086
  return this.apiClient.get(`${this.basePath}/me?${params.toString()}`);
5724
6087
  }
5725
6088
  /**
@@ -5769,45 +6132,17 @@ class TransactionApi {
5769
6132
  async prepareClientSignedTransaction(request) {
5770
6133
  return this.apiClient.post(`${this.basePath}`, request);
5771
6134
  }
5772
- /**
5773
- * ADMIN: Get all tenant transactions with pagination and filtering
5774
- *
5775
- * UPDATED: /transaction/admin → /transactions
5776
- * FIXED: Now correctly returns paginated response (was incorrectly unwrapping to array)
5777
- *
5778
- * @param options - Pagination and filter options
5779
- * @returns Paginated list of tenant transactions
5780
- *
5781
- * @example
5782
- * ```typescript
5783
- * // Get first page
5784
- * const page1 = await transactionApi.getTenantTransactions();
5785
- *
5786
- * // Get specific page with custom limit
5787
- * const page2 = await transactionApi.getTenantTransactions({
5788
- * page: 2,
5789
- * limit: 100
5790
- * });
5791
- *
5792
- * // Access data
5793
- * page1.data.forEach(tx => console.log(tx.id));
5794
- * ```
5795
- *
5796
- * @deprecated Consider using getPaginatedTransactions() for more advanced filtering
5797
- */
5798
- async getTenantTransactions(options) {
5799
- const params = buildPaginationParams(options);
5800
- // Backend already returns PaginatedResponseDTO - return it directly
5801
- return this.apiClient.get(`${this.basePath}?${params.toString()}`);
5802
- }
5803
6135
  /**
5804
6136
  * ADMIN: Get paginated transactions with filtering and sorting
5805
6137
  *
5806
6138
  * UPDATED: /transaction/admin → /transactions (same endpoint, better structure)
6139
+ *
6140
+ * @param options - Pagination request with filters and optional include relations
5807
6141
  */
5808
- async getPaginatedTransactions(params) {
5809
- const queryString = this.buildQueryParams(params).toString();
5810
- return this.apiClient.get(`${this.basePath}?${queryString}`);
6142
+ async getPaginatedTransactions(options) {
6143
+ const queryString = this.buildQueryParams(options).toString();
6144
+ const fullUrl = `${this.basePath}?${queryString}`;
6145
+ return this.apiClient.get(fullUrl);
5811
6146
  }
5812
6147
  /**
5813
6148
  * ADMIN: Export transactions to CSV
@@ -5861,7 +6196,7 @@ class TransactionApi {
5861
6196
  }
5862
6197
  /**
5863
6198
  * Helper to convert DTO object to URLSearchParams
5864
- * Handles nested 'filters' object and arrays correctly.
6199
+ * Handles nested 'filters' object, arrays, and include parameter correctly.
5865
6200
  */
5866
6201
  buildQueryParams(params) {
5867
6202
  const query = new URLSearchParams();
@@ -5874,7 +6209,11 @@ class TransactionApi {
5874
6209
  query.append('sortBy', params.sortBy);
5875
6210
  if (params.sortOrder)
5876
6211
  query.append('sortOrder', params.sortOrder);
5877
- // 2. Handle Nested Filters
6212
+ // 2. Handle Include Relations (root level)
6213
+ if (params.include && params.include.length > 0) {
6214
+ params.include.forEach((relation) => query.append('include', relation));
6215
+ }
6216
+ // 3. Handle Nested Filters
5878
6217
  if (params.filters) {
5879
6218
  Object.entries(params.filters).forEach(([key, value]) => {
5880
6219
  // Skip undefined/null values
@@ -5912,9 +6251,12 @@ class TransactionService {
5912
6251
  }
5913
6252
  /**
5914
6253
  * Get transaction by ID
6254
+ *
6255
+ * @param transactionId - Transaction ID
6256
+ * @param include - Optional relations to include (sender, recipient, business)
5915
6257
  */
5916
- async getTransactionById(transactionId) {
5917
- return this.transactionApi.getTransactionById(transactionId);
6258
+ async getTransactionById(transactionId, include) {
6259
+ return this.transactionApi.getTransactionById(transactionId, include);
5918
6260
  }
5919
6261
  // ==========================================
5920
6262
  // AUTHENTICATED OPERATIONS
@@ -5932,14 +6274,13 @@ class TransactionService {
5932
6274
  return this.transactionApi.submitSignedTransaction(signedTxData);
5933
6275
  }
5934
6276
  /**
5935
- * AUTH: Get user transaction history by type with pagination
6277
+ * AUTH: Get user transaction history with comprehensive filtering
5936
6278
  *
5937
- * @param role - Optional transaction role filter
5938
- * @param options - Pagination options
6279
+ * @param options - Query options including filters, pagination, and include relations
5939
6280
  * @returns Paginated transaction history
5940
6281
  */
5941
- async getUserTransactionHistory(role, options) {
5942
- return this.transactionApi.getUserTransactionHistory({ role, ...options });
6282
+ async getUserTransactionHistory(options) {
6283
+ return this.transactionApi.getUserTransactionHistory(options);
5943
6284
  }
5944
6285
  /**
5945
6286
  * AUTH: Prepare existing transaction for client-side signing
@@ -5971,20 +6312,11 @@ class TransactionService {
5971
6312
  /* async createAdminTransaction(request: TransactionRequestDTO): Promise<TransactionRequestResponseDTO> {
5972
6313
  return this.transactionApi.createAdminTransaction(request);
5973
6314
  } */
5974
- /**
5975
- * ADMIN: Get all tenant transactions with pagination
5976
- *
5977
- * @param options - Pagination options
5978
- * @returns Paginated tenant transactions
5979
- */
5980
- async getTenantTransactions(options) {
5981
- return this.transactionApi.getTenantTransactions(options || {});
5982
- }
5983
6315
  /**
5984
6316
  * ADMIN: Get paginated transactions with filtering and sorting
5985
6317
  */
5986
- async getPaginatedTransactions(params) {
5987
- return this.transactionApi.getPaginatedTransactions(params);
6318
+ async getPaginatedTransactions(options) {
6319
+ return this.transactionApi.getPaginatedTransactions(options);
5988
6320
  }
5989
6321
  /**
5990
6322
  * ADMIN: Export transactions to CSV
@@ -6739,7 +7071,191 @@ class AnalyticsApi {
6739
7071
  * ADMIN: Get transaction analytics with filtering and aggregation
6740
7072
  */
6741
7073
  async getTransactionAnalytics(request) {
6742
- return this.apiClient.post('/transactions/analytics', request);
7074
+ return this.apiClient.post('/analytics/transactions', request);
7075
+ }
7076
+ /**
7077
+ * ADMIN: Get campaign claim analytics with aggregation (charts, metrics, grouping)
7078
+ *
7079
+ * This endpoint is for aggregated analytics only (groupBy/metrics).
7080
+ * For enriched list data with nested objects, use campaign.getClaims() instead.
7081
+ *
7082
+ * @example Claims per campaign
7083
+ * ```typescript
7084
+ * const response = await analyticsApi.getCampaignClaimAnalytics({
7085
+ * filters: { status: 'COMPLETED' },
7086
+ * groupBy: ['campaignId'],
7087
+ * metrics: ['count'],
7088
+ * sortBy: 'count',
7089
+ * sortOrder: SortOrder.DESC,
7090
+ * limit: 10
7091
+ * });
7092
+ * ```
7093
+ *
7094
+ * @example Claims over time by status
7095
+ * ```typescript
7096
+ * const response = await analyticsApi.getCampaignClaimAnalytics({
7097
+ * groupBy: ['month', 'status'],
7098
+ * metrics: ['count'],
7099
+ * sortBy: 'month',
7100
+ * sortOrder: SortOrder.DESC,
7101
+ * startDate: new Date('2026-01-01'),
7102
+ * endDate: new Date('2026-12-31')
7103
+ * });
7104
+ * ```
7105
+ */
7106
+ async getCampaignClaimAnalytics(request) {
7107
+ return this.apiClient.post('/analytics/campaign-claims', request);
7108
+ }
7109
+ /**
7110
+ * ADMIN: Get user analytics with engagement metrics
7111
+ *
7112
+ * Returns aggregated user statistics including engagement rate, active users,
7113
+ * transaction metrics, and per-active-user averages (more accurate than all-user averages).
7114
+ *
7115
+ * Request structure matches TransactionAnalytics and CampaignClaimAnalytics for consistency:
7116
+ * - filters object for business-specific scoping
7117
+ * - startDate/endDate at root level for date range filtering
7118
+ *
7119
+ * @param request - Analytics request with optional filters and date range
7120
+ * @returns Aggregated user metrics with per-user and per-active-user averages
7121
+ *
7122
+ * @example Basic user analytics (all time, all businesses)
7123
+ * ```typescript
7124
+ * const analytics = await analyticsApi.getUserAnalytics({});
7125
+ * console.log(`Total users: ${analytics.totalUsers}`);
7126
+ * console.log(`Active users: ${analytics.activeUsers}`);
7127
+ * console.log(`Engagement rate: ${analytics.engagementRate}%`);
7128
+ *
7129
+ * // Per-user averages (includes inactive users)
7130
+ * console.log(`Avg transactions per user: ${analytics.averageTransactionsPerUser}`);
7131
+ *
7132
+ * // Per-active-user averages (only engaged users - more useful!)
7133
+ * console.log(`Avg transactions per active user: ${analytics.averageTransactionsPerActiveUser}`);
7134
+ * ```
7135
+ *
7136
+ * @example Filtered by date range
7137
+ * ```typescript
7138
+ * const analytics = await analyticsApi.getUserAnalytics({
7139
+ * startDate: new Date('2026-01-01'),
7140
+ * endDate: new Date('2026-01-31')
7141
+ * });
7142
+ * console.log(`January metrics:`);
7143
+ * console.log(`Active users: ${analytics.activeUsers}`);
7144
+ * console.log(`New users: ${analytics.newUsers}`);
7145
+ * console.log(`Date range applied: ${analytics.metadata.dateRange?.startDate} - ${analytics.metadata.dateRange?.endDate}`);
7146
+ * ```
7147
+ *
7148
+ * @example Business-specific analytics with filters
7149
+ * ```typescript
7150
+ * const analytics = await analyticsApi.getUserAnalytics({
7151
+ * filters: { businessId: 'biz-123' },
7152
+ * startDate: new Date('2026-01-01'),
7153
+ * endDate: new Date('2026-12-31')
7154
+ * });
7155
+ * console.log(`Business customer engagement in 2026:`);
7156
+ * console.log(`Active customers: ${analytics.activeUsers}`);
7157
+ * console.log(`Avg claims per active customer: ${analytics.averageClaimsPerActiveUser.toFixed(2)}`);
7158
+ * console.log(`Customer engagement rate: ${analytics.engagementRate.toFixed(1)}%`);
7159
+ * ```
7160
+ */
7161
+ async getUserAnalytics(request = {}) {
7162
+ return this.apiClient.post('/analytics/users', request);
7163
+ }
7164
+ /**
7165
+ * ADMIN: Get user transaction ranking with enriched user data
7166
+ *
7167
+ * Returns ranked list of users with full user details and transaction metrics.
7168
+ * Data enrichment happens via efficient SQL JOINs + UNION (handles legacy transactions).
7169
+ *
7170
+ * Use Cases:
7171
+ * - Admin leaderboards showing top users by activity
7172
+ * - User engagement analysis with full user context
7173
+ * - Identifying power users for campaigns
7174
+ *
7175
+ * @param request - Ranking request with filters, sorting, and limit
7176
+ * @returns Ranked list with user details (email, externalUserId) and transaction metrics
7177
+ *
7178
+ * @example Top 50 users by transaction count
7179
+ * ```typescript
7180
+ * const ranking = await analyticsApi.getUserRanking({
7181
+ * sortBy: 'totalTransactions',
7182
+ * sortOrder: SortOrder.DESC,
7183
+ * limit: 50
7184
+ * });
7185
+ *
7186
+ * ranking.results.forEach((user, index) => {
7187
+ * console.log(`#${index + 1}: ${user.email || user.externalUserId} - ${user.totalTransactions} transactions`);
7188
+ * });
7189
+ * ```
7190
+ *
7191
+ * @example Top users by STAMP spending
7192
+ * ```typescript
7193
+ * const ranking = await analyticsApi.getUserRanking({
7194
+ * filters: { tokenType: 'STAMP' },
7195
+ * sortBy: 'tokenSpent',
7196
+ * sortOrder: SortOrder.DESC,
7197
+ * limit: 20
7198
+ * });
7199
+ * ```
7200
+ *
7201
+ * @example Business-specific leaderboard with date range
7202
+ * ```typescript
7203
+ * const ranking = await analyticsApi.getUserRanking({
7204
+ * filters: {
7205
+ * businessId: 'business-uuid-here',
7206
+ * tokenType: 'CREDIT'
7207
+ * },
7208
+ * sortBy: 'totalTransactions',
7209
+ * sortOrder: SortOrder.DESC,
7210
+ * limit: 100,
7211
+ * startDate: new Date('2026-01-01'),
7212
+ * endDate: new Date('2026-12-31')
7213
+ * });
7214
+ * console.log(`Top ${ranking.totalUsers} users for business in 2026`);
7215
+ * ```
7216
+ */
7217
+ async getUserRanking(request = {}) {
7218
+ return this.apiClient.post('/analytics/users/ranking', request);
7219
+ }
7220
+ /**
7221
+ * ADMIN: Get business transaction ranking with enriched business data
7222
+ *
7223
+ * Returns ranked list of businesses with full business details and transaction metrics.
7224
+ * Data enrichment happens via efficient SQL JOINs + UNION (handles legacy transactions).
7225
+ *
7226
+ * @param request - Ranking request with filters, sorting, and limit
7227
+ * @returns Ranked list with business details and transaction metrics
7228
+ *
7229
+ * @example Top 50 businesses by transaction count
7230
+ * ```typescript
7231
+ * const ranking = await analyticsApi.getBusinessRanking({
7232
+ * sortBy: 'totalTransactions',
7233
+ * sortOrder: SortOrder.DESC,
7234
+ * limit: 50
7235
+ * });
7236
+ * ```
7237
+ */
7238
+ async getBusinessRanking(request = {}) {
7239
+ return this.apiClient.post('/analytics/businesses/ranking', request);
7240
+ }
7241
+ /**
7242
+ * ADMIN: Get monthly user retention analytics
7243
+ *
7244
+ * Returns monthly retention data with active/new/returning users and retention rates.
7245
+ * Replaces 13 separate API calls with 1 efficient recursive CTE query.
7246
+ *
7247
+ * @param request - Retention analytics request with monthsBack and filters
7248
+ * @returns Monthly retention data with user metrics and retention rates
7249
+ *
7250
+ * @example Last 13 months retention
7251
+ * ```typescript
7252
+ * const retention = await analyticsApi.getRetentionAnalytics({
7253
+ * monthsBack: 13
7254
+ * });
7255
+ * ```
7256
+ */
7257
+ async getRetentionAnalytics(request = {}) {
7258
+ return this.apiClient.post('/analytics/users/retention', request);
6743
7259
  }
6744
7260
  }
6745
7261
 
@@ -6764,6 +7280,47 @@ class AnalyticsService {
6764
7280
  async getTransactionAnalytics(request) {
6765
7281
  return this.analyticsApi.getTransactionAnalytics(request);
6766
7282
  }
7283
+ /**
7284
+ * ADMIN: Get campaign claim analytics with aggregation
7285
+ */
7286
+ async getCampaignClaimAnalytics(request) {
7287
+ return this.analyticsApi.getCampaignClaimAnalytics(request);
7288
+ }
7289
+ /**
7290
+ * ADMIN: Get user analytics with engagement metrics
7291
+ *
7292
+ * Returns aggregated user statistics including engagement rate, active users,
7293
+ * transaction metrics, and claims/redemption averages.
7294
+ */
7295
+ async getUserAnalytics(request = {}) {
7296
+ return this.analyticsApi.getUserAnalytics(request);
7297
+ }
7298
+ /**
7299
+ * ADMIN: Get user transaction ranking with enriched user data
7300
+ *
7301
+ * Returns ranked list of users with full user details (email, externalUserId)
7302
+ * and transaction metrics. Efficient data enrichment via SQL JOINs + UNION.
7303
+ */
7304
+ async getUserRanking(request = {}) {
7305
+ return this.analyticsApi.getUserRanking(request);
7306
+ }
7307
+ /**
7308
+ * ADMIN: Get business transaction ranking with enriched business data
7309
+ *
7310
+ * Returns ranked list of businesses with full business details and transaction metrics.
7311
+ */
7312
+ async getBusinessRanking(request = {}) {
7313
+ return this.analyticsApi.getBusinessRanking(request);
7314
+ }
7315
+ /**
7316
+ * ADMIN: Get monthly user retention analytics
7317
+ *
7318
+ * Returns monthly retention data with user metrics and retention rates.
7319
+ * Replaces 13 separate API calls with 1 efficient query.
7320
+ */
7321
+ async getRetentionAnalytics(request = {}) {
7322
+ return this.analyticsApi.getRetentionAnalytics(request);
7323
+ }
6767
7324
  }
6768
7325
 
6769
7326
  /**
@@ -6813,6 +7370,144 @@ class DonationService {
6813
7370
  }
6814
7371
  }
6815
7372
 
7373
+ /**
7374
+ * Platform-Agnostic TriggerSource API Client
7375
+ *
7376
+ * Handles all trigger source operations:
7377
+ * - CRUD operations for trigger sources (QR codes, NFC tags, GPS geofences, webhooks, etc.)
7378
+ * - Trigger sources are independent entities that can be assigned to campaigns
7379
+ *
7380
+ * Uses @explorins/pers-shared DTOs for consistency with backend.
7381
+ */
7382
+ class TriggerSourceApi {
7383
+ constructor(apiClient) {
7384
+ this.apiClient = apiClient;
7385
+ }
7386
+ /**
7387
+ * PUBLIC: Get trigger sources with optional filters and pagination
7388
+ *
7389
+ * @param options - Filter and pagination options
7390
+ * @returns Paginated list of trigger sources
7391
+ *
7392
+ * @example
7393
+ * ```typescript
7394
+ * // Get all QR code trigger sources
7395
+ * const qrSources = await triggerSourceApi.getTriggerSources({ type: 'QR_CODE' });
7396
+ *
7397
+ * // Get trigger sources for a specific campaign
7398
+ * const campaignSources = await triggerSourceApi.getTriggerSources({
7399
+ * campaignId: 'campaign-123',
7400
+ * page: 1,
7401
+ * limit: 20
7402
+ * });
7403
+ * ```
7404
+ */
7405
+ async getTriggerSources(options) {
7406
+ const params = buildPaginationParams(options);
7407
+ if (options?.type)
7408
+ params.set('type', options.type);
7409
+ if (options?.businessId)
7410
+ params.set('businessId', options.businessId);
7411
+ if (options?.campaignId)
7412
+ params.set('campaignId', options.campaignId);
7413
+ if (options?.active !== undefined)
7414
+ params.set('active', String(options.active));
7415
+ const response = await this.apiClient.get(`/trigger-sources?${params.toString()}`);
7416
+ return normalizeToPaginated(response);
7417
+ }
7418
+ /**
7419
+ * PUBLIC: Get trigger source by ID
7420
+ *
7421
+ * @param triggerSourceId - UUID of the trigger source
7422
+ * @returns Trigger source details
7423
+ */
7424
+ async getTriggerSourceById(triggerSourceId) {
7425
+ return this.apiClient.get(`/trigger-sources/${triggerSourceId}`);
7426
+ }
7427
+ /**
7428
+ * ADMIN: Create a new trigger source
7429
+ *
7430
+ * @param triggerSource - Trigger source creation data
7431
+ * @returns Created trigger source
7432
+ *
7433
+ * @example
7434
+ * ```typescript
7435
+ * const qrSource = await triggerSourceApi.createTriggerSource({
7436
+ * type: 'QR_CODE',
7437
+ * name: 'Store Entrance QR',
7438
+ * description: 'QR code at main entrance',
7439
+ * businessId: 'business-123',
7440
+ * coordsLatitude: 47.6062,
7441
+ * coordsLongitude: -122.3321
7442
+ * });
7443
+ * ```
7444
+ */
7445
+ async createTriggerSource(triggerSource) {
7446
+ return this.apiClient.post('/trigger-sources', triggerSource);
7447
+ }
7448
+ /**
7449
+ * ADMIN: Update a trigger source
7450
+ *
7451
+ * @param triggerSourceId - UUID of the trigger source to update
7452
+ * @param triggerSource - Updated trigger source data (partial)
7453
+ * @returns Updated trigger source
7454
+ */
7455
+ async updateTriggerSource(triggerSourceId, triggerSource) {
7456
+ return this.apiClient.put(`/trigger-sources/${triggerSourceId}`, triggerSource);
7457
+ }
7458
+ /**
7459
+ * ADMIN: Delete (soft delete) a trigger source
7460
+ *
7461
+ * @param triggerSourceId - UUID of the trigger source to delete
7462
+ * @returns Success status
7463
+ */
7464
+ async deleteTriggerSource(triggerSourceId) {
7465
+ return this.apiClient.delete(`/trigger-sources/${triggerSourceId}`);
7466
+ }
7467
+ }
7468
+
7469
+ /**
7470
+ * Platform-Agnostic TriggerSource Service
7471
+ *
7472
+ * Contains trigger source business logic and operations that work across platforms.
7473
+ * No framework dependencies - pure TypeScript business logic.
7474
+ */
7475
+ class TriggerSourceService {
7476
+ constructor(triggerSourceApi) {
7477
+ this.triggerSourceApi = triggerSourceApi;
7478
+ }
7479
+ /**
7480
+ * Get trigger sources with optional filters
7481
+ */
7482
+ async getTriggerSources(options) {
7483
+ return this.triggerSourceApi.getTriggerSources(options);
7484
+ }
7485
+ /**
7486
+ * Get trigger source by ID
7487
+ */
7488
+ async getTriggerSourceById(triggerSourceId) {
7489
+ return this.triggerSourceApi.getTriggerSourceById(triggerSourceId);
7490
+ }
7491
+ /**
7492
+ * ADMIN: Create a new trigger source
7493
+ */
7494
+ async createTriggerSource(triggerSource) {
7495
+ return this.triggerSourceApi.createTriggerSource(triggerSource);
7496
+ }
7497
+ /**
7498
+ * ADMIN: Update a trigger source
7499
+ */
7500
+ async updateTriggerSource(triggerSourceId, triggerSource) {
7501
+ return this.triggerSourceApi.updateTriggerSource(triggerSourceId, triggerSource);
7502
+ }
7503
+ /**
7504
+ * ADMIN: Delete a trigger source
7505
+ */
7506
+ async deleteTriggerSource(triggerSourceId) {
7507
+ return this.triggerSourceApi.deleteTriggerSource(triggerSourceId);
7508
+ }
7509
+ }
7510
+
6816
7511
  /**
6817
7512
  * PERS SDK Configuration interfaces and utilities
6818
7513
  *
@@ -6896,10 +7591,7 @@ class AuthApi {
6896
7591
  * Authenticates a user in a business context with role included in JWT.
6897
7592
  *
6898
7593
  * @param jwt - Authentication token (passkey or Firebase JWT)
6899
- * @param options - Business authentication options
6900
- * @param options.businessId - The business ID to authenticate as
6901
- * - If user has single business membership, auto-selected
6902
- * - If user has multiple memberships without businessId, throws MULTIPLE_CONTEXT_SELECTION_REQUIRED
7594
+ * @param options - Business authentication options (businessId for multi-business users, auto-selected if single membership)
6903
7595
  * @returns Session response with business context and role in JWT
6904
7596
  * @throws MultipleContextSelectionError when businessId is required but not provided
6905
7597
  *
@@ -7018,10 +7710,7 @@ class AuthService {
7018
7710
  * the user's role within that business.
7019
7711
  *
7020
7712
  * @param jwt - Authentication token (passkey or Firebase JWT)
7021
- * @param options - Business authentication options
7022
- * @param options.businessId - The business ID to authenticate as
7023
- * - If user has single business membership, auto-selected
7024
- * - If user has multiple memberships without businessId, throws MULTIPLE_CONTEXT_SELECTION_REQUIRED
7713
+ * @param options - Business authentication options (businessId for multi-business users)
7025
7714
  * @returns Session response with business context and role baked into JWT
7026
7715
  * @throws MultipleContextSelectionRequiredError when businessId is required but not provided
7027
7716
  *
@@ -7912,13 +8601,15 @@ class PersApiClient {
7912
8601
  * @internal
7913
8602
  */
7914
8603
  emitErrorEvent(errorDetails, errorMessage, endpoint, method, status, error) {
7915
- console.log('[PersApiClient] emitErrorEvent called', {
7916
- hasEvents: !!this._events,
7917
- instanceId: this._events?.instanceId ?? 'none',
7918
- subscriberCount: this._events?.subscriberCount ?? 0,
7919
- domain: errorDetails.domain,
7920
- message: errorDetails.message || errorMessage
7921
- });
8604
+ /* console.log('[PersApiClient] emitErrorEvent called', {
8605
+ hasEvents: !!this._events,
8606
+ instanceId: this._events?.instanceId ?? 'none',
8607
+ subscriberCount: this._events?.subscriberCount ?? 0,
8608
+ domain: errorDetails.domain,
8609
+ code: errorDetails.code,
8610
+ status: status,
8611
+ message: errorDetails.message || errorMessage
8612
+ }); */
7922
8613
  this._events?.emitError({
7923
8614
  domain: errorDetails.domain || 'external',
7924
8615
  type: errorDetails.code || 'API_ERROR',
@@ -8351,8 +9042,7 @@ class AuthManager {
8351
9042
  * throws `MULTIPLE_CONTEXT_SELECTION_REQUIRED` error with available options
8352
9043
  *
8353
9044
  * @param jwtToken - JWT token from auth provider (passkey, Firebase, etc.)
8354
- * @param options - Business authentication options
8355
- * @param options.businessId - The business ID to authenticate as (required for multi-business users)
9045
+ * @param options - Business authentication options (businessId for multi-business users)
8356
9046
  * @returns Promise resolving to authentication response with business context and role in JWT
8357
9047
  * @throws Error with code `MULTIPLE_CONTEXT_SELECTION_REQUIRED` when businessId is needed
8358
9048
  *
@@ -8779,26 +9469,49 @@ class UserManager {
8779
9469
  * requires administrator privileges and returns full user profiles including
8780
9470
  * private information.
8781
9471
  *
8782
- * @returns Promise resolving to array of all users with complete data
9472
+ * @param options - Pagination options (page, limit, sortBy, sortOrder)
9473
+ * @param search - Optional search query to filter users
9474
+ * @returns Promise resolving to paginated users with complete data
8783
9475
  * @throws {PersApiError} When not authenticated as admin
8784
9476
  *
8785
- * @example
9477
+ * @example Basic Usage
8786
9478
  * ```typescript
8787
9479
  * // Admin operation - requires admin authentication
8788
9480
  * try {
8789
- * const allUsers = await sdk.users.getAllUsers();
8790
- * console.log(`Total users: ${allUsers.length}`);
9481
+ * const result = await sdk.users.getAllUsers();
9482
+ * console.log(`Total users: ${result.pagination.total}`);
8791
9483
  *
8792
- * allUsers.forEach(user => {
8793
- * console.log(`${user.name} - ${user.email} - Active: ${user.isActive}`);
9484
+ * result.data.forEach(user => {
9485
+ * console.log(`${user.firstName} - ${user.email} - Active: ${user.isActive}`);
8794
9486
  * });
8795
9487
  * } catch (error) {
8796
9488
  * console.log('Admin access required');
8797
9489
  * }
8798
9490
  * ```
9491
+ *
9492
+ * @example With Sorting
9493
+ * ```typescript
9494
+ * import { SortOrder } from '@explorins/pers-sdk';
9495
+ *
9496
+ * // Get users sorted by email ascending
9497
+ * const result = await sdk.users.getAllUsers({
9498
+ * page: 1,
9499
+ * limit: 20,
9500
+ * sortBy: 'email',
9501
+ * sortOrder: SortOrder.ASC
9502
+ * });
9503
+ *
9504
+ * // Get users sorted by creation date (newest first)
9505
+ * const recent = await sdk.users.getAllUsers({
9506
+ * page: 1,
9507
+ * limit: 10,
9508
+ * sortBy: 'createdAt',
9509
+ * sortOrder: SortOrder.DESC
9510
+ * });
9511
+ * ```
8799
9512
  */
8800
- async getAllUsers(options) {
8801
- return this.userService.getAllRemoteUsers(options);
9513
+ async getAllUsers(options, search) {
9514
+ return this.userService.getAllRemoteUsers(options, search);
8802
9515
  }
8803
9516
  /**
8804
9517
  * Business/Admin: Create or update a user
@@ -9963,6 +10676,7 @@ class CampaignManager {
9963
10676
  * business partnerships, eligibility criteria, and claiming requirements.
9964
10677
  *
9965
10678
  * @param campaignId - Unique campaign identifier
10679
+ * @param include - Relations to include: 'triggerSources', 'businesses'
9966
10680
  * @returns Promise resolving to campaign data with complete details
9967
10681
  * @throws {PersApiError} When campaign with specified ID is not found
9968
10682
  *
@@ -9991,9 +10705,17 @@ class CampaignManager {
9991
10705
  * console.log('Campaign not found:', error.message);
9992
10706
  * }
9993
10707
  * ```
10708
+ *
10709
+ * @example With Include Relations
10710
+ * ```typescript
10711
+ * // Get campaign with trigger sources and businesses included
10712
+ * const campaign = await sdk.campaigns.getCampaignById('campaign-123', ['triggerSources', 'businesses']);
10713
+ * console.log('Trigger sources:', campaign.included?.triggerSources);
10714
+ * console.log('Businesses:', campaign.included?.businesses);
10715
+ * ```
9994
10716
  */
9995
- async getCampaignById(campaignId) {
9996
- return this.campaignService.getCampaignById(campaignId);
10717
+ async getCampaignById(campaignId, include) {
10718
+ return this.campaignService.getCampaignById(campaignId, include);
9997
10719
  }
9998
10720
  /**
9999
10721
  * Claim a campaign reward
@@ -10099,7 +10821,8 @@ class CampaignManager {
10099
10821
  * ```
10100
10822
  */
10101
10823
  async getUserClaims(options) {
10102
- return this.campaignService.getClaimsForLoggedUser(options);
10824
+ const { include, ...paginationOptions } = options || {};
10825
+ return this.campaignService.getClaimsForLoggedUser(paginationOptions, include);
10103
10826
  }
10104
10827
  /**
10105
10828
  * Get campaigns with pagination support
@@ -10107,13 +10830,7 @@ class CampaignManager {
10107
10830
  * Returns campaigns with pagination metadata for efficient data loading.
10108
10831
  * Intelligent access: Public gets active only, Business gets own campaigns, Admin gets all.
10109
10832
  *
10110
- * @param options - Pagination and filter options
10111
- * @param options.active - Filter by active status (true/false/undefined for all)
10112
- * @param options.businessId - Filter by business engagement
10113
- * @param options.page - Page number (1-based, default: 1)
10114
- * @param options.limit - Items per page (default: 50)
10115
- * @param options.sortBy - Sort field ('name', 'createdAt', 'startDate')
10116
- * @param options.sortOrder - Sort direction ('ASC' or 'DESC')
10833
+ * @param options - Pagination and filter options (page, limit, sortBy, sortOrder, active, businessId, include)
10117
10834
  * @returns Promise resolving to paginated campaigns with metadata
10118
10835
  *
10119
10836
  * @example
@@ -10138,6 +10855,11 @@ class CampaignManager {
10138
10855
  * page: 1,
10139
10856
  * limit: 25
10140
10857
  * });
10858
+ *
10859
+ * // Include related data (trigger sources and businesses)
10860
+ * const campaignsWithRelations = await sdk.campaigns.getCampaigns({
10861
+ * include: ['triggerSources', 'businesses']
10862
+ * });
10141
10863
  * ```
10142
10864
  */
10143
10865
  async getCampaigns(options) {
@@ -10255,57 +10977,188 @@ class CampaignManager {
10255
10977
  async toggleCampaignTestnet(campaignId) {
10256
10978
  return this.campaignService.toggleCampaignTestnet(campaignId);
10257
10979
  }
10980
+ // ==========================================
10981
+ // CAMPAIGN TRIGGER OPERATIONS
10982
+ // Rules & limits for campaign activation
10983
+ // ==========================================
10258
10984
  /**
10259
- * Admin: Get campaign triggers
10985
+ * Admin: Get all campaign triggers (paginated)
10260
10986
  *
10261
- * Retrieves all available campaign trigger mechanisms that can be used to
10262
- * automatically activate campaigns based on user actions or system events.
10263
- * Requires administrator privileges.
10987
+ * Retrieves all campaign trigger rules that define rate limits, geo-validation,
10988
+ * conditions, and trigger types for campaign activation.
10264
10989
  *
10265
- * @returns Promise resolving to array of campaign trigger definitions
10990
+ * @param options - Pagination options
10991
+ * @returns Promise resolving to paginated list of campaign triggers
10266
10992
  *
10267
10993
  * @example
10268
10994
  * ```typescript
10269
- * // Admin operation - view available triggers
10270
- * const triggers = await sdk.campaigns.getCampaignTriggers();
10271
- *
10272
- * console.log('Available Campaign Triggers:');
10273
- * triggers.forEach(trigger => {
10274
- * console.log(`- ${trigger.name}: ${trigger.description}`);
10275
- * console.log(` Type: ${trigger.type}`);
10276
- * console.log(` Parameters: ${JSON.stringify(trigger.parameters)}`);
10995
+ * const triggers = await sdk.campaigns.getCampaignTriggers({ page: 1, limit: 20 });
10996
+ * console.log(`Found ${triggers.total} triggers`);
10997
+ * triggers.data.forEach(trigger => {
10998
+ * console.log(`- ${trigger.name}: max ${trigger.maxPerDayPerUser}/day`);
10277
10999
  * });
10278
11000
  * ```
10279
11001
  */
10280
11002
  async getCampaignTriggers(options) {
10281
- return this.campaignService.getCampaignTriggers();
11003
+ return this.campaignService.getCampaignTriggers(options);
11004
+ }
11005
+ /**
11006
+ * Admin: Get campaign trigger by ID
11007
+ *
11008
+ * @param triggerId - The campaign trigger ID
11009
+ * @returns Promise resolving to the campaign trigger
11010
+ *
11011
+ * @example
11012
+ * ```typescript
11013
+ * const trigger = await sdk.campaigns.getCampaignTriggerById('trigger-123');
11014
+ * console.log('Max per day:', trigger.maxPerDayPerUser);
11015
+ * ```
11016
+ */
11017
+ async getCampaignTriggerById(triggerId) {
11018
+ return this.campaignService.getCampaignTriggerById(triggerId);
11019
+ }
11020
+ /**
11021
+ * Admin: Create a new campaign trigger
11022
+ *
11023
+ * Creates a trigger rule that defines rate limits, geo-validation, and conditions
11024
+ * for campaign activation. Triggers are created independently and then assigned
11025
+ * to campaigns via `setCampaignTrigger()`.
11026
+ *
11027
+ * @param data - Trigger configuration
11028
+ * @returns Promise resolving to created trigger
11029
+ *
11030
+ * @example
11031
+ * ```typescript
11032
+ * const trigger = await sdk.campaigns.createCampaignTrigger({
11033
+ * name: 'Daily Check-in',
11034
+ * maxPerDayPerUser: 1,
11035
+ * maxPerUser: 100,
11036
+ * minCooldownSeconds: 3600,
11037
+ * maxGeoDistanceInMeters: 50,
11038
+ * triggerType: 'CLAIM_BY_USER'
11039
+ * });
11040
+ *
11041
+ * // Then assign to campaign
11042
+ * await sdk.campaigns.setCampaignTrigger(campaignId, trigger.id);
11043
+ * ```
11044
+ */
11045
+ async createCampaignTrigger(data) {
11046
+ const result = await this.campaignService.createCampaignTrigger(data);
11047
+ this.events?.emitSuccess({
11048
+ domain: 'campaign',
11049
+ type: 'CAMPAIGN_TRIGGER_CREATED',
11050
+ userMessage: `Trigger "${data.name}" created successfully`,
11051
+ details: { triggerId: result.id, name: data.name }
11052
+ });
11053
+ return result;
11054
+ }
11055
+ /**
11056
+ * Admin: Update an existing campaign trigger
11057
+ *
11058
+ * @param triggerId - The campaign trigger ID
11059
+ * @param data - Updated trigger configuration (partial)
11060
+ * @returns Promise resolving to updated trigger
11061
+ *
11062
+ * @example
11063
+ * ```typescript
11064
+ * const updated = await sdk.campaigns.updateCampaignTrigger('trigger-123', {
11065
+ * maxPerDayPerUser: 5,
11066
+ * minCooldownSeconds: 1800
11067
+ * });
11068
+ * ```
11069
+ */
11070
+ async updateCampaignTrigger(triggerId, data) {
11071
+ const result = await this.campaignService.updateCampaignTrigger(triggerId, data);
11072
+ this.events?.emitSuccess({
11073
+ domain: 'campaign',
11074
+ type: 'CAMPAIGN_TRIGGER_UPDATED',
11075
+ userMessage: 'Trigger updated successfully',
11076
+ details: { triggerId, updates: Object.keys(data) }
11077
+ });
11078
+ return result;
11079
+ }
11080
+ /**
11081
+ * Admin: Delete a campaign trigger
11082
+ *
11083
+ * @param triggerId - The campaign trigger ID
11084
+ * @returns Promise resolving to success status
11085
+ *
11086
+ * @example
11087
+ * ```typescript
11088
+ * await sdk.campaigns.deleteCampaignTrigger('trigger-123');
11089
+ * ```
11090
+ */
11091
+ async deleteCampaignTrigger(triggerId) {
11092
+ const result = await this.campaignService.deleteCampaignTrigger(triggerId);
11093
+ this.events?.emitSuccess({
11094
+ domain: 'campaign',
11095
+ type: 'CAMPAIGN_TRIGGER_DELETED',
11096
+ userMessage: 'Trigger deleted successfully',
11097
+ details: { triggerId }
11098
+ });
11099
+ return result;
10282
11100
  }
10283
11101
  /**
10284
- * Admin: Set campaign trigger
11102
+ * Admin: Assign a trigger to a campaign
10285
11103
  *
10286
- * Associates a specific trigger mechanism with a campaign, enabling automatic
10287
- * campaign activation based on defined conditions. Requires administrator privileges.
11104
+ * Associates a trigger rule with a campaign. A campaign can have only one trigger
11105
+ * at a time. Assigning a new trigger replaces any existing trigger.
10288
11106
  *
10289
11107
  * @param campaignId - ID of the campaign
10290
11108
  * @param triggerId - ID of the trigger to associate
10291
- * @returns Promise resolving to updated campaign data
10292
- * @throws {PersApiError} When not authenticated as admin or entities not found
11109
+ * @returns Promise resolving to updated campaign
10293
11110
  *
10294
11111
  * @example
10295
11112
  * ```typescript
10296
- * // Admin operation - set up automatic campaign trigger
10297
11113
  * const updated = await sdk.campaigns.setCampaignTrigger(
10298
- * 'new-user-welcome',
10299
- * 'user-registration-trigger'
11114
+ * 'campaign-123',
11115
+ * 'trigger-456'
10300
11116
  * );
10301
- *
10302
- * console.log('Trigger set for campaign:', updated.title);
10303
- * console.log('Trigger will activate on user registration');
11117
+ * console.log('Trigger assigned:', updated.trigger?.name);
10304
11118
  * ```
10305
11119
  */
10306
11120
  async setCampaignTrigger(campaignId, triggerId) {
10307
- return this.campaignService.setCampaignTrigger(campaignId, triggerId);
11121
+ const result = await this.campaignService.setCampaignTrigger(campaignId, triggerId);
11122
+ this.events?.emitSuccess({
11123
+ domain: 'campaign',
11124
+ type: 'CAMPAIGN_TRIGGER_ASSIGNED',
11125
+ userMessage: 'Trigger assigned to campaign',
11126
+ details: { campaignId, triggerId }
11127
+ });
11128
+ return result;
10308
11129
  }
11130
+ /**
11131
+ * Admin: Remove a trigger from a campaign
11132
+ *
11133
+ * Removes the trigger rule from a campaign. The trigger itself is not deleted
11134
+ * and can be reassigned to other campaigns.
11135
+ *
11136
+ * @param campaignId - ID of the campaign
11137
+ * @param triggerId - ID of the trigger to remove
11138
+ * @returns Promise resolving to updated campaign
11139
+ *
11140
+ * @example
11141
+ * ```typescript
11142
+ * const updated = await sdk.campaigns.removeCampaignTrigger(
11143
+ * 'campaign-123',
11144
+ * 'trigger-456'
11145
+ * );
11146
+ * console.log('Trigger removed:', updated.trigger === null);
11147
+ * ```
11148
+ */
11149
+ async removeCampaignTrigger(campaignId, triggerId) {
11150
+ const result = await this.campaignService.removeCampaignTrigger(campaignId, triggerId);
11151
+ this.events?.emitSuccess({
11152
+ domain: 'campaign',
11153
+ type: 'CAMPAIGN_TRIGGER_REMOVED',
11154
+ userMessage: 'Trigger removed from campaign',
11155
+ details: { campaignId, triggerId }
11156
+ });
11157
+ return result;
11158
+ }
11159
+ // ==========================================
11160
+ // TOKEN UNIT OPERATIONS
11161
+ // ==========================================
10309
11162
  /**
10310
11163
  * Admin: Create campaign token unit
10311
11164
  *
@@ -10514,8 +11367,8 @@ class CampaignManager {
10514
11367
  * });
10515
11368
  * ```
10516
11369
  */
10517
- async getCampaignClaims(filters) {
10518
- return this.campaignService.getCampaignClaims(filters);
11370
+ async getCampaignClaims(filters, include) {
11371
+ return this.campaignService.getCampaignClaims(filters, include);
10519
11372
  }
10520
11373
  /**
10521
11374
  * Admin: Get campaign claims by user ID
@@ -10550,8 +11403,8 @@ class CampaignManager {
10550
11403
  * console.log(`\nTotal rewards earned: ${totalRewards}`);
10551
11404
  * ```
10552
11405
  */
10553
- async getCampaignClaimsByUserId(userId, options) {
10554
- return this.campaignService.getCampaignClaimsByUserId(userId);
11406
+ async getCampaignClaimsByUserId(userId, options, include) {
11407
+ return this.campaignService.getCampaignClaimsByUserId(userId, options, include);
10555
11408
  }
10556
11409
  /**
10557
11410
  * Admin: Get campaign claims by business ID
@@ -10591,8 +11444,79 @@ class CampaignManager {
10591
11444
  * console.log(`\nTotal rewards distributed: ${totalRewardsDistributed}`);
10592
11445
  * ```
10593
11446
  */
10594
- async getCampaignClaimsByBusinessId(businessId, options) {
10595
- return this.campaignService.getCampaignClaimsByBusinessId(businessId);
11447
+ async getCampaignClaimsByBusinessId(businessId, options, include) {
11448
+ return this.campaignService.getCampaignClaimsByBusinessId(businessId, options, include);
11449
+ }
11450
+ // ==========================================
11451
+ // TRIGGER SOURCE ASSIGNMENT
11452
+ // Note: TriggerSource CRUD is in TriggerSourceManager (sdk.triggerSources)
11453
+ // ==========================================
11454
+ /**
11455
+ * Admin: Assign a trigger source to a campaign
11456
+ *
11457
+ * Associates a trigger source with a campaign, enabling the trigger source
11458
+ * to activate campaign rewards when triggered. A campaign can have multiple
11459
+ * trigger sources. Requires administrator privileges.
11460
+ *
11461
+ * Note: To create/update/delete trigger sources, use `sdk.triggerSources`.
11462
+ *
11463
+ * @param campaignId - Campaign UUID
11464
+ * @param triggerSourceId - Trigger source UUID
11465
+ * @returns Promise resolving to updated campaign with trigger source assigned
11466
+ * @throws {PersApiError} When not authenticated as admin or entities not found
11467
+ *
11468
+ * @example
11469
+ * ```typescript
11470
+ * // Create trigger source first
11471
+ * const source = await sdk.triggerSources.create({
11472
+ * type: 'QR_CODE',
11473
+ * name: 'Store Entrance QR'
11474
+ * });
11475
+ *
11476
+ * // Then assign to campaign
11477
+ * const updated = await sdk.campaigns.assignTriggerSource(
11478
+ * 'campaign-123',
11479
+ * source.id
11480
+ * );
11481
+ *
11482
+ * console.log('Trigger source assigned:', updated.triggerSourceIds.length);
11483
+ * ```
11484
+ */
11485
+ async assignTriggerSource(campaignId, triggerSourceId) {
11486
+ const result = await this.campaignService.assignTriggerSourceToCampaign(campaignId, triggerSourceId);
11487
+ this.events?.emitSuccess({
11488
+ domain: 'campaign',
11489
+ type: 'TRIGGER_SOURCE_ASSIGNED',
11490
+ userMessage: 'Trigger source assigned to campaign',
11491
+ details: { campaignId, triggerSourceId }
11492
+ });
11493
+ return result;
11494
+ }
11495
+ /**
11496
+ * Admin: Remove a trigger source from a campaign
11497
+ *
11498
+ * Removes the association between a trigger source and a campaign. The trigger
11499
+ * source itself is not deleted and can be reassigned to other campaigns.
11500
+ * Requires administrator privileges.
11501
+ *
11502
+ * @param campaignId - Campaign UUID
11503
+ * @param triggerSourceId - Trigger source UUID
11504
+ * @returns Promise resolving to updated campaign with trigger source removed
11505
+ * @throws {PersApiError} When not authenticated as admin or entities not found
11506
+ *
11507
+ * @example
11508
+ * ```typescript
11509
+ * // Remove a trigger source from a campaign
11510
+ * const updated = await sdk.campaigns.removeTriggerSource(
11511
+ * 'campaign-123',
11512
+ * 'source-456'
11513
+ * );
11514
+ *
11515
+ * console.log('Remaining trigger source IDs:', updated.triggerSourceIds.length);
11516
+ * ```
11517
+ */
11518
+ async removeTriggerSource(campaignId, triggerSourceId) {
11519
+ return this.campaignService.removeTriggerSourceFromCampaign(campaignId, triggerSourceId);
10596
11520
  }
10597
11521
  /**
10598
11522
  * Get the full campaign service for advanced operations
@@ -10976,8 +11900,57 @@ class RedemptionManager {
10976
11900
  * });
10977
11901
  * ```
10978
11902
  */
10979
- async getUserRedemptions(options) {
10980
- return this.redemptionService.getUserRedeems(options);
11903
+ async getUserRedemptions(options, include) {
11904
+ return this.redemptionService.getUserRedeems(options, include);
11905
+ }
11906
+ /**
11907
+ * Admin: Get all redemption redeems with filtering and enriched data
11908
+ *
11909
+ * Retrieves all redemption redeems across the platform with filtering capabilities.
11910
+ * This is an admin-level operation that allows monitoring and analytics of redemption
11911
+ * activity. Supports filtering by user, redemption offer, and enrichment of related entities.
11912
+ *
11913
+ * @param filters - Filter options (userId, redemptionId, pagination)
11914
+ * @param include - Optional relations to include for enrichment
11915
+ * @returns Promise resolving to paginated list of redemption redeems
11916
+ * @throws {PersApiError} When not authenticated as admin
11917
+ *
11918
+ * @example Get All Redeems
11919
+ * ```typescript
11920
+ * // Admin operation - get all redemption redeems
11921
+ * const { data: allRedeems, pagination } = await sdk.redemptions.getRedemptionRedeems();
11922
+ *
11923
+ * console.log(`Total redeems: ${pagination.total}`);
11924
+ * console.log(`Page ${pagination.page} of ${pagination.pages}`);
11925
+ * ```
11926
+ *
11927
+ * @example Filter by User
11928
+ * ```typescript
11929
+ * // Get redeems for specific user
11930
+ * const { data: userRedeems } = await sdk.redemptions.getRedemptionRedeems({
11931
+ * userId: 'user-123',
11932
+ * limit: 50
11933
+ * });
11934
+ *
11935
+ * console.log(`User has ${userRedeems.length} redemptions`);
11936
+ * ```
11937
+ *
11938
+ * @example With Enriched Data
11939
+ * ```typescript
11940
+ * const { data: redeems } = await sdk.redemptions.getRedemptionRedeems(
11941
+ * { page: 1, limit: 20 },
11942
+ * ['redemption', 'user', 'business']
11943
+ * );
11944
+ *
11945
+ * redeems.forEach(redeem => {
11946
+ * const redemptionName = redeem.included?.redemption?.name || 'Unknown';
11947
+ * const userEmail = redeem.included?.user?.email || redeem.userId;
11948
+ * console.log(`${redemptionName} - ${userEmail}`);
11949
+ * });
11950
+ * ```
11951
+ */
11952
+ async getRedemptionRedeems(filters, include) {
11953
+ return this.redemptionService.getRedemptionRedeems(filters, include);
10981
11954
  }
10982
11955
  /**
10983
11956
  * Admin: Create new redemption offer
@@ -11220,8 +12193,8 @@ class RedemptionManager {
11220
12193
  *
11221
12194
  * @example Administrative Reporting
11222
12195
  * ```typescript
11223
- * // Admin: Get all tenant transactions for analysis
11224
- * const allTransactions = await sdk.transactions.getTenantTransactions();
12196
+ * // Admin: Get paginated transactions for analysis
12197
+ * const allTransactions = await sdk.transactions.getPaginatedTransactions({ page: 1, limit: 100 });
11225
12198
  *
11226
12199
  * // Export transaction data
11227
12200
  * const csvBlob = await sdk.transactions.exportTransactionsCSV();
@@ -11244,13 +12217,18 @@ class TransactionManager {
11244
12217
  * Provides complete transaction audit trail and verification data.
11245
12218
  *
11246
12219
  * @param transactionId - Unique transaction identifier
12220
+ * @param include - Optional relations to include (sender, recipient, business) for enriched entity data
11247
12221
  * @returns Promise resolving to complete transaction data
11248
12222
  * @throws {PersApiError} When transaction with specified ID is not found or access denied
11249
12223
  *
11250
12224
  * @example
11251
12225
  * ```typescript
11252
12226
  * try {
11253
- * const transaction = await sdk.transactions.getTransactionById('txn-abc123');
12227
+ * // Get transaction with enriched sender/recipient data
12228
+ * const transaction = await sdk.transactions.getTransactionById(
12229
+ * 'txn-abc123',
12230
+ * ['sender', 'recipient', 'business']
12231
+ * );
11254
12232
  *
11255
12233
  * console.log('Transaction Details:');
11256
12234
  * console.log('ID:', transaction.id);
@@ -11263,16 +12241,19 @@ class TransactionManager {
11263
12241
  * console.log('Description:', transaction.description);
11264
12242
  * }
11265
12243
  *
11266
- * if (transaction.business) {
11267
- * console.log('Business:', transaction.business.displayName);
12244
+ * // Access enriched sender data
12245
+ * if (transaction.included?.sender) {
12246
+ * console.log('Sender:', transaction.included.sender);
11268
12247
  * }
11269
12248
  *
11270
- * // Show token movements
11271
- * if (transaction.tokenTransfers?.length) {
11272
- * console.log('\nToken Transfers:');
11273
- * transaction.tokenTransfers.forEach(transfer => {
11274
- * console.log(`${transfer.amount} ${transfer.token.symbol} ${transfer.direction}`);
11275
- * });
12249
+ * // Access enriched recipient data
12250
+ * if (transaction.included?.recipient) {
12251
+ * console.log('Recipient:', transaction.included.recipient);
12252
+ * }
12253
+ *
12254
+ * // Access enriched business data
12255
+ * if (transaction.included?.engagedBusiness) {
12256
+ * console.log('Business:', transaction.included.engagedBusiness.displayName);
11276
12257
  * }
11277
12258
  *
11278
12259
  * // Show blockchain confirmation
@@ -11285,8 +12266,8 @@ class TransactionManager {
11285
12266
  * }
11286
12267
  * ```
11287
12268
  */
11288
- async getTransactionById(transactionId) {
11289
- return this.transactionService.getTransactionById(transactionId);
12269
+ async getTransactionById(transactionId, include) {
12270
+ return this.transactionService.getTransactionById(transactionId, include);
11290
12271
  }
11291
12272
  /**
11292
12273
  * Create a new transaction
@@ -11380,150 +12361,62 @@ class TransactionManager {
11380
12361
  /**
11381
12362
  * Get user's transaction history
11382
12363
  *
11383
- * Retrieves transaction history for the authenticated user, filtered by
11384
- * transaction role. Provides chronological view of all user's loyalty
12364
+ * Retrieves transaction history for the authenticated user with comprehensive
12365
+ * filtering options. Provides chronological view of all user's loyalty
11385
12366
  * activities including purchases, rewards, redemptions, and transfers.
12367
+ * Optionally enrich with related entities (sender, recipient, business).
11386
12368
  *
11387
- * @param role - Optional role filter (TransactionRole.SENDER, TransactionRole.RECIPIENT). If undefined, returns all user transactions
11388
- * @param limit - Maximum number of transactions to return (default: 1000)
11389
- * @returns Promise resolving to array of user's transactions
12369
+ * @param options - Query options including filters, pagination, and include relations
12370
+ * @returns Promise resolving to paginated transaction data with optional included entities
11390
12371
  *
11391
12372
  * @example All Transactions
11392
12373
  * ```typescript
11393
- * const allTransactions = await sdk.transactions.getUserTransactionHistory('ALL');
12374
+ * const result = await sdk.transactions.getUserTransactionHistory();
11394
12375
  *
11395
- * console.log(` Transaction History (${allTransactions.length} transactions):`);
12376
+ * console.log(`Transaction History (${result.data.length} of ${result.pagination.total} transactions)`);
11396
12377
  *
11397
- * allTransactions.forEach((transaction, index) => {
12378
+ * result.data.forEach((transaction, index) => {
11398
12379
  * const date = new Date(transaction.createdAt).toLocaleDateString();
11399
12380
  * console.log(`\n${index + 1}. ${transaction.type} - ${date}`);
11400
12381
  * console.log(` ${transaction.description || 'No description'}`);
11401
12382
  * console.log(` Status: ${transaction.status}`);
11402
- *
11403
- * if (transaction.amount && transaction.currency) {
11404
- * console.log(` Amount: ${transaction.amount} ${transaction.currency}`);
11405
- * }
11406
- *
11407
- * if (transaction.business) {
11408
- * console.log(` Business: ${transaction.business.displayName}`);
11409
- * }
11410
12383
  * });
11411
12384
  * ```
11412
12385
  *
11413
- * @example Filtered by Type
12386
+ * @example Filter by Role and Status
11414
12387
  * ```typescript
11415
- * // Get only purchase transactions
11416
- * const purchases = await sdk.transactions.getUserTransactionHistory('PURCHASE');
11417
- *
11418
- * let totalSpent = 0;
11419
- * let totalRewards = 0;
11420
- *
11421
- * purchases.forEach(purchase => {
11422
- * if (purchase.amount) {
11423
- * totalSpent += purchase.amount;
11424
- * }
11425
- *
11426
- * if (purchase.tokensEarned?.length) {
11427
- * purchase.tokensEarned.forEach(reward => {
11428
- * totalRewards += reward.amount;
11429
- * });
11430
- * }
12388
+ * // Get only completed sent transactions
12389
+ * const sent = await sdk.transactions.getUserTransactionHistory({
12390
+ * role: TransactionRole.SENDER,
12391
+ * status: TransactionStatus.COMPLETED,
12392
+ * include: ['recipient', 'business'],
12393
+ * page: 1,
12394
+ * limit: 50
11431
12395
  * });
11432
12396
  *
11433
- * console.log('Purchase Summary:');
11434
- * console.log(`Total purchases: ${purchases.length}`);
11435
- * console.log(`Total spent: $${totalSpent.toFixed(2)}`);
11436
- * console.log(`Total rewards earned: ${totalRewards} points`);
11437
- * ```
11438
- *
11439
- * @example Recent Activity
11440
- * ```typescript
11441
- * const recentTransactions = await sdk.transactions.getUserTransactionHistory('ALL');
11442
- *
11443
- * // Filter to last 30 days
11444
- * const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
11445
- * const recentActivity = recentTransactions.filter(t =>
11446
- * new Date(t.createdAt) > thirtyDaysAgo
11447
- * );
11448
- *
11449
- * console.log(`Recent Activity (${recentActivity.length} transactions in last 30 days):`);
11450
- *
11451
- * // Group by type
11452
- * const activityByType = recentActivity.reduce((acc, t) => {
11453
- * acc[t.type] = (acc[t.type] || 0) + 1;
11454
- * return acc;
11455
- * }, {});
11456
- *
11457
- * Object.entries(activityByType).forEach(([type, count]) => {
11458
- * console.log(`${type}: ${count} transactions`);
12397
+ * sent.data.forEach(tx => {
12398
+ * console.log(`Sent to: ${tx.included?.recipient?.displayName || tx.recipientAddress}`);
11459
12399
  * });
11460
12400
  * ```
11461
- */
11462
- async getUserTransactionHistory(role, options) {
11463
- return this.transactionService.getUserTransactionHistory(role, options);
11464
- }
11465
- /**
11466
- * Admin: Get all tenant transactions
11467
- *
11468
- * Retrieves all transactions across the entire tenant/organization for
11469
- * comprehensive reporting and analysis. This operation requires administrator
11470
- * privileges and provides system-wide transaction visibility.
11471
- *
11472
- * @param limit - Maximum number of transactions to return (default: 1000)
11473
- * @returns Promise resolving to array of all tenant transactions
11474
- * @throws {PersApiError} When not authenticated as administrator
11475
12401
  *
11476
- * @example
12402
+ * @example Filter by Trigger Process (Campaign/Redemption)
11477
12403
  * ```typescript
11478
- * // Admin operation - comprehensive transaction analysis
11479
- * const allTransactions = await sdk.transactions.getTenantTransactions();
11480
- *
11481
- * console.log(`Tenant Transaction Overview:`);
11482
- * console.log(`Total transactions: ${allTransactions.length}`);
11483
- *
11484
- * // Analyze by status
11485
- * const statusCounts = allTransactions.reduce((acc, t) => {
11486
- * acc[t.status] = (acc[t.status] || 0) + 1;
11487
- * return acc;
11488
- * }, {});
11489
- *
11490
- * console.log('\nBy status:');
11491
- * Object.entries(statusCounts).forEach(([status, count]) => {
11492
- * console.log(`${status}: ${count} transactions`);
12404
+ * // Get all transactions triggered by a specific campaign claim
12405
+ * const claimTxs = await sdk.transactions.getUserTransactionHistory({
12406
+ * triggerProcessId: 'claim-abc123',
12407
+ * include: ['sender', 'recipient', 'business']
11493
12408
  * });
11494
12409
  *
11495
- * // Analyze by type
11496
- * const typeCounts = allTransactions.reduce((acc, t) => {
11497
- * acc[t.type] = (acc[t.type] || 0) + 1;
11498
- * return acc;
11499
- * }, {});
11500
- *
11501
- * console.log('\nBy type:');
11502
- * Object.entries(typeCounts).forEach(([type, count]) => {
11503
- * console.log(`${type}: ${count} transactions`);
12410
+ * claimTxs.data.forEach(tx => {
12411
+ * console.log('Transaction from claim:', tx.id);
12412
+ * if (tx.included?.engagedBusiness) {
12413
+ * console.log('Business:', tx.included.engagedBusiness.displayName);
12414
+ * }
11504
12415
  * });
11505
- *
11506
- * // Calculate volume metrics
11507
- * const totalVolume = allTransactions.reduce((sum, t) =>
11508
- * sum + (t.amount || 0), 0
11509
- * );
11510
- *
11511
- * const avgTransactionSize = totalVolume / allTransactions.length;
11512
- *
11513
- * console.log('\nVolume metrics:');
11514
- * console.log(`Total volume: $${totalVolume.toFixed(2)}`);
11515
- * console.log(`Average transaction: $${avgTransactionSize.toFixed(2)}`);
11516
- *
11517
- * // Recent activity analysis
11518
- * const last24Hours = allTransactions.filter(t =>
11519
- * new Date(t.createdAt) > new Date(Date.now() - 24 * 60 * 60 * 1000)
11520
- * );
11521
- *
11522
- * console.log(`\n⏰ Last 24 hours: ${last24Hours.length} transactions`);
11523
12416
  * ```
11524
12417
  */
11525
- async getTenantTransactions(options) {
11526
- return this.transactionService.getTenantTransactions(options);
12418
+ async getUserTransactionHistory(options) {
12419
+ return this.transactionService.getUserTransactionHistory(options);
11527
12420
  }
11528
12421
  /**
11529
12422
  * Admin: Get paginated transactions
@@ -11532,7 +12425,7 @@ class TransactionManager {
11532
12425
  * handling. This operation requires administrator privileges and is used for
11533
12426
  * detailed transaction analysis and reporting interfaces.
11534
12427
  *
11535
- * @param params - Pagination and filtering parameters
12428
+ * @param options - Pagination, filtering parameters, and optional include relations
11536
12429
  * @returns Promise resolving to paginated transaction results with metadata
11537
12430
  * @throws {PersApiError} When not authenticated as administrator
11538
12431
  *
@@ -11587,9 +12480,24 @@ class TransactionManager {
11587
12480
  * console.log(`$${transaction.amount} - ${transaction.business?.displayName}`);
11588
12481
  * });
11589
12482
  * ```
12483
+ *
12484
+ * @example With Included Relations
12485
+ * ```typescript
12486
+ * // Include sender and recipient entities
12487
+ * const result = await sdk.transactions.getPaginatedTransactions({
12488
+ * page: 1,
12489
+ * limit: 50,
12490
+ * include: ['sender', 'recipient', 'business']
12491
+ * });
12492
+ *
12493
+ * result.data.forEach(tx => {
12494
+ * if (tx.included?.sender) console.log('From:', tx.included.sender);
12495
+ * if (tx.included?.recipient) console.log('To:', tx.included.recipient);
12496
+ * });
12497
+ * ```
11590
12498
  */
11591
- async getPaginatedTransactions(params) {
11592
- return this.transactionService.getPaginatedTransactions(params);
12499
+ async getPaginatedTransactions(options) {
12500
+ return this.transactionService.getPaginatedTransactions(options);
11593
12501
  }
11594
12502
  /**
11595
12503
  * Admin: Export transactions as CSV
@@ -13252,6 +14160,179 @@ class AnalyticsManager {
13252
14160
  async getTransactionAnalytics(request) {
13253
14161
  return this.analyticsService.getTransactionAnalytics(request);
13254
14162
  }
14163
+ /**
14164
+ * Get campaign claim analytics with aggregation
14165
+ *
14166
+ * Retrieves aggregated analytics for campaign claims with flexible grouping,
14167
+ * filtering, and metric selection. Provides insights into campaign performance,
14168
+ * claim patterns, and user engagement across the loyalty ecosystem.
14169
+ *
14170
+ * @param request - Analytics request with filters, groupBy, and metrics
14171
+ * @returns Promise resolving to campaign claim analytics data
14172
+ *
14173
+ * @example Claims per campaign
14174
+ * ```typescript
14175
+ * const analytics = await sdk.analytics.getCampaignClaimAnalytics({
14176
+ * filters: { status: 'COMPLETED' },
14177
+ * groupBy: ['campaignId'],
14178
+ * metrics: ['count'],
14179
+ * sortBy: 'count',
14180
+ * sortOrder: 'DESC',
14181
+ * limit: 10
14182
+ * });
14183
+ *
14184
+ * console.log('Top campaigns:', analytics.results);
14185
+ * ```
14186
+ */
14187
+ async getCampaignClaimAnalytics(request) {
14188
+ return this.analyticsService.getCampaignClaimAnalytics(request);
14189
+ }
14190
+ /**
14191
+ * Get user analytics with engagement metrics
14192
+ *
14193
+ * Retrieves aggregated user statistics including engagement rate, active users,
14194
+ * transaction metrics, and per-active-user averages. Provides comprehensive
14195
+ * insights into user behavior, engagement patterns, and platform adoption metrics.
14196
+ *
14197
+ * NEW: Added per-active-user metrics (averageTransactionsPerActiveUser, etc.) which
14198
+ * show concentrated engagement among active users - more useful than diluted all-user averages.
14199
+ *
14200
+ * Request structure matches TransactionAnalytics and CampaignClaimAnalytics:
14201
+ * - Use filters object for business-specific scoping
14202
+ * - startDate/endDate at root level for date range filtering
14203
+ *
14204
+ * @param request - Analytics request with optional filters and date range
14205
+ * @returns Promise resolving to user analytics data with per-user and per-active-user metrics
14206
+ *
14207
+ * @example Basic user analytics (compare per-user vs per-active-user)
14208
+ * ```typescript
14209
+ * const analytics = await sdk.analytics.getUserAnalytics({});
14210
+ * console.log(`Total users: ${analytics.totalUsers}`);
14211
+ * console.log(`Active users: ${analytics.activeUsers} (${analytics.engagementRate.toFixed(1)}%)`);
14212
+ *
14213
+ * // Per-user averages (includes inactive users - lower numbers)
14214
+ * console.log(`\\nPer-User Averages (all users):`);
14215
+ * console.log(` Transactions: ${analytics.averageTransactionsPerUser.toFixed(2)}`);
14216
+ * console.log(` Claims: ${analytics.averageClaimsPerUser.toFixed(2)}`);
14217
+ * console.log(` Redemptions: ${analytics.averageRedemptionsPerUser.toFixed(2)}`);
14218
+ *
14219
+ * // Per-active-user averages (only engaged users - higher, more meaningful numbers)
14220
+ * console.log(`\\nPer-Active-User Averages (engaged users only):`);
14221
+ * console.log(` Transactions: ${analytics.averageTransactionsPerActiveUser.toFixed(2)}`);
14222
+ * console.log(` Claims: ${analytics.averageClaimsPerActiveUser.toFixed(2)}`);
14223
+ * console.log(` Redemptions: ${analytics.averageRedemptionsPerActiveUser.toFixed(2)}`);
14224
+ * ```
14225
+ *
14226
+ * @example Monthly user analytics with date range
14227
+ * ```typescript
14228
+ * const analytics = await sdk.analytics.getUserAnalytics({
14229
+ * startDate: new Date('2026-02-01'),
14230
+ * endDate: new Date('2026-02-28')
14231
+ * });
14232
+ *
14233
+ * console.log('February 2026 User Metrics:');
14234
+ * console.log(`Total users: ${analytics.totalUsers}`);
14235
+ * console.log(`Active users: ${analytics.activeUsers}`);
14236
+ * console.log(`New users: ${analytics.newUsers}`);
14237
+ * console.log(`Engagement rate: ${analytics.engagementRate.toFixed(2)}%`);
14238
+ * console.log(`Total transaction volume: $${analytics.totalTransactionVolume.toLocaleString()}`);
14239
+ * console.log(`Avg transaction amount: $${analytics.averageTransactionAmount.toFixed(2)}`);
14240
+ * console.log(`\\nFilters applied: ${analytics.metadata.dateRange?.startDate} to ${analytics.metadata.dateRange?.endDate}`);
14241
+ * ```
14242
+ *
14243
+ * @example Business-specific user analytics with filters
14244
+ * ```typescript
14245
+ * const analytics = await sdk.analytics.getUserAnalytics({
14246
+ * filters: { businessId: 'business-123' },
14247
+ * startDate: new Date('2026-01-01'),
14248
+ * endDate: new Date('2026-12-31')
14249
+ * });
14250
+ *
14251
+ * console.log(`Business customer metrics for 2026:`);
14252
+ * console.log(`Active customers: ${analytics.activeUsers} / ${analytics.totalUsers}`);
14253
+ * console.log(`Customer engagement: ${analytics.engagementRate.toFixed(1)}%`);
14254
+ * console.log(`Avg spend per customer: $${analytics.averageTransactionAmount.toFixed(2)}`);
14255
+ * console.log(`Avg spend per active customer: $${(analytics.totalTransactionVolume / analytics.activeUsers).toFixed(2)}`);
14256
+ * console.log(`Avg claims per active customer: ${analytics.averageClaimsPerActiveUser.toFixed(2)}`);
14257
+ * ```
14258
+ */
14259
+ async getUserAnalytics(request = {}) {
14260
+ return this.analyticsService.getUserAnalytics(request);
14261
+ }
14262
+ /**
14263
+ * Get user transaction ranking with enriched user data
14264
+ *
14265
+ * Retrieves ranked list of users with full user details (email, externalUserId)
14266
+ * and transaction metrics. Data enrichment happens via efficient SQL JOINs + UNION
14267
+ * (handles legacy transactions). Ideal for leaderboards, engagement analysis, and
14268
+ * identifying power users for targeted campaigns.
14269
+ *
14270
+ * Use Cases:
14271
+ * - Admin leaderboards showing top users by activity
14272
+ * - User engagement analysis with full user context
14273
+ * - Identifying power users for campaigns
14274
+ * - Customer segmentation by transaction behavior
14275
+ *
14276
+ * @param request - Ranking request with filters, sorting, and limit
14277
+ * @returns Promise resolving to ranked user list with transaction metrics
14278
+ *
14279
+ * @example Top 50 users by transaction count
14280
+ * ```typescript
14281
+ * const ranking = await sdk.analytics.getUserRanking({
14282
+ * sortBy: 'totalTransactions',
14283
+ * sortOrder: 'DESC',
14284
+ * limit: 50
14285
+ * });
14286
+ *
14287
+ * console.log(`Top ${ranking.totalUsers} users:`);
14288
+ * ranking.results.forEach((user, index) => {
14289
+ * console.log(`#${index + 1}: ${user.email || user.externalUserId}`);
14290
+ * console.log(` Transactions: ${user.totalTransactions}`);
14291
+ * console.log(` Token spent: ${user.tokenSpent}`);
14292
+ * });
14293
+ * ```
14294
+ *
14295
+ * @example Top users by STAMP spending
14296
+ * ```typescript
14297
+ * const ranking = await sdk.analytics.getUserRanking({
14298
+ * filters: { tokenType: 'STAMP' },
14299
+ * sortBy: 'tokenSpent',
14300
+ * sortOrder: 'DESC',
14301
+ * limit: 20
14302
+ * });
14303
+ *
14304
+ * console.log('Top 20 STAMP spenders:');
14305
+ * ranking.results.forEach((user, index) => {
14306
+ * const identifier = user.email || user.externalUserId || user.userId;
14307
+ * console.log(`${index + 1}. ${identifier} - ${user.tokenSpent} STAMP`);
14308
+ * });
14309
+ * ```
14310
+ */
14311
+ async getUserRanking(request = {}) {
14312
+ return this.analyticsService.getUserRanking(request);
14313
+ }
14314
+ /**
14315
+ * Get business transaction ranking with enriched business data
14316
+ *
14317
+ * Returns ranked list of businesses with full business details and transaction metrics.
14318
+ *
14319
+ * @param request - Ranking request with filters, sorting, and limit
14320
+ * @returns Promise resolving to ranked business list
14321
+ */
14322
+ async getBusinessRanking(request = {}) {
14323
+ return this.analyticsService.getBusinessRanking(request);
14324
+ }
14325
+ /**
14326
+ * Get monthly user retention analytics
14327
+ *
14328
+ * Returns monthly retention data with user metrics and retention rates.
14329
+ *
14330
+ * @param request - Retention request with monthsBack and filters
14331
+ * @returns Promise resolving to monthly retention data
14332
+ */
14333
+ async getRetentionAnalytics(request = {}) {
14334
+ return this.analyticsService.getRetentionAnalytics(request);
14335
+ }
13255
14336
  /**
13256
14337
  * Get the full analytics service for advanced operations
13257
14338
  *
@@ -13346,6 +14427,222 @@ class DonationManager {
13346
14427
  }
13347
14428
  }
13348
14429
 
14430
+ /**
14431
+ * TriggerSource Manager - Clean, high-level interface for trigger source operations
14432
+ *
14433
+ * Manages trigger sources which are physical or digital activation points for campaigns:
14434
+ * - QR_CODE: Scannable QR codes
14435
+ * - NFC_TAG: NFC tap points
14436
+ * - GPS_GEOFENCE: Location-based triggers
14437
+ * - API_WEBHOOK: External system integration
14438
+ * - TRANSACTION: Purchase/payment based triggers
14439
+ *
14440
+ * Trigger sources are independent entities that can be created, managed, and then
14441
+ * assigned to campaigns. This separation allows reuse and flexible campaign configuration.
14442
+ *
14443
+ * @group Managers
14444
+ * @category TriggerSource Management
14445
+ *
14446
+ * @example Basic TriggerSource Operations
14447
+ * ```typescript
14448
+ * // Get all QR code trigger sources
14449
+ * const qrSources = await sdk.triggerSources.getAll({ type: 'QR_CODE' });
14450
+ *
14451
+ * // Create a new QR code trigger source
14452
+ * const source = await sdk.triggerSources.create({
14453
+ * type: 'QR_CODE',
14454
+ * name: 'Store Entrance QR',
14455
+ * businessId: 'business-123'
14456
+ * });
14457
+ *
14458
+ * // Assign to campaign (via CampaignManager)
14459
+ * await sdk.campaigns.assignTriggerSource('campaign-456', source.id);
14460
+ * ```
14461
+ */
14462
+ class TriggerSourceManager {
14463
+ constructor(apiClient, events) {
14464
+ this.apiClient = apiClient;
14465
+ this.events = events;
14466
+ const triggerSourceApi = new TriggerSourceApi(apiClient);
14467
+ this.triggerSourceService = new TriggerSourceService(triggerSourceApi);
14468
+ }
14469
+ /**
14470
+ * Get trigger sources with optional filters and pagination
14471
+ *
14472
+ * Retrieves trigger sources (QR codes, NFC tags, GPS geofences, webhooks, etc.)
14473
+ * that can be used to activate campaigns. Supports filtering by type, business,
14474
+ * campaign association, and active status.
14475
+ *
14476
+ * @param options - Filter and pagination options
14477
+ * @returns Promise resolving to paginated trigger sources
14478
+ *
14479
+ * @example
14480
+ * ```typescript
14481
+ * // Get all QR code trigger sources
14482
+ * const qrSources = await sdk.triggerSources.getAll({ type: 'QR_CODE' });
14483
+ *
14484
+ * // Get trigger sources for a specific business
14485
+ * const businessSources = await sdk.triggerSources.getAll({
14486
+ * businessId: 'business-123',
14487
+ * active: true,
14488
+ * page: 1,
14489
+ * limit: 20
14490
+ * });
14491
+ *
14492
+ * // Get trigger sources assigned to a campaign
14493
+ * const campaignSources = await sdk.triggerSources.getAll({
14494
+ * campaignId: 'campaign-456'
14495
+ * });
14496
+ * ```
14497
+ */
14498
+ async getAll(options) {
14499
+ return this.triggerSourceService.getTriggerSources(options);
14500
+ }
14501
+ /**
14502
+ * Get trigger source by ID
14503
+ *
14504
+ * Retrieves detailed information for a specific trigger source including
14505
+ * its type, location, metadata, and usage analytics.
14506
+ *
14507
+ * @param triggerSourceId - UUID of the trigger source
14508
+ * @returns Promise resolving to trigger source details
14509
+ * @throws {PersApiError} When trigger source with specified ID is not found
14510
+ *
14511
+ * @example
14512
+ * ```typescript
14513
+ * const source = await sdk.triggerSources.getById('source-123');
14514
+ *
14515
+ * console.log('Trigger Source:', source.name);
14516
+ * console.log('Type:', source.type);
14517
+ * console.log('Active:', source.isActive);
14518
+ *
14519
+ * if (source.coordsLatitude && source.coordsLongitude) {
14520
+ * console.log('Location:', source.coordsLatitude, source.coordsLongitude);
14521
+ * }
14522
+ * ```
14523
+ */
14524
+ async getById(triggerSourceId) {
14525
+ return this.triggerSourceService.getTriggerSourceById(triggerSourceId);
14526
+ }
14527
+ /**
14528
+ * Admin: Create a new trigger source
14529
+ *
14530
+ * Creates a new trigger source (QR code, NFC tag, GPS geofence, webhook, or
14531
+ * transaction-based trigger) that can be assigned to campaigns. Requires
14532
+ * administrator privileges.
14533
+ *
14534
+ * @param triggerSource - Trigger source creation data
14535
+ * @returns Promise resolving to created trigger source
14536
+ * @throws {PersApiError} When not authenticated as admin or validation fails
14537
+ *
14538
+ * @example
14539
+ * ```typescript
14540
+ * // Create a QR code trigger source
14541
+ * const qrSource = await sdk.triggerSources.create({
14542
+ * type: 'QR_CODE',
14543
+ * name: 'Store Entrance QR',
14544
+ * description: 'QR code at main entrance',
14545
+ * businessId: 'business-123',
14546
+ * coordsLatitude: 47.6062,
14547
+ * coordsLongitude: -122.3321
14548
+ * });
14549
+ *
14550
+ * // Create an NFC tag trigger source
14551
+ * const nfcSource = await sdk.triggerSources.create({
14552
+ * type: 'NFC_TAG',
14553
+ * name: 'Product Display NFC',
14554
+ * metadata: { productId: 'SKU-12345' }
14555
+ * });
14556
+ *
14557
+ * // Create a GPS geofence trigger source
14558
+ * const geoSource = await sdk.triggerSources.create({
14559
+ * type: 'GPS_GEOFENCE',
14560
+ * name: 'Store Area',
14561
+ * coordsLatitude: 47.6062,
14562
+ * coordsLongitude: -122.3321,
14563
+ * metadata: { radiusMeters: 100 }
14564
+ * });
14565
+ * ```
14566
+ */
14567
+ async create(triggerSource) {
14568
+ const result = await this.triggerSourceService.createTriggerSource(triggerSource);
14569
+ this.events?.emitSuccess({
14570
+ domain: 'trigger-source',
14571
+ type: 'TRIGGER_SOURCE_CREATED',
14572
+ userMessage: 'Trigger source created successfully',
14573
+ details: { triggerSourceId: result.id, type: result.type }
14574
+ });
14575
+ return result;
14576
+ }
14577
+ /**
14578
+ * Admin: Update a trigger source
14579
+ *
14580
+ * Updates an existing trigger source's configuration. All fields are optional;
14581
+ * only provided fields will be updated. Requires administrator privileges.
14582
+ *
14583
+ * @param triggerSourceId - UUID of the trigger source to update
14584
+ * @param triggerSource - Updated trigger source data (partial)
14585
+ * @returns Promise resolving to updated trigger source
14586
+ * @throws {PersApiError} When not authenticated as admin or trigger source not found
14587
+ *
14588
+ * @example
14589
+ * ```typescript
14590
+ * // Update trigger source name and location
14591
+ * const updated = await sdk.triggerSources.update('source-123', {
14592
+ * name: 'Updated Store Entrance QR',
14593
+ * description: 'New description',
14594
+ * coordsLatitude: 47.6063,
14595
+ * coordsLongitude: -122.3322
14596
+ * });
14597
+ * ```
14598
+ */
14599
+ async update(triggerSourceId, triggerSource) {
14600
+ const result = await this.triggerSourceService.updateTriggerSource(triggerSourceId, triggerSource);
14601
+ this.events?.emitSuccess({
14602
+ domain: 'trigger-source',
14603
+ type: 'TRIGGER_SOURCE_UPDATED',
14604
+ userMessage: 'Trigger source updated successfully',
14605
+ details: { triggerSourceId }
14606
+ });
14607
+ return result;
14608
+ }
14609
+ /**
14610
+ * Admin: Delete a trigger source
14611
+ *
14612
+ * Soft deletes a trigger source, making it inactive. The trigger source will
14613
+ * be removed from any campaigns it was assigned to. Requires administrator
14614
+ * privileges.
14615
+ *
14616
+ * @param triggerSourceId - UUID of the trigger source to delete
14617
+ * @returns Promise resolving to success status
14618
+ * @throws {PersApiError} When not authenticated as admin or trigger source not found
14619
+ *
14620
+ * @example
14621
+ * ```typescript
14622
+ * const success = await sdk.triggerSources.delete('source-123');
14623
+ * console.log('Trigger source deleted:', success);
14624
+ * ```
14625
+ */
14626
+ async delete(triggerSourceId) {
14627
+ const result = await this.triggerSourceService.deleteTriggerSource(triggerSourceId);
14628
+ this.events?.emitSuccess({
14629
+ domain: 'trigger-source',
14630
+ type: 'TRIGGER_SOURCE_DELETED',
14631
+ userMessage: 'Trigger source deleted successfully',
14632
+ details: { triggerSourceId }
14633
+ });
14634
+ return result;
14635
+ }
14636
+ /**
14637
+ * Get the full trigger source service for advanced operations
14638
+ *
14639
+ * @returns TriggerSourceService instance with full API access
14640
+ */
14641
+ getTriggerSourceService() {
14642
+ return this.triggerSourceService;
14643
+ }
14644
+ }
14645
+
13349
14646
  /**
13350
14647
  * @fileoverview PERS SDK - Platform-agnostic TypeScript SDK with High-Level Managers
13351
14648
  *
@@ -13832,6 +15129,40 @@ class PersSDK {
13832
15129
  }
13833
15130
  return this._donations;
13834
15131
  }
15132
+ /**
15133
+ * TriggerSource manager - High-level trigger source operations (Admin Only)
15134
+ *
15135
+ * Provides CRUD operations for managing trigger sources (QR codes, NFC tags,
15136
+ * GPS geofences, API webhooks). TriggerSources are standalone entities that
15137
+ * can be assigned to campaigns via the campaigns manager.
15138
+ *
15139
+ * @returns TriggerSourceManager instance
15140
+ *
15141
+ * @example TriggerSource Operations
15142
+ * ```typescript
15143
+ * // Create a QR code trigger source
15144
+ * const qrSource = await sdk.triggerSources.create({
15145
+ * name: 'Store Entrance QR',
15146
+ * type: 'QR_CODE',
15147
+ * description: 'QR code at main entrance'
15148
+ * });
15149
+ *
15150
+ * // Get all trigger sources
15151
+ * const sources = await sdk.triggerSources.getAll();
15152
+ *
15153
+ * // Update trigger source
15154
+ * await sdk.triggerSources.update(sourceId, { name: 'Updated Name' });
15155
+ *
15156
+ * // Assign to campaign (via campaigns manager)
15157
+ * await sdk.campaigns.assignTriggerSource(campaignId, qrSource.id);
15158
+ * ```
15159
+ */
15160
+ get triggerSources() {
15161
+ if (!this._triggerSources) {
15162
+ this._triggerSources = new TriggerSourceManager(this.apiClient, this._events);
15163
+ }
15164
+ return this._triggerSources;
15165
+ }
13835
15166
  /**
13836
15167
  * Gets the API client for direct PERS API requests
13837
15168
  *
@@ -37030,25 +38361,32 @@ const useTransactions = () => {
37030
38361
  }
37031
38362
  }, [sdk, isInitialized, signAndSubmitTransactionWithJWT, isSignerAvailable]);
37032
38363
  /**
37033
- * Retrieves a specific transaction by its ID
38364
+ * Retrieves a specific transaction by its ID with optional include relations
37034
38365
  *
37035
38366
  * @param transactionId - Unique identifier of the transaction
38367
+ * @param include - Optional relations to include (sender, recipient, business) for enriched entity data
37036
38368
  * @returns Promise resolving to transaction data or null if not found
37037
38369
  * @throws Error if SDK is not initialized
37038
38370
  *
37039
38371
  * @example
37040
38372
  * ```typescript
37041
38373
  * const { getTransactionById } = useTransactions();
38374
+ *
38375
+ * // Basic retrieval
37042
38376
  * const transaction = await getTransactionById('txn-123');
37043
- * console.log('Transaction details:', transaction);
38377
+ *
38378
+ * // With enriched sender/recipient data
38379
+ * const enrichedTx = await getTransactionById('txn-123', ['sender', 'recipient', 'business']);
38380
+ * console.log('Sender:', enrichedTx?.included?.sender);
38381
+ * console.log('Business:', enrichedTx?.included?.engagedBusiness?.displayName);
37044
38382
  * ```
37045
38383
  */
37046
- const getTransactionById = react.useCallback(async (transactionId) => {
38384
+ const getTransactionById = react.useCallback(async (transactionId, include) => {
37047
38385
  if (!isInitialized || !sdk) {
37048
38386
  throw new Error('SDK not initialized. Call initialize() first.');
37049
38387
  }
37050
38388
  try {
37051
- const result = await sdk.transactions.getTransactionById(transactionId);
38389
+ const result = await sdk.transactions.getTransactionById(transactionId, include);
37052
38390
  return result;
37053
38391
  }
37054
38392
  catch (error) {
@@ -37057,25 +38395,48 @@ const useTransactions = () => {
37057
38395
  }
37058
38396
  }, [sdk, isInitialized]);
37059
38397
  /**
37060
- * Retrieves transaction history for the authenticated user, filtered by role
38398
+ * Retrieves transaction history for the authenticated user with comprehensive filtering
38399
+ *
38400
+ * Supports filtering by role, status, type, business, token, and more.
38401
+ * Optionally enrich with related entities (sender, recipient, business).
37061
38402
  *
37062
- * @param role - Optional transaction role filter (TransactionRole.SENDER, TransactionRole.RECIPIENT)
37063
- * @returns Promise resolving to array of user's transactions
38403
+ * @param options - Query options including filters, pagination, and include relations
38404
+ * @returns Promise resolving to paginated array of user's transactions
37064
38405
  * @throws Error if SDK is not initialized
37065
38406
  *
37066
38407
  * @example
37067
38408
  * ```typescript
37068
38409
  * const { getUserTransactionHistory } = useTransactions();
37069
- * const sentTransactions = await getUserTransactionHistory(TransactionRole.SENDER);
37070
- * const allTransactions = await getUserTransactionHistory(); // No filter
38410
+ *
38411
+ * // Simple: Get all transactions
38412
+ * const allTransactions = await getUserTransactionHistory();
38413
+ *
38414
+ * // Filter by role (legacy support)
38415
+ * const sentTransactions = await getUserTransactionHistory({ role: 'SENDER' });
38416
+ *
38417
+ * // Advanced filtering with include relations
38418
+ * const filtered = await getUserTransactionHistory({
38419
+ * role: 'SENDER',
38420
+ * status: 'COMPLETED',
38421
+ * engagedBusinessId: 'business-123',
38422
+ * include: ['recipient', 'business'],
38423
+ * page: 1,
38424
+ * limit: 20
38425
+ * });
38426
+ *
38427
+ * // Access enriched data
38428
+ * filtered.data.forEach(tx => {
38429
+ * console.log('Recipient:', tx.included?.recipient);
38430
+ * console.log('Business:', tx.included?.engagedBusiness?.displayName);
38431
+ * });
37071
38432
  * ```
37072
38433
  */
37073
- const getUserTransactionHistory = react.useCallback(async (role) => {
38434
+ const getUserTransactionHistory = react.useCallback(async (options) => {
37074
38435
  if (!isInitialized || !sdk) {
37075
38436
  throw new Error('SDK not initialized. Call initialize() first.');
37076
38437
  }
37077
38438
  try {
37078
- const result = await sdk.transactions.getUserTransactionHistory(role);
38439
+ const result = await sdk.transactions.getUserTransactionHistory(options);
37079
38440
  return result;
37080
38441
  }
37081
38442
  catch (error) {
@@ -37083,25 +38444,39 @@ const useTransactions = () => {
37083
38444
  throw error;
37084
38445
  }
37085
38446
  }, [sdk, isInitialized]);
37086
- const getTenantTransactions = react.useCallback(async () => {
37087
- if (!isInitialized || !sdk) {
37088
- throw new Error('SDK not initialized. Call initialize() first.');
37089
- }
37090
- try {
37091
- const result = await sdk.transactions.getTenantTransactions();
37092
- return result;
37093
- }
37094
- catch (error) {
37095
- console.error('Failed to fetch tenant transactions:', error);
37096
- throw error;
37097
- }
37098
- }, [sdk, isInitialized]);
37099
- const getPaginatedTransactions = react.useCallback(async (params) => {
38447
+ /**
38448
+ * Admin: Get paginated transactions with optional include relations
38449
+ *
38450
+ * @param params - Pagination and filtering parameters
38451
+ * @param include - Optional relations to include for enriched entity data
38452
+ * @returns Promise resolving to paginated transaction results
38453
+ * @throws Error if SDK is not initialized
38454
+ *
38455
+ * @example
38456
+ * ```typescript
38457
+ * const { getPaginatedTransactions } = useTransactions();
38458
+ *
38459
+ * // Basic pagination
38460
+ * const result = await getPaginatedTransactions({ page: 1, limit: 50 });
38461
+ *
38462
+ * // With include relations
38463
+ * const enrichedResult = await getPaginatedTransactions(
38464
+ * { page: 1, limit: 50, sortBy: 'createdAt', sortOrder: 'DESC' },
38465
+ * include: ['sender', 'recipient', 'business']
38466
+ * });
38467
+ *
38468
+ * enrichedResult.data.forEach(tx => {
38469
+ * console.log('From:', tx.included?.sender);
38470
+ * console.log('To:', tx.included?.recipient);
38471
+ * });
38472
+ * ```
38473
+ */
38474
+ const getPaginatedTransactions = react.useCallback(async (options) => {
37100
38475
  if (!isInitialized || !sdk) {
37101
38476
  throw new Error('SDK not initialized. Call initialize() first.');
37102
38477
  }
37103
38478
  try {
37104
- const result = await sdk.transactions.getPaginatedTransactions(params);
38479
+ const result = await sdk.transactions.getPaginatedTransactions(options);
37105
38480
  return result;
37106
38481
  }
37107
38482
  catch (error) {
@@ -37126,7 +38501,6 @@ const useTransactions = () => {
37126
38501
  createTransaction,
37127
38502
  getTransactionById,
37128
38503
  getUserTransactionHistory,
37129
- getTenantTransactions,
37130
38504
  getPaginatedTransactions,
37131
38505
  exportTransactionsCSV,
37132
38506
  isAvailable: isInitialized && !!sdk?.transactions,
@@ -37359,12 +38733,26 @@ const useCampaigns = () => {
37359
38733
  throw error;
37360
38734
  }
37361
38735
  }, [sdk, isInitialized]);
37362
- const getCampaignById = react.useCallback(async (campaignId) => {
38736
+ /**
38737
+ * Get campaign by ID with optional include relations
38738
+ *
38739
+ * @param campaignId - The campaign ID
38740
+ * @param include - Relations to include: 'triggerSources', 'businesses'
38741
+ * @returns Promise resolving to campaign data
38742
+ *
38743
+ * @example
38744
+ * ```typescript
38745
+ * // Get campaign with all related data
38746
+ * const campaign = await getCampaignById('campaign-123', ['triggerSources', 'businesses']);
38747
+ * console.log('Trigger sources:', campaign.included?.triggerSources);
38748
+ * ```
38749
+ */
38750
+ const getCampaignById = react.useCallback(async (campaignId, include) => {
37363
38751
  if (!isInitialized || !sdk) {
37364
38752
  throw new Error('SDK not initialized. Call initialize() first.');
37365
38753
  }
37366
38754
  try {
37367
- const result = await sdk.campaigns.getCampaignById(campaignId);
38755
+ const result = await sdk.campaigns.getCampaignById(campaignId, include);
37368
38756
  return result;
37369
38757
  }
37370
38758
  catch (error) {
@@ -37388,7 +38776,25 @@ const useCampaigns = () => {
37388
38776
  throw error;
37389
38777
  }
37390
38778
  }, [sdk, isInitialized, isAuthenticated]);
37391
- const getUserClaims = react.useCallback(async () => {
38779
+ /**
38780
+ * Get user's campaign claims with optional pagination and include relations
38781
+ *
38782
+ * @param options - Optional options including pagination and include relations
38783
+ * @returns Promise resolving to paginated campaign claims
38784
+ *
38785
+ * @example
38786
+ * ```typescript
38787
+ * // Get user's claims with campaign details
38788
+ * const { data: claims } = await getUserClaims({ include: ['campaign', 'business'] });
38789
+ * claims.forEach(claim => {
38790
+ * console.log('Campaign:', claim.included?.campaign?.title);
38791
+ * });
38792
+ *
38793
+ * // With pagination
38794
+ * const { data } = await getUserClaims({ page: 1, limit: 10, include: ['campaign'] });
38795
+ * ```
38796
+ */
38797
+ const getUserClaims = react.useCallback(async (options) => {
37392
38798
  if (!isInitialized || !sdk) {
37393
38799
  throw new Error('SDK not initialized. Call initialize() first.');
37394
38800
  }
@@ -37397,7 +38803,7 @@ const useCampaigns = () => {
37397
38803
  return { data: [], pagination: { page: 1, limit: 0, total: 0, pages: 0, hasNext: false, hasPrev: false } };
37398
38804
  }
37399
38805
  try {
37400
- const result = await sdk.campaigns.getUserClaims();
38806
+ const result = await sdk.campaigns.getUserClaims(options);
37401
38807
  return result;
37402
38808
  }
37403
38809
  catch (error) {
@@ -37432,12 +38838,28 @@ const useCampaigns = () => {
37432
38838
  throw error;
37433
38839
  }
37434
38840
  }, [sdk, isInitialized]);
37435
- const getCampaignClaims = react.useCallback(async () => {
38841
+ /**
38842
+ * Admin: Get campaign claims with optional filters and include relations
38843
+ *
38844
+ * @param filters - Optional filters for campaign, user, or business
38845
+ * @param include - Relations to include: 'campaign', 'user', 'business'
38846
+ * @returns Promise resolving to paginated campaign claims
38847
+ *
38848
+ * @example
38849
+ * ```typescript
38850
+ * // Get all claims with user details
38851
+ * const { data: claims } = await getCampaignClaims({}, ['user']);
38852
+ *
38853
+ * // Get claims for a specific campaign
38854
+ * const { data } = await getCampaignClaims({ campaignId: 'campaign-123' }, ['user', 'business']);
38855
+ * ```
38856
+ */
38857
+ const getCampaignClaims = react.useCallback(async (filters, include) => {
37436
38858
  if (!isInitialized || !sdk) {
37437
38859
  throw new Error('SDK not initialized. Call initialize() first.');
37438
38860
  }
37439
38861
  try {
37440
- const result = await sdk.campaigns.getCampaignClaims();
38862
+ const result = await sdk.campaigns.getCampaignClaims(filters, include);
37441
38863
  return result;
37442
38864
  }
37443
38865
  catch (error) {
@@ -37445,12 +38867,19 @@ const useCampaigns = () => {
37445
38867
  throw error;
37446
38868
  }
37447
38869
  }, [sdk, isInitialized]);
37448
- const getCampaignClaimsByUserId = react.useCallback(async (userId) => {
38870
+ /**
38871
+ * Admin: Get campaign claims by user ID with optional include relations
38872
+ *
38873
+ * @param userId - The user ID
38874
+ * @param include - Relations to include: 'campaign', 'user', 'business'
38875
+ * @returns Promise resolving to paginated campaign claims
38876
+ */
38877
+ const getCampaignClaimsByUserId = react.useCallback(async (userId, include) => {
37449
38878
  if (!isInitialized || !sdk) {
37450
38879
  throw new Error('SDK not initialized. Call initialize() first.');
37451
38880
  }
37452
38881
  try {
37453
- const result = await sdk.campaigns.getCampaignClaimsByUserId(userId);
38882
+ const result = await sdk.campaigns.getCampaignClaimsByUserId(userId, undefined, include);
37454
38883
  return result;
37455
38884
  }
37456
38885
  catch (error) {
@@ -37458,12 +38887,19 @@ const useCampaigns = () => {
37458
38887
  throw error;
37459
38888
  }
37460
38889
  }, [sdk, isInitialized]);
37461
- const getCampaignClaimsByBusinessId = react.useCallback(async (businessId) => {
38890
+ /**
38891
+ * Admin: Get campaign claims by business ID with optional include relations
38892
+ *
38893
+ * @param businessId - The business ID
38894
+ * @param include - Relations to include: 'campaign', 'user', 'business'
38895
+ * @returns Promise resolving to paginated campaign claims
38896
+ */
38897
+ const getCampaignClaimsByBusinessId = react.useCallback(async (businessId, include) => {
37462
38898
  if (!isInitialized || !sdk) {
37463
38899
  throw new Error('SDK not initialized. Call initialize() first.');
37464
38900
  }
37465
38901
  try {
37466
- const result = await sdk.campaigns.getCampaignClaimsByBusinessId(businessId);
38902
+ const result = await sdk.campaigns.getCampaignClaimsByBusinessId(businessId, undefined, include);
37467
38903
  return result;
37468
38904
  }
37469
38905
  catch (error) {
@@ -37471,6 +38907,76 @@ const useCampaigns = () => {
37471
38907
  throw error;
37472
38908
  }
37473
38909
  }, [sdk, isInitialized]);
38910
+ // ==========================================
38911
+ // TRIGGER SOURCE ASSIGNMENT (Admin)
38912
+ // Note: TriggerSource CRUD is in useTriggerSources hook
38913
+ // ==========================================
38914
+ /**
38915
+ * Admin: Assign a trigger source to a campaign
38916
+ *
38917
+ * Associates a trigger source (QR code, NFC tag, etc.) with a campaign.
38918
+ * A campaign can have multiple trigger sources.
38919
+ *
38920
+ * Note: To create/update/delete trigger sources, use `useTriggerSources` hook.
38921
+ *
38922
+ * @param campaignId - Campaign UUID
38923
+ * @param triggerSourceId - Trigger source UUID
38924
+ * @returns Promise resolving to updated campaign
38925
+ *
38926
+ * @example
38927
+ * ```typescript
38928
+ * const { create } = useTriggerSources();
38929
+ * const { assignTriggerSource } = useCampaigns();
38930
+ *
38931
+ * // Create trigger source first
38932
+ * const source = await create({ type: 'QR_CODE', name: 'Store QR' });
38933
+ *
38934
+ * // Then assign to campaign
38935
+ * const updated = await assignTriggerSource('campaign-123', source.id);
38936
+ * ```
38937
+ */
38938
+ const assignTriggerSource = react.useCallback(async (campaignId, triggerSourceId) => {
38939
+ if (!isInitialized || !sdk) {
38940
+ throw new Error('SDK not initialized. Call initialize() first.');
38941
+ }
38942
+ if (!isAuthenticated) {
38943
+ throw new Error('SDK not authenticated. assignTriggerSource requires admin authentication.');
38944
+ }
38945
+ try {
38946
+ const result = await sdk.campaigns.assignTriggerSource(campaignId, triggerSourceId);
38947
+ return result;
38948
+ }
38949
+ catch (error) {
38950
+ console.error('Failed to assign trigger source:', error);
38951
+ throw error;
38952
+ }
38953
+ }, [sdk, isInitialized, isAuthenticated]);
38954
+ /**
38955
+ * Admin: Remove a trigger source from a campaign
38956
+ *
38957
+ * Removes the association between a trigger source and a campaign.
38958
+ * The trigger source itself is not deleted.
38959
+ *
38960
+ * @param campaignId - Campaign UUID
38961
+ * @param triggerSourceId - Trigger source UUID
38962
+ * @returns Promise resolving to updated campaign
38963
+ */
38964
+ const removeTriggerSource = react.useCallback(async (campaignId, triggerSourceId) => {
38965
+ if (!isInitialized || !sdk) {
38966
+ throw new Error('SDK not initialized. Call initialize() first.');
38967
+ }
38968
+ if (!isAuthenticated) {
38969
+ throw new Error('SDK not authenticated. removeTriggerSource requires admin authentication.');
38970
+ }
38971
+ try {
38972
+ const result = await sdk.campaigns.removeTriggerSource(campaignId, triggerSourceId);
38973
+ return result;
38974
+ }
38975
+ catch (error) {
38976
+ console.error('Failed to remove trigger source:', error);
38977
+ throw error;
38978
+ }
38979
+ }, [sdk, isInitialized, isAuthenticated]);
37474
38980
  return {
37475
38981
  getActiveCampaigns,
37476
38982
  getCampaignById,
@@ -37481,6 +38987,8 @@ const useCampaigns = () => {
37481
38987
  getCampaignClaims,
37482
38988
  getCampaignClaimsByUserId,
37483
38989
  getCampaignClaimsByBusinessId,
38990
+ assignTriggerSource,
38991
+ removeTriggerSource,
37484
38992
  isAvailable: isInitialized && !!sdk?.campaigns,
37485
38993
  };
37486
38994
  };
@@ -37509,7 +39017,23 @@ const useRedemptions = () => {
37509
39017
  throw error;
37510
39018
  }
37511
39019
  }, [sdk, isInitialized]);
37512
- const getUserRedemptions = react.useCallback(async () => {
39020
+ /**
39021
+ * Get user's redemption history with optional include relations
39022
+ *
39023
+ * @param include - Relations to include: 'redemption', 'user', 'business'
39024
+ * @returns Promise resolving to paginated redemption history
39025
+ *
39026
+ * @example
39027
+ * ```typescript
39028
+ * // Get user redemptions with full redemption details
39029
+ * const { data: redeems } = await getUserRedemptions(['redemption', 'business']);
39030
+ * redeems.forEach(redeem => {
39031
+ * console.log('Redemption:', redeem.included?.redemption?.title);
39032
+ * console.log('Business:', redeem.included?.business?.displayName);
39033
+ * });
39034
+ * ```
39035
+ */
39036
+ const getUserRedemptions = react.useCallback(async (include) => {
37513
39037
  if (!isInitialized || !sdk) {
37514
39038
  throw new Error('SDK not initialized. Call initialize() first.');
37515
39039
  }
@@ -37518,7 +39042,7 @@ const useRedemptions = () => {
37518
39042
  return { data: [], pagination: { page: 1, limit: 0, total: 0, pages: 0, hasNext: false, hasPrev: false } };
37519
39043
  }
37520
39044
  try {
37521
- const result = await sdk.redemptions.getUserRedemptions();
39045
+ const result = await sdk.redemptions.getUserRedemptions(undefined, include);
37522
39046
  return result;
37523
39047
  }
37524
39048
  catch (error) {
@@ -37572,6 +39096,40 @@ const useRedemptions = () => {
37572
39096
  }
37573
39097
  }, [sdk, isInitialized, isAuthenticated, signAndSubmitTransactionWithJWT, isSignerAvailable]);
37574
39098
  // Admin methods
39099
+ /**
39100
+ * Admin: Get all redemption redeems with filters and include relations
39101
+ *
39102
+ * Retrieves all redemption redeems across the platform with filtering.
39103
+ *
39104
+ * @param filters - Optional filters for user, redemption, and pagination
39105
+ * @param include - Relations to include: 'redemption', 'user', 'business'
39106
+ * @returns Promise resolving to paginated redemption redeems
39107
+ *
39108
+ * @example
39109
+ * ```typescript
39110
+ * // Get all redeems with user details
39111
+ * const { data: redeems } = await getRedemptionRedeems({}, ['user', 'redemption']);
39112
+ *
39113
+ * // Filter by specific user
39114
+ * const { data: userRedeems } = await getRedemptionRedeems(
39115
+ * { userId: 'user-123' },
39116
+ * ['redemption']
39117
+ * );
39118
+ * ```
39119
+ */
39120
+ const getRedemptionRedeems = react.useCallback(async (filters, include) => {
39121
+ if (!isInitialized || !sdk) {
39122
+ throw new Error('SDK not initialized. Call initialize() first.');
39123
+ }
39124
+ try {
39125
+ const result = await sdk.redemptions.getRedemptionRedeems(filters, include);
39126
+ return result;
39127
+ }
39128
+ catch (error) {
39129
+ console.error('Failed to fetch redemption redeems:', error);
39130
+ throw error;
39131
+ }
39132
+ }, [sdk, isInitialized]);
37575
39133
  const createRedemption = react.useCallback(async (redemptionData) => {
37576
39134
  if (!isInitialized || !sdk) {
37577
39135
  throw new Error('SDK not initialized. Call initialize() first.');
@@ -37629,6 +39187,7 @@ const useRedemptions = () => {
37629
39187
  getUserRedemptions,
37630
39188
  redeem,
37631
39189
  getRedemptionTypes,
39190
+ getRedemptionRedeems,
37632
39191
  createRedemption,
37633
39192
  updateRedemption,
37634
39193
  toggleRedemptionStatus,
@@ -38057,29 +39616,47 @@ const useFiles = () => {
38057
39616
  /**
38058
39617
  * React hook for analytics operations in the PERS SDK
38059
39618
  *
38060
- * Provides methods for retrieving transaction analytics and insights.
38061
- * Supports various analytics queries for business intelligence and reporting.
39619
+ * Provides comprehensive analytics and business intelligence capabilities:
39620
+ * - **Transaction Analytics**: Volume, trends, business performance
39621
+ * - **Campaign Claim Analytics**: Campaign performance and claim patterns
39622
+ * - **User Analytics**: Engagement metrics with per-active-user averages
39623
+ * - **User Ranking**: Leaderboards with full user details
39624
+ * - **Business Ranking**: Business performance rankings
39625
+ * - **Retention Analytics**: Monthly retention metrics
38062
39626
  *
38063
39627
  * @returns Analytics hook with methods for data analysis
38064
39628
  *
38065
- * @example
39629
+ * @example Basic Transaction Analytics
38066
39630
  * ```typescript
38067
39631
  * function AnalyticsComponent() {
38068
39632
  * const { getTransactionAnalytics } = useAnalytics();
38069
39633
  *
38070
39634
  * const loadAnalytics = async () => {
38071
- * try {
38072
- * const analytics = await getTransactionAnalytics({
38073
- * timeRange: 'last_30_days',
38074
- * groupBy: 'day'
38075
- * });
38076
- * console.log('Transaction analytics:', analytics);
38077
- * } catch (error) {
38078
- * console.error('Failed to load analytics:', error);
38079
- * }
39635
+ * const analytics = await getTransactionAnalytics({
39636
+ * startDate: '2024-01-01',
39637
+ * endDate: '2024-01-31',
39638
+ * groupBy: 'day'
39639
+ * });
39640
+ * console.log('Transaction analytics:', analytics);
38080
39641
  * };
39642
+ * }
39643
+ * ```
38081
39644
  *
38082
- * return <button onClick={loadAnalytics}>Load Analytics</button>;
39645
+ * @example User Leaderboard
39646
+ * ```typescript
39647
+ * function LeaderboardScreen() {
39648
+ * const { getUserRanking } = useAnalytics();
39649
+ *
39650
+ * const loadLeaderboard = async () => {
39651
+ * const ranking = await getUserRanking({
39652
+ * sortBy: 'totalTransactions',
39653
+ * sortOrder: 'DESC',
39654
+ * limit: 50
39655
+ * });
39656
+ * ranking.results.forEach((user, i) => {
39657
+ * console.log(`#${i + 1}: ${user.email} - ${user.totalTransactions} txns`);
39658
+ * });
39659
+ * };
38083
39660
  * }
38084
39661
  * ```
38085
39662
  */
@@ -38094,16 +39671,12 @@ const useAnalytics = () => {
38094
39671
  *
38095
39672
  * @example
38096
39673
  * ```typescript
38097
- * const { getTransactionAnalytics } = useAnalytics();
38098
39674
  * const analytics = await getTransactionAnalytics({
38099
- * groupBy: ['day'],
38100
- * metrics: ['count', 'sum'],
38101
39675
  * startDate: '2024-01-01',
38102
39676
  * endDate: '2024-01-31',
38103
- * filters: { status: 'completed' }
39677
+ * groupBy: 'day',
39678
+ * metrics: ['count', 'sum']
38104
39679
  * });
38105
- * console.log('Daily transaction analytics:', analytics.results);
38106
- * console.log('Execution time:', analytics.metadata.executionTime);
38107
39680
  * ```
38108
39681
  */
38109
39682
  const getTransactionAnalytics = react.useCallback(async (request) => {
@@ -38119,8 +39692,214 @@ const useAnalytics = () => {
38119
39692
  throw error;
38120
39693
  }
38121
39694
  }, [sdk, isInitialized]);
39695
+ /**
39696
+ * Retrieves campaign claim analytics with aggregation
39697
+ *
39698
+ * Provides insights into campaign performance, claim patterns, and user engagement.
39699
+ *
39700
+ * @param request - Analytics request with filters, groupBy, and metrics
39701
+ * @returns Promise resolving to campaign claim analytics data
39702
+ *
39703
+ * @example Claims per campaign
39704
+ * ```typescript
39705
+ * const analytics = await getCampaignClaimAnalytics({
39706
+ * filters: { status: 'COMPLETED' },
39707
+ * groupBy: ['campaignId'],
39708
+ * metrics: ['count'],
39709
+ * sortBy: 'count',
39710
+ * sortOrder: 'DESC',
39711
+ * limit: 10
39712
+ * });
39713
+ * console.log('Top campaigns:', analytics.results);
39714
+ * ```
39715
+ */
39716
+ const getCampaignClaimAnalytics = react.useCallback(async (request) => {
39717
+ if (!isInitialized || !sdk) {
39718
+ throw new Error('SDK not initialized. Call initialize() first.');
39719
+ }
39720
+ try {
39721
+ const result = await sdk.analytics.getCampaignClaimAnalytics(request);
39722
+ return result;
39723
+ }
39724
+ catch (error) {
39725
+ console.error('Failed to fetch campaign claim analytics:', error);
39726
+ throw error;
39727
+ }
39728
+ }, [sdk, isInitialized]);
39729
+ /**
39730
+ * Retrieves user analytics with engagement metrics
39731
+ *
39732
+ * Includes both per-user and per-active-user metrics for accurate engagement insights.
39733
+ * Per-active-user metrics show concentrated engagement among active users.
39734
+ *
39735
+ * @param request - Analytics request with optional filters and date range
39736
+ * @returns Promise resolving to user analytics data
39737
+ *
39738
+ * @example Compare per-user vs per-active-user metrics
39739
+ * ```typescript
39740
+ * const analytics = await getUserAnalytics({
39741
+ * startDate: new Date('2026-02-01'),
39742
+ * endDate: new Date('2026-02-28')
39743
+ * });
39744
+ *
39745
+ * console.log(`Active users: ${analytics.activeUsers} / ${analytics.totalUsers}`);
39746
+ * console.log(`Engagement rate: ${analytics.engagementRate.toFixed(1)}%`);
39747
+ *
39748
+ * // Per-active-user metrics (more meaningful)
39749
+ * console.log(`Avg transactions per active user: ${analytics.averageTransactionsPerActiveUser}`);
39750
+ * ```
39751
+ *
39752
+ * @example Business-specific analytics
39753
+ * ```typescript
39754
+ * const analytics = await getUserAnalytics({
39755
+ * filters: { businessId: 'business-123' },
39756
+ * startDate: new Date('2026-01-01'),
39757
+ * endDate: new Date('2026-12-31')
39758
+ * });
39759
+ * ```
39760
+ */
39761
+ const getUserAnalytics = react.useCallback(async (request = {}) => {
39762
+ if (!isInitialized || !sdk) {
39763
+ throw new Error('SDK not initialized. Call initialize() first.');
39764
+ }
39765
+ try {
39766
+ const result = await sdk.analytics.getUserAnalytics(request);
39767
+ return result;
39768
+ }
39769
+ catch (error) {
39770
+ console.error('Failed to fetch user analytics:', error);
39771
+ throw error;
39772
+ }
39773
+ }, [sdk, isInitialized]);
39774
+ /**
39775
+ * Retrieves user transaction ranking with enriched user data
39776
+ *
39777
+ * Returns ranked list of users with full user details (email, externalUserId)
39778
+ * and transaction metrics. Ideal for leaderboards, engagement analysis,
39779
+ * and identifying power users.
39780
+ *
39781
+ * @param request - Ranking request with filters, sorting, and limit
39782
+ * @returns Promise resolving to ranked user list with transaction metrics
39783
+ *
39784
+ * @example Top 50 users by transaction count
39785
+ * ```typescript
39786
+ * const ranking = await getUserRanking({
39787
+ * sortBy: 'totalTransactions',
39788
+ * sortOrder: 'DESC',
39789
+ * limit: 50
39790
+ * });
39791
+ *
39792
+ * ranking.results.forEach((user, index) => {
39793
+ * console.log(`#${index + 1}: ${user.email || user.externalUserId}`);
39794
+ * console.log(` Transactions: ${user.totalTransactions}`);
39795
+ * console.log(` Token spent: ${user.tokenSpent}`);
39796
+ * });
39797
+ * ```
39798
+ *
39799
+ * @example Top STAMP spenders
39800
+ * ```typescript
39801
+ * const ranking = await getUserRanking({
39802
+ * filters: { tokenType: 'STAMP' },
39803
+ * sortBy: 'tokenSpent',
39804
+ * sortOrder: 'DESC',
39805
+ * limit: 20
39806
+ * });
39807
+ * ```
39808
+ */
39809
+ const getUserRanking = react.useCallback(async (request = {}) => {
39810
+ if (!isInitialized || !sdk) {
39811
+ throw new Error('SDK not initialized. Call initialize() first.');
39812
+ }
39813
+ try {
39814
+ const result = await sdk.analytics.getUserRanking(request);
39815
+ return result;
39816
+ }
39817
+ catch (error) {
39818
+ console.error('Failed to fetch user ranking:', error);
39819
+ throw error;
39820
+ }
39821
+ }, [sdk, isInitialized]);
39822
+ /**
39823
+ * Retrieves business transaction ranking with enriched business data
39824
+ *
39825
+ * Returns ranked list of businesses with transaction metrics for
39826
+ * partner analytics and performance dashboards.
39827
+ *
39828
+ * @param request - Ranking request with filters, sorting, and limit
39829
+ * @returns Promise resolving to ranked business list
39830
+ *
39831
+ * @example Top businesses by transaction count
39832
+ * ```typescript
39833
+ * const ranking = await getBusinessRanking({
39834
+ * sortBy: 'totalTransactions',
39835
+ * sortOrder: 'DESC',
39836
+ * limit: 20
39837
+ * });
39838
+ *
39839
+ * ranking.results.forEach((business, index) => {
39840
+ * console.log(`#${index + 1}: ${business.businessId}`);
39841
+ * console.log(` Transactions: ${business.totalTransactions}`);
39842
+ * console.log(` Token spent: ${business.tokenSpent}`);
39843
+ * });
39844
+ * ```
39845
+ */
39846
+ const getBusinessRanking = react.useCallback(async (request = {}) => {
39847
+ if (!isInitialized || !sdk) {
39848
+ throw new Error('SDK not initialized. Call initialize() first.');
39849
+ }
39850
+ try {
39851
+ const result = await sdk.analytics.getBusinessRanking(request);
39852
+ return result;
39853
+ }
39854
+ catch (error) {
39855
+ console.error('Failed to fetch business ranking:', error);
39856
+ throw error;
39857
+ }
39858
+ }, [sdk, isInitialized]);
39859
+ /**
39860
+ * Retrieves monthly user retention analytics
39861
+ *
39862
+ * Returns monthly retention data with active, new, and returning users
39863
+ * along with retention rates. Useful for churn analysis and engagement trends.
39864
+ *
39865
+ * @param request - Retention request with monthsBack and filters
39866
+ * @returns Promise resolving to monthly retention data
39867
+ *
39868
+ * @example Get 12 months of retention data
39869
+ * ```typescript
39870
+ * const retention = await getRetentionAnalytics({
39871
+ * monthsBack: 12
39872
+ * });
39873
+ *
39874
+ * retention.results.forEach(month => {
39875
+ * console.log(`${month.month}:`);
39876
+ * console.log(` Active: ${month.activeUsers}`);
39877
+ * console.log(` New: ${month.newUsers}`);
39878
+ * console.log(` Returning: ${month.returningUsers}`);
39879
+ * console.log(` Retention Rate: ${month.retentionRate.toFixed(1)}%`);
39880
+ * });
39881
+ * ```
39882
+ */
39883
+ const getRetentionAnalytics = react.useCallback(async (request = {}) => {
39884
+ if (!isInitialized || !sdk) {
39885
+ throw new Error('SDK not initialized. Call initialize() first.');
39886
+ }
39887
+ try {
39888
+ const result = await sdk.analytics.getRetentionAnalytics(request);
39889
+ return result;
39890
+ }
39891
+ catch (error) {
39892
+ console.error('Failed to fetch retention analytics:', error);
39893
+ throw error;
39894
+ }
39895
+ }, [sdk, isInitialized]);
38122
39896
  return {
38123
39897
  getTransactionAnalytics,
39898
+ getCampaignClaimAnalytics,
39899
+ getUserAnalytics,
39900
+ getUserRanking,
39901
+ getBusinessRanking,
39902
+ getRetentionAnalytics,
38124
39903
  isAvailable: isInitialized && !!sdk?.analytics,
38125
39904
  };
38126
39905
  };
@@ -38496,6 +40275,277 @@ const useEvents = () => {
38496
40275
  };
38497
40276
  };
38498
40277
 
40278
+ /**
40279
+ * React hook for TriggerSource operations in the PERS SDK
40280
+ *
40281
+ * Manages trigger sources which are physical or digital activation points for campaigns:
40282
+ * - **QR_CODE**: Scannable QR codes at physical locations
40283
+ * - **NFC_TAG**: NFC tap points for contactless interactions
40284
+ * - **GPS_GEOFENCE**: Location-based triggers with radius detection
40285
+ * - **API_WEBHOOK**: External system integration triggers
40286
+ * - **TRANSACTION**: Purchase/payment based triggers
40287
+ *
40288
+ * TriggerSources are standalone entities that can be created, managed, and then
40289
+ * assigned to campaigns. This separation allows reuse across multiple campaigns.
40290
+ *
40291
+ * **Admin Only**: All create, update, and delete operations require admin authentication.
40292
+ *
40293
+ * @returns TriggerSource hook with CRUD operations
40294
+ *
40295
+ * @example Basic TriggerSource Operations
40296
+ * ```typescript
40297
+ * function TriggerSourceManager() {
40298
+ * const {
40299
+ * getAll,
40300
+ * getById,
40301
+ * create,
40302
+ * update,
40303
+ * remove
40304
+ * } = useTriggerSources();
40305
+ *
40306
+ * // List all QR code trigger sources
40307
+ * const loadQRSources = async () => {
40308
+ * const { data: sources } = await getAll({ type: 'QR_CODE' });
40309
+ * console.log('QR Sources:', sources);
40310
+ * };
40311
+ *
40312
+ * // Create a new QR code for a store
40313
+ * const createStoreQR = async () => {
40314
+ * const source = await create({
40315
+ * type: 'QR_CODE',
40316
+ * name: 'Store Entrance QR',
40317
+ * description: 'Scan at the entrance',
40318
+ * businessId: 'business-123',
40319
+ * coordsLatitude: 47.6062,
40320
+ * coordsLongitude: -122.3321
40321
+ * });
40322
+ * console.log('Created:', source.id);
40323
+ * };
40324
+ * }
40325
+ * ```
40326
+ *
40327
+ * @example GPS Geofence Trigger
40328
+ * ```typescript
40329
+ * const { create } = useTriggerSources();
40330
+ *
40331
+ * // Create a GPS geofence around a location
40332
+ * const geofence = await create({
40333
+ * type: 'GPS_GEOFENCE',
40334
+ * name: 'Downtown Area',
40335
+ * coordsLatitude: 47.6062,
40336
+ * coordsLongitude: -122.3321,
40337
+ * metadata: { radiusMeters: 100 }
40338
+ * });
40339
+ * ```
40340
+ */
40341
+ const useTriggerSources = () => {
40342
+ const { sdk, isInitialized, isAuthenticated } = usePersSDK();
40343
+ /**
40344
+ * Get trigger sources with optional filters and pagination
40345
+ *
40346
+ * Retrieves trigger sources (QR codes, NFC tags, GPS geofences, webhooks, etc.)
40347
+ * that can be used to activate campaigns. Supports filtering by type, business,
40348
+ * campaign association, and active status.
40349
+ *
40350
+ * @param options - Filter and pagination options
40351
+ * @returns Promise resolving to paginated trigger sources
40352
+ *
40353
+ * @example
40354
+ * ```typescript
40355
+ * // Get all QR code trigger sources
40356
+ * const { data: qrSources } = await getAll({ type: 'QR_CODE' });
40357
+ *
40358
+ * // Get trigger sources for a specific business
40359
+ * const { data: businessSources, pagination } = await getAll({
40360
+ * businessId: 'business-123',
40361
+ * active: true,
40362
+ * page: 1,
40363
+ * limit: 20
40364
+ * });
40365
+ *
40366
+ * // Get trigger sources assigned to a campaign
40367
+ * const { data: campaignSources } = await getAll({
40368
+ * campaignId: 'campaign-456'
40369
+ * });
40370
+ * ```
40371
+ */
40372
+ const getAll = react.useCallback(async (options) => {
40373
+ if (!isInitialized || !sdk) {
40374
+ throw new Error('SDK not initialized. Call initialize() first.');
40375
+ }
40376
+ try {
40377
+ const result = await sdk.triggerSources.getAll(options);
40378
+ return result;
40379
+ }
40380
+ catch (error) {
40381
+ console.error('Failed to fetch trigger sources:', error);
40382
+ throw error;
40383
+ }
40384
+ }, [sdk, isInitialized]);
40385
+ /**
40386
+ * Get trigger source by ID
40387
+ *
40388
+ * Retrieves detailed information for a specific trigger source including
40389
+ * its type, location, metadata, and configuration.
40390
+ *
40391
+ * @param triggerSourceId - UUID of the trigger source
40392
+ * @returns Promise resolving to trigger source details
40393
+ * @throws {PersApiError} When trigger source with specified ID is not found
40394
+ *
40395
+ * @example
40396
+ * ```typescript
40397
+ * const source = await getById('source-123');
40398
+ *
40399
+ * console.log('Trigger Source:', source.name);
40400
+ * console.log('Type:', source.type);
40401
+ * console.log('Active:', source.isActive);
40402
+ *
40403
+ * if (source.coordsLatitude && source.coordsLongitude) {
40404
+ * console.log('Location:', source.coordsLatitude, source.coordsLongitude);
40405
+ * }
40406
+ * ```
40407
+ */
40408
+ const getById = react.useCallback(async (triggerSourceId) => {
40409
+ if (!isInitialized || !sdk) {
40410
+ throw new Error('SDK not initialized. Call initialize() first.');
40411
+ }
40412
+ try {
40413
+ const result = await sdk.triggerSources.getById(triggerSourceId);
40414
+ return result;
40415
+ }
40416
+ catch (error) {
40417
+ console.error('Failed to fetch trigger source:', error);
40418
+ throw error;
40419
+ }
40420
+ }, [sdk, isInitialized]);
40421
+ /**
40422
+ * Admin: Create a new trigger source
40423
+ *
40424
+ * Creates a new trigger source (QR code, NFC tag, GPS geofence, webhook, or
40425
+ * transaction-based trigger) that can be assigned to campaigns. Requires
40426
+ * administrator privileges.
40427
+ *
40428
+ * @param triggerSource - Trigger source creation data
40429
+ * @returns Promise resolving to created trigger source
40430
+ * @throws {PersApiError} When not authenticated as admin or validation fails
40431
+ *
40432
+ * @example
40433
+ * ```typescript
40434
+ * // Create a QR code trigger source
40435
+ * const qrSource = await create({
40436
+ * type: 'QR_CODE',
40437
+ * name: 'Store Entrance QR',
40438
+ * description: 'QR code at main entrance',
40439
+ * businessId: 'business-123',
40440
+ * coordsLatitude: 47.6062,
40441
+ * coordsLongitude: -122.3321
40442
+ * });
40443
+ *
40444
+ * // Create an NFC tag trigger source
40445
+ * const nfcSource = await create({
40446
+ * type: 'NFC_TAG',
40447
+ * name: 'Product Display NFC',
40448
+ * metadata: { productId: 'SKU-12345' }
40449
+ * });
40450
+ * ```
40451
+ */
40452
+ const create = react.useCallback(async (triggerSource) => {
40453
+ if (!isInitialized || !sdk) {
40454
+ throw new Error('SDK not initialized. Call initialize() first.');
40455
+ }
40456
+ if (!isAuthenticated) {
40457
+ throw new Error('SDK not authenticated. create requires admin authentication.');
40458
+ }
40459
+ try {
40460
+ const result = await sdk.triggerSources.create(triggerSource);
40461
+ return result;
40462
+ }
40463
+ catch (error) {
40464
+ console.error('Failed to create trigger source:', error);
40465
+ throw error;
40466
+ }
40467
+ }, [sdk, isInitialized, isAuthenticated]);
40468
+ /**
40469
+ * Admin: Update a trigger source
40470
+ *
40471
+ * Updates an existing trigger source's configuration. All fields are optional;
40472
+ * only provided fields will be updated. Requires administrator privileges.
40473
+ *
40474
+ * @param triggerSourceId - UUID of the trigger source to update
40475
+ * @param triggerSource - Updated trigger source data (partial)
40476
+ * @returns Promise resolving to updated trigger source
40477
+ * @throws {PersApiError} When not authenticated as admin or trigger source not found
40478
+ *
40479
+ * @example
40480
+ * ```typescript
40481
+ * // Update trigger source name and location
40482
+ * const updated = await update('source-123', {
40483
+ * name: 'Updated Store Entrance QR',
40484
+ * description: 'New description',
40485
+ * coordsLatitude: 47.6063,
40486
+ * coordsLongitude: -122.3322
40487
+ * });
40488
+ * ```
40489
+ */
40490
+ const update = react.useCallback(async (triggerSourceId, triggerSource) => {
40491
+ if (!isInitialized || !sdk) {
40492
+ throw new Error('SDK not initialized. Call initialize() first.');
40493
+ }
40494
+ if (!isAuthenticated) {
40495
+ throw new Error('SDK not authenticated. update requires admin authentication.');
40496
+ }
40497
+ try {
40498
+ const result = await sdk.triggerSources.update(triggerSourceId, triggerSource);
40499
+ return result;
40500
+ }
40501
+ catch (error) {
40502
+ console.error('Failed to update trigger source:', error);
40503
+ throw error;
40504
+ }
40505
+ }, [sdk, isInitialized, isAuthenticated]);
40506
+ /**
40507
+ * Admin: Delete a trigger source
40508
+ *
40509
+ * Soft deletes a trigger source, making it inactive. The trigger source will
40510
+ * be removed from any campaigns it was assigned to. Requires administrator
40511
+ * privileges.
40512
+ *
40513
+ * @param triggerSourceId - UUID of the trigger source to delete
40514
+ * @returns Promise resolving to success status
40515
+ * @throws {PersApiError} When not authenticated as admin or trigger source not found
40516
+ *
40517
+ * @example
40518
+ * ```typescript
40519
+ * const success = await remove('source-123');
40520
+ * console.log('Trigger source deleted:', success);
40521
+ * ```
40522
+ */
40523
+ const remove = react.useCallback(async (triggerSourceId) => {
40524
+ if (!isInitialized || !sdk) {
40525
+ throw new Error('SDK not initialized. Call initialize() first.');
40526
+ }
40527
+ if (!isAuthenticated) {
40528
+ throw new Error('SDK not authenticated. remove requires admin authentication.');
40529
+ }
40530
+ try {
40531
+ const result = await sdk.triggerSources.delete(triggerSourceId);
40532
+ return result;
40533
+ }
40534
+ catch (error) {
40535
+ console.error('Failed to delete trigger source:', error);
40536
+ throw error;
40537
+ }
40538
+ }, [sdk, isInitialized, isAuthenticated]);
40539
+ return {
40540
+ getAll,
40541
+ getById,
40542
+ create,
40543
+ update,
40544
+ remove,
40545
+ isAvailable: isInitialized && !!sdk?.triggerSources,
40546
+ };
40547
+ };
40548
+
38499
40549
  // ==========================================
38500
40550
  // TOKEN UTILITY FUNCTIONS
38501
40551
  // ==========================================
@@ -38577,12 +40627,22 @@ exports.MEMBERSHIP_ROLE_HIERARCHY = MEMBERSHIP_ROLE_HIERARCHY;
38577
40627
  exports.NativeTokenTypes = NativeTokenTypes;
38578
40628
  exports.PersApiError = PersApiError;
38579
40629
  exports.PersSDKProvider = PersSDKProvider;
40630
+ exports.QUERY_OPERATOR_SQL_MAP = QUERY_OPERATOR_SQL_MAP;
38580
40631
  exports.ReactNativeDPoPProvider = ReactNativeDPoPProvider;
38581
40632
  exports.ReactNativeHttpClient = ReactNativeHttpClient;
38582
40633
  exports.ReactNativeSecureStorage = ReactNativeSecureStorage;
40634
+ exports.SOURCE_LOGIC_TYPES = SOURCE_LOGIC_TYPES;
40635
+ exports.SOURCE_LOGIC_TYPE_VALUES = SOURCE_LOGIC_TYPE_VALUES;
38583
40636
  exports.SigningStatus = SigningStatus;
38584
40637
  exports.TRANSACTION_FORMATS = TRANSACTION_FORMATS;
38585
40638
  exports.TRANSACTION_FORMAT_DESCRIPTIONS = TRANSACTION_FORMAT_DESCRIPTIONS;
40639
+ exports.TRIGGER_SOURCE_TYPES = TRIGGER_SOURCE_TYPES;
40640
+ exports.TRIGGER_SOURCE_TYPE_VALUES = TRIGGER_SOURCE_TYPE_VALUES;
40641
+ exports.VALID_CAMPAIGN_CLAIM_RELATIONS = VALID_CAMPAIGN_CLAIM_RELATIONS;
40642
+ exports.VALID_CAMPAIGN_RELATIONS = VALID_CAMPAIGN_RELATIONS;
40643
+ exports.VALID_REDEMPTION_REDEEM_RELATIONS = VALID_REDEMPTION_REDEEM_RELATIONS;
40644
+ exports.VALID_RELATIONS = VALID_RELATIONS;
40645
+ exports.VALID_TRANSACTION_RELATIONS = VALID_TRANSACTION_RELATIONS;
38586
40646
  exports.apiPublicKeyTestPrefix = apiPublicKeyTestPrefix;
38587
40647
  exports.buildBurnRequest = buildBurnRequest;
38588
40648
  exports.buildMintRequest = buildMintRequest;
@@ -38604,6 +40664,11 @@ exports.hasMinimumRole = hasMinimumRole;
38604
40664
  exports.initializeReactNativePolyfills = initializeReactNativePolyfills;
38605
40665
  exports.isPaginatedResponse = isPaginatedResponse;
38606
40666
  exports.isUserIdentifierObject = isUserIdentifierObject;
40667
+ exports.isValidCampaignClaimRelation = isValidCampaignClaimRelation;
40668
+ exports.isValidCampaignRelation = isValidCampaignRelation;
40669
+ exports.isValidRedemptionRedeemRelation = isValidRedemptionRedeemRelation;
40670
+ exports.isValidRelation = isValidRelation;
40671
+ exports.isValidTransactionRelation = isValidTransactionRelation;
38607
40672
  exports.normalizeToPaginated = normalizeToPaginated;
38608
40673
  exports.registerIdentifierType = registerIdentifierType;
38609
40674
  exports.testnetPrefix = testnetPrefix;
@@ -38624,6 +40689,7 @@ exports.useTokenBalances = useTokenBalances;
38624
40689
  exports.useTokens = useTokens;
38625
40690
  exports.useTransactionSigner = useTransactionSigner;
38626
40691
  exports.useTransactions = useTransactions;
40692
+ exports.useTriggerSources = useTriggerSources;
38627
40693
  exports.useUserStatus = useUserStatus;
38628
40694
  exports.useUsers = useUsers;
38629
40695
  exports.useWeb3 = useWeb3;