集成支付宝和微信支付的那些事

aliwxPay.jpeg

在集成支付宝和微信支付功能之前就听说了,支付集成有多烂,官方文档有多不清晰。当我怀着忐忑和不服输的心情处理这些问题的时候,还是结结实实的栽了跟头。虽然现在网上支付防坑的教程很多,但是无奈人家支付宝微信的维护也在继续,旧的解决办法已不适用新的环境╮(╯_╰)╭,这也是给我带来麻烦的一个方面。写这个文章的目的就是想把最新的解决方法,全面,细致的梳理一遍。废话结束,下面是正题。

运行环境

因为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。
导入代码完成之后运行程序会报错,类似:

F0F75B6A-A6BD-4900-A9D4-D1776E09D9F1.png

9A77D5B9-DE47-48AE-8625-72B4285A1B13.png

这些在文档的"针对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支付业务流程

chapter8_3_1 2.png

我们在大致了解这个业务流程之后,就可以进行下一步了。

第二步、配置支付信息

这个时候去看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所以会出现一个错误

GDataXMLNode_error.png

在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,或者下方评论

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,133评论 25 707
  • 实现支付宝支付的准备工作: 1.向支付宝签约,成为支付宝的商户 签约完成后,支付宝会提供一些必要的数据给我们 商户...
    Anson杨春安阅读 8,206评论 0 6
  • 自己总结的微信支付宝支付流程和注意点: 准备工作: 需要公司的营业执照,税务信息,等老板的身份证信息等,我记得,用...
    Www刘阅读 18,565评论 2 50
  • 准备工作: 需要公司的营业执照,税务信息,等老板的身份证信息等,我记得,用这些材料,去支付宝注册一个商家账户(审核...
    Hevin_Chen阅读 6,806评论 0 9
  • 依旧是北京往常的天气,不同的是,不太冷了。 走在去往地铁的路上,有一种渺小的感觉,原以为来北京可以闯出一番事业,却...
    陈zai三里屯阅读 147评论 0 0