flutter-支付宝、微信支付分享 与 ios UniversalLink

4,773 阅读9分钟

前言

本篇主要介绍 支付宝支付微信支付、微信分享、微信登陆功能,以及在微信支付过程中需要配置 iosUniversalLink 的问题(没有配置跳转微信时会提示:未验证应用

应用的组件分别为 alipay_kitwechat_kit,即:支付宝框架,微信框架

当然如果要对接国外的一些支付,则选择 stripe 更好,这里不介绍,有需要的可以使用这个

支付宝支付

这里面主要针对 ios 端设置,android 暂时没发现什么特殊问题

首先添加三方库 alipay_kitalipay_kit_ios,这两个都要添加,前面一个主要准备android,后面一个主要针对于 ios

flutter pub add alipay_kit
flutter pub add alipay_kit_ios

原生端配置

主要ios,android目前不需要配置,出现了问题,可以参考 alipay_kit 解决

ios端设置,设置 plist,主要设置网络、跳转支付宝的scheme

iOS 9系统策略更新,限制了http协议的访问,此外应用需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装。

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>alipay</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

ios端设置,除了上面的,还要设置自己的 scheme,用于支付宝跳回我们的 app

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>alipay</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>alipay123456</string>
        </array>
    </dict>
</array>

其实际上,在我们的 info 里面设置一下即可,会自动生成上面的代码,前面名称要用 alipay, 后面取一个相对比较唯一的字符串即可

image.png

flutter端使用

下面注册支付授权登陆回调,不注册监听,直接使用返回的 Future 也可以,但不确定是否覆盖所有版本或者情况

//导入头文件,无需导入ios的那个库
import 'package:alipay_kit/alipay_kit.dart';

//声明参数用于回调使用
late final StreamSubscription<AlipayResp> _alipaySubs; //用于支付
late final StreamSubscription<AlipayResp> _alipayAuthSubs; //用于授权登陆

//注册支付宝支付和授权结果回调
void registerAlipayResp() {
  _alipaySubs = Alipay.instance.payResp().listen(listenAlipayPay);
  _alipayAuthSubs = Alipay.instance.authResp().listen(_listenAlipayAuth);
}

//支付成功或者失败回调
void listenAlipayPay(AlipayResp resp) {
  final String content = 'pay: ${resp.resultStatus} - ${resp.result}';
  print(content);
}

//收取那登陆成功或者失败回调
void _listenAlipayAuth(AlipayResp resp) {
  final String content = 'auth: ${resp.resultStatus} - ${resp.result}';
  print(content);
}

检测是否安装了支付宝app

//返回的 Future 需要等待
final isInstall = await Alipay.instance.isInstalled();
if (!isInstall) {
  print("未安装支付宝app");
  return;
}

授权登陆,需要传递授权信息字符串,由服务器返回

Alipay.instance.auth(authInfo: "authInfo-123123");

支付接口,需要传递 订单信息字符串,由服务器返回

Alipay.instance.pay(orderInfo: "orderInfo-123123123123")

看到上面你可能知道为什么客户端不需要 appid 之类的信息了,没错,都在服务器返回的信息字符串里面,也是服务器进行部分加密,因此相对客户端比较安全,也是支付宝推荐

ps:对接前需要先到 支付宝商家平台 申请我们的应用服务,不然等待就比较浪费时间了,前后端都会卡到这里

微信支付、分享、登陆

微信大部分内容和支付宝类似,只不过ios端额外引出了 UniversalLink(下一小节专门介绍了) 作为新版本跳转传参方案,因此需要额外做一些操作

需要添加三方库 wechat_kit

//flutter 添加 alipay_kit
flutter pub add alipay_kit

原生端配置

设置 plist,以便于能够跳转到微信(使用该 scheme 支持新老版本的跳转),支付宝和微信都存在的情况,内容合并即可

<array>
   <string>weixinULAPI</string>
   <string>weixin</string>
    <string>alipay</string> //也有支付宝的话就是额外加一条即可,这条注释不要放进去
</array>
<key>NSAppTransportSecurity</key>
<dict>
   <key>NSAllowsArbitraryLoads</key>
   <true/>
</dict>

我们自己的 schemes 设置,用于支付宝微信跳回我们的app的,就在 info 里面设置

<array>
   <dict>
      <key>CFBundleTypeRole</key>
      <string>Editor</string>
      <key>CFBundleURLName</key>
      <string>alipay</string>
      <key>CFBundleURLSchemes</key>
      <array>
         <string>alipay123456</string>
      </array>
   </dict>
   <dict>
      <key>CFBundleTypeRole</key>
      <string>Editor</string>
      <key>CFBundleURLName</key>
      <string>weixin</string>
      <key>CFBundleURLSchemes</key>
      <array>
         <string>weixin123456</string> //注意:这里面填写自己的微信申请的 key
      </array>
   </dict>
</array>

image.png

flutter 端使用

下面是注册微信支付、分享、登陆等回调

//导入包
import 'package:wechat_kit/wechat_kit.dart';

//声明回调参数
late final StreamSubscription<BaseResp> _wechatRespSubs;
AuthResp? _wechatAuthResp;//保存授权的resp码,用于获取后续获取用户信息


//注册微信支付、分享、授权等回调
void registerWechatResp() {
  _wechatRespSubs = Wechat.instance.respStream().listen(listenWechatResp);
}

//监听回调,集多个回调与一身
void listenWechatResp(BaseResp resp) {
  final String content = '基础: ${resp.errorCode} ${resp.errorMsg}';
  print(content);
  if (resp is AuthResp) {
    //可以保存下来授权的状态(没必要这个),用来获取用户信息
    _wechatAuthResp = resp;
    final String content = '登录: ${resp.errorCode} ${resp.errorMsg}';
    print(content);
  } else if (resp is ShareMsgResp) {
    final String content = '分享: ${resp.errorCode} ${resp.errorMsg}';
    print(content);
  } else if (resp is PayResp) {
    final String content = '支付: ${resp.errorCode} ${resp.errorMsg}';
    print(content);
  } else if (resp is LaunchMiniProgramResp) {
    //这里就不加入案例了
    final String content = '拉起小程序: ${resp.errorCode} ${resp.errorMsg}';
    print(content);
  }
}

使用前需要先注册,应用启动时注册一次即可,传入商户平台申请的key,还是有就是 UniversalLink(后面讲) 配置后的 link 连,如果只是android端则不需要传递该参数,该参数用于新版本微信(但必须得处理)

await Wechat.instance.registerApp(
  appId: wechatKey,
  //不了解需要的话可以搜索ios universalLink 通用链接设置,能够直接通过链接唤起app,我也有相关文章哈
  universalLink: "https://help.wechat.com/$wechatKey ",
);

注册微信回调函数,伴随启动时注册即可,否则不回调注册的函数

await Wechat.instance.handleInitialWXReq();

判断是否,安装了微信

final isInstalled = await Wechat.instance.isInstalled();
if (!isInstalled) {
    print("没安装微信")
    return;
}

微信授权登陆,会返回需要的 id 一般登陆和绑定时使用

Wechat.instance.auth(
  scope: <String>[WechatScope.SNSAPI_USERINFO],
  state: 'auth',
);

分享图片、文字到微信聊天、朋友圈、收藏

//分享方法shareImage、shareText
//scene类型-- SESSION:聊天界面、TIMELINE:朋友圈、FAVORITE:收藏
Wechat.instance.shareText(
  scene: WechatScene.SESSION,
  text: '测试文字分享',
);

微信支付,里面提供了必要的一些参数,建议从服务器,校验加密后返回,也可以写到本地(毕竟注册也用到的appId)

//这就算完成支付了,这些东西最好让后台走校验接口返回
Wechat.instance.pay(
    appId: 'appId', //微信申请的appid
    partnerId: 'partnerId', //合作伙伴id
    prepayId: 'prepayId', //预支付id
    package: 'package', //微信传递参数
    nonceStr: 'nonceStr', //随机字符串
    timeStamp: 'timeStamp', //时间戳
    sign: 'sign', //微信签名
);

问题和解决方案

在测试ios app的时候,如果碰到老版本的微信是没问题的,如果碰到用户是新版本的微信,那需要配置 UniversalLink,否则跳转微信时,会提示"未验证引用",不配置甚至都不知道注册时的 universalLink 要传递啥,别说不适配新版本微信用户(那么这个功能可能大部分人ios用户使用可能都有问题)

因此只需要在 ios 端 配置好 UniversalLink即可,此外还需要将json配置文件放到我们后台服务器

下面会介绍到

UniversalLink 配置

这个是 ios 9 就已经推出的 UniversalLink 通用连接,点击该链接时,iOS 设备可以不通过 Safari 或网页,直接打开 App,比如在备忘录中直接打开App

ios开发实际上默认不是必须的,但是 flutter 想接入微信相关服务,那就是必须的了,下面介绍下其接入过程

1、创建一个名字为 apple-app-site-association 的文件

ps:内容为json格式,但是不需要.json为扩展名,扩展名只是方便在操作系统中,给应用标记类型,方便调用产生的一种策略,因此不是必要的)

{
  "applinks": {
    "apps": [],
    "details": [ //可以有多组,加入我们有多个应用,都可以使用这一个文件
      {
        "appID": "teamID.bundleID", //这里填写的是我们的 团队id,可以在证书或者开发者账号中查看
        "paths": [ "*" ] //path路径为,域名后面的路径,可以使用通配符,这里*表示任意路径都可以
      }
      { 
          "appID": "D1I2O3P3PK.com.example.appstore", //案例 teamid + bundleid
          "paths": [ "/qq_zone/*","/qq/*" ]  //通配符表示weixin或者qq路径下,后面任意路径或者参数
      }, 
      { 
          "appID": "D1I2O3P3PP.com.example.enterprise", 
          "paths": [ "/weixin/*","/wechat/10000000/*", /wx/10010/* ] 
      }
    ]
  }
}

注意上面里面的每一个 path 不应当重复,这个可能被应用到多个应用中

如果 teamId 还是不知道,登陆一下开发者账号,下面的位置,或者打开钥匙串,查看证书,就在我们证书里面也有

image.png

2、将 apple-app-site-association 放到服务器

上面的 apple-app-site-association 文件,我们需要将其放到我们的服务器中的根目录.well-known 目录(推荐这个,默认先访问这个目录)下

例如:

`https://host/.well-known/apple-app-site-association`  
`https://host/apple-app-site-association`

另外

iOS会先请求`.well-known`的域名
`apple-app-site-association`只会在APP第一次启动的时候请求一次,
因此文件的任何更新的验证都需要 APP 重新安装或 App Store 更新

如果说 apple-app-site-association为了找到我们的文件 paths里面设置的路径名就是为了区分app,因此,不同appIdpaths也应当不一样,paths 可以取多个的目的是多个路径都能指向我们的同一个 app

注意微信使用我们的 links 的时候,会在我们路径后面拼接参数,因此,单个 path 后面必须利用通配符,即使用 /* 收尾

3 设置 Associated Domains

进入开发账号,进入 Identifiers,设置我们 appIdentifiers 对应的 Associated Domains,加完后,记得更新我们的 profiles描述文件

然后 app项目配置 加入 Associated Domains,如下所示,需要 applinks: 开头,后面是我们的域名 host,例如:www.baidu.commail.qq.com(一般app会用子域名,就像腾讯一样)

image.png

4、填写 UniversalLink

host / path就是我们的 UniversalLink 链接了,例如

假设我们的域名是 www.baidu.com

//这就是我们的 UniversalLink,路径 wechat 专门给微信使用的
//末尾一定要加上 / ,微信要在后面拼参数传递, 域名 + 路径(wechat) + /
https://www.baidu.com/wechat/

//这就是我们qq应用的,域名 + 路径(qq) + /
https://www.baidu.com/qq/


//如果我们的域名是 mail.qq.com,域名 + 路径(qq) + /
https://mail.qq.com/qq/

验证我们的QQ、微信APP是否支持

验证当前版本 QQ 是否支持Universal Link,验证地址:qm.qq.com

验证当前微信版本是否支持Universal Link,验证地址:help.wechat.com/app/

下面是尝试结果,我的手机是支持的 😂

image.png

原生的部分处理

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler{
    if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
        NSURL  *webpageURL = userActivity.webpageURL;
        if(webpageURL && [TencentOAuth CanHandleUniversalLink:webpageURL]) { 
            // QQ
            return [QQApiInterface handleOpenUniversallink:webpageURL delegate:self] || [TencentOAuth HandleUniversalLink:webpageURL];
        }else if ([webpageURL.absoluteString hasPrefix:[NSString stringWithFormat:@"https://app.yourDomain.com/test/%@", WeChatAppId]]){
            // 微信
            if([WXApi handleOpenUniversalLink:userActivity delegate:self]){
            }else{
                [WXApi handleOpenURL:webpageURL delegate:self];
            }
            return YES;
        }
        // 编写我们自己的判断逻辑
    }
    return YES;
}

最后

部分内容无法贴出,或者实际 demo 部分无法真正跳转走动,需要自行走流程,更新一下

开来自己动手尝试一下吧