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

作者: zhangferry | 来源:发表于2016-05-20 18:55 被阅读855次
    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,或者下方评论

    相关文章

      网友评论

      • Z了个L:赞一个
      • 杨空空:想问一下 demo中 block config作用
        objc_setAssociatedObject(self, xwAdd_callbackConfig_key, config, OBJC_ASSOCIATION_COPY_NONATOMIC) 和 if (!config) {
        NSLog(@"必须设置回调block");
        return;
        } 麻烦 给讲解下
      • bed0965b05e8:勇闖天涯,雪花啤酒
        zhangferry:@TouAllCra 这个名字是我灵机一动想的
        bed0965b05e8:哈哈,茉莉花茶有这个活动吗
        zhangferry:@TouAllCra 没我这个好听 :smile:

      本文标题:集成支付宝和微信支付的那些事

      本文链接:https://www.haomeiwen.com/subject/gazyrttx.html