iOS 攔截h5 調(diào)起原生支付

1.背景

最近畫啦啦app打算接入直播買課一個(gè)功能,希望在app端通過配置一個(gè)購買鏈接,然后通過鏈接進(jìn)入支付界面調(diào)起原生的支付寶和微信支付功能。以前接觸的都是通過客戶端的sdk接入,這次的做法是通過攔截h5機(jī)制,然后調(diào)用原生支付。

2.微信支付原理

微信支付.png

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

  • 1.用戶在商戶側(cè)完成下單,使用微信支付進(jìn)行支付

  • 2.由商戶后臺(tái)向微信支付發(fā)起下單請(qǐng)求(調(diào)用統(tǒng)一下單接口)注:交易類型trade_type=MWEB

  • 3.統(tǒng)一下單接口返回支付相關(guān)參數(shù)給商戶后臺(tái),如支付跳轉(zhuǎn)url(參數(shù)名“mweb_url”),商戶通過mweb_url調(diào)起微信支付中間頁

  • 4.中間頁進(jìn)行H5權(quán)限的校驗(yàn),安全性檢查

  • 5.如支付成功,商戶后臺(tái)會(huì)接收到微信側(cè)的異步通知

  • 6.用戶在微信支付收銀臺(tái)完成支付或取消支付,返回商戶頁面(默認(rèn)為返回支付發(fā)起頁面),所以這里需要配置回調(diào)地址

  • 7.商戶在展示頁面,引導(dǎo)用戶主動(dòng)發(fā)起支付結(jié)果的查詢

  • 8~9.商戶后臺(tái)判斷是否接收到微信側(cè)的支付結(jié)果通知,如沒有,后臺(tái)調(diào)用我們的訂單查詢接口確認(rèn)訂單狀態(tài)(查單實(shí)現(xiàn)可參考:支付回調(diào)和查單實(shí)現(xiàn)指引

  • 10.展示最終的訂單支付結(jié)果給用戶

2.1 iOS端h5攔截調(diào)起原生微信支付

如果是在微信內(nèi)打開的話,是通過wechat-js的原理,會(huì)自動(dòng)打開微信進(jìn)行支付,但是若想在畫啦啦app里面通過鏈接打開微信支付,則需要通過webview 的代理方法,解析url的過程,對(duì)url進(jìn)行判斷,如果符合微信的鏈接,則接管支付需求,調(diào)起原生app支付。下面是具體的流程圖


攔截微信.png

文字描述

  • 1.通過MKWebView 代理方法攔截mweb_url
  • 2.配置好Referer和redirect_url,目的是為了可以返回應(yīng)用app和通過授權(quán)域名
  • 3.接收到支付通知(weixin://wap/pay),調(diào)用微信支付
  • 4.付款完成后,通過第2步設(shè)置的redirect_url 返回到app(app記得配置scheme和白名單)
Referer:HTTP Referer是header的一部分,當(dāng)發(fā)起頁面請(qǐng)求的時(shí)候,一般會(huì)帶上Referer,告訴服務(wù)器來源頁面,微信中間頁會(huì)對(duì)Referer進(jìn)行校驗(yàn),非安全域名將不能正常加載,目前我們配置的后臺(tái)是一級(jí)域名61info.cn
redirect_url :是微信中間頁喚起支付之后,頁面重定向的地址。中間頁喚起微信支付后會(huì)跳轉(zhuǎn)到指定的redirect_url,并且微信App在支付完成時(shí),也是通過redirect_url回調(diào)結(jié)果,redirect_url一般是一個(gè)頁面地址,所以微信支付完成打開safari瀏覽器,因此我們需要通過修改redirect_url,實(shí)現(xiàn)支付完畢跳轉(zhuǎn)回當(dāng)前APP
 
注意:微信會(huì)校驗(yàn)Referer(來源)和redirect_url(目標(biāo))是否是安全域名。如果不傳redirect_url,微信會(huì)將Referer當(dāng)成redirect_url,喚起支付之后會(huì)重定向到Referer對(duì)應(yīng)的頁面。
建議帶上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,否則會(huì)打開瀏覽器`

`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``];`

`//設(shè)置授權(quán)域名,偽造Referer頭,因?yàn)槲⑿胖虚g頁會(huì)檢驗(yàn)Referer頭,并且Referer對(duì)應(yīng)的值需要包含安全域名`

`[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網(wǎng)站下單支付后,商品系統(tǒng)調(diào)用alipay.trade.wap.pay 接口參數(shù)生成訂單數(shù)據(jù),然后在前端頁面通過Form表單的形式向支付寶系統(tǒng)發(fā)送支付請(qǐng)求。
  • 2.此時(shí)支付寶會(huì)自動(dòng)將頁面跳轉(zhuǎn)至支付寶H5收銀臺(tái)頁面(攔截就在這一步處理)
  • 3.用戶在支付寶App或者H5收銀臺(tái)支付完成后,會(huì)根據(jù)商戶在手機(jī)網(wǎng)站支付API中傳入的前臺(tái)回調(diào)地址return_url 自動(dòng)跳轉(zhuǎn)回商戶頁面,同時(shí)在URL請(qǐng)求中以Query String的形式附帶上支付結(jié)果參數(shù)
3.1 iOS端h5攔截調(diào)起原生支付寶支付

原理和攔截微信支付差不多的,流程圖


攔截支付寶.png

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

3.1.1 支付寶具體攔截OC代碼
NSString *decodeAlipayUrl = [originUrl stringByURLDecode];
  NSRange alipayRange = [decodeAlipayUrl rangeOfString:@"openapi.alipay.com"];
  //支付寶H5調(diào)用
  if ([url.scheme isEqualToString:@"alipay"]) {
      //  1.以?號(hào)來切割字符串
      NSArray *urlBaseArr = [originUrl componentsSeparatedByString:@"?"];
      NSString *urlBaseStr = urlBaseArr.firstObject;
      NSString *urlNeedDecode = urlBaseArr.lastObject;
      //  2.將截取以后的Str,做一下URLDecode,方便我們處理數(shù)據(jù)
      NSMutableString *afterDecodeStr = [NSMutableString stringWithString:[urlNeedDecode stringByURLDecode]];
      //  3.替換里面的默認(rèn)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.遇到的坑

商家參數(shù)格式有誤,請(qǐng)聯(lián)系商家解決

商家參數(shù)格式有誤,請(qǐng)聯(lián)系商家解決.jpg

出現(xiàn)這種情況可能是如下兩個(gè)原因:

  • 沒有設(shè)置Referer
  • 設(shè)置的域名不可行,在開發(fā)者后臺(tái)沒有配置
    解決問題的思路:查看是否沒有設(shè)置Referer,和開發(fā)者后臺(tái)配置的域名是否生效

支付之后沒有返回APP

出現(xiàn)這種情況可能是如下兩個(gè)原因:

  • Referer沒有設(shè)置成可靠域名://
  • scheme沒有設(shè)置成可靠域名
    解決問題的思路:檢查Referer和scheme

多APP的問題

出現(xiàn)這種情況可能是如下原因:

一個(gè)公司多個(gè)app,而一個(gè)用戶安裝了多個(gè)同一個(gè)公司的app,如果使用同一個(gè)安全域名的話,那么在支付回調(diào)之后就會(huì)出現(xiàn)跳轉(zhuǎn)錯(cuò)亂的問題
解決問題思路:如果你有兩款以上APP,比如(App1,App2,App3),并都把referer設(shè)置成pay-test.61info.cn,scheme 設(shè)置成pay-test.61info.cn,那么用戶只如果只安裝一臺(tái)APP1,此時(shí)支付能夠成功,并能轉(zhuǎn)回原APP1,一點(diǎn)問題都沒有。如果通過安裝了App1,App2,就會(huì)發(fā)現(xiàn)支付能夠成功,但有可能不能跳回原來的App,嚴(yán)重影響了用戶體驗(yàn),所以想到的第一個(gè)解決方案,就是更改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://

經(jīng)過測試,發(fā)現(xiàn)根本不起任何作用,但是想深入一層,微信不至于不給用戶通道呀,登錄到開發(fā)者后臺(tái)觀察一下:

微信開發(fā)者后臺(tái).png

可以看到上面是可以配置5個(gè)支付域名的,咨詢運(yùn)營中心的同事,目前我們配置的是一個(gè)頂級(jí)域名61info.cn,既然這樣的話,我們能否自己偽造一個(gè)假域名來處理呢,經(jīng)過更改變成如下處理方式:

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 來驗(yàn)證一下:

  • 能通過驗(yàn)證


    postman驗(yàn)證.jpg
  • 不能通過驗(yàn)證


    postman驗(yàn)證不通過.jpg

    通過測試結(jié)果發(fā)現(xiàn),這樣設(shè)置,可以完美達(dá)到我們的交互要求!

5.參考鏈接

微信支付
支付寶支付

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容