實際上,從代碼的角度,調起支付APP就是把一些關鍵的參數通過一定方式打包成為一個訂單,然後發送到支付平台的服務器。所以,只要搞清楚了參數設置,搞清楚了每個支付平台的SDK裡面一些關鍵API的使用,基本上就可以很簡單的支持支付。
今天記錄一下客戶端裡面,如何支持微信支付。首先。我們要仔細閱讀一下微信SDK的開發文檔,了解一下整個支付的大概流程。
然後根據提示,把相應的SDK下載下來,所謂的SDK,也就是一個鏈接庫和兩個頭文件,很簡單。
下載完畢,需要把SDK導入到工程裡面,並且配置一下工程。因為開發者文檔已經有詳細描述,這裡就不再復述。
從文檔看到,調起微信支付其實最核心的是一下這麼一段
<code class="hljs" vbscript="">PayReq *request = [[[PayReq alloc] init] autorelease]; request.partnerId = @10000100; request.prepayId= @1101000000140415649af9fc314aa427; request.package = @Sign=WXPay; request.nonceStr= @a462b76e7436e98e0ed6e13c64b4fd1c; request.timeStamp= @1397527777; request.sign= @582282D72DD2B03AD892830965F428CB16E7A256; [WXApi sendReq:request];</code>
這裡的范例是一段hardcode,真正使用的時候,參數都需要自行傳入。
為了搞清楚如何使用API,我們可以下載Sample代碼。不過,這個sample代碼應該是微信的實習生寫的,而且應該是一個對於C++比較熟悉,對於ObjectC比較陌生的實習生。。。代碼風格可以看出很多東西哈。。所以這個sample讀起來總覺得有點奇怪。當然,寫出這個demo也是需要不錯的水平,因為這個sample不僅僅是一些API的調用,還包括了一些算法的實現,MD5之類的。
看懂了sample之後,一般可以自己重構一下,成為自己APP裡面的一個Manager類。
我是在2015 5 23下載的微信Sampel代碼,裡面包括有:
ApiXml.h
ApiXml.m
WXUtil.h
WXUtil.m
payRequestHandler.h
payRequestHandler.m
如果比較看重命名規范的OC程序猿,就會覺得這個payRequestHandler類非常別扭,不符合camel命名規則,而且handler這個詞更偏向於c++風格。我就以這個類為原型,重構了一下,並改裝成一個傳參的方法,供自己的APP調用。APP裡面賣商品,一般就是商品名字,價格兩個關鍵參數。所以這個重構的方法也只是提供這兩個參數的接口。
ApiXml.h && ApiXml.m && WXUtil.h && WXUtil.m不變
<code class="hljs" objectivec="">// // WechatPayManager.h // // Created by HuangCharlie on 5/24/15. // // #import <foundation foundation.h=""> #import WXUtil.h #import ApiXml.h #import WXApi.h // 賬號帳戶資料 // 更改商戶把相關參數後可測試 #define APP_ID @wx@@@@@@@@@@@@@@@@ //APPID #define APP_SECRET @ //appsecret,看起來好像沒用 //商戶號,填寫商戶對應參數 #define MCH_ID @@@@@@@@@@@ //商戶API密鑰,填寫相應參數 #define PARTNER_ID @12345678901234567890123456789012 //支付結果回調頁面 #define NOTIFY_URL @http://wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php //獲取服務器端支付數據地址(商戶自定義)(在小吉這裡,簽名算法直接放在APP端,故不需要自定義) #define SP_URL @http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php @interface WechatPayManager : NSObject { } //預支付網關url地址 @property (nonatomic,strong) NSString* payUrl; //debug信息 @property (nonatomic,strong) NSMutableString *debugInfo; @property (nonatomic,assign) NSInteger lastErrCode;//返回的錯誤碼 //商戶關鍵信息 @property (nonatomic,strong) NSString *appId,*mchId,*spKey; //初始化函數 -(id)initWithAppID:(NSString*)appID mchID:(NSString*)mchID spKey:(NSString*)key; //獲取當前的debug信息 -(NSString *) getDebugInfo; //獲取預支付訂單信息(核心是一個prepayID) - (NSMutableDictionary*)getPrepayWithOrderName:(NSString*)name price:(NSString*)price device:(NSString*)device; @end </foundation></code> <code class="hljs" objectivec="">// // WechatPayManager.m // // Created by HuangCharlie on 5/24/15. // // #import WechatPayManager.h @implementation WechatPayManager //初始化函數 -(id)initWithAppID:(NSString*)appID mchID:(NSString*)mchID spKey:(NSString*)key { self = [super init]; if(self) { //初始化私有參數,主要是一些和商戶有關的參數 self.payUrl = @https://api.mch.weixin.qq.com/pay/unifiedorder; if (self.debugInfo == nil){ self.debugInfo = [NSMutableString string]; } [self.debugInfo setString:@]; self.appId = appID;//微信分配給商戶的appID self.mchId = mchID;// self.spKey = key;//商戶的密鑰 } return self; } //獲取debug信息 -(NSString*) getDebugInfo { NSString *res = [NSString stringWithString:self.debugInfo]; [self.debugInfo setString:@]; return res; } //創建package簽名 -(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=%@, self.spKey]; //得到MD5 sign簽名 NSString *md5Sign =[WXUtil md5:contentString]; //輸出Debug Info [self.debugInfo appendFormat:@MD5簽名字符串: %@ ,contentString]; return md5Sign; } //獲取package帶參數的簽名包 -(NSString *)genPackage:(NSMutableDictionary*)packageParams { NSString *sign; NSMutableString *reqPars=[NSMutableString string]; //生成簽名 sign = [self createMd5Sign:packageParams]; //生成xml的package NSArray *keys = [packageParams allKeys]; [reqPars appendString:@<xml> ]; for (NSString *categoryId in keys) { [reqPars appendFormat:@<%@>%@<!--%@--> , categoryId, [packageParams objectForKey:categoryId],categoryId]; } [reqPars appendFormat:@<sign>%@</sign> </xml>, sign]; return [NSString stringWithString:reqPars]; } //提交預支付 -(NSString *)sendPrepay:(NSMutableDictionary *)prePayParams { NSString *prepayid = nil; //獲取提交支付 NSString *send = [self genPackage:prePayParams]; //輸出Debug Info [self.debugInfo appendFormat:@API鏈接:%@ , self.payUrl]; [self.debugInfo appendFormat:@發送的xml:%@ , send]; //發送請求post xml數據 NSData *res = [WXUtil httpSend:self.payUrl method:@POST data:send]; //輸出Debug Info [self.debugInfo appendFormat:@服務器返回: %@ ,[[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; [self.debugInfo appendFormat:@獲取預支付交易標示成功! ]; } }else{ self.lastErrCode = 1; [self.debugInfo appendFormat:@gen_sign=%@ _sign=%@ ,sign,send_sign]; [self.debugInfo appendFormat:@服務器返回簽名驗證錯誤!!! ]; } }else{ self.lastErrCode = 2; [self.debugInfo appendFormat:@接口返回錯誤!!! ]; } return prepayid; } - (NSMutableDictionary*)getPrepayWithOrderName:(NSString*)name price:(NSString*)price device:(NSString*)device { //訂單標題,展示給用戶 NSString* orderName = name; //訂單金額,單位(分) NSString* orderPrice = price;//以分為單位的整數 //支付設備號或門店號 NSString* orderDevice = device; //支付類型,固定為APP NSString* orderType = @APP; //發器支付的機器ip,暫時沒有發現其作用 NSString* orderIP = @196.168.1.1; //隨機數串 srand( (unsigned)time(0) ); NSString *noncestr = [NSString stringWithFormat:@%d, rand()]; NSString *orderNO = [NSString stringWithFormat:@%ld,time(0)]; //================================ //預付單參數訂單設置 //================================ NSMutableDictionary *packageParams = [NSMutableDictionary dictionary]; [packageParams setObject: self.appId forKey:@appid]; //開放平台appid [packageParams setObject: self.mchId forKey:@mch_id]; //商戶號 [packageParams setObject: orderDevice forKey:@device_info]; //支付設備號或門店號 [packageParams setObject: noncestr forKey:@nonce_str]; //隨機串 [packageParams setObject: orderType forKey:@trade_type]; //支付類型,固定為APP [packageParams setObject: orderName forKey:@body]; //訂單描述,展示給用戶 [packageParams setObject: NOTIFY_URL forKey:@notify_url]; //支付結果異步通知 [packageParams setObject: orderNO forKey:@out_trade_no];//商戶訂單號 [packageParams setObject: orderIP forKey:@spbill_create_ip];//發器支付的機器ip [packageParams setObject: orderPrice forKey:@total_fee]; //訂單金額,單位為分 //獲取prepayId(預支付交易會話標識) NSString *prePayid; prePayid = [self sendPrepay:packageParams]; if(prePayid == nil) { [self.debugInfo appendFormat:@獲取prepayid失敗! ]; return nil; } //獲取到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: self.appId forKey:@appid]; [signParams setObject: self.mchId forKey:@partnerid]; [signParams setObject: nonce_str forKey:@noncestr]; [signParams setObject: package forKey:@package]; [signParams setObject: time_stamp forKey:@timestamp]; [signParams setObject: prePayid forKey:@prepayid]; //生成簽名 NSString *sign = [self createMd5Sign:signParams]; //添加簽名 [signParams setObject: sign forKey:@sign]; [self.debugInfo appendFormat:@第二步簽名成功,sign=%@ ,sign]; //返回參數列表 return signParams; } @end</code>
然後,在需要調用微信支付的Controller裡面,新建一個方法。在合適的地方調用。這個方法裡面利用WechatPayManager這個類進行了初始化和參數封裝,然後把上述的核心代碼(PayReq那一段)
<code class="hljs" objectivec="">- (void)wxPayWithOrderName:(NSString*)name price:(NSString*)price { //創建支付簽名對象 && 初始化支付簽名對象 WechatPayManager* wxpayManager = [[[WechatPayManager alloc]initWithAppID:APP_ID mchID:MCH_ID spKey:PARTNER_ID] autorelease]; //獲取到實際調起微信支付的參數後,在app端調起支付 //生成預支付訂單,實際上就是把關鍵參數進行第一次加密。 NSString* device = [[UserManager defaultManager]userId]; NSMutableDictionary *dict = [wxpayManager getPrepayWithOrderName:name price:price device:device]; if(dict == nil){ //錯誤提示 NSString *debug = [wxpayManager getDebugInfo]; return; } NSMutableString *stamp = [dict objectForKey:@timestamp]; //調起微信支付 PayReq* req = [[[PayReq alloc] init]autorelease]; req.openID = [dict objectForKey:@appid]; req.partnerId = [dict objectForKey:@partnerid]; req.prepayId = [dict objectForKey:@prepayid]; req.nonceStr = [dict objectForKey:@noncestr]; req.timeStamp = stamp.intValue; req.package = [dict objectForKey:@package]; req.sign = [dict objectForKey:@sign]; // BOOL flag = [WXApi sendReq:req]; BOOL flag = [WXApi safeSendReq:req]; }</code>
再者,支付完成了需要調用一個delegate,這個delegate方便個性化顯示支付結果。一般直接把這兩個delegate放在AppDelegate就好了。因為有一些其他內容也是需要在AppDelegate裡面實現,省的分開找不到。
<code class="hljs" objectivec="">-(void) onResp:(BaseResp*)resp { //啟動微信支付的response NSString *strMsg = [NSString stringWithFormat:@errcode:%d, resp.errCode]; if([resp isKindOfClass:[PayResp class]]){ //支付返回結果,實際支付結果需要去微信服務器端查詢 switch (resp.errCode) { case 0: strMsg = @支付結果:成功!; break; case -1: strMsg = @支付結果:失敗!; break; case -2: strMsg = @用戶已經退出支付!; break; default: strMsg = [NSString stringWithFormat:@支付結果:失敗!retcode = %d, retstr = %@, resp.errCode,resp.errStr]; break; } } }</code>
注意事項:
1)如果APP裡面已經使用了ShareSDK,就有一些地方要注意。不要再重復導入微信的SDK,因為shareSDK裡面的extend已經包括了微信的SDK。
2)微信本身是鼓勵客戶APP把簽名算法放到服務器上面,這樣信息就不容易被破解。但是如果客戶APP本身沒有服務器端,或者認為不需要放到服務器端,也可以直接把簽名(加密)的部分直接放在APP端。Sample代碼的注釋有點亂,多次提到服務器雲雲,但是其實可以不這麼做。
3)微信的price單位是分。注意下即可。
4)暫時想不到,以後想到了再記錄。。
以上是本文給大家敘述的IOS客戶端接入微信支付的全部內容,希望大家喜歡。