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閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容