一、概述
·一对一红包:在单聊中向一个用户发送红包。
·群红包:在群聊中发送红包,群内多名成员可参与领取。
方案核心思路是利用腾讯云 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. 消息扩展数据(领取记录)
利用 IM 的 消息扩展数据(Message Extension Data)存储红包的领取列表:
| 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?
注意:红包涉及资金操作,不支持重发。若由客户端发送红包消息,扣款成功后可能遇到两种情况:
注意:抢红包是高并发场景。若由客户端发起 modify 请求,A、B、C 三人同时拆红包,各自的 modify 调用无法保证执行顺序:可能 C 先将状态改为 "None left",随后 A 的请求才到达又把状态覆盖回 "Opened",造成状态回退。此外,多端并发修改还会触发版本冲突,导致修改失败。由服务端串行处理状态变更,确保 Unopened → Opened → None left 的状态流转严格有序、不可逆。
注意: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。
红包系统服务端通过 发送单聊消息 REST API 发送红包自定义消息。请求示例如下:
// 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 修改红包消息状态:
红包系统服务端通过 消息变更 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 保存领取记录:
红包系统服务端通过 设置消息扩展 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" } ]}
20、通过 REST API 通知发送方:
通过腾讯云 IM 的服务端 REST API 向发送方推送领取通知消息:
·一对一红包:使用 发送单聊消息 REST API。
·群红包:使用 发送群聊消息 REST API。
21、通知消息示例:
·"Anson 领取了你的红包"
22、4. 红包详情页
拆开后进入红包详情页,展示封面、发送方、祝福语、总金额及领取列表。领取列表的数据来源于消息扩展数据(Message Extension Data)。
23、数据来源
展示内容:
·封面图片、发送方信息、祝福语。
·红包总金额。
·领取列表:领取人姓名、领取金额、领取时间(数据来源于消息扩展数据)。
24、5. 完整聊天流程
下图展示了一对一红包从发送到领取的完整聊天流程,包括红包状态变更("已领取")以及通过 REST API 发送的领取通知消息。
26、1. 发送群红包界面
发送方选择拼手气模式(Random Amount),设置红包个数(Quantity)、总金额(Total)、祝福语和封面。底部提示"24 小时内未领取的红包将自动退回"。
27、实现步骤
1. 客户端收集:红包总金额、红包个数、祝福语、封面、是否拼手气。
2. 红包系统调用支付系统扣款接口,扣除红包总金额。
3. 红包系统服务端通过 发送群聊消息 REST API,以发送方身份(From_Account 设为发送方用户 ID)将红包作为自定义消息发送至群组。
28、示例
群红包的 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}
29、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"。
30、服务端调用修改群聊历史消息 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\\"}" } } ]}
31、服务端调用 设置群消息扩展 REST API 保存群红包领取记录(完整示例):
{ "GroupId": "group_id", "MsgSeq": 67890, "OperateType": 1, "ExtensionList": [ { "Key": "user_harvy", "Value": "1.90,1709312400" }, { "Key": "user_anson", "Value": "0.10,1709312580" } ]}
32、通过 REST API 通知发送方(群红包):
群红包使用 发送群聊消息 REST API 向群内推送领取通知:
·普通领取:"Hai 领取了你的红包"
·最后一人领取:"harvy 领取了你的红包。你发的红包已被全部领完。"
33、3. 群红包详情页
群红包详情页展示封面、发送方、祝福语、领取统计以及领取列表。金额最高者标注"手气最佳(Luckiest Draw)"。
34、数据来源
展示内容:
·封面图片、发送方信息、祝福语。
·领取统计:例如 "已领取 2 个红包,共 ¥2.00,用时 3 分钟"。
·领取列表:领取人姓名、头像、领取金额、领取时间。
·金额最高者标注"手气最佳"。
35、获取领取人信息
消息扩展数据中的 Key 为领取人的 userID。客户端获取到扩展数据中的 userID 列表后,需要调用批量拉取群成员资料接口来获取每位领取人的头像、昵称等展示信息。
·客户端:调用 getGroupMembersInfo (Java / Swift / Objective-C / C++) 方法,传入从扩展数据中提取的 userID 列表,批量获取群成员的头像(faceUrl)、昵称(nickName)等资料,用于渲染领取列表 UI。
·服务端:也可通过 获取群成员详细资料 REST API 批量拉取群成员信息。
注意:消息扩展数组最大支持300个元素,覆盖了 99%的红包场景, 如果消息扩展列表大于300,不建议继续通过消息扩展拉取详情, 此时请你直接从自己红包系统拉取该红包的领取详情。
36、4. 群聊中的完整流程
下图展示群红包从发送到全部领完的完整聊天流程,包括每次领取的通知以及最后一人领取时的"红包已被全部领完"提示。
37、5. 群红包与一对一红包的主要区别
·多人领取:同一个红包可被多名群成员领取(最多 quantity 个)。
·金额分配:拼手气模式下,红包系统随机计算每人领取的金额。
·状态流转:"Unopened"(未领取)→ "Opened"(部分领取)→ "None left"(已领完)。
·手气最佳:领取金额最高的成员在详情页标记为"手气最佳"。
39、1. 红包资金管理账户
设置专用的红包资金管理账户(Packet Fund Management Account)作为资金中转:
·发红包:从发送方账户扣款,资金暂存至资金管理账户。
·拆红包:从资金管理账户转账给领取方账户。
·退款:未被领取的金额从资金管理账户退回给发送方。
40、2. 支付系统接口
| 接口 | 调用时机 | 说明 |
| 扣款接口(Deduction API) | 发红包 | 从发送方余额扣款到资金管理账户。 |
| 转账接口(Transfer API) | 拆红包 | 从资金管理账户转账到领取方账户。 |
| 退款接口(Refund API) | 到期退款(24小时) | 未领取金额从资金管理账户退回发送方。 |
| 资金流水接口(Fund Flow API) | 查询/审计 | 查询交易记录,用于对账。 |
43、注意事项与限制
| 类别 | 项目 | 说明 |
| 通用规则 | 24 小时过期退款 | 红包发出后 24 小时内未被领取的金额,将自动退回发送方账户。 |
| | 一人一次 | 每位用户只能领取同一个红包一次,系统需在拆红包前做校验。 |
| | 状态一致性 | 通过消息修改接口保持红包状态在所有客户端的同步。 |
| 群红包专项 | 最大领取人数 | 群红包最多支持 300 人领取(受消息扩展数据容量限制)。 |
| | 并发领取 | 红包系统必须安全处理并发拆红包请求,建议使用分布式锁或原子操作,防止超发。 |
| | 随机金额算法 | 拼手气模式下,需确保公平性并保证每个红包有最低金额。常用方案为"二倍均值"算法。 |
| | 手气最佳 | 领取金额最高者在详情页标注"手气最佳"标签。 |
| 异常处理 | 扣款失败 | 不创建红包、不发送消息,提示发送方余额不足。 |
| | 转账失败 | 重试转账操作。若持续失败,标记为待人工对账,不更新消息状态。 |
| | 消息修改失败 | 使用指数退避策略重试。红包状态可能短暂不一致,但资金已完成处理。 |
| | 拆红包网络超时 | 采用幂等设计 —— 同一 packet_id + userid 的拆红包请求应返回相同结果。 |
| 安全实践 | 服务端校验 | 所有红包操作(发送、拆开、查询)必须在服务端校验,不能信任客户端提交的金额。 |
| | 幂等性设计 | 使用 packet_id + userid 作为拆红包操作的幂等键。 |
| | 限频控制 | 对拆红包请求实施限频策略,防止恶意刷取。 |
| | 审计日志 | 记录所有支付操作(扣款、转账、退款)的日志,用于对账和争议处理。 |