第一次集成微信支付出现了很多问题,当然微信支付如果是后台返回二次签名数据,这样做的话,就简单很多了,本文主要集中讲解的是微信支付如何在本地生成预支付订单,以及预支付订单的二次签名(ps.第一次写这样的文章,在文章中有不正确的地方,非常欢迎各位的指正,也很乐意和大家一起探讨)文章分两部分,第一部分讲如何集成微信支付,第二部分讲如何在本地生成预支付订单、以及预支付订单的二次签名,
一、如何集成微信支付
集成微信支付需要导入 SystemConfiguration.framework
libz.dylib
libsqlite3.0.dylib
libc++.dylib
这几个文件,当然添加微信支付的SDK是必不可少的
1、在AppDelegate的- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法里面注册微信支付
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[WXApi registerApp:@"appid" withDescription:@"描述"];
return YES;
}
在方法- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(nonnullNSDictionary<NSString *,id> *)options写入代码
return [WXApi handleOpenURL:url delegate:self];
遵守微信的代理方法 @interface AppDelegate ()< WXApiDelegate >,并实现代理方法
- (void)onResp:(BaseResp *)resp
{
NSString *strMsg = [NSString stringWithFormat:@"errcode:%d", resp.errCode];
NSString *strTitle;
if([resp isKindOfClass:[SendMessageToWXResp class]])
{
strTitle = [NSString stringWithFormat:@"发送媒体消息结果"];
}
if([resp isKindOfClass:[PayResp class]]){
#warning 4.支付返回结果,实际支付结果需要去自己的服务器端查询 由于demo的局限性这里直接使用返回的结果
strTitle = [NSString stringWithFormat:@"支付结果"];
// 返回码参考:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=9_12
switch (resp.errCode) {
case WXSuccess:{
[self.window addHUD:@"支付成功"];
NSNotification *notification = [NSNotification notificationWithName:@"ORDER_PAY_NOTIFICATION" object:@"success"];
[[NSNotificationCenter defaultCenter] postNotification:notification];
break;
}
default:{
[self.window addHUD:@"支付失败"];
NSNotification *notification = [NSNotification notificationWithName:@"ORDER_PAY_NOTIFICATION"object:@"fail"];
[[NSNotificationCenter defaultCenter] postNotification:notification];
break;
}
}
}
}
到这里AppDelegate的操作就算完成了,
2、在需要支付的事件里面加入微信支付代买:
if (![[PlistOperation arrayWithPlistOfName:nil] isEqualToArray:[NSArray array]]) {
if (self.payStyle == weixinPay) {
if (![WXApi isWXAppInstalled]) {//检查用户是否安装微信
[self.view addHUD:@"检查是否安装微信"];
return;
}
payRequsestHandler *handler = [[payRequsestHandler alloc]init];
BOOL han = [handler init:@"appid" mch_id:@"商户ID"];
[handler setKey:@"商户支付密钥"];
if (han) {
// NSLog(@"初始化成功");
}
NSMutableDictionary *dict = [handler sendPay_demo:@"商品价格(单位:分)" Title:@"商品title"];
//由于我的项目里面微信支付的预支付订单和二次签名都是在本地生成,以上都是生成预支付订单和二次签名的代码,如果是后台生成这些信息,个请求下订单后,直接进行如下代码,当然判断是否安装微信是必不可少的,
PayReq *req = [[PayReq alloc]init];
req.partnerId = dict[@"partnerid"];
req.prepayId= dict[@"prepayid"];
req.package = dict[@"package"];
req.nonceStr= dict[@"noncestr"];
req.timeStamp= [dict[@"timestamp"]intValue];
req.sign= dict[@"sign"];
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 );//打印订单信息
[self sendOrderInfo:req.prepayId];
[WXApi sendReq:req];
到这里整个微信支付的过程就算是完成了,在集成微信支付后往往出现很多错误,比如说调起微信支付后,只显示确认按钮,却没有订单信息,返回提示是用户取消支付的话,这是由于订单信息有误,在后面我会提到我在集成中遇到的错误
二、如何在本地提交预支付订单以及进行二次签名
在这一部分可以说,话了我绝大部分的时间,在网上找Demo,看微信的文档,等等...虽然网上有一些这样的Demo,但是我在学习中,却发现不少问题,Demo里面相关注释太少,不是无法调起微信支付就是报各种错误,在咨询身边朋友和研究微信支付的文档后,逐步将微信支付的整个流程弄明白。下面我将对本地提交预支付订单和进行二次签名做一个详细的介绍,当然是在我所理解的范围内,我也是第一次集成微信支付,中间的一些错误,或者不当之处还请各位大神提出,
1、如何提交预支付订单
1.1预支付订单的参数有那些,怎么去提交预支付订单。
从微信的文档里面我们可以知道预支付订单的参数有这些
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
公众账号ID | appid | 是 | String(32) | wxd678efh567hg6787 | 微信分配的公众账号ID(企业号corpid即为此appId) |
商户号 | mch_id | 是 | String(32) | 1230000109 | 微信支付分配的商户号 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB" |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,不长于32位。推荐随机数生成算法 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 签名,详见签名生成算法 |
商品描述 | body | 是 | String(128) | Ipad mini 16G 白色 | 商品或支付单简要描述 |
商品详情 | detail | 否 | String(8192) | Ipad mini 16G 白色 | 商品名称明细列表 |
附加数据 | attach | 否 | String(127) | 深圳分店 | 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 |
商户订单号 | out_trade_no | 是 | String(32) | 20150806125346 | 商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号 |
货币类型 | fee_type | 否 | String(16) | CNY | 符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 |
总金额 | total_fee | 是 | Int | 888 | 订单总金额,单位为分,详见支付金额 |
终端IP | spbill_create_ip | 是 | String(16) | 123.12.12.123 | APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。 |
交易起始时间 | time_start | 否 | String(14) | 20091225091010 | 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 |
交易结束时间 | time_expire | 否 | String(14) | 20091227091010 | 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟 |
商品标记 | goods_tag | 否 | String(32) | WXG | 商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠 |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。 |
交易类型 | trade_type | 是 | String(16) | JSAPI | 取值如下:JSAPI,NATIVE,APP,详细说明见参数规定 |
商品ID | product_id | 否 | String(32) | 12235413214070356458058 | trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。 |
指定支付方式 | limit_pay | 否 | String(32) | no_credit | no_credit--指定不能使用信用卡支付 |
用户标识 | openid | 否 | String(128) | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o | trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换 |
示例代码:
NSMutableDictionary *packageParams = [NSMutableDictionary dictionary];
[packageParams setObject: APP_ID forKey:@"appid"]; //开放平台appid
[packageParams setObject: MCH_ID forKey:@"mch_id"]; //商户号
[packageParams setObject: @"APP-001" forKey:@"device_info"]; //支付设备号或门店号
[packageParams setObject: noncestr forKey:@"nonce_str"]; //随机串
[packageParams setObject: @"APP" forKey:@"trade_type"]; //支付类型,固定为APP
[packageParams setObject: title forKey:@"body"]; //订单描述,展示给用户
[packageParams setObject: NOTIFY_URL forKey:@"notify_url"]; //支付结果异步通知
[packageParams setObject: orderno forKey:@"out_trade_no"];//商户订单号
[packageParams setObject: @"192.168.1.1" forKey:@"spbill_create_ip"];//发器支付的机器ip
[packageParams setObject: money forKey:@"total_fee"]; //订单金额,单位为分
现在微信的预支付订单信息已经生成好了,在提交预支付订单以前需要对订单进行排序和MD5加密的签名
-(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=%@", @"商户密钥"];
//得到MD5 sign签名
NSString *md5Sign =[WXUtil md5:contentString];//MD5加密方法
return md5Sign;
}
这是获取签名的方法,在获取签名后要对订单进行一个XML编码:
-(NSString *)genPackage:(NSMutableDictionary*)packageParams
{
NSString *sign;
NSMutableString *reqPars=[NSMutableString string];
//生成签名
sign = [self createMd5Sign:packageParams];
//生成xml的package
NSArray *keys = [packageParams allKeys];
[reqPars appendString:@"<xml>\n"];
for (NSString *categoryId in keys) {
[reqPars appendFormat:@"<%@>%@</%@>\n", categoryId, [packageParams objectForKey:categoryId],categoryId];
}
[reqPars appendFormat:@"<sign>%@</sign>\n</xml>", sign];
return [NSString stringWithString:reqPars];
}
在将订单信息拼接成XML之后还需要拼接一个sign,也就是上面得到的签名,当进行玩上面所述的两步之后就需要获取 prepayid了,获取prepayid需要将订单信息提交到微信服务器
-(NSString *)sendPrepay:(NSMutableDictionary *)prePayParams
{
NSString *prepayid = nil;
//获取提交支付
NSString *send = [self genPackage:prePayParams];
//输出Debug Info
[debugInfo appendFormat:@"API链接:%@\n", payUrl];
[debugInfo appendFormat:@"发送的xml:%@\n", send];
//发送请求post xml数据
NSData *res = [WXUtil httpSend:payUrl method:@"POST" data:send];
NSLog(@"res ====== %@",[[NSString alloc]initWithData:res encoding:NSUTF8StringEncoding]);
//输出Debug Info
[debugInfo appendFormat:@"服务器返回:\n%@\n\n",[[NSString alloc] initWithData:res encoding:NSUTF8StringEncoding]];
XMLHelper *xml = [[XMLHelper alloc] autorelease];
//开始解析
[xml startParse:res];
NSMutableDictionary *resParams = [xml getDict];
//判断返回
NSString *return_code = [resParams objectForKey:@"return_code"];
NSString *result_code = [resParams objectForKey:@"result_code"];
if ( [return_code isEqualToString:@"SUCCESS"] )
{
//生成返回数据的签名
NSString *sign = [self createMd5Sign:resParams ];
NSString *send_sign =[resParams objectForKey:@"sign"] ;
//验证签名正确性
if( [sign isEqualToString:send_sign]){
if( [result_code isEqualToString:@"SUCCESS"]) {
//验证业务处理状态
prepayid = [resParams objectForKey:@"prepay_id"];
return_code = 0;
[debugInfo appendFormat:@"获取预支付交易标示成功!\n"];
}
}else{
last_errcode = 1;
[debugInfo appendFormat:@"gen_sign=%@\n _sign=%@\n",sign,send_sign];
[debugInfo appendFormat:@"服务器返回签名验证错误!!!\n"];
}
}else{
last_errcode = 2;
[debugInfo appendFormat:@"接口返回错误!!!\n"];
}
return prepayid;
}
在提交过后就能获得prepayid了,当进行到这一步也就完成了一大半了。
在获取prepaid后就能进行二次签名了,二次签名的参数有:
字段名 | 变量名 | 类型 | 必填 | 示例值 | 描述 |
公众账号ID | appid | String(32) | 是 | wx8888888888888888 | 微信分配的公众账号ID |
商户号 | partnerid | String(32) | 是 | 1900000109 | 微信支付分配的商户号 |
预支付交易会话ID | prepayid | String(32) | 是 | WX1217752501201407033233368018 | 微信返回的支付交易会话ID |
扩展字段 | package | String(128) | 是 | Sign=WXPay | 暂填写固定值Sign=WXPay |
随机字符串 | noncestr | String(32) | 是 | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,不长于32位。推荐随机数生成算法 |
时间戳 | timestamp | String(10) | 是 | 1412000000 | 时间戳,请见接口规则-参数规定 |
签名 | sign | String(32) | 是 | C380BEC2BFD727A4B6845133519F3AD6 | 签名,详见签名生成算法 |
进行二次签名:
//获取到prepayid后进行第二次签名
NSString *package, *time_stamp, *nonce_str;
//设置支付参数
time_t now;
time(&now);
time_stamp = [NSString stringWithFormat:@"%ld", now];
nonce_str = [WXUtil md5:time_stamp];
//重新按提交格式组包,微信客户端暂只支持package=Sign=WXPay格式,须考虑升级后支持携带package具体参数的情况
// package = [NSString stringWithFormat:@"Sign=%@",package];
package = @"Sign=WXPay";
//第二次签名参数列表
NSMutableDictionary *signParams = [NSMutableDictionary dictionary];
[signParams setObject: appid forKey:@"appid"];
[signParams setObject: nonce_str forKey:@"noncestr"];
[signParams setObject: package forKey:@"package"];
[signParams setObject: mchid forKey:@"partnerid"];
[signParams setObject: time_stamp forKey:@"timestamp"];
[signParams setObject: prePayid forKey:@"prepayid"];
// [signParams setObject: @"MD5" forKey:@"signType"];
//生成签名
NSString *sign = [self createMd5Sign:signParams];
//添加签名
[signParams setObject: sign forKey:@"sign"];
[debugInfo appendFormat:@"第二步签名成功,sign=%@\n",sign];
//返回参数列表
return signParams;
到这一步,本地进行预支付,以及二次签名的过程也就完成了,
在完成二次签名后,将返回的数据调用第一部分的方法就能实现微信支付,但是在这其中需要特别注意:这里返回的时间戳是一个字符串,需要在调用支付的方法中将时间戳强转为int或long型数据,我在这里就吃了不少的亏!
三、特别声明
我是在使用网上提供的本地签名库,在使用过程中结合自己的理解,以及在集成中发现本地签名的方法中注释过少,不利于理解,所以某生出写一个文档去记录自己的想法并且希望能给同样处于我一样的新手一个参考,如果在这之中侵犯到谁的权利,请你联系我,我将修改侵权的地方,第一次写这样的文章也请各位多多指教!我将虚心向各位前辈以及大神们学习!
文件下载地址:点击下载