payment_service.dart
7.85 KB
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:Parlando/util/toast_utils.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
class PaymentService {
/// We want singleton object of ``PaymentService`` so create private constructor
///
/// Use PaymentService as ``PaymentService.instance``
PaymentService._internal();
static final PaymentService instance = PaymentService._internal();
/// To listen the status of connection between app and the billing server
late StreamSubscription<ConnectionResult> _connectionSubscription;
/// To listen the status of the purchase made inside or outside of the app (App Store / Play Store)
///
/// If status is not error then app will be notied by this stream
late StreamSubscription<PurchasedItem?> _purchaseUpdatedSubscription;
/// To listen the errors of the purchase
late StreamSubscription<PurchaseResult?> _purchaseErrorSubscription;
/// List of product ids you want to fetch
final List<String> _productIds = ['test.yiyan.vip.1.month'];
/// All available products will be store in this list
late List<IAPItem> _products;
/// All past purchases will be store in this list
late List<PurchasedItem> _pastPurchases;
/// view of the app will subscribe to this to get notified
/// when premium status of the user changes
final ObserverList<Function> _proStatusChangedListeners =
ObserverList<Function>();
/// view of the app will subscribe to this to get errors of the purchase
final ObserverList<Function(String)> _errorListeners =
ObserverList<Function(String)>();
/// logged in user's premium status
bool _isProUser = false;
bool get isProUser => _isProUser;
/// view can subscribe to _proStatusChangedListeners using this method
addToProStatusChangedListeners(Function callback) {
_proStatusChangedListeners.add(callback);
}
/// view can cancel to _proStatusChangedListeners using this method
removeFromProStatusChangedListeners(Function callback) {
_proStatusChangedListeners.remove(callback);
}
/// view can subscribe to _errorListeners using this method
addToErrorListeners(Function(String) callback) {
_errorListeners.add(callback);
}
/// view can cancel to _errorListeners using this method
removeFromErrorListeners(Function(String) callback) {
_errorListeners.remove(callback);
}
/// Call this method to notify all the subsctibers of _proStatusChangedListeners
void _callProStatusChangedListeners() {
for (var callback in _proStatusChangedListeners) {
callback();
}
}
/// Call this method to notify all the subsctibers of _errorListeners
void _callErrorListeners(String error) {
for (var callback in _errorListeners) {
callback(error);
}
}
/// Call this method at the startup of you app to initialize connection
/// with billing server and get all the necessary data
void initConnection() {
var result = FlutterInappPurchase.instance.initialize();
print("___________________________");
print("result:$result");
_connectionSubscription =
FlutterInappPurchase.connectionUpdated.listen((connected) {});
_purchaseUpdatedSubscription =
FlutterInappPurchase.purchaseUpdated.listen(_handlePurchaseUpdate);
_purchaseErrorSubscription =
FlutterInappPurchase.purchaseError.listen(_handlePurchaseError);
_getItems();
_getPastPurchases();
}
/// call when user close the app
void dispose() {
_connectionSubscription.cancel();
_purchaseErrorSubscription.cancel();
_purchaseUpdatedSubscription.cancel();
FlutterInappPurchase.instance.finalize();
}
void _handlePurchaseError(PurchaseResult? purchaseError) {
_callErrorListeners(purchaseError!.message.toString());
}
/// Called when new updates arrives at ``purchaseUpdated`` stream
void _handlePurchaseUpdate(PurchasedItem? productItem) async {
if (Platform.isAndroid) {
await _handlePurchaseUpdateAndroid(productItem!);
} else {
await _handlePurchaseUpdateIOS(productItem!);
}
}
Future<void> _handlePurchaseUpdateIOS(PurchasedItem purchasedItem) async {
switch (purchasedItem.transactionStateIOS) {
case TransactionState.deferred:
// Edit: This was a bug that was pointed out here : https://github.com/dooboolab/flutter_inapp_purchase/issues/234
// FlutterInappPurchase.instance.finishTransaction(purchasedItem);
break;
case TransactionState.failed:
_callErrorListeners("Transaction Failed");
FlutterInappPurchase.instance.finishTransaction(purchasedItem);
break;
case TransactionState.purchased:
await _verifyAndFinishTransaction(purchasedItem);
break;
case TransactionState.purchasing:
break;
case TransactionState.restored:
FlutterInappPurchase.instance.finishTransaction(purchasedItem);
break;
default:
}
}
/// three purchase state https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState
/// 0 : UNSPECIFIED_STATE
/// 1 : PURCHASED
/// 2 : PENDING
Future<void> _handlePurchaseUpdateAndroid(PurchasedItem purchasedItem) async {
switch (purchasedItem.purchaseStateAndroid) {
case PurchaseState.purchased:
if (purchasedItem.isAcknowledgedAndroid == null) {
await _verifyAndFinishTransaction(purchasedItem);
}
break;
default:
_callErrorListeners("Something went wrong");
}
}
/// Call this method when status of purchase is success
/// Call API of your back end to verify the receipt
/// back end has to call billing server's API to verify the purchase token
_verifyAndFinishTransaction(PurchasedItem purchasedItem) async {
bool isValid = false;
try {
// Call API
isValid = await _verifyPurchase(purchasedItem);
} on Exception {
_callErrorListeners("暂时无法购买,请稍后再试!");
return;
}
if (isValid) {
FlutterInappPurchase.instance.finishTransaction(purchasedItem);
_isProUser = true;
// save in sharedPreference here
_callProStatusChangedListeners();
} else {
_callErrorListeners("Verification failed");
}
}
Future<List<IAPItem>> get products async {
if (_products == null) {
await _getItems();
}
return _products;
}
Future<void> _getItems() async {
List<IAPItem> items =
await FlutterInappPurchase.instance.getSubscriptions(_productIds);
_products = [];
for (var item in items) {
_products.add(item);
}
print("############");
print(_products);
}
void _getPastPurchases() async {
// remove this if you want to restore past purchases in iOS
if (Platform.isIOS) {
return;
}
List<PurchasedItem>? purchasedItems =
await FlutterInappPurchase.instance.getAvailablePurchases();
for (var purchasedItem in purchasedItems!) {
bool isValid = false;
if (Platform.isAndroid) {
Map map = json.decode(purchasedItem.transactionReceipt!);
// if your app missed finishTransaction due to network or crash issue
// finish transactins
if (!map['acknowledged']) {
isValid = await _verifyPurchase(purchasedItem);
if (isValid) {
FlutterInappPurchase.instance.finishTransaction(purchasedItem);
_isProUser = true;
_callProStatusChangedListeners();
}
} else {
_isProUser = true;
_callProStatusChangedListeners();
}
}
}
_pastPurchases = [];
_pastPurchases.addAll(purchasedItems);
}
Future<void> buyProduct(IAPItem item) async {
try {
await FlutterInappPurchase.instance
.requestSubscription(item.productId.toString());
} catch (error) {
Toast.show("购买失败!");
}
}
_verifyPurchase(PurchasedItem purchasedItem) {}
}