Cw 935 get exchange payment method recommendations from on ramper (#2235)
* fix: default to recommended payment method in Onramper * fix: support displaying unknown payment methods * feat: fetch recommended Onramper payment type
Serhii committed
May 15, 2025 at 19:48 UTC
557e1c9839807c601626038637b737ab11705106
10 files changed
+89
-21
lib/buy/buy_provider.dart
+1
@@ -64,6 +64,7 @@ abstract class BuyProvider {
64
required bool isBuyAction,
65
required String walletAddress,
66
PaymentType? paymentType,
67
+ String? customPaymentMethodType,
68
String? countryCode}) async =>
69
null;
70
}
lib/buy/buy_quote.dart
+4
-1
@@ -50,6 +50,7 @@ class Quote extends SelectableOption {
50
this.rampName,
51
this.rampIconPath,
52
this.limits,
53
+ this.customPaymentMethodType,
54
}) : super(title: provider.isAggregator ? rampName ?? '' : provider.title);
55
56
final double rate;
@@ -68,6 +69,7 @@ class Quote extends SelectableOption {
69
bool _isBestRate = false;
70
bool isBuyAction;
71
Limits? limits;
72
+ String? customPaymentMethodType;
73
74
late FiatCurrency _fiatCurrency;
75
late CryptoCurrency _cryptoCurrency;
@@ -130,7 +132,7 @@ class Quote extends SelectableOption {
132
set setLimits(Limits limits) => this.limits = limits;
133
134
factory Quote.fromOnramperJson(Map<String, dynamic> json, bool isBuyAction,
133
- Map<String, dynamic> metaData, PaymentType paymentType) {
135
+ Map<String, dynamic> metaData, PaymentType paymentType, String? customPaymentMethodType) {
136
final rate = _toDouble(json['rate']) ?? 0.0;
137
final networkFee = _toDouble(json['networkFee']) ?? 0.0;
138
final transactionFee = _toDouble(json['transactionFee']) ?? 0.0;
@@ -183,6 +185,7 @@ class Quote extends SelectableOption {
185
rampName: rampName,
186
rampIconPath: rampIconPath,
187
paymentType: paymentType,
188
+ customPaymentMethodType: customPaymentMethodType,
189
quoteId: json['quoteId'] as String? ?? '',
190
recommendations: enumRecommendations,
191
provider: ProvidersHelper.getProviderByType(ProviderType.onramper)!,
lib/buy/dfx/dfx_buy_provider.dart
+2
-1
@@ -231,6 +231,7 @@ class DFXBuyProvider extends BuyProvider {
231
required bool isBuyAction,
232
required String walletAddress,
233
PaymentType? paymentType,
234
+ String? customPaymentMethodType,
235
String? countryCode}) async {
236
/// if buying with any currency other than eur or chf then DFX is not supported
237
@@ -373,7 +374,7 @@ class DFXBuyProvider extends BuyProvider {
374
case 'Instant':
375
return PaymentType.sepa;
376
default:
376
- return PaymentType.all;
377
+ return PaymentType.unknown;
378
}
379
}
380
lib/buy/kryptonim/kryptonim.dart
+2
-1
@@ -113,6 +113,7 @@ class KryptonimBuyProvider extends BuyProvider {
113
required bool isBuyAction,
114
required String walletAddress,
115
PaymentType? paymentType,
116
+ String? customPaymentMethodType,
117
String? countryCode,
118
}) async {
119
log('Kryptonim: Fetching quote: ${isBuyAction ? cryptoCurrency : fiatCurrency} -> ${isBuyAction ? fiatCurrency : cryptoCurrency}, amount: $amount');
@@ -149,7 +150,7 @@ class KryptonimBuyProvider extends BuyProvider {
150
151
final selectedPaymentType =
152
PaymentMethod.getPaymentTypeId(selectedPaymentMethod['payment_method'] as String?);
152
- final quote = Quote.fromKryptonimJson(selectedPaymentMethod, isBuyAction, selectedPaymentType);
153
+ final quote = Quote.fromKryptonimJson(selectedPaymentMethod, isBuyAction, selectedPaymentType ?? PaymentType.unknown);
154
155
quote.setFiatCurrency = fiatCurrency;
156
quote.setCryptoCurrency = cryptoCurrency;
lib/buy/meld/meld_buy_provider.dart
+1
@@ -104,6 +104,7 @@ class MeldBuyProvider extends BuyProvider {
104
required bool isBuyAction,
105
required String walletAddress,
106
PaymentType? paymentType,
107
+ String? customPaymentMethodType,
108
String? countryCode}) async {
109
String? paymentMethod;
110
if (paymentType != null && paymentType != PaymentType.all) {
lib/buy/moonpay/moonpay_provider.dart
+2
-1
@@ -162,6 +162,7 @@ class MoonPayProvider extends BuyProvider {
162
required bool isBuyAction,
163
required String walletAddress,
164
PaymentType? paymentType,
165
+ String? customPaymentMethodType,
166
String? countryCode}) async {
167
String? paymentMethod;
168
@@ -410,7 +411,7 @@ class MoonPayProvider extends BuyProvider {
411
case 'yellow_card_bank_transfer':
412
return PaymentType.yellowCardBankTransfer;
413
default:
413
- return PaymentType.all;
414
+ return PaymentType.unknown;
415
}
416
}
417
}
lib/buy/onramper/onramper_buy_provider.dart
+48
-8
@@ -33,6 +33,7 @@ class OnRamperBuyProvider extends BuyProvider {
33
static const quotes = '/quotes';
34
static const paymentTypes = '/payment-types';
35
static const supported = '/supported';
36
+ static const defaultsAll = '/defaults/all';
37
38
static const List<CryptoCurrency> _notSupportedCrypto = [];
39
static const List<FiatCurrency> _notSupportedFiat = [];
@@ -40,6 +41,8 @@ class OnRamperBuyProvider extends BuyProvider {
41
42
final SettingsStore _settingsStore;
43
44
+ String? recommendedPaymentType;
45
+
46
String get _apiKey => secrets.onramperApiKey;
47
48
@override
@@ -57,6 +60,34 @@ class OnRamperBuyProvider extends BuyProvider {
60
@override
61
bool get isAggregator => true;
62
63
+ Future<String?> getRecommendedPaymentType(bool isBuyAction) async {
64
+
65
+ final params = {'type': isBuyAction ? 'buy' : 'sell'};
66
+
67
+ final url = Uri.https(_baseApiUrl, '$supported$defaultsAll', params);
68
+
69
+ try {
70
+ final response =
71
+ await http.get(url, headers: {'Authorization': _apiKey, 'accept': 'application/json'});
72
+
73
+ if (response.statusCode == 200) {
74
+ final Map<String, dynamic> data = jsonDecode(response.body) as Map<String, dynamic>;
75
+ final recommended = data['message']['recommended'] as Map<String, dynamic>;
76
+
77
+ final recommendedPaymentType = recommended['paymentMethod'] as String?;
78
+
79
+ return recommendedPaymentType ;
80
+ } else {
81
+ final responseBody =
82
+ jsonDecode(response.body) as Map<String, dynamic>;
83
+ printV('Failed to fetch available payment types: ${responseBody['message']}');
84
+ }
85
+ } catch (e) {
86
+ printV('Failed to fetch available payment types: $e');
87
+ }
88
+ return null;
89
+ }
90
+
91
Future<List<PaymentMethod>> getAvailablePaymentTypes(
92
String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
93
@@ -77,9 +108,14 @@ class OnRamperBuyProvider extends BuyProvider {
108
if (response.statusCode == 200) {
109
final Map<String, dynamic> data = jsonDecode(response.body) as Map<String, dynamic>;
110
final List<dynamic> message = data['message'] as List<dynamic>;
80
- return message
111
+
112
+ final allAvailablePaymentMethods = message
113
.map((item) => PaymentMethod.fromOnramperJson(item as Map<String, dynamic>))
114
.toList();
115
+
116
+ recommendedPaymentType = await getRecommendedPaymentType(isBuyAction);
117
+
118
+ return allAvailablePaymentMethods;
119
} else {
120
final responseBody =
121
jsonDecode(response.body) as Map<String, dynamic>;
@@ -131,13 +167,13 @@ class OnRamperBuyProvider extends BuyProvider {
167
required bool isBuyAction,
168
required String walletAddress,
169
PaymentType? paymentType,
170
+ String? customPaymentMethodType,
171
String? countryCode}) async {
172
String? paymentMethod;
173
137
- if (paymentType != null && paymentType != PaymentType.all) {
138
- paymentMethod = normalizePaymentMethod(paymentType);
139
- if (paymentMethod == null) paymentMethod = paymentType.name;
140
- }
174
+ if (paymentType == PaymentType.all && recommendedPaymentType != null) paymentMethod = recommendedPaymentType!;
175
+ else if (paymentType == PaymentType.unknown) paymentMethod = customPaymentMethodType;
176
+ else if (paymentType != null) paymentMethod = normalizePaymentMethod(paymentType);
177
178
final actionType = isBuyAction ? 'buy' : 'sell';
179
@@ -182,7 +218,7 @@ class OnRamperBuyProvider extends BuyProvider {
218
if (rampMetaData == null) continue;
219
220
final quote = Quote.fromOnramperJson(
185
- item, isBuyAction, _onrampMetadata, _getPaymentTypeByString(paymentMethod));
221
+ item, isBuyAction, _onrampMetadata, _getPaymentTypeByString(paymentMethod), customPaymentMethodType);
222
quote.setFiatCurrency = fiatCurrency;
223
quote.setCryptoCurrency = cryptoCurrency;
224
validQuotes.add(quote);
@@ -225,7 +261,7 @@ class OnRamperBuyProvider extends BuyProvider {
261
final defaultCrypto =
262
quote.cryptoCurrency.title + _getNormalizeNetwork(quote.cryptoCurrency).toLowerCase();
263
228
- final paymentMethod = normalizePaymentMethod(quote.paymentType);
264
+ final paymentMethod = quote.paymentType == PaymentType.unknown ? quote.customPaymentMethodType : normalizePaymentMethod(quote.paymentType);
265
266
final uri = Uri.https(_baseUrl, '', {
267
'apiKey': _apiKey,
@@ -330,6 +366,8 @@ class OnRamperBuyProvider extends BuyProvider {
366
return 'dana';
367
case PaymentType.ideal:
368
return 'ideal';
369
+ case PaymentType.pixPay:
370
+ return 'pix';
371
default:
372
return null;
373
}
@@ -379,8 +417,10 @@ class OnRamperBuyProvider extends BuyProvider {
417
return PaymentType.dana;
418
case 'ideal':
419
return PaymentType.ideal;
420
+ case 'pix':
421
+ return PaymentType.pixPay;
422
default:
383
- return PaymentType.all;
423
+ return PaymentType.unknown;
424
}
425
}
426
lib/buy/payment_method.dart
+14
-5
@@ -34,6 +34,8 @@ enum PaymentType {
34
yellowCardBankTransfer,
35
fiatBalance,
36
bancontact,
37
+ pixPay,
38
+ unknown,
39
}
40
41
extension PaymentTypeTitle on PaymentType {
@@ -101,6 +103,8 @@ extension PaymentTypeTitle on PaymentType {
103
return 'Fiat Balance';
104
case PaymentType.bancontact:
105
return 'Bancontact';
106
+ case PaymentType.pixPay:
107
+ return 'PIX Pay';
108
default:
109
return null;
110
}
@@ -158,12 +162,14 @@ class PaymentMethod extends SelectableOption {
162
required this.customTitle,
163
required this.customIconPath,
164
this.customDescription,
165
+ this.customPaymentMethodType,
166
}) : super(title: paymentMethodType.title ?? customTitle);
167
168
final PaymentType paymentMethodType;
169
final String customTitle;
170
final String customIconPath;
171
final String? customDescription;
172
+ final String? customPaymentMethodType;
173
bool isSelected = false;
174
175
@override
@@ -188,7 +194,8 @@ class PaymentMethod extends SelectableOption {
194
factory PaymentMethod.fromOnramperJson(Map<String, dynamic> json) {
195
final type = PaymentMethod.getPaymentTypeId(json['paymentTypeId'] as String?);
196
return PaymentMethod(
191
- paymentMethodType: type,
197
+ paymentMethodType: type ?? PaymentType.unknown,
198
+ customPaymentMethodType: json['paymentTypeId'] as String?,
199
customTitle: json['name'] as String? ?? 'Unknown',
200
customIconPath: json['icon'] as String? ?? 'assets/images/card.png',
201
customDescription: json['description'] as String?);
@@ -212,7 +219,7 @@ class PaymentMethod extends SelectableOption {
219
final type = PaymentMethod.getPaymentTypeId(json['paymentMethod'] as String?);
220
final logos = json['logos'] as Map<String, dynamic>;
221
return PaymentMethod(
215
- paymentMethodType: type,
222
+ paymentMethodType: type ?? PaymentType.unknown,
223
customTitle: json['name'] as String? ?? 'Unknown',
224
customIconPath: logos['dark'] as String? ?? 'assets/images/card.png',
225
customDescription: json['description'] as String?);
@@ -221,13 +228,13 @@ class PaymentMethod extends SelectableOption {
228
factory PaymentMethod.fromKryptonimJson(Map<String, dynamic> json) {
229
final type = PaymentMethod.getPaymentTypeId(json['payment_method'] as String?);
230
return PaymentMethod(
224
- paymentMethodType: type,
231
+ paymentMethodType: type ?? PaymentType.unknown,
232
customTitle: json['payment_method'] as String? ?? 'Unknown',
233
customIconPath: 'assets/images/card.png',
234
);
235
}
236
230
- static PaymentType getPaymentTypeId(String? type) {
237
+ static PaymentType? getPaymentTypeId(String? type) {
238
switch (type?.toLowerCase()) {
239
case 'banktransfer':
240
case 'bank':
@@ -289,8 +296,10 @@ class PaymentMethod extends SelectableOption {
296
return PaymentType.sepaOpenBankingPayment;
297
case 'bancontact':
298
return PaymentType.bancontact;
299
+ case 'pix':
300
+ return PaymentType.pixPay;
301
default:
293
- return PaymentType.all;
302
+ return null;
303
}
304
}
305
}
lib/buy/robinhood/robinhood_buy_provider.dart
+2
-1
@@ -192,6 +192,7 @@ class RobinhoodBuyProvider extends BuyProvider {
192
required bool isBuyAction,
193
required String walletAddress,
194
PaymentType? paymentType,
195
+ String? customPaymentMethodType,
196
String? countryCode}) async {
197
String? paymentMethod;
198
@@ -267,7 +268,7 @@ class RobinhoodBuyProvider extends BuyProvider {
268
case 'bank_transfer':
269
return PaymentType.bankTransfer;
270
default:
270
- return PaymentType.all;
271
+ return PaymentType.unknown;
272
}
273
}
274
}
lib/view_model/buy/buy_sell_view_model.dart
+13
-3
@@ -359,14 +359,23 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
359
onTimeout: () => [],
360
)));
361
362
- final Map<PaymentType, PaymentMethod> uniquePaymentMethods = {};
362
+ final List<PaymentMethod> tempPaymentMethods = [];
363
+
364
for (var methods in result) {
365
for (var method in methods) {
365
- uniquePaymentMethods[method.paymentMethodType] = method;
366
+ final alreadyExists = tempPaymentMethods.any((m) {
367
+ return m.paymentMethodType == method.paymentMethodType &&
368
+ m.customTitle == method.customTitle;
369
+ });
370
+
371
+ if (!alreadyExists) {
372
+ tempPaymentMethods.add(method);
373
+ }
374
}
375
}
376
369
- paymentMethods = ObservableList<PaymentMethod>.of(uniquePaymentMethods.values);
377
+ paymentMethods = ObservableList<PaymentMethod>.of(tempPaymentMethods);
378
+
379
if (paymentMethods.isNotEmpty) {
380
paymentMethods.insert(0, PaymentMethod.all());
381
selectedPaymentMethod = paymentMethods.first;
@@ -404,6 +413,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
413
paymentType: selectedPaymentMethod?.paymentMethodType,
414
isBuyAction: isBuyAction,
415
walletAddress: wallet.walletAddresses.address,
416
+ customPaymentMethodType: selectedPaymentMethod?.customPaymentMethodType,
417
)
418
.timeout(
419
Duration(seconds: 10),