下面所写的微信支付SDK和之前写的支付宝的SDK支付,都是我在项目集成时的步骤,在写文章时,我又创建了一个小的demo进行截图说明的,有什么不恰当的地方,还请各位大神指出,共同学习进步,该文章只是作为集成参考,每个公司的需求不一样,服务器返回的数据不一样,具体还是请参考官方的文档.
微信支付的前期工作和支付宝差不多,注册认证,填写申请资料,这些一般是产品做好的工作.我们关心的就是appID,apiKey和商户账号了. SDK的下载地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=11_1
- 下载了SDK后开始一步步集成微信支付了.集成SDK所需要的.
添加到项目中后直接运行下,发现没有报错.官方文档里也没看见让添加依赖文件,我的项目中因为集成了别的SDK,添加的依赖文件挺多的,也没有报错.但是在我把工具类写好后编译下就开始狂报错了,
一般需要的依赖库,请根据具体情况添加
SystemConfiguration.framework
libz.tbd
libsqlite3.0.tbd
CoreTelephony.framework
libc++.tbd
添加依赖库的截图
- 完事后可以设置项目的APPID,请看截图
- 设置白名单
// 把info.plist文件 source code打开,把下面这段话直接复制进去就可以了
// 说明, 如果在项目中没有做是否能打开微信的判断,不加白名单也是可以的,但是这样不够严谨.
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>wechat</string>
</array>
// 不加白名单做微信是否能够打开的判断在控制台会报警告
-canOpenURL: failed for URL: "weixin://app/wxfc88431fb68d2797/" - error: "This app is not allowed to query for scheme weixin"
- 基本工作已经准备完毕,现在开始集成代码.
- 1.在AppDelegate里注册
//导入头文件:
#import "WXApi.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 注册
[WXApi registerApp:@"你们公司的appID"];
return YES;
}
// 回调处理
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url{
// 此处设置了代理,需要遵守协议: WXApiDelegate,并实现 - (void)onResp:(BaseResp *)resp; 方法
return [WXApi handleOpenURL:url delegate:self];
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
// 此处设置了代理,需要遵守协议: WXApiDelegate 并实现 - (void)onResp:(BaseResp *)resp; 方法
[WXApi handleOpenURL:url delegate:self];
return YES;
}
- 2.支付代码:
每个公司服务器返回的数据不一样,可能在集成的时候回有差异,先看下官方demo中的代码.官方代码中集成的东西比较多,查找起来不是特别的方便,请看截图
下面是官方demo中的代码,里面会用到时间戳,随机字符串和加密后的签名,这些东西有些公司是服务器直接做好返回给你的,有些公司不是,比如,我们公司
+ (NSString *)jumpToBizPay {
//============================================================
// V3&V4支付流程实现
// 注意:参数配置请查看服务器端Demo
// 更新时间:2015年11月20日
//============================================================
NSString *urlString = @"http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php?plat=ios";
//解析服务端返回json数据
NSError *error;
//加载一个NSURL对象
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
//将请求的url数据放到NSData对象中
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
if ( response != nil) {
NSMutableDictionary *dict = NULL;
//IOS5自带解析类NSJSONSerialization从response中解析出数据放到字典中
dict = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&error];
NSLog(@"url:%@",urlString);
if(dict != nil){
NSMutableString *retcode = [dict objectForKey:@"retcode"];
if (retcode.intValue == 0){
NSMutableString *stamp = [dict objectForKey:@"timestamp"];
//调起微信支付
PayReq* req = [[[PayReq alloc] init]autorelease];
req.partnerId = [dict objectForKey:@"partnerid"]; // 商户账生成号
req.prepayId = [dict objectForKey:@"prepayid"]; // 支付订单,有公司服务器生成
req.nonceStr = [dict objectForKey:@"noncestr"]; // 32位的随机字符创
req.timeStamp = stamp.intValue; // 时间戳
req.package = [dict objectForKey:@"package"]; // 固定写法 @"Sign=WXPay"
req.sign = [dict objectForKey:@"sign"]; // 签名
[WXApi sendReq:req];
//日志输出
NSLog(@"appid=%@\npartid=%@\nprepayid=%@\nnoncestr=%@\ntimestamp=%ld\npackage=%@\nsign=%@",[dict objectForKey:@"appid"],req.partnerId,req.prepayId,req.nonceStr,(long)req.timeStamp,req.package,req.sign );
return @"";
}else{
return [dict objectForKey:@"retmsg"];
}
}else{
return @"服务器返回错误,未获取到json对象";
}
}else{
return @"服务器返回错误";
}
}
代理 WXApiDelegate
#pragma mark - WXApiDelegate
- (void)onResp:(BaseResp *)resp{
// 支付成功后,返回你的app会调用该方法,可以在这里做相应的处理
if([resp isKindOfClass:[PayResp class]]){
//支付返回结果,实际支付结果需要去微信服务器端查询
NSString *strMsg = nil;
switch (resp.errCode) {
case WXSuccess:
strMsg = @"支付结果:成功!";
NSLog(@"支付成功-PaySuccess,retcode = %d", resp.errCode);
break;
default:
strMsg = [NSString stringWithFormat:@"支付结果:失败!retcode = %d, retstr = %@", resp.errCode,resp.errStr];
NSLog(@"错误,retcode = %d, retstr = %@", resp.errCode,resp.errStr);
break;
}
}
}
以上是官方demo,如果你们公司的服务器把什么都做好了,照着实现就可以了,但是,但是,为什么但是呢?如果后台有些时间没做,就麻烦一点了.比如,时间戳,随机字符和签名加密都需要自己做的时候,加密用的是md5加密
// md5加密
导入头文件:#import <CommonCrypto/CommonCrypto.h>
// 加密代码
/**
* md5加密
*
* @param input 需要加密的字符串
*
* @return 加密后生成的字符串
*/
+ (NSString *) md5: (NSString *) input {
const char *cStr = [input UTF8String];
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5( cStr, (CC_LONG)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 [NSString stringWithString: output];
}
- 先看下随机数,官方给的随机数算法
// 官方算法解释
微信支付API接口协议中包含字段nonce_str,主要保证签名不可预测。我们推荐生成随机数算法如下:调用随机数函数生成,将得到的值转换为字符串。
// 具体在做的时候,我查了写资料,有些大神是用时间生成的,也有用uid(有的公司是token)生成的,我用的是第一种
/**
* 随机字符串
*
* @return 生成一个32位的字母大小的随机字符串
*/
+ (NSString *)randomNum{
NSString *timeStamp = [NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]];
// 调用md5加密算法, WXPayTools是我创建的工具类名
timeStamp = [WXPayTools md5:timeStamp];
return [timeStamp uppercaseString];
}
- 时间戳
// 获取当前时间,这个是C语言获取的
time_t now;
time(&now);
// 时间戳
NSString *timestamp = [NSString stringWithFormat:@"%ld", now];
// OC的获取时间戳的方法
NSTimeInterval interval = [[NSDate date] timeIntervalSince1970];
- 签名的加密,简单来说就是把需要传的参数按照字典的格式进行排序,然后拼接,再然后添加key字典,就是添加到@"key=排序后拼接的字符串".排序的时候尽量不要手动排序,避免出错.
官方给的解释
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
举例:
假设传送的参数如下:
appid: wxd930ea5d5a258f4f
mch_id: 10000100
device_info: 1000
body: test
nonce_str: ibuaiVcKdpRxkhJA
第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下:
stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
第二步:拼接API密钥:
stringSignTemp="stringA&key=192006250b4c09247ec02edce69f6a2d"
sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7"
- 下面是我demo中对签名排序后加密的做法
// 获取当前时间
time_t now;
time(&now);
// 时间戳
NSString *timestamp = [NSString stringWithFormat:@"%ld", now];
// 随机字符串
NSString *nonceStr = [[WXPayTools md5:timestamp] uppercaseString];
NSDictionary *para = @{
@"appid" : appID,
@"noncestr" : nonceStr,
@"package" : @"Sign=WXPay",
@"partnerid" : WXpartnerId,
@"prepayid" : prepayid,
@"timestamp" : timestamp
};
NSMutableString *contentString = [NSMutableString string];
NSArray *keys = [para allKeys];
// 按字母排序
NSArray *sortedArry = [keys sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2 options:NSNumericSearch];
}];
// 拼接字符串
for (NSString *categoryId in sortedArry) {
if (![[para objectForKey:categoryId] isEqualToString:@""] && ![categoryId isEqualToString:@"sign"] && ![categoryId isEqualToString:@"key"]) {
[contentString appendFormat:@"%@=%@&", categoryId, para[categoryId]];
}
}
// 添加key字段
[contentString appendFormat:@"key=%@", apiKey];
// 加密生成字符串
// WXPayTools 是我创建的工具类名
NSString *sign = [WXPayTools md5:contentString];
- 上面的工作都做完后,就可以进行支付宝支付了.支付完成后的操作可以在代理方法里面做
PayReq *req = [[PayReq alloc] init];
req.partnerId = WXpartnerId;
req.prepayId = prepayid;
req.nonceStr = nonceStr;
req.timeStamp = timestamp.intValue;
req.package = @"Sign=WXPay";
req.sign = sign; // 签名
[WXApi sendReq:req];