前言:
支付,分享,第三方登录,即时通讯这些在现在的APP当中慢慢的变成了最基本,当这些技术已经慢慢的融入到人们的生活中的时候,这些也就成了APP最基本的需求,以前这部分的内容也是经常做,但就是没有好好的总结过,首先说说这几个点,支付的话我们就说说支付宝和微信吧,把我看到的收藏的好点的文章也全都分享出来,第三方登录和分享我用的一直都是友盟的,不为别的,只是友盟的统计和第三方登录分享这些都是一套SDK,就直接用友盟,友盟和微信的回调这里也有一些我们注意的,下面也会和大家说。
微信和支付宝:
其实这些支付麻烦的地方就是这些签名的操作,你要是把这些放在前端App的话的确是不安全的,所以这些最好都放在服务端,这些放在服务端其实你所要写的代码,估计也就十多行了!就像下面一样,下面方法是一个完整的微信请求后台操作,里面的参数你可以不用管,你看一下这个过程,你把后台需要的参数传给后台,你取到后台返回给你的信息,你再把这些信息给微信就OK,可我想说的并不是建议你粘贴这十多行代码就往事大吉了,想说的是希望把这整个过程和当中我们需要注意的一些地方给大家说说;
注意:这里说一点,如果你APP的微信支付功能审核的时候,你是先在微信平台添加了功能再提交申请的,可审核没通过,后来纠结几次才过了的。你在APP当中使用支付功能的时候,先在微信平台APP 的移动支付功能先删除再添加一次!不然它永远都是用不了!
// 调用微信支付 [MBPSecondary showMBProgressHUDWithText:@"请稍等..." ToView:self.view]; [WXPayViewModel RequestWXpayWithBody:@"明星私募网微信支付" andJsonString:bodyString andTotalfee:priceString withRequestSuccess:^(NSDictionary *payDictionary) { [MBPSecondary hideMBProgressHUDWith:self.view]; NSMutableString * stamp = [payDictionary objectForKey:@"timestamp"]; //调起微信支付 PayReq* request = [[PayReq alloc] init]; request.openID = [payDictionary objectForKey:@"appid"]; request.partnerId = [payDictionary objectForKey:@"mch_id"]; request.prepayId = [payDictionary objectForKey:@"prepay_id"]; request.nonceStr = [payDictionary objectForKey:@"nonce_str"]; request.timeStamp = stamp.intValue; request.package = @"Sign=WXPay"; // 这个参数可以直接写死 request.sign = [payDictionary objectForKey:@"sign"]; //微信支付 [WXApi sendReq:request]; } andRequestFail:^(NSString *message) { [MBPSecondary hideMBProgressHUDWith:self.view]; [MBPSecondary showMBProgressHUDWithText:message andType:FAILED]; }];
我们先看看微信的整个支付的流程图:
这里需要注意的几个点:
一:签名过程不要放在前端
二:返回结果最好去服务端验证,你要回调之后直接使用。
下面是我们在本地测试微信支付的时候的源码,这个过程就是把服务端做的事放在了我们移动前端,你要想试试我们自己在本地做微信支付,不经过服务端的话里面有些文件比如XML格式数据转字典,MD5加密这些文件你要需要的可以加我Q我发给你!。
#pragma mark - 微信支付相关测试方法 - (void)weixinChooseAct { NSString *appid,*mch_id,*nonce_str,*sign,*body,*out_trade_no,*total_fee,*spbill_create_ip,*notify_url,*trade_type,*partner,*attach; //应用APPID appid = WX_AppID; attach = @"test"; //微信支付商户号 mch_id = MCH_ID; //产生随机字符串,这里最好使用和安卓端一致的生成逻辑 nonce_str = [self generateTradeNO]; body = @"test"; //随机产生订单号用于测试,正式使用请换成你从自己服务器获取的订单号 out_trade_no = [self generateTradeNO]; //交易价格1表示0.01元,10表示0.1元 total_fee = @"1"; //获取本机IP地址,请再wifi环境下测试,否则获取的ip地址为error,正确格式应该是8.8.8.8 spbill_create_ip = [getIPhoneIP getIPAddress]; //spbill_create_ip = @"0.0.0.1"; //交易结果通知网站此处用于测试,随意填写,正式使用时填写正确网站 notify_url = @"http://120.76.214.93:8080/mxsm_app_api/common/zfb/callbacks.do"; trade_type = @"APP"; //商户密钥 partner = WX_PartnerKey; //获取sign签名 DataMD5 * data = [[DataMD5 alloc] initWithAppid:appid mch_id:mch_id nonce_str:nonce_str partner_id:partner body:body out_trade_no:out_trade_no total_fee:total_fee spbill_create_ip:spbill_create_ip notify_url:notify_url trade_type:trade_type]; // sign = [data getSignForMD5]; //设置参数并转化成xml格式 NSMutableDictionary * dic = [NSMutableDictionary dictionary]; [dic setValue:appid forKey:@"appid"]; //公众账号ID [dic setValue:mch_id forKey:@"mch_id"]; //商户号 [dic setValue:nonce_str forKey:@"nonce_str"]; //随机字符串 [dic setValue:body forKey:@"body"]; //商品描述 [dic setValue:out_trade_no forKey:@"out_trade_no"]; //订单号 [dic setValue:total_fee forKey:@"total_fee"]; //金额 [dic setValue:spbill_create_ip forKey:@"spbill_create_ip"];//终端IP [dic setValue:notify_url forKey:@"notify_url"]; //通知地址 [dic setValue:trade_type forKey:@"trade_type"]; //交易类型 [dic setValue:sign forKey:@"sign"]; //签名 // 转换成xml字符串 NSString * string = [dic XMLString]; [self http:string]; } #pragma mark - 拿到转换好的xml发送请求 - (void)http:(NSString *)xml { AFHTTPSessionManager * manager = [AFHTTPSessionManager manager]; // 这里传入的xml字符串只是形似xml,但是不是正确是xml格式,需要使用af方法进行转义 manager.responseSerializer = [[AFHTTPResponseSerializer alloc] init]; [manager.requestSerializer setValue: @"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; [manager.requestSerializer setValue: @"https://api.mch.weixin.qq.com/pay/unifiedorder" forHTTPHeaderField:@"SOAPAction"]; [manager.requestSerializer setQueryStringSerializationWithBlock:^NSString *(NSURLRequest *request, NSDictionary *parameters, NSError *__autoreleasing *error) { return xml; }]; // post 请求微信 [manager POST:@"https://api.mch.weixin.qq.com/pay/unifiedorder" parameters:xml progress:^(NSProgress * _Nonnull uploadProgress) { } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSString *responseString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] ; // 将微信返回的xml数据解析转义成字典 NSDictionary *dic = [NSDictionary dictionaryWithXMLString:responseString]; // 判断返回的许可 if ([[dic objectForKey:@"result_code"] isEqualToString:@"SUCCESS"] && [[dic objectForKey:@"return_code"] isEqualToString:@"SUCCESS"]) { // 发起微信支付,设置参数 PayReq *request = [[PayReq alloc] init]; request.openID = [dic objectForKey:@"appid"]; request.partnerId = [dic objectForKey:@"mch_id"]; request.prepayId = [dic objectForKey:@"prepay_id"]; request.package = @"Sign=WXPay"; request.nonceStr = [dic objectForKey:@"nonce_str"]; // 将当前事件转化成时间戳 NSDate *datenow = [NSDate date]; NSString *timeSp = [NSString stringWithFormat:@"%ld", (long)[datenow timeIntervalSince1970]]; UInt32 timeStamp = [timeSp intValue]; request.timeStamp = timeStamp; // 签名加密 DataMD5 *md5 = [[DataMD5 alloc] init]; request.sign = [md5 createMD5SingForPay:request.openID partnerid:request.partnerId prepayid:request.prepayId package:request.package noncestr:request.nonceStr timestamp:request.timeStamp]; // 调用微信 [WXApi sendReq:request]; }else{ // NSSLog(@"参数不正确,请检查参数"); } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { // NSSLog(@"error is %@",error); }]; } #pragma mark - 产生随机订单号 -(NSString *)generateTradeNO{ static int kNumber = 15; NSString *sourceStr = @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; NSMutableString *resultStr = [[NSMutableString alloc] init]; srand((unsigned)time(0)); for (int i = 0; i < kNumber; i++){ unsigned index = rand() % [sourceStr length]; NSString *oneStr = [sourceStr substringWithRange:NSMakeRange(index, 1)]; [resultStr appendString:oneStr]; } return resultStr; }
上面就是我们说的微信的,再说说这个支付宝的,我们先看看支付宝的一个流程图:
支付宝注意点:
一:如果你是在SDK的接入过程当中有问题的话你可以看看这篇博客 iOS接入支付宝支付(小白都能看懂的支付宝支付)
二:支付宝的一些注意事项和微信的有点相同,该放服务端的放在服务端
三:检测配置好你的公钥和私钥,你可以利用支付宝给的检测工具进行一下验证!确保他们没问题再上传!这一点很重要。
下面是可以本地测试源码,给大家看看:
//**** 重要说明 **** //这里只是为了方便直接展示支付宝的整个支付流程;所以Demo中加签过程直接放在客户端完成; //真实App里,privateKey等数据严禁放在客户端,加签过程务必要放在服务端完成; //防止商户私密数据泄露,造成不必要的资金损失,及面临各种安全风险; /*============================================================================*/ /*=============================支付宝流程测试================*/ -(void)doAlipayPay{ NSString *appID = @""; // AppId NSString *privateKey = @""; // 用户私钥 //partner和seller获取失败,提示 if ([appID length] == 0 ||[privateKey length] == 0){ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"缺少appId或者私钥。" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil]; [alert show]; return; } //生成订单信息及签名 //将商品信息赋予AlixPayOrder的成员变量 Order * order = [Order new]; // NOTE: app_id设置 order.app_id = appID; // NOTE: 支付接口名称 order.method = @"alipay.trade.app.pay"; // NOTE: 参数编码格式 order.charset = @"utf-8"; // NOTE: 当前时间点 NSDateFormatter* formatter = [NSDateFormatter new]; [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; order.timestamp = [formatter stringFromDate:[NSDate date]]; // NOTE: 支付版本,固定格式,暂时不需要修改 order.version = @"1.0"; // NOTE: sign_type设置 order.sign_type = @"RSA"; // NOTE: 商品数据 order.biz_content = [BizContent new]; order.biz_content.body = @"我是测试数据"; order.biz_content.subject = @"1"; order.biz_content.out_trade_no = [self generateTradeNO]; //订单ID(由商家自行制定) order.biz_content.timeout_express = @"30m"; //超时时间设置 order.biz_content.total_amount = [NSString stringWithFormat:@"%.2f", 0.01]; //商品价格 //将商品信息拼接成字符串 NSString *orderInfo = [order orderInfoEncoded:NO]; NSString *orderInfoEncoded = [order orderInfoEncoded:YES]; NSLog(@"orderSpec = %@",orderInfo); // NOTE: 获取私钥并将商户信息签名,外部商户的加签过程请务必放在服务端,防止公私钥数据泄露; // 需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode id<DataSigner> signer = CreateRSADataSigner(privateKey); NSString *signedString = [signer signString:orderInfo]; // NOTE: 如果加签成功,则继续执行支付 if (signedString != nil) { //应用注册scheme,在AliSDKDemo-Info.plist定义URL types NSString *appScheme = @"alisdkdemo"; // NOTE: 将签名成功字符串格式化为订单字符串,请严格按照该格式 NSString *orderString = [NSString stringWithFormat:@"%@&sign=%@", orderInfoEncoded, signedString]; // NOTE: 调用支付结果开始支付 [[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) { NSLog(@"reslut = %@",resultDic); }]; } } // 随机产生一个订单号 - (NSString *)generateTradeNO { static int kNumber = 15; NSString *sourceStr = @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; NSMutableString *resultStr = [[NSMutableString alloc] init]; srand((unsigned)time(0)); for (int i = 0; i < kNumber; i++){ unsigned index = rand() % [sourceStr length]; NSString *oneStr = [sourceStr substringWithRange:NSMakeRange(index, 1)]; [resultStr appendString:oneStr]; } return resultStr; }
再说说这篇博客的重点,支付宝、微信和友盟在一起时候的回调!
说说微信和友盟的注意事项,注意下面代理中的注释,那些注意的点全都加载代码的注释里面了;
/****** 支付和第三方登录分享回调注意事项 还有方法不过这个方法的系统版本最低要求是9.0 (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options // NOTE:微信回调在9.0之后还是会进入这里的,但这里就有一个微信回调和友盟回调冲突的问题,按照最开始这样写回调的话会造成微信支付成功不回调的现象,主要原因是微信支付,通过友盟判断,返回的结果是YES BOOL UMShareResult = [[UMSocialManager defaultManager] handleOpenURL:url]; 返回的结果示例: (lldb) po url 微信支付 wx47e8322e53c76163://pay/?returnKey=&ret=0 (lldb) po url 第三方登录 wx47e8322e53c76163://oauth?code=001J9HPU1SYnGS05xpOU1hpHPU1J9HPS&lang=zh_HK&country=CN&state= (lldb) po url 微信分享 wx47e8322e53c76163://platformId=wechat // NOTE:错误方法如下 -(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{ // 友盟SDK的回调 BOOL UMShareResult = [[UMSocialManager defaultManager] handleOpenURL:url]; if (!UMShareResult) { // 其他如支付等SDK的回调 // 微信支付SDK回调 BOOL WXPauResult = [WXApi handleOpenURL:url delegate:self]; if (!WXPauResult) { //不是微信支付回调,那就是支付宝 //跳转支付宝钱包进行支付,处理支付结果 if ([url.host isEqualToString:@"safepay"]) { [[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) { if ([resultDic[@"resultStatus"] isEqualToString:@"9000"]) { // 支付宝支付成功 } }]; return YES; } } return WXPauResult; } return UMShareResult; } END *****/ // APP跳转完之后的回调,这个回调接口是所有的系统类型都适用的 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{ // 首先判断是不是微信支付的回调 // NOTE:上面的回调示例可以看住出,微信的支付回调就是按下面格式开头的,前面是APPId,判断URL是不是这个开头 if ([[url absoluteString]hasPrefix:@"wx47e8322e53c76163://pay"]) { BOOL WXPauResult = [WXApi handleOpenURL:url delegate:self]; return WXPauResult; }else{ // 友盟SDK的回调 BOOL UMShareResult = [[UMSocialManager defaultManager] handleOpenURL:url]; if (!UMShareResult) { //跳转支付宝钱包进行支付,处理支付结果 if ([url.host isEqualToString:@"safepay"]) { [[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) { //NSLog(@"result = %@",resultDic); if ([resultDic[@"resultStatus"] isEqualToString:@"9000"]) { // 后台验证支付结果 [[NSNotificationCenter defaultCenter]postNotificationName:ALIPAYRESULT object:nil userInfo:@{@"result":resultDic[@"result"]}]; } }]; return YES; } } return UMShareResult; } }
最后说说支付宝回调的注意事项:
支付宝和微信相比,支付宝要是客户没有安装APP它是可以调用网页版本的,微信却不行!所以这里要特别注意支付宝网页版本的回调。在支付宝的SDK里面,有这两连个方法大家注意:
/** * 支付接口 * * @param orderStr 订单信息 * @param schemeStr 调用支付的app注册在info.plist中的scheme * @param compltionBlock 支付结果回调Block,用于wap支付结果回调(非跳转钱包支付) */ - (void)payOrder:(NSString *)orderStr fromScheme:(NSString *)schemeStr callback:(CompletionBlock)completionBlock; /** * 处理钱包或者独立快捷app支付跳回商户app携带的支付结果Url * * @param resultUrl 支付结果url * @param completionBlock 支付结果回调 */ - (void)processOrderWithPaymentResult:(NSURL *)resultUrl standbyCallback:(CompletionBlock)completionBlock;
注意点:
一:支付宝网页版本的回调就是在支付接口completionBlock里面回调的,不会去SDK里面的 processOrderWithPaymentResult 方法回调。
二:App通过支付宝App支付完成之后的回调缺恰恰是在 processOrderWithPaymentResult 里面。