在集成支付宝和微信支付功能之前就听说了,支付集成有多烂,官方文档有多不清晰。当我怀着忐忑和不服输的心情处理这些问题的时候,还是结结实实的栽了跟头。虽然现在网上支付防坑的教程很多,但是无奈人家支付宝微信的维护也在继续,旧的解决办法已不适用新的环境╮(╯_╰)╭,这也是给我带来麻烦的一个方面。写这个文章的目的就是想把最新的解决方法,全面,细致的梳理一遍。废话结束,下面是正题。
运行环境
因为SDK会随着维护更新而改变,有可能过了不知多长时间,我这这些方法都不适用了,所以提前说一下版本还是很有用的,我所下载的版本为:
-
支付宝钱包支付接口开发包2.0标准版(iOS 15.1.0)(更新时间:2016/03/22 )
-
微信支付SDKSample_ios9_v2.0.2_V3_pay(更新时间:2015年11月20日)
支付宝的更新时间是在官网文档找的,这个还比较像话。到了微信,找了好一阵,才发现原来在工程里,好坑啊。然后呢,微信有一个微信开放平台,需要调用微信的程序要在这里申请。跟支付有关的还需要转到另一个平台,微信支付商户平台,这两个平台帐号密码都不一样╮(╯_╰)╭真是搞不懂。我还仔细对比了在两个平台下载的Demo,内容几乎一样,就微信支付的Demo包含了微信Demo的所有功能,然后另外又加了一个支付。
集成支付宝
第一步、按照官方文档指示操作,解决运行bug
下载官方SDK,然后按照官方文档指示操作,这是导入一个三方库的常用步奏。支付宝的这个文档更新时间是2016/4/19,还指出了Xcode7之前的动态库为.dylib之后为.tbd。剩下的都可以参照文档,有不明白的参照Demo。
导入代码完成之后运行程序会报错,类似:
这些在文档的"针对Demo的运行注意"里面有提到解决办法,但后一个bug还需要拖openssl的上一级目录到
Header Search Paths
。如果运行还出现带有如下这样的错误,可以对比Demo是不是少导入那个库或者.a文件。 Paste_Image.png
还会有一个问题,当支付页面因为某种原因混合编译改为xxViewController.mm时,会报如下错误:
1A1EF184-5439-487A-85CD-BCEFE437D671.png解决办法是将DataSigner.m文件的后缀名也改为.mm文件
第二步、配置支付信息
支付信息里有三个比较重要的信息,商户ID(partner),收款帐号ID(seller)和支付宝私钥(privateKey)。前两者可以在应用详情获取,私钥需要可以根据文档RSA私钥级公钥生成在本地生成,Mac用户生成地址在前往>个人
目录下。可以用文本编辑器打开查看生成的公钥和密钥。然后上传公钥,将密钥设置为privateKey参数。订单参数可以根据实际情况进行修改。
这时运行可能会显示rsa_private read error : private key is NULL
解决方法:点这里
以下是支付函数:
id<DataSigner> signer = CreateRSADataSigner(privateKey);
NSString *signedString = [signer signString:orderSpec];
//将签名成功字符串格式化为订单字符串,请严格按照该格式
NSString *orderString = nil;
if (signedString != nil) {
orderString = [NSString stringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"",
orderSpec, signedString, @"RSA"];
[[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) {
//可以在这里处理回调
NSLog(@"reslut = %@",resultDic);
// 9000 订单支付成功
// 8000 正在处理中
// 4000 订单支付失败
// 6001 用户中途取消
// 6002 网络连接出错
}];
}
这时如果能跳转到支付宝页面,且支付回调reslut = 9000,那么恭喜你,集成成功!这一关已经打过去了!
集成微信支付
接下来我们要征服微信支付,还是原来的步奏
第一步、按照文档导入SDK,查bug
可以先不看文档直接根据Demo引入包、库文件和在AppDelegate中注册微信,然后根据Demo里的readme.txt设置appid
关于微信文档可看的也就是App支付业务流程
我们在大致了解这个业务流程之后,就可以进行下一步了。
第二步、配置支付信息
这个时候去看Demo的话发现支付就是调用+ (NSString *)jumpToBizPay
方法,里面有一个公共接口返回信息,但是我们惯常思维想到的商品名称、支付费用都没有。
然后找到一个网上的解决办法,但是不幸的是,之后再去早怎么都找不到了(绝对不是故意的0。0,这种功能文章太多了)
需要参数如下:
//微信参数
static NSString *kWeiXin_AppId = @"wxb4ba3c02aa476ea1";//appid
static NSString *kWeiXin_MchId = @"100324323";//商户号
static NSString *kWeiXin_APIKEY = @"xxxxx";//由微信端生成
static NSString *getPrePayIdUrl = @"https://api.mch.weixin.qq.com/pay/unifiedorder";/** 获取prePayId的url, 这是官方给的接口 */
看代码之前再看一遍微信支付流程图,我们首先是设置支付参数,然后通过这些参数请求微信支付公共接口生成prePayid,获取prePayid之后结合之前那些参数和api密钥生成sign签名(MD5加密),然后调用方法[WXApi sendReq:request];
请求支付。
因为引入了GDataXMLNode.h所以会出现一个错误
在Build Settings -> Header Search Paths里添加一项
/usr/include/libxml2
就行了。
提示:微信经常出现的问题是跳转支付界面时只有一个确定按钮,这说明你传的参数不对,仔细检查,我就被这坑过,所以最好从头到尾把所有参数都对照检查一遍。
参考代码如下:
- (IBAction)WeixinPay:(id)sender {
[self getWeChatPayWithOrderName:@"商品名称" price:@"1"];
}
// 调起微信支付,传进来商品名称和价格
- (void)getWeChatPayWithOrderName:(NSString *)name
price:(NSString*)price{
//----------------------------获取prePayId配置------------------------------
// 订单标题,展示给用户
NSString* orderName = name;
// 订单金额,单位(分), 1是0.01元
NSString* orderPrice = price;
// 支付类型,固定为APP
NSString* orderType = @"APP";
// 随机数串
NSString *noncestr = [self genNonceStr];
// 商户订单号
NSString *orderNO = [self genOutTradNo];
//================================
//预付单参数订单设置
//================================
NSMutableDictionary *packageParams = [NSMutableDictionary dictionary];
[packageParams setObject: kWeiXin_APIKEY forKey:@"appid"]; //开放平台appid
[packageParams setObject: kWeiXin_MchId forKey:@"mch_id"]; //商户号
[packageParams setObject: noncestr forKey:@"nonce_str"]; //随机串
[packageParams setObject: orderType forKey:@"trade_type"]; //支付类型,固定为APP
[packageParams setObject: orderName forKey:@"body"]; //订单描述,展示给用户
[packageParams setObject: orderNO forKey:@"out_trade_no"];//商户订单号
[packageParams setObject: orderPrice forKey:@"total_fee"]; //订单金额,单位为分
[packageParams setObject: [CommonUtil getIPAddress:YES] forKey:@"spbill_create_ip"];//发器支付的机器ip
[packageParams setObject: @"http://weixin.qq.com" forKey:@"notify_url"]; //支付结果异步通知
NSString *prePayid;
prePayid = [self sendPrepay:packageParams];
//--------------获取prePayId结束,利用参数生成sign签名-------------------
if(prePayid){
NSString *timeStamp = [self genTimeStamp];
// 调起微信支付
PayReq *request = [[PayReq alloc] init];
request.partnerId = kWeiXin_MchId;
request.prepayId = prePayid;
request.package = @"Sign=WXPay";
request.nonceStr = noncestr;
request.timeStamp = [timeStamp intValue];
// 这里要注意key里的值一定要填对, 微信官方给的参数名是错误的,不是第二个字母大写
NSMutableDictionary *signParams = [NSMutableDictionary dictionary];
[signParams setObject: kWeiXin_AppId forKey:@"appid"];
[signParams setObject: kWeiXin_MchId forKey:@"partnerid"];
[signParams setObject: request.nonceStr forKey:@"noncestr"];
[signParams setObject: request.package forKey:@"package"];
[signParams setObject: timeStamp forKey:@"timestamp"];
[signParams setObject: request.prepayId forKey:@"prepayid"];
//生成签名
NSString *sign = [self genSign:signParams];
//添加签名
request.sign = sign;
[WXApi sendReq:request];
} else{
NSLog(@"获取prePayId失败!");
}
}
// 发送给微信的XML格式数据
- (NSString *)genPackage:(NSMutableDictionary*)packageParams
{
NSString *sign;
NSMutableString *reqPars = [NSMutableString string];
// 生成签名
sign = [self genSign:packageParams];
// 生成xml格式的数据, 作为post给微信的数据
NSArray *keys = [packageParams allKeys];
[reqPars appendString:@"<xml>"];
for (NSString *categoryId in keys) {
[reqPars appendFormat:@"<%@>%@</%@>"
, categoryId, [packageParams objectForKey:categoryId],categoryId];
}
[reqPars appendFormat:@"<sign>%@</sign></xml>", sign];
return [NSString stringWithString:reqPars];
}
// 获取prePayId
- (NSString *)sendPrepay:(NSMutableDictionary *)prePayParams
{
// 获取提交预支付的xml格式数据
NSString *send = [self genPackage:prePayParams];
// 打印检查, 格式应该是xml格式的字符串
NSLog(@"%@", send);
// 转换成NSData
NSData *data_send = [send dataUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:getPrePayIdUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:data_send];
NSError *error = nil;
// 拿到data后, 用xml解析, 这里随便用什么方法解析
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error];
if (!error) {
// 1.根据data初始化一个GDataXMLDocument对象
GDataXMLDocument *document = [[GDataXMLDocument alloc] initWithData:data options:0 error:nil];
// 2.拿出根节点
GDataXMLElement *rootElement = [document rootElement];
GDataXMLElement *return_code = [[rootElement elementsForName:@"return_code"] lastObject];
GDataXMLElement *return_msg = [[rootElement elementsForName:@"return_msg"] lastObject];
GDataXMLElement *result_code = [[rootElement elementsForName:@"result_code"] lastObject];
GDataXMLElement *prepay_id = [[rootElement elementsForName:@"prepay_id"] lastObject];
// 如果return_code和result_code都为SUCCESS, 则说明成功
NSLog(@"return_code---%@", [return_code stringValue]);
// 返回信息,通常返回一些错误信息
NSLog(@"return_msg---%@", [return_msg stringValue]);
NSLog(@"result_code---%@", [result_code stringValue]);
// 拿到这个就成功一大半啦
NSLog(@"prepay_id---%@", [prepay_id stringValue]);
return [prepay_id stringValue];
} else {
return nil;
}
}
#pragma mark - 生成各种参数
- (NSString *)genTimeStamp
{
return [NSString stringWithFormat:@"%.0f", [[NSDate date] timeIntervalSince1970]];
}
/**
* 注意:商户系统内部的订单号,32个字符内、可包含字母,确保在商户系统唯一
*/
- (NSString *)genNonceStr
{
return [CommonUtil md5:[NSString stringWithFormat:@"%d", arc4random() % 10000]];
}
/**
* 建议 traceid 字段包含用户信息及订单信息,方便后续对订单状态的查询和跟踪
*/
- (NSString *)genTraceId
{
return [NSString stringWithFormat:@"myt_%@", [self genTimeStamp]];
}
- (NSString *)genOutTradNo
{
return [CommonUtil md5:[NSString stringWithFormat:@"%d", arc4random() % 10000]];
}
#pragma mark - 签名
/** 签名 */
- (NSString *)genSign:(NSDictionary *)signParams
{
// 排序, 因为微信规定 ---> 参数名ASCII码从小到大排序
NSArray *keys = [signParams allKeys];
NSArray *sortedKeys = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2 options:NSNumericSearch];
}];
//生成 ---> 微信规定的签名格式
NSMutableString *sign = [NSMutableString string];
for (NSString *key in sortedKeys) {
[sign appendString:key];
[sign appendString:@"="];
[sign appendString:[signParams objectForKey:key]];
[sign appendString:@"&"];
}
NSString *signString = [[sign copy] substringWithRange:NSMakeRange(0, sign.length - 1)];
// 拼接API密钥
NSString *result = [NSString stringWithFormat:@"%@&key=%@", signString, kWeiXin_APIKEY];
// 打印检查
NSLog(@"result = %@", result);
// md5加密
NSString *signMD5 = [CommonUtil md5:result];
// 微信规定签名英文大写
signMD5 = signMD5.uppercaseString;
// 打印检查
NSLog(@"signMD5 = %@", signMD5);
return signMD5;
}
如果还有什么不明白的,可以看参考Demo,或者下方评论
网友评论
objc_setAssociatedObject(self, xwAdd_callbackConfig_key, config, OBJC_ASSOCIATION_COPY_NONATOMIC) 和 if (!config) {
NSLog(@"必须设置回调block");
return;
} 麻烦 给讲解下