iOS-微信支付流程(客戶端下單)(備忘)

微信支付流程其實挺簡單的,但苦逼的我需要在客戶端下單,瘋狂百度之后終于是搞定了,所以有必要把代碼記錄一下。
一,導入SDK
如果使用了分享的SDK中包含微信SDK,就不用再次導入了。

FE4CF3D5-69DA-44A2-9D7A-FF2A743ABAF4.png

依賴庫

07CE9A94-CA02-469D-95EC-5866CF732FBF.png

二,配置
1.URL Types

FD7D523C-8A49-48C1-B153-774241FE51BE.png

2.白名單


2DE1F041-EF23-4E93-9315-859117B91906.png

三、代碼導入
1.AppDelegate.m中

#import "WXApi.h"
<WXApiDelegate>
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options
{

    return [WXApi handleOpenURL:url delegate:self];
}
- (void)onResp:(BaseResp *)resp
{
    //支付返回結果,實際支付結果需要去微信服務器端查詢
    NSString *strMsg = [NSString stringWithFormat:@"支付結果"];

    switch (resp.errCode) {
        case WXSuccess:
            strMsg = @"支付結果:成功!";
            NSLog(@"支付成功-PaySuccess,retcode = %d", resp.errCode);

            break;
        default:
            strMsg = [NSString stringWithFormat:@"支付結果:失敗!retcode = %d, retstr = %@", resp.errCode,resp.errStr];
            NSLog(@"錯誤,retcode = %d, retstr = %@", resp.errCode,resp.errStr);

            break;
    }
    self.alipayNav = nil;
}

官方把代理寫在了WXApiManager.h類里

2.支付文件中

//微信支付商戶號
#define MCH_ID  @"10位數字那個"
//開戶郵件中的(公眾賬號APPID或者應用APPID)
#define WX_AppID @"wx開頭的那個"
//安全校驗碼(MD5)密鑰,商戶平臺登錄賬戶和密碼登錄http://pay.weixin.qq.com 平臺設置的“API密鑰”,為了安全,請設置為以數字和字母組成的32字符串。
#define WX_PartnerKey @"純英文的那個"
//獲取用戶openid,可使用APPID對應的公眾平臺登錄http://mp.weixin.qq.com 的開發者中心獲取AppSecret。
#define WX_AppSecret @"app secret"

下單,個別參數在下面給出方法,我自己分裝在其他類里了

//統一下單
- (void)order:(NSString *)tradeNo withMoney:(NSString *)price
{
   NSString *appid = WX_AppID;
   NSString *mch_id = MCH_ID;
   NSString *partner = WX_PartnerKey;
   NSString *nonce_str =[self generateTradeNO];
   NSString *body = @"商品購買";
   NSString *out_trade_no = tradeNo;
   NSString *total_fee = [NSString stringWithFormat:@"%d",(int)([price floatValue]*10000/100)];
   NSString *notify_url = @"回調地址";
   NSString *spbill_create_ip = [getIPhoneIP getIPAddress:NO];
   NSString *trade_type = @"APP";
   
   DataMD5 *data = [[DataMD5 alloc] initWithAppid:appid mch_id:mch_id nonce_str:nonce_str partner_id:partner body:body out_trade_no:out_trade_no total_fee:total_fee spbill_create_ip:spbill_create_ip notify_url:notify_url trade_type:trade_type];
   NSString *sign = [data getSignForMD5];
   
   NSMutableDictionary *dic = [NSMutableDictionary dictionary];
   [dic setValue:appid forKey:@"appid"];//公眾賬號ID
   [dic setValue:mch_id forKey:@"mch_id"];//商戶號
   [dic setValue:nonce_str forKey:@"nonce_str"];//隨機字符串
   [dic setValue:sign forKey:@"sign"];//簽名
   [dic setValue:body forKey:@"body"];//商品描述
   [dic setValue:out_trade_no forKey:@"out_trade_no"];//訂單號
   [dic setValue:total_fee forKey:@"total_fee"];//金額,注意100=1元
   [dic setValue:spbill_create_ip forKey:@"spbill_create_ip"];//終端IP
   [dic setValue:notify_url forKey:@"notify_url"];//通知地址
   [dic setValue:trade_type forKey:@"trade_type"];//交易類型
   
   NSLog(@"+++%@",dic);
   
   NSString *xmlString = [dic XMLString];
   
   
   AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
   //這里傳入的xml字符串只是形似xml,但是不是正確是xml格式,需要使用af方法進行轉義
   manager.responseSerializer = [[AFHTTPResponseSerializer alloc] init];
   [manager.requestSerializer setValue:@"text/xml;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
   [manager.requestSerializer setValue:@"https://api.mch.weixin.qq.com/pay/unifiedorder" forHTTPHeaderField:@"SOAPAction"];
   [manager.requestSerializer setQueryStringSerializationWithBlock:^NSString *(NSURLRequest *request, NSDictionary *parameters, NSError *__autoreleasing *error) {
       return xmlString;
   }];
   //發起請求
   [manager POST:@"https://api.mch.weixin.qq.com/pay/unifiedorder"
             parameters:xmlString
               progress:nil
                success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    NSString *responseString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] ;
                    // LXLog(@"responseString is %@",responseString);
                    //將微信返回的xml數據解析轉義成字典
                    NSDictionary *dic = [NSDictionary dictionaryWithXMLString:responseString];
                    NSLog(@"++++++++++%@,%@",dic,[dic objectForKey:@"return_msg"]);
                    //此處為調起支付接口
                    [self WXPayRequest:appid nonceStr:[dic objectForKey:@"nonce_str"] package:@"Sign=WXPay" partnerId:[dic objectForKey:@"mch_id"] prepayId:[dic objectForKey:@"prepay_id"] timeStamp:@"1" sign:[dic objectForKey:@"sign"]];
                }
                failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    
                }];

}

支付:

//調起微信接口
- (void)WXPayRequest:(NSString *)appId nonceStr:(NSString *)nonceStr package:(NSString *)package partnerId:(NSString *)partnerId prepayId:(NSString *)prepayId timeStamp:(NSString *)timeStamp sign:(NSString *)sign{
    
    //調起微信支付
    
    PayReq* wxreq = [[PayReq alloc] init];
    wxreq.partnerId= partnerId;
    wxreq.prepayId= prepayId;
    wxreq.nonceStr= nonceStr;
    
    NSDate *date = [NSDate date];
    NSString *time1970 = [NSString stringWithFormat:@"%ld", (long)[date timeIntervalSince1970]];
    wxreq.timeStamp= [time1970 intValue];
    
    wxreq.package= package;
    DataMD5 *md5 = [[DataMD5 alloc] init];
    wxreq.sign=[md5 createMD5SingForPay:appId partnerid:wxreq.partnerId prepayid:wxreq.prepayId package:wxreq.package noncestr:wxreq.nonceStr timestamp:wxreq.timeStamp];
    [WXApi sendReq:wxreq];
}

3.nonce_str隨機字符串的獲得

- (NSString *)generateTradeNO {
    static int kNumber = 15;
    NSString *sourceStr = @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    NSMutableString *resultStr = [[NSMutableString alloc] init];
    srand(time(0)); // 此行代碼有警告:
    for (int i = 0; i < kNumber; i++) {
        unsigned index = rand() % [sourceStr length];
        NSString *oneStr = [sourceStr substringWithRange:NSMakeRange(index, 1)];
        [resultStr appendString:oneStr];
    }
    return resultStr;
}

4,IP地址的獲得

#pragma mark - 獲取設備當前網絡IP地址
+ (NSString *)getIPAddress:(BOOL)preferIPv4
{
    NSArray *searchArray = preferIPv4 ?
    @[ IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] :
    @[ IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ;
    
    NSDictionary *addresses = [self getIPAddresses];
    NSLog(@"addresses: %@", addresses);
    
    __block NSString *address;
    [searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
     {
         address = addresses[key];
         //篩選出IP地址格式
         if([self isValidatIP:address]) *stop = YES;
     } ];
    return address ? address : @"0.0.0.0";
}

+ (BOOL)isValidatIP:(NSString *)ipAddress {
    if (ipAddress.length == 0) {
        return NO;
    }
    NSString *urlRegEx = @"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
    "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
    "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
    "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
    
    NSError *error;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:urlRegEx options:0 error:&error];
    
    if (regex != nil) {
        NSTextCheckingResult *firstMatch=[regex firstMatchInString:ipAddress options:0 range:NSMakeRange(0, [ipAddress length])];
        
        if (firstMatch) {
            NSRange resultRange = [firstMatch rangeAtIndex:0];
            NSString *result=[ipAddress substringWithRange:resultRange];
            //輸出結果
            NSLog(@"%@",result);
            return YES;
        }
    }
    return NO;
}

+ (NSDictionary *)getIPAddresses
{
    NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
    
    // retrieve the current interfaces - returns 0 on success
    struct ifaddrs *interfaces;
    if(!getifaddrs(&interfaces)) {
        // Loop through linked list of interfaces
        struct ifaddrs *interface;
        for(interface=interfaces; interface; interface=interface->ifa_next) {
            if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
                continue; // deeply nested code harder to read
            }
            const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
            char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
            if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
                NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
                NSString *type;
                if(addr->sin_family == AF_INET) {
                    if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
                        type = IP_ADDR_IPv4;
                    }
                } else {
                    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
                    if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
                        type = IP_ADDR_IPv6;
                    }
                }
                if(type) {
                    NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
                    addresses[key] = [NSString stringWithUTF8String:addrBuf];
                }
            }
        }
        // Free memory
        freeifaddrs(interfaces);
    }
    return [addresses count] ? addresses : nil;
}

5.獲取sign簽名

///獲取sign簽名
- (NSString *)getSignForMD5{
    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
    [dic setValue:_appid forKey:@"appid"];
    [dic setValue:_mch_id forKey:@"mch_id"];
    [dic setValue:_nonce_str forKey:@"nonce_str"];
    [dic setValue:_body forKey:@"body"];
    [dic setValue:_out_trade_no forKey:@"out_trade_no"];
    [dic setValue:_total_fee forKey:@"total_fee"];
    [dic setValue:_spbill_create_ip forKey:@"spbill_create_ip"];
    [dic setValue:_notify_url forKey:@"notify_url"];
    [dic setValue:_trade_type forKey:@"trade_type"];
    return [self createMd5Sign:dic];
}

//創建簽名
-(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:@""]
            && ![[dict objectForKey:categoryId] isEqualToString:@"sign"]
            && ![[dict objectForKey:categoryId] isEqualToString:@"key"]
            )
        {
            [contentString appendFormat:@"%@=%@&", categoryId, [dict objectForKey:categoryId]];
        }
    }
    //添加商戶密鑰key字段
    [contentString appendFormat:@"key=%@", _partnerkey];
    //得到MD5 sign簽名
    NSString *md5Sign =[self md5:contentString];
    
    return md5Sign;
}

6.創建發起支付時的sige簽名

//創建發起支付時的sige簽名
-(NSString *)createMD5SingForPay:(NSString *)appid_key partnerid:(NSString *)partnerid_key prepayid:(NSString *)prepayid_key package:(NSString *)package_key noncestr:(NSString *)noncestr_key timestamp:(UInt32)timestamp_key{
    NSMutableDictionary *signParams = [NSMutableDictionary dictionary];
    [signParams setObject:appid_key forKey:@"appid"];
    [signParams setObject:noncestr_key forKey:@"noncestr"];
    [signParams setObject:package_key forKey:@"package"];
    [signParams setObject:partnerid_key forKey:@"partnerid"];
    [signParams setObject:prepayid_key forKey:@"prepayid"];
    [signParams setObject:[NSString stringWithFormat:@"%u",timestamp_key] forKey:@"timestamp"];
    
    NSMutableString *contentString  =[NSMutableString string];
    NSArray *keys = [signParams allKeys];
    //按字母順序排序
    NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        return [obj1 compare:obj2 options:NSNumericSearch];
    }];
    //拼接字符串
    for (NSString *categoryId in sortedArray) {
        if (   ![[signParams objectForKey:categoryId] isEqualToString:@""]
            && ![[signParams objectForKey:categoryId] isEqualToString:@"sign"]
            && ![[signParams objectForKey:categoryId] isEqualToString:@"key"]
            )
        {
            [contentString appendFormat:@"%@=%@&", categoryId, [signParams objectForKey:categoryId]];
        }
    }
    //添加商戶密鑰key字段
#warning 注意此處一定要添加上商戶密鑰 別忘了改
    [contentString appendFormat:@"key=%@", @"partnerkey"];
    NSString *result = [self md5:contentString];
    return result;
}
-(NSString *) md5:(NSString *)str
{
    const char *cStr = [str UTF8String];
    //加密規則,因為逗比微信沒有出微信支付demo,這里加密規則是參照安卓demo來得
    unsigned char result[16]= "0123456789abcdef";
    CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
    //這里的x是小寫則產生的md5也是小寫,x是大寫則md5是大寫,這里只能用大寫,逗比微信的大小寫驗證很逗
    return [NSString stringWithFormat:
            @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
            result[0], result[1], result[2], result[3],
            result[4], result[5], result[6], result[7],
            result[8], result[9], result[10], result[11],
            result[12], result[13], result[14], result[15]
            ];
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容