红包
概述
基于网易云信的即时通讯(NIM),接入金融魔方 IM 红包 SDK 实现 App 快速便捷地集成发单聊红包、发群聊红包、拆红包并查看交易记录等功能。
【注:示例中的 partnerId 为金融魔方分配给商户的唯一标识的渠道 id,对于云信 SDK 直接使用云信为客户分配的 appKey 作为 partnerId,因此不需要再配置渠道 id。】
进行如下集成之前,务必保证项目中已经导入了金融魔方红包 SDK 如若想集成钱包功能,也请集成钱包 SDK
集成准备工作
- 在 Target -> Build Phase 选项卡的 Link Binary With Libraries 中,增加以下依赖:
- 在 Info -> URL Types 中添加支付宝 URL 配置
- 在需要的地方导入头文件
#import "JRMFHeader.h"
- 在 appDelegate 中添加以下配置
- (void)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
if ([url.host isEqualToString:@"safepay"]) {
//跳转支付宝钱包进行支付,处理支付结果
[[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
if ([[resultDic objectForKey:@"resultStatus"] isEqualToString:@"9000"]) {
[JrmfPacket doActionAlipayDone];
}
}];
}
[[SPayClient sharedInstance] application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
}
- (void)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<NSString*, id> *)options
{
if ([url.host isEqualToString:@"safepay"]) {
//跳转支付宝钱包进行支付,处理支付结果
[[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
if ([[resultDic objectForKey:@"resultStatus"] isEqualToString:@"9000"]) {
[JrmfPacket doActionAlipayDone];
}
}];
}
[[SPayClient sharedInstance]application:app openURL:url options:options];
}
- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[SPayClient sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
}
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
return [[SPayClient sharedInstance] application:application handleOpenURL:url];
}
集成流程
1. 获取 token
@implementation NTESRedPacketManager
#pragma mark - NIMLoginManagerDelegate
//云信登录之后的回调方法 NIMLoginManagerDelegate
- (void)onLogin:(NIMLoginStep)step
{
switch (step)
{
case NIMLoginStepSyncOK:
{
NIMRedPacketTokenRequest *request = [[NIMRedPacketTokenRequest alloc] init];
request.type = NIMRedPacketServiceTypeJRMF;
NSString *envelopeName = @"云信红包";
BOOL isOnLine = [NTESDemoConfig sharedConfig].redPacketConfig.useOnlineEnv;
NSString *aliPaySchemeUrl = [NTESDemoConfig sharedConfig].redPacketConfig.aliPaySchemeUrl;
[[NIMSDK sharedSDK].redPacketManager fetchTokenWithRedPacketRequest:request completion:^(NSError * _Nullable error, NSString * _Nullable token) {
if (!error)
{
[JRMFSington GetPacketSington].JrmfThirdToken = token;
[JrmfPacket instanceJrmfPacketWithPartnerId:[JRMFSington GetPacketSington].JrmfPartnerId EnvelopeName:envelopeName aliPaySchemeUrl:aliPaySchemeUrl weChatSchemeUrl:nil appMothod:isOnLine];
[JrmfWalletSDK instanceJrmfWalletSDKWithPartnerId:[JRMFSington GetPacketSington].JrmfPartnerId AppMethod:isOnLine];
}
else
{
DDLogError(@"fetch red packet token error : %@",error);
}
}];
}
break;
default:
break;
}
}
2. 添加红包相关自定义消息类型
在 NTESCustomAttachmentDefines 中自定义消息枚举
typedef NS_ENUM(NSInteger,NTESCustomMessageType) {
CustomMessageTypeJanKenPon = 1, //剪子石头布
CustomMessageTypeSnapchat = 2, //阅后即焚
CustomMessageTypeChartlet = 3, //贴图表情
CustomMessageTypeWhiteboard = 4, //白板会话
CustomMessageTypeRedPacket = 5, //红包消息
CustomMessageTypeRedPacketTip = 6, //红包提示消息
};
3. 自定义消息体和视图
- 构建红包消息对象
创建一个实现 NIMCustomAttachment,NTESCusteomAttachmentInfo 协议的红包附件对象,这里命名为 NTESRedPacketAttachment
@interface NTESRedPacketAttachment : NSObject<NIMCustomAttachment,NTESCustomAttachmentInfo>
//红包 ID
@property (nonatomic, copy) NSString *redPacketId;
//红包标题
@property (nonatomic, copy) NSString *title;
//红包内容
@property (nonatomic, copy) NSString *content;
@end
@implementation NTESRedPacketAttachment
// 该方法必须实现,用于自定义消息的解码
- (NSString *)encodeAttachment {
NSDictionary *dictContent = @{
CMRedPacketTitle : self.title,
CMRedPacketContent : self.content,
CMRedPacketId : self.redPacketId
};
NSDictionary *dict = @{CMType: @(CustomMessageTypeRedPacket), CMData: dictContent};
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:0
error:nil];
return [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
}
//该方法用于返回红包消息视图的大小,开发者可自行定义
- (CGSize)contentSize:(NIMMessage *)message cellWidth:(CGFloat)width {
return CGSizeMake(249, 96);
}
//该方法返回消息内容和消息视图的边界距离
- (UIEdgeInsets)contentViewInsets:(NIMMessage *)message {
CGFloat bubblePaddingForImage = 3.f;
CGFloat bubbleArrowWidthForImage = 5.f;
if (message.isOutgoingMsg) {
return UIEdgeInsetsMake(bubblePaddingForImage,bubblePaddingForImage,bubblePaddingForImage,bubblePaddingForImage + bubbleArrowWidthForImage);
}else{
return UIEdgeInsetsMake(bubblePaddingForImage,bubblePaddingForImage + bubbleArrowWidthForImage, bubblePaddingForImage,bubblePaddingForImage);
}
}
//该方法返回与之相关联的显示消息的视图,即 NTESSessionRedPacketContentView
- (NSString *)cellContent:(NIMMessage *)message{
return @"NTESSessionRedPacketContentView";
}
@end
- 构建红包领取消息对象
@interface NTESRedPacketTipAttachment : NSObject<NIMCustomAttachment,NTESCustomAttachmentInfo>
//红包发送者的 ID
@property (nonatomic, strong) NSString * sendPacketId;
//拆红包者的 ID
@property (nonatomic, strong) NSString * openPacketId;
//红包 ID
@property (nonatomic, strong) NSString * packetId;
//是否为最后一个红包
@property (nonatomic, strong) NSString * isGetDone;
@end
【注:NTESRedPacketTipAttachment 的实现和 NTESRedPacketAttachment 相似,具体实现可以参考 demo 代码】
构建红包消息界面
通过新建气泡内容,气泡内容类需要继承 NIMSessionMessageContentView,并使用 initSessionMessageContentView 作为初始化方法;具体显示内容根据业务需要,开发者可自行排版
NSString *const NIMDemoEventNameOpenRedPacket = @"NIMDemoEventNameOpenRedPacket";
@interface NTESSessionRedPacketContentView()
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *subTitleLabel;
@property (nonatomic, strong) UILabel *descLabel;
@property (nonatomic, strong) UITapGestureRecognizer *tap;
@end
@implementation NTESSessionRedPacketContentView
- (instancetype)initSessionMessageContentView{
self = [super initSessionMessageContentView];
if (self) {
// 内容布局
}
return self;
}
//点击红包的手势处理
- (void)onTouchUpInside:(id)sender
{
if ([self.delegate respondsToSelector:@selector(onCatchEvent:)]) {
NIMKitEvent *event = [[NIMKitEvent alloc] init];
event.eventName = NIMDemoEventNameOpenRedPacket;
event.messageModel = self.model;
event.data = self;
[self.delegate onCatchEvent:event];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
BOOL outgoing = self.model.message.isOutgoingMsg;
if (outgoing)
{
//靠界面左排版
}
else
{
//靠界面右排版
}
}
- (UIImage *)chatBubbleImageForState:(UIControlState)state outgoing:(BOOL)outgoing
{
NSString *stateString = state == UIControlStateNormal? @"normal" : @"pressed";
NSString *imageName = @"icon_redpacket_";
if (outgoing)
{
//当消息头像位于右边的时候,cell 显示的内容以及返回的背景图片
}
else
{
//当消息头像位于左边的时候,cell 显示的内容以及返回的背景图片
}
return [UIImage imageNamed:imageName];
}
- 构建红包领取提示界面
static NSString *const NTESShowRedPacketDetailEvent = @"NTESShowRedPacketDetailEvent";
@interface NTESSessionRedPacketTipContentView : NIMSessionMessageContentView
@property (nonatomic,strong) M80AttributedLabel *label;
@end
//实现 NTESSessionRedPacketContentView 中相同的布局方法
//...
- (UIImage *)chatBubbleImageForState:(UIControlState)state outgoing:(BOOL)outgoing
{
NSString *name = [[[NIMKit sharedKit] resourceBundleName] stringByAppendingPathComponent:@"icon_session_time_bg"];
UIEdgeInsets insets = UIEdgeInsetsFromString(@"{8,20,8,20}");
return [[UIImage imageNamed:name] resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeStretch];
}
构建自定义消息气泡布局配置
配置需要实现 NIMCellLayoutConfig 协议。这里除了自定义消息外,其他消息沿用内置配置,所以配置类继承基类 NIMCellLayoutConfig。 demo 里已经实现了该协议的方法,我们只需要在方法内添加红包相关的配置即可
1)在 init 方法的 type 里添加自定义消息对象
@implementation NTESCellLayoutConfig
- (instancetype)init
{
if (self = [super init])
{
_types = @[
//...
@"NTESRedPacketAttachment",
@"NTESRedPacketTipAttachment"
];
_sessionCustomconfig = [[NTESSessionCustomContentConfig alloc] init];
_chatroomTextConfig = [[NTESChatroomTextContentConfig alloc] init];
_chatroomRobotConfig = [[NTESChatroomRobotContentConfig alloc] init];
}
return self;
}
2)当显示的消息是领取红包的提示信息的时候不显示头像和昵称
- (BOOL)shouldShowAvatar:(NIMMessageModel *)model
{
//...
if ([self isRedpacketTip:model.message]) {
return NO;
}
return [super shouldShowAvatar:model];
}
- (BOOL)shouldShowNickName:(NIMMessageModel *)model
{
//...
if ([self isRedpacketTip:model.message]) {
return NO;
}
return [super shouldShowNickName:model];
}
//不显示红包领取的头像和昵称
- (BOOL)isRedpacketTip:(NIMMessage *)message
{
if (message.messageType == NIMMessageTypeCustom) {
NIMCustomObject *object = message.messageObject;
if ([object.attachment isKindOfClass:[NTESRedPacketTipAttachment class]]) {
return YES;
}
}
return NO;
}
- 注入组件
将创建好的布局配置类注入到组件中,保证在会话页实例化之前注入即可
@implementation NTESAppDelegate
//...
//注入 NIMKit 自定义排版配置
[[NIMKit sharedKit] registerLayoutConfig:[NTESCellLayoutConfig new]];
//...
@end
- 附加按钮面板添加发送红包按钮
@implementation NTESSessionConfig
- (NSArray *)mediaItems
{
//...
NIMMediaItem *redPacket = [NIMMediaItem item:@"onTapMediaItemRedPacket:"
normalImage:[UIImage imageNamed:@"icon_redpacket_normal"]
selectedImage:[UIImage imageNamed:@"icon_redpacket_pressed"]
title:@"红包"];
//...
//将 redPacket 按钮添加到数组 items 中,并将 items 添加 defaultMediaItems 中返回
return [defaultMediaItems arrayByAddingObjectsFromArray:items];
}
- 定义红包消息发送方法
@implementation NTESSessionMsgConverter
//发送红包
+ (NIMMessage *)msgWithRedPacket:(NTESRedPacketAttachment *)attachment
{
NIMMessage *message = [[NIMMessage alloc] init];
NIMCustomObject *customObject = [[NIMCustomObject alloc] init];
customObject.attachment = attachment;
message.messageObject = customObject;
message.apnsContent = @"发来了一个红包";
NIMMessageSetting *setting = [[NIMMessageSetting alloc] init];
setting.historyEnabled = NO;
message.setting = setting;
return message;
}
//领红包后的提示
+ (NIMMessage *)msgWithRedPacketTip:(NTESRedPacketTipAttachment *)attachment
{
NIMMessage *message = [[NIMMessage alloc] init];
NIMCustomObject *customObject = [[NIMCustomObject alloc] init];
customObject.attachment = attachment;
message.messageObject = customObject;
NIMMessageSetting *setting = [[NIMMessageSetting alloc] init];
setting.apnsEnabled = NO;
setting.shouldBeCounted = NO;
setting.historyEnabled = NO;
message.setting = setting;
return message;
}
4. 消息发送及消息事件处理
- 在 NTESSessionViewController 实现消息发送,并在 NTESRedPacketManager 中封装金融魔方的相关回调接口
@implementation NTESSessionViewController
//附加按钮面板
- (void)onTapMediaItemRedPacket:(NIMMediaItem *)item
{
//调用红包发送接口,这里统一封装相关接口到 NTESRedPacketManager 类中
[[NTESRedPacketManager sharedManager] sendRedPacket:self.session];
}
//调用红包发送的回调方法(金融魔方 SDK 提供)
@implementation NTESRedPacketManager
#pragma mark - jrmfManagerDelegate
- (void)dojrmfActionDidSendEnvelopedWithID:(NSString *)envId Name:(NSString *)envName Message:(NSString *)envMsg Stat:(jrmfSendStatus)jrmfStat
{
switch (jrmfStat) {
case kjrmfStatUnknow:
break;
case kjrmfStatSucess:
{
NTESRedPacketAttachment *attachment = [[NTESRedPacketAttachment alloc] init];
attachment.title = envName;
attachment.redPacketId = envId;
attachment.content = envMsg;
NIMMessage *message = [NTESSessionMsgConverter msgWithRedPacket:attachment];
[[NIMSDK sharedSDK].chatManager sendMessage:message toSession:_currentSession error:nil];
}
case kjrmfStatCancel:
//取消成功都重置会话
_currentSession = nil;
default:
break;
}
}
- 消息点击事件的处理
@implementation NTESSessionViewController
- (BOOL)onTapCell:(NIMKitEvent *)event
{
BOOL handled = [super onTapCell:event];
NSString *eventName = event.eventName;
//拆红包
if([eventName isEqualToString:NIMDemoEventNameOpenRedPacket])
{
NIMCustomObject *object = event.messageModel.message.messageObject;
NTESRedPacketAttachment *attachment = (NTESRedPacketAttachment *)object.attachment;
//拆红包相关接口
[[NTESRedPacketManager sharedManager] openRedPacket:attachment.redPacketId from:event.messageModel.message.from session:self.session];
handled = YES;
}
else if([eventName isEqualToString:NTESShowRedPacketDetailEvent])
{
NIMCustomObject *object = event.messageModel.message.messageObject;
NTESRedPacketTipAttachment *attachment = (NTESRedPacketTipAttachment *)object.attachment;
//调用展示详情页面的接口
[[NTESRedPacketManager sharedManager] showRedPacketDetail:attachment.packetId];
handled = YES;
}
if (!handled) {
NSAssert(0, @"invalid event");
}
return handled;
}
- 成功领取红包后的回调
@implementation NTESRedPacketManager
#pragma mark - jrmfManagerDelegate
- (void)dojrmfActionOpenPacketSuccessWithGetDone:(BOOL)isDone
{
NTESRedPacketTipAttachment *attachment = [[NTESRedPacketTipAttachment alloc] init];
attachment.isGetDone = @(isDone).description;
attachment.openPacketId = [[NIMSDK sharedSDK].loginManager currentAccount];
attachment.packetId = _currentRedpacketId;
attachment.sendPacketId = _currentRedpacketFrom;
[[NIMSDK sharedSDK].chatManager sendMessage:[NTESSessionMsgConverter msgWithRedPacketTip:attachment] toSession:_currentSession error:nil];
_currentSession = nil;
_currentRedpacketId = nil;
_currentRedpacketFrom = nil;
}
5. 消息接收
自定义消息反序列化
接收消息,需要进行接收内容的反序列化,使用 NIMKi 组件里的 NIMCustomAttachmentCoding 协议支持自定义消息的反序列化,在云信 demo 中已经提供 NTESCustomAttachmentDecoder 类,开发者只需要实现里面相关的消息解码即可
- (id<NIMCustomAttachment>)decodeAttachment:(NSString *)content
{
id<NIMCustomAttachment> attachment = nil;
NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
if (data) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
if ([dict isKindOfClass:[NSDictionary class]])
{
NSInteger type = [dict jsonInteger:CMType];
NSDictionary *data = [dict jsonDict:CMData];
switch (type) {
//在当前方法中添加红包消息的解码信息
case CustomMessageTypeRedPacket:
{
attachment = [[NTESRedPacketAttachment alloc] init];
((NTESRedPacketAttachment *)attachment).title = [data jsonString:CMRedPacketTitle];
((NTESRedPacketAttachment *)attachment).content = [data jsonString:CMRedPacketContent];
((NTESRedPacketAttachment *)attachment).redPacketId = [data jsonString:CMRedPacketId];
}
break;
//同理,添加红包接受的解码信息
case CustomMessageTypeRedPacketTip:
{
attachment = [[NTESRedPacketTipAttachment alloc] init];
((NTESRedPacketTipAttachment *)attachment).sendPacketId = [data jsonString:CMRedPacketSendId];
((NTESRedPacketTipAttachment *)attachment).packetId = [data jsonString:CMRedPacketId];
((NTESRedPacketTipAttachment *)attachment).isGetDone = [data jsonString:CMRedPacketDone];
((NTESRedPacketTipAttachment *)attachment).openPacketId = [data jsonString:CMRedPacketOpenId];
}
break;
default:
break;
}
attachment = [self checkAttachment:attachment] ? attachment : nil;
}
}
return attachment;
}
- 反序列化工具的注入
@implementation NTESAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//...
//注册自定义消息的解析器
[NIMCustomObject registerCustomDecoder:[NTESCustomAttachmentDecoder new]];
//...
}
- 部分消息拦截
群组中的红包领取信息,如果不是红包发送者或者是领取者,则需要进行红包消息拦截处理
@implementation NTESNotificationCenter
#pragma mark - NIMChatManagerDelegate
- (void)onRecvMessages:(NSArray *)recvMessages
{
//首先对于消息进行过滤,接下来再显示需要显示的消息
NSArray *messages = [self filterMessages:recvMessages];
if (messages.count)
{
static BOOL isPlaying = NO;
if (isPlaying) {
return;
}
isPlaying = YES;
[self playMessageAudioTip];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
isPlaying = NO;
});
[self checkMessageAt:messages];
}
}
- (NSArray *)filterMessages:(NSArray *)messages
{
NSMutableArray *array = [[NSMutableArray alloc] init];
for (NIMMessage *message in messages)
{
if ([self checkRedPacketTip:message] && ![self canSaveMessageRedPacketTip:message])
{
[[NIMSDK sharedSDK].conversationManager deleteMessage:message];
[self.currentSessionViewController uiDeleteMessage:message];
continue;
}
[array addObject:message];
}
return [NSArray arrayWithArray:array];
}
//需要记得添加 delegate
- (instancetype)init {
self = [super init];
if(self) {
//...
[[NIMSDK sharedSDK].chatManager addDelegate:self];
//...
}
return self;
}
6. 消息列表最近消息展示
在消息列表的对应会话中显示最近消息,云信 demo 中在 NTESSessionListViewController 中进行操作
@implementation NTESSessionListViewController
- (NSAttributedString *)contentForRecentSession:(NIMRecentSession *)recent{
//根据 obeject.attachment 的类型来确定展示的信息
if ([object.attachment isKindOfClass:[NTESRedPacketAttachment class]])
{
text = @"[红包消息]";
}
else if ([object.attachment isKindOfClass:[NTESRedPacketTipAttachment class]])
{
NTESRedPacketTipAttachment *attach = (NTESRedPacketTipAttachment *)object.attachment;
text = attach.formatedMessage;
}
return attContent;
}
7. 设置页面展示 "我的钱包"
- 首先确保第一步 token 已经获取
- 在设置界面添加“我的钱包”
- (void)buildData {
//...
NSArray *data = @[
@{
HeaderTitle:@"",
RowContent :@[
@{
Title : @"我的钱包",
CellAction : @"onTouchMyWallet:",
ShowAccessory : @(YES),
},
],
},
];
//...
}
//点击“我的钱包”事件
- (void)onTouchMyWallet:(id)sender
{
JrmfWalletSDK * jrmf = [[JrmfWalletSDK alloc] init];
NSString *userId = [[NIMSDK sharedSDK].loginManager currentAccount];
NIMKitInfo *userInfo = [[NIMKit sharedKit] infoByUser:userId option:nil];
//展示我的钱包页面
[jrmf doPresentJrmfWalletPageWithBaseViewController:self userId:userId userName:userInfo.showName userHeadLink:userInfo.avatarUrlString thirdToken:[JRMFSington GetPacketSington].JrmfThirdToken];
}
8. 用户基本信息更新
在云信 Demo 中更新用户信息,并不会实时更新红包中的用户基本信息,需要手动调用更新接口
- (void)updateUserInfo
{
NSString *me = [[NIMSDK sharedSDK].loginManager currentAccount];
NSString *nickName = [NTESSessionUtil showNick:me inSession:nil];
NSString *headUrl = [[NIMKit sharedKit] infoByUser:me option:nil].avatarUrlString;
//需要手动调用金融魔方提供的该接口
[JrmfPacket updateUserMsgWithUserId:me userName:nickName userHead:headUrl thirdToken:[JRMFSington GetPacketSington].JrmfThirdToken completion:^(NSError *error, NSDictionary *resultDic) {
DDLogInfo(@"red packet update user info complete, error : %@",error);
}];
}