diff --git a/lib/buy/buy_provider.dart b/lib/buy/buy_provider.dart index 55ef5e6c3..f57cc5472 100644 --- a/lib/buy/buy_provider.dart +++ b/lib/buy/buy_provider.dart @@ -64,6 +64,7 @@ abstract class BuyProvider { required bool isBuyAction, required String walletAddress, PaymentType? paymentType, + String? customPaymentMethodType, String? countryCode}) async => null; } diff --git a/lib/buy/buy_quote.dart b/lib/buy/buy_quote.dart index 1805b7e1a..da854faaf 100644 --- a/lib/buy/buy_quote.dart +++ b/lib/buy/buy_quote.dart @@ -50,6 +50,7 @@ class Quote extends SelectableOption { this.rampName, this.rampIconPath, this.limits, + this.customPaymentMethodType, }) : super(title: provider.isAggregator ? rampName ?? '' : provider.title); final double rate; @@ -68,6 +69,7 @@ class Quote extends SelectableOption { bool _isBestRate = false; bool isBuyAction; Limits? limits; + String? customPaymentMethodType; late FiatCurrency _fiatCurrency; late CryptoCurrency _cryptoCurrency; @@ -130,7 +132,7 @@ class Quote extends SelectableOption { set setLimits(Limits limits) => this.limits = limits; factory Quote.fromOnramperJson(Map json, bool isBuyAction, - Map metaData, PaymentType paymentType) { + Map metaData, PaymentType paymentType, String? customPaymentMethodType) { final rate = _toDouble(json['rate']) ?? 0.0; final networkFee = _toDouble(json['networkFee']) ?? 0.0; final transactionFee = _toDouble(json['transactionFee']) ?? 0.0; @@ -183,6 +185,7 @@ class Quote extends SelectableOption { rampName: rampName, rampIconPath: rampIconPath, paymentType: paymentType, + customPaymentMethodType: customPaymentMethodType, quoteId: json['quoteId'] as String? ?? '', recommendations: enumRecommendations, provider: ProvidersHelper.getProviderByType(ProviderType.onramper)!, diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index 267406893..150f731dc 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -231,6 +231,7 @@ class DFXBuyProvider extends BuyProvider { required bool isBuyAction, required String walletAddress, PaymentType? paymentType, + String? customPaymentMethodType, String? countryCode}) async { /// if buying with any currency other than eur or chf then DFX is not supported @@ -373,7 +374,7 @@ class DFXBuyProvider extends BuyProvider { case 'Instant': return PaymentType.sepa; default: - return PaymentType.all; + return PaymentType.unknown; } } diff --git a/lib/buy/kryptonim/kryptonim.dart b/lib/buy/kryptonim/kryptonim.dart index 6e00a7e07..5e58ce190 100644 --- a/lib/buy/kryptonim/kryptonim.dart +++ b/lib/buy/kryptonim/kryptonim.dart @@ -113,6 +113,7 @@ class KryptonimBuyProvider extends BuyProvider { required bool isBuyAction, required String walletAddress, PaymentType? paymentType, + String? customPaymentMethodType, String? countryCode, }) async { log('Kryptonim: Fetching quote: ${isBuyAction ? cryptoCurrency : fiatCurrency} -> ${isBuyAction ? fiatCurrency : cryptoCurrency}, amount: $amount'); @@ -149,7 +150,7 @@ class KryptonimBuyProvider extends BuyProvider { final selectedPaymentType = PaymentMethod.getPaymentTypeId(selectedPaymentMethod['payment_method'] as String?); - final quote = Quote.fromKryptonimJson(selectedPaymentMethod, isBuyAction, selectedPaymentType); + final quote = Quote.fromKryptonimJson(selectedPaymentMethod, isBuyAction, selectedPaymentType ?? PaymentType.unknown); quote.setFiatCurrency = fiatCurrency; quote.setCryptoCurrency = cryptoCurrency; diff --git a/lib/buy/meld/meld_buy_provider.dart b/lib/buy/meld/meld_buy_provider.dart index e96a3575c..db593d358 100644 --- a/lib/buy/meld/meld_buy_provider.dart +++ b/lib/buy/meld/meld_buy_provider.dart @@ -104,6 +104,7 @@ class MeldBuyProvider extends BuyProvider { required bool isBuyAction, required String walletAddress, PaymentType? paymentType, + String? customPaymentMethodType, String? countryCode}) async { String? paymentMethod; if (paymentType != null && paymentType != PaymentType.all) { diff --git a/lib/buy/moonpay/moonpay_provider.dart b/lib/buy/moonpay/moonpay_provider.dart index 6c568886f..1cfef7202 100644 --- a/lib/buy/moonpay/moonpay_provider.dart +++ b/lib/buy/moonpay/moonpay_provider.dart @@ -162,6 +162,7 @@ class MoonPayProvider extends BuyProvider { required bool isBuyAction, required String walletAddress, PaymentType? paymentType, + String? customPaymentMethodType, String? countryCode}) async { String? paymentMethod; @@ -410,7 +411,7 @@ class MoonPayProvider extends BuyProvider { case 'yellow_card_bank_transfer': return PaymentType.yellowCardBankTransfer; default: - return PaymentType.all; + return PaymentType.unknown; } } } diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index dc9812d1d..35683f01d 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -33,6 +33,7 @@ class OnRamperBuyProvider extends BuyProvider { static const quotes = '/quotes'; static const paymentTypes = '/payment-types'; static const supported = '/supported'; + static const defaultsAll = '/defaults/all'; static const List _notSupportedCrypto = []; static const List _notSupportedFiat = []; @@ -40,6 +41,8 @@ class OnRamperBuyProvider extends BuyProvider { final SettingsStore _settingsStore; + String? recommendedPaymentType; + String get _apiKey => secrets.onramperApiKey; @override @@ -57,6 +60,34 @@ class OnRamperBuyProvider extends BuyProvider { @override bool get isAggregator => true; + Future getRecommendedPaymentType(bool isBuyAction) async { + + final params = {'type': isBuyAction ? 'buy' : 'sell'}; + + final url = Uri.https(_baseApiUrl, '$supported$defaultsAll', params); + + try { + final response = + await http.get(url, headers: {'Authorization': _apiKey, 'accept': 'application/json'}); + + if (response.statusCode == 200) { + final Map data = jsonDecode(response.body) as Map; + final recommended = data['message']['recommended'] as Map; + + final recommendedPaymentType = recommended['paymentMethod'] as String?; + + return recommendedPaymentType ; + } else { + final responseBody = + jsonDecode(response.body) as Map; + printV('Failed to fetch available payment types: ${responseBody['message']}'); + } + } catch (e) { + printV('Failed to fetch available payment types: $e'); + } + return null; + } + Future> getAvailablePaymentTypes( String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async { @@ -77,9 +108,14 @@ class OnRamperBuyProvider extends BuyProvider { if (response.statusCode == 200) { final Map data = jsonDecode(response.body) as Map; final List message = data['message'] as List; - return message + + final allAvailablePaymentMethods = message .map((item) => PaymentMethod.fromOnramperJson(item as Map)) .toList(); + + recommendedPaymentType = await getRecommendedPaymentType(isBuyAction); + + return allAvailablePaymentMethods; } else { final responseBody = jsonDecode(response.body) as Map; @@ -131,13 +167,13 @@ class OnRamperBuyProvider extends BuyProvider { 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; - } + if (paymentType == PaymentType.all && recommendedPaymentType != null) paymentMethod = recommendedPaymentType!; + else if (paymentType == PaymentType.unknown) paymentMethod = customPaymentMethodType; + else if (paymentType != null) paymentMethod = normalizePaymentMethod(paymentType); final actionType = isBuyAction ? 'buy' : 'sell'; @@ -182,7 +218,7 @@ class OnRamperBuyProvider extends BuyProvider { if (rampMetaData == null) continue; final quote = Quote.fromOnramperJson( - item, isBuyAction, _onrampMetadata, _getPaymentTypeByString(paymentMethod)); + item, isBuyAction, _onrampMetadata, _getPaymentTypeByString(paymentMethod), customPaymentMethodType); quote.setFiatCurrency = fiatCurrency; quote.setCryptoCurrency = cryptoCurrency; validQuotes.add(quote); @@ -225,7 +261,7 @@ class OnRamperBuyProvider extends BuyProvider { final defaultCrypto = quote.cryptoCurrency.title + _getNormalizeNetwork(quote.cryptoCurrency).toLowerCase(); - final paymentMethod = normalizePaymentMethod(quote.paymentType); + final paymentMethod = quote.paymentType == PaymentType.unknown ? quote.customPaymentMethodType : normalizePaymentMethod(quote.paymentType); final uri = Uri.https(_baseUrl, '', { 'apiKey': _apiKey, @@ -330,6 +366,8 @@ class OnRamperBuyProvider extends BuyProvider { return 'dana'; case PaymentType.ideal: return 'ideal'; + case PaymentType.pixPay: + return 'pix'; default: return null; } @@ -379,8 +417,10 @@ class OnRamperBuyProvider extends BuyProvider { return PaymentType.dana; case 'ideal': return PaymentType.ideal; + case 'pix': + return PaymentType.pixPay; default: - return PaymentType.all; + return PaymentType.unknown; } } diff --git a/lib/buy/payment_method.dart b/lib/buy/payment_method.dart index 14b119aa0..322ca68ba 100644 --- a/lib/buy/payment_method.dart +++ b/lib/buy/payment_method.dart @@ -34,6 +34,8 @@ enum PaymentType { yellowCardBankTransfer, fiatBalance, bancontact, + pixPay, + unknown, } extension PaymentTypeTitle on PaymentType { @@ -101,6 +103,8 @@ extension PaymentTypeTitle on PaymentType { return 'Fiat Balance'; case PaymentType.bancontact: return 'Bancontact'; + case PaymentType.pixPay: + return 'PIX Pay'; default: return null; } @@ -158,12 +162,14 @@ class PaymentMethod extends SelectableOption { required this.customTitle, required this.customIconPath, this.customDescription, + this.customPaymentMethodType, }) : super(title: paymentMethodType.title ?? customTitle); final PaymentType paymentMethodType; final String customTitle; final String customIconPath; final String? customDescription; + final String? customPaymentMethodType; bool isSelected = false; @override @@ -188,7 +194,8 @@ class PaymentMethod extends SelectableOption { factory PaymentMethod.fromOnramperJson(Map json) { final type = PaymentMethod.getPaymentTypeId(json['paymentTypeId'] as String?); return PaymentMethod( - paymentMethodType: type, + paymentMethodType: type ?? PaymentType.unknown, + customPaymentMethodType: json['paymentTypeId'] as String?, customTitle: json['name'] as String? ?? 'Unknown', customIconPath: json['icon'] as String? ?? 'assets/images/card.png', customDescription: json['description'] as String?); @@ -212,7 +219,7 @@ class PaymentMethod extends SelectableOption { final type = PaymentMethod.getPaymentTypeId(json['paymentMethod'] as String?); final logos = json['logos'] as Map; return PaymentMethod( - paymentMethodType: type, + paymentMethodType: type ?? PaymentType.unknown, customTitle: json['name'] as String? ?? 'Unknown', customIconPath: logos['dark'] as String? ?? 'assets/images/card.png', customDescription: json['description'] as String?); @@ -221,13 +228,13 @@ class PaymentMethod extends SelectableOption { factory PaymentMethod.fromKryptonimJson(Map json) { final type = PaymentMethod.getPaymentTypeId(json['payment_method'] as String?); return PaymentMethod( - paymentMethodType: type, + paymentMethodType: type ?? PaymentType.unknown, customTitle: json['payment_method'] as String? ?? 'Unknown', customIconPath: 'assets/images/card.png', ); } - static PaymentType getPaymentTypeId(String? type) { + static PaymentType? getPaymentTypeId(String? type) { switch (type?.toLowerCase()) { case 'banktransfer': case 'bank': @@ -289,8 +296,10 @@ class PaymentMethod extends SelectableOption { return PaymentType.sepaOpenBankingPayment; case 'bancontact': return PaymentType.bancontact; + case 'pix': + return PaymentType.pixPay; default: - return PaymentType.all; + return null; } } } diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index 93efd5642..4945529ef 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -192,6 +192,7 @@ class RobinhoodBuyProvider extends BuyProvider { required bool isBuyAction, required String walletAddress, PaymentType? paymentType, + String? customPaymentMethodType, String? countryCode}) async { String? paymentMethod; @@ -267,7 +268,7 @@ class RobinhoodBuyProvider extends BuyProvider { case 'bank_transfer': return PaymentType.bankTransfer; default: - return PaymentType.all; + return PaymentType.unknown; } } } diff --git a/lib/view_model/buy/buy_sell_view_model.dart b/lib/view_model/buy/buy_sell_view_model.dart index df4b838f2..db75dc236 100644 --- a/lib/view_model/buy/buy_sell_view_model.dart +++ b/lib/view_model/buy/buy_sell_view_model.dart @@ -359,14 +359,23 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S onTimeout: () => [], ))); - final Map uniquePaymentMethods = {}; + final List tempPaymentMethods = []; + for (var methods in result) { for (var method in methods) { - uniquePaymentMethods[method.paymentMethodType] = method; + final alreadyExists = tempPaymentMethods.any((m) { + return m.paymentMethodType == method.paymentMethodType && + m.customTitle == method.customTitle; + }); + + if (!alreadyExists) { + tempPaymentMethods.add(method); + } } } - paymentMethods = ObservableList.of(uniquePaymentMethods.values); + paymentMethods = ObservableList.of(tempPaymentMethods); + if (paymentMethods.isNotEmpty) { paymentMethods.insert(0, PaymentMethod.all()); selectedPaymentMethod = paymentMethods.first; @@ -404,6 +413,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S paymentType: selectedPaymentMethod?.paymentMethodType, isBuyAction: isBuyAction, walletAddress: wallet.walletAddresses.address, + customPaymentMethodType: selectedPaymentMethod?.customPaymentMethodType, ) .timeout( Duration(seconds: 10),