天天看點

iOS微信支付開發

第一次內建微信支付出現了很多問題,當然微信支付如果是背景傳回二次簽名資料,這樣做的話,就簡單很多了,本文主要集中講解的是微信支付如何在本地生成預支付訂單,以及預支付訂單的二次簽名(ps.第一次寫這樣的文章,在文章中有不正确的地方,非常歡迎各位的指正,也很樂意和大家一起探讨)文章分兩部分,第一部分講如何內建微信支付,第二部分講如何在本地生成預支付訂單、以及預支付訂單的二次簽名,

一、如何內建微信支付

內建微信支付需要導入 SystemConfiguration.framework

libz.dylib

libsqlite3.0.dylib

libc++.dylib

這幾個檔案,當然添加微信支付的SDK是必不可少的

1、在AppDelegate的- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法裡面注冊微信支付

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [WXApi registerApp:@"appid" withDescription:@"描述"];
    return YES;
}
           

在方法- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(nonnullNSDictionary<NSString *,id> *)options寫入代碼

return [WXApi handleOpenURL:url delegate:self];
           

遵守微信的代理方法  @interface AppDelegate ()< WXApiDelegate >,并實作代理方法

- (void)onResp:(BaseResp *)resp
{
    NSString *strMsg = [NSString stringWithFormat:@"errcode:%d", resp.errCode];
    NSString *strTitle;
    if([resp isKindOfClass:[SendMessageToWXResp class]])
    {
        strTitle = [NSString stringWithFormat:@"發送媒體消息結果"];
    }
    if([resp isKindOfClass:[PayResp class]]){
#warning 4.支付傳回結果,實際支付結果需要去自己的伺服器端查詢  由于demo的局限性這裡直接使用傳回的結果
        strTitle = [NSString stringWithFormat:@"支付結果"];
        // 傳回碼參考:https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=9_12
        switch (resp.errCode) {
            case WXSuccess:{
                [self.window addHUD:@"支付成功"];
                NSNotification *notification = [NSNotification notificationWithName:@"ORDER_PAY_NOTIFICATION" object:@"success"];
                [[NSNotificationCenter defaultCenter] postNotification:notification];
                break;
            }
            default:{
                [self.window addHUD:@"支付失敗"];
                NSNotification *notification = [NSNotification notificationWithName:@"ORDER_PAY_NOTIFICATION"object:@"fail"];
                [[NSNotificationCenter defaultCenter] postNotification:notification];
                break;
            }
        }
    }
}
           

到這裡AppDelegate的操作就算完成了,

2、在需要支付的事件裡面加入微信支付代買:

if (![[PlistOperation arrayWithPlistOfName:nil] isEqualToArray:[NSArray array]]) {
        if (self.payStyle == weixinPay) {
            if (![WXApi isWXAppInstalled]) {//檢查使用者是否安裝微信
                [self.view addHUD:@"檢查是否安裝微信"];
                return;
            }
            payRequsestHandler *handler = [[payRequsestHandler alloc]init];
            BOOL han = [handler init:@"appid" mch_id:@"商戶ID"];
            [handler setKey:@"商戶支付密鑰"];
            if (han) {
                //            NSLog(@"初始化成功");
            }
            
            
            NSMutableDictionary *dict = [handler sendPay_demo:@"商品價格(機關:分)" Title:@"商品title"];
           
//由于我的項目裡面微信支付的預支付訂單和二次簽名都是在本地生成,以上都是生成預支付訂單和二次簽名的代碼,如果是背景生成這些資訊,個請求下訂單後,直接進行如下代碼,當然判斷是否安裝微信是必不可少的,
           
PayReq *req = [[PayReq alloc]init];
            req.partnerId = dict[@"partnerid"];
            req.prepayId= dict[@"prepayid"];
            req.package = dict[@"package"];
            req.nonceStr= dict[@"noncestr"];
            req.timeStamp= [dict[@"timestamp"]intValue];
            req.sign= dict[@"sign"];
            NSLog(@"appid=%@\npartid=%@\nprepayid=%@\nnoncestr=%@\ntimestamp=%ld\npackage=%@\nsign=%@",[dict objectForKey:@"appid"],req.partnerId,req.prepayId,req.nonceStr,(long)req.timeStamp,req.package,req.sign );//列印訂單資訊
            [self sendOrderInfo:req.prepayId];
            [WXApi sendReq:req];
           

到這裡整個微信支付的過程就算是完成了,在內建微信支付後往往出現很多錯誤,比如說調起微信支付後,隻顯示确認按鈕,卻沒有訂單資訊,傳回提示是使用者取消支付的話,這是由于訂單資訊有誤,在後面我會提到我在內建中遇到的錯誤

二、如何在本地送出預支付訂單以及進行二次簽名

    在這一部分可以說,話了我絕大部分的時間,在網上找Demo,看微信的文檔,等等...雖然網上有一些這樣的Demo,但是我在學習中,卻發現不少問題,Demo裡面相關注釋太少,不是無法調起微信支付就是報各種錯誤,在咨詢身邊朋友和研究微信支付的文檔後,逐漸将微信支付的整個流程弄明白。下面我将對本地送出預支付訂單和進行二次簽名做一個詳細的介紹,當然是在我所了解的範圍内,我也是第一次內建微信支付,中間的一些錯誤,或者不當之處還請各位大神提出,

1、如何送出預支付訂單

    1.1預支付訂單的參數有那些,怎麼去送出預支付訂單。

從微信的文檔裡面我們可以知道預支付訂單的參數有這些

字段名 變量名 必填 類型 示例值 描述
公衆賬号ID appid String(32) wxd678efh567hg6787 微信配置設定的公衆賬号ID(企業号corpid即為此appId) 
商戶号 mch_id String(32) 1230000109 微信支付配置設定的商戶号
裝置号 device_info String(32) 013467007045764 終端裝置号(門店号或收銀裝置ID),注意:PC網頁或公衆号内支付請傳"WEB"
随機字元串 nonce_str String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随機字元串,不長于32位。推薦随機數生成算法
簽名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 簽名,詳見簽名生成算法
商品描述 body String(128) Ipad mini  16G  白色 商品或支付單簡要描述
商品詳情 detail String(8192) Ipad mini  16G  白色 商品名稱明細清單
附加資料 attach String(127) 深圳分店 附加資料,在查詢API和支付通知中原樣傳回,該字段主要用于商戶攜帶訂單的自定義資料
商戶訂單号 out_trade_no String(32) 20150806125346 商戶系統内部的訂單号,32個字元内、可包含字母, 其他說明見商戶訂單号
貨币類型 fee_type String(16) CNY 符合ISO 4217标準的三位字母代碼,預設人民币:CNY,其他值清單詳見貨币類型
總金額 total_fee Int 888 訂單總金額,機關為分,詳見支付金額
終端IP spbill_create_ip String(16) 123.12.12.123 APP和網頁支付送出使用者端ip,Native支付填調用微信支付API的機器IP。
交易起始時間 time_start String(14) 20091225091010 訂單生成時間,格式為yyyyMMddHHmmss,如2009年12月25日9點10分10秒表示為20091225091010。其他詳見時間規則
交易結束時間 time_expire String(14) 20091227091010

訂單失效時間,格式為yyyyMMddHHmmss,如2009年12月27日9點10分10秒表示為20091227091010。其他詳見時間規則

注意:最短失效時間間隔必須大于5分鐘

商品标記 goods_tag String(32) WXG 商品标記,代金券或立減優惠功能的參數,說明詳見代金券或立減優惠
通知位址 notify_url String(256) http://www.weixin.qq.com/wxpay/pay.php 接收微信支付異步通知回調位址,通知url必須為直接可通路的url,不能攜帶參數。
交易類型 trade_type String(16) JSAPI 取值如下:JSAPI,NATIVE,APP,詳細說明見參數規定
商品ID product_id String(32) 12235413214070356458058 trade_type=NATIVE,此參數必傳。此id為二維碼中包含的商品ID,商戶自行定義。
指定支付方式 limit_pay String(32) no_credit no_credit--指定不能使用信用卡支付
使用者辨別 openid String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI,此參數必傳,使用者在商戶appid下的唯一辨別。openid如何擷取,可參考【擷取openid】。企業号請使用【企業号OAuth2.0接口】擷取企業号内成員userid,再調用【企業号userid轉openid接口】進行轉換

示例代碼:

NSMutableDictionary *packageParams = [NSMutableDictionary dictionary];
    
    [packageParams setObject: APP_ID             forKey:@"appid"];       //開放平台appid
    [packageParams setObject: MCH_ID             forKey:@"mch_id"];      //商戶号
    [packageParams setObject: @"APP-001"        forKey:@"device_info"]; //支付裝置号或門店号
    [packageParams setObject: noncestr          forKey:@"nonce_str"];   //随機串
    [packageParams setObject: @"APP"            forKey:@"trade_type"];  //支付類型,固定為APP
    [packageParams setObject: title        forKey:@"body"];        //訂單描述,展示給使用者
    [packageParams setObject: NOTIFY_URL        forKey:@"notify_url"];  //支付結果異步通知
    [packageParams setObject: orderno           forKey:@"out_trade_no"];//商戶訂單号
    [packageParams setObject: @"192.168.1.1"    forKey:@"spbill_create_ip"];//發器支付的機器ip
    [packageParams setObject: money       forKey:@"total_fee"];       //訂單金額,機關為分
           

現在微信的預支付訂單資訊已經生成好了,在送出預支付訂單以前需要對訂單進行排序和MD5加密的簽名

-(NSString*) createMd5Sign:(NSMutableDictionary*)dict
{
    NSMutableString *contentString  =[NSMutableString string];
    NSArray *keys = [dict allKeys];//擷取所有的關鍵字
    //按字母順序排序
    NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        return [obj1 compare:obj2 options:NSNumericSearch];
    }];
    //拼接排序後的字元串
    for (NSString *categoryId in sortedArray) {
        if (   ![[dict objectForKey:categoryId] isEqualToString:@""]
            && ![categoryId isEqualToString:@"sign"]
            && ![categoryId isEqualToString:@"key"]
            )
        {
            [contentString appendFormat:@"%@=%@&", categoryId, [dict objectForKey:categoryId]];
        }
        
    }
    //添加key字段
    [contentString appendFormat:@"key=%@", @"商戶密鑰"];
    //得到MD5 sign簽名
    NSString *md5Sign =[WXUtil md5:contentString];//MD5加密方法

    return md5Sign;
}
           

這是擷取簽名的方法,在擷取簽名後要對訂單進行一個XML編碼:

-(NSString *)genPackage:(NSMutableDictionary*)packageParams
{
    NSString *sign;
    NSMutableString *reqPars=[NSMutableString string];
    //生成簽名
    sign        = [self createMd5Sign:packageParams];
    //生成xml的package
    NSArray *keys = [packageParams allKeys];
    [reqPars appendString:@"<xml>\n"];
    for (NSString *categoryId in keys) {
        [reqPars appendFormat:@"<%@>%@</%@>\n", categoryId, [packageParams objectForKey:categoryId],categoryId];
    }
    [reqPars appendFormat:@"<sign>%@</sign>\n</xml>", sign];
    
    return [NSString stringWithString:reqPars];
}
           

在将訂單資訊拼接成XML之後還需要拼接一個sign,也就是上面得到的簽名,當進行玩上面所述的兩步之後就需要擷取 prepayid了,擷取prepayid需要将訂單資訊送出到微信伺服器

-(NSString *)sendPrepay:(NSMutableDictionary *)prePayParams
{
    NSString *prepayid = nil;
    
    //擷取送出支付
    NSString *send      = [self genPackage:prePayParams];
    
    //輸出Debug Info
    [debugInfo appendFormat:@"API連結:%@\n", payUrl];
    [debugInfo appendFormat:@"發送的xml:%@\n", send];
    
    //發送請求post xml資料
    NSData *res = [WXUtil httpSend:payUrl method:@"POST" data:send];
    NSLog(@"res ====== %@",[[NSString alloc]initWithData:res encoding:NSUTF8StringEncoding]);
    //輸出Debug Info
    [debugInfo appendFormat:@"伺服器傳回:\n%@\n\n",[[NSString alloc] initWithData:res encoding:NSUTF8StringEncoding]];
    
    XMLHelper *xml  = [[XMLHelper alloc] autorelease];
    
    //開始解析
    [xml startParse:res];
    
    NSMutableDictionary *resParams = [xml getDict];

    //判斷傳回
    NSString *return_code   = [resParams objectForKey:@"return_code"];
    NSString *result_code   = [resParams objectForKey:@"result_code"];
    if ( [return_code isEqualToString:@"SUCCESS"] )
    {
        //生成傳回資料的簽名
        NSString *sign      = [self createMd5Sign:resParams ];
        NSString *send_sign =[resParams objectForKey:@"sign"] ;
        
        //驗證簽名正确性
        if( [sign isEqualToString:send_sign]){
            if( [result_code isEqualToString:@"SUCCESS"]) {
                //驗證業務處理狀态
                prepayid    = [resParams objectForKey:@"prepay_id"];
                return_code = 0;
                
                [debugInfo appendFormat:@"擷取預支付交易标示成功!\n"];
            }
        }else{
            last_errcode = 1;
            [debugInfo appendFormat:@"gen_sign=%@\n   _sign=%@\n",sign,send_sign];
            [debugInfo appendFormat:@"伺服器傳回簽名驗證錯誤!!!\n"];
        }
    }else{
        last_errcode = 2;
        [debugInfo appendFormat:@"接口傳回錯誤!!!\n"];
    }

    return prepayid;
}
           

在送出過後就能獲得prepayid了,當進行到這一步也就完成了一大半了。

在擷取prepaid後就能進行二次簽名了,二次簽名的參數有:

字段名 變量名 類型 必填 示例值 描述
公衆賬号ID appid String(32) wx8888888888888888 微信配置設定的公衆賬号ID
商戶号 partnerid String(32) 1900000109 微信支付配置設定的商戶号
預支付交易會話ID prepayid String(32) WX1217752501201407033233368018 微信傳回的支付交易會話ID
擴充字段 package String(128) Sign=WXPay 暫填寫固定值Sign=WXPay
随機字元串 noncestr String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随機字元串,不長于32位。推薦随機數生成算法
時間戳 timestamp String(10) 1412000000 時間戳,請見接口規則-參數規定
簽名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 簽名,詳見簽名生成算法

進行二次簽名:

//擷取到prepayid後進行第二次簽名
        NSString    *package, *time_stamp, *nonce_str;
        //設定支付參數
        time_t now;
        time(&now);
        time_stamp  = [NSString stringWithFormat:@"%ld", now];
        nonce_str	= [WXUtil md5:time_stamp];
        //重新按送出格式組包,微信用戶端暫隻支援package=Sign=WXPay格式,須考慮更新後支援攜帶package具體參數的情況
//        package       = [NSString stringWithFormat:@"Sign=%@",package];
        package         = @"Sign=WXPay";
        //第二次簽名參數清單
        NSMutableDictionary *signParams = [NSMutableDictionary dictionary];
        [signParams setObject: appid        forKey:@"appid"];
        [signParams setObject: nonce_str    forKey:@"noncestr"];
        [signParams setObject: package      forKey:@"package"];
        [signParams setObject: mchid        forKey:@"partnerid"];
        [signParams setObject: time_stamp   forKey:@"timestamp"];
        [signParams setObject: prePayid     forKey:@"prepayid"];
//        [signParams setObject: @"MD5"       forKey:@"signType"];
        //生成簽名
        NSString *sign  = [self createMd5Sign:signParams];
        
        //添加簽名
        [signParams setObject: sign         forKey:@"sign"];
        
        [debugInfo appendFormat:@"第二步簽名成功,sign=%@\n",sign];
        
        //傳回參數清單
        return signParams;
           

到這一步,本地進行預支付,以及二次簽名的過程也就完成了,

在完成二次簽名後,将傳回的資料調用第一部分的方法就能實作微信支付,但是在這其中需要特别注意:這裡傳回的時間戳是一個字元串,需要在調用支付的方法中将時間戳強轉為int或long型資料,我在這裡就吃了不少的虧!

三、特别聲明

我是在使用網上提供的本地簽名庫,在使用過程中結合自己的了解,以及在內建中發現本地簽名的方法中注釋過少,不利于了解,是以某生出寫一個文檔去記錄自己的想法并且希望能給同樣處于我一樣的新手一個參考,如果在這之中侵犯到誰的權利,請你聯系我,我将修改侵權的地方,第一次寫這樣的文章也請各位多多指教!我将虛心向各位前輩以及大神們學習!

檔案下載下傳位址:點選下載下傳