in_app_purchase苹果内购

使用in_app_purchase这个库

流程

1. 修改XCode配置文件,支持内购

2. 项目中添加in_app_purchase配置

1
2
3
4
5
6
7

dependencies:
flutter:
sdk: flutter
in_app_purchase:
in_app_purchase_storekit:

3. 编写内购代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158

class ShopPayUtil {
static ShopPayUtil? _instance;

static ShopPayUtil getInstance() {
_instance ??= ShopPayUtil._internal();
return _instance!;
}

ShopPayUtil._internal();

factory ShopPayUtil() => getInstance();

///购买商品
void buy(GoodsModel item) async {
try {
EasyLoading.show();
// 根据商品信息创建订单
var res = await rechargeCreate(item.code!);
if(res != null) {
// 调用苹果内购
await applePay(res);
}
} catch (e, s) {
Logger.error('$e $s');
} finally {
EasyLoading.dismiss();
}
}

/// 创建充值订单
Future<dynamic> rechargeCreate(String goodsCode) async {
// todo 调用服务器接口
}

/// 验单
static Future paymentIpa(String orderNo, String payload, String transactionId) async {
// todo 检查订单准确性
}

/// 消耗虚拟币
Future reviewModeConsume(String outlay) async {
// todo 调用后台接口
}

static StreamSubscription<List<PurchaseDetails>>? _subscription;

/// 苹果内购 (购买)
static Future<void> applePay(Map dataMap) async {
final Stream<List<PurchaseDetails>> purchaseUpdated = InAppPurchase.instance.purchaseStream;
_subscription?.cancel();
// 添加监听器
_subscription = purchaseUpdated.listen((List<PurchaseDetails> purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList, dataMap);
}, onDone: () {
_subscription?.cancel();
Logger.debug('=applePay=====onDone');
}, onError: (Object error) {
Logger.error(error);
ToastUtil.show(error.toString());
});


final bool isAvailable = await InAppPurchase.instance.isAvailable();
// 判断是否可用
if (!isAvailable) {
ToastUtil.show('The store cannot be reached, check your network connection please.');
return;
}

if (Platform.isIOS) {
final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
InAppPurchase.instance.getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
await iosPlatformAddition.setDelegate(PaymentQueueDelegate());
}

Set<String> ids = {dataMap['goodsCode']};
// 查询商品的信息(需要先在苹果后台配置)
final ProductDetailsResponse productDetailResponse = await InAppPurchase.instance.queryProductDetails(ids);

if (productDetailResponse.error != null) {
WSToastUtil.show('Failed to obtain product information.');
return;
}

if (productDetailResponse.productDetails.isEmpty) {
ToastUtil.show('No product');
return;
}
List<ProductDetails> _products = productDetailResponse.productDetails;
// 查询成功
ProductDetails productDetails = _products[0];
PurchaseParam purchaseParam = PurchaseParam(
productDetails: productDetails,
applicationUserName: '${dataMap['orderNo']}',
);
//向苹果服务器发起支付请求
var res = await InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam);
Logger.debug('buyConsumable result: $res');
}

static Future<void> _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList, Map dataMap) async {
Logger.debug('===>>>_listenToPurchaseUpdated callback');
for (final PurchaseDetails purchaseDetails in purchaseDetailsList) {
Logger.debug(
'===>>>_listenToPurchaseUpdated status=${purchaseDetails.status} productID=${purchaseDetails.productID} '
'transactionDate=${purchaseDetails.transactionDate}'
'purchaseID=${purchaseDetails.purchaseID} transactionDate=${purchaseDetails.transactionDate}'
'verificationData.localVerificationData=${purchaseDetails.verificationData.localVerificationData}'
'verificationData.serverVerificationData=${purchaseDetails.verificationData.serverVerificationData}'
'verificationData.source=${purchaseDetails.verificationData.source}');
if (purchaseDetails.status == PurchaseStatus.pending) {
/// 等待购买中
} else if (purchaseDetails.status == PurchaseStatus.canceled) {
/// 取消订单
InAppPurchase.instance.completePurchase(purchaseDetails);
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
/// 购买出错
WSToastUtil.show('Purchase error');
InAppPurchase.instance.completePurchase(purchaseDetails);
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchase.instance.completePurchase(purchaseDetails);
}
if (purchaseDetails.productID == dataMap['goodsCode']) {
// 支付成功,发放商品
await deliverProduct(purchaseDetails, dataMap);
}
}
}
}
}

/// 调用后台接口,发放商品
static Future<void> deliverProduct(PurchaseDetails purchaseDetails, Map dataMap) async {
String code = dataMap['goodsCode'];
String payload = purchaseDetails.verificationData.serverVerificationData;
String transactionId = purchaseDetails.purchaseID!;
var res = await paymentIpa(dataMap['orderNo'], payload, transactionId);
// todo 调用后台接口
}
}

class PaymentQueueDelegate implements SKPaymentQueueDelegateWrapper {
@override
bool shouldContinueTransaction(SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) {
return true;
}

@override
bool shouldShowPriceConsent() {
return false;
}
}


Read More

空安全

概述

为什么需要空安全?

当你选择使用空安全时,代码中的类型将默认是非空的,意味着除非你声明它们可空,它们的值都不能为空。有了空安全,原本处于你的 运行时 的空值引用错误将变为 编辑时 的分析错误。这样可以大大降低空指针问题。

比如以下代码,string参数可能会报NoSuchMethodError异常。

1
2
3
4
5
6
7
// Without null safety:
bool isEmpty(String string) => string.length == 0;

main() {
isEmpty(null);
}

Read More

isolate

定义

isolate是Dart对actor并发模式的实现。运行中的Dart程序由一个或多个actor组成,这些actor也就是Dart概念里面的isolate。isolate是有自己的内存和单线程控制的运行实体。isolate本身的意思是“隔离”,因为isolate之间的内存在逻辑上是隔离的。isolate中的代码是按顺序执行的,任何Dart程序的并发都是运行多个isolate的结果。因为Dart没有共享内存的并发,没有竞争的可能性所以不需要锁,也就不用担心死锁的问题。

由于isolate之间没有共享内存,所以他们之间的通信唯一方式只能是通过Port进行,而且Dart中的消息传递总是异步的。

isolate跟线程类似,但线程之间可以共享内存,isolate不可以。

原理

Read More

dart命令行工具

dart analyze 命令

代码分析工具

1
2
3
4
5
6
7

# 设置分析等级 --no-fatal-warnings
dart analyze --fatal-infos

# 指定目录或文件
dart analyze [<DIRECTORY> | <DART_FILE>]

dart compile 命令

编译dart文件

子命令

Read More