CAKE-306 | fixed moonpay_buy_provider.dart; added onEnabled() method to moonpay_buy_provider.dart; checked is MoonPayBuyProvider enabled; added isMoonPayEnabled to settings_store.dart

OleksandrSobol committed Jun 1, 2021 at 21:03 UTC 9e1c151f85ce2d1d9b48fd1c40ac0057891b3b45
6 files changed +135 -30
lib/buy/buy_amount.dart
+5 -1
@@ -1,8 +1,12 @@
1 import 'package:flutter/foundation.dart';
2
3 class BuyAmount {
4 - BuyAmount({@required this.sourceAmount, @required this.destAmount});
4 + BuyAmount({
5 + @required this.sourceAmount,
6 + @required this.destAmount,
7 + this.minAmount = 0});
8
9 final double sourceAmount;
10 final double destAmount;
11 + final int minAmount;
12 }
\ No newline at end of file
lib/buy/moonpay/moonpay_buy_provider.dart
+54 -13
@@ -1,4 +1,5 @@
1 import 'dart:convert';
2 +import 'package:crypto/crypto.dart';
3 import 'package:cake_wallet/buy/buy_exception.dart';
4 import 'package:http/http.dart';
5 import 'package:cake_wallet/buy/buy_amount.dart';
@@ -13,17 +14,18 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
14 class MoonPayBuyProvider extends BuyProvider {
15 MoonPayBuyProvider({WalletBase wallet, bool isTestEnvironment = false})
16 : super(wallet: wallet, isTestEnvironment: isTestEnvironment) {
16 - baseApiUrl = isTestEnvironment
17 - ? _baseTestApiUrl
18 - : _baseProductApiUrl;
17 + baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl;
18 }
19
21 - static const _baseTestApiUrl = 'https://buy-staging.moonpay.com';
22 - static const _baseProductApiUrl = 'https://api.moonpay.com';
20 + static const _baseTestUrl = 'https://buy-staging.moonpay.com';
21 + static const _baseProductUrl = 'https://buy.moonpay.com';
22 + static const _apiUrl = 'https://api.moonpay.com';
23 static const _currenciesSuffix = '/v3/currencies';
24 static const _quoteSuffix = '/buy_quote';
25 static const _transactionsSuffix = '/v1/transactions';
26 + static const _countrySuffix = '/v3/countries';
27 static const _apiKey = secrets.moonPayApiKey;
28 + static const _secretKey = secrets.moonPaySecretKey;
29
30 @override
31 String get title => 'MoonPay';
@@ -35,9 +37,9 @@ class MoonPayBuyProvider extends BuyProvider {
37 walletTypeToCryptoCurrency(walletType).title.toLowerCase();
38
39 @override
38 - String get trackUrl => baseApiUrl + '/transaction_receipt?transactionId=';
40 + String get trackUrl => baseUrl + '/transaction_receipt?transactionId=';
41
40 - String baseApiUrl;
42 + String baseUrl;
43
44 @override
45 Future<String> requestUrl(String amount, String sourceCurrency) async {
@@ -45,22 +47,32 @@ class MoonPayBuyProvider extends BuyProvider {
47 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay'
48 '%2Csepa_bank_transfer%2Cgbp_bank_transfer%2Cgbp_open_banking_payment';
49
48 - final originalUrl = baseApiUrl + '?apiKey=' + _apiKey + '&currencyCode=' +
50 + final suffix = '?apiKey=' + _apiKey + '&currencyCode=' +
51 currencyCode + '&enabledPaymentMethods=' + enabledPaymentMethods +
52 '&walletAddress=' + walletAddress +
53 '&baseCurrencyCode=' + sourceCurrency.toLowerCase() +
54 '&baseCurrencyAmount=' + amount + '&lockAmount=true' +
55 '&showAllCurrencies=false' + '&showWalletAddressForm=false';
56
55 - return originalUrl;
57 + final originalUrl = baseUrl + suffix;
58 +
59 + final messageBytes = utf8.encode(suffix);
60 + final key = utf8.encode(_secretKey);
61 + final hmac = Hmac(sha256, key);
62 + final digest = hmac.convert(messageBytes);
63 + final signature = base64.encode(digest.bytes);
64 + final urlWithSignature = originalUrl +
65 + '&signature=${Uri.encodeComponent(signature)}';
66 +
67 + return isTestEnvironment ? originalUrl : urlWithSignature;
68 }
69
70 @override
71 Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
60 - final url = _baseProductApiUrl + _currenciesSuffix + '/$currencyCode' +
72 + final url = _apiUrl + _currenciesSuffix + '/$currencyCode' +
73 _quoteSuffix + '/?apiKey=' + _apiKey +
74 '&baseCurrencyAmount=' + amount +
63 - '&baseCurrencyCode' + sourceCurrency.toLowerCase();
75 + '&baseCurrencyCode=' + sourceCurrency.toLowerCase();
76
77 final response = await get(url);
78
@@ -73,13 +85,17 @@ class MoonPayBuyProvider extends BuyProvider {
85 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
86 final sourceAmount = responseJSON['totalAmount'] as double;
87 final destAmount = responseJSON['quoteCurrencyAmount'] as double;
88 + final minSourceAmount = responseJSON['baseCurrency']['minAmount'] as int;
89
77 - return BuyAmount(sourceAmount: sourceAmount, destAmount: destAmount);
90 + return BuyAmount(
91 + sourceAmount: sourceAmount,
92 + destAmount: destAmount,
93 + minAmount: minSourceAmount);
94 }
95
96 @override
97 Future<Order> findOrderById(String id) async {
82 - final url = _baseProductApiUrl + _transactionsSuffix + '/$id' +
98 + final url = _apiUrl + _transactionsSuffix + '/$id' +
99 '?apiKey=' + _apiKey;
100
101 final response = await get(url);
@@ -108,4 +124,29 @@ class MoonPayBuyProvider extends BuyProvider {
124 walletId: walletId
125 );
126 }
127 +
128 + static Future<bool> onEnabled(String deviceCountryCode) async {
129 + final url = _apiUrl + _countrySuffix;
130 + var isBuyEnable = false;
131 +
132 + final response = await get(url);
133 +
134 + try {
135 + final responseJSON = json.decode(response.body) as List<dynamic>;
136 +
137 + for (var element in responseJSON) {
138 + final countryMap = element as Map<String, dynamic>;
139 + final countryCode = countryMap['alpha2'] as String;
140 + if (countryCode == deviceCountryCode) {
141 + isBuyEnable = countryMap['isBuyAllowed'] as bool;
142 + break;
143 + }
144 + }
145 + } catch (e) {
146 + isBuyEnable = false;
147 + print(e.toString());
148 + }
149 +
150 + return isBuyEnable;
151 + }
152 }
\ No newline at end of file
lib/di.dart
+11 -2
@@ -1,5 +1,6 @@
1 import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
2 import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart';
3 +import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
4 import 'package:cake_wallet/core/backup_service.dart';
5 import 'package:cake_wallet/core/wallet_service.dart';
6 import 'package:cake_wallet/entities/biometric_auth.dart';
@@ -91,6 +92,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
92 import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
93 import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
94 import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
95 +import 'package:devicelocale/devicelocale.dart';
96 import 'package:flutter/widgets.dart';
97 import 'package:get_it/get_it.dart';
98 import 'package:hive/hive.dart';
@@ -152,8 +154,14 @@ Future setup(
154 final isBitcoinBuyEnabled = (secrets.wyreSecretKey?.isNotEmpty ?? false) &&
155 (secrets.wyreApiKey?.isNotEmpty ?? false) &&
156 (secrets.wyreAccountId?.isNotEmpty ?? false);
157 +
158 + final locale = await Devicelocale.currentLocale;
159 + final deviceCountryCode = locale.split('_').last;
160 + final isMoonPayEnabled = await MoonPayBuyProvider.onEnabled(deviceCountryCode);
161 +
162 final settingsStore = await SettingsStoreBase.load(
156 - nodeSource: _nodeSource, isBitcoinBuyEnabled: isBitcoinBuyEnabled);
163 + nodeSource: _nodeSource, isBitcoinBuyEnabled: isBitcoinBuyEnabled,
164 + isMoonPayEnabled: isMoonPayEnabled);
165
166 if (_isSetupFinished) {
167 return;
@@ -541,7 +549,8 @@ Future setup(
549 final wallet = getIt.get<AppStore>().wallet;
550
551 return BuyViewModel(_ordersSource, getIt.get<OrdersStore>(),
544 - getIt.get<BuyAmountViewModel>(), wallet: wallet);
552 + getIt.get<SettingsStore>(), getIt.get<BuyAmountViewModel>(),
553 + wallet: wallet);
554 });
555
556 getIt.registerFactory(() {
lib/src/screens/buy/pre_order_page.dart
+38 -8
@@ -1,7 +1,11 @@
1 import 'dart:ui';
2 import 'package:cake_wallet/buy/buy_amount.dart';
3 +import 'package:cake_wallet/buy/buy_provider.dart';
4 +import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
5 import 'package:cake_wallet/src/screens/buy/widgets/buy_list_item.dart';
6 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
7 import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
8 +import 'package:cake_wallet/utils/show_pop_up.dart';
9 import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
10 import 'package:flutter/cupertino.dart';
11 import 'package:flutter/material.dart';
@@ -172,13 +176,16 @@ class PreOrderPage extends BasePage {
176 builder: (context, AsyncSnapshot<BuyAmount> snapshot) {
177 double sourceAmount;
178 double destAmount;
179 + int minAmount;
180
181 if (snapshot.hasData) {
182 sourceAmount = snapshot.data.sourceAmount;
183 destAmount = snapshot.data.destAmount;
184 + minAmount = snapshot.data.minAmount;
185 } else {
186 sourceAmount = 0.0;
187 destAmount = 0.0;
188 + minAmount = 0;
189 }
190
191 return Padding(
@@ -192,14 +199,15 @@ class PreOrderPage extends BasePage {
199 sourceCurrency: buyViewModel.fiatCurrency,
200 destAmount: destAmount,
201 destCurrency: buyViewModel.cryptoCurrency,
195 - onTap:
196 - buyViewModel.buyAmountViewModel
197 - .doubleAmount == 0.0 ? null : () {
198 - buyViewModel.selectedProvider = item.provider;
199 - sourceAmount > 0
200 - ? buyViewModel.isDisabled = false
201 - : buyViewModel.isDisabled = true;
202 - }
202 + onTap: ((buyViewModel.buyAmountViewModel
203 + .doubleAmount != 0.0) &&
204 + (snapshot.hasData)) ? () =>
205 + onSelectBuyProvider(
206 + context: context,
207 + provider: item.provider,
208 + sourceAmount: sourceAmount,
209 + minAmount: minAmount
210 + ) : null
211 );
212 })
213 );
@@ -242,4 +250,26 @@ class PreOrderPage extends BasePage {
250 )
251 );
252 }
253 +
254 + void onSelectBuyProvider({BuildContext context, BuyProvider provider,
255 + double sourceAmount, int minAmount}) {
256 +
257 + if ((provider is MoonPayBuyProvider)&&
258 + (buyViewModel.buyAmountViewModel.doubleAmount < minAmount)) {
259 + showPopUp<void>(
260 + context: context,
261 + builder: (BuildContext context) {
262 + return AlertWithOneAction(
263 + alertTitle: 'MoonPay',
264 + alertContent: 'Value of the amount must be more than $minAmount ${buyViewModel.fiatCurrency.toString()}',
265 + buttonText: S.of(context).ok,
266 + buttonAction: () => Navigator.of(context).pop());
267 + });
268 + return;
269 + }
270 + buyViewModel.selectedProvider = provider;
271 + sourceAmount > 0
272 + ? buyViewModel.isDisabled = false
273 + : buyViewModel.isDisabled = true;
274 + }
275 }
\ No newline at end of file
lib/store/settings_store.dart
+17
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
2 +import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
3 import 'package:cake_wallet/entities/preferences_key.dart';
4 import 'package:cake_wallet/entities/transaction_priority.dart';
5 import 'package:cake_wallet/themes/theme_base.dart';
@@ -18,6 +19,7 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
19 import 'package:cake_wallet/entities/node.dart';
20 import 'package:cake_wallet/entities/monero_transaction_priority.dart';
21 import 'package:cake_wallet/entities/action_list_display_mode.dart';
22 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
23
24 part 'settings_store.g.dart';
25
@@ -39,6 +41,7 @@ abstract class SettingsStoreBase with Store {
41 @required TransactionPriority initialBitcoinTransactionPriority,
42 @required TransactionPriority initialMoneroTransactionPriority,
43 @required this.isBitcoinBuyEnabled,
44 + @required this.isMoonPayEnabled,
45 this.actionlistDisplayMode}) {
46 fiatCurrency = initialFiatCurrency;
47 balanceDisplayMode = initialBalanceDisplayMode;
@@ -147,9 +150,12 @@ abstract class SettingsStoreBase with Store {
150
151 bool isBitcoinBuyEnabled;
152
153 + bool isMoonPayEnabled;
154 +
155 static Future<SettingsStore> load(
156 {@required Box<Node> nodeSource,
157 @required bool isBitcoinBuyEnabled,
158 + @required bool isMoonPayEnabled,
159 FiatCurrency initialFiatCurrency = FiatCurrency.usd,
160 MoneroTransactionPriority initialMoneroTransactionPriority =
161 MoneroTransactionPriority.slow,
@@ -221,6 +227,7 @@ abstract class SettingsStoreBase with Store {
227 },
228 appVersion: packageInfo.version,
229 isBitcoinBuyEnabled: isBitcoinBuyEnabled,
230 + isMoonPayEnabled: isMoonPayEnabled,
231 initialFiatCurrency: currentFiatCurrency,
232 initialBalanceDisplayMode: currentBalanceDisplayMode,
233 initialSaveRecipientAddress: shouldSaveRecipientAddress,
@@ -242,8 +249,18 @@ abstract class SettingsStoreBase with Store {
249 BitcoinTransactionPriority.medium,
250 BalanceDisplayMode initialBalanceDisplayMode =
251 BalanceDisplayMode.availableBalance}) async {
252 + final isBitcoinBuyEnabled = (secrets.wyreSecretKey?.isNotEmpty ?? false) &&
253 + (secrets.wyreApiKey?.isNotEmpty ?? false) &&
254 + (secrets.wyreAccountId?.isNotEmpty ?? false);
255 +
256 + final locale = await Devicelocale.currentLocale;
257 + final deviceCountryCode = locale.split('_').last;
258 + final isMoonPayEnabled = await MoonPayBuyProvider.onEnabled(deviceCountryCode);
259 +
260 final settings = await SettingsStoreBase.load(
261 nodeSource: nodeSource,
262 + isBitcoinBuyEnabled: isBitcoinBuyEnabled,
263 + isMoonPayEnabled: isMoonPayEnabled,
264 initialBalanceDisplayMode: initialBalanceDisplayMode,
265 initialFiatCurrency: initialFiatCurrency,
266 initialMoneroTransactionPriority: initialMoneroTransactionPriority,
lib/view_model/buy/buy_view_model.dart
+10 -6
@@ -4,6 +4,7 @@ import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
4 import 'package:cake_wallet/entities/crypto_currency.dart';
5 import 'package:cake_wallet/entities/fiat_currency.dart';
6 import 'package:cake_wallet/entities/wallet_type.dart';
7 +import 'package:cake_wallet/store/settings_store.dart';
8 import 'package:cake_wallet/view_model/buy/buy_item.dart';
9 import 'package:flutter/foundation.dart';
10 import 'package:hive/hive.dart';
@@ -18,12 +19,14 @@ part 'buy_view_model.g.dart';
19 class BuyViewModel = BuyViewModelBase with _$BuyViewModel;
20
21 abstract class BuyViewModelBase with Store {
21 - BuyViewModelBase(this.ordersSource, this.ordersStore, this.buyAmountViewModel,
22 - {@required this.wallet}) {
23 - providerList = [
24 - WyreBuyProvider(wallet: wallet),
25 - MoonPayBuyProvider(wallet: wallet)
26 - ];
22 + BuyViewModelBase(this.ordersSource, this.ordersStore, this.settingsStore,
23 + this.buyAmountViewModel, {@required this.wallet}) {
24 + providerList = [WyreBuyProvider(wallet: wallet)];
25 +
26 + if (settingsStore.isMoonPayEnabled ?? false) {
27 + providerList.add(MoonPayBuyProvider(wallet: wallet));
28 + }
29 +
30 items = providerList.map((provider) =>
31 BuyItem(provider: provider, buyAmountViewModel: buyAmountViewModel))
32 .toList();
@@ -34,6 +37,7 @@ abstract class BuyViewModelBase with Store {
37
38 final Box<Order> ordersSource;
39 final OrdersStore ordersStore;
40 + final SettingsStore settingsStore;
41 final BuyAmountViewModel buyAmountViewModel;
42 final WalletBase wallet;
43