188金宝搏iOS微信支付接入以及工具类包装,iOS集成微信支付

在刚刚结束的一个项目中用到了微信支付,从接入微信支付到工具类的封装,在本文中做个积累,方便日后使用。

刚集成完微信支付,总结总结

188金宝搏 1接入流程.png

1. 准备

微信平台分为微信公众平台和微信开放平台,公众平台是运营微信公众号的管理系统,开放平台主要针对app、网站开发,提供登录、分享、支付等功能。

注册开放平台之后,新建应用,填写应用信息(Android、iOS等信息),创建之后需要等待审核(这个审核很快的,几个小时就通过了)。

然后是为该应用申请支付功能,要注意个人是无法申请的,具体可参考微信支付申请条件和资格。这里牵涉到一系列公司资质的审核和费用支付,需要几个工作日的时间

完成之后可以获取到appid(微信开放平台为应用生成的唯一识别码)、商户id、商户secretKey。对于app端来说只用到appid,商户id最好通过接口从server获取,商户secretKey是用来签名的,一般只有server能用到。

首先你需要去微信开放平台注册账号,在这里要吐槽一下,不知道这个微信平台的账号密码验证机制,忘了以前的密码,重新登录,各种找回密码都不行,实在没辙,去注册个Gmail😑,登录成功后,可以看到如下界面。

2. 支付流程

先上一个开放平台给出的流程图:

188金宝搏 2

支付流程.png

这个图很实用很详细很清晰,但一开始看可能会觉得复杂。其实对开发者来说,比较关心的流程是:

  1. app向server发起支付请求
  2. server收到请求后向微信后台调用统一下单API,获得预付单信息
  3. server生成带签名的客户端支付信息并返回给app
  4. 用户确认支付,app调起微信客户端进行支付
  5. app获得支付结果后向server查询最终结果并显示

流程了解之后,了解下需要定义的接口和前后端的具体工作:

188金宝搏 30.jpeg

新接口:
  • app向server发起请求,获得签名后的app支付信息
  • app支付之后向server查询支付结果(微信回调的结果不可信,必须以server的结果为准)

点击右上角的创建移动应用,一步一步填写必要填入的信息,在这里就不做多余的赘述了。需要注意的是这里填入的Bundle
ID需要和项目的一一对应

app需要做的:
  • 项目接入微信支付sdk
  • 向server请求支付信息
  • 用支付信息调起微信客户端,然后支付
  • 收到微信回调之后向server查询支付结果
  • 根据支付结果展示页面

188金宝搏 422.jpeg

server需要做的:
  • 收到app端支付请求后调用统一下单API向微信后台获取预支付信息
  • 将app端需要的支付信息签名之后返回给app
  • 接收微信后台回调信息(支付结果),以供app查询

接下来就等一个星期左右,等待微信审核通过你就可以将微信支付SDK集成到项目中,详情可以看官方给出的文档.pod
集成方法

3. iOS开发

开发文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8\_1

  1. 下载并运行demo
    其实把demo看明白了,直接运用到自己的app里,也不是不可以的
  2. 设置项目
    在Xcode中,projectName-->Info-->URL Types
    添加appid。设置之后才能实现应用间跳转
![](https://upload-images.jianshu.io/upload_images/2411791-9c2cfed134475ded.png)urltypes.png

导入sdk

188金宝搏 5

sdk.png

如果使用了pod,直接在Podfile中添加pod 'WechatOpenSDK' 然后执行 pod
update即可。如果没有使用pod,在添加了sdk文件包之后,需要在pojectName-->General-->Linked
Frameworks and Libraries 中添加相应内容

188金宝搏 6

libs.png

  1. 代码
  • AppDelegate.m
    didFinishLaunchingWithOptions方法中
    [WXApi registerApp:@"wx000999888777"];//注册appid
    openUrl、handleOpenURL方法

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url { return [WXApi handleOpenURL:url delegate:[PaymentManager sharedManager]];}- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { return [WXApi handleOpenURL:url delegate:[PaymentManager sharedManager]];}// NOTE: 9.0以后使用新API接口- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options{ return [WXApi handleOpenURL:url delegate:[PaymentManager sharedManager]];}

如果之前项目中使用了友盟等第三方框架,直接并排写就可以:

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url{ [WXApi handleOpenURL:url delegate:[PaymentAction sharedPayment]]; [UMSocialSnsService handleOpenURL:url]; return YES;}

另外,这个地方的delegate也可以直接设置成nil,表示用当前AppDelegate作为微信支付的代理,这样一来支付回调写在AppDelegate.m中即可。
但不推荐这种做法,我是设置了PaymentManager作为代理,这是项目中专门管理支付的一个类,包括之前的支付宝支付。这也是微信demo中的做法。(支付宝是用block获取回调的,个人感觉代码更紧凑,不容易乱;微信是用的代理,刚开始看晕乎乎的)

  • PaymentManager
    首先是遵守协议WXApiDelegate
    然后在PayVC中通过接口获取支付信息
    然后调起微信客户端:

- (void)weiXinPayWithDic:(NSDictionary *)wechatPayDic { PayReq *req = [[PayReq alloc] init]; req.openID = [wechatPayDic objectForKey:@"appId"]; req.partnerId = [wechatPayDic objectForKey:@"partnerId"]; req.prepayId = [wechatPayDic objectForKey:@"prepayId"]; req.package = [wechatPayDic objectForKey:@"packages"]; req.nonceStr = [wechatPayDic objectForKey:@"nonceStr"]; req.timeStamp = [[wechatPayDic objectForKey:@"timesTamp"] intValue]; req.sign = [wechatPayDic objectForKey:@"sign"]; [WXApi sendReq:req];}

这里的数据wechatPayDic一定是server经过二次签名的

回调

// 微信支付返回结果回调- (void)onResp:(BaseResp *)resp { if ([resp isKindOfClass:[PayResp class]]) { PayResp *response = (PayResp *)resp; if (_delegate && [_delegate respondsToSelector:@selector(managerDidRecvPaymentResponse:)]) { [_delegate managerDidRecvPaymentResponse:response]; } }}

当然前提是在PaymentManager.h中已经定义了代理:

@protocol WXApiManagerDelegate <NSObject>@required- (void)managerDidRecvPaymentResponse:(PayResp *)response;@end@interface PaymentManager : NSObject <WXApiDelegate>@property (nonatomic, assign) id<WXApiManagerDelegate> delegate;

这个代理是用来处理回调结果,展示页面的,所以设置成PayVC控制器

处理回调结果
PayVC.m
遵守协议WXApiManagerDelegate
在viewDidLoad中设置[PaymentManager sharedManager].delegate = self;

- (void)managerDidRecvPaymentResponse:(PayResp *)response { switch (response.errCode) { case WXSuccess: [self checkWechatPayResult]; break; case WXErrCodeUserCancel: [[HintManager shareManager] showHint:@"中途取消"]; break; default:{ [[HintManager shareManager]showHint:@"支付失败"]; } break; }}

然后在checkWechatPayResult向server查询支付结果,刷新页面
哦了~~ 泼佛客特

pod 'WechatOpenSDK'

4. 出现的问题

当然了,并不泼佛客特

  • 系统版本大于等于iOS9的,调起微信客户端之后,可以直接点击状态栏左侧按钮返回,这时是不走回调方法的。
    这样在支付成功之后,不走回调方法,就无法知道支付状态,当前页面无法给出提示。
    解决方案是,在AppDelegate.m的applicationWillEnterForeground方法中,调用查询支付结果接口然后刷新当然页面。需要设置bool变量作为标志,否则每次应用进入前台都去查询,就不符合业务要求了。
  • 进入微信支付页面之后,不做操作,切换到自己应用中,退出当前支付页面,然后再进入微信客户端点击支付或者取消,此时自己的应用会崩溃闪退
    原因是退出页面后页面已经出栈被销毁,但wx回调时还是去调用其中的代理方法,就会出现野指针。
    解决方法是,在页面的viewWillDisappear方法中加入[PaymentManager sharedManager].delegate = nil;

在Xcode中,选择你的工程设置项,选中“TARGETS”一栏,在“info”标签栏的“URL
type“添加“URL scheme”为你所注册的应用程序id。

5. 需要注意的点

  • 一定设置好scheme,否则应用无法跳转
  • 在调起微信支付使用的PayReq类,一定要用WXApi中自带的类,不要自己创建新类。我之前自己创建的类,定义了同样的变量,同样继承了BaseReq,然并卵,sendReq方法一直返回false
  • 调起微信支付的数据,一定是server经过二次签名的,不要把调用统一下单API获取到的数据直接返给app
  • 虽然类中的变量命名是驼峰式,但签名时的key值全部是小写的,签名时不要忘了后面加上商户key。

188金宝搏 72.jpeg

6. 签名

可能某些server端由于这样那样的原因不愿意做二次签名,app可以自己做。(不过因为签名需要用到secretKey,让app端做签名是有风险的)
下面是签名代码:

#import <CommonCrypto/CommonDigest.h>// wechatDic中的数据是调用统一下单API之后返回的数据- (void)wxpay:(NSDictionary *)wechatDic { time_t now; time(&now); NSString *timestamp = [NSString stringWithFormat:@"%ld",now]; NSString *noncestr = [self md5:timestamp]; NSDictionary *dict = @{ @"appid":[wechatDic objectForKey:@"appId"], @"noncestr":noncestr, @"package":@"Sign=WXPay", @"partnerid":[wechatDic objectForKey:@"partnerId"], @"prepayid":[wechatDic objectForKey:@"prepayId"], @"timestamp":timestamp }; NSMutableString *contentString = [NSMutableString string]; NSArray *keys = [dict allKeys]; // 按照ASCII 码排序 NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { return [obj1 compare:obj2 options:NSNumericSearch]; }]; // 拼接字符串 for (NSString *categoryId in sortedArray) { if (![[dict objectForKey:categoryId] isEqualToString:@""]&&![[dict objectForKey:categoryId] isEqualToString:@"key"]&&![[dict objectForKey:categoryId] isEqualToString:@"sign"]) { [contentString appendFormat:@"%@=%@&",categoryId,dict[categoryId]]; } } // 添加商户key字段 NSString *secretkey = @"1K2222ILTKCH33CQ4444SI5ZNMTM66VS"; [contentString appendFormat:@"key=%@",secretkey]; // 加密 NSString *md5Sign = [self md5:contentString]; // 支付数据 PayReq *req = [[PayReq alloc] init]; req.openID = [wechatDic objectForKey:@"appId"]; req.partnerId = [wechatDic objectForKey:@"partnerId"]; req.prepayId = [wechatDic objectForKey:@"prepayId"]; req.package = @"Sign=WXPay"; req.nonceStr = noncestr; req.timeStamp = [timestamp intValue]; req.sign = md5Sign; [WXApi sendReq:req];}- (NSString *)md5:(NSString *)str { const char *cStr = [str UTF8String]; unsigned char digest[CC_MD5_DIGEST_LENGTH]; CC_MD5(cStr,(unsigned int)strlen(cStr),digest); NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2]; for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i ++) { [output appendFormat:@"%02X",digest[i]]; } return output;}

完成之后可以获取到appid(微信开放平台为应用生成的唯一识别码)、商户id、商户secretKey。对于app端来说只用到appid,商户id最好通过接口从server获取,商户secretKey是用来签名的,一般只有server能用到。

188金宝搏 8支付流程.png

刚开始看这个流程图可能会觉得很复杂,所以总结了我们比较关系的流程是:

  1. app客户端向服务器发送支付请求
  2. 服务器在收到客户端请求之后向微信后台调用统一下单API,获得预付单信息
  3. 服务端生成带签名的客户端支付信息给app
  4. app客户端用户确认支付,app唤醒微信客户端进行支付
  5. app获得支付结果后向服务端查询最终的结果并显示
app端的工作:
  • 接入微信支付SDK
  • 向服务器发送支付请求
  • 支付信息唤醒微信app,然后进行支付
  • 收到微信支付回调后向服务器确认支付结果
  • 根据查询结果展示结果页面告知用户支付结果
服务器端的工作:
  • 收到app客户端支付请求后向微信后台请求预支付订单
  • 服务器端签名并返回信息给app客户端
  • 接收微信后台返回的支付结果,用来app端查询

服务器端返回的字段说明:

appId:返回的appidpartnerId: 父级idprepayId: 支付idpackages:
包名(微信默认的为“Sign=WXPay”)nonceStr: 生成的随机字符串timesTamp:
时间戳sign: 签名

188金宝搏 93.jpeg

AppDelegate.m,导入微信SDK头文件WXApi.h

- application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {[WXApi registerApp:@"注册获得的appid"];//注册appid return YES;} //支持所有iOS系统回调- application:(UIApplication *)application openURL:url sourceApplication:(NSString *)sourceApplication annotation:annotation{// [self handleOpenURL:url]; BOOL result = [[UMSocialManager defaultManager] handleOpenURL:url sourceApplication:sourceApplication annotation:annotation]; if { // 其他如支付等SDK的回调 [self handleOpenURL:url]; } return YES;}- handleOpenURL:url { if ([url.host isEqualToString:@"pay"]) { // -- 微信支付 [WXApi handleOpenURL:url delegate:[WXPayService sharedInstance]]; }}

此处的WXPayService就是自己单独抽出来写的一个类,遵循WXApiManagerDelegate协议

WXPayService.h
#import <Foundation/Foundation.h>#import "WXApi.h"@interface WXPayService : NSObject <WXApiDelegate>///单例来接收微信请求的回调+ (instancetype)sharedInstance;// -- 根据接口返回的预支付信息,构造支付请求+ getPayRequest:(NSDictionary *)prepayData;///处理非支付请求的回调- onRespCallBack:(BaseResp * resp))callback;///从服务器端获取到微信返回的支付请求用到的参数来发起支付请求- startPayWithReq:req callback:(BaseResp * resp))callback; @end
WXPayService.m
#import "WXPayService.h"@interface WXPayService ()@property (nonatomic,copy) void(^RespCallBack)(BaseResp *);@endstatic WXPayService *sharedInstance;@implementation WXPayService+ (instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [super allocWithZone:zone]; }); return sharedInstance;}///单例来接收微信请求的回调+ (instancetype)sharedInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance;}// -- 根据接口返回的预支付信息,构造支付请求+ getPayRequest:(NSDictionary *)prepayData { if (prepayData) {// 此处Tools是自己的另一个工具类,用来判断字典的 PayReq *req = [[PayReq alloc] init]; if ([Tools dicContain:prepayData withKey:@"partnerid"]) { req.partnerId = prepayData[@"partnerid"]; } if ([Tools dicContain:prepayData withKey:@"prepayid"]) { req.prepayId = prepayData[@"prepayid"]; } if ([Tools dicContain:prepayData withKey:@"noncestr"]) { req.nonceStr = prepayData[@"noncestr"]; } if ([Tools dicContain:prepayData withKey:@"timestamp"]) { req.timeStamp = [prepayData[@"timestamp"] intValue]; } req.package =@"Sign=WXPay"; req.sign = @"null"; //日志输出 NSLog(@"appid=%@/npartid=%@/nprepayid=%@/nnoncestr=%@/ntimestamp=%ld/npackage=%@/nsign=%@",[prepayData objectForKey:@"appid"],req.partnerId,req.prepayId,req.nonceStr,req.timeStamp,req.package,req.sign); return req; } return nil;}///处理非支付请求的回调- onRespCallBack:(BaseResp * resp))callback { self.RespCallBack = callback;}///从服务器端获取到微信返回的支付请求用到的参数来发起支付请求- startPayWithReq:req callback:(BaseResp * resp))callback { NSAssert(req !=nil , @"未成功创建微信支付请求"); self.RespCallBack = callback; if ([WXApi isWXAppInstalled]) { // -- 判断是否安装微信应用 //发起微信支付,设置参数 [WXApi sendReq:req]; }else { self.RespCallBack; }}#pragma mark WXApiDelegate- onResp:(BaseResp *)resp { if ([resp isKindOfClass:[PayResp class]]) { // -- 判断是否为支付的回调 self.RespCallBack; }}@end

在需要支付的ViewController中导入工具类WXPayService

188金宝搏 104.jpeg

  • 设置好scheme,否则应用无法跳转到微信客户端
  • 支付签名时的key值全部是小写的
  • 如果支付显示验证签名失败的时候,可以将packages设为默认值(Sign=WXPay)试试

系统版本大于等于iOS9的,调起微信客户端之后,可以直接点击状态栏左侧按钮返回,这时是不走回调方法的。

解决方案:在AppDelegate.m的applicationWillEnterForeground方法中,调用查询支付结果接口然后刷新当然页面。需要设置bool变量作为标志,否则每次应用进入前台都去查询,就不符合业务要求了。

进入微信支付页面之后,不做操作,切换到自己应用中,退出当前支付页面,然后再进入微信客户端点击支付或者取消,此时自己的应用会崩溃闪退

原因:退出页面后页面已经出栈被销毁,但wx回调时还是去调用其中的代理方法,就会出现野指针。解决方案:在页面的viewWillDisappear方法中加入

[WXPayService sharedManager].delegate = nil;

微信支付签名建议和服务端协商做二次签名,以保证支付的安全性。ps:
如有不对的地方,欢迎批评指正,更多文章请点击这里

如有需要,请关注公众号JackerooChu,了解更多文章

188金宝搏 11公众号

相关文章

Comment ()
评论是一种美德,说点什么吧,否则我会恨你的。。。