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("<xml>")
    for key in keys {
        reqPars.appendFormat("<%@>%@</%@>", key, String(packageParams[key]!), key)
    }
    reqPars.appendFormat("<sign>%@</sign></xml>", 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-第七城市末尾有写到。

做完后才发现,其他当时踩的好些小坑都不记得了,说到底还是不熟悉这个具体的流程,所以才举步维艰。最后,按照这个代码写应该是可以走通的~ 文中有不对的地方欢迎指正。新手上路,感觉自己现在就是面向百度和谷歌编程,所以自己也把自己的经验写出来分享下。相互学习 共同进步。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容