reason

增加支付

...@@ -95,7 +95,7 @@ class MyApp extends StatelessWidget { ...@@ -95,7 +95,7 @@ class MyApp extends StatelessWidget {
95 } 95 }
96 interceptors.add(AdapterInterceptor()); 96 interceptors.add(AdapterInterceptor());
97 configDio( 97 configDio(
98 - baseUrl: 'https://www.yiyan.pub/api/v1/', 98 + baseUrl: 'https://api.parlando.ink/api/v1/',
99 interceptors: interceptors, 99 interceptors: interceptors,
100 ); 100 );
101 } 101 }
......
1 import 'dart:async'; 1 import 'dart:async';
2 -import 'dart:io';
3 import 'dart:ui'; 2 import 'dart:ui';
4 3
5 import 'package:Parlando/apis/api_response.dart'; 4 import 'package:Parlando/apis/api_response.dart';
6 import 'package:Parlando/login/login_router.dart'; 5 import 'package:Parlando/login/login_router.dart';
7 import 'package:Parlando/membership/models/membership_entity.dart'; 6 import 'package:Parlando/membership/models/membership_entity.dart';
8 import 'package:Parlando/membership/view_models/membership_view_model.dart'; 7 import 'package:Parlando/membership/view_models/membership_view_model.dart';
8 +import 'package:Parlando/payment/payment_service.dart';
9 import 'package:Parlando/res/constant.dart'; 9 import 'package:Parlando/res/constant.dart';
10 import 'package:cached_network_image/cached_network_image.dart'; 10 import 'package:cached_network_image/cached_network_image.dart';
11 import 'package:flustars/flustars.dart'; 11 import 'package:flustars/flustars.dart';
...@@ -14,6 +14,7 @@ import 'package:Parlando/res/resources.dart'; ...@@ -14,6 +14,7 @@ import 'package:Parlando/res/resources.dart';
14 import 'package:Parlando/routers/fluro_navigator.dart'; 14 import 'package:Parlando/routers/fluro_navigator.dart';
15 import 'package:Parlando/extension/int_extension.dart'; 15 import 'package:Parlando/extension/int_extension.dart';
16 import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'; 16 import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
17 +import 'package:getwidget/getwidget.dart';
17 import 'package:provider/provider.dart'; 18 import 'package:provider/provider.dart';
18 19
19 class MembershipPage extends StatefulWidget { 20 class MembershipPage extends StatefulWidget {
...@@ -27,18 +28,7 @@ class MembershipPageState extends State<MembershipPage> ...@@ -27,18 +28,7 @@ class MembershipPageState extends State<MembershipPage>
27 with WidgetsBindingObserver { 28 with WidgetsBindingObserver {
28 bool _isLoading = false; 29 bool _isLoading = false;
29 30
30 - late StreamSubscription _purchaseUpdatedSubscription; 31 + final List<Widget> _productWidgets = <Widget>[];
31 - late StreamSubscription _purchaseErrorSubscription;
32 - late StreamSubscription _conectionSubscription;
33 - final List<String> _productLists = Platform.isAndroid
34 - ? [
35 - 'test.yiyan.vip.1.month',
36 - ]
37 - : ['test.yiyan.vip.1.month'];
38 -
39 - String _platformVersion = 'Unknown';
40 - List<IAPItem> _items = [];
41 - List<PurchasedItem> _purchases = [];
42 32
43 @override 33 @override
44 void initState() { 34 void initState() {
...@@ -48,8 +38,7 @@ class MembershipPageState extends State<MembershipPage> ...@@ -48,8 +38,7 @@ class MembershipPageState extends State<MembershipPage>
48 .setSelectedMembership(null); 38 .setSelectedMembership(null);
49 Provider.of<MembershipViewProvider>(context, listen: false) 39 Provider.of<MembershipViewProvider>(context, listen: false)
50 .fetchMembershipData('0'); 40 .fetchMembershipData('0');
51 - 41 + PaymentService.instance.initConnection();
52 - initPlatformState();
53 } else { 42 } else {
54 NavigatorUtils.push(context, LoginRouter.loginPage, replace: true); 43 NavigatorUtils.push(context, LoginRouter.loginPage, replace: true);
55 } 44 }
...@@ -130,9 +119,11 @@ class MembershipPageState extends State<MembershipPage> ...@@ -130,9 +119,11 @@ class MembershipPageState extends State<MembershipPage>
130 SizedBox( 119 SizedBox(
131 width: double.infinity, 120 width: double.infinity,
132 height: 100, 121 height: 100,
133 - child: Column( 122 + child: _productWidgets.isEmpty
134 - children: _renderInApps(), 123 + ? Column(
135 - ), 124 + children: _productWidgets,
125 + )
126 + : const GFLoader(),
136 ), 127 ),
137 Gaps.vGap24, 128 Gaps.vGap24,
138 Text( 129 Text(
...@@ -145,7 +136,7 @@ class MembershipPageState extends State<MembershipPage> ...@@ -145,7 +136,7 @@ class MembershipPageState extends State<MembershipPage>
145 Gaps.vGap10, 136 Gaps.vGap10,
146 Row( 137 Row(
147 mainAxisAlignment: 138 mainAxisAlignment:
148 - MainAxisAlignment.spaceBetween, 139 + MainAxisAlignment.spaceBetween,
149 mainAxisSize: MainAxisSize.min, 140 mainAxisSize: MainAxisSize.min,
150 crossAxisAlignment: CrossAxisAlignment.center, 141 crossAxisAlignment: CrossAxisAlignment.center,
151 children: [ 142 children: [
...@@ -220,72 +211,15 @@ class MembershipPageState extends State<MembershipPage> ...@@ -220,72 +211,15 @@ class MembershipPageState extends State<MembershipPage>
220 211
221 @override 212 @override
222 void dispose() { 213 void dispose() {
223 - _conectionSubscription.cancel(); 214 + PaymentService.instance.dispose();
224 super.dispose(); 215 super.dispose();
225 } 216 }
226 217
227 - Future<void> initPlatformState() async { 218 + void _renderInApps() async {
228 - String platformVersion; 219 + List<IAPItem> items = await PaymentService.instance.products;
229 - // Platform messages may fail, so we use a try/catch PlatformException. 220 + for (IAPItem item in items) {
230 - 221 + _productWidgets.add(Text(item.title!));
231 - // prepare
232 - var result = await FlutterInappPurchase.instance.initialize();
233 - print('result: $result');
234 -
235 - // If the widget was removed from the tree while the asynchronous platform
236 - // message was in flight, we want to discard the reply rather than calling
237 - // setState to update our non-existent appearance.
238 - if (!mounted) return;
239 -
240 - setState(() {
241 - // _platformVersion = platformVersion;
242 - });
243 -
244 - // refresh items for android
245 - try {
246 - String msg = await FlutterInappPurchase.instance.consumeAll();
247 - print('consumeAllItems: $msg');
248 - } catch (err) {
249 - print('consumeAllItems error: $err');
250 - }
251 -
252 - _conectionSubscription =
253 - FlutterInappPurchase.connectionUpdated.listen((connected) {
254 - print('connected: $connected');
255 - });
256 -
257 - _purchaseUpdatedSubscription =
258 - FlutterInappPurchase.purchaseUpdated.listen((productItem) {
259 - print('purchase-updated: $productItem');
260 - });
261 -
262 - _purchaseErrorSubscription =
263 - FlutterInappPurchase.purchaseError.listen((purchaseError) {
264 - print('purchase-error: $purchaseError');
265 - });
266 -
267 - List<IAPItem> items =
268 - await FlutterInappPurchase.instance.getSubscriptions(_productLists);
269 - for (var item in items) {
270 - print('${item.toString()}');
271 - _items.add(item);
272 - }
273 -
274 - setState(() {
275 - _items = items;
276 - _purchases = [];
277 - });
278 - }
279 -
280 - List<Widget> _renderInApps() {
281 - List<Widget> widgets = <Widget>[];
282 - for (IAPItem item in _items) {
283 - widgets.add(Text(item.title!));
284 } 222 }
285 - return widgets; 223 + setState(() {});
286 - }
287 -
288 - void _requestPurchase(IAPItem item) {
289 - FlutterInappPurchase.instance.requestPurchase(item.productId!);
290 } 224 }
291 } 225 }
......
1 +import 'dart:async';
2 +import 'dart:convert';
3 +import 'dart:io';
4 +
5 +import 'package:Parlando/util/toast_utils.dart';
6 +import 'package:flutter/foundation.dart';
7 +import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
8 +
9 +class PaymentService {
10 + /// We want singleton object of ``PaymentService`` so create private constructor
11 + ///
12 + /// Use PaymentService as ``PaymentService.instance``
13 + PaymentService._internal();
14 +
15 + static final PaymentService instance = PaymentService._internal();
16 +
17 + /// To listen the status of connection between app and the billing server
18 + late StreamSubscription<ConnectionResult> _connectionSubscription;
19 +
20 + /// To listen the status of the purchase made inside or outside of the app (App Store / Play Store)
21 + ///
22 + /// If status is not error then app will be notied by this stream
23 + late StreamSubscription<PurchasedItem?> _purchaseUpdatedSubscription;
24 +
25 + /// To listen the errors of the purchase
26 + late StreamSubscription<PurchaseResult?> _purchaseErrorSubscription;
27 +
28 + /// List of product ids you want to fetch
29 + final List<String> _productIds = ['test.yiyan.vip.1.month'];
30 +
31 + /// All available products will be store in this list
32 + late List<IAPItem> _products;
33 +
34 + /// All past purchases will be store in this list
35 + late List<PurchasedItem> _pastPurchases;
36 +
37 + /// view of the app will subscribe to this to get notified
38 + /// when premium status of the user changes
39 + final ObserverList<Function> _proStatusChangedListeners =
40 + ObserverList<Function>();
41 +
42 + /// view of the app will subscribe to this to get errors of the purchase
43 + final ObserverList<Function(String)> _errorListeners =
44 + ObserverList<Function(String)>();
45 +
46 + /// logged in user's premium status
47 + bool _isProUser = false;
48 +
49 + bool get isProUser => _isProUser;
50 +
51 + /// view can subscribe to _proStatusChangedListeners using this method
52 + addToProStatusChangedListeners(Function callback) {
53 + _proStatusChangedListeners.add(callback);
54 + }
55 +
56 + /// view can cancel to _proStatusChangedListeners using this method
57 + removeFromProStatusChangedListeners(Function callback) {
58 + _proStatusChangedListeners.remove(callback);
59 + }
60 +
61 + /// view can subscribe to _errorListeners using this method
62 + addToErrorListeners(Function(String) callback) {
63 + _errorListeners.add(callback);
64 + }
65 +
66 + /// view can cancel to _errorListeners using this method
67 + removeFromErrorListeners(Function(String) callback) {
68 + _errorListeners.remove(callback);
69 + }
70 +
71 + /// Call this method to notify all the subsctibers of _proStatusChangedListeners
72 + void _callProStatusChangedListeners() {
73 + for (var callback in _proStatusChangedListeners) {
74 + callback();
75 + }
76 + }
77 +
78 + /// Call this method to notify all the subsctibers of _errorListeners
79 + void _callErrorListeners(String error) {
80 + for (var callback in _errorListeners) {
81 + callback(error);
82 + }
83 + }
84 +
85 + /// Call this method at the startup of you app to initialize connection
86 + /// with billing server and get all the necessary data
87 + void initConnection() {
88 + var result = FlutterInappPurchase.instance.initialize();
89 + print("___________________________");
90 + print("result:$result");
91 + _connectionSubscription =
92 + FlutterInappPurchase.connectionUpdated.listen((connected) {});
93 +
94 + _purchaseUpdatedSubscription =
95 + FlutterInappPurchase.purchaseUpdated.listen(_handlePurchaseUpdate);
96 +
97 + _purchaseErrorSubscription =
98 + FlutterInappPurchase.purchaseError.listen(_handlePurchaseError);
99 +
100 + _getItems();
101 + _getPastPurchases();
102 + }
103 +
104 + /// call when user close the app
105 + void dispose() {
106 + _connectionSubscription.cancel();
107 + _purchaseErrorSubscription.cancel();
108 + _purchaseUpdatedSubscription.cancel();
109 + FlutterInappPurchase.instance.finalize();
110 + }
111 +
112 + void _handlePurchaseError(PurchaseResult? purchaseError) {
113 + _callErrorListeners(purchaseError!.message.toString());
114 + }
115 +
116 + /// Called when new updates arrives at ``purchaseUpdated`` stream
117 + void _handlePurchaseUpdate(PurchasedItem? productItem) async {
118 + if (Platform.isAndroid) {
119 + await _handlePurchaseUpdateAndroid(productItem!);
120 + } else {
121 + await _handlePurchaseUpdateIOS(productItem!);
122 + }
123 + }
124 +
125 + Future<void> _handlePurchaseUpdateIOS(PurchasedItem purchasedItem) async {
126 + switch (purchasedItem.transactionStateIOS) {
127 + case TransactionState.deferred:
128 + // Edit: This was a bug that was pointed out here : https://github.com/dooboolab/flutter_inapp_purchase/issues/234
129 + // FlutterInappPurchase.instance.finishTransaction(purchasedItem);
130 + break;
131 + case TransactionState.failed:
132 + _callErrorListeners("Transaction Failed");
133 + FlutterInappPurchase.instance.finishTransaction(purchasedItem);
134 + break;
135 + case TransactionState.purchased:
136 + await _verifyAndFinishTransaction(purchasedItem);
137 + break;
138 + case TransactionState.purchasing:
139 + break;
140 + case TransactionState.restored:
141 + FlutterInappPurchase.instance.finishTransaction(purchasedItem);
142 + break;
143 + default:
144 + }
145 + }
146 +
147 + /// three purchase state https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState
148 + /// 0 : UNSPECIFIED_STATE
149 + /// 1 : PURCHASED
150 + /// 2 : PENDING
151 + Future<void> _handlePurchaseUpdateAndroid(PurchasedItem purchasedItem) async {
152 + switch (purchasedItem.purchaseStateAndroid) {
153 + case PurchaseState.purchased:
154 + if (purchasedItem.isAcknowledgedAndroid == null) {
155 + await _verifyAndFinishTransaction(purchasedItem);
156 + }
157 + break;
158 + default:
159 + _callErrorListeners("Something went wrong");
160 + }
161 + }
162 +
163 + /// Call this method when status of purchase is success
164 + /// Call API of your back end to verify the receipt
165 + /// back end has to call billing server's API to verify the purchase token
166 + _verifyAndFinishTransaction(PurchasedItem purchasedItem) async {
167 + bool isValid = false;
168 + try {
169 + // Call API
170 + isValid = await _verifyPurchase(purchasedItem);
171 + } on Exception {
172 + _callErrorListeners("暂时无法购买,请稍后再试!");
173 + return;
174 + }
175 +
176 + if (isValid) {
177 + FlutterInappPurchase.instance.finishTransaction(purchasedItem);
178 + _isProUser = true;
179 + // save in sharedPreference here
180 + _callProStatusChangedListeners();
181 + } else {
182 + _callErrorListeners("Verification failed");
183 + }
184 + }
185 +
186 + Future<List<IAPItem>> get products async {
187 + if (_products == null) {
188 + await _getItems();
189 + }
190 + return _products;
191 + }
192 +
193 + Future<void> _getItems() async {
194 + List<IAPItem> items =
195 + await FlutterInappPurchase.instance.getSubscriptions(_productIds);
196 + _products = [];
197 + for (var item in items) {
198 + _products.add(item);
199 + }
200 + print("############");
201 + print(_products);
202 + }
203 +
204 + void _getPastPurchases() async {
205 + // remove this if you want to restore past purchases in iOS
206 + if (Platform.isIOS) {
207 + return;
208 + }
209 + List<PurchasedItem>? purchasedItems =
210 + await FlutterInappPurchase.instance.getAvailablePurchases();
211 +
212 + for (var purchasedItem in purchasedItems!) {
213 + bool isValid = false;
214 +
215 + if (Platform.isAndroid) {
216 + Map map = json.decode(purchasedItem.transactionReceipt!);
217 + // if your app missed finishTransaction due to network or crash issue
218 + // finish transactins
219 + if (!map['acknowledged']) {
220 + isValid = await _verifyPurchase(purchasedItem);
221 + if (isValid) {
222 + FlutterInappPurchase.instance.finishTransaction(purchasedItem);
223 + _isProUser = true;
224 + _callProStatusChangedListeners();
225 + }
226 + } else {
227 + _isProUser = true;
228 + _callProStatusChangedListeners();
229 + }
230 + }
231 + }
232 +
233 + _pastPurchases = [];
234 + _pastPurchases.addAll(purchasedItems);
235 + }
236 +
237 + Future<void> buyProduct(IAPItem item) async {
238 + try {
239 + await FlutterInappPurchase.instance
240 + .requestSubscription(item.productId.toString());
241 + } catch (error) {
242 + Toast.show("购买失败!");
243 + }
244 + }
245 +
246 + _verifyPurchase(PurchasedItem purchasedItem) {}
247 +}