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

    前言

    支付分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支付内容请搜索lingkb以前的文章或继续浏览下面的相关文章希望大家以后多多支持lingkb!