refactor of money utils with new money2 package
This commit is contained in:
@@ -1,102 +1,78 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
import 'package:pshared/models/asset.dart';
|
||||
import 'package:pshared/models/currency.dart';
|
||||
|
||||
|
||||
const nonBreakingSpace = '\u00A0';
|
||||
final Currency _usdtCurrency = Currency.create(
|
||||
'USDT',
|
||||
6,
|
||||
symbol: '₮',
|
||||
isIso: false,
|
||||
country: 'Digital',
|
||||
unit: 'Tether',
|
||||
name: 'Tether',
|
||||
);
|
||||
|
||||
String withTrailingNonBreakingSpace(String value) {
|
||||
return '$value$nonBreakingSpace';
|
||||
}
|
||||
final Currency _usdcCurrency = Currency.create(
|
||||
'USDC',
|
||||
6,
|
||||
symbol: r'($)',
|
||||
isIso: false,
|
||||
country: 'Digital',
|
||||
unit: 'USD Coin',
|
||||
name: 'USD Coin',
|
||||
);
|
||||
|
||||
String joinWithNonBreakingSpace(String left, String right) {
|
||||
return '$left$nonBreakingSpace$right';
|
||||
}
|
||||
final Map<String, Currency> _commonCurrenciesByCode =
|
||||
<String, Currency>{
|
||||
for (final currency in CommonCurrencies().asList())
|
||||
currency.isoCode: currency,
|
||||
_usdtCurrency.isoCode: _usdtCurrency,
|
||||
_usdcCurrency.isoCode: _usdcCurrency,
|
||||
};
|
||||
|
||||
String currencyCodeToSymbol(Currency currencyCode) {
|
||||
switch (currencyCode) {
|
||||
case Currency.usd:
|
||||
return '\$';
|
||||
case Currency.usdt:
|
||||
return '₮';
|
||||
case Currency.usdc:
|
||||
return '\$';
|
||||
case Currency.rub:
|
||||
return '₽';
|
||||
case Currency.eur:
|
||||
return '€';
|
||||
String currencyCodeToSymbol(CurrencyCode currencyCode) {
|
||||
final symbol = currencySymbolFromCode(currencyCodeToString(currencyCode));
|
||||
if (symbol == null || symbol.trim().isEmpty) {
|
||||
return currencyCodeToString(currencyCode);
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
String amountToString(double amount) {
|
||||
return amount.toStringAsFixed(2);
|
||||
}
|
||||
|
||||
String currencyToString(Currency currencyCode, double amount) {
|
||||
return joinWithNonBreakingSpace(
|
||||
currencyCodeToSymbol(currencyCode),
|
||||
amountToString(amount),
|
||||
);
|
||||
}
|
||||
|
||||
String assetToString(Asset asset) {
|
||||
return currencyToString(asset.currency, asset.amount);
|
||||
}
|
||||
|
||||
Currency currencyStringToCode(String currencyCode) {
|
||||
switch (currencyCode) {
|
||||
case 'USD':
|
||||
return Currency.usd;
|
||||
case 'USDT':
|
||||
return Currency.usdt;
|
||||
case 'USDC':
|
||||
return Currency.usdc;
|
||||
case 'RUB':
|
||||
return Currency.rub;
|
||||
case 'EUR':
|
||||
return Currency.eur;
|
||||
default:
|
||||
throw ArgumentError('Unknown currency code: $currencyCode');
|
||||
String currencyToString(CurrencyCode currencyCode, double amount) {
|
||||
final code = currencyCodeToString(currencyCode);
|
||||
final currency = money2CurrencyFromCode(code);
|
||||
if (currency == null) {
|
||||
return '$amount $code';
|
||||
}
|
||||
final money = Money.fromNumWithCurrency(amount, currency);
|
||||
return money.toString();
|
||||
}
|
||||
|
||||
String currencyCodeToString(Currency currencyCode) {
|
||||
switch (currencyCode) {
|
||||
case Currency.usd:
|
||||
return 'USD';
|
||||
case Currency.usdt:
|
||||
return 'USDT';
|
||||
case Currency.usdc:
|
||||
return 'USDC';
|
||||
case Currency.rub:
|
||||
return 'RUB';
|
||||
case Currency.eur:
|
||||
return 'EUR';
|
||||
CurrencyCode currencyStringToCode(String currencyCode) {
|
||||
final normalized = currencyCode.trim().toUpperCase();
|
||||
for (final value in CurrencyCode.values) {
|
||||
if (currencyCodeToString(value) == normalized) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
throw ArgumentError('Unknown currency code: $currencyCode');
|
||||
}
|
||||
|
||||
IconData iconForCurrencyType(Currency currencyCode) {
|
||||
switch (currencyCode) {
|
||||
case Currency.usd:
|
||||
return Icons.currency_exchange;
|
||||
case Currency.eur:
|
||||
return Icons.currency_exchange;
|
||||
case Currency.rub:
|
||||
return Icons.currency_ruble;
|
||||
case Currency.usdt:
|
||||
return Icons.currency_exchange;
|
||||
case Currency.usdc:
|
||||
return Icons.money;
|
||||
}
|
||||
String currencyCodeToString(CurrencyCode currencyCode) {
|
||||
return currencyCode.name.toUpperCase();
|
||||
}
|
||||
|
||||
String? currencySymbolFromCode(String? code) {
|
||||
final normalized = code?.trim();
|
||||
if (normalized == null || normalized.isEmpty) return null;
|
||||
try {
|
||||
return currencyCodeToSymbol(currencyStringToCode(normalized.toUpperCase()));
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
final currency = money2CurrencyFromCode(code);
|
||||
if (currency == null) return null;
|
||||
final symbol = currency.symbol.trim();
|
||||
return symbol.isEmpty ? null : symbol;
|
||||
}
|
||||
|
||||
Currency? money2CurrencyFromCode(String? code) {
|
||||
final normalized = code?.trim().toUpperCase();
|
||||
if (normalized == null || normalized.isEmpty) return null;
|
||||
return _commonCurrenciesByCode[normalized] ?? Currencies().find(normalized);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,35 @@
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
const String _decimalMoneyPattern = '0.##################';
|
||||
|
||||
double parseMoneyAmount(String? raw, {double fallback = 0}) {
|
||||
final trimmed = raw?.trim();
|
||||
if (trimmed == null || trimmed.isEmpty) return fallback;
|
||||
return double.tryParse(trimmed) ?? fallback;
|
||||
Money? parseMoneyWithCurrency(String? amount, Currency? currency) {
|
||||
if (currency == null) return null;
|
||||
final value = _normalizeMoneyAmount(amount);
|
||||
if (value == null || value.isEmpty) return null;
|
||||
|
||||
try {
|
||||
return Money.parseWithCurrency(
|
||||
value,
|
||||
currency,
|
||||
pattern: _decimalMoneyPattern,
|
||||
);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String formatMoneyDisplay(
|
||||
Money? money, {
|
||||
String fallback = '--',
|
||||
String separator = ' ',
|
||||
String invalidAmountFallback = '',
|
||||
}) {
|
||||
if (money == null) return fallback;
|
||||
|
||||
final rawAmount = money.amount.trim();
|
||||
final rawCurrency = money.currency.trim();
|
||||
final parsedAmount = parseMoneyAmount(rawAmount, fallback: double.nan);
|
||||
final amountToken = parsedAmount.isNaN
|
||||
? (rawAmount.isEmpty ? invalidAmountFallback : rawAmount)
|
||||
: amountToString(parsedAmount);
|
||||
|
||||
final symbol = currencySymbolFromCode(rawCurrency);
|
||||
final normalizedSymbol = symbol?.trim() ?? '';
|
||||
final hasSymbol = normalizedSymbol.isNotEmpty;
|
||||
final currencyToken = hasSymbol ? normalizedSymbol : rawCurrency;
|
||||
final first = amountToken;
|
||||
final second = currencyToken;
|
||||
|
||||
if (first.isEmpty && second.isEmpty) return fallback;
|
||||
if (first.isEmpty) return second;
|
||||
if (second.isEmpty) return first;
|
||||
return '$first$separator$second';
|
||||
Money? parseMoneyWithCurrencyCode(String? amount, String? currencyCode) {
|
||||
return parseMoneyWithCurrency(amount, money2CurrencyFromCode(currencyCode));
|
||||
}
|
||||
|
||||
extension MoneyAmountX on Money {
|
||||
double get amountValue => parseMoneyAmount(amount);
|
||||
String? _normalizeMoneyAmount(String? value) {
|
||||
final normalized = value?.trim();
|
||||
if (normalized == null || normalized.isEmpty) return null;
|
||||
|
||||
if (normalized.contains(',') && !normalized.contains('.')) {
|
||||
return normalized.replaceAll(',', '.');
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
import 'package:pshared/models/payment/currency_pair.dart';
|
||||
import 'package:pshared/models/payment/fx/intent.dart';
|
||||
import 'package:pshared/models/payment/fx/side.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
|
||||
|
||||
class FxIntentHelper {
|
||||
@@ -37,11 +38,15 @@ class FxIntentHelper {
|
||||
case FxSide.unspecified:
|
||||
break;
|
||||
}
|
||||
if (amount.currency == pair.base && pair.quote.isNotEmpty) return pair.quote;
|
||||
if (amount.currency == pair.quote && pair.base.isNotEmpty) return pair.base;
|
||||
if (amount.currency.isoCode == pair.base && pair.quote.isNotEmpty) {
|
||||
return pair.quote;
|
||||
}
|
||||
if (amount.currency.isoCode == pair.quote && pair.base.isNotEmpty) {
|
||||
return pair.base;
|
||||
}
|
||||
if (pair.quote.isNotEmpty) return pair.quote;
|
||||
if (pair.base.isNotEmpty) return pair.base;
|
||||
}
|
||||
return amount.currency;
|
||||
return amount.currency.isoCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class PaymentQuotationCurrencyResolver {
|
||||
PaymentMethodData? paymentData,
|
||||
}) {
|
||||
final quoteCurrency = _normalizeCurrency(
|
||||
quote?.amounts?.destinationSettlement?.currency,
|
||||
quote?.amounts?.destinationSettlement?.currency.isoCode,
|
||||
);
|
||||
if (quoteCurrency != null) return quoteCurrency;
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
import 'package:pshared/models/payment/fees/line.dart';
|
||||
import 'package:pshared/models/payment/quote/quote.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
|
||||
Money? quoteFeeTotal(PaymentQuote? quote) {
|
||||
final preferredCurrency =
|
||||
quote?.amounts?.sourcePrincipal?.currency ??
|
||||
quote?.amounts?.sourceDebitTotal?.currency;
|
||||
return quoteFeeTotalFromLines(
|
||||
quote?.fees?.lines,
|
||||
preferredCurrency: preferredCurrency,
|
||||
preferredCurrency:
|
||||
quote?.amounts?.sourcePrincipal?.currency.isoCode ??
|
||||
quote?.amounts?.sourceDebitTotal?.currency.isoCode,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +20,8 @@ Money? quoteSourceDebitTotal(
|
||||
}) {
|
||||
final sourceDebitTotal = quote?.amounts?.sourceDebitTotal;
|
||||
final preferredCurrency = _normalizeCurrency(
|
||||
preferredSourceCurrency ?? quote?.amounts?.sourcePrincipal?.currency,
|
||||
preferredSourceCurrency ??
|
||||
quote?.amounts?.sourcePrincipal?.currency.isoCode,
|
||||
);
|
||||
|
||||
if (sourceDebitTotal == null) {
|
||||
@@ -31,10 +31,9 @@ Money? quoteSourceDebitTotal(
|
||||
);
|
||||
}
|
||||
|
||||
final debitCurrency = _normalizeCurrency(sourceDebitTotal.currency);
|
||||
if (preferredCurrency == null ||
|
||||
debitCurrency == null ||
|
||||
debitCurrency == preferredCurrency) {
|
||||
_normalizeCurrency(sourceDebitTotal.currency.isoCode) ==
|
||||
preferredCurrency) {
|
||||
return sourceDebitTotal;
|
||||
}
|
||||
|
||||
@@ -52,22 +51,18 @@ Money? quoteFeeTotalFromLines(
|
||||
if (lines == null || lines.isEmpty) return null;
|
||||
|
||||
final normalizedPreferred = _normalizeCurrency(preferredCurrency);
|
||||
final totalsByCurrency = <String, double>{};
|
||||
final totalsByCurrency = <String, Money>{};
|
||||
|
||||
for (final line in lines) {
|
||||
final money = line.amount;
|
||||
if (money == null) continue;
|
||||
final parsedAmount = line.amount;
|
||||
if (parsedAmount == null) continue;
|
||||
|
||||
final currency = _normalizeCurrency(money.currency);
|
||||
if (currency == null) continue;
|
||||
|
||||
final amount = parseMoneyAmount(money.amount, fallback: double.nan);
|
||||
if (amount.isNaN) continue;
|
||||
|
||||
final sign = _lineSign(line.side);
|
||||
final signedAmount = sign * amount.abs();
|
||||
totalsByCurrency[currency] =
|
||||
(totalsByCurrency[currency] ?? 0) + signedAmount;
|
||||
final currencyCode = parsedAmount.currency.isoCode;
|
||||
final signedAmount = _isCreditLine(line.side) ? -parsedAmount : parsedAmount;
|
||||
final current = totalsByCurrency[currencyCode];
|
||||
totalsByCurrency[currencyCode] = current == null
|
||||
? signedAmount
|
||||
: current + signedAmount;
|
||||
}
|
||||
|
||||
if (totalsByCurrency.isEmpty) return null;
|
||||
@@ -77,85 +72,59 @@ Money? quoteFeeTotalFromLines(
|
||||
totalsByCurrency.containsKey(normalizedPreferred)
|
||||
? normalizedPreferred
|
||||
: totalsByCurrency.keys.first;
|
||||
final total = totalsByCurrency[selectedCurrency];
|
||||
if (total == null) return null;
|
||||
|
||||
return Money(amount: amountToString(total), currency: selectedCurrency);
|
||||
return totalsByCurrency[selectedCurrency];
|
||||
}
|
||||
|
||||
List<Money> aggregateMoneyByCurrency(Iterable<Money?> values) {
|
||||
final totals = <String, double>{};
|
||||
final totals = <String, Money>{};
|
||||
for (final value in values) {
|
||||
if (value == null) continue;
|
||||
|
||||
final currency = _normalizeCurrency(value.currency);
|
||||
if (currency == null) continue;
|
||||
|
||||
final amount = parseMoneyAmount(value.amount, fallback: double.nan);
|
||||
if (amount.isNaN) continue;
|
||||
|
||||
totals[currency] = (totals[currency] ?? 0) + amount;
|
||||
final currency = value.currency.isoCode;
|
||||
final current = totals[currency];
|
||||
totals[currency] = current == null ? value : current + value;
|
||||
}
|
||||
|
||||
return totals.entries
|
||||
.map(
|
||||
(entry) =>
|
||||
Money(amount: amountToString(entry.value), currency: entry.key),
|
||||
)
|
||||
.toList();
|
||||
return totals.values.toList();
|
||||
}
|
||||
|
||||
Money? _rebuildSourceDebitTotal(
|
||||
PaymentQuote? quote, {
|
||||
String? preferredSourceCurrency,
|
||||
}) {
|
||||
final sourcePrincipal = quote?.amounts?.sourcePrincipal;
|
||||
if (sourcePrincipal == null) return null;
|
||||
final principal = quote?.amounts?.sourcePrincipal;
|
||||
if (principal == null) return null;
|
||||
|
||||
final principalCurrency = _normalizeCurrency(sourcePrincipal.currency);
|
||||
if (principalCurrency == null) return null;
|
||||
final principalCurrency = principal.currency.isoCode;
|
||||
if (preferredSourceCurrency != null &&
|
||||
principalCurrency != preferredSourceCurrency) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final principalAmount = parseMoneyAmount(
|
||||
sourcePrincipal.amount,
|
||||
fallback: double.nan,
|
||||
);
|
||||
if (principalAmount.isNaN) return null;
|
||||
|
||||
double totalAmount = principalAmount;
|
||||
var totalAmount = principal;
|
||||
final fee = quoteFeeTotalFromLines(
|
||||
quote?.fees?.lines,
|
||||
preferredCurrency: principalCurrency,
|
||||
);
|
||||
if (fee != null && _normalizeCurrency(fee.currency) == principalCurrency) {
|
||||
final feeAmount = parseMoneyAmount(fee.amount, fallback: double.nan);
|
||||
if (!feeAmount.isNaN) {
|
||||
totalAmount += feeAmount;
|
||||
}
|
||||
if (fee != null && fee.currency.isoCode == principalCurrency) {
|
||||
totalAmount += fee;
|
||||
}
|
||||
|
||||
return Money(
|
||||
amount: amountToString(totalAmount),
|
||||
currency: principalCurrency,
|
||||
);
|
||||
return totalAmount;
|
||||
}
|
||||
|
||||
double _lineSign(String? side) {
|
||||
bool _isCreditLine(String? side) {
|
||||
final normalized = side?.trim().toLowerCase() ?? '';
|
||||
switch (normalized) {
|
||||
case 'entry_side_credit':
|
||||
case 'credit':
|
||||
return -1;
|
||||
return true;
|
||||
default:
|
||||
return 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String? _normalizeCurrency(String? currency) {
|
||||
final normalized = currency?.trim().toUpperCase();
|
||||
final normalized = currency?.trim();
|
||||
if (normalized == null || normalized.isEmpty) return null;
|
||||
return normalized;
|
||||
return money2CurrencyFromCode(normalized)?.isoCode ?? normalized.toUpperCase();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user