iOS实现H5支付(微信、支付宝)原生封装

 更新时间:2020年6月30日 23:42  点击:1385

前言

支付分APP支付、H5支付、扫码支付等。app支付一般在app中使用,并且需要集成相应的支付SDK,H5支付多用于网页。如果你的APP不想集成支付SDK,又想实现支付功能,你可以在项目中使用H5支付。本文主要讲述如何将H5支付封装成一个原生可调用的组件。

1.H5支付流程

注:以下为网页H5支付流程,原生调用需要修改部分流程

1.1 微信支付

  • 统一下单,获取微信中间页地址mweb_url
  • 页面重定向到微信中间页
  • 微信中间页发起支付请求
  • safari浏览器拦截支付请求打开微信APP开始支付(如果在app中,需要在shouldStartLoadWithRequest:方法里面拦截支付请求,并打开微信)

微信中间页重新向到redirect_url

1.2 支付宝支付

  • 发起网页支付请求,H5为一个form表单提交。
  • 页面重定向到支付宝收银台页面
  • 发起APP支付请求,并且开始倒计时,如果打开支付宝超时页面跳转到网页支付界面,如果唤起支付宝,倒计时结束。
  • 支付完毕页面跳转到return_url页面,需用户手动触发。

2.原生封装思路

新开一个webView加载支付中间页,拦截中间页支付请求并唤起支付,然后关闭webView流程结束。

webView需要加到window(或者当前控制器的view上),并设置一个大小(肉眼不可见就行)。因为使用wkwebview时,webView不显示的情况下,H5请求会被挂起,会导致支付宝页面不能唤起支付请求。

3.代码实现

具体步骤见代码注释

@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,否则会打开Safari浏览器(因为redirect_url一般为一个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];
 //设置授权域名,伪造Referer头,因为微信中间页会检验Referer头,并且Referer对应的值需要包含安全域名
 [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对应的应该是一段form表单提交脚本,需要调用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,返回错误
 handle(HJH5SendWebPayResultOther);
 return;
 }
 
 
 //容错处理,20秒没唤起支付,当错误处理。
 __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{
//页面加载失败,返回错误
 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;
 //拦截微信支付请求,并打开微信
 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){
 //拦截支付宝支付请求,并且替换fromAppUrlScheme参数为当前APP的scheme,实现支付完毕返回APP功能。
 NSString *aliScheme = @"支付宝支付scheme,支付完毕可通过scheme返回到当前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入参说明

调用该方法唤起支付-(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错误处理

typedef NS_ENUM(NSUInteger,HJH5SendWebPayResult) {
 HJH5SendWebPayResultSuccess = 0, //唤起登录成功
 HJH5SendWebPayResultLoadFail, //支付页面加载失败
 HJH5SendWebPayResultSendFail, //调起支付失败,可能是没添加未安装微信或者支付宝
 HJH5SendWebPayResultOther //其他
};

支付请求发送成功则表示这次H5支付发起完成,具体支付结果需要查询后台获得。所以需要对一些异常情况进行处理,比如页面加载失败,微信或支付宝未安装等异常进行处理。

4.说明

这种方案可以统一微信和支付宝H5支付的流程,并且隐式地显示支付中间页,不会影响H5单页面应用的路由。APP不需要集成支付SDK,可以绕过苹果扫描代码。

由于支付宝支付流程改成和微信一样,所以支付宝网页支付功能被砍掉,只能通过打开支付宝APP去支付。这也是这种方案的不足之处。

iOS-APP实现微信H5支付总结 

到此这篇关于iOS实现H5支付(微信、支付宝)原生封装的文章就介绍到这了,更多相关iOS H5支付内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • MySQL性能监控软件Nagios的安装及配置教程

    这篇文章主要介绍了MySQL性能监控软件Nagios的安装及配置教程,这里以CentOS操作系统为环境进行演示,需要的朋友可以参考下...2015-12-14
  • iOS APP h5快捷程序 .mobileconfig的生成

    1.从APP Store 下载Apple Configurator 2从一个管理点管理所有iOS设备应用程序,文档和配置文件。想要确保您的所有家庭成员在其每台iOS设备上都有类似的应用和文档,管理日益增...2021-12-23
  • iOS设置UIButton文字显示位置和字体大小、颜色的方法

    这篇文章给大家分享了iOS如何设置UIButton的文字显示位置和字体的大小、颜色,文中给出了示例代码,相信对大家的学习和理解很有帮助,有需要的朋友们下面来一起看看吧。...2020-06-30
  • iOS如何将图片裁剪成圆形

    这篇文章主要为大家详细介绍了iOS如何将图片裁剪成圆形,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-30
  • iOS给border设置渐变色的方法实例

    这篇文章主要给大家介绍了关于iOS给border设置渐变色的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-09
  • C#微信开发之发送模板消息

    这篇文章主要为大家详细介绍了C#微信开发之发送模板消息的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-25
  • JavaScript仿支付宝密码输入框

    那么今天我就用JavaScript代码来实现这个效果吧,那么首先介绍一下整个的思路,首先我们先将确定输入密码的位数,我的需求是5位,那么就用一个div标签包住5个input标签...2016-01-02
  • iOS新版微信底部返回横条问题的解决

    这篇文章主要介绍了iOS新版微信底部返回横条问题的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
  • 基于C#实现微信支付宝扫码支付功能

    为公司系统业务需要,这几天了解了一下微信和支付宝扫码支付的接口,并用c#实现了微信和支付宝扫码支付的功能。需要的朋友跟随小编一起看看吧...2020-06-25
  • Python爬取微信小程序通用方法代码实例详解

    这篇文章主要介绍了Python爬取微信小程序通用方法代码实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-09-29
  • C#实现的微信网页授权操作逻辑封装示例

    这篇文章主要介绍了C#实现的微信网页授权操作逻辑封装,分析了微信网页授权操作的原理、步骤并给出了C#实现的网页授权操作逻辑封装类,需要的朋友可以参考下...2020-06-25
  • vue+axios全局添加请求头和参数操作

    这篇文章主要介绍了vue+axios全局添加请求头和参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-24
  • iOS蓝牙设备名称缓存问题的解决方法

    这篇文章主要给大家介绍了关于iOS蓝牙设备名称缓存问题的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-12-08
  • iOS新版微信底部工具栏遮挡问题完美解决

    这篇文章主要介绍了iOS新版微信底部工具栏遮挡问题完美解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
  • C#图像识别 微信跳一跳机器人

    这篇文章主要为大家详细介绍了C#图像识别,微信跳一跳机器人,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-25
  • iOS新增绘制圆的方法实例代码

    这篇文章主要给大家介绍了关于iOS新增绘制圆的方法,文中通过示例代码介绍的非常详细,对各位iOS开发者们具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-30
  • iOS UIBezierPath实现饼状图

    这篇文章主要为大家详细介绍了iOS UIBezierPath实现饼状图,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-03-20
  • 封装 axios+promise通用请求函数操作

    这篇文章主要介绍了封装 axios+promise通用请求函数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-12
  • 简单用VBS调用企业微信机器人发定时消息的方法

    这篇文章主要介绍了简单用VBS调用企业微信机器人发定时消息的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-12-08
  • IOS获取各种文件目录路径的方法

    ios获取文件路径的方法,iphone沙箱模型的四个文件夹,通过documents,tmp,app,Library得到模拟器路径的简单方式,下面小编整理相关资料,把IOS获取各种文件目录路径的方式总结如下,需要的朋友可以参考下...2020-06-30