Social Commerce Mini Program Case
This case showcases a social commerce mini program that combines social interaction with e-commerce functionality, enabling users to share products, participate in group buying, and earn through social recommendations.
Project Overview
Project Background
Social commerce represents the convergence of social media and e-commerce, leveraging social relationships and user-generated content to drive sales. This mini program creates a community-driven shopping experience where users can discover products through friends' recommendations, participate in group activities, and build their own social commerce networks.
Core Features
- Social Product Sharing: Users can share favorite products with friends and social networks
- Group Buying: Collaborative purchasing with discounted prices for group orders
- Referral System: Earn commissions by referring friends and promoting products
- Live Streaming: Real-time product demonstrations and interactive shopping
- Community Features: User reviews, discussions, and product recommendations
- Gamification: Points, badges, and rewards for social activities
Technical Implementation
Social Sharing System
// utils/social-share.js
class SocialShare {
static async shareProduct(product) {
const shareData = {
title: product.name,
desc: `${product.description.substring(0, 50)}...`,
path: `/pages/product/detail?id=${product.id}&from=share`,
imageUrl: product.image
}
try {
await wx.shareAppMessage(shareData)
// Track sharing activity
this.trackShare(product.id, 'product')
// Reward user for sharing
this.rewardUser('share_product', 10)
} catch (error) {
console.error('Share failed:', error)
}
}
static async shareToMoments(product) {
const shareData = {
title: `Check out this amazing product: ${product.name}`,
imageUrl: product.image,
query: `id=${product.id}&from=moments`
}
try {
await wx.shareTimeline(shareData)
this.trackShare(product.id, 'moments')
this.rewardUser('share_moments', 20)
} catch (error) {
console.error('Share to moments failed:', error)
}
}
static trackShare(productId, platform) {
wx.request({
url: '/api/analytics/share',
method: 'POST',
data: {
productId,
platform,
userId: wx.getStorageSync('userId'),
timestamp: Date.now()
}
})
}
static rewardUser(action, points) {
wx.request({
url: '/api/user/reward',
method: 'POST',
data: {
action,
points,
userId: wx.getStorageSync('userId')
}
})
}
}
export default SocialShare
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Group Buying System
// pages/group-buy/group-buy.js
Page({
data: {
groupInfo: null,
participants: [],
timeLeft: 0,
canJoin: true,
isCreator: false
},
onLoad(options) {
const { groupId } = options
this.groupId = groupId
this.loadGroupInfo()
this.startCountdown()
},
async loadGroupInfo() {
try {
const res = await wx.request({
url: `/api/group-buy/${this.groupId}`,
method: 'GET'
})
const groupInfo = res.data
const userId = wx.getStorageSync('userId')
this.setData({
groupInfo,
participants: groupInfo.participants,
isCreator: groupInfo.creatorId === userId,
canJoin: groupInfo.participants.length < groupInfo.maxParticipants &&
!groupInfo.participants.some(p => p.userId === userId)
})
} catch (error) {
console.error('Failed to load group info:', error)
}
},
async joinGroup() {
if (!this.data.canJoin) return
try {
wx.showLoading({ title: 'Joining group...' })
const res = await wx.request({
url: `/api/group-buy/${this.groupId}/join`,
method: 'POST',
data: {
userId: wx.getStorageSync('userId')
}
})
if (res.data.success) {
wx.showToast({
title: 'Joined successfully!',
icon: 'success'
})
this.loadGroupInfo()
// Check if group is complete
if (res.data.groupComplete) {
this.handleGroupComplete()
}
}
} catch (error) {
console.error('Failed to join group:', error)
wx.showToast({
title: 'Failed to join',
icon: 'error'
})
} finally {
wx.hideLoading()
}
},
async createGroup() {
try {
const res = await wx.request({
url: '/api/group-buy/create',
method: 'POST',
data: {
productId: this.data.groupInfo.productId,
maxParticipants: this.data.groupInfo.maxParticipants,
duration: 24 * 60 * 60 * 1000, // 24 hours
creatorId: wx.getStorageSync('userId')
}
})
const newGroupId = res.data.groupId
// Share the new group
wx.shareAppMessage({
title: `Join my group buy for ${this.data.groupInfo.productName}!`,
path: `/pages/group-buy/group-buy?groupId=${newGroupId}`,
imageUrl: this.data.groupInfo.productImage
})
} catch (error) {
console.error('Failed to create group:', error)
}
},
handleGroupComplete() {
wx.showModal({
title: 'Group Complete!',
content: 'The group buying is successful. Proceed to payment?',
success: (res) => {
if (res.confirm) {
this.processGroupPayment()
}
}
})
},
async processGroupPayment() {
try {
const res = await wx.request({
url: `/api/group-buy/${this.groupId}/payment`,
method: 'POST'
})
const paymentParams = res.data
wx.requestPayment({
...paymentParams,
success: () => {
wx.showToast({
title: 'Payment successful!',
icon: 'success'
})
setTimeout(() => {
wx.redirectTo({
url: '/pages/order/list'
})
}, 1500)
},
fail: (error) => {
console.error('Payment failed:', error)
}
})
} catch (error) {
console.error('Failed to process payment:', error)
}
},
startCountdown() {
const updateCountdown = () => {
if (!this.data.groupInfo) return
const endTime = new Date(this.data.groupInfo.endTime).getTime()
const now = Date.now()
const timeLeft = Math.max(0, endTime - now)
this.setData({ timeLeft })
if (timeLeft > 0) {
setTimeout(updateCountdown, 1000)
} else {
this.handleGroupExpired()
}
}
updateCountdown()
},
handleGroupExpired() {
wx.showModal({
title: 'Group Expired',
content: 'This group buying has expired.',
showCancel: false,
success: () => {
wx.navigateBack()
}
})
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
Referral System
// utils/referral.js
class ReferralSystem {
static async generateReferralCode(userId) {
try {
const res = await wx.request({
url: '/api/referral/generate',
method: 'POST',
data: { userId }
})
return res.data.referralCode
} catch (error) {
console.error('Failed to generate referral code:', error)
throw error
}
}
static async trackReferral(referralCode, newUserId) {
try {
await wx.request({
url: '/api/referral/track',
method: 'POST',
data: {
referralCode,
newUserId,
timestamp: Date.now()
}
})
} catch (error) {
console.error('Failed to track referral:', error)
}
}
static async calculateCommission(orderId, referrerId) {
try {
const res = await wx.request({
url: '/api/referral/commission',
method: 'POST',
data: {
orderId,
referrerId
}
})
return res.data.commission
} catch (error) {
console.error('Failed to calculate commission:', error)
return 0
}
}
static async shareReferralLink(productId, referralCode) {
const shareData = {
title: 'Join me on this amazing shopping platform!',
desc: 'Get exclusive discounts and earn rewards',
path: `/pages/product/detail?id=${productId}&ref=${referralCode}`,
imageUrl: '/images/referral-banner.jpg'
}
try {
await wx.shareAppMessage(shareData)
// Track referral sharing
this.trackReferralShare(referralCode)
} catch (error) {
console.error('Failed to share referral link:', error)
}
}
static trackReferralShare(referralCode) {
wx.request({
url: '/api/referral/share-track',
method: 'POST',
data: {
referralCode,
timestamp: Date.now()
}
})
}
}
export default ReferralSystem
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
Live Streaming Integration
// pages/live-stream/live-stream.js
Page({
data: {
streamInfo: null,
products: [],
comments: [],
viewerCount: 0,
isLive: false
},
onLoad(options) {
const { streamId } = options
this.streamId = streamId
this.loadStreamInfo()
this.connectWebSocket()
},
async loadStreamInfo() {
try {
const res = await wx.request({
url: `/api/live-stream/${this.streamId}`,
method: 'GET'
})
this.setData({
streamInfo: res.data.stream,
products: res.data.products,
isLive: res.data.stream.status === 'live'
})
} catch (error) {
console.error('Failed to load stream info:', error)
}
},
connectWebSocket() {
this.socketTask = wx.connectSocket({
url: `wss://api.example.com/live-stream/${this.streamId}/ws`
})
this.socketTask.onMessage((res) => {
const data = JSON.parse(res.data)
this.handleWebSocketMessage(data)
})
this.socketTask.onError((error) => {
console.error('WebSocket error:', error)
})
},
handleWebSocketMessage(data) {
switch (data.type) {
case 'comment':
this.addComment(data.comment)
break
case 'viewer_count':
this.setData({ viewerCount: data.count })
break
case 'product_highlight':
this.highlightProduct(data.productId)
break
case 'stream_end':
this.handleStreamEnd()
break
}
},
addComment(comment) {
const comments = [...this.data.comments, comment]
this.setData({ comments })
// Auto-scroll to latest comment
this.scrollToLatestComment()
},
sendComment(e) {
const content = e.detail.value.trim()
if (!content) return
const comment = {
userId: wx.getStorageSync('userId'),
username: wx.getStorageSync('username'),
content,
timestamp: Date.now()
}
this.socketTask.send({
data: JSON.stringify({
type: 'comment',
comment
})
})
// Clear input
this.setData({ commentInput: '' })
},
onProductTap(e) {
const productId = e.currentTarget.dataset.id
// Track product interest
this.trackProductInterest(productId)
wx.navigateTo({
url: `/pages/product/detail?id=${productId}&from=live`
})
},
async trackProductInterest(productId) {
try {
await wx.request({
url: '/api/analytics/product-interest',
method: 'POST',
data: {
productId,
streamId: this.streamId,
userId: wx.getStorageSync('userId'),
timestamp: Date.now()
}
})
} catch (error) {
console.error('Failed to track product interest:', error)
}
},
highlightProduct(productId) {
const products = this.data.products.map(product => ({
...product,
highlighted: product.id === productId
}))
this.setData({ products })
// Remove highlight after 5 seconds
setTimeout(() => {
const products = this.data.products.map(product => ({
...product,
highlighted: false
}))
this.setData({ products })
}, 5000)
},
handleStreamEnd() {
this.setData({ isLive: false })
wx.showModal({
title: 'Stream Ended',
content: 'The live stream has ended. Thank you for watching!',
showCancel: false,
success: () => {
wx.navigateBack()
}
})
},
onUnload() {
if (this.socketTask) {
this.socketTask.close()
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
User Engagement Features
Gamification System
// utils/gamification.js
class GamificationSystem {
static async getUserLevel(userId) {
try {
const res = await wx.request({
url: `/api/user/${userId}/level`,
method: 'GET'
})
return res.data
} catch (error) {
console.error('Failed to get user level:', error)
return { level: 1, points: 0, nextLevelPoints: 100 }
}
}
static async awardPoints(userId, action, points) {
try {
const res = await wx.request({
url: '/api/user/award-points',
method: 'POST',
data: {
userId,
action,
points
}
})
if (res.data.levelUp) {
this.showLevelUpAnimation(res.data.newLevel)
}
return res.data
} catch (error) {
console.error('Failed to award points:', error)
}
}
static showLevelUpAnimation(newLevel) {
wx.showModal({
title: 'Level Up!',
content: `Congratulations! You've reached level ${newLevel}!`,
showCancel: false,
success: () => {
// Show celebration animation
this.triggerCelebration()
}
})
}
static triggerCelebration() {
// Trigger confetti or other celebration effects
const animation = wx.createAnimation({
duration: 1000,
timingFunction: 'ease'
})
animation.scale(1.2).rotate(360).step()
animation.scale(1).step()
// Apply animation to celebration element
this.setData({
celebrationAnimation: animation.export()
})
}
static async getUserBadges(userId) {
try {
const res = await wx.request({
url: `/api/user/${userId}/badges`,
method: 'GET'
})
return res.data.badges
} catch (error) {
console.error('Failed to get user badges:', error)
return []
}
}
static async checkBadgeEligibility(userId, action) {
try {
const res = await wx.request({
url: '/api/user/check-badges',
method: 'POST',
data: {
userId,
action
}
})
if (res.data.newBadges.length > 0) {
this.showNewBadges(res.data.newBadges)
}
return res.data.newBadges
} catch (error) {
console.error('Failed to check badge eligibility:', error)
return []
}
}
static showNewBadges(badges) {
badges.forEach((badge, index) => {
setTimeout(() => {
wx.showModal({
title: 'New Badge Earned!',
content: `You've earned the "${badge.name}" badge!`,
showCancel: false
})
}, index * 1000)
})
}
}
export default GamificationSystem
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
Social Feed System
// pages/social-feed/social-feed.js
Page({
data: {
posts: [],
loading: false,
hasMore: true,
page: 1
},
onLoad() {
this.loadFeed()
},
onReachBottom() {
if (this.data.hasMore && !this.data.loading) {
this.loadMore()
}
},
onPullDownRefresh() {
this.setData({
posts: [],
page: 1,
hasMore: true
})
this.loadFeed()
},
async loadFeed() {
this.setData({ loading: true })
try {
const res = await wx.request({
url: '/api/social-feed',
method: 'GET',
data: {
page: this.data.page,
limit: 10,
userId: wx.getStorageSync('userId')
}
})
const newPosts = res.data.posts
this.setData({
posts: this.data.page === 1 ? newPosts : [...this.data.posts, ...newPosts],
hasMore: newPosts.length === 10,
loading: false
})
wx.stopPullDownRefresh()
} catch (error) {
console.error('Failed to load feed:', error)
this.setData({ loading: false })
wx.stopPullDownRefresh()
}
},
async loadMore() {
this.setData({
page: this.data.page + 1
})
await this.loadFeed()
},
async onLikePost(e) {
const postId = e.currentTarget.dataset.id
const postIndex = e.currentTarget.dataset.index
try {
const res = await wx.request({
url: `/api/social-feed/${postId}/like`,
method: 'POST',
data: {
userId: wx.getStorageSync('userId')
}
})
// Update local state
const posts = [...this.data.posts]
posts[postIndex].liked = res.data.liked
posts[postIndex].likeCount = res.data.likeCount
this.setData({ posts })
// Show heart animation
this.showLikeAnimation(e.currentTarget)
} catch (error) {
console.error('Failed to like post:', error)
}
},
showLikeAnimation(target) {
const animation = wx.createAnimation({
duration: 300,
timingFunction: 'ease-out'
})
animation.scale(1.3).step()
animation.scale(1).step()
// Apply animation
const query = wx.createSelectorQuery()
query.select('.like-button').boundingClientRect()
query.exec((res) => {
if (res[0]) {
// Create floating heart effect
this.createFloatingHeart(res[0])
}
})
},
createFloatingHeart(rect) {
const heart = {
id: Date.now(),
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2,
opacity: 1
}
// Add heart to floating hearts array
const floatingHearts = [...(this.data.floatingHearts || []), heart]
this.setData({ floatingHearts })
// Animate heart floating up
const animation = wx.createAnimation({
duration: 1000,
timingFunction: 'ease-out'
})
animation.translateY(-100).opacity(0).step()
setTimeout(() => {
// Remove heart after animation
const hearts = this.data.floatingHearts.filter(h => h.id !== heart.id)
this.setData({ floatingHearts: hearts })
}, 1000)
},
onSharePost(e) {
const post = e.currentTarget.dataset.post
wx.shareAppMessage({
title: post.content.substring(0, 50),
desc: `Shared by ${post.author.name}`,
path: `/pages/social-feed/post-detail?id=${post.id}`,
imageUrl: post.images[0] || '/images/default-share.jpg'
})
// Track sharing
this.trackPostShare(post.id)
},
async trackPostShare(postId) {
try {
await wx.request({
url: `/api/social-feed/${postId}/share`,
method: 'POST',
data: {
userId: wx.getStorageSync('userId'),
timestamp: Date.now()
}
})
} catch (error) {
console.error('Failed to track post share:', error)
}
},
onCommentPost(e) {
const postId = e.currentTarget.dataset.id
wx.navigateTo({
url: `/pages/social-feed/comments?postId=${postId}`
})
},
onCreatePost() {
wx.navigateTo({
url: '/pages/social-feed/create-post'
})
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
Analytics and Insights
Social Commerce Analytics
// utils/social-analytics.js
class SocialAnalytics {
static async trackSocialAction(action, data) {
try {
await wx.request({
url: '/api/analytics/social-action',
method: 'POST',
data: {
action,
data,
userId: wx.getStorageSync('userId'),
timestamp: Date.now()
}
})
} catch (error) {
console.error('Failed to track social action:', error)
}
}
static async getSocialInsights(userId) {
try {
const res = await wx.request({
url: `/api/analytics/social-insights/${userId}`,
method: 'GET'
})
return res.data
} catch (error) {
console.error('Failed to get social insights:', error)
return null
}
}
static async trackInfluencerMetrics(userId) {
try {
const res = await wx.request({
url: `/api/analytics/influencer-metrics/${userId}`,
method: 'GET'
})
return {
totalShares: res.data.totalShares,
totalReferrals: res.data.totalReferrals,
conversionRate: res.data.conversionRate,
totalCommissions: res.data.totalCommissions,
topProducts: res.data.topProducts
}
} catch (error) {
console.error('Failed to get influencer metrics:', error)
return null
}
}
static async generateSocialReport(userId, period = '30d') {
try {
const res = await wx.request({
url: `/api/analytics/social-report/${userId}`,
method: 'GET',
data: { period }
})
return res.data
} catch (error) {
console.error('Failed to generate social report:', error)
return null
}
}
}
export default SocialAnalytics
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
Project Results
Key Metrics
- Social Engagement: 65% of users actively share products
- Group Buying Success Rate: 78% of groups reach completion
- Referral Conversion: 25% of referred users make purchases
- User Retention: 70% monthly active user retention
- Average Order Value: 40% higher than traditional e-commerce
Business Impact
- Viral Growth: 200% user acquisition through social sharing
- Revenue Increase: 150% boost in sales through social features
- Customer Acquisition Cost: 60% reduction through referral program
- Community Building: 50,000+ active community members
- Brand Advocacy: 85% user satisfaction with social features
This social commerce mini program successfully combines the power of social interaction with e-commerce functionality, creating a engaging and profitable platform that leverages user relationships to drive business growth.