dev
dart 279 lines 9.04 KB
Raw
1 import 'dart:convert';
2
3 import 'package:cake_wallet/.secrets.g.dart' as secrets;
4 import 'package:cake_wallet/buy/buy_provider.dart';
5 import 'package:cake_wallet/buy/buy_quote.dart';
6 import 'package:cake_wallet/buy/pairs_utils.dart';
7 import 'package:cake_wallet/buy/payment_method.dart';
8 import 'package:cake_wallet/entities/fiat_currency.dart';
9 import 'package:cake_wallet/generated/i18n.dart';
10 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
11 import 'package:cw_core/utils/proxy_wrapper.dart';
12 import 'package:cake_wallet/utils/show_pop_up.dart';
13 import 'package:cw_core/crypto_currency.dart';
14 import 'package:cw_core/utils/print_verbose.dart';
15 import 'package:cw_core/wallet_base.dart';
16 import 'package:flutter/material.dart';
17 import 'dart:developer';
18 import 'package:url_launcher/url_launcher.dart';
19
20 class MeldBuyProvider extends BuyProvider {
21 MeldBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
22 : super(
23 wallet: wallet,
24 isTestEnvironment: isTestEnvironment,
25 hardwareWalletVM: null,
26 supportedCryptoList: supportedCryptoToFiatPairs(
27 notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
28 supportedFiatList: supportedFiatToCryptoPairs(
29 notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto));
30
31 static const _isProduction = false;
32
33 static const _baseUrl = _isProduction ? 'api.meld.io' : 'api-sb.meld.io';
34 static const _providersProperties = '/service-providers/properties';
35 static const _paymentMethodsPath = '/payment-methods';
36 static const _quotePath = '/payments/crypto/quote';
37
38 static const String sandboxUrl = 'sb.fluidmoney.xyz';
39 static const String productionUrl = 'fluidmoney.xyz';
40
41 static const String _baseWidgetUrl = _isProduction ? productionUrl : sandboxUrl;
42
43 static String get _testApiKey => secrets.meldTestApiKey;
44
45 static const List<CryptoCurrency> _notSupportedCrypto = [];
46 static const List<FiatCurrency> _notSupportedFiat = [];
47
48 static String get _testPublicKey => ''; //secrets.meldTestPublicKey;
49
50 @override
51 String get title => 'Meld';
52
53 @override
54 String get providerDescription => 'Meld Buy Provider';
55
56 @override
57 String get lightIcon => 'assets/images/meld_logo.svg';
58
59 @override
60 String get darkIcon => 'assets/images/meld_logo.svg';
61
62 @override
63 bool get isAggregator => true;
64
65 @override
66 Future<List<PaymentMethod>> getAvailablePaymentTypes(
67 String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
68 final params = {'fiatCurrencies': fiatCurrency, 'statuses': 'LIVE,RECENTLY_ADDED,BUILDING'};
69
70 final path = '$_providersProperties$_paymentMethodsPath';
71 final url = Uri.https(_baseUrl, path, params);
72
73 try {
74 final response = await ProxyWrapper().get(
75 clearnetUri: url,
76 headers: {
77 'Authorization': _isProduction ? '' : _testApiKey,
78 'Meld-Version': '2023-12-19',
79 'accept': 'application/json',
80 'content-type': 'application/json',
81 },
82 );
83
84 if (response.statusCode == 200) {
85 final data = jsonDecode(response.body) as List<dynamic>;
86 final paymentMethods =
87 data.map((e) => PaymentMethod.fromMeldJson(e as Map<String, dynamic>)).toList();
88 return paymentMethods;
89 } else {
90 printV('Meld: Failed to fetch payment types');
91 return List<PaymentMethod>.empty();
92 }
93 } catch (e) {
94 printV('Meld: Failed to fetch payment types: $e');
95 return List<PaymentMethod>.empty();
96 }
97 }
98
99 @override
100 Future<List<Quote>?> fetchQuote(
101 {required CryptoCurrency cryptoCurrency,
102 required FiatCurrency fiatCurrency,
103 required double amount,
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) {
111 paymentMethod = normalizePaymentMethod(paymentType);
112 if (paymentMethod == null) paymentMethod = paymentType.name;
113 }
114
115 log('Meld: Fetching buy quote: ${isBuyAction ? cryptoCurrency : fiatCurrency} -> ${isBuyAction ? fiatCurrency : cryptoCurrency}, amount: $amount');
116
117 final url = Uri.https(_baseUrl, _quotePath);
118 final headers = {
119 'Authorization': _testApiKey,
120 'Meld-Version': '2023-12-19',
121 'accept': 'application/json',
122 'content-type': 'application/json',
123 };
124 final body = jsonEncode({
125 'countryCode': countryCode,
126 'destinationCurrencyCode': isBuyAction ? fiatCurrency.name : cryptoCurrency.title,
127 'sourceAmount': amount,
128 'sourceCurrencyCode': isBuyAction ? cryptoCurrency.title : fiatCurrency.name,
129 if (paymentMethod != null) 'paymentMethod': paymentMethod,
130 });
131
132 try {
133 final response = await ProxyWrapper().post(
134 clearnetUri: url,
135 headers: headers,
136 body: body,
137 );
138
139 if (response.statusCode == 200) {
140 final data = jsonDecode(response.body) as Map<String, dynamic>;
141 final paymentType = _getPaymentTypeByString(data['paymentMethodType'] as String?);
142 final quote = Quote.fromMeldJson(data, isBuyAction, paymentType);
143
144 quote.setFiatCurrency = fiatCurrency;
145 quote.setCryptoCurrency = cryptoCurrency;
146
147 return [quote];
148 } else {
149 return null;
150 }
151 } catch (e) {
152 printV('Error fetching buy quote: $e');
153 return null;
154 }
155 }
156
157 Future<void>? launchProvider(
158 {required BuildContext context,
159 required Quote quote,
160 required double amount,
161 required bool isBuyAction,
162 required String cryptoCurrencyAddress,
163 String? countryCode}) async {
164 final actionType = isBuyAction ? 'BUY' : 'SELL';
165
166 final params = {
167 'publicKey': _isProduction ? '' : _testPublicKey,
168 'countryCode': countryCode,
169 //'paymentMethodType': normalizePaymentMethod(paymentMethod.paymentMethodType),
170 'sourceAmount': amount.toString(),
171 'sourceCurrencyCode': quote.fiatCurrency,
172 'destinationCurrencyCode': quote.cryptoCurrency,
173 'walletAddress': cryptoCurrencyAddress,
174 'transactionType': actionType
175 };
176
177 final uri = Uri.https(_baseWidgetUrl, '', params);
178
179 try {
180 if (await canLaunchUrl(uri)) {
181 await launchUrl(uri, mode: LaunchMode.externalApplication);
182 } else {
183 throw Exception('Could not launch URL');
184 }
185 } catch (e) {
186 await showPopUp<void>(
187 context: context,
188 builder: (BuildContext context) {
189 return AlertWithOneAction(
190 alertTitle: "Meld",
191 alertContent: S.of(context).buy_provider_unavailable + ': $e',
192 buttonText: S.of(context).ok,
193 buttonAction: () => Navigator.of(context).pop());
194 });
195 }
196 }
197
198 String? normalizePaymentMethod(PaymentType paymentType) {
199 switch (paymentType) {
200 case PaymentType.creditCard:
201 return 'CREDIT_DEBIT_CARD';
202 case PaymentType.applePay:
203 return 'APPLE_PAY';
204 case PaymentType.googlePay:
205 return 'GOOGLE_PAY';
206 case PaymentType.neteller:
207 return 'NETELLER';
208 case PaymentType.skrill:
209 return 'SKRILL';
210 case PaymentType.sepa:
211 return 'SEPA';
212 case PaymentType.sepaInstant:
213 return 'SEPA_INSTANT';
214 case PaymentType.ach:
215 return 'ACH';
216 case PaymentType.achInstant:
217 return 'INSTANT_ACH';
218 case PaymentType.Khipu:
219 return 'KHIPU';
220 case PaymentType.ovo:
221 return 'OVO';
222 case PaymentType.zaloPay:
223 return 'ZALOPAY';
224 case PaymentType.zaloBankTransfer:
225 return 'ZA_BANK_TRANSFER';
226 case PaymentType.gcash:
227 return 'GCASH';
228 case PaymentType.imps:
229 return 'IMPS';
230 case PaymentType.dana:
231 return 'DANA';
232 case PaymentType.ideal:
233 return 'IDEAL';
234 default:
235 return null;
236 }
237 }
238
239 PaymentType _getPaymentTypeByString(String? paymentMethod) {
240 switch (paymentMethod?.toUpperCase()) {
241 case 'CREDIT_DEBIT_CARD':
242 return PaymentType.creditCard;
243 case 'APPLE_PAY':
244 return PaymentType.applePay;
245 case 'GOOGLE_PAY':
246 return PaymentType.googlePay;
247 case 'NETELLER':
248 return PaymentType.neteller;
249 case 'SKRILL':
250 return PaymentType.skrill;
251 case 'SEPA':
252 return PaymentType.sepa;
253 case 'SEPA_INSTANT':
254 return PaymentType.sepaInstant;
255 case 'ACH':
256 return PaymentType.ach;
257 case 'INSTANT_ACH':
258 return PaymentType.achInstant;
259 case 'KHIPU':
260 return PaymentType.Khipu;
261 case 'OVO':
262 return PaymentType.ovo;
263 case 'ZALOPAY':
264 return PaymentType.zaloPay;
265 case 'ZA_BANK_TRANSFER':
266 return PaymentType.zaloBankTransfer;
267 case 'GCASH':
268 return PaymentType.gcash;
269 case 'IMPS':
270 return PaymentType.imps;
271 case 'DANA':
272 return PaymentType.dana;
273 case 'IDEAL':
274 return PaymentType.ideal;
275 default:
276 return PaymentType.all;
277 }
278 }
279 }