in_app_purchase: ^3.0.7 实现苹果内购功能及问题详解

2024-06-15
来源:网络整理

工作中做过一个数字收藏项目,苹果需要增加内购功能才能上线,本文主要讲述使用插件:^3.0.7实现苹果内购功能以及遇到的问题,纯属技术交流,欢迎大家提出意见交流。主要思路:1.在苹果开发账号上创建虚拟商品。2.将苹果开发账号上创建的虚拟商品录入到后端系统中,以便后端通过接口返回对应的商品ID给前端。3.使用插件通过后端返回的商品ID查询商品信息,然后将商品信息提交给苹果服务器发起支付请求,再将苹果服务器返回的支付结果传递给自己的服务器。4.自己的服务器利用前端传递过来的支付结果进行发货等操作。详细操作:

苹果开发账号创建产品的流程比较复杂,不是本文的重点,就不详细讲了,大家可以在网上搜一下,很多博主都写得很详细了,我贴个图吧。

使用

是官方插件,比较强大,可以放心使用。我用的是:^3.0.7#内购版本,直接导入项目就可以使用了。点击商品获取后端返回时,调用如下方法,注意看注释。

/// 苹果内购 (购买) Future<void> 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); }

###注意:以上是向苹果服务器发起支付请求时传递的参数,在这个对象中,你可以设置自定义参数,方便订单的判断。

监听来自 服务器的回调

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); } } } }

当 . == . || . == . 时,就代表成功在苹果服务器上发起请求,然后通知自己的服务器发货了。然后记得调用这个方法()不然下次就拉不起来苹果支付了。不过你想多了,测试不是这样进行的。

相信大家都很熟悉这张图,当在苹果服务器上支付成功后,就会弹出这个弹窗,只有用户点击“确定”才会触发成功回调,也就是代码会走到这个 . == . || . == . 判断。如果此时测试人员不点击“确定”,直接退出到App后台,直接杀掉App或者卸载App,那么服务器并不知道用户已经在苹果服务器上完成支付,不会给用户发货,这就是丢单的老问题了。

#处理丢单问题

.用于监控消息队列的回调,也就是所有订单的状态和信息回调,这个属性的文档是这么说的:

!您必须在应用程序启动后立即执行此操作,最好在 main() 中执行此操作,然后再返回主应用程序小部件。否则,您将错过订阅此流之前更新的购买。

也就是说当我们的App第一次启动的时候,我们可以订阅这个流,完成补单操作。但是,如果用户之前丢过单然后卸载了App,然后又在未登录的情况下再次下载打开App,在无法获取用户登录信息的情况下,怎么补单呢?####补单方案可以让后端提供补单接口,补单的时候只需要传递一个订单号就可以了,如果App被删除了,客户端怎么获取之前的订单号呢?利用iOS的来实现,当用户在苹果服务器下单的时候,后端生成的订单号会保存在中,然后在商品成功发货之后再删除中的订单号,完成一个完整的购买流程。如果购买过程中任何一个环节出现问题,中缓存的订单号都不会被清除。这样,下次启动App的时候,就可以使用。 在首页或者主函数中监听,获取 中保存的订单号,完成下单流程。

##注意事项###1.在苹果服务器上完成支付流程后,一定要调用此方法()通知自己的服务器接口,也就是验证订单的接口,是返回成功还是失败。否则下次就没法使用苹果支付了。当然失败判断里肯定会有一段文字,让用户自己走苹果退款流程(概率很小,但一定要考虑)

###2.如果商品类型是非消耗品,下单后必须写一个按钮,点击后调用恢复方法,不恢复的话,每次订单的订单号都一样,我开发的时候就遇到过这个问题,折腾了好久才发现是这个问题。##最后放一张整个支付流程的图

分享