iOS填一填微信支付的坑

第一篇,想想就有点小激动呢~,就拿最近做了4天的微信支付开始吧。

作为一个iOS开发的搬砖农民工,感受到微信支付的小坑实在太多。所以,首先得感谢大神们在网上分享微信支付的各种经验,我才能照葫芦画瓢做出来。

第一步当然是看微信支付官网文档,其中有一张业务流程图,结合官网demo把支付的流程看懂:

【微信支付】公众号支付开发者文档

我下载的官方demo中,调起微信接口payReq的参数都是在服务器端生成(微信本身是鼓励客户APP把签名算法放到服务器上面,这样信息就不容易被破解),而我需要在本地完成这些参数的设定,客户端进行2次签名验证,主要是为了获取到prePayId(统一下单号)。主要参照了以下文章:

a.iOS微信支付开发

b.iOS-关于微信支付-IOS-第七城市

c.iOS客户端的微信支付接入 - iPhone手机开发技术文章 - 红黑联盟

这三篇结合起来看应该讲的很详细。声明一下:我的代码完全是照着c篇,把OC翻译成swift写出来的。如下:

'
import Foundation

class WXPayManager: NSObject, WXApiDelegate {

let WX_PAY_APP_ID = "wxbaf100d54*******"      //公众账号ID
let WX_PAY_COMPANY_ID = "12769*****"          //商户号
let WX_PAY_API_KEY = "chuxiaolan******"
let WX_PAY_PREPAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"         //预支付相关url
let WX_PAY_NOTIFY_URL = "http://218.244.***.***:8080/payment_server/ops/payForWechat.do"
var debugInfo = NSMutableString()        //debug信息
var lastErrCode = Int()         //返回的错误码

var orderBean:OrderBean?


class func defaultManager()->WXPayManager {
    struct Singleton {
        static var predicate:dispatch_once_t = 0
        static var instance:WXPayManager? = nil
    }
    dispatch_once(&Singleton.predicate) { () -> Void in
        Singleton.instance = WXPayManager()
    }
    return Singleton.instance!
}

//MARK:- WXApiDelegate
//微信支付的回调方法
func onResp(resp:BaseResp) {
    print(resp.errCode)
    
    if resp is PayResp {
        switch (resp.errCode) {
        case 0:
            NSNotificationCenter.defaultCenter().postNotificationName("NOTIFICATION_WX_PAY", object: true)
        default:
            NSNotificationCenter.defaultCenter().postNotificationName("NOTIFICATION_WX_PAY", object: false)
            break
        }
    }
}

//创建package签名
func createMd5Sign(dict:NSMutableDictionary)->String {
    let contentString = NSMutableString()
    let keys0 = dict.allKeys as! [String]
    //按字母排序
    let keys = keys0.sort()
    //拼接字符串
    for key in keys {
        if !(dict[key] == nil) && !(key == "sign") && !(key == "key") {
            contentString.appendFormat("%@=%@&", key, String(dict[key]!))
        }
    }
    //添加key字段
    contentString.appendFormat("key=%@", WX_PAY_API_KEY)
    //得到MD5 sign签名
    let md5Sign = WXUtil.md5(contentString as String)
    //输出Debug Info
    debugInfo.appendFormat("MD5签名字符串:%@", contentString)
    return md5Sign
}

//获取package带参数的签名包
func genPackage(packageParams:NSMutableDictionary)->String {
    //生成签名
    let sign = self.createMd5Sign(packageParams)
    //生成xml的package
    let reqPars = NSMutableString()
    let keys = packageParams.allKeys as! [String]
    reqPars.appendString("")
    for key in keys {
        reqPars.appendFormat("<%@>%@", key, String(packageParams[key]!), key)
    }
    reqPars.appendFormat("%@", sign)
    return reqPars as String
}

//提交预支付
func sendPrepay(prePayParams:NSMutableDictionary)->String? {
    var prepayid:String?
    //获取提交支付
    let send = self.genPackage(prePayParams)
    debugInfo.appendFormat("API链接:%@", WX_PAY_PREPAY_URL)
    debugInfo.appendFormat("发送的xml:%@", send)
    //发送请求的post xml数据
    let res:NSData = WXUtil.httpSend(WX_PAY_PREPAY_URL, method: "POST", data: send)
    //输出Debug Info
    debugInfo.appendFormat("服务器返回:%@", String(data: res, encoding: NSUTF8StringEncoding)!)
    
    let xml = XMLHelper()
    //开始解析
    xml.startParse(res)
    var resParams = xml.getDict()
    //判断返回
    var return_code = resParams["return_code"] as? String
    var result_code = resParams["result_code"] as? String
    if return_code == "SUCCESS" {
        //生成返回数据的签名
        let sign = self.createMd5Sign(resParams)
        let send_sign = resParams["sign"] as? String
        //验证签名正确性
        if sign == send_sign {
            if result_code == "SUCCESS" {
                //验证业务处理状态
                prepayid = resParams["prepay_id"] as! String
                return_code = ""
                debugInfo.appendString("获取预支付交易标示成功!")
            }
        } else {
            self.lastErrCode = 1
            debugInfo.appendFormat("gen_sign=%@, send_sign=%@", sign, send_sign!)
            debugInfo.appendString("服务器返回签名验证错误!")
        }
    } else {
        self.lastErrCode = 2
        debugInfo.appendString("接口返回错误!")
    }
    return prepayid
}

//生成预支付订单
func getPrepayOrder()->NSMutableDictionary? {
    
    let appid = self.WX_PAY_APP_ID
    let mch_id = self.WX_PAY_COMPANY_ID
    let nonce_str = self.getRandom_32()
    let trade_type = "APP"
    let body = "众菜-订单号\(orderBean!.id!)"
    let notify_url = self.WX_PAY_NOTIFY_URL
    
    //商户支付的订单号由商户自定义生成,微信支付要求商户订单号保持唯一性(建议根据当前系统时间加随机序列来生成订单号)。重新发起一笔支付要使用原订单号,避免重复支付;已支付过或已调用关单、撤销(请见后文的API列表)的订单号不能重新发起支付。
    var out_trade_no = String(orderBean!.id!)
    out_trade_no = out_trade_no.stringByAppendingString("_")
    out_trade_no = out_trade_no.stringByAppendingString(String(Int(NSDate().timeIntervalSince1970)))
    
    let spbill_create_ip = "127.0.0.1"
    
    // “*100”,因为微信支付的下单金额 以分为单位!
    let total_fee0 = Int((orderBean?.placedPrice)!*100)
    let total_fee = String(total_fee0)
    //        print(total_fee0,   total_fee)
    
    //预付单参数订单设置
    var packageParams = NSMutableDictionary()
    packageParams["appid"] = appid                 //开放平台appid
    packageParams["mch_id"] = mch_id               //商户号
    //        packageParams["device_info"] =               //支付设备号或门店号
    packageParams["nonce_str"] = nonce_str         //随机串
    packageParams["trade_type"] = trade_type       //支付类型,固定为APP
    packageParams["body"] = body                   //订单描述,展示给用户
    packageParams["notify_url"] = notify_url       //支付结果异步通知
    packageParams["out_trade_no"] = out_trade_no   //商户订单号
    packageParams["spbill_create_ip"] = spbill_create_ip   //发器支付的机器ip
    packageParams["total_fee"] = total_fee         //订单金额
    
    //获取prepayId (预支付会话标识)
    let prePayid:String? = self.sendPrepay(packageParams)
    if prePayid == nil {
        debugInfo.appendString("获取prepayid失败!")
        return nil
    }
    
    //获取到prepayid后进行二次签名
    //网上有人说:第二次签名时的nonce_str需要是第一次的nonce_str。 但我试了下,好像不需要啊
    let package = "Sign=WXPay"
    //第二次签名参数列表
    //这里有个大坑!sign签名时的key,一定要和文档上对应的key一样,如appid,noncestr;千万不能写成发请求的那种req.appId。
    let signParams = NSMutableDictionary()
    signParams["appid"] = appid
    signParams["partnerid"] = mch_id
    signParams["noncestr"] = getRandom_32()
    signParams["package"] = package
    
    let timeStamp = Int(NSDate().timeIntervalSince1970)
    signParams["timestamp"] = String(timeStamp)
    signParams["prepayid"] = prePayid
    
    //生成签名
    let sign = self.createMd5Sign(signParams)
    //添加签名
    signParams["sign"] = sign
    debugInfo.appendFormat("第二步签名成功,sign=%@", sign)
    
    return signParams
}

//调用支付接口, 唤起微信支付界面
func WXPay() {
    if WXApi.isWXAppInstalled() {
        let dict = self.getPrepayOrder()
        if dict == nil {
            //错误提示
            let debug = debugInfo
            print("WXPay failed...")
            print(debugInfo)
            return
        }
        
        let timeStamp = Int(dict!["timestamp"] as! String)
        
        let req = PayReq()
        req.partnerId = dict!["partnerid"] as! String
        req.prepayId = dict!["prepayid"] as! String
        req.nonceStr = dict!["noncestr"] as! String
        req.timeStamp = UInt32(timeStamp!)
        req.package = dict!["package"] as! String
        req.sign = dict!["sign"] as! String
        print(dict!)
        WXApi.sendReq(req)
    } else {
        print("请安装微信")
    }
}

//随机生成32位的字母加数字混合的字符串
func getRandom_32()->String {
    var str = String()
    for var i = 0; i < 32; i++ {
        let number = arc4random() % 36
        if number < 10 {
            let figure = arc4random() % 10;
            str = str.stringByAppendingFormat("%d", figure)
        } else {
            let figure = (arc4random() % 26) + 97
            let char = Character(UnicodeScalar(figure))
            str = str.stringByAppendingString(String(char))
        }
    }
    print(str)
    return str
}

}'

接下来说一说我遇到的那些坑:

1.除了WXApi.h,WXApiObject.h,libWeChatSDK等之外,还要导入WXUtil.h,WXUtil.m(用于签名md5),ApiXml.h,ApiXml.m(用于Xml解析),否则写代码时找不到这两个类。

2.遵守WXApiDelegate,才能调用onResp方法。

3.配置完URL Schemes后,需要在plist里加上以下两个属性,Allow Arbitrary Loads传输协议什么鬼的(我也不懂);LSApplicationQueriesSchemes添加weixin:因为苹果公司iOS 9系统策略更新,限制了http协议的访问,此外应用需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装。

4.微信的price单位是分,所以total_fee要注意*100。

5.所有的参数和参数类型都一定不要写错!appid一定不能错。

6.除了最后发送payReq.timeStamp(时间戳)是Int类型,其他地方的参数字典,不管用于签名还是xml解析 应该都是String类型吧(total_fee我也是写的String)

7.进行sign签名的时候:对签名的key应该像文档中定义的这样写如:appid,mch_id...(注意大小写),而不是payReq中appId,mch_Id这样。否则,我出现的错误是:唤起了微信,界面却是空白只有一个确定按钮,点击就返回到原app中,支付失败,反复检查参数也都是正确的,找了大半天,这也是我唤起支付界面的最后一个bug,所以说,这对我是个大坑!(如果跳出空白的确定页面,也许是参数错了)

8.网上还有一些其他的坑,也有很多有效的解决办法,多上网找找,然后对着代码仔细找找。像友盟分享已经导入了微信的SDK,微信有冲突,这在b篇iOS-关于微信支付-IOS-第七城市末尾有写到。

做完后才发现,其他当时踩的好些小坑都不记得了,说到底还是不熟悉这个具体的流程,所以才举步维艰。最后,按照这个代码写应该是可以走通的~ 文中有不对的地方欢迎指正。新手上路,感觉自己现在就是面向百度和谷歌编程,所以自己也把自己的经验写出来分享下。相互学习 共同进步。

你可能感兴趣的:(iOS填一填微信支付的坑)