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 + '¤cyCode=' +
50
+ final suffix = '?apiKey=' + _apiKey + '¤cyCode=' +
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