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 配置
<key>CFBundleURLTypes</key>
 <array>
     <dict>
         <key>CFBundleTypeRole</key>
         <string>Editor</string>
         <key>CFBundleURLName</key>
         <string>wxPay</string>
         <key>CFBundleURLSchemes</key>
         <array>
<string>微信scheme(安全域名)</string> </array>
     </dict>
 </array>
 
<key>LSApplicationQueriesSchemes</key>
 <array>
     <string>wechat</string>
     <string>weixin</string>
 
</array>

如下图所示:


微信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配置
<key>LSApplicationQueriesSchemes</key>
 <array>
     <string>wechat</string>
     <string>weixin</string>
 
</array>

如下图所示


支付宝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.参考链接

微信支付
支付宝支付

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容