@warriorteam/redai-zalo-sdk 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1248 @@
1
+ # RedAI Zalo SDK - User Management Guide
2
+
3
+ ## Tổng quan
4
+
5
+ User Management trong RedAI Zalo SDK cung cấp các công cụ toàn diện để quản lý người dùng, bao gồm:
6
+
7
+ - 👥 **UserService** - Truy cập thông tin user social và OA followers
8
+ - 🏷️ **UserManagementService** - Quản lý user profiles và interactions
9
+ - 📊 **User Analytics** - Phân tích hành vi và tương tác
10
+ - 🔍 **User Search** - Tìm kiếm và lọc users
11
+ - 📋 **Bulk Operations** - Xử lý hàng loạt users
12
+
13
+ ---
14
+
15
+ ## UserService
16
+
17
+ Truy cập thông tin user từ Social API và OA followers.
18
+
19
+ ### Khởi tạo
20
+
21
+ ```typescript
22
+ import { ZaloSDK } from "@warriorteam/redai-zalo-sdk";
23
+
24
+ const zalo = new ZaloSDK({
25
+ appId: "your-app-id",
26
+ appSecret: "your-app-secret"
27
+ });
28
+
29
+ // Access user service
30
+ const userService = zalo.user;
31
+ ```
32
+
33
+ ### 1. Lấy thông tin user Social
34
+
35
+ ```typescript
36
+ // Lấy thông tin user từ Social API
37
+ const socialUserInfo = await zalo.getSocialUserInfo(
38
+ socialAccessToken,
39
+ "id,name,picture,birthday,gender,locale"
40
+ );
41
+
42
+ console.log("User ID:", socialUserInfo.id);
43
+ console.log("Name:", socialUserInfo.name);
44
+ console.log("Avatar:", socialUserInfo.picture?.data.url);
45
+ console.log("Birthday:", socialUserInfo.birthday);
46
+ ```
47
+
48
+ ### 2. Lấy thông tin user OA
49
+
50
+ ```typescript
51
+ // Lấy thông tin user đã tương tác với OA
52
+ const userInfo = await zalo.user.getUserInfo(
53
+ oaAccessToken,
54
+ "zalo-user-id"
55
+ );
56
+
57
+ console.log("User Info:", {
58
+ userId: userInfo.user_id,
59
+ displayName: userInfo.display_name,
60
+ avatar: userInfo.avatar,
61
+ userGender: userInfo.user_gender,
62
+ userAlias: userInfo.user_alias
63
+ });
64
+ ```
65
+
66
+ ### 3. Lấy danh sách users
67
+
68
+ ```typescript
69
+ // Lấy danh sách users với filter
70
+ const userList = await zalo.user.getUserList(oaAccessToken, {
71
+ offset: 0,
72
+ count: 50,
73
+ tag_name: "VIP_CUSTOMER", // Lọc theo tag (tùy chọn)
74
+ is_follower: true // Chỉ lấy followers (tùy chọn)
75
+ });
76
+
77
+ console.log("Total users:", userList.total);
78
+ userList.data.forEach(user => {
79
+ console.log(`${user.display_name} - ${user.user_id}`);
80
+ });
81
+ ```
82
+
83
+ ### 4. Lấy danh sách followers
84
+
85
+ ```typescript
86
+ // Lấy tất cả followers của OA
87
+ const followers = await zalo.user.getFollowers(oaAccessToken, {
88
+ offset: 0,
89
+ count: 100
90
+ });
91
+
92
+ console.log("Total followers:", followers.total);
93
+ followers.data.forEach(follower => {
94
+ console.log(`Follower: ${follower.display_name}`);
95
+ console.log(`Follow time: ${new Date(follower.user_id_by_app).toLocaleString()}`);
96
+ });
97
+ ```
98
+
99
+ ---
100
+
101
+ ## UserManagementService
102
+
103
+ Quản lý user profiles chi tiết và interactions.
104
+
105
+ ### Khởi tạo
106
+
107
+ ```typescript
108
+ const userManagement = zalo.userManagement;
109
+ ```
110
+
111
+ ### 1. Lấy user profile chi tiết
112
+
113
+ ```typescript
114
+ // Lấy profile đầy đủ của user
115
+ const userProfile = await zalo.userManagement.getUserProfile(
116
+ oaAccessToken,
117
+ "user-zalo-id"
118
+ );
119
+
120
+ console.log("User Profile:", {
121
+ userId: userProfile.user_id,
122
+ displayName: userProfile.display_name,
123
+ avatar: userProfile.avatar,
124
+ phone: userProfile.shared_info?.phone,
125
+ address: userProfile.shared_info?.address,
126
+ tags: userProfile.tags,
127
+ notes: userProfile.notes,
128
+ lastInteraction: userProfile.last_interaction_time
129
+ });
130
+ ```
131
+
132
+ ### 2. Cập nhật user profile
133
+
134
+ ```typescript
135
+ // Cập nhật thông tin user
136
+ const updatedProfile = await zalo.userManagement.updateUserProfile(
137
+ oaAccessToken,
138
+ "user-zalo-id",
139
+ {
140
+ notes: "Customer quan tâm sản phẩm cao cấp",
141
+ custom_fields: {
142
+ customer_tier: "VIP",
143
+ preferred_contact: "zalo",
144
+ last_purchase_date: "2024-12-01",
145
+ total_spent: "5000000"
146
+ }
147
+ }
148
+ );
149
+ ```
150
+
151
+ ### 3. Lấy lịch sử tương tác
152
+
153
+ ```typescript
154
+ // Lấy tất cả interactions với user
155
+ const interactions = await zalo.userManagement.getUserInteractions(
156
+ oaAccessToken,
157
+ "user-zalo-id",
158
+ {
159
+ from_time: Date.now() - (30 * 24 * 60 * 60 * 1000), // 30 days ago
160
+ to_time: Date.now(),
161
+ interaction_type: "all", // message, call, order, etc.
162
+ limit: 100
163
+ }
164
+ );
165
+
166
+ interactions.forEach(interaction => {
167
+ console.log(`${interaction.type}: ${interaction.content} at ${new Date(interaction.time).toLocaleString()}`);
168
+ });
169
+ ```
170
+
171
+ ### 4. Phân tích user analytics
172
+
173
+ ```typescript
174
+ // Lấy analytics của user
175
+ const userAnalytics = await zalo.userManagement.getUserAnalytics(
176
+ oaAccessToken,
177
+ "user-zalo-id"
178
+ );
179
+
180
+ console.log("User Analytics:", {
181
+ totalMessages: userAnalytics.total_messages_sent,
182
+ totalMessagesReceived: userAnalytics.total_messages_received,
183
+ avgResponseTime: userAnalytics.avg_response_time,
184
+ engagementScore: userAnalytics.engagement_score,
185
+ lastSeenTime: userAnalytics.last_seen_time,
186
+ preferredTime: userAnalytics.most_active_time
187
+ });
188
+ ```
189
+
190
+ ### 5. Tìm kiếm users
191
+
192
+ ```typescript
193
+ // Tìm kiếm users theo điều kiện
194
+ const searchResult = await zalo.userManagement.searchUsers(
195
+ oaAccessToken,
196
+ {
197
+ query: "Nguyễn", // Tên hoặc phone
198
+ tags: ["VIP", "Premium"], // Tags
199
+ interaction_period: 30, // Tương tác trong 30 ngày qua
200
+ min_order_value: 1000000, // Đơn hàng tối thiểu
201
+ location: "Ho Chi Minh", // Địa điểm
202
+ gender: "male", // Giới tính
203
+ age_range: "25-35", // Độ tuổi
204
+ limit: 50,
205
+ offset: 0
206
+ }
207
+ );
208
+
209
+ console.log(`Found ${searchResult.total} users matching criteria`);
210
+ searchResult.users.forEach(user => {
211
+ console.log(`${user.display_name} - Score: ${user.match_score}`);
212
+ });
213
+ ```
214
+
215
+ ---
216
+
217
+ ## User Segmentation & Analytics
218
+
219
+ ### 1. User Segmentation Service
220
+
221
+ ```typescript
222
+ class UserSegmentationService {
223
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
224
+
225
+ // Phân đoạn users theo hành vi mua hàng
226
+ async segmentUsersByPurchaseBehavior(): Promise<UserSegments> {
227
+ const allUsers = await this.getAllUsers();
228
+ const segments = {
229
+ highValue: [], // > 10M VND
230
+ mediumValue: [], // 1M - 10M VND
231
+ lowValue: [], // < 1M VND
232
+ inactive: [] // Không mua trong 90 ngày
233
+ };
234
+
235
+ for (const user of allUsers) {
236
+ const analytics = await this.zalo.userManagement.getUserAnalytics(
237
+ this.accessToken,
238
+ user.user_id
239
+ );
240
+
241
+ const totalSpent = analytics.total_spent || 0;
242
+ const daysSinceLastPurchase = analytics.days_since_last_purchase || 999;
243
+
244
+ if (daysSinceLastPurchase > 90) {
245
+ segments.inactive.push(user);
246
+ } else if (totalSpent > 10000000) {
247
+ segments.highValue.push(user);
248
+ } else if (totalSpent > 1000000) {
249
+ segments.mediumValue.push(user);
250
+ } else {
251
+ segments.lowValue.push(user);
252
+ }
253
+ }
254
+
255
+ return segments;
256
+ }
257
+
258
+ // Phân đoạn theo engagement
259
+ async segmentUsersByEngagement(): Promise<EngagementSegments> {
260
+ const users = await this.getAllUsers();
261
+ const segments = {
262
+ champions: [], // High value + High engagement
263
+ loyalists: [], // High engagement
264
+ potential: [], // Medium engagement
265
+ atRisk: [], // Low recent engagement
266
+ lost: [] // No recent engagement
267
+ };
268
+
269
+ for (const user of users) {
270
+ const analytics = await this.zalo.userManagement.getUserAnalytics(
271
+ this.accessToken,
272
+ user.user_id
273
+ );
274
+
275
+ const engagementScore = analytics.engagement_score || 0;
276
+ const daysSinceLastInteraction = analytics.days_since_last_interaction || 999;
277
+ const totalSpent = analytics.total_spent || 0;
278
+
279
+ if (engagementScore > 80 && totalSpent > 5000000) {
280
+ segments.champions.push(user);
281
+ } else if (engagementScore > 70) {
282
+ segments.loyalists.push(user);
283
+ } else if (engagementScore > 40) {
284
+ segments.potential.push(user);
285
+ } else if (daysSinceLastInteraction < 30) {
286
+ segments.atRisk.push(user);
287
+ } else {
288
+ segments.lost.push(user);
289
+ }
290
+ }
291
+
292
+ return segments;
293
+ }
294
+
295
+ private async getAllUsers(): Promise<UserProfile[]> {
296
+ const allUsers = [];
297
+ let offset = 0;
298
+ const limit = 100;
299
+
300
+ while (true) {
301
+ const userList = await this.zalo.user.getUserList(this.accessToken, {
302
+ offset,
303
+ count: limit
304
+ });
305
+
306
+ allUsers.push(...userList.data);
307
+
308
+ if (userList.data.length < limit) break;
309
+ offset += limit;
310
+ }
311
+
312
+ return allUsers;
313
+ }
314
+ }
315
+ ```
316
+
317
+ ### 2. Customer Lifetime Value Analysis
318
+
319
+ ```typescript
320
+ class CLVAnalysisService {
321
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
322
+
323
+ async calculateUserCLV(userId: string): Promise<CLVMetrics> {
324
+ const analytics = await this.zalo.userManagement.getUserAnalytics(
325
+ this.accessToken,
326
+ userId
327
+ );
328
+
329
+ const interactions = await this.zalo.userManagement.getUserInteractions(
330
+ this.accessToken,
331
+ userId,
332
+ { interaction_type: "purchase", limit: 1000 }
333
+ );
334
+
335
+ const purchases = interactions.filter(i => i.type === "purchase");
336
+
337
+ if (purchases.length === 0) {
338
+ return { clv: 0, tier: "inactive", recommendations: [] };
339
+ }
340
+
341
+ // Tính toán CLV
342
+ const totalSpent = purchases.reduce((sum, p) => sum + (p.value || 0), 0);
343
+ const avgOrderValue = totalSpent / purchases.length;
344
+ const purchaseFrequency = this.calculatePurchaseFrequency(purchases);
345
+ const customerLifespan = this.calculateCustomerLifespan(purchases);
346
+
347
+ const clv = avgOrderValue * purchaseFrequency * customerLifespan;
348
+
349
+ return {
350
+ clv,
351
+ avgOrderValue,
352
+ purchaseFrequency,
353
+ customerLifespan,
354
+ totalOrders: purchases.length,
355
+ totalSpent,
356
+ tier: this.determineTier(clv),
357
+ recommendations: this.generateRecommendations(clv, analytics)
358
+ };
359
+ }
360
+
361
+ private calculatePurchaseFrequency(purchases: any[]): number {
362
+ if (purchases.length < 2) return 0;
363
+
364
+ const timespan = purchases[0].time - purchases[purchases.length - 1].time;
365
+ const days = timespan / (24 * 60 * 60 * 1000);
366
+
367
+ return purchases.length / (days / 365); // Purchases per year
368
+ }
369
+
370
+ private calculateCustomerLifespan(purchases: any[]): number {
371
+ if (purchases.length < 2) return 1;
372
+
373
+ const timespan = purchases[0].time - purchases[purchases.length - 1].time;
374
+ return Math.max(1, timespan / (365 * 24 * 60 * 60 * 1000)); // Years
375
+ }
376
+
377
+ private determineTier(clv: number): string {
378
+ if (clv > 50000000) return "diamond";
379
+ if (clv > 20000000) return "gold";
380
+ if (clv > 5000000) return "silver";
381
+ if (clv > 1000000) return "bronze";
382
+ return "standard";
383
+ }
384
+
385
+ private generateRecommendations(clv: number, analytics: any): string[] {
386
+ const recommendations = [];
387
+
388
+ if (clv > 20000000) {
389
+ recommendations.push("Assign dedicated account manager");
390
+ recommendations.push("Offer exclusive products and early access");
391
+ recommendations.push("Provide VIP customer service");
392
+ } else if (clv > 5000000) {
393
+ recommendations.push("Implement loyalty program");
394
+ recommendations.push("Send personalized offers");
395
+ recommendations.push("Prioritize customer support");
396
+ } else if (clv < 1000000) {
397
+ recommendations.push("Focus on engagement and education");
398
+ recommendations.push("Offer entry-level products");
399
+ recommendations.push("Encourage repeat purchases");
400
+ }
401
+
402
+ return recommendations;
403
+ }
404
+ }
405
+ ```
406
+
407
+ ---
408
+
409
+ ## Bulk Operations
410
+
411
+ ### 1. Bulk User Operations
412
+
413
+ ```typescript
414
+ // Thực hiện bulk operations trên nhiều users
415
+ const bulkResult = await zalo.userManagement.bulkUserOperation(
416
+ oaAccessToken,
417
+ {
418
+ operation: "update_tags",
419
+ user_ids: ["user1", "user2", "user3"],
420
+ data: {
421
+ add_tags: ["FLASH_SALE_2024"],
422
+ remove_tags: ["OLD_CAMPAIGN"]
423
+ }
424
+ }
425
+ );
426
+
427
+ console.log(`Updated ${bulkResult.successful_count} users`);
428
+ console.log(`Failed: ${bulkResult.failed_count}`);
429
+ ```
430
+
431
+ ### 2. Bulk Message Service
432
+
433
+ ```typescript
434
+ class BulkMessageService {
435
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
436
+
437
+ async sendBulkConsultationMessages(
438
+ userMessages: Array<{userId: string, message: any}>
439
+ ): Promise<BulkMessageResult> {
440
+ const results = {
441
+ successful: 0,
442
+ failed: 0,
443
+ errors: [] as Array<{userId: string, error: string}>
444
+ };
445
+
446
+ const batchSize = 10; // Process 10 users at a time
447
+
448
+ for (let i = 0; i < userMessages.length; i += batchSize) {
449
+ const batch = userMessages.slice(i, i + batchSize);
450
+
451
+ const promises = batch.map(async ({userId, message}) => {
452
+ try {
453
+ await this.zalo.consultation.sendTextMessage(
454
+ this.accessToken,
455
+ { user_id: userId },
456
+ message
457
+ );
458
+ return { userId, success: true };
459
+ } catch (error) {
460
+ return { userId, success: false, error: error.message };
461
+ }
462
+ });
463
+
464
+ const batchResults = await Promise.all(promises);
465
+
466
+ batchResults.forEach(result => {
467
+ if (result.success) {
468
+ results.successful++;
469
+ } else {
470
+ results.failed++;
471
+ results.errors.push({
472
+ userId: result.userId,
473
+ error: result.error || 'Unknown error'
474
+ });
475
+ }
476
+ });
477
+
478
+ // Delay between batches to avoid rate limiting
479
+ if (i + batchSize < userMessages.length) {
480
+ await this.delay(1000);
481
+ }
482
+ }
483
+
484
+ return results;
485
+ }
486
+
487
+ // Gửi tin nhắn theo segments
488
+ async sendSegmentedCampaign(campaign: Campaign): Promise<CampaignResult> {
489
+ const segments = await this.getUserSegments(campaign.targetSegments);
490
+ const results = new Map<string, BulkMessageResult>();
491
+
492
+ for (const [segmentName, users] of segments) {
493
+ console.log(`Sending campaign to ${segmentName}: ${users.length} users`);
494
+
495
+ const segmentMessage = this.personalizeMessageForSegment(
496
+ campaign.message,
497
+ segmentName
498
+ );
499
+
500
+ const userMessages = users.map(user => ({
501
+ userId: user.user_id,
502
+ message: this.personalizeMessage(segmentMessage, user)
503
+ }));
504
+
505
+ const segmentResult = await this.sendBulkConsultationMessages(userMessages);
506
+ results.set(segmentName, segmentResult);
507
+ }
508
+
509
+ return this.aggregateResults(results);
510
+ }
511
+
512
+ private async getUserSegments(targetSegments: string[]): Promise<Map<string, UserProfile[]>> {
513
+ const segments = new Map();
514
+
515
+ for (const segment of targetSegments) {
516
+ const users = await this.zalo.userManagement.searchUsers(
517
+ this.accessToken,
518
+ { tags: [segment], limit: 1000 }
519
+ );
520
+ segments.set(segment, users.users);
521
+ }
522
+
523
+ return segments;
524
+ }
525
+
526
+ private personalizeMessageForSegment(template: string, segment: string): any {
527
+ // Customize message based on segment
528
+ const customizations = {
529
+ 'VIP': {
530
+ greeting: 'Kính chào Quý khách VIP',
531
+ offer: 'ưu đãi đặc biệt dành riêng cho VIP'
532
+ },
533
+ 'Premium': {
534
+ greeting: 'Xin chào khách hàng Premium',
535
+ offer: 'chương trình ưu đãi Premium'
536
+ },
537
+ 'Standard': {
538
+ greeting: 'Xin chào',
539
+ offer: 'chương trình khuyến mại'
540
+ }
541
+ };
542
+
543
+ const custom = customizations[segment] || customizations['Standard'];
544
+
545
+ return {
546
+ type: "text",
547
+ text: template
548
+ .replace('{greeting}', custom.greeting)
549
+ .replace('{offer}', custom.offer)
550
+ };
551
+ }
552
+
553
+ private personalizeMessage(template: any, user: UserProfile): any {
554
+ return {
555
+ ...template,
556
+ text: template.text.replace('{name}', user.display_name || 'bạn')
557
+ };
558
+ }
559
+
560
+ private delay(ms: number): Promise<void> {
561
+ return new Promise(resolve => setTimeout(resolve, ms));
562
+ }
563
+ }
564
+ ```
565
+
566
+ ---
567
+
568
+ ## User Journey Tracking
569
+
570
+ ### 1. Journey Mapping Service
571
+
572
+ ```typescript
573
+ class UserJourneyService {
574
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
575
+
576
+ async mapUserJourney(userId: string): Promise<UserJourney> {
577
+ const interactions = await this.zalo.userManagement.getUserInteractions(
578
+ this.accessToken,
579
+ userId,
580
+ { limit: 1000 }
581
+ );
582
+
583
+ const journey = this.analyzeJourneyStages(interactions);
584
+ const touchpoints = this.identifyTouchpoints(interactions);
585
+ const conversion = this.calculateConversionMetrics(interactions);
586
+
587
+ return {
588
+ userId,
589
+ currentStage: journey.currentStage,
590
+ stages: journey.stages,
591
+ touchpoints,
592
+ conversion,
593
+ recommendations: this.generateJourneyRecommendations(journey, conversion)
594
+ };
595
+ }
596
+
597
+ private analyzeJourneyStages(interactions: any[]): JourneyStages {
598
+ const stages = {
599
+ awareness: [],
600
+ consideration: [],
601
+ purchase: [],
602
+ retention: [],
603
+ advocacy: []
604
+ };
605
+
606
+ const currentStage = this.determineCurrentStage(interactions);
607
+
608
+ // Classify interactions by journey stage
609
+ interactions.forEach(interaction => {
610
+ const stage = this.classifyInteractionStage(interaction);
611
+ stages[stage].push(interaction);
612
+ });
613
+
614
+ return { currentStage, stages };
615
+ }
616
+
617
+ private identifyTouchpoints(interactions: any[]): Touchpoint[] {
618
+ const touchpointMap = new Map();
619
+
620
+ interactions.forEach(interaction => {
621
+ const touchpoint = this.getTouchpointFromInteraction(interaction);
622
+
623
+ if (touchpointMap.has(touchpoint.type)) {
624
+ touchpointMap.get(touchpoint.type).count++;
625
+ touchpointMap.get(touchpoint.type).lastInteraction = interaction.time;
626
+ } else {
627
+ touchpointMap.set(touchpoint.type, {
628
+ ...touchpoint,
629
+ count: 1,
630
+ lastInteraction: interaction.time
631
+ });
632
+ }
633
+ });
634
+
635
+ return Array.from(touchpointMap.values()).sort((a, b) => b.count - a.count);
636
+ }
637
+
638
+ private calculateConversionMetrics(interactions: any[]): ConversionMetrics {
639
+ const totalInteractions = interactions.length;
640
+ const purchases = interactions.filter(i => i.type === 'purchase');
641
+ const inquiries = interactions.filter(i => i.type === 'product_inquiry');
642
+
643
+ return {
644
+ conversionRate: purchases.length / totalInteractions,
645
+ inquiryToPurchase: purchases.length / (inquiries.length || 1),
646
+ avgTimeToConversion: this.calculateAvgTimeToConversion(interactions),
647
+ totalPurchases: purchases.length,
648
+ totalValue: purchases.reduce((sum, p) => sum + (p.value || 0), 0)
649
+ };
650
+ }
651
+
652
+ private generateJourneyRecommendations(
653
+ journey: JourneyStages,
654
+ conversion: ConversionMetrics
655
+ ): string[] {
656
+ const recommendations = [];
657
+
658
+ switch (journey.currentStage) {
659
+ case 'awareness':
660
+ recommendations.push('Send educational content about products');
661
+ recommendations.push('Showcase customer testimonials and reviews');
662
+ break;
663
+
664
+ case 'consideration':
665
+ recommendations.push('Provide detailed product comparisons');
666
+ recommendations.push('Offer consultation sessions');
667
+ recommendations.push('Send limited-time offers to encourage decision');
668
+ break;
669
+
670
+ case 'purchase':
671
+ recommendations.push('Streamline checkout process');
672
+ recommendations.push('Offer multiple payment options');
673
+ recommendations.push('Provide immediate support');
674
+ break;
675
+
676
+ case 'retention':
677
+ recommendations.push('Send onboarding and education materials');
678
+ recommendations.push('Implement loyalty program');
679
+ recommendations.push('Request feedback and reviews');
680
+ break;
681
+
682
+ case 'advocacy':
683
+ recommendations.push('Encourage referrals with incentives');
684
+ recommendations.push('Feature as case study');
685
+ recommendations.push('Invite to exclusive events');
686
+ break;
687
+ }
688
+
689
+ if (conversion.conversionRate < 0.1) {
690
+ recommendations.push('Focus on engagement improvement');
691
+ recommendations.push('Personalize content based on interests');
692
+ }
693
+
694
+ return recommendations;
695
+ }
696
+ }
697
+ ```
698
+
699
+ ---
700
+
701
+ ## User Retention & Re-engagement
702
+
703
+ ### 1. Retention Analysis
704
+
705
+ ```typescript
706
+ class UserRetentionService {
707
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
708
+
709
+ async analyzeUserRetention(period: 'weekly' | 'monthly'): Promise<RetentionAnalysis> {
710
+ const cohorts = await this.getCohorts(period);
711
+ const retentionRates = new Map();
712
+
713
+ for (const [cohortDate, users] of cohorts) {
714
+ const retention = await this.calculateCohortRetention(users, cohortDate, period);
715
+ retentionRates.set(cohortDate, retention);
716
+ }
717
+
718
+ return {
719
+ period,
720
+ cohorts: Array.from(retentionRates.entries()),
721
+ averageRetention: this.calculateAverageRetention(retentionRates),
722
+ insights: this.generateRetentionInsights(retentionRates)
723
+ };
724
+ }
725
+
726
+ // Identify users at risk of churning
727
+ async identifyChurnRisk(): Promise<ChurnRiskAnalysis> {
728
+ const allUsers = await this.getAllActiveUsers();
729
+ const riskUsers = [];
730
+
731
+ for (const user of allUsers) {
732
+ const riskScore = await this.calculateChurnRiskScore(user.user_id);
733
+
734
+ if (riskScore > 0.7) {
735
+ riskUsers.push({
736
+ ...user,
737
+ riskScore,
738
+ riskFactors: await this.identifyRiskFactors(user.user_id)
739
+ });
740
+ }
741
+ }
742
+
743
+ return {
744
+ totalUsers: allUsers.length,
745
+ highRiskUsers: riskUsers.filter(u => u.riskScore > 0.9),
746
+ mediumRiskUsers: riskUsers.filter(u => u.riskScore > 0.7 && u.riskScore <= 0.9),
747
+ recommendations: this.generateChurnPreventionStrategies(riskUsers)
748
+ };
749
+ }
750
+
751
+ private async calculateChurnRiskScore(userId: string): Promise<number> {
752
+ const analytics = await this.zalo.userManagement.getUserAnalytics(
753
+ this.accessToken,
754
+ userId
755
+ );
756
+
757
+ let score = 0;
758
+
759
+ // Days since last interaction
760
+ const daysSinceLastInteraction = analytics.days_since_last_interaction || 0;
761
+ if (daysSinceLastInteraction > 30) score += 0.3;
762
+ if (daysSinceLastInteraction > 60) score += 0.2;
763
+ if (daysSinceLastInteraction > 90) score += 0.3;
764
+
765
+ // Declining engagement
766
+ const engagementTrend = analytics.engagement_trend || 0;
767
+ if (engagementTrend < -0.2) score += 0.2;
768
+
769
+ // Reduced purchase frequency
770
+ const purchaseTrend = analytics.purchase_frequency_trend || 0;
771
+ if (purchaseTrend < -0.3) score += 0.3;
772
+
773
+ // Support issues
774
+ const supportIssues = analytics.recent_support_issues || 0;
775
+ if (supportIssues > 2) score += 0.2;
776
+
777
+ return Math.min(1, score);
778
+ }
779
+
780
+ // Re-engagement campaign for inactive users
781
+ async runReengagementCampaign(
782
+ inactiveUsers: string[],
783
+ campaignType: 'win_back' | 'survey' | 'special_offer'
784
+ ): Promise<ReengagementResult> {
785
+ const results = {
786
+ contacted: 0,
787
+ responded: 0,
788
+ reactivated: 0,
789
+ errors: []
790
+ };
791
+
792
+ for (const userId of inactiveUsers) {
793
+ try {
794
+ const message = this.createReengagementMessage(campaignType, userId);
795
+
796
+ await this.zalo.consultation.sendTextMessage(
797
+ this.accessToken,
798
+ { user_id: userId },
799
+ message
800
+ );
801
+
802
+ results.contacted++;
803
+
804
+ // Track if user responds within campaign period
805
+ setTimeout(async () => {
806
+ const isReactivated = await this.checkUserReactivation(userId);
807
+ if (isReactivated) {
808
+ results.reactivated++;
809
+ }
810
+ }, 7 * 24 * 60 * 60 * 1000); // Check after 7 days
811
+
812
+ } catch (error) {
813
+ results.errors.push({ userId, error: error.message });
814
+ }
815
+ }
816
+
817
+ return results;
818
+ }
819
+
820
+ private createReengagementMessage(
821
+ campaignType: string,
822
+ userId: string
823
+ ): any {
824
+ const messages = {
825
+ win_back: {
826
+ type: "text",
827
+ text: "Chúng tôi nhớ bạn! 🥺\nBạn đã không tương tác với chúng tôi một thời gian rồi. Có gì chúng tôi có thể giúp bạn không?"
828
+ },
829
+ survey: {
830
+ type: "text",
831
+ text: "Xin chào! 👋\nChúng tôi muốn cải thiện dịch vụ. Bạn có thể chia sẻ lý do tại sao ít tương tác với chúng tôi gần đây không?"
832
+ },
833
+ special_offer: {
834
+ type: "text",
835
+ text: "🎁 Ưu đãi đặc biệt dành cho bạn!\nGiảm 50% cho lần mua hàng tiếp theo. Mã: COMEBACK50\nChỉ còn 3 ngày!"
836
+ }
837
+ };
838
+
839
+ return messages[campaignType] || messages.win_back;
840
+ }
841
+ }
842
+ ```
843
+
844
+ ---
845
+
846
+ ## Data Privacy & GDPR Compliance
847
+
848
+ ### 1. User Consent Management
849
+
850
+ ```typescript
851
+ class UserConsentService {
852
+ constructor(private zalo: ZaloSDK, private accessToken: string) {}
853
+
854
+ async recordUserConsent(
855
+ userId: string,
856
+ consentType: ConsentType,
857
+ granted: boolean
858
+ ): Promise<void> {
859
+ await this.zalo.userManagement.updateUserProfile(
860
+ this.accessToken,
861
+ userId,
862
+ {
863
+ custom_fields: {
864
+ [`consent_${consentType}`]: granted.toString(),
865
+ [`consent_${consentType}_date`]: new Date().toISOString(),
866
+ [`consent_${consentType}_ip`]: this.getCurrentIP()
867
+ }
868
+ }
869
+ );
870
+ }
871
+
872
+ async getUserConsents(userId: string): Promise<UserConsents> {
873
+ const profile = await this.zalo.userManagement.getUserProfile(
874
+ this.accessToken,
875
+ userId
876
+ );
877
+
878
+ return {
879
+ marketing: profile.custom_fields?.consent_marketing === 'true',
880
+ analytics: profile.custom_fields?.consent_analytics === 'true',
881
+ data_processing: profile.custom_fields?.consent_data_processing === 'true',
882
+ third_party_sharing: profile.custom_fields?.consent_third_party === 'true'
883
+ };
884
+ }
885
+
886
+ async exportUserData(userId: string): Promise<UserDataExport> {
887
+ const profile = await this.zalo.userManagement.getUserProfile(
888
+ this.accessToken,
889
+ userId
890
+ );
891
+
892
+ const interactions = await this.zalo.userManagement.getUserInteractions(
893
+ this.accessToken,
894
+ userId,
895
+ { limit: 10000 }
896
+ );
897
+
898
+ const analytics = await this.zalo.userManagement.getUserAnalytics(
899
+ this.accessToken,
900
+ userId
901
+ );
902
+
903
+ return {
904
+ personal_data: {
905
+ user_id: profile.user_id,
906
+ display_name: profile.display_name,
907
+ avatar: profile.avatar,
908
+ phone: profile.shared_info?.phone,
909
+ address: profile.shared_info?.address
910
+ },
911
+ interaction_history: interactions,
912
+ analytics_data: analytics,
913
+ consent_records: await this.getUserConsents(userId),
914
+ export_date: new Date().toISOString(),
915
+ retention_period: "36_months"
916
+ };
917
+ }
918
+
919
+ async deleteUserData(userId: string): Promise<DeletionResult> {
920
+ // Implement GDPR-compliant data deletion
921
+ const deletionTasks = [
922
+ this.deleteUserProfile(userId),
923
+ this.deleteUserInteractions(userId),
924
+ this.deleteUserAnalytics(userId),
925
+ this.removeFromMarketingLists(userId)
926
+ ];
927
+
928
+ const results = await Promise.allSettled(deletionTasks);
929
+
930
+ return {
931
+ userId,
932
+ deleted: results.every(r => r.status === 'fulfilled'),
933
+ deletion_date: new Date().toISOString(),
934
+ retention_logs: this.createDeletionLog(userId, results)
935
+ };
936
+ }
937
+ }
938
+ ```
939
+
940
+ ---
941
+
942
+ ## Testing User Management
943
+
944
+ ### 1. Unit Tests
945
+
946
+ ```typescript
947
+ // user-management.test.ts
948
+ import { ZaloSDK } from '@warriorteam/redai-zalo-sdk';
949
+
950
+ describe('User Management', () => {
951
+ const zalo = new ZaloSDK({
952
+ appId: 'test_app_id',
953
+ appSecret: 'test_app_secret'
954
+ });
955
+
956
+ it('should get user profile', async () => {
957
+ const mockProfile = {
958
+ user_id: 'test_user',
959
+ display_name: 'Test User',
960
+ avatar: 'https://example.com/avatar.jpg'
961
+ };
962
+
963
+ jest.spyOn(zalo.userManagement, 'getUserProfile').mockResolvedValue(mockProfile);
964
+
965
+ const profile = await zalo.userManagement.getUserProfile('test_token', 'test_user');
966
+
967
+ expect(profile.user_id).toBe('test_user');
968
+ expect(profile.display_name).toBe('Test User');
969
+ });
970
+
971
+ it('should search users with filters', async () => {
972
+ const mockResult = {
973
+ total: 5,
974
+ users: [
975
+ { user_id: '1', display_name: 'User 1' },
976
+ { user_id: '2', display_name: 'User 2' }
977
+ ]
978
+ };
979
+
980
+ jest.spyOn(zalo.userManagement, 'searchUsers').mockResolvedValue(mockResult);
981
+
982
+ const result = await zalo.userManagement.searchUsers('test_token', {
983
+ query: 'test',
984
+ tags: ['VIP']
985
+ });
986
+
987
+ expect(result.total).toBe(5);
988
+ expect(result.users).toHaveLength(2);
989
+ });
990
+ });
991
+ ```
992
+
993
+ ---
994
+
995
+ ## Performance Optimization
996
+
997
+ ### 1. Caching User Data
998
+
999
+ ```typescript
1000
+ class UserDataCache {
1001
+ private cache = new Map<string, CachedUserData>();
1002
+ private readonly ttl = 10 * 60 * 1000; // 10 minutes
1003
+
1004
+ async getUserProfile(
1005
+ zalo: ZaloSDK,
1006
+ accessToken: string,
1007
+ userId: string
1008
+ ): Promise<UserProfile> {
1009
+ const cacheKey = `profile_${userId}`;
1010
+ const cached = this.get(cacheKey);
1011
+
1012
+ if (cached) {
1013
+ return cached.data;
1014
+ }
1015
+
1016
+ const profile = await zalo.userManagement.getUserProfile(accessToken, userId);
1017
+ this.set(cacheKey, profile);
1018
+
1019
+ return profile;
1020
+ }
1021
+
1022
+ private get(key: string): CachedUserData | null {
1023
+ const item = this.cache.get(key);
1024
+
1025
+ if (!item) return null;
1026
+
1027
+ if (Date.now() - item.timestamp > this.ttl) {
1028
+ this.cache.delete(key);
1029
+ return null;
1030
+ }
1031
+
1032
+ return item;
1033
+ }
1034
+
1035
+ private set(key: string, data: any): void {
1036
+ this.cache.set(key, {
1037
+ data,
1038
+ timestamp: Date.now()
1039
+ });
1040
+ }
1041
+
1042
+ // Batch load multiple users
1043
+ async batchLoadUsers(
1044
+ zalo: ZaloSDK,
1045
+ accessToken: string,
1046
+ userIds: string[]
1047
+ ): Promise<Map<string, UserProfile>> {
1048
+ const results = new Map();
1049
+ const uncachedIds = [];
1050
+
1051
+ // Check cache first
1052
+ for (const userId of userIds) {
1053
+ const cached = this.get(`profile_${userId}`);
1054
+ if (cached) {
1055
+ results.set(userId, cached.data);
1056
+ } else {
1057
+ uncachedIds.push(userId);
1058
+ }
1059
+ }
1060
+
1061
+ // Batch load uncached users
1062
+ if (uncachedIds.length > 0) {
1063
+ const batchSize = 5; // API rate limiting
1064
+
1065
+ for (let i = 0; i < uncachedIds.length; i += batchSize) {
1066
+ const batch = uncachedIds.slice(i, i + batchSize);
1067
+
1068
+ const promises = batch.map(async (userId) => {
1069
+ try {
1070
+ const profile = await zalo.userManagement.getUserProfile(accessToken, userId);
1071
+ this.set(`profile_${userId}`, profile);
1072
+ return { userId, profile };
1073
+ } catch (error) {
1074
+ console.error(`Failed to load user ${userId}:`, error);
1075
+ return { userId, profile: null };
1076
+ }
1077
+ });
1078
+
1079
+ const batchResults = await Promise.all(promises);
1080
+
1081
+ batchResults.forEach(({ userId, profile }) => {
1082
+ if (profile) {
1083
+ results.set(userId, profile);
1084
+ }
1085
+ });
1086
+
1087
+ // Delay between batches
1088
+ if (i + batchSize < uncachedIds.length) {
1089
+ await new Promise(resolve => setTimeout(resolve, 1000));
1090
+ }
1091
+ }
1092
+ }
1093
+
1094
+ return results;
1095
+ }
1096
+ }
1097
+ ```
1098
+
1099
+ ---
1100
+
1101
+ ## Best Practices
1102
+
1103
+ ### 1. Data Management Best Practices
1104
+
1105
+ ```typescript
1106
+ // ✅ Good practices
1107
+ class UserDataBestPractices {
1108
+ // Always validate user IDs
1109
+ private validateUserId(userId: string): boolean {
1110
+ return /^[0-9]+$/.test(userId) && userId.length > 0;
1111
+ }
1112
+
1113
+ // Implement proper error handling
1114
+ async safeGetUserProfile(
1115
+ zalo: ZaloSDK,
1116
+ accessToken: string,
1117
+ userId: string
1118
+ ): Promise<UserProfile | null> {
1119
+ if (!this.validateUserId(userId)) {
1120
+ throw new Error('Invalid user ID format');
1121
+ }
1122
+
1123
+ try {
1124
+ return await zalo.userManagement.getUserProfile(accessToken, userId);
1125
+ } catch (error) {
1126
+ if (error.code === -233) {
1127
+ console.log(`User ${userId} not found`);
1128
+ return null;
1129
+ }
1130
+ throw error;
1131
+ }
1132
+ }
1133
+
1134
+ // Always paginate large datasets
1135
+ async getAllUsersWithPagination(
1136
+ zalo: ZaloSDK,
1137
+ accessToken: string
1138
+ ): Promise<UserProfile[]> {
1139
+ const allUsers = [];
1140
+ let offset = 0;
1141
+ const limit = 50; // Reasonable page size
1142
+
1143
+ while (true) {
1144
+ const page = await zalo.user.getUserList(accessToken, {
1145
+ offset,
1146
+ count: limit
1147
+ });
1148
+
1149
+ allUsers.push(...page.data);
1150
+
1151
+ if (page.data.length < limit) break;
1152
+ offset += limit;
1153
+
1154
+ // Rate limiting
1155
+ await new Promise(resolve => setTimeout(resolve, 500));
1156
+ }
1157
+
1158
+ return allUsers;
1159
+ }
1160
+
1161
+ // Implement data validation
1162
+ private validateUserData(userData: any): boolean {
1163
+ const required = ['user_id', 'display_name'];
1164
+ return required.every(field => userData[field]);
1165
+ }
1166
+ }
1167
+ ```
1168
+
1169
+ ### 2. Privacy-First Approach
1170
+
1171
+ ```typescript
1172
+ class PrivacyCompliantUserService {
1173
+ // Always check consent before processing
1174
+ async sendMarketingMessage(
1175
+ userId: string,
1176
+ message: any
1177
+ ): Promise<boolean> {
1178
+ const consents = await this.getUserConsents(userId);
1179
+
1180
+ if (!consents.marketing) {
1181
+ console.log(`User ${userId} has not consented to marketing messages`);
1182
+ return false;
1183
+ }
1184
+
1185
+ // Proceed with sending message
1186
+ return true;
1187
+ }
1188
+
1189
+ // Minimize data collection
1190
+ async updateUserProfile(
1191
+ userId: string,
1192
+ updates: Partial<UserProfile>
1193
+ ): Promise<void> {
1194
+ // Only update necessary fields
1195
+ const allowedFields = ['notes', 'tags', 'preferences'];
1196
+ const filteredUpdates = Object.keys(updates)
1197
+ .filter(key => allowedFields.includes(key))
1198
+ .reduce((obj, key) => {
1199
+ obj[key] = updates[key];
1200
+ return obj;
1201
+ }, {});
1202
+
1203
+ if (Object.keys(filteredUpdates).length === 0) {
1204
+ throw new Error('No valid fields to update');
1205
+ }
1206
+
1207
+ // Proceed with update
1208
+ }
1209
+ }
1210
+ ```
1211
+
1212
+ ---
1213
+
1214
+ ## Troubleshooting
1215
+
1216
+ ### Common Issues
1217
+
1218
+ **Q: "User not found" error khi lấy profile**
1219
+ ```
1220
+ A: User có thể đã unfollow OA hoặc chặn OA.
1221
+ Kiểm tra danh sách followers trước khi truy cập profile.
1222
+ ```
1223
+
1224
+ **Q: Không lấy được thông tin phone number**
1225
+ ```
1226
+ A: Phone number chỉ có sẵn nếu user đã share với OA.
1227
+ Cần yêu cầu user cung cấp thông tin qua request_user_info.
1228
+ ```
1229
+
1230
+ **Q: Search results không accurate**
1231
+ ```
1232
+ A: Zalo search có giới hạn. Implement local caching và filtering
1233
+ để có kết quả search tốt hơn.
1234
+ ```
1235
+
1236
+ ---
1237
+
1238
+ ## Next Steps
1239
+
1240
+ Sau khi nắm vững User Management:
1241
+
1242
+ 1. **[Tag Management](./TAG_MANAGEMENT.md)** - User tagging và segmentation
1243
+ 2. **[Group Management](./GROUP_MANAGEMENT.md)** - Quản lý Zalo groups
1244
+ 3. **[Webhook Events](./WEBHOOK_EVENTS.md)** - Xử lý user events
1245
+ 4. **[Error Handling](./ERROR_HANDLING.md)** - Xử lý lỗi toàn diện
1246
+ 5. **[Video Upload](./VIDEO_UPLOAD.md)** - Upload và manage media
1247
+
1248
+ Tham khảo **[API Reference](./API_REFERENCE.md)** để biết chi tiết về tất cả user management methods.