[iOS]微信支付接入详解
文章涉及的demo在Github LQThirdParty, 欢迎Star | Fork
微信支付,在许多需要支付的APP中,几乎是必须要集成的,使用微信支付,也渐渐成为用户的习惯,笔者花了一些时间整理了一下微信支付的流程,在这里分享一下,也是对自己学习的总结.
一. 前期准备
- 到微信的开放平台注册账号:https://open.weixin.qq.com;
- 进入管理中心-->移动应用-->创建移动应用;根据页面提示完善应用资料;
- 审核通过后,进入应用详情页,查看应用详情,这里可以查看AppID和AppSecret以及一些接口信息;
应用创建时,是没有支付能力的,需要额外申请,具体的申请过程,根据网页提示,一步步完善资料,具体资料就向你公司的相关人员索取吧,提交审核;审核通过后,微信平台会给你填写审核资料时预留邮箱发送一个邮件,邮件中包含了与支付能力相关的微信商户号的信息,然后到微信的商户平台:https://pay.weixin.qq.com,填写相关资料,最主要的是验证开户行,微信会向你填写的开户银行账户汇一笔钱(一般是几分钱),让你们的财务查一下,然后验证一下即可,通过后即获取了支付能力.下面就开始集成到APP中去吧...
二. 适配iOS9
在iOS9下,默认使用的是HTTPS协议,系统会拦截对HTTP协议接口的访问,因此无法获取HTTP协议接口的数据,控制台会输出如下信息:
2016-02-29 15:42:35.765 LQQWeChatDemo[3228:740276] App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
2016-02-29 15:42:35.769 LQQWeChatDemo[3228:740247] Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSErrorFailingURLStringKey=http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php, NSErrorFailingURLKey=http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection., NSUnderlyingError=0x145826d0 {Error Domain=kCFErrorDomainCFNetwork Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSErrorFailingURLKey=http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php, NSErrorFailingURLStringKey=http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.}}}
解决方案(以下方法2选1)
1. 暂时全部回退到HTTP协议
具体方法:
在项目的Info.plist中添加一个Key:NSAppTransportSecurity
,类型为字典类型。
然后给它添加一个Key:NSAllowsArbitraryLoads
,类型为Boolean
类型,值为YES
;
2.设置域,把不支持HTTPS协议的接口设置成HTTP的接口
具体方法:
设置域 各平台域1.在项目的info.plist中添加一个Key:
NSAppTransportSecurity
,类型为字典类型。
2.然后给它添加一个NSExceptionDomains
,类型为字典类型;
3.把需要的支持的域添加給NSExceptionDomains
。其中域作为Key,类型为字典类型。
4.每个域下面需要设置3个属性:
NSIncludesSubdomains
、NSExceptionRequiresForwardSecrecy
、NSExceptionAllowsInsecureHTTPLoads
。
均为Boolean类型,值分别为YES、NO、YES。
方式二设置的需要把所有不支持HTTPS的接口都设置一遍,如果项目中都是HTTP协议的接口,可使用方式一,直接回退到HTTP,就不用单个设置了.
三. 导入微信SDK
到微信开放平台下载最新的SDK,下载之后导入文件:
libWeChatSDK.a
,WXApi.h
,WXApiObject.h
到你的工程中
添加依赖库:
SystemConfiguration.framework
,liz.tbd
,libsqlite3.0.tbd
,libc++.tbd
设置URL scheme
设置URL scheme编译运行,如果报以下错误:
Undefined symbols for architecture armv7:
"_OBJC_CLASS_$_CTTelephonyNetworkInfo", referenced from:
objc-class-ref in libWeChatSDK.a(MTAHelper.o)
这是因为还需要添加库文件:CoreTelephony.framework
PS:如果项目中使用了ShareSDK,可以直接使用ShareSDK导入的微信SDK,ShareSDK是支持cocoa pods的,只需在Podfile文件中加入:
pod 'ShareSDK3'
pod 'ShareSDK3/ShareSDKPlatforms/WeChat'
使用cocoa pods的好处就不用说了...
四. 使用微信的API
在需要使用微信支付的地方导入:
#import "WXApi.h"
- 在AppDelegate中的didFinishLaunchingWithOptions方法中添加以下代码向微信注册APP:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//注册APP,
[WXApi registerApp:@"wxb4ba3c02aa476ea1"];
return YES;
}
- 然后,重写以下两个方法:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options
{
// 跳转到URL scheme中配置的地址
//NSLog(@"跳转到URL scheme中配置的地址-->%@",url);
return [WXApi handleOpenURL:url delegate:(id<WXApiDelegate>)self];
}
//支付成功时调用,回到第三方应用中
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
// NSLog(@"****************url.host -- %@",url.host);
if ([url.scheme isEqualToString:@"wx23a1f7f291ef4b3d"])
{
return [WXApi handleOpenURL:url delegate:(id<WXApiDelegate>)self];
}
return YES;
}
- 添加微信的支付结果回调方法:
//微信回调,有支付结果的时候会回调这个方法
- (void)onResp:(BaseResp *)resp
{
// 支付结果回调
if([resp isKindOfClass:[PayResp class]]){
switch (resp.errCode) {
case WXSuccess:{
//支付返回结果,实际支付结果需要去自己的服务器端查询
NSNotification *notification = [NSNotification notificationWithName:@"ORDER_PAY_NOTIFICATION" object:@"success"];
[[NSNotificationCenter defaultCenter] postNotification:notification];
break;
}
default:{
NSNotification *notification = [NSNotification notificationWithName:@"ORDER_PAY_NOTIFICATION"object:@"fail"];
[[NSNotificationCenter defaultCenter] postNotification:notification];
break;
}
}
}
}
这里微信的支付结果需要向自己的服务器查询,成功后通过发送通知的方式告诉吊起微信的控制器...
- 吊起微信
吊起微信所需的参数配置可参考官方提供的一个连接:
http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php,用于调试支付接口的参数设置;
这里所获取到的参数可以全部由服务器提供,即在服务器返回prepayId的时候,一并将吊起微信所需的参数返回:
[Networking getWithUrl:@"http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php" params:nil success:^(id response) {
NSLog(@"%@",response);
//配置调起微信支付所需要的参数
PayReq *req = [[PayReq alloc] init];
req.partnerId = [response objectForKey:@"partnerid"];
req.prepayId = [response objectForKey:@"prepayid"];
req.package = [response objectForKey:@"package"];
req.nonceStr = [response objectForKey:@"noncestr"];
req.timeStamp = [[response objectForKey:@"timestamp"]intValue];
req.sign = [response objectForKey:@"sign"];
//调起微信支付
if ([WXApi sendReq:req]) {
NSLog(@"吊起成功");
}
} fail:^(NSError *error) {
NSLog(@"%@",error);
}];
- 注册通知
在吊起微信的地方注册通知(用于接收支付结果)的时候,最好先判断一下用户是否安装了微信:
//判断是否安装微信
if([WXApi isWXAppInstalled]) {
// 监听一个通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getOrderPayResult:) name:@"ORDER_PAY_NOTIFICATION" object:nil];
}
然后实现通知方法,在这里获取支付结果,处理相关逻辑:
#pragma mark - 收到支付成功的消息后作相应的处理
- (void)getOrderPayResult:(NSNotification *)notification
{
if ([notification.object isEqualToString:@"success"]) {
NSLog(@"支付成功");
} else {
NSLog(@"支付失败");
}
}
到此,一个完整的支付流程就完成了...
附加
如果,吊起微信后,页面只显示一个确定按钮,如图所示:
吊起微信异常引起这个问题的主要原因就是吊起微信时,参数设置的不正确,而且多半是因为签名的问题,就是参数中的
sign
值的问题,在后台向微信后台请求账单的时候有过一次签名,而客户端吊起支付的签名和那个签名是不同的,多半是后台直接把那个签名发送给客户端,并没有进行二次签名,可以和后台协商,再签一次,因为两次签名的规则都一样,没必要再让客户端写一遍签名规则了.当然,客户端签名也不是不可以,Demo中也有二次签名的方法,需要自己去配置参数:
//有的服务器没有对sign字段进行二次签名,需要客户端进行,下面这些是对吊起支付时的sign字段进行二次签名的,这些操作可以和服务器协商全让服务器做了,因为签名算法都是一样的,后台已经进行了第一次的签名,第二次只是多了prePayid,算法都是一样的没必要客户端再写一次算法
//注意:下面的方法不能直接使用,这里只是给出了算法和参数配置,相应的填充数据就行
//创建package签名
-(NSString*) createMd5Sign:(NSMutableDictionary*)dict
{
NSMutableString *contentString =[NSMutableString string];
NSArray *keys = [dict allKeys];
//按字母顺序排序
NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2 options:NSNumericSearch];
}];
//拼接字符串
for (NSString *categoryId in sortedArray) {
if ( ![[dict objectForKey:categoryId] isEqualToString:@""]
&& ![categoryId isEqualToString:@"sign"]
&& ![categoryId isEqualToString:@"key"]
)
{
[contentString appendFormat:@"%@=%@&", categoryId, [dict objectForKey:categoryId]];
}
}
//添加key字段
[contentString appendFormat:@"key=%@", self.spKey];
//得到MD5 sign签名
NSString *md5Sign =[contentString MD5];
return md5Sign;
}
文章涉及的demo在Github LQThirdParty, 欢迎Star | Fork