目录
- 序
-
问题
- applicationUsername为空问题
- 苹果验单需求什么东西?
- 账户登录退出与苹果内购行列监听问题
- 简略代码逻辑示例
- 总结
序
本篇文章意图主要是纠正我之前写过的一篇内购文章的过错说法,以及稍微展示一下现在的flutter内购流程.
之前的文章戳这儿
问题解决
applicationUsername为空问题
flutter在这儿确实是有这个问题,可是applicationUsername
本来就不应该存放一些敏感数据. 有些项目为了自己的验单安全,用户安全等等原因,将json数据都存到了这个字段中其实是不恰当的行为.
咱们细心想想,内购是什么在付出,内购使用的是appstore的登录账户,这个权限等级是苹果商店登录账户等级
- 生产环境: 设置->最顶部账户->媒体与购买项目
- 沙盒环境: 设置->App Store->最底部沙盒账户
也就是说,在你没有更改appstore登录账户时,他使用的都是用一个,这东西根本能够说跟keychain是同一等级的,其实咱们能够理解为苹果登录账户大于app登录账户. 所以,根据这一思考点,咱们其试验单没有必要去区别验单成功时充值的是什么app账户
- 苹果内购付出跟app是彻底独立的,付出成功,那他乐意去哪个app账户验单就去哪个app验单,没必要做什么预订单,校验是否发起订单和验单是否是同一用户.
- 关于咱们程序开发而言,只需确保不要掉单即可
- 关于财务而言,他们要确认只需确认对账没有问题就行.
苹果验单需求什么东西?
- productID(details.productID)
- transactionId(details.purchaseID)
- receiptKey(details.verificationData.serverVerificationData)
账户登录退出与苹果内购行列监听问题
之前我说过
in_app_purchase希望咱们在app启动时就敞开行列监听
这个其实是有问题的,细心查看源码,在调用InAppPurchase.instance
这个单例时,它做了什么事情呢?
// in_app_purchase.dart line:28
static InAppPurchase get instance => _getOrCreateInstance();
// 获取单例
static InAppPurchase _getOrCreateInstance() {
// 假如有直接返回
if (_instance != null) {
return _instance!;
}
// 假如为空,则进行渠道初始化
if (defaultTargetPlatform == TargetPlatform.android) {
InAppPurchaseAndroidPlatform.registerPlatform();
} else if (defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS) {
// 留意这一行,iOS渠道的初始化
InAppPurchaseStoreKitPlatform.registerPlatform();
}
_instance = InAppPurchase._();
return _instance!;
}
static void registerPlatform() {
*******省掉其他内容********
_skPaymentQueueWrapper = SKPaymentQueueWrapper();
// Create a purchaseUpdatedController and notify the native side when to
// start of stop sending updates.
// 这儿创建了一个streamcontroller
final StreamController<List<PurchaseDetails>> updateController =
StreamController<List<PurchaseDetails>>.broadcast(
// 敞开行列监听
onListen: () => _skPaymentQueueWrapper.startObservingTransactionQueue(),
// 撤销行列监听
onCancel: () => _skPaymentQueueWrapper.stopObservingTransactionQueue(),
);
_observer = _TransactionObserver(updateController);
_skPaymentQueueWrapper.setTransactionObserver(observer);
}
startObservingTransactionQueue与stopObservingTransactionQueue在iOS端完结:
// startObservingTransactionQueue
[_paymentQueueHandler startObservingPaymentQueue];
// stopObservingTransactionQueue
[_paymentQueueHandler stopObservingPaymentQueue];
那么onListen和onCancel什么时候触发呢?
stream_controller.dart
line:167
The [onListen] callback is called when the first listener is subscribed, and the [onCancel] is called when there are no longer any active listeners. If a listener is added again later, after the [onCancel] was called, the [onListen] will be called again.
当第一个侦听器被订阅时,调用[onListen]回调, 而且当不再有任何活动侦听器时调用[onCancel]。假如稍后在调用[onCancel]之后再次添加侦听器, 将再次调用[onListen]。
定论:
只需咱们在账号登录后进行第一次监听,而且在账号退出后撤销一切的买卖监听,底层仍是能正常调用到startObservingPaymentQueue与stopObservingPaymentQueue,而且账号从头登录后startObservingPaymentQueue也能够正常执行.
- 退出登录时:
- 从头登录:
总结就是,彻底没有我之前所说的那个账户登录退出与苹果内购行列监听问题
,只需你做好行列退出的撤销监听即可
简略代码逻辑示例
做了一层相对比较抽象的内购逻辑完结层,不触及app业务验单等,不过该代码并不是最终代码,安卓端的谷歌付出我现在并未测试过,因为安卓不仅需求翻墙,还需求goole play环境,还需求谷歌账号配置,有些费事,不过想来大差不差,等安卓童鞋测试完后,看看需求抛出的类是否需求封装一下.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
enum InAppPurchaseStep {
// 等待中
pending,
// 购买成功
purchased,
// 验单成功
verified,
// 过错
error,
// 康复购买
restored,
// 已撤销
canceled;
}
abstract class InAppPurchaseServiceInterface {
/*
* 购买状况监听回调
* */
void listenToPurchaseUpdated(InAppPurchaseStep status);
/*
* 验单接口
* */
Future<bool> verifyPurchase(PurchaseDetails details);
}
class InAppPurchaseService {
InAppPurchaseService(this.iapServiceImpl);
InAppPurchaseServiceInterface iapServiceImpl;
StreamSubscription<List<PurchaseDetails>>? _subscription;
List<ProductDetails> _products = [];
Completer<bool>? _buySignal;
InAppPurchaseStep? _lastStatus;
/*
* 2.登录后调用监听
* */
void listen() {
_subscription ??= InAppPurchase.instance.purchaseStream.listen(
_listenToPurchaseUpdated,
);
}
/*
* 需求撤销监听,退出登录时调用
* */
void cancel() {
_subscription?.cancel();
_subscription = null;
}
/*
* 确认苹果购买服务可不可用
* */
Future<bool> checkAvailable() async {
final bool available = await InAppPurchase.instance.isAvailable();
return available;
}
/*
* 敞开购买
* */
Future<bool> buy(String priceId) async {
try {
handlerLoadingCallBack(InAppPurchaseStep.pending);
final isAvailable = await checkAvailable();
if (!isAvailable) {
throw Exception('服务不可用');
}
_iapLog('内购服务可用');
// 1.商品概况
final detail = await _queryProductDetails(priceId);
if (detail == null) {
throw Exception('商品不存在');
}
_iapLog('获取到商品概况信息');
// 2.购买参
final purchaseParam = PurchaseParam(productDetails: detail);
// 3.购买
final res = await InAppPurchase.instance
.buyConsumable(purchaseParam: purchaseParam);
_iapLog('购买成果: $res');
final fu = Completer<bool>();
_buySignal = fu;
return fu.future;
} catch (e) {
_iapLog(e);
handlerLoadingCallBack(InAppPurchaseStep.error);
return false;
}
}
/*
* 查询可用商品
* */
Future<ProductDetails?> _queryProductDetails(String priceId) async {
// query local
for (final pro in _products) {
if (pro.id == priceId) {
return pro;
}
}
// query remote
ProductDetailsResponse res =
await InAppPurchase.instance.queryProductDetails({priceId}).timeout(const Duration(seconds: 15,));
for (final pro in res.productDetails) {
if (pro.id == priceId) {
_products.add(pro);
return pro;
}
}
// find null
return null;
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
switch (purchaseDetails.status) {
case PurchaseStatus.pending:
handlerLoadingCallBack(InAppPurchaseStep.pending);
break;
case PurchaseStatus.purchased:
try {
handlerLoadingCallBack(InAppPurchaseStep.purchased);
// 验单结束
final verifyResult =
await iapServiceImpl.verifyPurchase(purchaseDetails);
if (verifyResult) {
_buySignal?.complete(true);
handlerLoadingCallBack(InAppPurchaseStep.verified);
// 完结订单
await _finishDetails(purchaseDetails, true);
} else {
// 发送失利
_buySignal?.complete(false);
// 状况更新
handlerLoadingCallBack(InAppPurchaseStep.error);
// 完结订单
await _finishDetails(purchaseDetails, true);
}
} catch (e) {
_buySignal?.complete(false);
handlerLoadingCallBack(InAppPurchaseStep.error);
// 完结订单
await _finishDetails(purchaseDetails, true);
}
break;
case PurchaseStatus.error:
await _finishDetails(purchaseDetails, true);
handlerLoadingCallBack(InAppPurchaseStep.error);
break;
case PurchaseStatus.restored:
await _finishDetails(purchaseDetails, true);
handlerLoadingCallBack(InAppPurchaseStep.restored);
break;
case PurchaseStatus.canceled:
await _finishDetails(purchaseDetails, true);
handlerLoadingCallBack(InAppPurchaseStep.canceled);
break;
}
_iapLog(
'订单状况: ${purchaseDetails.status.name} 是否需求完结订单: ${purchaseDetails.pendingCompletePurchase}',
);
});
}
/*
* 购买各状况回调
* */
void handlerLoadingCallBack(InAppPurchaseStep status) {
if (status != _lastStatus) {
iapServiceImpl.listenToPurchaseUpdated(status);
_lastStatus = status;
}
}
Future<void> _finishDetails(PurchaseDetails details, [bool force = false]) async {
// 完结订单
if (details.pendingCompletePurchase || force) {
await InAppPurchase.instance.completePurchase(details);
}
}
}
void _iapLog(dynamic m) {
debugPrint('内购 $m');
}
总结
内购说来也是比较简略的,需求留意的点也是简略,不管你技术能力强仍是弱,顺嘴最终说几个点:
- if else判断彻底,要确保loading能够移除掉
- 验单成功后再完结订单