由于公司的业务需要,为了节省申请开通微信支付的时间和人力,公司决定使用微信h5支付。这样即节省了时间,同时以后所有的APP都能使用h5支付,既方便又快捷。
但是真正做的时候问题来了,当你支付成功之后或者是取消支付的时候会跳转到Safari浏览器,这就很尴尬了,完全无用户体验啊。当时看着Android可以直接返回到APP,心里瞬间不爽了。开始埋怨苹果公司了,但是由于公司的业务必须要做h5,没办法开始找办法解决。
首先你在Xcode中,选择你的工程设置项,选中“TARGETS”一栏,在“info”标签栏的“LSApplicationQueriesSchemes“添加weixin
然后你通过统一下单后台会从微信拿到这么个链接https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20180115115052bedf091fba0369993002&package=2975002856给你
进入微信查了一下,发现需要设置Referer这个请求头的参数,当然微信也给出了例子微信h5支付其他常见错误。针对于这个问题在网上搜索了一下发现有专门针对于微信h5支付设置Referer的文章iOSwebView设置Referer,在这里我给大家贴一下代码吧!
#pragma mark - MajordomoWKWebViewDelegate
-(void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void(^)(WKNavigationActionPolicy))decisionHandler
{
NSString *url = navigationAction.request.URL.absoluteString;
if ([url containsString:@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?"] || [url rangeOfString:@"https://wx.tenpay.com"].location != NSNotFound) {
#warning 链接不要拼接redirect_url,如果拼接了还是会返回到浏览器的
//这里把webView设置成一个像素点,主要是不影响操作和界面,主要的作用是设置referer和调起微信
WebChatPayH5VIew*h5View = [[WebChatPayH5VIewalloc]initWithFrame:CGRectMake(0,0,1,1)];
//url是没有拼接redirect_url微信h5支付链接
[h5ViewloadingURL:url withIsWebChatURL:NO];
[self.viewaddSubview:h5View];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
#warning todo -处理支付宝h5支付返回问题
NSString *urlStr = [navigationAction.request.URL.absoluteString stringByRemovingPercentEncoding];
DLOG(@"urlStr == %@",urlStr);
NSLog(@"当前加载的webView decidePolicyForNavigationAction:\n%@",webView.URL.absoluteString);
if([urlStrhasPrefix:@"alipayqr://"] || [urlStrisStartWithString:@"alipay"] || [urlStrhasPrefix:@"alipay://"]) {
NSMutableString* tmpUrlStr = urlStr.mutableCopy;
if([urlStrcontainsString:@"fromAppUrlScheme"]) {
NSDictionary* tmpDic = [selfdictionaryWithUrlString:tmpUrlStr];
DLOG(@"tmpDic == %@",tmpDic);
NSString* tmpValue = [tmpDicvalueForKey:@"fromAppUrlScheme"];
tmpUrlStr = [[tmpUrlStrstringByReplacingOccurrencesOfString:tmpValue withString:@"panda"] mutableCopy];
DLOG(@"tmpUrlStr == %@",tmpUrlStr);
tmpUrlStr = [[tmpUrlStrstringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] mutableCopy];
}
NSURL * newURl = [NSURL URLWithString:tmpUrlStr];//navigationAction.request.URL;//[NSURL URLWithString:urlStr];//
[[UIApplication sharedApplication]openURL:newURl];
}
//如果是跳转一个新页面
if(navigationAction.targetFrame==nil) {
[webViewloadRequest:navigationAction.request];
}
decisionHandler(WKNavigationActionPolicyAllow);
self.loadUrl= webView.URL.absoluteString;
}
-(NSDictionary*)dictionaryWithUrlString:(NSString*)urlStr
{
if(urlStr && urlStr.length&& [urlStrrangeOfString:@"?"].length==1) {
NSArray *array = [urlStr componentsSeparatedByString:@"?"];
if(array && array.count==2) {
NSString*paramsStr = array[1];
if(paramsStr.length) {
//paramsStr解析出来的数据:{"soeNote":"电压高超标,当前值:390.78V,大于上上限值:350.0V","name":"电压"}
NSData *jsonData = [paramsStr dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *responseDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:nil];
DLOG(@"responseDic == %@",responseDic);
returnresponseDic;
}else{
returnnil;
}
}else{
returnnil;
}
}else{
returnnil;
}
}
###############################
WebChatPayH5VIew
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL*url = [requestURL];
NSString *newUrl = url.absoluteString;
if(!self.isLoading) {
if ([newUrl rangeOfString:@"weixin://wap/pay"].location != NSNotFound) {
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[self.myWebViewloadRequest:request];
self.isLoading=YES;
returnNO;
}
}else{
if ([newUrl rangeOfString:@"weixin://wap/pay"].location != NSNotFound) {
self.myWebView=nil;
UIWebView *web = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
[selfaddSubview:web];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[webloadRequest:request];
// [[self getCurrentVC] showhide];
returnYES;
}
}
NSDictionary *headers = [request allHTTPHeaderFields];
BOOLhasReferer = [headersobjectForKey:@"Referer"] !=nil;
if(hasReferer) {
returnYES;
}else{
// relaunch with a modified request
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSURL*url = [requestURL];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
//设置授权域名 就是你们商户申请H5时提交的授权域名.
[requestsetValue:@"channel.vk7201.com://" forHTTPHeaderField:@"Referer"];//www.aglhz.com://
[self.myWebView loadRequest:request];//http://channel.vk7201.com/front/register/index.html://
});
});
returnNO;
}
}
走到这一步,你就可以加载webView进行支付了,但是问题来了,不论是支付成功还是取消支付之后他都会跳转到Safari浏览器,而且打开的内容是就是你设置Referer时的授权域名http://www.xxx.com,这个家伙就是我们的公司的主页。
那么,怎么办呢,最后我们经过协商,只要能支付,不管支付成功或者取消支付,只要停留在微信界面就可以了,然后让用户点击右上角手动返回APP,这个是没有办法的办法了。然后Android那个哥们给我生成了一个weixin://wap/pay?prepayid%3Dwx201801151450335872c8f41a0452242290&package=122735683&noncestr=1515999038&sign=0b3590852e847b336e6f0187a0f56ab1这样的链接,这个链接是通过拦截webView访问获取的,然后我试了一下,可以支付,也停留在了微信,于是我高高兴兴的去尝试了一下。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
通过webView的代理方法来获取到这个链接,然后进行支付,发现还是一样的会跳转到Safari浏览器,当时瞬间懵逼了,怎么会这个样子呢,不信邪的我又让Android那个哥们拦截一个链接给我,尝试一下不会跳转浏览器,我又试了一下自己的拦截的链接还是会跳转到Safari浏览器。我突然明白了这个是我设置了Referer,不管是用https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20180115115052bedf091fba0369993002&package=2975002856还是拦截的weixin://wap/pay?prepayid%3Dwx201801151450335872c8f41a0452242290&package=122735683&noncestr=1515999038&sign=0b3590852e847b336e6f0187a0f56ab1进行支付都是不行的,他都会跳转到Safari。
我当时想要不用TFHpple解析HTML内容吧,但是仔细一想不行的,因为头部没有Referer,无法解析。最后实在没办法了,都准备放弃了,突然想到了设置scheme,既然我可以Safari可以打开APP,那么我这肯定也能做的。于是我又信心满满的开始我的实验了。
首先要设置scheme,假如scheme设置xxxx,那么你在Safari输入xxxx://就可以打开APP了。做到这就全部明白了,只要设置好Referer和我的scheme对应就行了,然后我开始实验:
Referer设置:http://www.xxx.com scheme设置:http://www.xxx.com发现不行
我突然间发现是不是只要是带有h5的授权的域名就可以呢,于是我把公司的一个链接www.aglhz.com/sub_property_ysq/m/html/introduction.html设置成Referer之后我发现我还是能支付的,我擦,突然之间发现了一个新的大陆啊,太激动了,然后为了确保准确性进行了各种尝试,发现只要带有域名的都可以,然后我又进行了一个尝试:就是把http://去掉直接把Referer设置成:www.xxx.com,然后也是能支付的。这样就全部OK了,我把Referer设置成:www.xxx.com://这个样式的然后把scheme设置成:www.xxx.com这样的话支付成功或者取消支付都可以直接返回到APP了。
所以h5支付最终返回APP的解决方案是:把Referer设置成:www.xxx.com:// scheme设置成:www.xxx.com就可以直接返回APP了。
当然目前还没有解决多个APP同时使用同一个授权域名时,怎么返回APP的问题,多个APP使用h5支付的话会导致返回错乱的问题,如果哪位大神知道解决办法的话可以告诉我一下,谢谢!
同样你如果有多个APP被同一家客户使用的话,客户也同意用户点击左上角手动返回的话你可以这样设置Referer: www.xxx.com/test://,这样的话你支付成功会直接停留在微信,不会造成返回APP错乱的问题。
由于大家都在问微信h5支付返回的问题,所以抽空写了一个demo,针对的是webVIew和WKWebVIewdemo地址。喜欢的朋友给个star。
如果有多个APP运用到h5支付的话,返回的Scheme设置是一个问题,在这里我给大家提供一篇文章,是解决多个APP返回的问题iOS实现微信外部H5支付完成后返回原APP(多APP也可实现)
微信审核通过需要3至5个工作日。
我们假设支付域名填写的 aaa.cn,注意是顶级域名,不是二级或三级的子域名。
服务器端通过统一下单接口从微信拿到支付跳转链接(https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20180115115052bedf091fba0369993002&package=2975002856),称为mweb_url,有效期为5分钟。
由于这里是APP调起支付,不是网页H5发起支付,所以这里mweb_url值不需要再拼接上redirect_url参数。
然后移动端进行请求mweb_url,就会调起微信APP支付。
可能会出现以下问题:
“出现商家参数格式有误,请联系商家解决”
说明在请求mweb_url时referer为空导致,设置上就可以,这里我们设置最先在商户后台注册的aaa.cn的一个多级子域名,比如:b.aaa.cn。
如果不是设置商户后台注册的顶级域名的多级子域名,比如:b.ccc.cn。
就会出现“商家存在未配置的参数,请联系商家解决”异常。
那就把ccc.cn注册到商户后台就可以解决了。
到目前为止,你就可能正常支付了,但支付成功后,并不能正常跳回原APP,严重影响了用户体验。
解决方案:把referer设置成:b.aaa.cn://,scheme设置成:b.aaa.cn就可以直接返回APP了。
如是你只有一款APP使用此支付方式,那就可以到此为止了。
如果你有两款以上APP(比如X1,X2,X3),并都把referer设置成:b.aaa.cn://,scheme设置成:b.aaa.cn。
用户只安装了其中一款APP(X1),此时支付能够成功,并能转回原APP,一点问题没有。如果同时安装了(X1,X2),就会发现支付能够成功,但成功后页面静止,不能跳回原APP了,也严重影响了用户体验。
想到的第一个解决方案就是更改referer与scheme。
APPX1X2X3
refererb.aaa.cn/x1://b.aaa.cn/x2://b.aaa.cn/x3://
schemeb.aaa.cn/x1b.aaa.cn/x2b.aaa.cn/x3
通过测试发现不起作用。在网上搜索了很久也没有找到解决方案,感觉只能妥协了,牺牲点用户体验,让用户点击左上角返回原APP。
突然灵光一闪,微信商户后台即然让我们注册顶级域名,那是不是只要把referer与scheme设置成注册的顶级域名的多级子域名就可以了呢,如下:
APPX1X2X3
refererb1.aaa.cn://b2.aaa.cn://b3.aaa.cn://
schemeb1.aaa.cnb2.aaa.cnb3.aaa.cn
通过测试,完美运行。理论上这个解决方案可以支持无数个APP,也同时解决了微信开放平台APP支付的限制个数。
//支付宝h5支付返回页面
schemeStr,这个关键字让我眼前一亮。
手机网站支付转Native支付:支付宝的意思是,我们提供了一个SDK,你接了之后就可以很方便的实现H5收银台(即在web上输入支付宝账号密码支付)到支付宝APP收银台的过渡。
如果你是一个"正规"APP开发者,至此已经可以解决你的问题了。按照支付宝文档接支付宝SDK即可,就能实现H5支付回调APP了。
下载了支付宝的Demo,试了下确实可以跳回APP。
楼主琢磨着,既然SDK能实现这个功能,说明支付宝还是支持的H5支付完成后返回APP功能的,并不是向文档上说的,"在iOS系统中,唤起支付宝App支付完成后,不会自动回到浏览器或商户APP。用户可手工切回到浏览器或商户APP",只是支付宝不愿意公开而已。
/** * 支付接口
* *@paramorderStr 订单信息
*@paramschemeStr 调用支付的app注册在info.plist中的scheme
*@paramcompletionBlock 支付结果回调Block,用于wap支付结果回调(非跳转钱包支付) */
- (void)payOrder:(NSString *)orderStr fromScheme:(NSString *)schemeStr callback:(CompletionBlock)completionBlock;
最终打开支付宝APP都会走 [UIApplicationsharedApplication] openURL:]
#import
#import
@implementationUIApplication(TrackTimer)
+ (void)load{staticdispatch_once_toneToken;
dispatch_once(&oneToken, ^{
SEL mySelector =NSSelectorFromString(@"my_openURL:");
SEL orginalSelector =NSSelectorFromString(@"openURL:");
Method myMethod = class_getInstanceMethod([selfclass], mySelector);
Method orginalMethod = class_getInstanceMethod([selfclass], orginalSelector);
BOOLdidAddMethod = class_addMethod([selfclass], orginalSelector, method_getImplementation(myMethod), method_getTypeEncoding(myMethod));
If(didAddMethod) {
class_replaceMethod([selfclass], mySelector, method_getImplementation(orginalMethod), method_getTypeEncoding(orginalMethod));
}else{
method_exchangeImplementations(myMethod, orginalMethod);
}
});
}
- (void)my_openURL:(NSURL*)url{
NSLog(@"%@",url);
[selfmy_openURL:url];
}
打印结果
alipaymatrixbwf0cml3://alipayclient/?%7B%0A%20%20%22fromAppUrlScheme%22%20%3A%20%22alisdkdemo%22%2C%0A%20%20%22requestType%22%20%3A%20%22SafePay%22%2C%0A%20%20%22dataString%22%20%3A%20%22trade_no%3D%5C%222018041921001001270586523089%5C%22%26pay_phase_id%3D%5C%22%5C%22%26biz_type%3D%5C%22trade%5C%22%26biz_sub_type%3D%5C%22TRADE%5C%22%26app_name%3D%5C%22tb%5C%22%26extern_token%3D%5C%221fc77c67c70ef70d58e2bf7c513d91a5%5C%22%26appenv%3D%5C%22%5C%22%26pay_channel_id%3D%5C%22alipay_sdk%5C%22%26bizcontext%3D%5C%22%7B%5C%22av%5C%22%3A%5C%221%5C%22%2C%5C%22sc%5C%22%3A%5C%22h5tonative%5C%22%2C%5C%22ty%5C%22%3A%5C%22ios_lite%5C%22%2C%5C%22appkey%5C%22%3A%5C%222014052600006128%5C%22%2C%5C%22sv%5C%22%3A%5C%22h.a.3.5.3%5C%22%2C%5C%22an%5C%22%3A%5C%22com.antfin.AliSDKDemo%5C%22%7D%5C%22%22%0A%7D
URLDecode
alipaymatrixbwf0cml3://alipayclient/?{
"fromAppUrlScheme" : "alisdkdemo",
"requestType" : "SafePay",
"dataString" : "trade_no="2018041921001001270586523089"&pay_phase_id=""&biz_type="trade"&biz_sub_type="TRADE"&app_name="tb"&extern_token="1fc77c67c70ef70d58e2bf7c513d91a5"&appenv=""&pay_channel_id="alipay_sdk"&bizcontext="{"av":"1","sc":"h5tonative","ty":"ios_lite","appkey":"2014052600006128","sv":"h.a.3.5.3","an":"com.antfin.AliSDKDemo"}""
}
为什么前面会有一点乱码,alipaymatrixbwf0cml3,不管它了。fromAppUrlScheme,是不是很惊喜。
URLDecode以后(部分字段我做了屏蔽)
alipay://alipayclient/?{"dataString":"h5_route_token="xxxxxxxxxxxxx"&is_h5_route="true"","requestType":"SafePay","fromAppUrlScheme":"alipays"}
也有fromAppUrlScheme这个参数,而且默认是支付宝APP的URLScheme:alipays。
对比一下发现,SDK中传入的schemeStr参数对应就是alipay:// 中的 fromAppUrlScheme字段。
由此楼主猜测,在 alipay:// 打开支付宝时,传入 fromAppUrlScheme,支付结束后就会跳到对应的APP。
最终,楼主在自己的项目中验证了上述猜测。
总结下吧
最终解决方案
方案一
接支付宝提供的SDK,schemeStr参数传入自己APP的URLScheme即可。 具体的手机网站支付转Native支付
方案二
不需要接SDK,不需要添加支付宝白名单,webView拦截alipay://alipayclient请求,追加或修改参数 fromAppUrlScheme为你自己的URLScheme值(这一步可没那么简单,自己动手吧,偷笑),生成新的NSURL,然后用 [[UIApplication sharedApplication] openURL:] 打开即可。
2018.5.24更新(上面看懂了下面这段可以不用看)
总有人私信我问我怎么追加fromAppUrlScheme。可能是我说的"webView拦截",让初学者不明白吧,简单提一下。
在webView"发起请求的代理方法"里面拦截请求的URL,即如果请求是alipay://alipayclient开头的,说明这个请求就跳转支付宝的请求。
替换里面fromAppUrlScheme的值为你自己的scheme值。(当然如果更严谨的话,应该是判断fromAppUrlScheme这个key是否存在,存在则替换其值,不存在则追加)。用替换好的字符串生成一个NSURL,用[[UIApplication sharedApplication] openURL:newURL]跳转支付宝。代理方法返回NO,即终止这次请求。
应该是判断fromAppUrlScheme这个key是否存在,存在则替换其值,不存在则追加
代码如下:
NSString *urlStr = [navigationAction.request.URL.absoluteString stringByRemovingPercentEncoding];
DLOG(@"urlStr == %@",urlStr);
NSLog(@"当前加载的webView decidePolicyForNavigationAction:\n%@",webView.URL.absoluteString);
if([urlStrhasPrefix:@"alipayqr://"] || [urlStrisStartWithString:@"alipay"] || [urlStrhasPrefix:@"alipay://"]) {
NSMutableString* tmpUrlStr = urlStr.mutableCopy;
if([urlStrcontainsString:@"fromAppUrlScheme"]) {
NSDictionary* tmpDic = [selfdictionaryWithUrlString:tmpUrlStr];
DLOG(@"tmpDic == %@",tmpDic);
NSString* tmpValue = [tmpDicvalueForKey:@"fromAppUrlScheme"];
tmpUrlStr = [[tmpUrlStrstringByReplacingOccurrencesOfString:tmpValue withString:@"panda"] mutableCopy];
DLOG(@"tmpUrlStr == %@",tmpUrlStr);
tmpUrlStr = [[tmpUrlStrstringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] mutableCopy];
}
NSURL * newURl = [NSURL URLWithString:tmpUrlStr];//navigationAction.request.URL;//[NSURL URLWithString:urlStr];//
[[UIApplication sharedApplication]openURL:newURl];
}
-(NSDictionary*)dictionaryWithUrlString:(NSString*)urlStr
{
if(urlStr && urlStr.length&& [urlStrrangeOfString:@"?"].length==1) {
NSArray *array = [urlStr componentsSeparatedByString:@"?"];
if(array && array.count==2) {
NSString*paramsStr = array[1];
if(paramsStr.length) {
//paramsStr解析出来的数据:{"soeNote":"电压高超标,当前值:390.78V,大于上上限值:350.0V","name":"电压"}
NSData *jsonData = [paramsStr dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *responseDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:nil];
DLOG(@"responseDic == %@",responseDic);
returnresponseDic;
}else{
returnnil;
}
}else{
returnnil;
}
}else{
returnnil;
}
}
作者:CocoaKier
链接:https://www.jianshu.com/p/0d8dd04fe94e