1.背景
最近画啦啦app打算接入直播买课一个功能,希望在app端通过配置一个购买链接,然后通过链接进入支付界面调起原生的支付宝和微信支付功能。以前接触的都是通过客户端的sdk接入,这次的做法是通过拦截h5机制,然后调用原生支付。
2.微信支付原理
用文字描述,大概的流程如下:
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支付。下面是具体的流程图
文字描述
- 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
如下图所示:
3.支付宝支付流程
用文字描述,大致如下:
- 1.用户在商户的h5网站下单支付后,商品系统调用alipay.trade.wap.pay 接口参数生成订单数据,然后在前端页面通过Form表单的形式向支付宝系统发送支付请求。
- 2.此时支付宝会自动将页面跳转至支付宝H5收银台页面(拦截就在这一步处理)
- 3.用户在支付宝App或者H5收银台支付完成后,会根据商户在手机网站支付API中传入的前台回调地址return_url 自动跳转回商户页面,同时在URL请求中以Query String的形式附带上支付结果参数
3.1 iOS端h5拦截调起原生支付宝支付
原理和拦截微信支付差不多的,流程图
原理大致和微信相差不大,所以这里不作解释
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
如下图所示
4.遇到的坑
商家参数格式有误,请联系商家解决
出现这种情况可能是如下两个原因:
- 没有设置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:// |
经过测试,发现根本不起任何作用,但是想深入一层,微信不至于不给用户通道呀,登录到开发者后台观察一下:
可以看到上面是可以配置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 来验证一下:
-
能通过验证
-
不能通过验证
通过测试结果发现,这样设置,可以完美达到我们的交互要求!
5.参考链接
微信支付
支付宝支付