iOS實(shí)現(xiàn)H5支付(微信、支付寶)原生封裝
前言
支付分APP支付、H5支付、掃碼支付等。app支付一般在app中使用,并且需要集成相應(yīng)的支付SDK,H5支付多用于網(wǎng)頁(yè)。如果你的APP不想集成支付SDK,又想實(shí)現(xiàn)支付功能,你可以在項(xiàng)目中使用H5支付。本文主要講述如何將H5支付封裝成一個(gè)原生可調(diào)用的組件。
1.H5支付流程
注:以下為網(wǎng)頁(yè)H5支付流程,原生調(diào)用需要修改部分流程
1.1 微信支付
- 統(tǒng)一下單,獲取微信中間頁(yè)地址mweb_url
- 頁(yè)面重定向到微信中間頁(yè)
- 微信中間頁(yè)發(fā)起支付請(qǐng)求
- safari瀏覽器攔截支付請(qǐng)求打開微信APP開始支付(如果在app中,需要在shouldStartLoadWithRequest:方法里面攔截支付請(qǐng)求,并打開微信)
微信中間頁(yè)重新向到redirect_url
1.2 支付寶支付
- 發(fā)起網(wǎng)頁(yè)支付請(qǐng)求,H5為一個(gè)form表單提交。
- 頁(yè)面重定向到支付寶收銀臺(tái)頁(yè)面
- 發(fā)起APP支付請(qǐng)求,并且開始倒計(jì)時(shí),如果打開支付寶超時(shí)頁(yè)面跳轉(zhuǎn)到網(wǎng)頁(yè)支付界面,如果喚起支付寶,倒計(jì)時(shí)結(jié)束。
- 支付完畢頁(yè)面跳轉(zhuǎn)到return_url頁(yè)面,需用戶手動(dòng)觸發(fā)。
2.原生封裝思路
新開一個(gè)webView加載支付中間頁(yè),攔截中間頁(yè)支付請(qǐng)求并喚起支付,然后關(guān)閉webView流程結(jié)束。
webView需要加到window(或者當(dāng)前控制器的view上),并設(shè)置一個(gè)大小(肉眼不可見就行)。因?yàn)槭褂脀kwebview時(shí),webView不顯示的情況下,H5請(qǐng)求會(huì)被掛起,會(huì)導(dǎo)致支付寶頁(yè)面不能喚起支付請(qǐng)求。
3.代碼實(shí)現(xiàn)
具體步驟見代碼注釋
@interface HJH5WebPayManager()<UIWebViewDelegate> @property (nonatomic,strong) UIWebView *payWebview; @property (nonatomic,strong) void(^sendPayResult)(HJH5SendWebPayResult); @end @implementation HJH5WebPayManager +(instancetype)sharedInstance{ static dispatch_once_t once ; static HJH5WebPayManager *_instace = nil; dispatch_once(&once, ^{ _instace = [[self alloc] init]; }); return _instace; } -(void)loadWebPayTransitionPage:(NSString *)html handleBlock:(void (^)(HJH5SendWebPayResult))handle{ NSMutableURLRequest *request = nil; if ([html hasPrefix:@"https://wx.tenpay.com"]) { //微信安全域名 NSString *wxScheme = @""; NSString *referer = [NSString stringWithFormat:@"%@://",wxScheme]; //將redirect_url替換成scheme,微信支付完畢才能跳回APP,否則會(huì)打開Safari瀏覽器(因?yàn)閞edirect_url一般為一個(gè)HTTP的地址) NSRange range = [html rangeOfString:@"redirect_url="]; NSString *reqUrl; if (range.length>0) { reqUrl = [html substringToIndex:range.location+range.length]; reqUrl = [reqUrl stringByAppendingString:referer]; }else{ reqUrl = [html stringByAppendingString:[NSString stringWithFormat:@"&redirect_url=%@",referer]]; } request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:reqUrl] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; //設(shè)置授權(quán)域名,偽造Referer頭,因?yàn)槲⑿胖虚g頁(yè)會(huì)檢驗(yàn)Referer頭,并且Referer對(duì)應(yīng)的值需要包含安全域名 [request setValue:referer forHTTPHeaderField:@"Referer"]; if (self.payWebview) { [self.payWebview removeFromSuperview]; self.payWebview = nil; } self.payWebview = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 0.1, 0.1)]; self.sendPayResult = handle; [[UIApplication sharedApplication].keyWindow addSubview:self.payWebview]; self.payWebview.delegate = self; [self.payWebview loadRequest:request]; }else if ([html hasPrefix:@"<form"]){ //如果是支付寶,html對(duì)應(yīng)的應(yīng)該是一段form表單提交腳本,需要調(diào)用loadString方法加載 if (self.payWebview) { [self.payWebview removeFromSuperview]; self.payWebview = nil; } self.payWebview = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 0.1, 0.1)]; self.sendPayResult = handle; [[UIApplication sharedApplication].keyWindow addSubview:self.payWebview]; self.payWebview.delegate = self; NSString *payStr = html; NSString *htmlString = [NSString stringWithFormat:@"htmlString:<html> \n" "<head> \n" "<meta name=\"viewport\" content=\"initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" /> \n" "<style type=\"text/css\"> \n" "body {font-size:16px;}\n" "</style> \n" "</head> \n" "<body>" "%@" "</body>" "</html>",payStr]; [self.payWebview loadHTMLString:htmlString baseURL:nil]; }else{ //非法html,返回錯(cuò)誤 handle(HJH5SendWebPayResultOther); return; } //容錯(cuò)處理,20秒沒喚起支付,當(dāng)錯(cuò)誤處理。 __weak typeof(self) weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (weakSelf.sendPayResult) { weakSelf.sendPayResult(HJH5SendWebPayResultOther); } [weakSelf endPayment]; }); } - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{ //頁(yè)面加載失敗,返回錯(cuò)誤 if (self.sendPayResult) { self.sendPayResult(HJH5SendWebPayResultLoadFail); } [self endPayment]; } - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ NSURL *url = request.URL; NSString *newUrl = url.absoluteString; //攔截微信支付請(qǐng)求,并打開微信 if([newUrl rangeOfString:@"weixin://wap/pay"].location != NSNotFound){ //判斷是否能打開微信 if ([[UIApplication sharedApplication] canOpenURL:url]) { if (@available(iOS 10.0, *)){ [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; }else{ [[UIApplication sharedApplication] openURL:url]; } if (self.sendPayResult) { self.sendPayResult(HJH5SendWebPayResultSuccess); } [self endPayment]; }else{ if (self.sendPayResult) { self.sendPayResult(HJH5SendWebPayResultSendFail); } [self endPayment]; } return NO; }else if([newUrl rangeOfString:@"alipay://alipayclient/?"].location != NSNotFound){ //攔截支付寶支付請(qǐng)求,并且替換fromAppUrlScheme參數(shù)為當(dāng)前APP的scheme,實(shí)現(xiàn)支付完畢返回APP功能。 NSString *aliScheme = @"支付寶支付scheme,支付完畢可通過scheme返回到當(dāng)前APP"; newUrl = [HJStringHelper decodeURL:newUrl]; NSString *parameterString = [newUrl stringByReplacingOccurrencesOfString:@"alipay://alipayclient/?" withString:@""]; NSError *error = nil; id dict = [NSJSONSerialization JSONObjectWithData:[parameterString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&error]; if (!error) { if ([dict isKindOfClass:[NSMutableDictionary class]]) { dict[@"fromAppUrlScheme"] = aliScheme; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error]; if (!error) { parameterString = [HJStringHelper escapeURL:[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]]; NSString *payUrl = [NSString stringWithFormat:@"alipay://alipayclient/?%@",parameterString]; dispatch_async(dispatch_get_main_queue(), ^{ //判斷是否能打開支付寶 if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:payUrl]]) { if (@available(iOS 10.0, *)){ [[UIApplication sharedApplication] openURL:[NSURL URLWithString:payUrl] options:@{} completionHandler:nil]; }else{ [[UIApplication sharedApplication] openURL:[NSURL URLWithString:payUrl]]; } if (self.sendPayResult) { self.sendPayResult(HJH5SendWebPayResultSuccess); } [self endPayment]; }else{ if (self.sendPayResult) { self.sendPayResult(HJH5SendWebPayResultSendFail); } [self endPayment]; } }); } } } return NO; }else{ return YES; } } -(void)endPayment{ self.sendPayResult = nil; [self.payWebview removeFromSuperview]; self.payWebview = nil; } @end
3.1入?yún)⒄f明
調(diào)用該方法喚起支付-(void)loadWebPayTransitionPage:(NSString *)html handleBlock:(void (^)(HJH5SendWebPayResult))handle.
其中html為微信中間頁(yè)地址和支付寶form表單腳本。如:
微信: https://wx.tenpay.com ?xxxx
支付寶:<form id=" alipaysubmit " name="alipaysubmit" action=xxxx></form><script>document.forms[' alipaysubmit '].submit();</script>
見1.H5支付流程,微信下單之后可以獲取中間頁(yè)地址,支付則需要form表單提交加載中間頁(yè)。
3.2錯(cuò)誤處理
typedef NS_ENUM(NSUInteger,HJH5SendWebPayResult) { HJH5SendWebPayResultSuccess = 0, //喚起登錄成功 HJH5SendWebPayResultLoadFail, //支付頁(yè)面加載失敗 HJH5SendWebPayResultSendFail, //調(diào)起支付失敗,可能是沒添加未安裝微信或者支付寶 HJH5SendWebPayResultOther //其他 };
支付請(qǐng)求發(fā)送成功則表示這次H5支付發(fā)起完成,具體支付結(jié)果需要查詢后臺(tái)獲得。所以需要對(duì)一些異常情況進(jìn)行處理,比如頁(yè)面加載失敗,微信或支付寶未安裝等異常進(jìn)行處理。
4.說明
這種方案可以統(tǒng)一微信和支付寶H5支付的流程,并且隱式地顯示支付中間頁(yè),不會(huì)影響H5單頁(yè)面應(yīng)用的路由。APP不需要集成支付SDK,可以繞過蘋果掃描代碼。
由于支付寶支付流程改成和微信一樣,所以支付寶網(wǎng)頁(yè)支付功能被砍掉,只能通過打開支付寶APP去支付。這也是這種方案的不足之處。
iOS-APP實(shí)現(xiàn)微信H5支付總結(jié)
到此這篇關(guān)于iOS實(shí)現(xiàn)H5支付(微信、支付寶)原生封裝的文章就介紹到這了,更多相關(guān)iOS H5支付內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
iOS開發(fā)中UITabBarController的使用示例
這篇文章主要介紹了iOS開發(fā)中UITabBarController的使用示例,代碼基于Objective-C進(jìn)行演示,需要的朋友可以參考下2015-09-09在iOS應(yīng)用中使用UIWebView創(chuàng)建簡(jiǎn)單的網(wǎng)頁(yè)瀏覽器界面
這篇文章主要介紹了在iOS應(yīng)用中使用UIWebView創(chuàng)建簡(jiǎn)單的網(wǎng)頁(yè)瀏覽器界面的方法,包括動(dòng)態(tài)獲取UIWebView高度的實(shí)現(xiàn),需要的朋友可以參考下2016-01-01Xcode 10升級(jí)導(dǎo)致項(xiàng)目報(bào)錯(cuò)的常見問題解決
這篇文章主要給大家介紹了關(guān)于Xcode 10升級(jí)導(dǎo)致項(xiàng)目報(bào)錯(cuò)的常見問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12詳解iOS應(yīng)用UI開發(fā)中的九宮格坐標(biāo)計(jì)算與字典轉(zhuǎn)換模型
這篇文章主要介紹了iOS應(yīng)用UI開發(fā)中的九宮格坐標(biāo)計(jì)算與字典轉(zhuǎn)換模型,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-01-01iOS自定義相機(jī)實(shí)現(xiàn)拍照、錄制視頻
這篇文章主要為大家詳細(xì)介紹了iOS自定義相機(jī)實(shí)現(xiàn)拍照、錄制視頻,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04iOS DispatchSourceTimer 定時(shí)器的具體使用
定時(shí)器在很多地方都可以用到,本文主要介紹了iOS DispatchSourceTimer 定時(shí)器的具體使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05