iOS 拦截h5 调起原生支付

1.背景

最近画啦啦app打算接入直播买课一个功能,希望在app端通过配置一个购买链接,然后通过链接进入支付界面调起原生的支付宝和微信支付功能。以前接触的都是通过客户端的sdk接入,这次的做法是通过拦截h5机制,然后调用原生支付。

2.微信支付原理

微信支付.png

用文字描述,大概的流程如下:

  • 1.用户在商户侧完成下单,使用微信支付进行支付

  • 2.由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB

  • 3.统一下单接口返回支付相关参数给商户后台,如支付跳转url(参数名“mweb_url”),商户通过mweb_url调起微信支付中间页

  • 4.中间页进行H5权限的校验,安全性检查

  • 5.如支付成功,商户后台会接收到微信侧的异步通知

  • 6.用户在微信支付收银台完成支付或取消支付,返回商户页面(默认为返回支付发起页面),所以这里需要配置回调地址

  • 7.商户在展示页面,引导用户主动发起支付结果的查询

  • 8~9.商户后台判断是否接收到微信侧的支付结果通知,如没有,后台调用我们的订单查询接口确认订单状态(查单实现可参考:支付回调和查单实现指引)

  • 10.展示最终的订单支付结果给用户

2.1 iOS端h5拦截调起原生微信支付

如果是在微信内打开的话,是通过wechat-js的原理,会自动打开微信进行支付,但是若想在画啦啦app里面通过链接打开微信支付,则需要通过webview 的代理方法,解析url的过程,对url进行判断,如果符合微信的链接,则接管支付需求,调起原生app支付。下面是具体的流程图


拦截微信.png

文字描述

  • 1.通过MKWebView 代理方法拦截mweb_url
  • 2.配置好Referer和redirect_url,目的是为了可以返回应用app和通过授权域名
  • 3.接收到支付通知(weixin://wap/pay),调用微信支付
  • 4.付款完成后,通过第2步设置的redirect_url 返回到app(app记得配置scheme和白名单)
Referer:HTTP Referer是header的一部分,当发起页面请求的时候,一般会带上Referer,告诉服务器来源页面,微信中间页会对Referer进行校验,非安全域名将不能正常加载,目前我们配置的后台是一级域名61info.cn
redirect_url :是微信中间页唤起支付之后,页面重定向的地址。中间页唤起微信支付后会跳转到指定的redirect_url,并且微信App在支付完成时,也是通过redirect_url回调结果,redirect_url一般是一个页面地址,所以微信支付完成打开safari浏览器,因此我们需要通过修改redirect_url,实现支付完毕跳转回当前APP
 
注意:微信会校验Referer(来源)和redirect_url(目标)是否是安全域名。如果不传redirect_url,微信会将Referer当成redirect_url,唤起支付之后会重定向到Referer对应的页面。
建议带上redirect_url
2.1.1 微信具体拦截OC代码
`if` `([originUrl rangeOfString:@``"[https://wx.tenpay.com](https://wx.tenpay.com/)"``].location != NSNotFound) {`

`NSDictionary *params = [originUrl getUrlParams];`

`NSString *backUrl = params[@``"redirect_url"``];`

`NSURL *redirURL = nil;`

`if` `(backUrl && [backUrl isKindOfClass:[NSString ``class``]] && backUrl.length > ``0``) {`

`redirURL = [NSURL URLWithString:backUrl];`

`}`

`if` `(!wxScheme || ![wxScheme isKindOfClass:[NSString ``class``]] || wxScheme.length <= ``0``) {`

`return` `YES;`

`}`

`NSString *referer = [NSString stringWithFormat:@``"%@://"``, wxScheme];`

`if` `([backUrl isEqualToString:referer]) {`

`return` `YES;`

`} ``else` `{`

`self.redirectUrl = [backUrl stringByURLDecode];`

`dispatch_async(dispatch_get_main_queue(), ^{`

`NSRange range = [originUrl rangeOfString:@``"redirect_url="``];`

`NSString *reqUrl;`

`//将redirect_url 替换成scheme,微信支付完毕才能跳回App,否则会打开浏览器`

`if` `(range.length > ``0``) {`

`reqUrl = [originUrl substringToIndex:range.location + range.length];`

`reqUrl = [reqUrl stringByAppendingString:referer];`

`} ``else` `{`

`reqUrl = [originUrl stringByAppendingString:[NSString stringWithFormat:@``"&redirect_url=%@"``, referer]];`

`}`

`NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:reqUrl] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:``60.0``];`

`//设置授权域名,伪造Referer头,因为微信中间页会检验Referer头,并且Referer对应的值需要包含安全域名`

`[request setValue:referer forHTTPHeaderField:@``"Referer"``];`

`[webView loadRequest:request];`

`});`

`return` `NO;`

`}`

`} ``else` `if` `([originUrl 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];`

`}`

`}`

`dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(``3` `* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{`

`if` `(self.redirectUrl) {`

`self.redirectUrl = [self.redirectUrl stringByURLDecode];`

`NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.redirectUrl] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:``60.0``];`

`[webView loadRequest:request];`

`self.redirectUrl = nil;`

`}`

`});`

`return` `NO;`

`}`

2.1.2 Xcode 配置
CFBundleURLTypes
 
     
         CFBundleTypeRole
         Editor
         CFBundleURLName
         wxPay
         CFBundleURLSchemes
         
微信scheme(安全域名) 
     
 
 
LSApplicationQueriesSchemes
 
     wechat
     weixin
 

如下图所示:


微信infoplist配置.png

3.支付宝支付流程

支付宝支付流程.png

用文字描述,大致如下:

  • 1.用户在商户的h5网站下单支付后,商品系统调用alipay.trade.wap.pay 接口参数生成订单数据,然后在前端页面通过Form表单的形式向支付宝系统发送支付请求。
  • 2.此时支付宝会自动将页面跳转至支付宝H5收银台页面(拦截就在这一步处理)
  • 3.用户在支付宝App或者H5收银台支付完成后,会根据商户在手机网站支付API中传入的前台回调地址return_url 自动跳转回商户页面,同时在URL请求中以Query String的形式附带上支付结果参数
3.1 iOS端h5拦截调起原生支付宝支付

原理和拦截微信支付差不多的,流程图


拦截支付宝.png

原理大致和微信相差不大,所以这里不作解释

3.1.1 支付宝具体拦截OC代码
NSString *decodeAlipayUrl = [originUrl stringByURLDecode];
  NSRange alipayRange = [decodeAlipayUrl rangeOfString:@"openapi.alipay.com"];
  //支付宝H5调用
  if ([url.scheme isEqualToString:@"alipay"]) {
      //  1.以?号来切割字符串
      NSArray *urlBaseArr = [originUrl componentsSeparatedByString:@"?"];
      NSString *urlBaseStr = urlBaseArr.firstObject;
      NSString *urlNeedDecode = urlBaseArr.lastObject;
      //  2.将截取以后的Str,做一下URLDecode,方便我们处理数据
      NSMutableString *afterDecodeStr = [NSMutableString stringWithString:[urlNeedDecode stringByURLDecode]];
      //  3.替换里面的默认Scheme为自己的Scheme
      NSString *afterHandleStr = [afterDecodeStr stringByReplacingOccurrencesOfString:@"alipays" withString:@"ioshllcourselive"];
      //  4.然后把处理后的,和最开始切割的做下拼接,就得到了最终的字符串
      NSString *finalStr = [NSString stringWithFormat:@"%@?%@", urlBaseStr, [afterHandleStr stringByURLEncode]];
      NSURL *toPaymentUrl = [NSURL URLWithString:finalStr];
      if ([[UIApplication sharedApplication] canOpenURL:toPaymentUrl]) {
          if (@available(iOS 10.0, *)) {
              [[UIApplication sharedApplication] openURL:toPaymentUrl options:@{} completionHandler:nil];
          } else {
              [[UIApplication sharedApplication] openURL:toPaymentUrl];
          }
      }
 
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          if (self.redirectUrl) {
              self.redirectUrl = [self.redirectUrl stringByURLDecode];
              NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.redirectUrl] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
              [webView loadRequest:request];
              self.redirectUrl = nil;
          }
      });
      //  2.这里告诉页面不走了 -_-
      return NO;
  } else if (alipayRange.location != NSNotFound) {
      self.redirectUrl = [[decodeAlipayUrl getUrlValueWithKey:@"return_url"] stringByURLDecode];
  }
3.1.2 Xcode 配置
Info.plist配置
LSApplicationQueriesSchemes
 
     wechat
     weixin
 

如下图所示


支付宝infoplist配置.png

4.遇到的坑

商家参数格式有误,请联系商家解决

商家参数格式有误,请联系商家解决.jpg

出现这种情况可能是如下两个原因:

  • 没有设置Referer
  • 设置的域名不可行,在开发者后台没有配置
    解决问题的思路:查看是否没有设置Referer,和开发者后台配置的域名是否生效

支付之后没有返回APP

出现这种情况可能是如下两个原因:

  • Referer没有设置成可靠域名://
  • scheme没有设置成可靠域名
    解决问题的思路:检查Referer和scheme

多APP的问题

出现这种情况可能是如下原因:

一个公司多个app,而一个用户安装了多个同一个公司的app,如果使用同一个安全域名的话,那么在支付回调之后就会出现跳转错乱的问题
解决问题思路:如果你有两款以上APP,比如(App1,App2,App3),并都把referer设置成pay-test.61info.cn,scheme 设置成pay-test.61info.cn,那么用户只如果只安装一台APP1,此时支付能够成功,并能转回原APP1,一点问题都没有。如果通过安装了App1,App2,就会发现支付能够成功,但有可能不能跳回原来的App,严重影响了用户体验,所以想到的第一个解决方案,就是更改referer与scheme

APP APP1 APP2 APP3
referer pay-test.61inf.cn/App1:// pay-test.61inf.cn/App2:// pay-test.61inf.cn/App3://
scheme test.61inf.cn/App1:// test.61inf.cn/App2:// test.61inf.cn/App3://

经过测试,发现根本不起任何作用,但是想深入一层,微信不至于不给用户通道呀,登录到开发者后台观察一下:

微信开发者后台.png

可以看到上面是可以配置5个支付域名的,咨询运营中心的同事,目前我们配置的是一个顶级域名61info.cn,既然这样的话,我们能否自己伪造一个假域名来处理呢,经过更改变成如下处理方式:

APP APP1 APP2 APP3
referer app1.61info.cn:// app2.61info.cn:// app3.61info.cn://
scheme app1.61info.cn app2.61info.cn app3.61info.cn

通过postman 来验证一下:

  • 能通过验证


    postman验证.jpg
  • 不能通过验证


    postman验证不通过.jpg

    通过测试结果发现,这样设置,可以完美达到我们的交互要求!

5.参考链接

微信支付
支付宝支付

你可能感兴趣的:(iOS 拦截h5 调起原生支付)