Flutter iOS内购

工作中用flutter做过一个数字藏品的项目,苹果端需要加入内购才能上线发布,本文主要写一下使用flutter插件in_app_purchase: ^3.0.7 实现苹果内购功能以及遇到的问题。纯属技术交流,欢迎评论交流.
主要思路:
1.在苹果开发账号上面建好虚拟商品。
2.将在苹果开发账号上面建好的虚拟商品录入后台系统,便于后端通过接口返回给前端对应的商品id。
3.使用in_app_purchase插件,通过后端返回的商品id,查询到商品信息,再将商品信息提交给苹果服务器发起支付请求,再将苹果服务器返回的支付结果传给自己的服务器。
4.自己的服务器通过前端传给的支付结果去进行发货以及其他操作。
详细操作:

关于怎么在苹果开发账号上面建商品过程有些繁琐,不是本文的重点内容在这里就不详细说明了,可以网上搜搜,有很多博主写的很详细,贴一个图示。
3A7EF337-BAAB-4076-8BD2-D82363973441.png
in_app_purchase的使用

in_app_purchase是谷歌官方出的插件,相对来说还是很坚挺的,所以用的还是比较放心。我是用的in_app_purchase: ^3.0.7 #内购版本,直接导入项目使用即可。
当点到某个商品获取到后台返回的ProductId,调用下面的方法,仔细看注释即可

/// 苹果内购 (购买)
  Future applePay(Map dataMap) async {
  /// _inAppPurchase是否有效
    final bool isAvailable = await _inAppPurchase.isAvailable();
    if (!isAvailable) {
      return;
    }
    /// 如果是iOS设备进行设置代理,接口苹果服务器的回调。
    if (Platform.isIOS) {
      final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
          _inAppPurchase
              .getPlatformAddition();
      await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate());
    }

    /// 获取后台返回的产品id
    if (dataMap['iosProductId'] == null) {
      Loading.dissmiss();
      AppDialog.showShucangText(title: '暂无产品');
      return;
    }
    _kProductIds.add(dataMap['ProductId']);

   /// 查询后台返回的ProductId是否在苹果服务器上注册了
    final ProductDetailsResponse productDetailResponse =
        await _inAppPurchase.queryProductDetails(_kProductIds.toSet());
    /// 查询不到说明没注册
    if (productDetailResponse.error != null) {
      Loading.dissmiss();
      AppDialog.showShucangText(title: '获取产品信息失败');
      return;
    }
    /// 查询不到商品详情说明没注册
    if (productDetailResponse.productDetails.isEmpty) {
      Loading.dissmiss();
      AppDialog.showShucangText(title: '暂无产品');
      return;
    }
    _products = productDetailResponse.productDetails;
  /// 查询成功
    ProductDetails productDetails = _products[0];
    FlutterKeychain.put(key: "iosPrice", value: productDetails.price);
    late PurchaseParam purchaseParam;
    purchaseParam = PurchaseParam(
        productDetails: productDetails,

        /// 添加自己服务器上生成的订单
        applicationUserName:
            '${dataMap['orderNo11']}' + '${dataMap['orderNo22']}');
   /// 向苹果服务器发起支付请求
    _inAppPurchase.buyConsumable(purchaseParam: purchaseParam);
  }

注意点:

上面的 purchaseParam就是向苹果服务器发起支付请求时传递的参数,在purchaseParam这个对象里,可以设置自定义参数,来方便判断那笔订单。

监听苹果服务器的回调

Future _listenToPurchaseUpdated(
      List purchaseDetailsList) async {
    for (final PurchaseDetails purchaseDetails in purchaseDetailsList) {
      if (purchaseDetails.status == PurchaseStatus.pending) {
        /// 等待购买中
      } else if (purchaseDetails.status == PurchaseStatus.canceled) {
        /// 取消订单
        _inAppPurchase.completePurchase(purchaseDetails);
        Loading.dissmiss();
      } else {
        if (purchaseDetails.status == PurchaseStatus.error) {
          /// 购买出错
          Loading.dissmiss();
          AppDialog.showShucangText(title: '购买出错');
          _inAppPurchase.completePurchase(purchaseDetails);
        } else if (purchaseDetails.status == PurchaseStatus.purchased ||
            purchaseDetails.status == PurchaseStatus.restored) {
          /// 调用后台接口,发放商品
          deliverProduct(purchaseDetails);
        }
      }
    }
  }

purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored说明在苹果服务器上发起请求成功了,然后再通知一下自己的服务器去发货然后一定记得要调用下这个方法_inAppPurchase.completePurchase(purchaseDetails)不然下一次就拉不起苹果支付来了就万事大吉了。但是你想多了,测试可不这样整。

这张图片相信大家很熟悉吧,当在苹果服务器上付款成功后变会弹出这个弹窗,只有当用户点击“好”时才会触发成功的回掉,也就是才会代码才会走到这个purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored判断里面来,那如果这时候,测试同学,不点击“好”直接退到App后台,把App直接杀死或者把App卸载了,那自己的服务器是不知道用户在苹果服务器上已经完成付款了,是不会给用户发货的,这也就是老生常谈的丢单问题

WeChatc5a772f243ebc90533be17c3af6bd25e.jpg

丢单问题处理

使用

_inAppPurchase.purchaseStream是用来监听消息队列的回调的,也就是所有订单的状态以及信息回调,in_app_purchase这个属性的文档中这么说到:

IMPORTANT! You must subscribe to this stream as soon as your app launches,
preferably before returning your main App Widget in main(). Otherwise you
will miss purchase updated made before this stream is subscribed to.
重要!你必须在应用程序启动后立即订阅此流,
最好在main()中返回主应用程序小部件之前。否则你
将错过订阅此流之前更新的购买。

也就是说当我们的App在第一次启动的时候可以订阅此流来完成补单的操作,但是如果用户是之前丢单了,然后把App又卸载了,再次下载打开App后并没有进行登录操作,那用户的登录信息都拿不到怎么进行补单操作呢?

补单解决方案

让后端出一个补单的接口,在补单时只需要传一个订单号即可,那App都删除了,之前的订单号客户端怎么获取呢?使用flutter_keychain来实现,flutter_keychain就是使用的iOS的钥匙串来实现的,当用户在苹果服务器下单时,在钥匙串中保存后端生成的订单号,然后再商品成功发货后删除钥匙串里面的订单号,完成一个完整的购买过程,再购买时任何一环出了问题钥匙串里面缓存的订单号都不会被清空,这样在App下一次启动时,在首页或者main函数中使用_inAppPurchase.purchaseStream监听,在拿到flutter_keychain中保存的订单号完成补单过程。

注意点

1.在完成苹果服务器付款流程后通知到自己服务器接口也就是验单的接口返回的是成功或者不成功都要调用_inAppPurchase.completePurchase(purchaseDetails)这个方法,不然下次就掉不起苹果支付来了,当然肯定会在失败的判断里面写明白让用户自己去走苹果退款流程的文案(概率较小,但是也得考虑)

2.商品类型如果是非消耗品的话,在下单完之后一定写一个按钮供点击调用复原的方法,要是不复原的话每次下的订单,订单号都是一样的,我在开发时就遇到了这个问题,折腾了半天也发现是这个问题。

结尾

放上一张整个支付流程的图示吧


5CE5E56A-63AC-4A95-A224-741D06DFAF80.png

你可能感兴趣的:(Flutter iOS内购)