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)求打開(kāi)微信APP開(kāi)始支付(如果在app中,需要在shouldStartLoadWithRequest:方法里面攔截支付請(qǐng)求,并打開(kāi)微信)
微信中間頁(yè)重新向到redirect_url
1.2 支付寶支付
- 發(fā)起網(wǎng)頁(yè)支付請(qǐng)求,H5為一個(gè)form表單提交。
- 頁(yè)面重定向到支付寶收銀臺(tái)頁(yè)面
- 發(fā)起APP支付請(qǐng)求,并且開(kāi)始倒計(jì)時(shí),如果打開(kāi)支付寶超時(shí)頁(yè)面跳轉(zhuǎn)到網(wǎng)頁(yè)支付界面,如果喚起支付寶,倒計(jì)時(shí)結(jié)束。
- 支付完畢頁(yè)面跳轉(zhuǎn)到return_url頁(yè)面,需用戶手動(dòng)觸發(fā)。
2.原生封裝思路
新開(kāi)一個(gè)webView加載支付中間頁(yè),攔截中間頁(yè)支付請(qǐng)求并喚起支付,然后關(guān)閉webView流程結(jié)束。
webView需要加到window(或者當(dāng)前控制器的view上),并設(shè)置一個(gè)大?。ㄈ庋鄄豢梢?jiàn)就行)。因?yàn)槭褂脀kwebview時(shí),webView不顯示的情況下,H5請(qǐng)求會(huì)被掛起,會(huì)導(dǎo)致支付寶頁(yè)面不能喚起支付請(qǐng)求。
3.代碼實(shí)現(xiàn)
具體步驟見(jià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ì)打開(kāi)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秒沒(méi)喚起支付,當(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)求,并打開(kāi)微信
if([newUrl rangeOfString:@"weixin://wap/pay"].location != NSNotFound){
//判斷是否能打開(kāi)微信
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,支付完畢可通過(guò)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(), ^{
//判斷是否能打開(kāi)支付寶
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(shuō)明
調(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>
見(jiàn)1.H5支付流程,微信下單之后可以獲取中間頁(yè)地址,支付則需要form表單提交加載中間頁(yè)。
3.2錯(cuò)誤處理
typedef NS_ENUM(NSUInteger,HJH5SendWebPayResult) {
HJH5SendWebPayResultSuccess = 0, //喚起登錄成功
HJH5SendWebPayResultLoadFail, //支付頁(yè)面加載失敗
HJH5SendWebPayResultSendFail, //調(diào)起支付失敗,可能是沒(méi)添加未安裝微信或者支付寶
HJH5SendWebPayResultOther //其他
};
支付請(qǐng)求發(fā)送成功則表示這次H5支付發(fā)起完成,具體支付結(jié)果需要查詢后臺(tái)獲得。所以需要對(duì)一些異常情況進(jìn)行處理,比如頁(yè)面加載失敗,微信或支付寶未安裝等異常進(jìn)行處理。
4.說(shuō)明
這種方案可以統(tǒng)一微信和支付寶H5支付的流程,并且隱式地顯示支付中間頁(yè),不會(huì)影響H5單頁(yè)面應(yīng)用的路由。APP不需要集成支付SDK,可以繞過(guò)蘋(píng)果掃描代碼。
由于支付寶支付流程改成和微信一樣,所以支付寶網(wǎng)頁(yè)支付功能被砍掉,只能通過(guò)打開(kāi)支付寶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)文章希望大家以后多多支持腳本之家!
- 微信支付終于成功了(安卓、iOS)在此分享
- 微信支付開(kāi)發(fā)IOS圖文教程案例
- 解決iOS調(diào)起微信支付顯示系統(tǒng)繁忙問(wèn)題
- IOS客戶端接入微信支付
- iOS仿支付寶芝麻信用分?jǐn)?shù)儀表盤(pán)動(dòng)畫(huà)效果
- iOS開(kāi)發(fā)支付寶支付成功返回字符串的處理操作
- iOS 9.0后微信支付回調(diào)處理實(shí)例
- iOS實(shí)現(xiàn)微信支付流程詳解
- iOS支付寶支付方法詳解
- iOS支付寶、微信、銀聯(lián)支付集成封裝調(diào)用(上)
- iOS微信支付開(kāi)發(fā)案例
- IOS 仿支付寶支付屏幕亮度變化機(jī)制
相關(guān)文章
iOS開(kāi)發(fā)中UITabBarController的使用示例
這篇文章主要介紹了iOS開(kāi)發(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-01
Xcode 10升級(jí)導(dǎo)致項(xiàng)目報(bào)錯(cuò)的常見(jiàn)問(wèn)題解決
這篇文章主要給大家介紹了關(guān)于Xcode 10升級(jí)導(dǎo)致項(xiàng)目報(bào)錯(cuò)的常見(jiàn)問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12
詳解iOS應(yīng)用UI開(kāi)發(fā)中的九宮格坐標(biāo)計(jì)算與字典轉(zhuǎn)換模型
這篇文章主要介紹了iOS應(yīng)用UI開(kāi)發(fā)中的九宮格坐標(biāo)計(jì)算與字典轉(zhuǎn)換模型,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-01-01
iOS自定義相機(jī)實(shí)現(xiàn)拍照、錄制視頻
這篇文章主要為大家詳細(xì)介紹了iOS自定義相機(jī)實(shí)現(xiàn)拍照、錄制視頻,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04
iOS DispatchSourceTimer 定時(shí)器的具體使用
定時(shí)器在很多地方都可以用到,本文主要介紹了iOS DispatchSourceTimer 定時(shí)器的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05

