iOS實(shí)現(xiàn)H5支付(微信、支付寶)原生封裝
前言
支付分APP支付、H5支付、掃碼支付等。app支付一般在app中使用,并且需要集成相應(yīng)的支付SDK,H5支付多用于網(wǎng)頁。如果你的APP不想集成支付SDK,又想實(shí)現(xiàn)支付功能,你可以在項(xiàng)目中使用H5支付。本文主要講述如何將H5支付封裝成一個(gè)原生可調(diào)用的組件。
1.H5支付流程
注:以下為網(wǎng)頁H5支付流程,原生調(diào)用需要修改部分流程
1.1 微信支付
- 統(tǒng)一下單,獲取微信中間頁地址mweb_url
- 頁面重定向到微信中間頁
- 微信中間頁發(fā)起支付請(qǐng)求
- safari瀏覽器攔截支付請(qǐng)求打開微信APP開始支付(如果在app中,需要在shouldStartLoadWithRequest:方法里面攔截支付請(qǐng)求,并打開微信)
微信中間頁重新向到redirect_url
1.2 支付寶支付
- 發(fā)起網(wǎng)頁支付請(qǐng)求,H5為一個(gè)form表單提交。
- 頁面重定向到支付寶收銀臺(tái)頁面
- 發(fā)起APP支付請(qǐng)求,并且開始倒計(jì)時(shí),如果打開支付寶超時(shí)頁面跳轉(zhuǎn)到網(wǎng)頁支付界面,如果喚起支付寶,倒計(jì)時(shí)結(jié)束。
- 支付完畢頁面跳轉(zhuǎn)到return_url頁面,需用戶手動(dòng)觸發(fā)。
2.原生封裝思路
新開一個(gè)webView加載支付中間頁,攔截中間頁支付請(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)致支付寶頁面不能喚起支付請(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頁會(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{
//頁面加載失敗,返回錯(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為微信中間頁地址和支付寶form表單腳本。如:
微信: https://wx.tenpay.com ?xxxx
支付寶:<form id=" alipaysubmit " name="alipaysubmit" action=xxxx></form><script>document.forms[' alipaysubmit '].submit();</script>
見1.H5支付流程,微信下單之后可以獲取中間頁地址,支付則需要form表單提交加載中間頁。
3.2錯(cuò)誤處理
typedef NS_ENUM(NSUInteger,HJH5SendWebPayResult) {
HJH5SendWebPayResultSuccess = 0, //喚起登錄成功
HJH5SendWebPayResultLoadFail, //支付頁面加載失敗
HJH5SendWebPayResultSendFail, //調(diào)起支付失敗,可能是沒添加未安裝微信或者支付寶
HJH5SendWebPayResultOther //其他
};
支付請(qǐng)求發(fā)送成功則表示這次H5支付發(fā)起完成,具體支付結(jié)果需要查詢后臺(tái)獲得。所以需要對(duì)一些異常情況進(jìn)行處理,比如頁面加載失敗,微信或支付寶未安裝等異常進(jìn)行處理。
4.說明
這種方案可以統(tǒng)一微信和支付寶H5支付的流程,并且隱式地顯示支付中間頁,不會(huì)影響H5單頁面應(yīng)用的路由。APP不需要集成支付SDK,可以繞過蘋果掃描代碼。
由于支付寶支付流程改成和微信一樣,所以支付寶網(wǎng)頁支付功能被砍掉,只能通過打開支付寶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毛玻璃效果的實(shí)現(xiàn)及圖片模糊效果的三種方法
App設(shè)計(jì)時(shí)往往會(huì)用到一些模糊效果或者毛玻璃效果,iOS目前已提供一些模糊API可以讓我們方便是使用,本文給大家介紹iOS毛玻璃效果的實(shí)現(xiàn)及圖片模糊效果的三種方法,感興趣的朋友一起學(xué)習(xí)吧2016-01-01
深入詳解Objective-C中的@Synchronized關(guān)鍵字
這篇文章主要為大家介紹了深入詳解Objective-C中的@Synchronized關(guān)鍵字,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
Objective-C 實(shí)現(xiàn)2048算法類
本文給大家介紹的是使用Objective-C 實(shí)現(xiàn)的IOS版小游戲2048算法類,十分的實(shí)用,有需要的小伙伴可以參考下。2015-06-06
iOS?RN啟動(dòng)中管理Native?Module詳解
這篇文章主要為大家介紹了iOS?RN啟動(dòng)中?Native?Module?是如何被管理的,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
iOS中設(shè)置清除緩存功能的實(shí)現(xiàn)方法
清除緩存基本上都是在設(shè)置界面的某一個(gè)Cell,于是我們可以把清除緩存封裝在某一個(gè)自定義Cell中,現(xiàn)在位大家介紹一種最基礎(chǔ)的清除緩存的方法,感興趣的朋友一起看看吧2017-07-07
ios中g(shù)etTime()的兼容性實(shí)例代碼
在本篇文章里小編給大家整理的是關(guān)于ios中g(shù)etTime()的兼容性實(shí)例代碼,需要的朋友們可以學(xué)習(xí)下。2020-03-03
iOS中實(shí)現(xiàn)簡單易懂秒殺倒計(jì)時(shí)/倒計(jì)時(shí)代碼
這篇文章主要介紹了iOS中實(shí)現(xiàn)簡單易懂秒殺倒計(jì)時(shí)/倒計(jì)時(shí)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02
iOS開發(fā)Quick Actions創(chuàng)建桌面Icon快捷方式
在本文里我們給大家分享了關(guān)于iOS開發(fā)Quick Actions創(chuàng)建桌面Icon快捷方式的相關(guān)知識(shí)點(diǎn)內(nèi)容,需要的讀者們可以參考下。2019-05-05
Objective-C中block循環(huán)引用問題詳解
這篇文章主要給大家介紹了關(guān)于Objective-C中block循環(huán)引用問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Objective-C具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06

