背景
领导希望在iOS客户端内集成H5微信支付跟支付宝支付,所以我们需要实现的效果是:App→支付宝/微信→支付(成功失败或取消)→App(提示用户刷新页面)
方案
1、 添加 URL Scheme 并把支付宝加入白名单
添加 URL Scheme。在 xcodeproj
文件 Info
选项卡最下面的 URL Types
内设置。
1、支付宝的这个可以任意设置
2、微信支付设置商户后台填的域名(跟H5同事确认一下就行)
微信支付设置商户后台填的域名:
把支付宝/微信的 URL Scheme alipay
、alipays
、weixin
和 wechat
填入项目的白名单。在 xcodeproj
文件 Info
选项卡内的 LSApplicationQueriesSchemes
字段里设置。
2、代理方法拦截链接并跳转支付宝/微信
@property (nonatomic, copy ) NSString *redirectUrl;
#pragma mark - WKNavigationDelegate -
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURLRequest *request = navigationAction.request;
NSString *absoluteString = [navigationAction.request.URL.absoluteString stringByRemovingPercentEncoding];
if (!self.redirectUrl) {
if ([absoluteString hasPrefix:@"https://openapi.alipay.com"]) {
self.redirectUrl = [self getURLParam:navigationAction.request.URL.absoluteString key:@"return_url"];
}
if ([absoluteString hasPrefix:@"https://wx.tenpay.com"]) {
self.redirectUrl = [self getURLParam:navigationAction.request.URL.absoluteString key:@"redirect_url"];
}
}
if ([navigationAction.request.URL.scheme isEqualToString:@"alipay"]) {
// 1.以?号来切割字符串
NSArray *urlBaseArr = [navigationAction.request.URL.absoluteString componentsSeparatedByString:@"?"];
NSString *urlBaseStr = urlBaseArr.firstObject;
NSString *urlNeedDecode = urlBaseArr.lastObject;
// 2.将截取以后的Str,做一下URLDecode,方便我们处理数据
NSMutableString *afterDecodeStr = [NSMutableString stringWithString:[urlNeedDecode urlDecodeString]];
// 3.替换里面的默认Scheme为自己的Scheme
NSString *afterHandleStr = [afterDecodeStr stringByReplacingOccurrencesOfString:@"alipays" withString:@"alipayreturn.company.com"];
// 4.然后把处理后的,和最开始切割的做下拼接,就得到了最终的字符串
NSString *finalStr = [NSString stringWithFormat:@"%@?%@",urlBaseStr, [afterHandleStr urlEncodeString]];
// 判断一下,是否安装了支付宝APP
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:finalStr]]) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:finalStr]];
} else {
[KKBUtility showHUDError:@"未安装支付宝,请先安装支付宝!"];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
}
// 拦截WKWebView加载的微信支付统一下单链接, 将redirect_url参数修改为唤起自己App的URLScheme
NSString *domainUrl = @"www.company.com://wxpaycallback/";
if ([absoluteString hasPrefix:@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb"] && ![absoluteString hasSuffix:[NSString stringWithFormat:@"redirect_url=%@",domainUrl]]) {
NSString *redirectUrl = nil;
if ([absoluteString containsString:@"redirect_url="]) {
NSRange redirectRange = [absoluteString rangeOfString:@"redirect_url"];
redirectUrl = [[absoluteString substringToIndex:redirectRange.location] stringByAppendingString:[NSString stringWithFormat:@"redirect_url=%@",domainUrl]];
} else {
redirectUrl = [absoluteString stringByAppendingString:[NSString stringWithFormat:@"redirect_url=%@",domainUrl]];
}
NSMutableURLRequest *newRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:redirectUrl] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];
newRequest.allHTTPHeaderFields = request.allHTTPHeaderFields;
newRequest.URL = [NSURL URLWithString:redirectUrl];
[webView loadRequest:newRequest];
}
//拦截重定向的跳转微信的 URL Scheme, 打开微信
if ([absoluteString hasPrefix:@"weixin://"]) {
if ([[UIApplication sharedApplication] canOpenURL:navigationAction.request.URL]) {
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
} else {
[KKBUtility showHUDError:@"未安装微信,请先安装微信!"];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
}
decisionHandler(WKNavigationActionPolicyAllow);
}
- (NSString *)getURLParam:(NSString *)url key:(NSString *)key {
NSMutableDictionary *paramer = [[NSMutableDictionary alloc]init];
//创建url组件类
NSURLComponents *urlComponents = [[NSURLComponents alloc] initWithString:url];
//遍历所有参数,添加入字典
[urlComponents.queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[paramer setObject:obj.value forKey:obj.name];
}];
return [paramer objectForKey:key];
}
- (void)addNotification {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadRedirectUrl) name:@"htmlPaymentNotification" object:nil];
}
- (void)removeNotification {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// 支付宝/微信返回APP 刷新当前页面 更新支付状态
- (void)reloadRedirectUrl {
if (self.redirectUrl) {
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.redirectUrl] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
[self.webView loadRequest:request];
self.redirectUrl = nil;
});
}
}
NSString 的类目中添加 URL 的 Encode 和 Decode 方法:
- (NSString *)urlEncodeString {
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@";
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < self.length) {
NSUInteger length = MIN(self.length - index, batchSize);
NSRange range = NSMakeRange(index, length);
// To avoid breaking up character sequences such as 👴🏻👮🏽
range = [self rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [self substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
- (NSString *)urlDecodeString {
NSMutableString *outputStr = [NSMutableString stringWithString:self];
[outputStr replaceOccurrencesOfString:@"+" withString:@"" options:NSLiteralSearch range:NSMakeRange(0,[outputStr length])];
return [outputStr stringByRemovingPercentEncoding];
}
3、 AppDelegate 中接收跳转动作
支付宝回调的 host 是固定的 safepay,而微信支付的 host 随意定义:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options{
//safepay是支付宝H5支付的回调host,
if ([url.host isEqualToString:@"wxpaycallback"] || [url.host isEqualToString:@"safepay"]) {
// 自行操作业务逻辑,比如使用通知请求查询订单状态,popView回上级页面等
[[NSNotificationCenter defaultCenter] postNotificationName:@"htmlPaymentNotification" object:self userInfo:nil];
}
}