diff --git a/lib/buy/buy_quote.dart b/lib/buy/buy_quote.dart index 370e57a8f..1075e3091 100644 --- a/lib/buy/buy_quote.dart +++ b/lib/buy/buy_quote.dart @@ -200,7 +200,8 @@ class Quote extends SelectableOption { } factory Quote.fromMoonPayJson( - Map json, bool isBuyAction, PaymentType paymentType) { + Map json, bool isBuyAction, PaymentType paymentType, + {String? customPaymentMethodType}) { final rate = isBuyAction ? json['quoteCurrencyPrice'] as double? ?? 0.0 : json['baseCurrencyPrice'] as double? ?? 0.0; @@ -226,6 +227,7 @@ class Quote extends SelectableOption { transactionFee: transactionFee, payout: _toDouble(json['quoteCurrencyAmount']) ?? 0.0, paymentType: paymentType, + customPaymentMethodType: customPaymentMethodType, recommendations: [], quoteId: json['signature'] as String? ?? '', provider: ProvidersHelper.getProviderByType(ProviderType.moonpay), diff --git a/lib/buy/moonpay/moonpay_payment_methods.dart b/lib/buy/moonpay/moonpay_payment_methods.dart new file mode 100644 index 000000000..d9e17c3b0 --- /dev/null +++ b/lib/buy/moonpay/moonpay_payment_methods.dart @@ -0,0 +1,32 @@ +class MoonPayPaymentMethod { + MoonPayPaymentMethod({ + required this.type, + required this.displayName, + required this.iconUrl, + required this.active, + this.limitAmount, + this.limitCurrencyCode, + }); + + factory MoonPayPaymentMethod.fromJson(Map json) { + final availability = json["availability"] as Map? ?? {}; + final limits = json["limits"] as Map?; + final perTransaction = limits?["perTransaction"] as Map?; + + return MoonPayPaymentMethod( + type: json["type"] as String? ?? "", + displayName: json["displayName"] as String? ?? "", + iconUrl: json["iconUrl"] as String? ?? "", + active: availability["active"] as bool? ?? false, + limitAmount: (perTransaction?["limit"] as num?)?.toDouble(), + limitCurrencyCode: perTransaction?["currencyCode"] as String?, + ); + } + + final String type; + final String displayName; + final String iconUrl; + final bool active; + final double? limitAmount; + final String? limitCurrencyCode; +} \ No newline at end of file diff --git a/lib/buy/moonpay/moonpay_provider.dart b/lib/buy/moonpay/moonpay_provider.dart index c4a03a853..46694468a 100644 --- a/lib/buy/moonpay/moonpay_provider.dart +++ b/lib/buy/moonpay/moonpay_provider.dart @@ -1,41 +1,38 @@ -import 'dart:convert'; -import 'dart:developer'; +import "dart:convert"; +import "dart:developer"; -import 'package:cake_wallet/.secrets.g.dart' as secrets; -import 'package:cake_wallet/buy/buy_exception.dart'; -import 'package:cake_wallet/buy/buy_provider.dart'; -import 'package:cake_wallet/buy/buy_provider_description.dart'; -import 'package:cake_wallet/buy/buy_quote.dart'; -import 'package:cake_wallet/order/order.dart'; -import 'package:cake_wallet/order/order_source_description.dart'; -import 'package:cake_wallet/buy/pairs_utils.dart'; -import 'package:cake_wallet/buy/payment_method.dart'; -import 'package:cake_wallet/entities/fiat_currency.dart'; -import 'package:cake_wallet/exchange/trade_state.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/palette.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/themes/core/material_base_theme.dart'; -import 'package:cw_core/currency_for_wallet_type.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; +import "package:cake_wallet/.secrets.g.dart" as secrets; +import "package:cake_wallet/buy/buy_exception.dart"; +import "package:cake_wallet/buy/buy_provider.dart"; +import "package:cake_wallet/buy/buy_provider_description.dart"; +import "package:cake_wallet/buy/buy_quote.dart"; +import "package:cake_wallet/buy/moonpay/moonpay_payment_methods.dart"; +import "package:cake_wallet/buy/pairs_utils.dart"; +import "package:cake_wallet/buy/payment_method.dart"; +import "package:cake_wallet/entities/fiat_currency.dart"; +import "package:cake_wallet/exchange/trade_state.dart"; +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/order/order.dart"; +import "package:cake_wallet/order/order_source_description.dart"; +import "package:cake_wallet/palette.dart"; +import "package:cake_wallet/src/widgets/alert_with_one_action.dart"; +import "package:cake_wallet/store/app_store.dart"; +import "package:cw_core/crypto_currency.dart"; +import "package:cw_core/currency_for_wallet_type.dart"; +import "package:cw_core/utils/print_verbose.dart"; +import "package:cw_core/utils/proxy_wrapper.dart"; +import "package:flutter/material.dart"; +import "package:url_launcher/url_launcher.dart"; class MoonPayProvider extends BuyProvider { MoonPayProvider({ required AppStore appStore, - required WalletBase wallet, - bool isTestEnvironment = false, + required super.wallet, + super.isTestEnvironment = false, }) : baseSellUrl = isTestEnvironment ? _baseSellTestUrl : _baseSellProductUrl, baseBuyUrl = isTestEnvironment ? _baseBuyTestUrl : _baseBuyProductUrl, - this._appStore = appStore, + _appStore = appStore, super( - wallet: wallet, - isTestEnvironment: isTestEnvironment, hardwareWalletVM: null, supportedCryptoList: supportedCryptoToFiatPairs( notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat), @@ -44,37 +41,70 @@ class MoonPayProvider extends BuyProvider { final AppStore _appStore; - static const _baseSellTestUrl = 'sell-sandbox.moonpay.com'; - static const _baseSellProductUrl = 'sell.moonpay.com'; - static const _baseBuyTestUrl = 'buy-staging.moonpay.com'; - static const _baseBuyProductUrl = 'buy.moonpay.com'; - static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; - static const _apiUrl = 'https://api.moonpay.com'; - static const _baseUrl = 'api.moonpay.com'; - static const _currenciesPath = '/v3/currencies'; - static const _buyQuote = '/buy_quote'; - static const _sellQuote = '/sell_quote'; + static const _baseSellTestUrl = "sell-sandbox.moonpay.com"; + static const _baseSellProductUrl = "sell.moonpay.com"; + static const _baseBuyTestUrl = "buy-staging.moonpay.com"; + static const _baseBuyProductUrl = "buy.moonpay.com"; + static const _cIdBaseUrl = "exchange-helper.cakewallet.com"; + static const _apiUrl = "https://api.moonpay.com"; + static const _baseUrl = "api.moonpay.com"; + static const _currenciesPath = "/v3/currencies"; + static const _buyQuote = "/buy_quote"; + static const _sellQuote = "/sell_quote"; + static const _paymentMethodsPath = "/api/moonpay/payment-methods"; - static const _transactionsSuffix = '/v1/transactions'; + static const _transactionsSuffix = "/v1/transactions"; static const List _notSupportedCrypto = []; static const List _notSupportedFiat = []; + static String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey; + + static const Map _moonPayPaymentMethodStrings = { + PaymentType.creditCard: 'credit_debit_card', + PaymentType.debitCard: 'credit_debit_card', + PaymentType.ach: 'ach_bank_transfer', + PaymentType.applePay: 'apple_pay', + PaymentType.googlePay: 'google_pay', + PaymentType.sepa: 'sepa_bank_transfer', + PaymentType.paypal: 'paypal', + PaymentType.sepaOpenBankingPayment: 'sepa_open_banking_payment', + PaymentType.gbpOpenBankingPayment: 'gbp_open_banking_payment', + PaymentType.lowCostAch: 'low_cost_ach', + PaymentType.mobileWallet: 'mobile_wallet', + PaymentType.pixInstantPayment: 'pix_instant_payment', + PaymentType.yellowCardBankTransfer: 'yellow_card_bank_transfer', + PaymentType.fiatBalance: 'fiat_balance', + PaymentType.revolutPay: 'revolut_pay', + PaymentType.moonpayCashApp: 'cash_app', + PaymentType.moonpayBalance: 'moonpay_balance', + }; + + static final Map _paymentTypeByMoonPayString = { + for (final entry in _moonPayPaymentMethodStrings.entries) entry.value: entry.key, + }; + + String? normalizePaymentMethod(PaymentType paymentMethod) => + _moonPayPaymentMethodStrings[paymentMethod]; + + PaymentType _getPaymentTypeByString(String? paymentMethod) => + _paymentTypeByMoonPayString[paymentMethod] ?? PaymentType.unknown; + final String baseBuyUrl; final String baseSellUrl; @override String get providerDescription => - 'MoonPay offers a fast and simple way to buy and sell cryptocurrencies'; + "MoonPay offers a fast and simple way to buy and sell cryptocurrencies"; @override - String get title => 'MoonPay'; + String get title => "MoonPay"; @override - String get lightIcon => 'assets/images/moonpay_light.png'; + String get lightIcon => "assets/images/moonpay_light.png"; @override - String get darkIcon => 'assets/images/moonpay_dark.png'; + String get darkIcon => "assets/images/moonpay_dark.png"; @override bool get isAggregator => false; @@ -84,18 +114,7 @@ class MoonPayProvider extends BuyProvider { String get currencyCode => walletTypeToCryptoCurrency(wallet.type, chainId: wallet.chainId).title.toLowerCase(); - String get trackUrl => baseBuyUrl + '/transaction_receipt?transactionId='; - - static String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey; - - static String themeToMoonPayTheme(MaterialThemeBase theme) { - switch (theme.type) { - case ThemeType.light: - return 'light'; - case ThemeType.dark: - return 'dark'; - } - } + String get trackUrl => "$baseBuyUrl/transaction_receipt?transactionId="; Future getMoonpaySignedQuery(String query) async { final uri = @@ -103,102 +122,126 @@ class MoonPayProvider extends BuyProvider { final response = await ProxyWrapper().post( clearnetUri: uri, - headers: {'Content-Type': 'application/json', 'x-api-key': _exchangeHelperApiKey}, - body: json.encode({'query': query}), + headers: {"Content-Type": "application/json", "x-api-key": _exchangeHelperApiKey}, + body: json.encode({"query": query}), ); if (response.statusCode == 200) { - printV((jsonDecode(response.body) as Map)); - return (jsonDecode(response.body) as Map)['query'] as String; + printV(jsonDecode(response.body) as Map); + return (jsonDecode(response.body) as Map)["query"] as String; } else { throw Exception( - 'Provider currently unavailable. Status: ${response.statusCode} ${response.body}'); - } - } - - Future> fetchFiatCredentials( - String fiatCurrency, String cryptocurrency, String? paymentMethod) async { - final params = {'baseCurrencyCode': fiatCurrency.toLowerCase(), 'apiKey': _apiKey}; - - if (paymentMethod != null) params['paymentMethod'] = paymentMethod; - - final path = '$_currenciesPath/${cryptocurrency.toLowerCase()}/limits'; - final url = Uri.https(_baseUrl, path, params); - - try { - final response = await ProxyWrapper().get( - clearnetUri: url, - headers: {'accept': 'application/json'}, + "Provider currently unavailable. Status: ${response.statusCode} ${response.body}", ); - - if (response.statusCode == 200) { - return jsonDecode(response.body) as Map; - } else { - printV('MoonPay does not support fiat: $fiatCurrency'); - return {}; - } - } catch (e) { - printV('MoonPay Error fetching fiat currencies: $e'); - return {}; } } + Future> fetchMoonPayPaymentMethods({ + required String currencyCode, + String transactionType = "buy", + }) async { + final String? countryCode = FiatCurrency.tryDeserialize(raw: currencyCode)?.apiCountryCode; + + if (countryCode == null) { + throw Exception("failed to determine country code for currency: $currencyCode"); + } + + final params = { + "currencyCode": currencyCode, + "countryCode": countryCode, + "transactionType": transactionType, + if (countryCode == "USA") "stateOfResidence": "NY", + }; + + final uri = Uri.https(_cIdBaseUrl, _paymentMethodsPath, params); + + final response = await ProxyWrapper().get( + clearnetUri: uri, + headers: { + "Accept": "application/json", + "x-api-key": _exchangeHelperApiKey, + }, + ); + + if (response.statusCode != 200) { + if (response.statusCode == 500) { + throw Exception("failed to fetch payment methods. Status: 500 Internal Server Error"); + } + throw Exception( + "failed to fetch payment methods. Status: ${response.statusCode} ${response.body}"); + } + + final decoded = jsonDecode(response.body) as Map; + final list = decoded["payment-methods"] as List? ?? []; + + return list.map((e) => MoonPayPaymentMethod.fromJson(e as Map)).toList(); + } + + @override Future> getAvailablePaymentTypes( String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async { final List paymentMethods = []; - if (isBuyAction) { - final fiatBuyCredentials = - await fetchFiatCredentials(fiatCurrency, cryptoCurrency.title, null); - if (fiatBuyCredentials.isNotEmpty) { - final paymentMethod = fiatBuyCredentials['paymentMethod'] as String?; + try { + final moonPayMethods = await fetchMoonPayPaymentMethods( + currencyCode: fiatCurrency, + transactionType: isBuyAction ? "buy" : "sell", + ); + + for (final method in moonPayMethods) { + if (!method.active) { + continue; + } + final paymentType = _getPaymentTypeByString(method.type); + paymentMethods.add(PaymentMethod.fromMoonPayJson( - fiatBuyCredentials, _getPaymentTypeByString(paymentMethod))); - return paymentMethods; + method, + paymentType, + ),); } + } catch (e) { + printV("Error fetching payment methods: $e"); } return paymentMethods; } @override - Future?> fetchQuote( - {required CryptoCurrency cryptoCurrency, - required FiatCurrency fiatCurrency, - required double amount, - required bool isBuyAction, - required String walletAddress, - PaymentType? paymentType, - String? customPaymentMethodType, - String? countryCode}) async { + Future?> fetchQuote({ + required CryptoCurrency cryptoCurrency, + required FiatCurrency fiatCurrency, + required double amount, + required bool isBuyAction, + required String walletAddress, + PaymentType? paymentType, + String? customPaymentMethodType, + String? countryCode, + }) async { String? paymentMethod; if (paymentType != null && paymentType != PaymentType.all) { - paymentMethod = normalizePaymentMethod(paymentType); - if (paymentMethod == null) paymentMethod = paymentType.name; - } else { - paymentMethod = 'credit_debit_card'; + paymentMethod = normalizePaymentMethod(paymentType) ?? customPaymentMethodType; } - final action = isBuyAction ? 'buy' : 'sell'; + final action = isBuyAction ? "buy" : "sell"; final formattedCryptoCurrency = _normalizeCurrency(cryptoCurrency); final baseCurrencyCode = isBuyAction ? fiatCurrency.name.toLowerCase() : cryptoCurrency.title.toLowerCase(); final params = { - 'baseCurrencyCode': baseCurrencyCode, - 'baseCurrencyAmount': amount.toStringAsFixed(2), - 'paymentMethod': paymentMethod, - 'areFeesIncluded': 'false', + "baseCurrencyCode": baseCurrencyCode, + "baseCurrencyAmount": amount.toStringAsFixed(2), + if (paymentMethod != null) "paymentMethod": paymentMethod, + "areFeesIncluded": "false", 'apiKey': _apiKey }; - log('MoonPay: Fetching $action quote: ${isBuyAction ? formattedCryptoCurrency : fiatCurrency.name.toLowerCase()} -> ${isBuyAction ? baseCurrencyCode : formattedCryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod'); + log("MoonPay: Fetching $action quote: ${isBuyAction ? formattedCryptoCurrency : fiatCurrency.name.toLowerCase()} -> ${isBuyAction ? baseCurrencyCode : formattedCryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod"); final quotePath = isBuyAction ? _buyQuote : _sellQuote; - final path = '$_currenciesPath/$formattedCryptoCurrency$quotePath'; + final path = "$_currenciesPath/$formattedCryptoCurrency$quotePath"; final url = Uri.https(_baseUrl, path, params); try { final response = await ProxyWrapper().get(clearnetUri: url); @@ -218,19 +261,30 @@ class MoonPayProvider extends BuyProvider { } final paymentMethods = data['paymentMethod'] as String?; - final quote = - Quote.fromMoonPayJson(data, isBuyAction, _getPaymentTypeByString(paymentMethods)); + + final quote = Quote.fromMoonPayJson( + data, + isBuyAction, + _getPaymentTypeByString(paymentMethods ?? paymentMethod), + customPaymentMethodType: paymentMethod, + ); quote.setFiatCurrency = fiatCurrency; quote.setCryptoCurrency = cryptoCurrency; return [quote]; - } else { - printV('Moon Pay: Error fetching buy quote: '); + } + try { + final errorData = jsonDecode(response.body) as Map; + final errorMessage = errorData["message"] as String?; + printV("Error fetching buy quote: $errorMessage"); + return null; + } catch (e) { + printV("Error parsing error response: $e"); return null; } } catch (e) { - printV('Moon Pay: Error fetching buy quote: $e'); + printV("Exception while fetching buy quote: $e"); return null; } } @@ -244,20 +298,21 @@ class MoonPayProvider extends BuyProvider { required String cryptoCurrencyAddress, String? countryCode}) async { final Map params = { - 'theme': themeToMoonPayTheme(_appStore.themeStore.currentTheme), - 'language': _appStore.settingsStore.languageCode, - 'colorCode': _appStore.themeStore.currentTheme.isDark + "theme": _appStore.themeStore.currentTheme.type.name, + "language": _appStore.settingsStore.languageCode, + "colorCode": _appStore.themeStore.currentTheme.isDark ? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}' : '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}', - 'baseCurrencyCode': isBuyAction ? quote.fiatCurrency.name : quote.cryptoCurrency.name, - 'baseCurrencyAmount': amount.toStringAsFixed(2), - 'walletAddress': cryptoCurrencyAddress, - 'lockAmount': 'false', - 'showAllCurrencies': 'false', - 'showWalletAddressForm': 'false', + "baseCurrencyCode": isBuyAction ? quote.fiatCurrency.name : quote.cryptoCurrency.name, + "baseCurrencyAmount": amount.toStringAsFixed(2), + "walletAddress": cryptoCurrencyAddress, + "lockAmount": "false", + "showAllCurrencies": "false", + "showWalletAddressForm": "false", if (isBuyAction) - 'enabledPaymentMethods': normalizePaymentMethod(quote.paymentType) ?? - 'credit_debit_card,apple_pay,google_pay,samsung_pay,sepa_bank_transfer,gbp_bank_transfer,gbp_open_banking_payment', + "enabledPaymentMethods": normalizePaymentMethod(quote.paymentType) ?? + quote.customPaymentMethodType ?? + "credit_debit_card,apple_pay,google_pay,samsung_pay,sepa_bank_transfer,gbp_bank_transfer,gbp_open_banking_payment", if (!isBuyAction) 'refundWalletAddress': cryptoCurrencyAddress }; @@ -274,20 +329,18 @@ class MoonPayProvider extends BuyProvider { if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } else { - throw Exception('Could not launch URL'); + throw Exception("Could not launch URL"); } } catch (e) { if (context.mounted) { await showDialog( context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: 'MoonPay', - alertContent: 'The MoonPay service is currently unavailable: $e', - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop(), - ); - }, + builder: (BuildContext context) => AlertWithOneAction( + alertTitle: 'MoonPay', + alertContent: 'The MoonPay service is currently unavailable: $e', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ), ); } } @@ -347,76 +400,4 @@ class MoonPayProvider extends BuyProvider { return currency.toString().toLowerCase(); } - - String? normalizePaymentMethod(PaymentType paymentMethod) { - switch (paymentMethod) { - case PaymentType.creditCard: - return 'credit_debit_card'; - case PaymentType.debitCard: - return 'credit_debit_card'; - case PaymentType.ach: - return 'ach_bank_transfer'; - case PaymentType.applePay: - return 'apple_pay'; - case PaymentType.googlePay: - return 'google_pay'; - case PaymentType.sepa: - return 'sepa_bank_transfer'; - case PaymentType.paypal: - return 'paypal'; - case PaymentType.sepaOpenBankingPayment: - return 'sepa_open_banking_payment'; - case PaymentType.gbpOpenBankingPayment: - return 'gbp_open_banking_payment'; - case PaymentType.lowCostAch: - return 'low_cost_ach'; - case PaymentType.mobileWallet: - return 'mobile_wallet'; - case PaymentType.pixInstantPayment: - return 'pix_instant_payment'; - case PaymentType.yellowCardBankTransfer: - return 'yellow_card_bank_transfer'; - case PaymentType.fiatBalance: - return 'fiat_balance'; - case PaymentType.revolutPay: - return 'revolut_pay'; - default: - return null; - } - } - - PaymentType _getPaymentTypeByString(String? paymentMethod) { - switch (paymentMethod) { - case 'ach_bank_transfer': - return PaymentType.ach; - case 'apple_pay': - return PaymentType.applePay; - case 'credit_debit_card': - return PaymentType.creditCard; - case 'fiat_balance': - return PaymentType.fiatBalance; - case 'gbp_open_banking_payment': - return PaymentType.gbpOpenBankingPayment; - case 'google_pay': - return PaymentType.googlePay; - case 'low_cost_ach': - return PaymentType.lowCostAch; - case 'mobile_wallet': - return PaymentType.mobileWallet; - case 'paypal': - return PaymentType.paypal; - case 'pix_instant_payment': - return PaymentType.pixInstantPayment; - case 'sepa_bank_transfer': - return PaymentType.sepa; - case 'sepa_open_banking_payment': - return PaymentType.sepaOpenBankingPayment; - case 'yellow_card_bank_transfer': - return PaymentType.yellowCardBankTransfer; - case 'revolut_pay': - return PaymentType.revolutPay; - default: - return PaymentType.unknown; - } - } } diff --git a/lib/buy/payment_method.dart b/lib/buy/payment_method.dart index 2f51db902..6ee3f59e4 100644 --- a/lib/buy/payment_method.dart +++ b/lib/buy/payment_method.dart @@ -1,4 +1,6 @@ +import "package:cake_wallet/buy/moonpay/moonpay_payment_methods.dart"; import 'package:cake_wallet/core/selectable_option.dart'; +import "package:cake_wallet/entities/calculate_fiat_amount.dart"; enum PaymentType { all, @@ -33,6 +35,8 @@ enum PaymentType { fiatBalance, bancontact, pixPay, + moonpayCashApp, + moonpayBalance, unknown, } @@ -103,6 +107,10 @@ extension PaymentTypeTitle on PaymentType { return 'Bancontact'; case PaymentType.pixPay: return 'PIX Pay'; + case PaymentType.moonpayCashApp: + return 'Cash App'; + case PaymentType.moonpayBalance: + return 'MoonPay Balance'; default: return null; } @@ -175,6 +183,8 @@ class PaymentMethod extends SelectableOption { required this.customIconPath, this.customDescription, this.customPaymentMethodType, + this.customBadges = const [], + this.customBottomLeftSubTitle, }) : super(title: paymentMethodType.title ?? customTitle); final PaymentType paymentMethodType; @@ -182,8 +192,16 @@ class PaymentMethod extends SelectableOption { final String customIconPath; final String? customDescription; final String? customPaymentMethodType; + final List customBadges; + final String? customBottomLeftSubTitle; bool isSelected = false; + @override + List get badges => customBadges; + + @override + String? get bottomLeftSubTitle => customBottomLeftSubTitle; + @override String? get description => paymentMethodType.description ?? customDescription; @@ -220,11 +238,25 @@ class PaymentMethod extends SelectableOption { customIconPath: 'assets/new-ui/buy_payment_methods/debit_card.svg'); } - factory PaymentMethod.fromMoonPayJson(Map json, PaymentType paymentType) { + factory PaymentMethod.fromMoonPayJson(MoonPayPaymentMethod method, PaymentType paymentType) { + final List badges = []; + String? limitSubtitle; + if (paymentType == PaymentType.moonpayCashApp) { + badges.add('New'); + } + if (method.limitAmount != null && method.limitCurrencyCode != null) { + limitSubtitle = + 'max: ${formatWithCommas(method.limitAmount.toString())} ${method.limitCurrencyCode!.toUpperCase()}'; + } + return PaymentMethod( - paymentMethodType: paymentType, - customTitle: json['paymentMethod'] as String, - customIconPath: 'assets/new-ui/buy_payment_methods/debit_card.svg'); + paymentMethodType: paymentType, + customTitle: method.displayName, + customIconPath: method.iconUrl.isNotEmpty ? method.iconUrl : 'assets/images/card.png', + customPaymentMethodType: method.type, + customBadges: badges, + customBottomLeftSubTitle: limitSubtitle, + ); } factory PaymentMethod.fromMeldJson(Map json) { diff --git a/lib/entities/fiat_currency.dart b/lib/entities/fiat_currency.dart index 56a836bf3..9966e37f2 100644 --- a/lib/entities/fiat_currency.dart +++ b/lib/entities/fiat_currency.dart @@ -9,9 +9,11 @@ class FiatCurrency extends EnumerableItem with Serializable impl required this.fullName, required this.emoji, this.decimals = 2, + this.isoCountryCode, }) : super(title: symbol, raw: symbol); final String countryCode; + final String? isoCountryCode; @override final String fullName; @@ -25,6 +27,8 @@ class FiatCurrency extends EnumerableItem with Serializable impl @override final String symbol; + String get apiCountryCode => isoCountryCode ?? countryCode.toUpperCase(); + final String emoji; static List get all => _all.values.toList(); @@ -86,8 +90,13 @@ class FiatCurrency extends EnumerableItem with Serializable impl FiatCurrency(symbol: "BGN", countryCode: "bgr", fullName: "Bulgarian Lev", emoji: "πŸ‡§πŸ‡¬"); static const brl = FiatCurrency(symbol: "BRL", countryCode: "bra", fullName: "Brazilian Real", emoji: "πŸ‡§πŸ‡·"); - static const cad = - FiatCurrency(symbol: "CAD", countryCode: "cad", fullName: "Canadian Dollar", emoji: "πŸ‡¨πŸ‡¦"); + static const cad = FiatCurrency( + symbol: "CAD", + countryCode: "cad", + fullName: "Canadian Dollar", + emoji: "πŸ‡¨πŸ‡¦", + isoCountryCode: "CAN", + ); static const chf = FiatCurrency(symbol: "CHF", countryCode: "che", fullName: "Swiss Franc", emoji: "πŸ‡¨πŸ‡­"); static const clp = @@ -96,14 +105,24 @@ class FiatCurrency extends EnumerableItem with Serializable impl FiatCurrency(symbol: "CNY", countryCode: "chn", fullName: "Chinese Yuan", emoji: "πŸ‡¨πŸ‡³"); static const cop = FiatCurrency(symbol: "COP", countryCode: "col", fullName: "Colombian Peso", emoji: "πŸ‡¨πŸ‡΄"); - static const czk = - FiatCurrency(symbol: "CZK", countryCode: "czk", fullName: "Czech Koruna", emoji: "πŸ‡¨πŸ‡Ώ"); + static const czk = FiatCurrency( + symbol: "CZK", + countryCode: "czk", + fullName: "Czech Koruna", + emoji: "πŸ‡¨πŸ‡Ώ", + isoCountryCode: "CZE", + ); static const dkk = FiatCurrency(symbol: "DKK", countryCode: "dnk", fullName: "Danish Krone", emoji: "πŸ‡©πŸ‡°"); static const egp = FiatCurrency(symbol: "EGP", countryCode: "egy", fullName: "Egyptian Pound", emoji: "πŸ‡ͺπŸ‡¬"); - static const eur = - FiatCurrency(symbol: "EUR", countryCode: "eur", fullName: "Euro", emoji: "πŸ‡ͺπŸ‡Ί"); + static const eur = FiatCurrency( + symbol: "EUR", + countryCode: "eur", + fullName: "Euro", + emoji: "πŸ‡ͺπŸ‡Ί", + isoCountryCode: "DEU", + ); static const gbp = FiatCurrency(symbol: "GBP", countryCode: "gbr", fullName: "Pound Sterling", emoji: "πŸ‡¬πŸ‡§"); static const ghs = @@ -184,8 +203,8 @@ class FiatCurrency extends EnumerableItem with Serializable impl fullName: "Thai Baht", emoji: "πŸ‡ΉπŸ‡­", ); - static const twd = - FiatCurrency(symbol: "TWD", countryCode: "twn", fullName: "New Thaiwan Dollar", emoji: "πŸ‡ΉπŸ‡Ό"); + static const twd = FiatCurrency( + symbol: "TWD", countryCode: "twn", fullName: "New Thaiwan Dollar", emoji: "πŸ‡ΉπŸ‡Ό"); static const uah = FiatCurrency(symbol: "UAH", countryCode: "ukr", fullName: "Ukrainian Hryvnia", emoji: "πŸ‡ΊπŸ‡¦"); static const usd = FiatCurrency( @@ -211,6 +230,7 @@ class FiatCurrency extends EnumerableItem with Serializable impl countryCode: "saf", fullName: "South African Rand", emoji: "πŸ‡ΏπŸ‡¦", + isoCountryCode: "ZAF", ); static const tur = FiatCurrency(symbol: "TRY", countryCode: "tur", fullName: "Turkish Lira", emoji: "πŸ‡ΉπŸ‡·"); @@ -273,6 +293,8 @@ class FiatCurrency extends EnumerableItem with Serializable impl static FiatCurrency deserialize({required String raw}) => _all[raw] ?? FiatCurrency.usd; + static FiatCurrency? tryDeserialize({required String raw}) => _all[raw]; + @override bool operator ==(Object other) => other is FiatCurrency && other.raw == raw; diff --git a/lib/new-ui/pages/buy_sell/buy_sell_confirmation_page.dart b/lib/new-ui/pages/buy_sell/buy_sell_confirmation_page.dart index 3b95faea7..3a6be22ef 100644 --- a/lib/new-ui/pages/buy_sell/buy_sell_confirmation_page.dart +++ b/lib/new-ui/pages/buy_sell/buy_sell_confirmation_page.dart @@ -91,13 +91,15 @@ class BuySellConfirmationPage extends StatelessWidget { ], ), ), - if (buySellViewModel.selectedQuote!.paymentType.title?.isNotEmpty ?? - false) + if (buySellViewModel.selectedQuote!.paymentType.title?.isNotEmpty + ?? buySellViewModel.selectedQuote!.customPaymentMethodType?.isNotEmpty + ?? false) ListItemRegularRow( showArrow: false, keyValue: "payment method", label: S.of(context).payment_method, - trailingText: buySellViewModel.selectedQuote!.paymentType.title, + trailingText: buySellViewModel.selectedQuote!.paymentType.title + ?? buySellViewModel.selectedQuote!.customPaymentMethodType, ), ListItemRegularRow( showArrow: false, @@ -105,6 +107,13 @@ class BuySellConfirmationPage extends StatelessWidget { label: S.of(context).rate, trailingText: buySellViewModel.selectedQuote!.topLeftSubTitle, ), + if (_limitsText != null) + ListItemRegularRow( + showArrow: false, + keyValue: "limits", + label: "Limits", + trailingText: _limitsText, + ), ], }, ), @@ -127,6 +136,14 @@ class BuySellConfirmationPage extends StatelessWidget { ), ); + String? get _limitsText { + final limits = buySellViewModel.selectedQuote?.limits; + final max = limits?.max; + if (limits == null || max == null || max == double.infinity) return null; + + return "${limits.min ?? 0} - $max"; + } + String get _pageTitle => "${buySellViewModel.mode == BuySellPageMode.buy ? S.current.buy : S.current.sell} ${buySellViewModel.cryptoCurrency.fullName ?? ""}"; diff --git a/lib/src/screens/select_options_page.dart b/lib/src/screens/select_options_page.dart index 630041655..e45c1efb6 100644 --- a/lib/src/screens/select_options_page.dart +++ b/lib/src/screens/select_options_page.dart @@ -41,8 +41,7 @@ abstract class SelectOptionsPage extends BasePage { String get title => pageTitle; @override - Widget body(BuildContext context) { - return ScrollableWithBottomSection( + Widget body(BuildContext context) => ScrollableWithBottomSection( content: BodySelectOptionsPage( currentTheme: currentTheme, items: items, @@ -81,7 +80,6 @@ abstract class SelectOptionsPage extends BasePage { ), ), ); - } } class BodySelectOptionsPage extends StatefulWidget { @@ -131,8 +129,7 @@ class _BodySelectOptionsPageState extends State { } @override - Widget build(BuildContext context) { - return Center( + Widget build(BuildContext context) => Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 350), child: Column( @@ -191,5 +188,4 @@ class _BodySelectOptionsPageState extends State { ), ), ); - } } diff --git a/lib/view_model/buy/buy_sell_view_model.dart b/lib/view_model/buy/buy_sell_view_model.dart index 978d48f71..631f64518 100644 --- a/lib/view_model/buy/buy_sell_view_model.dart +++ b/lib/view_model/buy/buy_sell_view_model.dart @@ -443,7 +443,9 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S for (var methods in result) { for (var method in methods) { final alreadyExists = tempPaymentMethods.any((m) { - return m.paymentMethodType == method.paymentMethodType; + if (m.paymentMethodType != method.paymentMethodType) return false; + if (method.paymentMethodType != PaymentType.unknown) return true; + return m.customTitle == method.customTitle; }); if (!alreadyExists) {