发送红包

最近更新时间:2026-03-13 17:26:08

我的收藏

概述

本文档介绍了基于腾讯云 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. 消息扩展数据(领取记录)

利用 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 定位消息。
要使消息支持扩展数据,发送消息时须设置 SupportMessageExtension1
能力
用途(红包场景)
单聊定位参数
群聊定位参数
消息变更
更新红包状态(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
红包系统服务端通过 发送单聊消息 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"
}
]
}

通过 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 增加 quantitytotalrandom_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 APIstatus 更新为 "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 通知发送方(群红包):

群红包使用 发送群聊消息 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 作为拆红包操作的幂等键。
限频控制
对拆红包请求实施限频策略,防止恶意刷取。
审计日志
记录所有支付操作(扣款、转账、退款)的日志,用于对账和争议处理。