diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b177357..c264370 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -377,5 +377,14 @@ "share_ready_message": "Bereit zum Teilen", "lnurl_copied_message": "LNURL in die Zwischenablage kopiert", "qr_scanner_title": "QR scannen", - "qr_scanner_instructions": "Kamera auf den QR-Code richten\num Rechnung oder Adresse zu scannen" + "qr_scanner_instructions": "Kamera auf den QR-Code richten\num Rechnung oder Adresse zu scannen", + "history_then_label": "Damals", + "history_now_label": "Jetzt", + "history_date_label": "Datum", + "history_description_label": "Beschreibung", + "history_hash_label": "Hash", + "history_fee_label": "Gebühr", + "history_original_rate_label": "Originalkurs", + "history_wallet_amount_label": "Wallet-Betrag", + "history_wallet_rate_label": "Wallet-Kurs" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e5fc63f..4986ab6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -392,5 +392,14 @@ "share_ready_message": "Ready to share", "lnurl_copied_message": "LNURL copied to clipboard", "qr_scanner_title": "Scan QR", - "qr_scanner_instructions": "Point the camera at the QR code\nto scan the invoice or address" + "qr_scanner_instructions": "Point the camera at the QR code\nto scan the invoice or address", + "history_then_label": "Then", + "history_now_label": "Now", + "history_date_label": "Date", + "history_description_label": "Description", + "history_hash_label": "Hash", + "history_fee_label": "Fee", + "history_original_rate_label": "Original Rate", + "history_wallet_amount_label": "Wallet Amount", + "history_wallet_rate_label": "Wallet Rate" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 9abb5fa..9e29275 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -377,5 +377,14 @@ "share_ready_message": "Listo para compartir", "lnurl_copied_message": "LNURL copiado al portapapeles", "qr_scanner_title": "Escanear QR", - "qr_scanner_instructions": "Apunta la cámara al código QR\npara escanear la factura o dirección" + "qr_scanner_instructions": "Apunta la cámara al código QR\npara escanear la factura o dirección", + "history_then_label": "Entonces", + "history_now_label": "Ahora", + "history_date_label": "Fecha", + "history_description_label": "Descripción", + "history_hash_label": "Hash", + "history_fee_label": "Comisión", + "history_original_rate_label": "Tasa Original", + "history_wallet_amount_label": "Monto en Wallet", + "history_wallet_rate_label": "Tasa en Wallet" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 7e07862..3736144 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -377,5 +377,14 @@ "share_ready_message": "Prêt à partager", "lnurl_copied_message": "LNURL copié dans le presse-papiers", "qr_scanner_title": "Scanner QR", - "qr_scanner_instructions": "Pointez la caméra vers le code QR\npour scanner la facture ou l'adresse" + "qr_scanner_instructions": "Pointez la caméra vers le code QR\npour scanner la facture ou l'adresse", + "history_then_label": "Avant", + "history_now_label": "Maintenant", + "history_date_label": "Date", + "history_description_label": "Description", + "history_hash_label": "Hash", + "history_fee_label": "Frais", + "history_original_rate_label": "Taux d'origine", + "history_wallet_amount_label": "Montant du portefeuille", + "history_wallet_rate_label": "Taux du portefeuille" } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 0e62f78..d191652 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -377,5 +377,14 @@ "share_ready_message": "Pronto da condividere", "lnurl_copied_message": "LNURL copiato negli appunti", "qr_scanner_title": "Scansiona QR", - "qr_scanner_instructions": "Punta la fotocamera sul codice QR\nper scansionare la fattura o l'indirizzo" + "qr_scanner_instructions": "Punta la fotocamera sul codice QR\nper scansionare la fattura o l'indirizzo", + "history_then_label": "Allora", + "history_now_label": "Ora", + "history_date_label": "Data", + "history_description_label": "Descrizione", + "history_hash_label": "Hash", + "history_fee_label": "Commissione", + "history_original_rate_label": "Tasso originale", + "history_wallet_amount_label": "Importo nel portafoglio", + "history_wallet_rate_label": "Tasso del portafoglio" } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 5a3e9a5..0f8e7b2 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -377,5 +377,14 @@ "share_ready_message": "Pronto para compartilhar", "lnurl_copied_message": "LNURL copiado para a área de transferência", "qr_scanner_title": "Escanear QR", - "qr_scanner_instructions": "Aponte a câmera para o código QR\npara escanear a fatura ou endereço" + "qr_scanner_instructions": "Aponte a câmera para o código QR\npara escanear a fatura ou endereço", + "history_then_label": "Então", + "history_now_label": "Agora", + "history_date_label": "Data", + "history_description_label": "Descrição", + "history_hash_label": "Hash", + "history_fee_label": "Taxa", + "history_original_rate_label": "Taxa original", + "history_wallet_amount_label": "Valor na carteira", + "history_wallet_rate_label": "Taxa da carteira" } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 8076ab3..c5f07bc 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -377,5 +377,14 @@ "share_ready_message": "Готово к отправке", "lnurl_copied_message": "LNURL скопирован в буфер обмена", "qr_scanner_title": "Сканировать QR", - "qr_scanner_instructions": "Наведите камеру на QR-код\nчтобы сканировать счёт или адрес" + "qr_scanner_instructions": "Наведите камеру на QR-код\nчтобы сканировать счёт или адрес", + "history_then_label": "Тогда", + "history_now_label": "Сейчас", + "history_date_label": "Дата", + "history_description_label": "Описание", + "history_hash_label": "Хэш", + "history_fee_label": "Комиссия", + "history_original_rate_label": "Оригинальный курс", + "history_wallet_amount_label": "Сумма в кошельке", + "history_wallet_rate_label": "Курс кошелька" } \ No newline at end of file diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 49457e1..f473d9e 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -1925,6 +1925,60 @@ abstract class AppLocalizations { /// In es, this message translates to: /// **'Apunta la cámara al código QR\npara escanear la factura o dirección'** String get qr_scanner_instructions; + + /// No description provided for @history_then_label. + /// + /// In es, this message translates to: + /// **'Entonces'** + String get history_then_label; + + /// No description provided for @history_now_label. + /// + /// In es, this message translates to: + /// **'Ahora'** + String get history_now_label; + + /// No description provided for @history_date_label. + /// + /// In es, this message translates to: + /// **'Fecha'** + String get history_date_label; + + /// No description provided for @history_description_label. + /// + /// In es, this message translates to: + /// **'Descripción'** + String get history_description_label; + + /// No description provided for @history_hash_label. + /// + /// In es, this message translates to: + /// **'Hash'** + String get history_hash_label; + + /// No description provided for @history_fee_label. + /// + /// In es, this message translates to: + /// **'Comisión'** + String get history_fee_label; + + /// No description provided for @history_original_rate_label. + /// + /// In es, this message translates to: + /// **'Tasa Original'** + String get history_original_rate_label; + + /// No description provided for @history_wallet_amount_label. + /// + /// In es, this message translates to: + /// **'Monto en Wallet'** + String get history_wallet_amount_label; + + /// No description provided for @history_wallet_rate_label. + /// + /// In es, this message translates to: + /// **'Tasa en Wallet'** + String get history_wallet_rate_label; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 031d657..8c6ce16 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -1014,4 +1014,31 @@ class AppLocalizationsDe extends AppLocalizations { @override String get qr_scanner_instructions => 'Kamera auf den QR-Code richten\num Rechnung oder Adresse zu scannen'; + + @override + String get history_then_label => 'Damals'; + + @override + String get history_now_label => 'Jetzt'; + + @override + String get history_date_label => 'Datum'; + + @override + String get history_description_label => 'Beschreibung'; + + @override + String get history_hash_label => 'Hash'; + + @override + String get history_fee_label => 'Gebühr'; + + @override + String get history_original_rate_label => 'Originalkurs'; + + @override + String get history_wallet_amount_label => 'Wallet-Betrag'; + + @override + String get history_wallet_rate_label => 'Wallet-Kurs'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 3b87bf7..4b5346f 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -989,4 +989,31 @@ class AppLocalizationsEn extends AppLocalizations { @override String get qr_scanner_instructions => 'Point the camera at the QR code\nto scan the invoice or address'; + + @override + String get history_then_label => 'Then'; + + @override + String get history_now_label => 'Now'; + + @override + String get history_date_label => 'Date'; + + @override + String get history_description_label => 'Description'; + + @override + String get history_hash_label => 'Hash'; + + @override + String get history_fee_label => 'Fee'; + + @override + String get history_original_rate_label => 'Original Rate'; + + @override + String get history_wallet_amount_label => 'Wallet Amount'; + + @override + String get history_wallet_rate_label => 'Wallet Rate'; } diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index d28b1d1..11bed92 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -1003,4 +1003,31 @@ class AppLocalizationsEs extends AppLocalizations { @override String get qr_scanner_instructions => 'Apunta la cámara al código QR\npara escanear la factura o dirección'; + + @override + String get history_then_label => 'Entonces'; + + @override + String get history_now_label => 'Ahora'; + + @override + String get history_date_label => 'Fecha'; + + @override + String get history_description_label => 'Descripción'; + + @override + String get history_hash_label => 'Hash'; + + @override + String get history_fee_label => 'Comisión'; + + @override + String get history_original_rate_label => 'Tasa Original'; + + @override + String get history_wallet_amount_label => 'Monto en Wallet'; + + @override + String get history_wallet_rate_label => 'Tasa en Wallet'; } diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index c1c2d0a..79286ea 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -1020,4 +1020,31 @@ class AppLocalizationsFr extends AppLocalizations { @override String get qr_scanner_instructions => 'Pointez la caméra vers le code QR\npour scanner la facture ou l\'adresse'; + + @override + String get history_then_label => 'Avant'; + + @override + String get history_now_label => 'Maintenant'; + + @override + String get history_date_label => 'Date'; + + @override + String get history_description_label => 'Description'; + + @override + String get history_hash_label => 'Hash'; + + @override + String get history_fee_label => 'Frais'; + + @override + String get history_original_rate_label => 'Taux d\'origine'; + + @override + String get history_wallet_amount_label => 'Montant du portefeuille'; + + @override + String get history_wallet_rate_label => 'Taux du portefeuille'; } diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 6021611..19c785a 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -1017,4 +1017,31 @@ class AppLocalizationsIt extends AppLocalizations { @override String get qr_scanner_instructions => 'Punta la fotocamera sul codice QR\nper scansionare la fattura o l\'indirizzo'; + + @override + String get history_then_label => 'Allora'; + + @override + String get history_now_label => 'Ora'; + + @override + String get history_date_label => 'Data'; + + @override + String get history_description_label => 'Descrizione'; + + @override + String get history_hash_label => 'Hash'; + + @override + String get history_fee_label => 'Commissione'; + + @override + String get history_original_rate_label => 'Tasso originale'; + + @override + String get history_wallet_amount_label => 'Importo nel portafoglio'; + + @override + String get history_wallet_rate_label => 'Tasso del portafoglio'; } diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index 23cfd53..ab9728b 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -1003,4 +1003,31 @@ class AppLocalizationsPt extends AppLocalizations { @override String get qr_scanner_instructions => 'Aponte a câmera para o código QR\npara escanear a fatura ou endereço'; + + @override + String get history_then_label => 'Então'; + + @override + String get history_now_label => 'Agora'; + + @override + String get history_date_label => 'Data'; + + @override + String get history_description_label => 'Descrição'; + + @override + String get history_hash_label => 'Hash'; + + @override + String get history_fee_label => 'Taxa'; + + @override + String get history_original_rate_label => 'Taxa original'; + + @override + String get history_wallet_amount_label => 'Valor na carteira'; + + @override + String get history_wallet_rate_label => 'Taxa da carteira'; } diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index aa87202..80040ce 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -993,4 +993,31 @@ class AppLocalizationsRu extends AppLocalizations { @override String get qr_scanner_instructions => 'Наведите камеру на QR-код\nчтобы сканировать счёт или адрес'; + + @override + String get history_then_label => 'Тогда'; + + @override + String get history_now_label => 'Сейчас'; + + @override + String get history_date_label => 'Дата'; + + @override + String get history_description_label => 'Описание'; + + @override + String get history_hash_label => 'Хэш'; + + @override + String get history_fee_label => 'Комиссия'; + + @override + String get history_original_rate_label => 'Оригинальный курс'; + + @override + String get history_wallet_amount_label => 'Сумма в кошельке'; + + @override + String get history_wallet_rate_label => 'Курс кошелька'; } diff --git a/lib/models/transaction_info.dart b/lib/models/transaction_info.dart index e9c4b5e..d979920 100644 --- a/lib/models/transaction_info.dart +++ b/lib/models/transaction_info.dart @@ -1,5 +1,3 @@ -import 'package:flutter/foundation.dart'; - /// Lightning transaction information class TransactionInfo { final String id; @@ -54,6 +52,11 @@ class TransactionInfo { String get displayAmount { final satsAmount = type == TransactionType.incoming ? '+$amountFormatted' : '-$amountFormatted'; + if (originalFiatAmount != null && originalFiatCurrency != null) { + final fiatSign = type == TransactionType.incoming ? '+' : '-'; + return '$satsAmount\n$fiatSign${originalFiatAmount!.toStringAsFixed(2)} $originalFiatCurrency'; + } + if (fiatAmount != null && fiatCurrency != null) { final fiatSign = type == TransactionType.incoming ? '+' : '-'; return '$satsAmount\n$fiatSign${fiatAmount!.toStringAsFixed(2)} $fiatCurrency'; @@ -91,6 +94,8 @@ class TransactionInfo { DateTime timestamp; if (json['time'] is int) { timestamp = DateTime.fromMillisecondsSinceEpoch(json['time'] * 1000); + } else if (json['time'] is double) { + timestamp = DateTime.fromMillisecondsSinceEpoch((json['time'] as double).round() * 1000); } else if (json['time'] is String) { timestamp = DateTime.tryParse(json['time']) ?? DateTime.now(); } else { @@ -134,18 +139,18 @@ class TransactionInfo { } // FALLBACK: Parse original fiat info from memo if LNBits doesn't provide it - // This handles cases like "25 ZAR", "100 CUP", etc. + // Handles "25 CUP", "25 CUP - description", "100.50 ZAR", etc. if (originalFiatCurrency == null && originalFiatAmount == null) { final memo = json['memo']?.toString() ?? ''; - final memoMatch = RegExp(r'^(\d+(?:\.\d+)?)\s*([A-Z]{3})$').firstMatch(memo.trim()); + final memoMatch = RegExp(r'(\d+(?:\.\d+)?)\s*([A-Z]{3})').firstMatch(memo.trim()); if (memoMatch != null) { final fiatAmount = double.tryParse(memoMatch.group(1) ?? ''); final currency = memoMatch.group(2); - if (fiatAmount != null && currency != null && currency != 'USD') { + if (fiatAmount != null && currency != null) { originalFiatAmount = fiatAmount; originalFiatCurrency = currency; // Calculate rate based on sats amount (amount is in msat) - final satsAmount = amount ~/ 1000; // Convert msat to sats + final satsAmount = amount ~/ 1000; if (satsAmount > 0 && fiatAmount > 0) { originalFiatRate = satsAmount / fiatAmount; } diff --git a/lib/screens/11amount_screen.dart b/lib/screens/11amount_screen.dart index c379050..e6a46d4 100644 --- a/lib/screens/11amount_screen.dart +++ b/lib/screens/11amount_screen.dart @@ -203,52 +203,43 @@ class _AmountScreenState extends State { return amount.round(); } - // Use INVERSE conversion from the working convertSatsToFiat method - // This ensures we use exactly the same rates as home_screen + // Use YadioService directly as the primary rate source try { - final currencyProvider = context.read(); - - print('[AMOUNT_SCREEN] Using inverse conversion method for consistency'); - - // Step 1: Get rate by converting 1 BTC (100M sats) to fiat - const oneBtcInSats = 100000000; // 1 BTC = 100M sats - final oneBtcInFiat = await currencyProvider.convertSatsToFiat(oneBtcInSats, _selectedCurrency); - - print('[AMOUNT_SCREEN] Rate check: $oneBtcInSats sats = $oneBtcInFiat $_selectedCurrency'); - - // Step 2: Parse the result to get numeric rate - final fiatString = oneBtcInFiat.replaceAll(RegExp(r'[^\d.]'), ''); // Remove non-numeric chars - final oneBtcRate = double.tryParse(fiatString); - - if (oneBtcRate == null || oneBtcRate <= 0) { - throw Exception('Invalid rate obtained: $oneBtcInFiat'); + print('[AMOUNT_SCREEN] Using YadioService for conversion'); + final sats = await _yadioService.convertToSats( + amount: amount, + currency: _selectedCurrency, + ); + if (sats > 0) { + print('[AMOUNT_SCREEN] YadioService conversion: $amount $_selectedCurrency = $sats sats'); + return sats; } - - print('[AMOUNT_SCREEN] Parsed rate: 1 BTC = $oneBtcRate $_selectedCurrency'); - - // Step 3: Calculate sats using inverse proportion - // If 1 BTC = oneBtcRate fiat, then amount fiat = ? sats - // sats = (amount / oneBtcRate) * 100000000 - final btcAmount = amount / oneBtcRate; - final satsAmount = (btcAmount * 100000000).round(); - - print('[AMOUNT_SCREEN] Conversion successful: $amount $_selectedCurrency = $satsAmount sats'); - print('[AMOUNT_SCREEN] Math: ($amount / $oneBtcRate) * 100000000 = $satsAmount'); - - return satsAmount; - + throw Exception('YadioService returned 0 sats'); } catch (e) { - print('[AMOUNT_SCREEN] Error with inverse conversion: $e'); + print('[AMOUNT_SCREEN] YadioService failed: $e'); - // Fallback to YadioService as last resort + // Fallback to CurrencySettingsProvider (LNBits server conversion) try { - print('[AMOUNT_SCREEN] Trying YadioService fallback'); - final sats = await _yadioService.convertToSats( - amount: amount, - currency: _selectedCurrency, - ); - print('[AMOUNT_SCREEN] YadioService conversion: $amount $_selectedCurrency = $sats sats'); - return sats; + final currencyProvider = context.read(); + + print('[AMOUNT_SCREEN] Using CurrencySettingsProvider fallback'); + + const oneBtcInSats = 100000000; + final oneBtcInFiat = await currencyProvider.convertSatsToFiat(oneBtcInSats, _selectedCurrency); + + final fiatString = oneBtcInFiat.replaceAll(RegExp(r'[^\d.]'), ''); + final oneBtcRate = double.tryParse(fiatString); + + if (oneBtcRate == null || oneBtcRate <= 0) { + throw Exception('Invalid rate obtained: $oneBtcInFiat'); + } + + final btcAmount = amount / oneBtcRate; + final satsAmount = (btcAmount * 100000000).round(); + + print('[AMOUNT_SCREEN] Fallback conversion: $amount $_selectedCurrency = $satsAmount sats'); + + return satsAmount; } catch (fallbackError) { print('[AMOUNT_SCREEN] All conversion methods failed: $fallbackError'); return 0; @@ -344,16 +335,36 @@ class _AmountScreenState extends State { _isProcessingPayment = true; }); + // Extract fiat data if user entered amount in fiat currency + final bool isFiat = _selectedCurrency != 'sats'; + final double? fiatAmount = isFiat ? double.tryParse(_amount) : null; + final String? fiatCurrency = isFiat ? _selectedCurrency : null; + final double? fiatRate = (isFiat && fiatAmount != null && satsAmount > 0) + ? satsAmount / fiatAmount + : null; + try { // Process payment based on destination type (LNURL vs Lightning Address) print('[AMOUNT_SCREEN] Processing payment with $satsAmount sats'); + if (fiatCurrency != null) { + print('[AMOUNT_SCREEN] Original fiat: $fiatAmount $fiatCurrency (rate: $fiatRate sats/$fiatCurrency)'); + } if (widget.destinationType == 'bolt11') { - await _processBolt11Payment(satsAmount); + await _processBolt11Payment(satsAmount, + originalFiatCurrency: fiatCurrency, + originalFiatAmount: fiatAmount, + originalFiatRate: fiatRate); } else if (widget.destinationType == 'lnurl') { - await _processLNURLPayment(satsAmount); + await _processLNURLPayment(satsAmount, + originalFiatCurrency: fiatCurrency, + originalFiatAmount: fiatAmount, + originalFiatRate: fiatRate); } else if (widget.destinationType == 'lightning_address') { - await _processLightningAddressPayment(satsAmount); + await _processLightningAddressPayment(satsAmount, + originalFiatCurrency: fiatCurrency, + originalFiatAmount: fiatAmount, + originalFiatRate: fiatRate); } } catch (e) { _showErrorSnackBar('${AppLocalizations.of(context)!.send_error_prefix}$e'); @@ -364,7 +375,11 @@ class _AmountScreenState extends State { } } - Future _processBolt11Payment(int satsAmount) async { + Future _processBolt11Payment(int satsAmount, { + String? originalFiatCurrency, + double? originalFiatAmount, + double? originalFiatRate, + }) async { if (widget.decodedInvoice == null) { throw Exception('Missing decodedInvoice for bolt11 payment'); } @@ -376,13 +391,20 @@ class _AmountScreenState extends State { builder: (context) => InvoiceConfirmScreen( decodedInvoice: widget.decodedInvoice!, overrideAmountSats: satsAmount, + originalFiatCurrency: originalFiatCurrency, + originalFiatAmount: originalFiatAmount, + originalFiatRate: originalFiatRate, ), ), ); } } - Future _processLNURLPayment(int satsAmount) async { + Future _processLNURLPayment(int satsAmount, { + String? originalFiatCurrency, + double? originalFiatAmount, + double? originalFiatRate, + }) async { try { print('[AMOUNT_SCREEN] Processing LNURL payment: ${widget.destination}'); print('[AMOUNT_SCREEN] Amount: $satsAmount sats'); @@ -411,6 +433,9 @@ class _AmountScreenState extends State { lnurl: widget.destination, amountSats: satsAmount, comment: _commentController.text.trim().isEmpty ? null : _commentController.text.trim(), + originalFiatCurrency: originalFiatCurrency, + originalFiatAmount: originalFiatAmount, + originalFiatRate: originalFiatRate, ); print('[AMOUNT_SCREEN] LNURL payment sent successfully: $paymentResult'); @@ -460,7 +485,11 @@ class _AmountScreenState extends State { } } - Future _processLightningAddressPayment(int satsAmount) async { + Future _processLightningAddressPayment(int satsAmount, { + String? originalFiatCurrency, + double? originalFiatAmount, + double? originalFiatRate, + }) async { try { print('[AMOUNT_SCREEN] Processing Lightning Address payment: ${widget.destination}'); print('[AMOUNT_SCREEN] Amount: $satsAmount sats'); @@ -489,6 +518,9 @@ class _AmountScreenState extends State { lightningAddress: widget.destination, amountSats: satsAmount, comment: _commentController.text.trim().isEmpty ? null : _commentController.text.trim(), + originalFiatCurrency: originalFiatCurrency, + originalFiatAmount: originalFiatAmount, + originalFiatRate: originalFiatRate, ); print('[AMOUNT_SCREEN] Payment sent successfully: $paymentResult'); diff --git a/lib/screens/12invoice_confirm_screen.dart b/lib/screens/12invoice_confirm_screen.dart index f9a0fe9..37da11b 100644 --- a/lib/screens/12invoice_confirm_screen.dart +++ b/lib/screens/12invoice_confirm_screen.dart @@ -11,11 +11,17 @@ import '../theme/app_tokens.dart'; class InvoiceConfirmScreen extends StatefulWidget { final DecodedInvoice decodedInvoice; final int? overrideAmountSats; + final String? originalFiatCurrency; + final double? originalFiatAmount; + final double? originalFiatRate; const InvoiceConfirmScreen({ super.key, required this.decodedInvoice, this.overrideAmountSats, + this.originalFiatCurrency, + this.originalFiatAmount, + this.originalFiatRate, }); @override @@ -72,9 +78,12 @@ class _InvoiceConfirmScreenState extends State { // Make payment using wallet's admin key final paymentResult = await _invoiceService.sendPayment( serverUrl: session.serverUrl, - adminKey: wallet.adminKey, // Use wallet's admin key + adminKey: wallet.adminKey, bolt11: widget.decodedInvoice.originalInvoice, amount: widget.overrideAmountSats, + originalFiatCurrency: widget.originalFiatCurrency, + originalFiatAmount: widget.originalFiatAmount, + originalFiatRate: widget.originalFiatRate, ); print('[INVOICE_CONFIRM] Payment made: $paymentResult'); diff --git a/lib/screens/7history_screen.dart b/lib/screens/7history_screen.dart index ef05df8..de09088 100644 --- a/lib/screens/7history_screen.dart +++ b/lib/screens/7history_screen.dart @@ -10,6 +10,7 @@ import '../services/wallet_service.dart'; import '../models/transaction_info.dart'; import '../l10n/generated/app_localizations.dart'; import '../theme/app_tokens.dart'; +import '../services/yadio_service.dart'; class HistoryScreen extends StatefulWidget { const HistoryScreen({super.key}); @@ -25,10 +26,12 @@ class _HistoryScreenState extends State with TickerProviderStateM late Animation _headerAnimation; late Animation _listAnimation; + final YadioService _yadioService = YadioService(); List _transactions = []; bool _isLoading = true; bool _hasError = false; String _errorMessage = ''; + Map _currentRates = {}; TransactionFilter _currentFilter = TransactionFilter.all; final ScrollController _scrollController = ScrollController(); @@ -148,6 +151,7 @@ class _HistoryScreenState extends State with TickerProviderStateM _transactions = transactions; _isLoading = false; }); + _fetchCurrentRates(); } } catch (e) { print('=== ERROR LOADING TRANSACTIONS ==='); @@ -615,6 +619,36 @@ class _HistoryScreenState extends State with TickerProviderStateM ); } + Future _fetchCurrentRates() async { + final currencies = {}; + for (final tx in _transactions) { + if (tx.originalFiatCurrency != null) { + currencies.add(tx.originalFiatCurrency!); + } else if (tx.fiatCurrency != null) { + currencies.add(tx.fiatCurrency!); + } + } + if (currencies.isEmpty) return; + + try { + final rates = await _yadioService.getExchangeRates(currencies: currencies); + if (mounted) setState(() => _currentRates = rates); + } catch (e) { + print('[HISTORY] Error fetching rates: $e'); + } + } + + double? _getCurrentFiatValue(TransactionInfo tx, String currency) { + final btcUsd = _currentRates['BTC']; + final currencyPerUsd = _currentRates[currency]; + if (btcUsd == null || currencyPerUsd == null) return null; + + final sats = tx.amountSats.abs(); + final btcValue = sats / 100000000.0; + final usdValue = btcValue * btcUsd; + return usdValue * currencyPerUsd; + } + Widget _buildTransactionCard(TransactionInfo transaction, int index, AppTokens t) { final iconColor = _getTransactionIconColor(transaction, t); return Container( @@ -712,24 +746,41 @@ class _HistoryScreenState extends State with TickerProviderStateM fontWeight: FontWeight.w700, ), ), - if (transaction.fiatAmount != null && transaction.fiatCurrency != null) + if (transaction.originalFiatAmount != null && transaction.originalFiatCurrency != null) ...[ Text( - '${transaction.type == TransactionType.incoming ? '+' : '-'}${transaction.fiatAmount!.toStringAsFixed(2)} ${transaction.fiatCurrency}', + '${AppLocalizations.of(context)!.history_then_label}: ${transaction.originalFiatAmount!.toStringAsFixed(2)} ${transaction.originalFiatCurrency}', + style: TextStyle( + color: t.textPrimary.withValues(alpha: 0.7), + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + Text( + '${AppLocalizations.of(context)!.history_now_label}: ${_getCurrentFiatValue(transaction, transaction.originalFiatCurrency!)?.toStringAsFixed(2) ?? '...'} ${transaction.originalFiatCurrency}', style: TextStyle( color: iconColor.withValues(alpha: 0.8), fontSize: 16, fontWeight: FontWeight.w600, ), ), - if (transaction.originalFiatAmount != null && transaction.originalFiatCurrency != null) + ] else if (transaction.fiatAmount != null && transaction.fiatCurrency != null) ...[ Text( - '${transaction.originalFiatAmount!.toStringAsFixed(2)} ${transaction.originalFiatCurrency}', + '${AppLocalizations.of(context)!.history_then_label}: ${transaction.fiatAmount!.toStringAsFixed(2)} ${transaction.fiatCurrency}', style: TextStyle( color: t.textPrimary.withValues(alpha: 0.7), fontSize: 15, fontWeight: FontWeight.w500, ), ), + Text( + '${AppLocalizations.of(context)!.history_now_label}: ${_getCurrentFiatValue(transaction, transaction.fiatCurrency!)?.toStringAsFixed(2) ?? '...'} ${transaction.fiatCurrency}', + style: TextStyle( + color: iconColor.withValues(alpha: 0.8), + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ], ], ), ], @@ -805,24 +856,41 @@ class _HistoryScreenState extends State with TickerProviderStateM fontWeight: FontWeight.w700, ), ), - if (transaction.fiatAmount != null && transaction.fiatCurrency != null) + if (transaction.originalFiatAmount != null && transaction.originalFiatCurrency != null) ...[ + Text( + '${AppLocalizations.of(context)!.history_then_label}: ${transaction.originalFiatAmount!.toStringAsFixed(2)} ${transaction.originalFiatCurrency}', + style: TextStyle( + color: t.textSecondary, + fontSize: 16, + fontWeight: FontWeight.w400, + ), + ), Text( - '${transaction.type == TransactionType.incoming ? '+' : '-'}${transaction.fiatAmount!.toStringAsFixed(2)} ${transaction.fiatCurrency}', + '${AppLocalizations.of(context)!.history_now_label}: ${_getCurrentFiatValue(transaction, transaction.originalFiatCurrency!)?.toStringAsFixed(2) ?? '...'} ${transaction.originalFiatCurrency}', style: TextStyle( color: iconColor.withValues(alpha: 0.8), fontSize: 18, fontWeight: FontWeight.w500, ), ), - if (transaction.originalFiatAmount != null && transaction.originalFiatCurrency != null) + ] else if (transaction.fiatAmount != null && transaction.fiatCurrency != null) ...[ Text( - '${transaction.originalFiatAmount!.toStringAsFixed(2)} ${transaction.originalFiatCurrency}', + '${AppLocalizations.of(context)!.history_then_label}: ${transaction.fiatAmount!.toStringAsFixed(2)} ${transaction.fiatCurrency}', style: TextStyle( color: t.textSecondary, fontSize: 16, fontWeight: FontWeight.w400, ), ), + Text( + '${AppLocalizations.of(context)!.history_now_label}: ${_getCurrentFiatValue(transaction, transaction.fiatCurrency!)?.toStringAsFixed(2) ?? '...'} ${transaction.fiatCurrency}', + style: TextStyle( + color: iconColor.withValues(alpha: 0.8), + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + ], ], ), ], @@ -838,22 +906,23 @@ class _HistoryScreenState extends State with TickerProviderStateM const SizedBox(height: 24), ], - _buildDetailRow(t, 'Date', transaction.formattedDate), - _buildDetailRow(t, 'Description', transaction.memo.isEmpty ? AppLocalizations.of(context)!.no_description_text : transaction.memo), + _buildDetailRow(t, AppLocalizations.of(context)!.history_date_label, transaction.formattedDate), + _buildDetailRow(t, AppLocalizations.of(context)!.history_description_label, transaction.memo.isEmpty ? AppLocalizations.of(context)!.no_description_text : transaction.memo), if (transaction.originalFiatAmount != null && transaction.originalFiatCurrency != null) ...[ - _buildDetailRow(t, 'Original Amount', '${transaction.originalFiatAmount!.toStringAsFixed(2)} ${transaction.originalFiatCurrency}'), + _buildDetailRow(t, AppLocalizations.of(context)!.history_then_label, '${transaction.originalFiatAmount!.toStringAsFixed(2)} ${transaction.originalFiatCurrency}'), + _buildDetailRow(t, AppLocalizations.of(context)!.history_now_label, '${_getCurrentFiatValue(transaction, transaction.originalFiatCurrency!)?.toStringAsFixed(2) ?? '...'} ${transaction.originalFiatCurrency}'), if (transaction.originalFiatRate != null) - _buildDetailRow(t, 'Original Rate', '${transaction.originalFiatRate!.toStringAsFixed(4)} sats/${transaction.originalFiatCurrency}'), - ], - if (transaction.fiatAmount != null && transaction.fiatCurrency != null) ...[ - _buildDetailRow(t, 'Wallet Amount', '${transaction.fiatAmount!.toStringAsFixed(2)} ${transaction.fiatCurrency}'), + _buildDetailRow(t, AppLocalizations.of(context)!.history_original_rate_label, '${transaction.originalFiatRate!.toStringAsFixed(4)} sats/${transaction.originalFiatCurrency}'), + ] else if (transaction.fiatAmount != null && transaction.fiatCurrency != null) ...[ + _buildDetailRow(t, AppLocalizations.of(context)!.history_then_label, '${transaction.fiatAmount!.toStringAsFixed(2)} ${transaction.fiatCurrency}'), + _buildDetailRow(t, AppLocalizations.of(context)!.history_now_label, '${_getCurrentFiatValue(transaction, transaction.fiatCurrency!)?.toStringAsFixed(2) ?? '...'} ${transaction.fiatCurrency}'), if (transaction.fiatRate != null) - _buildDetailRow(t, 'Wallet Rate', '${transaction.fiatRate!.toStringAsFixed(2)} sats/${transaction.fiatCurrency}'), + _buildDetailRow(t, AppLocalizations.of(context)!.history_wallet_rate_label, '${transaction.fiatRate!.toStringAsFixed(2)} sats/${transaction.fiatCurrency}'), ], if (transaction.paymentHash != null) - _buildDetailRow(t, 'Hash', transaction.paymentHash!, copyable: true), + _buildDetailRow(t, AppLocalizations.of(context)!.history_hash_label, transaction.paymentHash!, copyable: true), if (transaction.fee != null) - _buildDetailRow(t, 'Fee', '${(transaction.fee! / 1000).toStringAsFixed(3)} sats'), + _buildDetailRow(t, AppLocalizations.of(context)!.history_fee_label, '${(transaction.fee! / 1000).toStringAsFixed(3)} sats'), _buildDetailRow(t, AppLocalizations.of(context)!.invoice_status_label, _getTransactionStatus(transaction)), ], ), diff --git a/lib/services/invoice_service.dart b/lib/services/invoice_service.dart index 3ed677c..5ba1642 100644 --- a/lib/services/invoice_service.dart +++ b/lib/services/invoice_service.dart @@ -116,6 +116,20 @@ class InvoiceService { _debugLog('[INVOICE_SERVICE] Original fiat: $originalFiatAmount $originalFiatCurrency (rate: $originalFiatRate)'); } + // Embed fiat info in memo since LNBits does not preserve custom extra keys + // This guarantees the memo fallback parser in TransactionInfo finds the data + final String effectiveMemo; + if (originalFiatCurrency != null && originalFiatAmount != null) { + final fiatSuffix = '${originalFiatAmount.toStringAsFixed(2)} $originalFiatCurrency'; + if (memo != null && memo.trim().isNotEmpty) { + effectiveMemo = '$memo | $fiatSuffix'; + } else { + effectiveMemo = fiatSuffix; + } + } else { + effectiveMemo = memo ?? ''; + } + final headers = { 'X-API-KEY': adminKey, 'Content-Type': 'application/json', @@ -146,13 +160,24 @@ class InvoiceService { if (_isAndroid) { // Android-optimized endpoints tested manually + // Standard /api/v1/payments goes FIRST — same endpoint used by sendPayment(), + // proven to preserve memo+fiat data on LNBits. endpoints = [ - // Try LNBits LNURLP endpoint with fiat info (common pattern) + { + 'url': '$baseUrl/api/v1/payments', + 'data': { + 'out': false, + 'amount': amount, + 'memo': effectiveMemo, + 'extra': extras, + } + }, + // Try LNBits LNURLP endpoint with fiat info (fallback) if (originalFiatCurrency != null && originalFiatAmount != null) { 'url': '$baseUrl/lnurlp/api/v1/invoice', 'data': { 'amount': (amount * 1000).toString(), // LNURLP expects msat as string - 'description': memo ?? '', + 'description': effectiveMemo, 'currency': originalFiatCurrency.toUpperCase(), 'fiat_amount': originalFiatAmount.toString(), 'comment': comment ?? '', @@ -164,19 +189,10 @@ class InvoiceService { 'data': { 'out': false, 'amount': amount, - 'memo': memo ?? '', + 'memo': effectiveMemo, 'fiat_currency': originalFiatCurrency.toUpperCase(), 'fiat_amount': originalFiatAmount, - 'extras': comment != null && comment.isNotEmpty ? {'comment': comment} : null, - } - }, - { - 'url': '$baseUrl/api/v1/payments', - 'data': { - 'out': false, - 'amount': amount, - 'memo': memo ?? '', - 'extras': extras, + 'extra': comment != null && comment.isNotEmpty ? {'comment': comment} : null, } }, // Try approach 2: fiat fields at root level @@ -185,11 +201,11 @@ class InvoiceService { 'data': { 'out': false, 'amount': amount, - 'memo': memo ?? '', + 'memo': effectiveMemo, 'fiat_currency': originalFiatCurrency, 'fiat_amount': originalFiatAmount, 'fiat_rate': originalFiatRate, - 'extras': comment != null && comment.isNotEmpty ? {'comment': comment} : null, + 'extra': comment != null && comment.isNotEmpty ? {'comment': comment} : null, } }, // Try approach 3: using 'currency' parameter (common in some LNBits extensions) @@ -198,18 +214,18 @@ class InvoiceService { 'data': { 'out': false, 'amount': amount, - 'memo': memo ?? '', + 'memo': effectiveMemo, 'currency': originalFiatCurrency, 'currency_amount': originalFiatAmount, - 'extras': extras, + 'extra': extras, } }, { 'url': '$baseUrl/api/v1/payments', 'data': { 'amount': amount, - 'memo': memo ?? '', - 'extras': extras, + 'memo': effectiveMemo, + 'extra': extras, } }, { @@ -217,8 +233,8 @@ class InvoiceService { 'data': { 'out': false, 'amount': amount, - 'description': memo ?? '', - 'extras': extras, + 'description': effectiveMemo, + 'extra': extras, } }, { @@ -226,17 +242,17 @@ class InvoiceService { 'data': { 'out': false, 'amount': amount, - 'memo': memo ?? '', - 'extras': extras, + 'memo': effectiveMemo, + 'extra': extras, } }, { 'url': '$baseUrl/api/v1/payments', 'data': { 'amount': amount, - 'description': memo ?? '', + 'description': effectiveMemo, 'unit': 'sat', - 'extras': extras, + 'extra': extras, } }, { @@ -244,36 +260,47 @@ class InvoiceService { 'data': { 'out': false, 'amount': amount, - 'memo': memo ?? '', - 'extras': extras, + 'memo': effectiveMemo, + 'extra': extras, } }, { 'url': '$baseUrl/api/v1/invoices', 'data': { 'amount': amount, - 'memo': memo ?? '', - 'extras': extras, + 'memo': effectiveMemo, + 'extra': extras, } }, { 'url': '$baseUrl/invoice', 'data': { 'amount': amount, - 'memo': memo ?? '', - 'extras': extras, + 'memo': effectiveMemo, + 'extra': extras, } }, ]; } else { // Web/Desktop-optimized endpoints + // Standard /api/v1/payments goes FIRST for consistent memo preservation endpoints = [ - // Try LNURLP endpoint for web with fiat info + { + 'url': '$baseUrl/api/v1/payments', + 'data': { + 'out': false, + 'amount': amount, + 'memo': effectiveMemo, + 'unit': 'sat', + 'extra': extras, + } + }, + // Try LNURLP endpoint for web with fiat info (fallback) if (originalFiatCurrency != null && originalFiatAmount != null) { 'url': '$baseUrl/lnurlp/api/v1/invoice', 'data': { 'amount': (amount * 1000).toString(), // LNURLP expects msat as string - 'description': memo ?? '', + 'description': effectiveMemo, 'currency': originalFiatCurrency.toUpperCase(), 'fiat_amount': originalFiatAmount.toString(), 'comment': comment ?? '', @@ -285,21 +312,11 @@ class InvoiceService { 'data': { 'out': false, 'amount': amount, - 'memo': memo ?? '', + 'memo': effectiveMemo, 'unit': 'sat', 'fiat_currency': originalFiatCurrency.toUpperCase(), 'fiat_amount': originalFiatAmount, - 'extras': comment != null && comment.isNotEmpty ? {'comment': comment} : null, - } - }, - { - 'url': '$baseUrl/api/v1/payments', - 'data': { - 'out': false, - 'amount': amount, - 'memo': memo ?? '', - 'unit': 'sat', - 'extras': extras, + 'extra': comment != null && comment.isNotEmpty ? {'comment': comment} : null, } }, { @@ -307,27 +324,27 @@ class InvoiceService { 'data': { 'out': false, 'amount': amount, - 'memo': memo ?? '', + 'memo': effectiveMemo, 'unit': 'sat', - 'extras': extras, + 'extra': extras, } }, { 'url': '$baseUrl/api/v1/invoice', 'data': { 'amount': amount, - 'memo': memo ?? '', + 'memo': effectiveMemo, 'unit': 'sat', - 'extras': extras, + 'extra': extras, } }, { 'url': '$baseUrl/api/v1/payments', 'data': { 'amount': amount, - 'memo': memo ?? '', - 'description': memo ?? '', - 'extras': extras, + 'memo': effectiveMemo, + 'description': effectiveMemo, + 'extra': extras, } }, ]; @@ -348,11 +365,11 @@ class InvoiceService { _debugLog('[INVOICE_SERVICE] Headers: $headers'); // Special logging for fiat endpoints - if (data.containsKey('fiat_currency') || data.containsKey('currency') || (data.containsKey('extras') && data['extras'] != null)) { + if (data.containsKey('fiat_currency') || data.containsKey('currency') || (data.containsKey('extra') && data['extra'] != null)) { _debugLog('[INVOICE_SERVICE] 💰 FIAT ENDPOINT ATTEMPT: Contains fiat data'); _debugLog('[INVOICE_SERVICE] 💰 Fiat currency: ${data['fiat_currency'] ?? data['currency']}'); _debugLog('[INVOICE_SERVICE] 💰 Fiat amount: ${data['fiat_amount'] ?? data['currency_amount']}'); - _debugLog('[INVOICE_SERVICE] 💰 Extras: ${data['extras']}'); + _debugLog('[INVOICE_SERVICE] 💰 Extras: ${data['extra']}'); } final response = await _dio.post( @@ -651,6 +668,9 @@ class InvoiceService { required String adminKey, required String bolt11, int? amount, + String? originalFiatCurrency, + double? originalFiatAmount, + double? originalFiatRate, }) async { try { // Asegurar que la URL tenga HTTPS @@ -788,6 +808,19 @@ class InvoiceService { ]; } + // Build extras with original fiat data if provided + final Map? extras; + if (originalFiatCurrency != null && originalFiatAmount != null && amount != null) { + extras = { + 'fiat_currency': originalFiatCurrency, + 'fiat_amount': originalFiatAmount, + 'fiat_rate': amount / originalFiatAmount, + 'btc_rate': (originalFiatAmount / amount) * 100000000, + }; + } else { + extras = null; + } + Exception? lastException; // Try each endpoint until one works @@ -795,6 +828,7 @@ class InvoiceService { final endpoint = endpoints[i]; final url = endpoint['url'] as String; final data = endpoint['data'] as Map; + if (extras != null) data['extra'] = extras; try { _debugLog('[INVOICE_SERVICE] Trying endpoint $url (${i + 1}/${endpoints.length})'); @@ -873,6 +907,9 @@ class InvoiceService { required String lightningAddress, required int amountSats, String? comment, + String? originalFiatCurrency, + double? originalFiatAmount, + double? originalFiatRate, }) async { try { _debugLog('[INVOICE_SERVICE] 🚀 Sending payment to Lightning Address using native LNBits API'); @@ -935,6 +972,21 @@ class InvoiceService { _debugLog('[INVOICE_SERVICE] - Description hash: ${hash.toString()}'); // Step 2: Use native LNBits endpoint /api/v1/payments/lnurl + final extras = {}; + if (originalFiatCurrency != null && originalFiatAmount != null) { + extras['fiat_currency'] = originalFiatCurrency; + extras['fiat_amount'] = originalFiatAmount; + extras['fiat_rate'] = originalFiatRate; + } + + // Embed fiat in description so the memo fallback parser in TransactionInfo can recover it + final String effectiveDescription; + if (originalFiatCurrency != null && originalFiatAmount != null) { + effectiveDescription = 'Payment to $lightningAddress | ${originalFiatAmount!.toStringAsFixed(2)} $originalFiatCurrency'; + } else { + effectiveDescription = 'Payment to $lightningAddress'; + } + final response = await _dio.post( '$baseUrl/api/v1/payments/lnurl', data: { @@ -942,7 +994,8 @@ class InvoiceService { 'callback': metadata['callback'], 'amount': amountMsat, 'comment': comment ?? '', - 'description': 'Payment to $lightningAddress', + 'description': effectiveDescription, + if (extras.isNotEmpty) 'extra': extras, }, options: Options( headers: { @@ -971,6 +1024,9 @@ class InvoiceService { lightningAddress: lightningAddress, amountSats: amountSats, comment: comment, + originalFiatCurrency: originalFiatCurrency, + originalFiatAmount: originalFiatAmount, + originalFiatRate: originalFiatRate, ); } @@ -986,6 +1042,9 @@ class InvoiceService { required String lightningAddress, required int amountSats, String? comment, + String? originalFiatCurrency, + double? originalFiatAmount, + double? originalFiatRate, }) async { try { _debugLog('[INVOICE_SERVICE] 🔧 Using manual method for Lightning Address'); @@ -1005,6 +1064,10 @@ class InvoiceService { serverUrl: serverUrl, adminKey: adminKey, bolt11: bolt11, + amount: amountSats, + originalFiatCurrency: originalFiatCurrency, + originalFiatAmount: originalFiatAmount, + originalFiatRate: originalFiatRate, ); } catch (e) { if (e.toString().contains('CORS') || e.toString().contains('XMLHttpRequest')) { @@ -1030,6 +1093,9 @@ class InvoiceService { required String lnurl, required int amountSats, String? comment, + String? originalFiatCurrency, + double? originalFiatAmount, + double? originalFiatRate, }) async { try { _debugLog('[INVOICE_SERVICE] Resolving LNURL: ${lnurl.substring(0, 20)}...'); @@ -1049,6 +1115,10 @@ class InvoiceService { serverUrl: serverUrl, adminKey: adminKey, bolt11: bolt11, + amount: amountSats, + originalFiatCurrency: originalFiatCurrency, + originalFiatAmount: originalFiatAmount, + originalFiatRate: originalFiatRate, ); } catch (e) { _debugLog('[INVOICE_SERVICE] Error sending payment to LNURL: $e'); diff --git a/lib/services/yadio_service.dart b/lib/services/yadio_service.dart index af3e2cd..45fa33d 100644 --- a/lib/services/yadio_service.dart +++ b/lib/services/yadio_service.dart @@ -97,24 +97,45 @@ class YadioService { } } - /// Gets current exchange rates - Future> getExchangeRates() async { + /// Gets current exchange rates using the convert endpoint (more reliable than /rates) + /// [currencies] - set of currency codes to fetch (e.g. {'CUP', 'USD'}) + Future> getExchangeRates({Set? currencies}) async { try { - final response = await _dio.get('$baseUrl/rates'); - - if (response.statusCode == 200) { - return Map.from(response.data); - } else { - throw Exception('Error getting rates: ${response.statusCode}'); + // Get BTC/USD rate first + final btcUsdResponse = await _dio.get('$baseUrl/convert/1/BTC/USD'); + if (btcUsdResponse.statusCode != 200) throw Exception('BTC/USD failed'); + final btcUsd = btcUsdResponse.data['result'] as double; + + final rates = { + 'BTC': btcUsd, + 'USD': 1.0, + }; + + // Fetch each needed currency via /convert/1/BTC/XXX + if (currencies != null) { + for (final currency in currencies) { + if (currency == 'USD') continue; + try { + final response = await _dio.get('$baseUrl/convert/1/BTC/$currency'); + if (response.statusCode == 200) { + final btcToCurrency = response.data['result'] as double; + rates[currency] = btcToCurrency / btcUsd; + } + } catch (e) { + print('[YADIO_SERVICE] Failed to get rate for $currency: $e'); + } + } } + + print('[YADIO_SERVICE] Rates obtained: $rates'); + return rates; } catch (e) { - print('[YADIO_SERVICE] Error getting rates: $e'); - - // Fallback with fixed rates + print('[YADIO_SERVICE] Error getting rates via convert: $e'); + return { - 'CUP': 120.0, // CUP por USD - 'USD': 1.0, // USD base - 'BTC': 45000.0 // USD por BTC (ejemplo) + 'CUP': 120.0, + 'USD': 1.0, + 'BTC': 45000.0, }; } }