概述
本文档介绍了基于腾讯云 IM(即时通信)实现红包功能的实践方案。红包功能支持两种场景:
一对一红包:在单聊中向一个用户发送红包。
群红包:在群聊中发送红包,群内多名成员可参与领取。
方案核心思路是利用腾讯云 IM 的自定义消息、消息修改和消息扩展数据能力,配合外部的支付系统和红包管理系统,完成红包的发送、领取、状态同步和通知等完整流程。
整体架构
整体架构由四个核心组件组成:

各组件职责:
组件 | 职责 |
客户端(发送方/接收方) | 发起发送红包/拆红包请求;渲染红包卡片 UI。 |
腾讯云 IM | 消息投递、状态更新、领取记录存储、领取通知推送。 |
红包系统 | 红包生命周期管理:创建、校验、拆开、金额计算。 |
支付系统 | 资金管理:扣款、转账、退款。 |
核心概念与数据结构
1. 自定义消息字段
红包通过 IM 的自定义消息(Custom Message)实现。自定义消息的 payload 包含以下关键字段:
字段 | 类型 | 说明 |
BusinessID | String | 消息类型标识,红包消息固定为 "red_packet"。 |
packet_id | String | 发送方调用红包系统创建红包,支付系统扣款成功后,由红包系统生成唯一 packet_id ,红包系统通过 REST API 将它写入 IM 自定义消息的 customData,使消息与红包一一绑定。 |
money_amount | Number | 红包金额(一对一红包使用)。 |
best_wishes | String | 祝福语,例如"恭喜发财,大吉大利"。 |
cover | String | 红包封面图片 URL。 |
status | String | 红包状态: "Unopened"(未领取)/ "Opened"(已领取)/ "None left"(已领完)。 |
quantity | Number | (仅群红包) 红包个数。 |
total | Number | (仅群红包) 红包总金额。 |
random_amount | Boolean | (仅群红包) 是否为拼手气红包(随机金额)。 |
一对一红包 payload 示例:
{"BusinessID": "red_packet","money_amount": 1.00,"best_wishes": "恭喜发财!","cover": "https://example.com/cover.png","status": "Unopened","packet_id": "rp_20260301_abc123"}
群红包 payload 示例:
{"BusinessID": "red_packet","total": 2.00,"quantity": 3,"random_amount": true,"best_wishes": "新年快乐!","cover": "https://example.com/cover.png","status": "Unopened","packet_id": "rp_20260301_xyz789"}
2. 消息扩展数据(领取记录)
Key | Value | 说明 |
{userID} | {金额},{时间戳} | 每条领取记录作为一个 KV 对存储。 |
示例:
{"user_daniel": "1.40,1709312400","user_anson": "0.60,1709312580"}
客户端通过读取消息的扩展数据,即可在红包详情页展示完整的领取列表,包括领取人、金额和时间。
3. 消息变更与消息扩展
红包方案的核心依赖 IM 的两项关键能力:消息变更(Message Modification)和消息扩展(Message Extension)。两者配合使用,实现红包状态的实时同步和领取记录的分布式存储。
消息变更
消息变更允许服务端在消息发送后修改消息内容(
MsgBody)。在红包场景中,用于更新红包卡片的 status 字段(例如从 "Unopened" 变为 "Opened" 或 "None left"),修改后所有客户端都能看到最新的红包状态。单聊消息变更:使用 修改单聊历史消息 REST API,通过
From_Account + To_Account + MsgKey 定位消息。群聊消息变更:使用 修改群聊历史消息 REST API,通过
GroupId + MsgSeq 定位消息。消息扩展
消息扩展允许在已发送消息上附加 KV(Key-Value)数据,而不修改原始消息内容。在红包场景中,用于存储每位用户的领取记录(key = userID,value = 金额,时间戳)。消息扩展的数据变更会实时同步到所有客户端。
单聊消息扩展:使用 设置单聊消息扩展 REST API,通过
From_Account + To_Account + MsgKey 定位消息。群聊消息扩展:使用 设置群消息扩展 REST API,通过
GroupId + MsgSeq 定位消息。要使消息支持扩展数据,发送消息时须设置
SupportMessageExtension 为 1。能力 | 用途(红包场景) | 单聊定位参数 | 群聊定位参数 |
消息变更 | 更新红包状态(Unopened → Opened → None left) | From_Account + To_Account + MsgKey | GroupId + MsgSeq |
消息扩展 | 存储红包领取记录(key=userID, value=金额,时间戳) | From_Account + To_Account + MsgKey | GroupId + MsgSeq |
为什么必须使用 REST API 而非客户端 API?
发送红包消息:保证「扣款 → 发消息」的一致性(避免已经扣款,未发出红包消息的情况,且红包系统内部可掌控资金流向与红包状态)。
红包涉及资金操作,不支持重发。若由客户端发送红包消息,扣款成功后可能遇到两种情况:
发送成功:还需将产生的 MsgKey / MsgSeq 回传给红包系统,链路复杂且不可靠;
发送失败:钱已扣但消息未送达,红包无法重发,用户体验严重受损。由服务端通过 REST API 发送,扣款成功即刻发消息,不依赖客户端网络状态,保证红包必达。
消息变更(修改红包状态):避免并发竞态导致状态回退。
抢红包是高并发场景。若由客户端发起 modify 请求,A、B、C 三人同时拆红包,各自的 modify 调用无法保证执行顺序:可能 C 先将状态改为 "None left",随后 A 的请求才到达又把状态覆盖回 "Opened",造成状态回退。此外,多端并发修改还会触发版本冲突,导致修改失败。由服务端串行处理状态变更,确保 Unopened → Opened → None left 的状态流转严格有序、不可逆。
红包状态的唯一可信源:红包系统,而非 IM 消息。
IM 消息中的 status 字段仅用于 UI 展示,客户端应对其保持零信任。红包的真实状态(是否可领、剩余金额、是否过期)必须以红包系统的实时查询结果为准。用户点击红包时,客户端应携带 packet_id 向红包系统发起查询,以红包系统返回的状态决定后续操作,而非依赖消息体中可能因网络延迟、消息变更失败等原因而过时的 status 值。消息上的状态只是「尽力同步」的快照,红包系统才是唯一的事实来源(Single Source of Truth)。
一对一红包
1. 发送红包界面
发送方在聊天中点击红包入口,填写金额、祝福语并选择封面。这些字段将映射到 IM 自定义消息的对应字段中。

实现步骤
1. 客户端收集红包信息:金额、祝福语、封面图片(可选)。
2. 红包系统调用支付系统扣款接口(Deduction API),从发送方账户扣款至红包资金管理账户。
3. 扣款成功后,红包系统生成唯一的
packet_id。4. 红包系统服务端通过 发送单聊消息 REST API,以发送方身份(
From_Account 设为发送方用户 ID)将红包作为自定义消息发送给接收方。这确保了扣款与消息发送的原子性,只有扣款成功才会发送红包消息 packet_id。// POST https://console.tim.qq.com/v4/openim/sendmsg{"SyncOtherMachine": 1,"From_Account": "sender_userid","To_Account": "receiver_userid","MsgRandom": 1287657,"MsgBody": [{"MsgType": "TIMCustomElem","MsgContent": {"Data": "{\\"BusinessID\\":\\"red_packet\\",\\"money_amount\\":1.00,\\"best_wishes\\":\\"恭喜发财!\\",\\"cover\\":\\"https://example.com/cover.png\\",\\"status\\":\\"Unopened\\",\\"packet_id\\":\\"rp_20260301_abc123\\"}"}}],"SupportMessageExtension": 1}
2. 聊天中的红包消息展示
红包以自定义卡片形式展示在聊天列表中,接收方可直接点击卡片进行领取。领取后会在聊天中显示领取通知。


3. 拆红包界面
接收方点击红包卡片后,进入拆红包界面,显示发送方信息、祝福语以及"拆开"按钮。


实现步骤
1. 接收方点击红包卡片,点击"拆开"按钮。
2. 红包系统校验红包状态(是否已被领取、是否已过期等)。
3. 校验通过后,调用支付系统转账接口(Transfer API),将金额从红包资金管理账户转入接收方账户。
4. 转账成功后,红包系统服务端执行以下操作(注意:这些操作均不由客户端发起,必须由服务端通过 REST API 完成,以确保安全性):
保存领取记录:红包系统服务端通过 设置消息扩展 REST API 写入领取记录,key =
userID,value = 金额,时间戳。修改消息状态:红包系统服务端通过 消息变更 REST API ,将
status 从 "Unopened" 更新为 "Opened"。通知发送方:通过 REST API 发送单聊消息 接口,发送通知消息,例如 "XX 领取了你的红包"。
服务端调用消息变更 REST API 修改红包消息状态:
{"From_Account": "sender_userid","To_Account": "receiver_userid","MsgKey": "msg_key_from_send_response","MsgBody": [{"MsgType": "TIMCustomElem","MsgContent": {"Data": "{\\"BusinessID\\":\\"red_packet\\",\\"money_amount\\":1.00,\\"best_wishes\\":\\"恭喜发财!\\",\\"cover\\":\\"https://example.com/cover.png\\",\\"status\\":\\"Opened\\",\\"packet_id\\":\\"rp_20260301_abc123\\"}"}}]}
服务端调用设置消息扩展 REST API 保存领取记录:
{"From_Account": "sender_userid","To_Account": "receiver_userid","MsgKey": "msg_key_from_send_response","OperateType": 1,"ExtensionList": [{"Key": "user_anson","Value": "0.60,1709312580"}]}
通过 REST API 通知发送方:
通过腾讯云 IM 的服务端 REST API 向发送方推送领取通知消息:
一对一红包:使用 发送单聊消息 REST API。
群红包:使用 发送群聊消息 REST API。
通知消息示例:
"Anson 领取了你的红包"
4. 红包详情页
拆开后进入红包详情页,展示封面、发送方、祝福语、总金额及领取列表。领取列表的数据来源于消息扩展数据(Message Extension Data)。


数据来源
展示内容:
封面图片、发送方信息、祝福语。
红包总金额。
领取列表:领取人姓名、领取金额、领取时间(数据来源于消息扩展数据)。
5. 完整聊天流程
下图展示了一对一红包从发送到领取的完整聊天流程,包括红包状态变更("已领取")以及通过 REST API 发送的领取通知消息。


群红包
1. 发送群红包界面
发送方选择拼手气模式(Random Amount),设置红包个数(Quantity)、总金额(Total)、祝福语和封面。底部提示"24 小时内未领取的红包将自动退回"。


实现步骤
1. 客户端收集:红包总金额、红包个数、祝福语、封面、是否拼手气。
2. 红包系统调用支付系统扣款接口,扣除红包总金额。
3. 红包系统服务端通过 发送群聊消息 REST API,以发送方身份(
From_Account 设为发送方用户 ID)将红包作为自定义消息发送至群组。示例
群红包的 REST API 请求与一对一红包相似(参见 4.1 节示例),区别在于使用发送群聊消息 REST API,将
GroupId 设为目标群组 ID,payload 增加 quantity、total、random_amount 字段。请求示例如下:// POST https://console.tim.qq.com/v4/group_open_http_svc/send_group_msg{"GroupId": "group_id","From_Account": "sender_userid","Random": 1287657,"MsgBody": [{"MsgType": "TIMCustomElem","MsgContent": {"Data": "{\\"BusinessID\\":\\"red_packet\\",\\"total\\":2.00,\\"quantity\\":3,\\"random_amount\\":true,\\"best_wishes\\":\\"新年快乐!\\",\\"cover\\":\\"https://example.com/cover.png\\",\\"status\\":\\"Unopened\\",\\"packet_id\\":\\"rp_20260301_xyz789\\"}"}}],"SupportMessageExtension": 1}
2. 群成员拆红包
每位成员拆红包时:
1. 红包系统校验:红包是否还有剩余?该用户是否已领取过?
2. 红包系统计算领取金额:
拼手气模式:在剩余金额中随机分配(常用"二倍均值"算法确保公平性)。
最后一个红包:接收方获得剩余全部金额。
3. 调用支付系统转账接口,将计算出的金额转给领取成员。
4. 红包系统服务端通过 设置群消息扩展 REST API 保存领取记录。
5. 通过 REST API 通知发送方:
普通领取:"XX 领取了你的红包"
最后一人领取:"XX 领取了你的红包。你发的红包已被全部领完。"
6. 如果所有红包已被领完,红包系统服务端通过 修改群聊历史消息 REST API 将
status 更新为 "None left"。群红包的消息变更、消息扩展数据设置、通知发送均由红包系统服务端通过 REST API 完成,群聊使用专用的群消息变更和群消息扩展 API,参数为
GroupId + MsgSeq。状态流转包含 "None left"。服务端调用修改群聊历史消息 REST API 修改群红包消息状态(完整示例):
以最后一人领取(红包领完)为例,红包系统服务端将
status 更新为 "None left":{"GroupId": "group_id","MsgSeq": 67890,"MsgBody": [{"MsgType": "TIMCustomElem","MsgContent": {"Data": "{\\"BusinessID\\":\\"red_packet\\",\\"total\\":2.00,\\"quantity\\":3,\\"random_amount\\":true,\\"best_wishes\\":\\"新年快乐!\\",\\"cover\\":\\"https://example.com/cover.png\\",\\"status\\":\\"None left\\",\\"packet_id\\":\\"rp_20260301_xyz789\\"}"}}]}
服务端调用 设置群消息扩展 REST API 保存群红包领取记录(完整示例):
{"GroupId": "group_id","MsgSeq": 67890,"OperateType": 1,"ExtensionList": [{"Key": "user_harvy","Value": "1.90,1709312400"},{"Key": "user_anson","Value": "0.10,1709312580"}]}
通过 REST API 通知发送方(群红包):
普通领取:"Hai 领取了你的红包"
最后一人领取:"harvy 领取了你的红包。你发的红包已被全部领完。"
3. 群红包详情页
群红包详情页展示封面、发送方、祝福语、领取统计以及领取列表。金额最高者标注"手气最佳(Luckiest Draw)"。


数据来源
展示内容:
封面图片、发送方信息、祝福语。
领取统计:例如 "已领取 2 个红包,共 ¥2.00,用时 3 分钟"。
领取列表:领取人姓名、头像、领取金额、领取时间。
金额最高者标注"手气最佳"。
获取领取人信息
消息扩展数据中的 Key 为领取人的
userID。客户端获取到扩展数据中的 userID 列表后,需要调用批量拉取群成员资料接口来获取每位领取人的头像、昵称等展示信息。客户端:调用 getGroupMembersInfo (Java / Swift / Objective-C / C++) 方法,传入从扩展数据中提取的 userID 列表,批量获取群成员的头像(
faceUrl)、昵称(nickName)等资料,用于渲染领取列表 UI。服务端:也可通过 获取群成员详细资料 REST API 批量拉取群成员信息。
注意:
消息扩展数组最大支持300个元素,覆盖了 99%的红包场景, 如果消息扩展列表大于300,不建议继续通过消息扩展拉取详情, 此时请您直接从自己红包系统拉取该红包的领取详情。
4. 群聊中的完整流程
下图展示群红包从发送到全部领完的完整聊天流程,包括每次领取的通知以及最后一人领取时的"红包已被全部领完"提示。


5. 群红包与一对一红包的主要区别
多人领取:同一个红包可被多名群成员领取(最多
quantity 个)。金额分配:拼手气模式下,红包系统随机计算每人领取的金额。
状态流转:
"Unopened"(未领取)→ "Opened"(部分领取)→ "None left"(已领完)。手气最佳:领取金额最高的成员在详情页标记为"手气最佳"。
支付系统集成
1. 红包资金管理账户
设置专用的红包资金管理账户(Packet Fund Management Account)作为资金中转:
发红包:从发送方账户扣款,资金暂存至资金管理账户。
拆红包:从资金管理账户转账给领取方账户。
退款:未被领取的金额从资金管理账户退回给发送方。
2. 支付系统接口
接口 | 调用时机 | 说明 |
扣款接口(Deduction API) | 发红包 | 从发送方余额扣款到资金管理账户。 |
转账接口(Transfer API) | 拆红包 | 从资金管理账户转账到领取方账户。 |
退款接口(Refund API) | 到期退款(24小时) | 未领取金额从资金管理账户退回发送方。 |
资金流水接口(Fund Flow API) | 查询/审计 | 查询交易记录,用于对账。 |
3. 资金流转图


4. 完整系统流程图

注意事项与限制
类别 | 项目 | 说明 |
通用规则 | 24 小时过期退款 | 红包发出后 24 小时内未被领取的金额,将自动退回发送方账户。 |
| 一人一次 | 每位用户只能领取同一个红包一次,系统需在拆红包前做校验。 |
| 状态一致性 | 通过消息修改接口保持红包状态在所有客户端的同步。 |
群红包专项 | 最大领取人数 | 群红包最多支持 300 人领取(受消息扩展数据容量限制)。 |
| 并发领取 | 红包系统必须安全处理并发拆红包请求,建议使用分布式锁或原子操作,防止超发。 |
| 随机金额算法 | 拼手气模式下,需确保公平性并保证每个红包有最低金额。常用方案为"二倍均值"算法。 |
| 手气最佳 | 领取金额最高者在详情页标注"手气最佳"标签。 |
异常处理 | 扣款失败 | 不创建红包、不发送消息,提示发送方余额不足。 |
| 转账失败 | 重试转账操作。若持续失败,标记为待人工对账,不更新消息状态。 |
| 消息修改失败 | 使用指数退避策略重试。红包状态可能短暂不一致,但资金已完成处理。 |
| 拆红包网络超时 | 采用幂等设计 —— 同一 packet_id + userid 的拆红包请求应返回相同结果。 |
安全实践 | 服务端校验 | 所有红包操作(发送、拆开、查询)必须在服务端校验,不能信任客户端提交的金额。 |
| 幂等性设计 | 使用 packet_id + userid 作为拆红包操作的幂等键。 |
| 限频控制 | 对拆红包请求实施限频策略,防止恶意刷取。 |
| 审计日志 | 记录所有支付操作(扣款、转账、退款)的日志,用于对账和争议处理。 |