refactor of money utils with new money2 package
This commit is contained in:
@@ -1,17 +1,20 @@
|
|||||||
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
import 'package:pshared/data/dto/money.dart';
|
import 'package:pshared/data/dto/money.dart';
|
||||||
import 'package:pshared/models/money.dart';
|
import 'package:pshared/utils/money.dart';
|
||||||
|
|
||||||
|
|
||||||
extension MoneyMapper on Money {
|
extension MoneyMapper on Money {
|
||||||
MoneyDTO toDTO() => MoneyDTO(
|
MoneyDTO toDTO() =>
|
||||||
amount: amount,
|
MoneyDTO(amount: toDecimal().toString(), currency: currency.isoCode);
|
||||||
currency: currency,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MoneyDTOMapper on MoneyDTO {
|
extension MoneyDTOMapper on MoneyDTO {
|
||||||
Money toDomain() => Money(
|
Money toDomain() {
|
||||||
amount: amount,
|
final parsed = parseMoneyWithCurrencyCode(amount, currency);
|
||||||
currency: currency,
|
if (parsed == null) {
|
||||||
);
|
throw FormatException('Invalid money dto: $currency $amount');
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import 'package:pshared/data/dto/wallet/chain_asset.dart';
|
|
||||||
import 'package:pshared/data/mapper/payment/enums.dart';
|
|
||||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
|
||||||
|
|
||||||
|
|
||||||
extension ChainAssetDTOMapper on ChainAssetDTO {
|
|
||||||
ChainAsset toDomain() => ChainAsset(
|
|
||||||
chain: chainNetworkFromValue(chain),
|
|
||||||
tokenSymbol: tokenSymbol,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ChainAssetMapper on ChainAsset {
|
|
||||||
ChainAssetDTO toDTO() => ChainAssetDTO(
|
|
||||||
chain: chainNetworkToValue(chain),
|
|
||||||
tokenSymbol: tokenSymbol,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
import 'package:pshared/models/wallet/wallet.dart' as domain;
|
import 'package:pshared/models/wallet/wallet.dart' as domain;
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
import 'package:pshared/utils/currency.dart';
|
import 'package:pshared/utils/currency.dart';
|
||||||
import 'package:pshared/utils/money.dart';
|
|
||||||
|
|
||||||
|
|
||||||
extension WalletUiMapper on domain.WalletModel {
|
extension WalletUiMapper on domain.WalletModel {
|
||||||
Wallet toUi() => Wallet(
|
Wallet toUi() => Wallet(
|
||||||
id: walletRef,
|
id: walletRef,
|
||||||
walletUserID: walletRef,
|
walletUserID: walletRef,
|
||||||
balance: parseMoneyAmount(
|
balance: availableMoney?.toDouble() ?? balance?.available?.toDouble() ?? 0,
|
||||||
availableMoney?.amount ?? balance?.available?.amount,
|
|
||||||
),
|
|
||||||
currency: currencyStringToCode(asset.tokenSymbol),
|
currency: currencyStringToCode(asset.tokenSymbol),
|
||||||
calculatedAt: balance?.calculatedAt ?? DateTime.now(),
|
calculatedAt: balance?.calculatedAt ?? DateTime.now(),
|
||||||
depositAddress: depositAddress,
|
depositAddress: depositAddress,
|
||||||
|
|||||||
@@ -16,15 +16,19 @@ extension WalletDTOMapper on WalletDTO {
|
|||||||
depositAddress: depositAddress,
|
depositAddress: depositAddress,
|
||||||
status: status,
|
status: status,
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
createdAt: (createdAt == null || createdAt!.isEmpty) ? null : DateTime.tryParse(createdAt!),
|
createdAt: (createdAt == null || createdAt!.isEmpty)
|
||||||
updatedAt: (updatedAt == null || updatedAt!.isEmpty) ? null : DateTime.tryParse(updatedAt!),
|
? null
|
||||||
|
: DateTime.tryParse(createdAt!),
|
||||||
|
updatedAt: (updatedAt == null || updatedAt!.isEmpty)
|
||||||
|
? null
|
||||||
|
: DateTime.tryParse(updatedAt!),
|
||||||
balance: balance?.toDomain(),
|
balance: balance?.toDomain(),
|
||||||
availableMoney: balance?.available?.toDomain(),
|
availableMoney: balance?.available?.toDomain(),
|
||||||
describable: newDescribable(
|
describable: newDescribable(
|
||||||
name: name.isNotEmpty ? name : (metadata?['name']?.toString() ?? ''),
|
name: name.isNotEmpty ? name : (metadata?['name']?.toString() ?? ''),
|
||||||
description: (description != null && description!.isNotEmpty)
|
description: (description != null && description!.isNotEmpty)
|
||||||
? description
|
? description
|
||||||
: metadata?['description'],
|
: metadata?['description'],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import 'package:pshared/models/currency.dart';
|
|
||||||
import 'package:pshared/utils/currency.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class Asset {
|
|
||||||
final Currency currency;
|
|
||||||
final double amount;
|
|
||||||
|
|
||||||
const Asset({
|
|
||||||
required this.currency,
|
|
||||||
required this.amount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Asset createAsset(String currencyCode, String amount) => Asset(
|
|
||||||
currency: currencyStringToCode(currencyCode),
|
|
||||||
amount: double.parse(amount),
|
|
||||||
);
|
|
||||||
@@ -1 +1 @@
|
|||||||
enum Currency {usd, eur, rub, usdt, usdc}
|
enum CurrencyCode {usd, eur, rub, usdt, usdc}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:pshared/models/money.dart';
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
|
|
||||||
class LedgerBalance {
|
class LedgerBalance {
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
class Money {
|
|
||||||
final String amount;
|
|
||||||
final String currency;
|
|
||||||
|
|
||||||
const Money({
|
|
||||||
required this.amount,
|
|
||||||
required this.currency,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:pshared/models/money.dart';
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentExecutionOperation {
|
class PaymentExecutionOperation {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:pshared/models/money.dart';
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
|
|
||||||
class FeeLine {
|
class FeeLine {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:pshared/models/money.dart';
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
|
|
||||||
class FxQuote {
|
class FxQuote {
|
||||||
final String? quoteRef;
|
final String? quoteRef;
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/payment/fees/treatment.dart';
|
import 'package:pshared/models/payment/fees/treatment.dart';
|
||||||
import 'package:pshared/models/payment/fx/intent.dart';
|
import 'package:pshared/models/payment/fx/intent.dart';
|
||||||
import 'package:pshared/models/payment/kind.dart';
|
import 'package:pshared/models/payment/kind.dart';
|
||||||
import 'package:pshared/models/payment/customer.dart';
|
import 'package:pshared/models/payment/customer.dart';
|
||||||
import 'package:pshared/models/payment/methods/data.dart';
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
import 'package:pshared/models/money.dart';
|
|
||||||
import 'package:pshared/models/payment/settlement_mode.dart';
|
import 'package:pshared/models/payment/settlement_mode.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentIntent {
|
class PaymentIntent {
|
||||||
final PaymentKind kind;
|
final PaymentKind kind;
|
||||||
final String? sourceRef;
|
final String? sourceRef;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:pshared/models/money.dart';
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
|
|
||||||
class QuoteAmounts {
|
class QuoteAmounts {
|
||||||
final Money? sourcePrincipal;
|
final Money? sourcePrincipal;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class Wallet implements Describable {
|
|||||||
final String id;
|
final String id;
|
||||||
final String walletUserID; // ID or number that we show the user
|
final String walletUserID; // ID or number that we show the user
|
||||||
final double balance;
|
final double balance;
|
||||||
final Currency currency;
|
final CurrencyCode currency;
|
||||||
final DateTime calculatedAt;
|
final DateTime calculatedAt;
|
||||||
final String? depositAddress;
|
final String? depositAddress;
|
||||||
final ChainNetwork? network;
|
final ChainNetwork? network;
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
import 'package:pshared/models/payment/chain_network.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletAsset extends ChainAsset {
|
class WalletAsset {
|
||||||
|
final ChainNetwork chain;
|
||||||
|
final String tokenSymbol;
|
||||||
final String contractAddress;
|
final String contractAddress;
|
||||||
|
|
||||||
const WalletAsset({
|
const WalletAsset({
|
||||||
required super.chain,
|
required this.chain,
|
||||||
required super.tokenSymbol,
|
required this.tokenSymbol,
|
||||||
required this.contractAddress,
|
required this.contractAddress,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:pshared/models/money.dart';
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletBalance {
|
class WalletBalance {
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import 'package:pshared/models/payment/chain_network.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class ChainAsset {
|
|
||||||
final ChainNetwork chain;
|
|
||||||
final String tokenSymbol;
|
|
||||||
|
|
||||||
const ChainAsset({
|
|
||||||
required this.chain,
|
|
||||||
required this.tokenSymbol,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:pshared/models/describable.dart';
|
import 'package:pshared/models/describable.dart';
|
||||||
import 'package:pshared/models/money.dart';
|
import 'package:money2/money2.dart';
|
||||||
import 'package:pshared/models/wallet/asset.dart';
|
import 'package:pshared/models/wallet/asset.dart';
|
||||||
import 'package:pshared/models/wallet/balance.dart';
|
import 'package:pshared/models/wallet/balance.dart';
|
||||||
|
|
||||||
@@ -39,9 +39,7 @@ class WalletModel implements Describable {
|
|||||||
required this.describable,
|
required this.describable,
|
||||||
});
|
});
|
||||||
|
|
||||||
WalletModel copyWith({
|
WalletModel copyWith({Describable? describable}) => WalletModel(
|
||||||
Describable? describable,
|
|
||||||
}) => WalletModel(
|
|
||||||
walletRef: walletRef,
|
walletRef: walletRef,
|
||||||
organizationRef: organizationRef,
|
organizationRef: organizationRef,
|
||||||
ownerRef: ownerRef,
|
ownerRef: ownerRef,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import 'package:pshared/provider/resource.dart';
|
|||||||
import 'package:pshared/service/ledger.dart';
|
import 'package:pshared/service/ledger.dart';
|
||||||
import 'package:pshared/utils/exception.dart';
|
import 'package:pshared/utils/exception.dart';
|
||||||
|
|
||||||
|
|
||||||
class LedgerAccountsProvider with ChangeNotifier {
|
class LedgerAccountsProvider with ChangeNotifier {
|
||||||
final LedgerService _service;
|
final LedgerService _service;
|
||||||
OrganizationsProvider? _organizations;
|
OrganizationsProvider? _organizations;
|
||||||
@@ -179,7 +180,7 @@ class LedgerAccountsProvider with ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> create({
|
Future<void> create({
|
||||||
required Describable describable,
|
required Describable describable,
|
||||||
required Currency currency,
|
required CurrencyCode currency,
|
||||||
String? ownerRef,
|
String? ownerRef,
|
||||||
}) async {
|
}) async {
|
||||||
final org = _organizations;
|
final org = _organizations;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/payment/source.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
import 'package:pshared/models/payment/asset.dart';
|
import 'package:pshared/models/payment/asset.dart';
|
||||||
import 'package:pshared/models/payment/chain_network.dart';
|
import 'package:pshared/models/payment/chain_network.dart';
|
||||||
@@ -13,19 +15,18 @@ import 'package:pshared/models/payment/methods/data.dart';
|
|||||||
import 'package:pshared/models/payment/methods/ledger.dart';
|
import 'package:pshared/models/payment/methods/ledger.dart';
|
||||||
import 'package:pshared/models/payment/methods/managed_wallet.dart';
|
import 'package:pshared/models/payment/methods/managed_wallet.dart';
|
||||||
import 'package:pshared/models/payment/methods/type.dart';
|
import 'package:pshared/models/payment/methods/type.dart';
|
||||||
import 'package:pshared/models/money.dart';
|
|
||||||
import 'package:pshared/models/payment/settlement_mode.dart';
|
import 'package:pshared/models/payment/settlement_mode.dart';
|
||||||
import 'package:pshared/models/payment/intent.dart';
|
import 'package:pshared/models/payment/intent.dart';
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
import 'package:pshared/provider/payment/amount.dart';
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
import 'package:pshared/provider/payment/flow.dart';
|
import 'package:pshared/provider/payment/flow.dart';
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
import 'package:pshared/utils/currency.dart';
|
||||||
import 'package:pshared/utils/payment/fx_helpers.dart';
|
import 'package:pshared/utils/payment/fx_helpers.dart';
|
||||||
|
|
||||||
|
|
||||||
class QuotationIntentBuilder {
|
class QuotationIntentBuilder {
|
||||||
static const String _settlementCurrency = 'RUB';
|
static const String _settlementCurrency = 'RUB';
|
||||||
static const String _addressBookCustomerFallbackId = 'address_book_customer';
|
|
||||||
|
|
||||||
PaymentIntent? build({
|
PaymentIntent? build({
|
||||||
required PaymentAmountProvider payment,
|
required PaymentAmountProvider payment,
|
||||||
@@ -38,10 +39,9 @@ class QuotationIntentBuilder {
|
|||||||
final paymentData = flow.selectedPaymentData;
|
final paymentData = flow.selectedPaymentData;
|
||||||
final selectedMethod = flow.selectedMethod;
|
final selectedMethod = flow.selectedMethod;
|
||||||
final amountValue = payment.amount;
|
final amountValue = payment.amount;
|
||||||
if (sourceMethod == null || sourceCurrency == null || paymentData == null) {
|
if (sourceCurrency == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (amountValue == null) return null;
|
|
||||||
|
|
||||||
final customer = _buildCustomer(
|
final customer = _buildCustomer(
|
||||||
recipient: recipients.currentObject,
|
recipient: recipients.currentObject,
|
||||||
@@ -51,22 +51,22 @@ class QuotationIntentBuilder {
|
|||||||
final amountCurrency = payment.settlementMode == SettlementMode.fixReceived
|
final amountCurrency = payment.settlementMode == SettlementMode.fixReceived
|
||||||
? _settlementCurrency
|
? _settlementCurrency
|
||||||
: sourceCurrency;
|
: sourceCurrency;
|
||||||
final amount = Money(
|
final currency = money2CurrencyFromCode(amountCurrency);
|
||||||
amount: amountValue.toString(),
|
if (currency == null) return null;
|
||||||
currency: amountCurrency,
|
final amount = amountValue == null
|
||||||
);
|
? null
|
||||||
|
: Money.fromNumWithCurrency(amountValue, currency);
|
||||||
final isLedgerSource = source.selectedLedgerAccount != null;
|
final isLedgerSource = source.selectedLedgerAccount != null;
|
||||||
final isCryptoToCrypto =
|
final isCryptoToCrypto =
|
||||||
paymentData is CryptoAddressPaymentMethod &&
|
paymentData is CryptoAddressPaymentMethod &&
|
||||||
(paymentData.asset?.tokenSymbol ?? '').trim().toUpperCase() ==
|
(paymentData.asset?.tokenSymbol ?? '').trim().toUpperCase() ==
|
||||||
amount.currency;
|
sourceCurrency.trim().toUpperCase();
|
||||||
final fxIntent = _buildFxIntent(
|
final fxIntent = _buildFxIntent(
|
||||||
sourceCurrency: sourceCurrency,
|
sourceCurrency: sourceCurrency,
|
||||||
settlementMode: payment.settlementMode,
|
settlementMode: payment.settlementMode,
|
||||||
isLedgerSource: isLedgerSource,
|
isLedgerSource: isLedgerSource,
|
||||||
enabled: !isCryptoToCrypto,
|
enabled: !isCryptoToCrypto,
|
||||||
);
|
);
|
||||||
final comment = _resolveComment(payment.comment);
|
|
||||||
return PaymentIntent(
|
return PaymentIntent(
|
||||||
kind: PaymentKind.payout,
|
kind: PaymentKind.payout,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
@@ -77,7 +77,7 @@ class QuotationIntentBuilder {
|
|||||||
? FeeTreatment.addToSource
|
? FeeTreatment.addToSource
|
||||||
: FeeTreatment.deductFromDestination,
|
: FeeTreatment.deductFromDestination,
|
||||||
settlementMode: payment.settlementMode,
|
settlementMode: payment.settlementMode,
|
||||||
comment: comment,
|
comment: payment.comment,
|
||||||
customer: customer,
|
customer: customer,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -94,14 +94,9 @@ class QuotationIntentBuilder {
|
|||||||
// BFF maps only settlement currency + fx side, then quotation derives pair.
|
// BFF maps only settlement currency + fx side, then quotation derives pair.
|
||||||
// For ledger this preserves source debit in ledger currency (e.g. USDT).
|
// For ledger this preserves source debit in ledger currency (e.g. USDT).
|
||||||
if (isLedgerSource && settlementMode == SettlementMode.fixReceived) {
|
if (isLedgerSource && settlementMode == SettlementMode.fixReceived) {
|
||||||
final base = sourceCurrency.trim();
|
|
||||||
final quote = _settlementCurrency;
|
|
||||||
if (base.isEmpty || base.toUpperCase() == quote.toUpperCase()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return FxIntent(
|
return FxIntent(
|
||||||
pair: CurrencyPair(base: base, quote: quote),
|
pair: CurrencyPair(base: sourceCurrency, quote: _settlementCurrency),
|
||||||
side: FxSide.sellBaseBuyQuote,
|
side: FxSide.buyBaseSellQuote,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,39 +132,59 @@ class QuotationIntentBuilder {
|
|||||||
required PaymentMethod? method,
|
required PaymentMethod? method,
|
||||||
required PaymentMethodData? data,
|
required PaymentMethodData? data,
|
||||||
}) {
|
}) {
|
||||||
final name = recipient?.name.trim();
|
final customerId = recipient?.id.trim() ?? '';
|
||||||
if (name == null || name.isEmpty) return null;
|
final card = _resolveCard(method: method, data: data);
|
||||||
final id = recipient?.id.trim();
|
final fromRecipient = _buildCustomerFromName(
|
||||||
final customerId = id == null || id.isEmpty
|
customerId: customerId,
|
||||||
? _addressBookCustomerFallbackId
|
fullName: recipient?.name,
|
||||||
: id;
|
country: card?.country,
|
||||||
|
);
|
||||||
|
if (fromRecipient != null) return fromRecipient;
|
||||||
|
|
||||||
final parts = name.split(RegExp(r'\s+'));
|
if (card != null) {
|
||||||
|
final firstName = _normalizedOrNull(card.firstName);
|
||||||
|
final lastName = _normalizedOrNull(card.lastName);
|
||||||
|
if (firstName == null && lastName == null) return null;
|
||||||
|
return Customer(
|
||||||
|
id: customerId,
|
||||||
|
firstName: firstName,
|
||||||
|
lastName: lastName,
|
||||||
|
country: card.country,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardPaymentMethod? _resolveCard({
|
||||||
|
required PaymentMethod? method,
|
||||||
|
required PaymentMethodData? data,
|
||||||
|
}) => method?.cardData ?? (data is CardPaymentMethod ? data : null);
|
||||||
|
|
||||||
|
Customer? _buildCustomerFromName({
|
||||||
|
required String customerId,
|
||||||
|
required String? fullName,
|
||||||
|
String? country,
|
||||||
|
}) {
|
||||||
|
final normalizedName = _normalizedOrNull(fullName);
|
||||||
|
if (normalizedName == null) return null;
|
||||||
|
final parts = normalizedName.split(RegExp(r'\s+'));
|
||||||
final firstName = parts.isNotEmpty ? parts.first : null;
|
final firstName = parts.isNotEmpty ? parts.first : null;
|
||||||
final lastName = parts.length >= 2 ? parts.last : null;
|
final lastName = parts.length >= 2 ? parts.last : null;
|
||||||
final middleName = parts.length > 2
|
final middleName = parts.length > 2
|
||||||
? parts.sublist(1, parts.length - 1).join(' ')
|
? parts.sublist(1, parts.length - 1).join(' ')
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return Customer(
|
return Customer(
|
||||||
id: customerId,
|
id: customerId,
|
||||||
firstName: firstName,
|
firstName: firstName,
|
||||||
middleName: middleName,
|
middleName: middleName,
|
||||||
lastName: lastName,
|
lastName: lastName,
|
||||||
country: _resolveCustomerCountry(method: method, data: data),
|
country: country,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _resolveCustomerCountry({
|
String? _normalizedOrNull(String? value) {
|
||||||
required PaymentMethod? method,
|
if (value == null) return null;
|
||||||
required PaymentMethodData? data,
|
final normalized = value.trim();
|
||||||
}) {
|
|
||||||
final card = method?.cardData ?? (data is CardPaymentMethod ? data : null);
|
|
||||||
return card?.country;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _resolveComment(String comment) {
|
|
||||||
final normalized = comment.trim();
|
|
||||||
return normalized.isEmpty ? null : normalized;
|
return normalized.isEmpty ? null : normalized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import 'package:logging/logging.dart';
|
|||||||
|
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
import 'package:pshared/api/requests/payment/quote.dart';
|
import 'package:pshared/api/requests/payment/quote.dart';
|
||||||
import 'package:pshared/controllers/payment/source.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
import 'package:pshared/data/mapper/payment/intent/payment.dart';
|
import 'package:pshared/data/mapper/payment/intent/payment.dart';
|
||||||
import 'package:pshared/models/asset.dart';
|
|
||||||
import 'package:pshared/models/payment/intent.dart';
|
import 'package:pshared/models/payment/intent.dart';
|
||||||
import 'package:pshared/models/payment/quote/quote.dart';
|
import 'package:pshared/models/payment/quote/quote.dart';
|
||||||
import 'package:pshared/models/money.dart';
|
|
||||||
import 'package:pshared/models/auto_refresh_mode.dart';
|
import 'package:pshared/models/auto_refresh_mode.dart';
|
||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
import 'package:pshared/provider/payment/amount.dart';
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
@@ -79,20 +79,12 @@ class QuotationProvider extends ChangeNotifier {
|
|||||||
return DateTime.fromMillisecondsSinceEpoch(expiresAtUnixMs, isUtc: true);
|
return DateTime.fromMillisecondsSinceEpoch(expiresAtUnixMs, isUtc: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Asset? get fee => _assetFromMoney(quoteFeeTotal(quotation));
|
Money? get fee => quoteFeeTotal(quotation);
|
||||||
Asset? get total => _assetFromMoney(
|
Money? get total => quoteSourceDebitTotal(
|
||||||
quoteSourceDebitTotal(
|
quotation,
|
||||||
quotation,
|
preferredSourceCurrency: _sourceCurrencyCode,
|
||||||
preferredSourceCurrency: _sourceCurrencyCode,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
Asset? get recipientGets =>
|
Money? get recipientGets => quotation?.amounts?.destinationSettlement;
|
||||||
_assetFromMoney(quotation?.amounts?.destinationSettlement);
|
|
||||||
|
|
||||||
Asset? _assetFromMoney(Money? money) {
|
|
||||||
if (money == null) return null;
|
|
||||||
return createAsset(money.currency, money.amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setResource(Resource<PaymentQuote> quotation) {
|
void _setResource(Resource<PaymentQuote> quotation) {
|
||||||
_quotation = quotation;
|
_quotation = quotation;
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ import 'package:flutter/foundation.dart';
|
|||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/currency.dart';
|
||||||
import 'package:pshared/models/describable.dart';
|
import 'package:pshared/models/describable.dart';
|
||||||
|
import 'package:pshared/models/payment/chain_network.dart';
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
|
||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
import 'package:pshared/provider/resource.dart';
|
import 'package:pshared/provider/resource.dart';
|
||||||
import 'package:pshared/service/payment/wallets.dart';
|
import 'package:pshared/service/payment/wallets.dart';
|
||||||
import 'package:pshared/utils/exception.dart';
|
import 'package:pshared/utils/exception.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletsProvider with ChangeNotifier {
|
class WalletsProvider with ChangeNotifier {
|
||||||
final WalletsService _service;
|
final WalletsService _service;
|
||||||
OrganizationsProvider? _organizations;
|
OrganizationsProvider? _organizations;
|
||||||
@@ -180,7 +182,8 @@ class WalletsProvider with ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> create({
|
Future<void> create({
|
||||||
required Describable describable,
|
required Describable describable,
|
||||||
required ChainAsset asset,
|
required ChainNetwork chain,
|
||||||
|
required CurrencyCode currency,
|
||||||
required String? ownerRef,
|
required String? ownerRef,
|
||||||
}) async {
|
}) async {
|
||||||
final org = _organizations;
|
final org = _organizations;
|
||||||
@@ -195,7 +198,8 @@ class WalletsProvider with ChangeNotifier {
|
|||||||
await _service.create(
|
await _service.create(
|
||||||
organizationRef: org.current.id,
|
organizationRef: org.current.id,
|
||||||
describable: describable,
|
describable: describable,
|
||||||
asset: asset,
|
chain: chain,
|
||||||
|
currency: currency,
|
||||||
ownerRef: ownerRef,
|
ownerRef: ownerRef,
|
||||||
);
|
);
|
||||||
await loadWalletsWithBalances();
|
await loadWalletsWithBalances();
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class LedgerService {
|
|||||||
required String organizationRef,
|
required String organizationRef,
|
||||||
required Describable describable,
|
required Describable describable,
|
||||||
required String? ownerRef,
|
required String? ownerRef,
|
||||||
required Currency currency,
|
required CurrencyCode currency,
|
||||||
}) async => AuthorizationService.getPOSTResponse(
|
}) async => AuthorizationService.getPOSTResponse(
|
||||||
_objectType,
|
_objectType,
|
||||||
'/$organizationRef',
|
'/$organizationRef',
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:pshared/data/mapper/wallet/ui.dart';
|
import 'package:pshared/data/mapper/wallet/ui.dart';
|
||||||
|
import 'package:pshared/models/currency.dart';
|
||||||
import 'package:pshared/models/describable.dart';
|
import 'package:pshared/models/describable.dart';
|
||||||
|
import 'package:pshared/models/payment/chain_network.dart';
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
|
||||||
import 'package:pshared/service/wallet.dart' as shared_wallet_service;
|
import 'package:pshared/service/wallet.dart' as shared_wallet_service;
|
||||||
import 'package:pshared/utils/money.dart';
|
|
||||||
|
|
||||||
|
|
||||||
abstract class WalletsService {
|
abstract class WalletsService {
|
||||||
@@ -12,7 +12,8 @@ abstract class WalletsService {
|
|||||||
Future<void> create({
|
Future<void> create({
|
||||||
required String organizationRef,
|
required String organizationRef,
|
||||||
required Describable describable,
|
required Describable describable,
|
||||||
required ChainAsset asset,
|
required ChainNetwork chain,
|
||||||
|
required CurrencyCode currency,
|
||||||
required String? ownerRef,
|
required String? ownerRef,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -30,19 +31,21 @@ class ApiWalletsService implements WalletsService {
|
|||||||
organizationRef: organizationRef,
|
organizationRef: organizationRef,
|
||||||
walletRef: walletRef,
|
walletRef: walletRef,
|
||||||
);
|
);
|
||||||
return parseMoneyAmount(balance.available?.amount);
|
return balance.available?.toDouble() ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> create({
|
Future<void> create({
|
||||||
required String organizationRef,
|
required String organizationRef,
|
||||||
required Describable describable,
|
required Describable describable,
|
||||||
required ChainAsset asset,
|
required ChainNetwork chain,
|
||||||
|
required CurrencyCode currency,
|
||||||
required String? ownerRef,
|
required String? ownerRef,
|
||||||
}) => shared_wallet_service.WalletService.create(
|
}) => shared_wallet_service.WalletService.create(
|
||||||
organizationRef: organizationRef,
|
organizationRef: organizationRef,
|
||||||
describable: describable,
|
describable: describable,
|
||||||
asset: asset,
|
chain: chain,
|
||||||
|
currency: currency,
|
||||||
ownerRef: ownerRef,
|
ownerRef: ownerRef,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import 'package:pshared/api/requests/wallet/create.dart';
|
import 'package:pshared/api/requests/wallet/create.dart';
|
||||||
import 'package:pshared/api/responses/wallet_balance.dart';
|
import 'package:pshared/api/responses/wallet_balance.dart';
|
||||||
import 'package:pshared/api/responses/wallets.dart';
|
import 'package:pshared/api/responses/wallets.dart';
|
||||||
|
import 'package:pshared/data/dto/wallet/chain_asset.dart';
|
||||||
import 'package:pshared/data/mapper/describable.dart';
|
import 'package:pshared/data/mapper/describable.dart';
|
||||||
import 'package:pshared/data/mapper/wallet/chain_asset.dart';
|
import 'package:pshared/data/mapper/payment/enums.dart';
|
||||||
import 'package:pshared/data/mapper/wallet/response.dart';
|
import 'package:pshared/data/mapper/wallet/response.dart';
|
||||||
|
import 'package:pshared/models/currency.dart';
|
||||||
import 'package:pshared/models/describable.dart';
|
import 'package:pshared/models/describable.dart';
|
||||||
|
import 'package:pshared/models/payment/chain_network.dart';
|
||||||
import 'package:pshared/models/wallet/balance.dart';
|
import 'package:pshared/models/wallet/balance.dart';
|
||||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
|
||||||
import 'package:pshared/models/wallet/wallet.dart';
|
import 'package:pshared/models/wallet/wallet.dart';
|
||||||
import 'package:pshared/service/authorization/service.dart';
|
import 'package:pshared/service/authorization/service.dart';
|
||||||
import 'package:pshared/service/services.dart';
|
import 'package:pshared/service/services.dart';
|
||||||
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletService {
|
class WalletService {
|
||||||
@@ -37,13 +40,19 @@ class WalletService {
|
|||||||
static Future<void> create({
|
static Future<void> create({
|
||||||
required String organizationRef,
|
required String organizationRef,
|
||||||
required Describable describable,
|
required Describable describable,
|
||||||
required ChainAsset asset,
|
required ChainNetwork chain,
|
||||||
|
required CurrencyCode currency,
|
||||||
required String? ownerRef,
|
required String? ownerRef,
|
||||||
}) async => AuthorizationService.getPOSTResponse(
|
}) async => AuthorizationService.getPOSTResponse(
|
||||||
_objectType,
|
_objectType,
|
||||||
'/$organizationRef',
|
'/$organizationRef',
|
||||||
CreateWalletRequest(
|
CreateWalletRequest(
|
||||||
asset: asset.toDTO(),
|
asset: ChainAssetDTO(
|
||||||
|
chain: chainNetworkToValue(chain),
|
||||||
|
tokenSymbol:
|
||||||
|
money2CurrencyFromCode(currencyCodeToString(currency))?.isoCode ??
|
||||||
|
currencyCodeToString(currency),
|
||||||
|
),
|
||||||
describable: describable.toDTO(),
|
describable: describable.toDTO(),
|
||||||
ownerRef: ownerRef,
|
ownerRef: ownerRef,
|
||||||
).toJson(),
|
).toJson(),
|
||||||
|
|||||||
@@ -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';
|
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) {
|
final Currency _usdcCurrency = Currency.create(
|
||||||
return '$value$nonBreakingSpace';
|
'USDC',
|
||||||
}
|
6,
|
||||||
|
symbol: r'($)',
|
||||||
|
isIso: false,
|
||||||
|
country: 'Digital',
|
||||||
|
unit: 'USD Coin',
|
||||||
|
name: 'USD Coin',
|
||||||
|
);
|
||||||
|
|
||||||
String joinWithNonBreakingSpace(String left, String right) {
|
final Map<String, Currency> _commonCurrenciesByCode =
|
||||||
return '$left$nonBreakingSpace$right';
|
<String, Currency>{
|
||||||
}
|
for (final currency in CommonCurrencies().asList())
|
||||||
|
currency.isoCode: currency,
|
||||||
|
_usdtCurrency.isoCode: _usdtCurrency,
|
||||||
|
_usdcCurrency.isoCode: _usdcCurrency,
|
||||||
|
};
|
||||||
|
|
||||||
String currencyCodeToSymbol(Currency currencyCode) {
|
String currencyCodeToSymbol(CurrencyCode currencyCode) {
|
||||||
switch (currencyCode) {
|
final symbol = currencySymbolFromCode(currencyCodeToString(currencyCode));
|
||||||
case Currency.usd:
|
if (symbol == null || symbol.trim().isEmpty) {
|
||||||
return '\$';
|
return currencyCodeToString(currencyCode);
|
||||||
case Currency.usdt:
|
|
||||||
return '₮';
|
|
||||||
case Currency.usdc:
|
|
||||||
return '\$';
|
|
||||||
case Currency.rub:
|
|
||||||
return '₽';
|
|
||||||
case Currency.eur:
|
|
||||||
return '€';
|
|
||||||
}
|
}
|
||||||
|
return symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
String amountToString(double amount) {
|
String currencyToString(CurrencyCode currencyCode, double amount) {
|
||||||
return amount.toStringAsFixed(2);
|
final code = currencyCodeToString(currencyCode);
|
||||||
}
|
final currency = money2CurrencyFromCode(code);
|
||||||
|
if (currency == null) {
|
||||||
String currencyToString(Currency currencyCode, double amount) {
|
return '$amount $code';
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
final money = Money.fromNumWithCurrency(amount, currency);
|
||||||
|
return money.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
String currencyCodeToString(Currency currencyCode) {
|
CurrencyCode currencyStringToCode(String currencyCode) {
|
||||||
switch (currencyCode) {
|
final normalized = currencyCode.trim().toUpperCase();
|
||||||
case Currency.usd:
|
for (final value in CurrencyCode.values) {
|
||||||
return 'USD';
|
if (currencyCodeToString(value) == normalized) {
|
||||||
case Currency.usdt:
|
return value;
|
||||||
return 'USDT';
|
}
|
||||||
case Currency.usdc:
|
|
||||||
return 'USDC';
|
|
||||||
case Currency.rub:
|
|
||||||
return 'RUB';
|
|
||||||
case Currency.eur:
|
|
||||||
return 'EUR';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw ArgumentError('Unknown currency code: $currencyCode');
|
||||||
}
|
}
|
||||||
|
|
||||||
IconData iconForCurrencyType(Currency currencyCode) {
|
String currencyCodeToString(CurrencyCode currencyCode) {
|
||||||
switch (currencyCode) {
|
return currencyCode.name.toUpperCase();
|
||||||
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? currencySymbolFromCode(String? code) {
|
String? currencySymbolFromCode(String? code) {
|
||||||
final normalized = code?.trim();
|
final currency = money2CurrencyFromCode(code);
|
||||||
if (normalized == null || normalized.isEmpty) return null;
|
if (currency == null) return null;
|
||||||
try {
|
final symbol = currency.symbol.trim();
|
||||||
return currencyCodeToSymbol(currencyStringToCode(normalized.toUpperCase()));
|
return symbol.isEmpty ? null : symbol;
|
||||||
} catch (_) {
|
}
|
||||||
return null;
|
|
||||||
}
|
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';
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
|
const String _decimalMoneyPattern = '0.##################';
|
||||||
|
|
||||||
double parseMoneyAmount(String? raw, {double fallback = 0}) {
|
Money? parseMoneyWithCurrency(String? amount, Currency? currency) {
|
||||||
final trimmed = raw?.trim();
|
if (currency == null) return null;
|
||||||
if (trimmed == null || trimmed.isEmpty) return fallback;
|
final value = _normalizeMoneyAmount(amount);
|
||||||
return double.tryParse(trimmed) ?? fallback;
|
if (value == null || value.isEmpty) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Money.parseWithCurrency(
|
||||||
|
value,
|
||||||
|
currency,
|
||||||
|
pattern: _decimalMoneyPattern,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String formatMoneyDisplay(
|
Money? parseMoneyWithCurrencyCode(String? amount, String? currencyCode) {
|
||||||
Money? money, {
|
return parseMoneyWithCurrency(amount, money2CurrencyFromCode(currencyCode));
|
||||||
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';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MoneyAmountX on Money {
|
String? _normalizeMoneyAmount(String? value) {
|
||||||
double get amountValue => parseMoneyAmount(amount);
|
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/currency_pair.dart';
|
||||||
import 'package:pshared/models/payment/fx/intent.dart';
|
import 'package:pshared/models/payment/fx/intent.dart';
|
||||||
import 'package:pshared/models/payment/fx/side.dart';
|
import 'package:pshared/models/payment/fx/side.dart';
|
||||||
import 'package:pshared/models/money.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class FxIntentHelper {
|
class FxIntentHelper {
|
||||||
@@ -37,11 +38,15 @@ class FxIntentHelper {
|
|||||||
case FxSide.unspecified:
|
case FxSide.unspecified:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (amount.currency == pair.base && pair.quote.isNotEmpty) return pair.quote;
|
if (amount.currency.isoCode == pair.base && pair.quote.isNotEmpty) {
|
||||||
if (amount.currency == pair.quote && pair.base.isNotEmpty) return pair.base;
|
return pair.quote;
|
||||||
|
}
|
||||||
|
if (amount.currency.isoCode == pair.quote && pair.base.isNotEmpty) {
|
||||||
|
return pair.base;
|
||||||
|
}
|
||||||
if (pair.quote.isNotEmpty) return pair.quote;
|
if (pair.quote.isNotEmpty) return pair.quote;
|
||||||
if (pair.base.isNotEmpty) return pair.base;
|
if (pair.base.isNotEmpty) return pair.base;
|
||||||
}
|
}
|
||||||
return amount.currency;
|
return amount.currency.isoCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class PaymentQuotationCurrencyResolver {
|
|||||||
PaymentMethodData? paymentData,
|
PaymentMethodData? paymentData,
|
||||||
}) {
|
}) {
|
||||||
final quoteCurrency = _normalizeCurrency(
|
final quoteCurrency = _normalizeCurrency(
|
||||||
quote?.amounts?.destinationSettlement?.currency,
|
quote?.amounts?.destinationSettlement?.currency.isoCode,
|
||||||
);
|
);
|
||||||
if (quoteCurrency != null) return quoteCurrency;
|
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/fees/line.dart';
|
||||||
import 'package:pshared/models/payment/quote/quote.dart';
|
import 'package:pshared/models/payment/quote/quote.dart';
|
||||||
import 'package:pshared/utils/currency.dart';
|
import 'package:pshared/utils/currency.dart';
|
||||||
import 'package:pshared/utils/money.dart';
|
|
||||||
|
|
||||||
|
|
||||||
Money? quoteFeeTotal(PaymentQuote? quote) {
|
Money? quoteFeeTotal(PaymentQuote? quote) {
|
||||||
final preferredCurrency =
|
|
||||||
quote?.amounts?.sourcePrincipal?.currency ??
|
|
||||||
quote?.amounts?.sourceDebitTotal?.currency;
|
|
||||||
return quoteFeeTotalFromLines(
|
return quoteFeeTotalFromLines(
|
||||||
quote?.fees?.lines,
|
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 sourceDebitTotal = quote?.amounts?.sourceDebitTotal;
|
||||||
final preferredCurrency = _normalizeCurrency(
|
final preferredCurrency = _normalizeCurrency(
|
||||||
preferredSourceCurrency ?? quote?.amounts?.sourcePrincipal?.currency,
|
preferredSourceCurrency ??
|
||||||
|
quote?.amounts?.sourcePrincipal?.currency.isoCode,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (sourceDebitTotal == null) {
|
if (sourceDebitTotal == null) {
|
||||||
@@ -31,10 +31,9 @@ Money? quoteSourceDebitTotal(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final debitCurrency = _normalizeCurrency(sourceDebitTotal.currency);
|
|
||||||
if (preferredCurrency == null ||
|
if (preferredCurrency == null ||
|
||||||
debitCurrency == null ||
|
_normalizeCurrency(sourceDebitTotal.currency.isoCode) ==
|
||||||
debitCurrency == preferredCurrency) {
|
preferredCurrency) {
|
||||||
return sourceDebitTotal;
|
return sourceDebitTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,22 +51,18 @@ Money? quoteFeeTotalFromLines(
|
|||||||
if (lines == null || lines.isEmpty) return null;
|
if (lines == null || lines.isEmpty) return null;
|
||||||
|
|
||||||
final normalizedPreferred = _normalizeCurrency(preferredCurrency);
|
final normalizedPreferred = _normalizeCurrency(preferredCurrency);
|
||||||
final totalsByCurrency = <String, double>{};
|
final totalsByCurrency = <String, Money>{};
|
||||||
|
|
||||||
for (final line in lines) {
|
for (final line in lines) {
|
||||||
final money = line.amount;
|
final parsedAmount = line.amount;
|
||||||
if (money == null) continue;
|
if (parsedAmount == null) continue;
|
||||||
|
|
||||||
final currency = _normalizeCurrency(money.currency);
|
final currencyCode = parsedAmount.currency.isoCode;
|
||||||
if (currency == null) continue;
|
final signedAmount = _isCreditLine(line.side) ? -parsedAmount : parsedAmount;
|
||||||
|
final current = totalsByCurrency[currencyCode];
|
||||||
final amount = parseMoneyAmount(money.amount, fallback: double.nan);
|
totalsByCurrency[currencyCode] = current == null
|
||||||
if (amount.isNaN) continue;
|
? signedAmount
|
||||||
|
: current + signedAmount;
|
||||||
final sign = _lineSign(line.side);
|
|
||||||
final signedAmount = sign * amount.abs();
|
|
||||||
totalsByCurrency[currency] =
|
|
||||||
(totalsByCurrency[currency] ?? 0) + signedAmount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalsByCurrency.isEmpty) return null;
|
if (totalsByCurrency.isEmpty) return null;
|
||||||
@@ -77,85 +72,59 @@ Money? quoteFeeTotalFromLines(
|
|||||||
totalsByCurrency.containsKey(normalizedPreferred)
|
totalsByCurrency.containsKey(normalizedPreferred)
|
||||||
? normalizedPreferred
|
? normalizedPreferred
|
||||||
: totalsByCurrency.keys.first;
|
: totalsByCurrency.keys.first;
|
||||||
final total = totalsByCurrency[selectedCurrency];
|
return totalsByCurrency[selectedCurrency];
|
||||||
if (total == null) return null;
|
|
||||||
|
|
||||||
return Money(amount: amountToString(total), currency: selectedCurrency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Money> aggregateMoneyByCurrency(Iterable<Money?> values) {
|
List<Money> aggregateMoneyByCurrency(Iterable<Money?> values) {
|
||||||
final totals = <String, double>{};
|
final totals = <String, Money>{};
|
||||||
for (final value in values) {
|
for (final value in values) {
|
||||||
if (value == null) continue;
|
if (value == null) continue;
|
||||||
|
final currency = value.currency.isoCode;
|
||||||
final currency = _normalizeCurrency(value.currency);
|
final current = totals[currency];
|
||||||
if (currency == null) continue;
|
totals[currency] = current == null ? value : current + value;
|
||||||
|
|
||||||
final amount = parseMoneyAmount(value.amount, fallback: double.nan);
|
|
||||||
if (amount.isNaN) continue;
|
|
||||||
|
|
||||||
totals[currency] = (totals[currency] ?? 0) + amount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return totals.entries
|
return totals.values.toList();
|
||||||
.map(
|
|
||||||
(entry) =>
|
|
||||||
Money(amount: amountToString(entry.value), currency: entry.key),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Money? _rebuildSourceDebitTotal(
|
Money? _rebuildSourceDebitTotal(
|
||||||
PaymentQuote? quote, {
|
PaymentQuote? quote, {
|
||||||
String? preferredSourceCurrency,
|
String? preferredSourceCurrency,
|
||||||
}) {
|
}) {
|
||||||
final sourcePrincipal = quote?.amounts?.sourcePrincipal;
|
final principal = quote?.amounts?.sourcePrincipal;
|
||||||
if (sourcePrincipal == null) return null;
|
if (principal == null) return null;
|
||||||
|
|
||||||
final principalCurrency = _normalizeCurrency(sourcePrincipal.currency);
|
final principalCurrency = principal.currency.isoCode;
|
||||||
if (principalCurrency == null) return null;
|
|
||||||
if (preferredSourceCurrency != null &&
|
if (preferredSourceCurrency != null &&
|
||||||
principalCurrency != preferredSourceCurrency) {
|
principalCurrency != preferredSourceCurrency) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final principalAmount = parseMoneyAmount(
|
var totalAmount = principal;
|
||||||
sourcePrincipal.amount,
|
|
||||||
fallback: double.nan,
|
|
||||||
);
|
|
||||||
if (principalAmount.isNaN) return null;
|
|
||||||
|
|
||||||
double totalAmount = principalAmount;
|
|
||||||
final fee = quoteFeeTotalFromLines(
|
final fee = quoteFeeTotalFromLines(
|
||||||
quote?.fees?.lines,
|
quote?.fees?.lines,
|
||||||
preferredCurrency: principalCurrency,
|
preferredCurrency: principalCurrency,
|
||||||
);
|
);
|
||||||
if (fee != null && _normalizeCurrency(fee.currency) == principalCurrency) {
|
if (fee != null && fee.currency.isoCode == principalCurrency) {
|
||||||
final feeAmount = parseMoneyAmount(fee.amount, fallback: double.nan);
|
totalAmount += fee;
|
||||||
if (!feeAmount.isNaN) {
|
|
||||||
totalAmount += feeAmount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Money(
|
return totalAmount;
|
||||||
amount: amountToString(totalAmount),
|
|
||||||
currency: principalCurrency,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double _lineSign(String? side) {
|
bool _isCreditLine(String? side) {
|
||||||
final normalized = side?.trim().toLowerCase() ?? '';
|
final normalized = side?.trim().toLowerCase() ?? '';
|
||||||
switch (normalized) {
|
switch (normalized) {
|
||||||
case 'entry_side_credit':
|
case 'entry_side_credit':
|
||||||
case 'credit':
|
case 'credit':
|
||||||
return -1;
|
return true;
|
||||||
default:
|
default:
|
||||||
return 1;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _normalizeCurrency(String? currency) {
|
String? _normalizeCurrency(String? currency) {
|
||||||
final normalized = currency?.trim().toUpperCase();
|
final normalized = currency?.trim();
|
||||||
if (normalized == null || normalized.isEmpty) return null;
|
if (normalized == null || normalized.isEmpty) return null;
|
||||||
return normalized;
|
return money2CurrencyFromCode(normalized)?.isoCode ?? normalized.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ dependencies:
|
|||||||
uuid: ^4.5.1
|
uuid: ^4.5.1
|
||||||
image: ^4.5.4
|
image: ^4.5.4
|
||||||
shared_preferences: ^2.5.3
|
shared_preferences: ^2.5.3
|
||||||
|
money2: ^6.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:pshared/models/payment/status.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/models/wallet/wallet_transaction.dart';
|
|
||||||
import 'package:pweb/providers/wallet_transactions.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class WalletTransactionsController extends ChangeNotifier {
|
|
||||||
List<WalletTransaction> _filteredTransactions = [];
|
|
||||||
DateTimeRange? _dateRange;
|
|
||||||
final Set<OperationStatus> _selectedStatuses = {};
|
|
||||||
final Set<WalletTransactionType> _selectedTypes = {};
|
|
||||||
WalletTransactionsProvider? _provider;
|
|
||||||
|
|
||||||
List<WalletTransaction> get transactions =>
|
|
||||||
_provider?.transactions ?? const [];
|
|
||||||
List<WalletTransaction> get filteredTransactions => _filteredTransactions;
|
|
||||||
DateTimeRange? get dateRange => _dateRange;
|
|
||||||
Set<OperationStatus> get selectedStatuses => _selectedStatuses;
|
|
||||||
Set<WalletTransactionType> get selectedTypes => _selectedTypes;
|
|
||||||
bool get isLoading => _provider?.isLoading ?? false;
|
|
||||||
String? get error => _provider?.error;
|
|
||||||
bool get hasFilters =>
|
|
||||||
_dateRange != null ||
|
|
||||||
_selectedStatuses.isNotEmpty ||
|
|
||||||
_selectedTypes.isNotEmpty;
|
|
||||||
|
|
||||||
void update(WalletTransactionsProvider provider) {
|
|
||||||
if (identical(_provider, provider)) return;
|
|
||||||
_provider?.removeListener(_onProviderChanged);
|
|
||||||
_provider = provider;
|
|
||||||
_provider?.addListener(_onProviderChanged);
|
|
||||||
_rebuildFiltered(notify: false);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDateRange(DateTimeRange? range) {
|
|
||||||
_dateRange = range;
|
|
||||||
_rebuildFiltered();
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggleStatus(OperationStatus status) {
|
|
||||||
if (_selectedStatuses.contains(status)) {
|
|
||||||
_selectedStatuses.remove(status);
|
|
||||||
} else {
|
|
||||||
_selectedStatuses.add(status);
|
|
||||||
}
|
|
||||||
_rebuildFiltered();
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggleType(WalletTransactionType type) {
|
|
||||||
if (_selectedTypes.contains(type)) {
|
|
||||||
_selectedTypes.remove(type);
|
|
||||||
} else {
|
|
||||||
_selectedTypes.add(type);
|
|
||||||
}
|
|
||||||
_rebuildFiltered();
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetFilters() {
|
|
||||||
_dateRange = null;
|
|
||||||
_selectedStatuses.clear();
|
|
||||||
_selectedTypes.clear();
|
|
||||||
_rebuildFiltered();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onProviderChanged() {
|
|
||||||
_rebuildFiltered();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _rebuildFiltered({bool notify = true}) {
|
|
||||||
final source = _provider?.transactions ?? const <WalletTransaction>[];
|
|
||||||
final activeWalletId = _provider?.walletId;
|
|
||||||
_filteredTransactions = source.where((tx) {
|
|
||||||
final walletMatch =
|
|
||||||
activeWalletId == null || tx.walletId == activeWalletId;
|
|
||||||
final statusMatch =
|
|
||||||
_selectedStatuses.isEmpty || _selectedStatuses.contains(tx.status);
|
|
||||||
final typeMatch =
|
|
||||||
_selectedTypes.isEmpty || _selectedTypes.contains(tx.type);
|
|
||||||
final dateMatch =
|
|
||||||
_dateRange == null ||
|
|
||||||
(tx.date.isAfter(
|
|
||||||
_dateRange!.start.subtract(const Duration(seconds: 1)),
|
|
||||||
) &&
|
|
||||||
tx.date.isBefore(
|
|
||||||
_dateRange!.end.add(const Duration(seconds: 1)),
|
|
||||||
));
|
|
||||||
|
|
||||||
return walletMatch && statusMatch && typeMatch && dateMatch;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
if (notify) notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_provider?.removeListener(_onProviderChanged);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/payment/source.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
import 'package:pshared/models/payment/settlement_mode.dart';
|
import 'package:pshared/models/payment/settlement_mode.dart';
|
||||||
import 'package:pshared/provider/payment/amount.dart';
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
import 'package:pshared/utils/currency.dart';
|
import 'package:pshared/utils/currency.dart';
|
||||||
import 'package:pshared/utils/money.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/models/payment/amount/mode.dart';
|
import 'package:pweb/models/payment/amount/mode.dart';
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ class PaymentAmountFieldController extends ChangeNotifier {
|
|||||||
|
|
||||||
PaymentAmountFieldController({required double? initialAmount})
|
PaymentAmountFieldController({required double? initialAmount})
|
||||||
: textController = TextEditingController(
|
: textController = TextEditingController(
|
||||||
text: initialAmount == null ? '' : amountToString(initialAmount),
|
text: initialAmount == null ? '' : initialAmount.toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
PaymentAmountMode get mode => _mode;
|
PaymentAmountMode get mode => _mode;
|
||||||
@@ -122,18 +123,14 @@ class PaymentAmountFieldController extends ChangeNotifier {
|
|||||||
};
|
};
|
||||||
|
|
||||||
double? _parseAmount(String value) {
|
double? _parseAmount(String value) {
|
||||||
final parsed = parseMoneyAmount(
|
return double.tryParse(value.replaceAll(',', '.').trim());
|
||||||
value.replaceAll(',', '.'),
|
|
||||||
fallback: double.nan,
|
|
||||||
);
|
|
||||||
return parsed.isNaN ? null : parsed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _syncTextWithAmount(double? amount) {
|
void _syncTextWithAmount(double? amount) {
|
||||||
final parsedText = _parseAmount(textController.text);
|
final parsedText = _parseAmount(textController.text);
|
||||||
if (parsedText == amount) return;
|
if (parsedText == amount) return;
|
||||||
|
|
||||||
final nextText = amount == null ? '' : amountToString(amount);
|
final nextText = amount == null ? '' : _formatAmount(amount);
|
||||||
_isSyncingText = true;
|
_isSyncingText = true;
|
||||||
textController.value = TextEditingValue(
|
textController.value = TextEditingValue(
|
||||||
text: nextText,
|
text: nextText,
|
||||||
@@ -142,6 +139,12 @@ class PaymentAmountFieldController extends ChangeNotifier {
|
|||||||
_isSyncingText = false;
|
_isSyncingText = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _formatAmount(double amount) {
|
||||||
|
final currency = money2CurrencyFromCode(activeCurrencyCode);
|
||||||
|
if (currency == null) return amount.toString();
|
||||||
|
return Money.fromNumWithCurrency(amount, currency).toDecimal().toString();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_provider?.removeListener(_handleProviderChanged);
|
_provider?.removeListener(_handleProviderChanged);
|
||||||
|
|||||||
@@ -29,5 +29,4 @@ class RecentPaymentsController extends ChangeNotifier {
|
|||||||
_recent = sortOperations(operations).take(5).toList();
|
_recent = sortOperations(operations).take(5).toList();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/payment/source.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
import 'package:pshared/models/money.dart';
|
|
||||||
import 'package:pshared/models/payment/asset.dart';
|
import 'package:pshared/models/payment/asset.dart';
|
||||||
import 'package:pshared/models/payment/chain_network.dart';
|
import 'package:pshared/models/payment/chain_network.dart';
|
||||||
import 'package:pshared/models/payment/methods/data.dart';
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
@@ -17,6 +18,7 @@ import 'package:pweb/models/payment/multiple_payouts/state.dart';
|
|||||||
import 'package:pweb/providers/multiple_payouts.dart';
|
import 'package:pweb/providers/multiple_payouts.dart';
|
||||||
import 'package:pweb/services/payments/csv_input.dart';
|
import 'package:pweb/services/payments/csv_input.dart';
|
||||||
|
|
||||||
|
|
||||||
class MultiplePayoutsController extends ChangeNotifier {
|
class MultiplePayoutsController extends ChangeNotifier {
|
||||||
final CsvInputService _csvInput;
|
final CsvInputService _csvInput;
|
||||||
MultiplePayoutsProvider? _provider;
|
MultiplePayoutsProvider? _provider;
|
||||||
|
|||||||
@@ -31,10 +31,7 @@ import 'package:pweb/app/app.dart';
|
|||||||
import 'package:pweb/pages/invitations/widgets/list/view_model.dart';
|
import 'package:pweb/pages/invitations/widgets/list/view_model.dart';
|
||||||
import 'package:pweb/app/timeago.dart';
|
import 'package:pweb/app/timeago.dart';
|
||||||
import 'package:pweb/providers/two_factor.dart';
|
import 'package:pweb/providers/two_factor.dart';
|
||||||
import 'package:pweb/controllers/operations/wallet_transactions.dart';
|
|
||||||
import 'package:pweb/providers/wallet_transactions.dart';
|
|
||||||
import 'package:pweb/services/posthog.dart';
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/services/wallet_transactions.dart';
|
|
||||||
import 'package:pweb/providers/account.dart';
|
import 'package:pweb/providers/account.dart';
|
||||||
import 'package:pweb/providers/locale.dart';
|
import 'package:pweb/providers/locale.dart';
|
||||||
|
|
||||||
@@ -142,18 +139,6 @@ void main() async {
|
|||||||
create: (_) => WalletsController(),
|
create: (_) => WalletsController(),
|
||||||
update: (_, wallets, controller) => controller!..update(wallets),
|
update: (_, wallets, controller) => controller!..update(wallets),
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider(
|
|
||||||
create: (_) => WalletTransactionsProvider(
|
|
||||||
MockWalletTransactionsService(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ChangeNotifierProxyProvider<
|
|
||||||
WalletTransactionsProvider,
|
|
||||||
WalletTransactionsController
|
|
||||||
>(
|
|
||||||
create: (_) => WalletTransactionsController(),
|
|
||||||
update: (_, provider, controller) => controller!..update(provider),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: const PayApp(),
|
child: const PayApp(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class WalletTransaction {
|
|||||||
final WalletTransactionType type;
|
final WalletTransactionType type;
|
||||||
final OperationStatus status;
|
final OperationStatus status;
|
||||||
final double amount;
|
final double amount;
|
||||||
final Currency currency;
|
final CurrencyCode currency;
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
final String description;
|
final String description;
|
||||||
final String? counterparty;
|
final String? counterparty;
|
||||||
@@ -56,7 +56,7 @@ class WalletTransaction {
|
|||||||
WalletTransactionType? type,
|
WalletTransactionType? type,
|
||||||
OperationStatus? status,
|
OperationStatus? status,
|
||||||
double? amount,
|
double? amount,
|
||||||
Currency? currency,
|
CurrencyCode? currency,
|
||||||
DateTime? date,
|
DateTime? date,
|
||||||
String? description,
|
String? description,
|
||||||
String? counterparty,
|
String? counterparty,
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import 'package:pshared/models/currency.dart';
|
|||||||
import 'package:pshared/models/payment/chain_network.dart';
|
import 'package:pshared/models/payment/chain_network.dart';
|
||||||
|
|
||||||
|
|
||||||
const Currency managedCurrencyDefault = Currency.usdt;
|
const CurrencyCode managedCurrencyDefault = CurrencyCode.usdt;
|
||||||
const Currency ledgerCurrencyDefault = Currency.rub;
|
const CurrencyCode ledgerCurrencyDefault = CurrencyCode.rub;
|
||||||
const ChainNetwork managedNetworkDefault = ChainNetwork.tronMainnet;
|
const ChainNetwork managedNetworkDefault = ChainNetwork.tronMainnet;
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ import 'package:pshared/models/currency.dart';
|
|||||||
import 'package:pshared/models/describable.dart';
|
import 'package:pshared/models/describable.dart';
|
||||||
import 'package:pshared/models/payment/chain_network.dart';
|
import 'package:pshared/models/payment/chain_network.dart';
|
||||||
import 'package:pshared/models/payment/type.dart';
|
import 'package:pshared/models/payment/type.dart';
|
||||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
|
||||||
import 'package:pshared/provider/accounts/employees.dart';
|
import 'package:pshared/provider/accounts/employees.dart';
|
||||||
import 'package:pshared/provider/ledger.dart';
|
import 'package:pshared/provider/ledger.dart';
|
||||||
import 'package:pshared/provider/payment/wallets.dart';
|
import 'package:pshared/provider/payment/wallets.dart';
|
||||||
import 'package:pshared/utils/currency.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/add/form.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/add/form.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/add/constants.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/add/constants.dart';
|
||||||
@@ -42,9 +40,9 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
|
|||||||
|
|
||||||
PaymentType _assetType = PaymentType.managedWallet;
|
PaymentType _assetType = PaymentType.managedWallet;
|
||||||
String? _ownerRef;
|
String? _ownerRef;
|
||||||
Currency _managedCurrency = managedCurrencyDefault;
|
CurrencyCode _managedCurrency = managedCurrencyDefault;
|
||||||
ChainNetwork _network = managedNetworkDefault;
|
ChainNetwork _network = managedNetworkDefault;
|
||||||
Currency _ledgerCurrency = ledgerCurrencyDefault;
|
CurrencyCode _ledgerCurrency = ledgerCurrencyDefault;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -60,7 +58,7 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
|
|||||||
|
|
||||||
void _setOwnerRef(String? value) => setState(() => _ownerRef = value);
|
void _setOwnerRef(String? value) => setState(() => _ownerRef = value);
|
||||||
|
|
||||||
void _setManagedCurrency(Currency? value) {
|
void _setManagedCurrency(CurrencyCode? value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
setState(() => _managedCurrency = value);
|
setState(() => _managedCurrency = value);
|
||||||
}
|
}
|
||||||
@@ -70,7 +68,7 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
|
|||||||
setState(() => _network = value);
|
setState(() => _network = value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setLedgerCurrency(Currency? value) {
|
void _setLedgerCurrency(CurrencyCode? value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
setState(() => _ledgerCurrency = value);
|
setState(() => _ledgerCurrency = value);
|
||||||
}
|
}
|
||||||
@@ -102,7 +100,8 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
|
|||||||
if (_assetType == PaymentType.managedWallet) {
|
if (_assetType == PaymentType.managedWallet) {
|
||||||
await context.read<WalletsProvider>().create(
|
await context.read<WalletsProvider>().create(
|
||||||
describable: newDescribable(name: name, description: description),
|
describable: newDescribable(name: name, description: description),
|
||||||
asset: ChainAsset(chain: _network, tokenSymbol: currencyCodeToString(_managedCurrency)),
|
chain: _network,
|
||||||
|
currency: _managedCurrency,
|
||||||
ownerRef: owner?.id,
|
ownerRef: owner?.id,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ class AddBalanceForm extends StatelessWidget {
|
|||||||
final ValueChanged<String?> onOwnerChanged;
|
final ValueChanged<String?> onOwnerChanged;
|
||||||
final TextEditingController nameController;
|
final TextEditingController nameController;
|
||||||
final TextEditingController descriptionController;
|
final TextEditingController descriptionController;
|
||||||
final Currency managedCurrency;
|
final CurrencyCode managedCurrency;
|
||||||
final ChainNetwork network;
|
final ChainNetwork network;
|
||||||
final Currency ledgerCurrency;
|
final CurrencyCode ledgerCurrency;
|
||||||
final ValueChanged<Currency?> onManagedCurrencyChanged;
|
final ValueChanged<CurrencyCode?> onManagedCurrencyChanged;
|
||||||
final ValueChanged<ChainNetwork?> onNetworkChanged;
|
final ValueChanged<ChainNetwork?> onNetworkChanged;
|
||||||
final ValueChanged<Currency?> onLedgerCurrencyChanged;
|
final ValueChanged<CurrencyCode?> onLedgerCurrencyChanged;
|
||||||
final bool showEmployeesLoading;
|
final bool showEmployeesLoading;
|
||||||
|
|
||||||
const AddBalanceForm({
|
const AddBalanceForm({
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:pshared/models/currency.dart';
|
|||||||
import 'package:pshared/utils/currency.dart';
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
|
|
||||||
DropdownMenuItem<Currency> currencyItem(Currency currency) => DropdownMenuItem(
|
DropdownMenuItem<CurrencyCode> currencyItem(CurrencyCode currency) => DropdownMenuItem(
|
||||||
value: currency,
|
value: currency,
|
||||||
child: Text(currencyCodeToString(currency)),
|
child: Text(currencyCodeToString(currency)),
|
||||||
);
|
);
|
||||||
@@ -10,8 +10,8 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
|||||||
|
|
||||||
|
|
||||||
class LedgerFields extends StatelessWidget {
|
class LedgerFields extends StatelessWidget {
|
||||||
final Currency currency;
|
final CurrencyCode currency;
|
||||||
final ValueChanged<Currency?>? onCurrencyChanged;
|
final ValueChanged<CurrencyCode?>? onCurrencyChanged;
|
||||||
|
|
||||||
const LedgerFields({
|
const LedgerFields({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -20,7 +20,7 @@ class LedgerFields extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => DropdownButtonFormField<Currency>(
|
Widget build(BuildContext context) => DropdownButtonFormField<CurrencyCode>(
|
||||||
initialValue: currency,
|
initialValue: currency,
|
||||||
decoration: getInputDecoration(context, AppLocalizations.of(context)!.currency, true),
|
decoration: getInputDecoration(context, AppLocalizations.of(context)!.currency, true),
|
||||||
items: [
|
items: [
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
|||||||
|
|
||||||
|
|
||||||
class ManagedWalletFields extends StatelessWidget {
|
class ManagedWalletFields extends StatelessWidget {
|
||||||
final Currency currency;
|
final CurrencyCode currency;
|
||||||
final ChainNetwork network;
|
final ChainNetwork network;
|
||||||
final ValueChanged<Currency?>? onCurrencyChanged;
|
final ValueChanged<CurrencyCode?>? onCurrencyChanged;
|
||||||
final ValueChanged<ChainNetwork?>? onNetworkChanged;
|
final ValueChanged<ChainNetwork?>? onNetworkChanged;
|
||||||
|
|
||||||
const ManagedWalletFields({
|
const ManagedWalletFields({
|
||||||
@@ -31,7 +31,7 @@ class ManagedWalletFields extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
spacing: 12,
|
spacing: 12,
|
||||||
children: [
|
children: [
|
||||||
DropdownButtonFormField<Currency>(
|
DropdownButtonFormField<CurrencyCode>(
|
||||||
initialValue: currency,
|
initialValue: currency,
|
||||||
decoration: getInputDecoration(context, l10n.currency, true),
|
decoration: getInputDecoration(context, l10n.currency, true),
|
||||||
items: [
|
items: [
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||||
import 'package:pshared/models/money.dart';
|
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
import 'package:pshared/utils/currency.dart';
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
@@ -28,12 +27,10 @@ class BalanceAmount extends StatelessWidget {
|
|||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
final currencyBalance = currencyCodeToSymbol(wallet.currency);
|
final currencyBalance = currencyCodeToSymbol(wallet.currency);
|
||||||
final formattedBalance = formatMoneyUi(
|
final formattedBalance = formatAmountUi(
|
||||||
context,
|
context,
|
||||||
Money(
|
amount: wallet.balance,
|
||||||
amount: amountToString(wallet.balance),
|
currency: currencyCodeToString(wallet.currency),
|
||||||
currency: currencyCodeToString(wallet.currency),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
final wallets = context.watch<WalletsController>();
|
final wallets = context.watch<WalletsController>();
|
||||||
final isMasked = wallets.isBalanceMasked(wallet.id);
|
final isMasked = wallets.isBalanceMasked(wallet.id);
|
||||||
|
|||||||
@@ -36,9 +36,7 @@ class PaymentAmountField extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: loc.amount,
|
labelText: loc.amount,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
prefixText: symbol == null
|
prefixText: symbol == null ? null : '$symbol ',
|
||||||
? null
|
|
||||||
: withTrailingNonBreakingSpace(symbol),
|
|
||||||
),
|
),
|
||||||
onChanged: ui.handleChanged,
|
onChanged: ui.handleChanged,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:pshared/utils/currency.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/controllers/payouts/multiple_payouts.dart';
|
import 'package:pweb/controllers/payouts/multiple_payouts.dart';
|
||||||
import 'package:pweb/models/dashboard/summary_values.dart';
|
import 'package:pweb/models/dashboard/summary_values.dart';
|
||||||
import 'package:pweb/pages/dashboard/payouts/summary/widget.dart';
|
import 'package:pweb/pages/dashboard/payouts/summary/widget.dart';
|
||||||
@@ -28,21 +26,12 @@ class SourceQuoteSummary extends StatelessWidget {
|
|||||||
values: PaymentSummaryValues(
|
values: PaymentSummaryValues(
|
||||||
fee: controller.aggregateFeeAmount == null
|
fee: controller.aggregateFeeAmount == null
|
||||||
? l10n.noFee
|
? l10n.noFee
|
||||||
: formatMoneyUiWithL10n(
|
: formatMoneyUi(context, controller.aggregateFeeAmount),
|
||||||
l10n,
|
recipientReceives: formatMoneyUi(
|
||||||
controller.aggregateFeeAmount,
|
context,
|
||||||
separator: nonBreakingSpace,
|
|
||||||
),
|
|
||||||
recipientReceives: formatMoneyUiWithL10n(
|
|
||||||
l10n,
|
|
||||||
controller.aggregateSettlementAmount,
|
controller.aggregateSettlementAmount,
|
||||||
separator: nonBreakingSpace,
|
|
||||||
),
|
|
||||||
total: formatMoneyUiWithL10n(
|
|
||||||
l10n,
|
|
||||||
controller.aggregateDebitAmount,
|
|
||||||
separator: nonBreakingSpace,
|
|
||||||
),
|
),
|
||||||
|
total: formatMoneyUi(context, controller.aggregateDebitAmount),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ class UploadHistorySection extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProxyProvider<PaymentsProvider, RecentPaymentsController>(
|
return ChangeNotifierProxyProvider<
|
||||||
|
PaymentsProvider,
|
||||||
|
RecentPaymentsController
|
||||||
|
>(
|
||||||
create: (_) => RecentPaymentsController(),
|
create: (_) => RecentPaymentsController(),
|
||||||
update: (_, payments, controller) => controller!..update(payments),
|
update: (_, payments, controller) => controller!..update(payments),
|
||||||
child: const _RecentPaymentsView(),
|
child: const _RecentPaymentsView(),
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentFeeRow extends StatelessWidget {
|
class PaymentFeeRow extends StatelessWidget {
|
||||||
const PaymentFeeRow({super.key});
|
const PaymentFeeRow({super.key});
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ class PaymentFeeRow extends StatelessWidget {
|
|||||||
final l10 = AppLocalizations.of(context)!;
|
final l10 = AppLocalizations.of(context)!;
|
||||||
return PaymentSummaryRow(
|
return PaymentSummaryRow(
|
||||||
labelFactory: l10.fee,
|
labelFactory: l10.fee,
|
||||||
asset: fee,
|
money: fee,
|
||||||
value: fee == null ? l10.noFee : null,
|
value: fee == null ? l10.noFee : null,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentRecipientReceivesRow extends StatelessWidget {
|
class PaymentRecipientReceivesRow extends StatelessWidget {
|
||||||
const PaymentRecipientReceivesRow({super.key});
|
const PaymentRecipientReceivesRow({super.key});
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ class PaymentRecipientReceivesRow extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) => Consumer<QuotationProvider>(
|
Widget build(BuildContext context) => Consumer<QuotationProvider>(
|
||||||
builder: (context, provider, _) => PaymentSummaryRow(
|
builder: (context, provider, _) => PaymentSummaryRow(
|
||||||
labelFactory: AppLocalizations.of(context)!.recipientWillReceive,
|
labelFactory: AppLocalizations.of(context)!.recipientWillReceive,
|
||||||
asset: provider.recipientGets,
|
money: provider.recipientGets,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:money2/money2.dart';
|
||||||
import 'package:pshared/models/asset.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/utils/money_display.dart';
|
import 'package:pweb/utils/money_display.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentSummaryRow extends StatelessWidget {
|
class PaymentSummaryRow extends StatelessWidget {
|
||||||
final String Function(String) labelFactory;
|
final String Function(String) labelFactory;
|
||||||
final Asset? asset;
|
final Money? money;
|
||||||
final String? value;
|
final String? value;
|
||||||
final TextStyle? style;
|
final TextStyle? style;
|
||||||
|
|
||||||
const PaymentSummaryRow({
|
const PaymentSummaryRow({
|
||||||
super.key,
|
super.key,
|
||||||
required this.labelFactory,
|
required this.labelFactory,
|
||||||
required this.asset,
|
required this.money,
|
||||||
this.value,
|
this.value,
|
||||||
this.style,
|
this.style,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final formatted = value ?? formatAssetUi(context, asset);
|
final formatted = value ?? formatMoneyUi(context, money);
|
||||||
return Text(labelFactory(formatted), style: style);
|
return Text(labelFactory(formatted), style: style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentTotalRow extends StatelessWidget {
|
class PaymentTotalRow extends StatelessWidget {
|
||||||
const PaymentTotalRow({super.key});
|
const PaymentTotalRow({super.key});
|
||||||
|
|
||||||
@@ -16,8 +15,10 @@ class PaymentTotalRow extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) => Consumer<QuotationProvider>(
|
Widget build(BuildContext context) => Consumer<QuotationProvider>(
|
||||||
builder: (context, provider, _) => PaymentSummaryRow(
|
builder: (context, provider, _) => PaymentSummaryRow(
|
||||||
labelFactory: AppLocalizations.of(context)!.total,
|
labelFactory: AppLocalizations.of(context)!.total,
|
||||||
asset: provider.total,
|
money: provider.total,
|
||||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600),
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,16 +8,11 @@ import 'package:pweb/pages/dashboard/payouts/summary/total.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentSummary extends StatelessWidget {
|
class PaymentSummary extends StatelessWidget {
|
||||||
final double spacing;
|
final double spacing;
|
||||||
final PaymentSummaryValues? values;
|
final PaymentSummaryValues? values;
|
||||||
|
|
||||||
const PaymentSummary({
|
const PaymentSummary({super.key, required this.spacing, this.values});
|
||||||
super.key,
|
|
||||||
required this.spacing,
|
|
||||||
this.values,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -32,20 +27,20 @@ class PaymentSummary extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
PaymentSummaryRow(
|
PaymentSummaryRow(
|
||||||
labelFactory: loc.fee,
|
labelFactory: loc.fee,
|
||||||
asset: null,
|
money: null,
|
||||||
value: resolvedValues.fee,
|
value: resolvedValues.fee,
|
||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
PaymentSummaryRow(
|
PaymentSummaryRow(
|
||||||
labelFactory: loc.recipientWillReceive,
|
labelFactory: loc.recipientWillReceive,
|
||||||
asset: null,
|
money: null,
|
||||||
value: resolvedValues.recipientReceives,
|
value: resolvedValues.recipientReceives,
|
||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
SizedBox(height: spacing),
|
SizedBox(height: spacing),
|
||||||
PaymentSummaryRow(
|
PaymentSummaryRow(
|
||||||
labelFactory: loc.total,
|
labelFactory: loc.total,
|
||||||
asset: null,
|
money: null,
|
||||||
value: resolvedValues.total,
|
value: resolvedValues.total,
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||||
import 'package:pshared/utils/currency.dart';
|
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
|
|
||||||
import 'package:pweb/models/state/visibility.dart';
|
import 'package:pweb/models/state/visibility.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/amount.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/amount.dart';
|
||||||
|
import 'package:pweb/pages/payout_page/wallet/currency_symbol_avatar.dart';
|
||||||
import 'package:pweb/widgets/refresh_balance/wallet.dart';
|
import 'package:pweb/widgets/refresh_balance/wallet.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
@@ -35,10 +35,7 @@ class WalletCard extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
spacing: 3,
|
spacing: 3,
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CurrencySymbolAvatar(wallet: wallet),
|
||||||
radius: 24,
|
|
||||||
child: Icon(iconForCurrencyType(wallet.currency), size: 28),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -51,7 +48,9 @@ class WalletCard extends StatelessWidget {
|
|||||||
BalanceAmount(
|
BalanceAmount(
|
||||||
wallet: wallet,
|
wallet: wallet,
|
||||||
onToggleMask: () {
|
onToggleMask: () {
|
||||||
context.read<WalletsController>().toggleBalanceMask(wallet.id);
|
context.read<WalletsController>().toggleBalanceMask(
|
||||||
|
wallet.id,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
WalletBalanceRefreshButton(
|
WalletBalanceRefreshButton(
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class CurrencySymbolAvatar extends StatelessWidget {
|
||||||
|
final Wallet wallet;
|
||||||
|
|
||||||
|
const CurrencySymbolAvatar({super.key, required this.wallet});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final code = currencyCodeToString(wallet.currency);
|
||||||
|
final symbol = currencySymbolFromCode(code) ?? code;
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return CircleAvatar(
|
||||||
|
radius: 24,
|
||||||
|
child: Text(
|
||||||
|
symbol,
|
||||||
|
style: textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,14 +17,10 @@ class LedgerBalanceFormatter {
|
|||||||
final currency = account.currency.trim();
|
final currency = account.currency.trim();
|
||||||
if (currency.isEmpty) return '••••';
|
if (currency.isEmpty) return '••••';
|
||||||
|
|
||||||
try {
|
final symbol = currencySymbolFromCode(currency);
|
||||||
final symbol = currencyCodeToSymbol(currencyStringToCode(currency));
|
if (symbol == null || symbol.trim().isEmpty) {
|
||||||
if (symbol.trim().isEmpty) {
|
|
||||||
return '•••• $currency';
|
|
||||||
}
|
|
||||||
return '•••• $symbol';
|
|
||||||
} catch (_) {
|
|
||||||
return '•••• $currency';
|
return '•••• $currency';
|
||||||
}
|
}
|
||||||
|
return '•••• $symbol';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:pshared/models/payment/status.dart';
|
|
||||||
import 'package:pshared/utils/localization.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/models/wallet/wallet_transaction.dart';
|
|
||||||
import 'package:pweb/controllers/operations/wallet_transactions.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class WalletHistoryFilters extends StatelessWidget {
|
|
||||||
final WalletTransactionsController provider;
|
|
||||||
final VoidCallback onPickRange;
|
|
||||||
|
|
||||||
const WalletHistoryFilters({
|
|
||||||
super.key,
|
|
||||||
required this.provider,
|
|
||||||
required this.onPickRange,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
final loc = AppLocalizations.of(context)!;
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
elevation: 2,
|
|
||||||
color: theme.colorScheme.onSecondary,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
loc.walletActivity,
|
|
||||||
style: theme.textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
if (provider.hasFilters)
|
|
||||||
TextButton(
|
|
||||||
onPressed: provider.resetFilters,
|
|
||||||
child: Text(loc.reset),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: WalletTransactionType.values.map((type) {
|
|
||||||
final isSelected = provider.selectedTypes.contains(type);
|
|
||||||
return FilterChip(
|
|
||||||
label: Text(type.label(context)),
|
|
||||||
selected: isSelected,
|
|
||||||
onSelected: (_) => provider.toggleType(type),
|
|
||||||
pressElevation: 0,
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: OperationStatus.values.map((status) {
|
|
||||||
final isSelected = provider.selectedStatuses.contains(status);
|
|
||||||
return FilterChip(
|
|
||||||
label: Text(status.localized(context)),
|
|
||||||
selected: isSelected,
|
|
||||||
onSelected: (_) => provider.toggleStatus(status),
|
|
||||||
pressElevation: 0,
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: OutlinedButton.icon(
|
|
||||||
onPressed: onPickRange,
|
|
||||||
icon: const Icon(Icons.date_range_outlined),
|
|
||||||
label: Text(
|
|
||||||
provider.dateRange == null
|
|
||||||
? loc.selectPeriod
|
|
||||||
: '${dateToLocalFormat(context, provider.dateRange!.start)} – ${dateToLocalFormat(context, provider.dateRange!.end)}',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,17 +23,12 @@ List<Widget> buildOperationCardItems(
|
|||||||
if (items.isNotEmpty) {
|
if (items.isNotEmpty) {
|
||||||
items.add(const SizedBox(height: 16));
|
items.add(const SizedBox(height: 16));
|
||||||
}
|
}
|
||||||
items.add(_DateHeader(
|
items.add(_DateHeader(label: _dateLabel(context, operation.date, loc)));
|
||||||
label: _dateLabel(context, operation.date, loc),
|
|
||||||
));
|
|
||||||
items.add(const SizedBox(height: 8));
|
items.add(const SizedBox(height: 8));
|
||||||
currentKey = dateKey;
|
currentKey = dateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
items.add(OperationCard(
|
items.add(OperationCard(operation: operation, onTap: onTap));
|
||||||
operation: operation,
|
|
||||||
onTap: onTap,
|
|
||||||
));
|
|
||||||
items.add(const SizedBox(height: 12));
|
items.add(const SizedBox(height: 12));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,9 +61,7 @@ class _DateHeader extends StatelessWidget {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Text(
|
return Text(
|
||||||
label,
|
label,
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:pshared/models/payment/payment.dart';
|
|||||||
import 'package:pweb/pages/report/details/sections/fx.dart';
|
import 'package:pweb/pages/report/details/sections/fx.dart';
|
||||||
import 'package:pweb/pages/report/details/sections/operations/section.dart';
|
import 'package:pweb/pages/report/details/sections/operations/section.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentDetailsSections extends StatelessWidget {
|
class PaymentDetailsSections extends StatelessWidget {
|
||||||
final Payment payment;
|
final Payment payment;
|
||||||
final bool Function(PaymentExecutionOperation operation)?
|
final bool Function(PaymentExecutionOperation operation)?
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/money.dart';
|
|
||||||
import 'package:pshared/models/payment/payment.dart';
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
import 'package:pshared/models/payment/fx/quote.dart';
|
import 'package:pshared/models/payment/fx/quote.dart';
|
||||||
import 'package:pshared/utils/currency.dart';
|
import 'package:pshared/utils/currency.dart';
|
||||||
@@ -33,39 +32,36 @@ class PaymentFxSection extends StatelessWidget {
|
|||||||
final price = fx.price?.trim();
|
final price = fx.price?.trim();
|
||||||
if (price == null || price.isEmpty) return null;
|
if (price == null || price.isEmpty) return null;
|
||||||
|
|
||||||
final baseCurrency = _firstNonEmpty([
|
final baseCurrency = _resolveCurrencyCode([
|
||||||
fx.baseCurrency,
|
fx.baseCurrency,
|
||||||
fx.baseAmount?.currency,
|
fx.baseAmount?.currency.isoCode,
|
||||||
currencySymbolFromCode(fx.baseCurrency),
|
|
||||||
currencySymbolFromCode(fx.baseAmount?.currency),
|
|
||||||
]);
|
]);
|
||||||
final quoteCurrency = _firstNonEmpty([
|
final quoteCurrency = _resolveCurrencyCode([
|
||||||
fx.quoteCurrency,
|
fx.quoteCurrency,
|
||||||
fx.quoteAmount?.currency,
|
fx.quoteAmount?.currency.isoCode,
|
||||||
currencySymbolFromCode(fx.quoteCurrency),
|
|
||||||
currencySymbolFromCode(fx.quoteAmount?.currency),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (baseCurrency == null || quoteCurrency == null) return price;
|
if (baseCurrency == null || quoteCurrency == null) return price;
|
||||||
|
|
||||||
final baseDisplay = formatMoneyDisplay(
|
final baseDisplay =
|
||||||
Money(amount: '1', currency: baseCurrency),
|
parseMoneyWithCurrencyCode('1', baseCurrency)?.toString() ??
|
||||||
fallback: '1 $baseCurrency',
|
'1 $baseCurrency';
|
||||||
invalidAmountFallback: '1',
|
final quoteDisplay =
|
||||||
);
|
parseMoneyWithCurrencyCode(
|
||||||
final quoteDisplay = formatMoneyDisplay(
|
_normalizeAmount(price),
|
||||||
Money(amount: _normalizeAmount(price), currency: quoteCurrency),
|
quoteCurrency,
|
||||||
fallback: '$price $quoteCurrency',
|
)?.toString() ??
|
||||||
invalidAmountFallback: price,
|
'$price $quoteCurrency';
|
||||||
);
|
|
||||||
|
|
||||||
return '$baseDisplay = $quoteDisplay';
|
return '$baseDisplay = $quoteDisplay';
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _firstNonEmpty(List<String?> values) {
|
String? _resolveCurrencyCode(List<String?> values) {
|
||||||
for (final value in values) {
|
for (final value in values) {
|
||||||
final trimmed = value?.trim();
|
if (value == null || value.isEmpty) continue;
|
||||||
if (trimmed != null && trimmed.isNotEmpty) return trimmed;
|
final resolved = money2CurrencyFromCode(value);
|
||||||
|
if (resolved != null) return resolved.isoCode;
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,11 @@ import 'package:pweb/pages/report/details/summary_card/info_line.dart';
|
|||||||
import 'package:pweb/pages/report/table/badge.dart';
|
import 'package:pweb/pages/report/table/badge.dart';
|
||||||
import 'package:pweb/utils/report/amount_parts.dart';
|
import 'package:pweb/utils/report/amount_parts.dart';
|
||||||
import 'package:pweb/utils/report/format.dart';
|
import 'package:pweb/utils/report/format.dart';
|
||||||
import 'package:pweb/utils/money_display.dart';
|
|
||||||
import 'package:pweb/utils/report/payment_mapper.dart';
|
import 'package:pweb/utils/report/payment_mapper.dart';
|
||||||
import 'package:pweb/utils/clipboard.dart';
|
import 'package:pweb/utils/clipboard.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentSummaryCard extends StatelessWidget {
|
class PaymentSummaryCard extends StatelessWidget {
|
||||||
final Payment payment;
|
final Payment payment;
|
||||||
|
|
||||||
@@ -25,7 +23,7 @@ class PaymentSummaryCard extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final unavailableValue = unavailableMoneyValue(context);
|
final unavailableValue = loc.valueUnavailable;
|
||||||
final status = statusFromPayment(payment);
|
final status = statusFromPayment(payment);
|
||||||
final dateLabel = formatDateLabel(context, resolvePaymentDate(payment));
|
final dateLabel = formatDateLabel(context, resolvePaymentDate(payment));
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ Future<void> pickOperationsRange(
|
|||||||
ReportOperationsController controller,
|
ReportOperationsController controller,
|
||||||
) async {
|
) async {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final initial = controller.selectedRange ??
|
final initial =
|
||||||
DateTimeRange(
|
controller.selectedRange ??
|
||||||
start: now.subtract(const Duration(days: 30)),
|
DateTimeRange(start: now.subtract(const Duration(days: 30)), end: now);
|
||||||
end: now,
|
|
||||||
);
|
|
||||||
|
|
||||||
final picked = await showDateRangePicker(
|
final picked = await showDateRangePicker(
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/money.dart';
|
|
||||||
import 'package:pshared/models/payment/operation.dart';
|
import 'package:pshared/models/payment/operation.dart';
|
||||||
import 'package:pshared/models/payment/status.dart';
|
import 'package:pshared/models/payment/status.dart';
|
||||||
import 'package:pshared/utils/currency.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/pages/report/table/badge.dart';
|
import 'package:pweb/pages/report/table/badge.dart';
|
||||||
import 'package:pweb/utils/money_display.dart';
|
import 'package:pweb/utils/money_display.dart';
|
||||||
@@ -38,13 +36,15 @@ class OperationRow {
|
|||||||
label: Text(loc.downloadAct),
|
label: Text(loc.downloadAct),
|
||||||
)
|
)
|
||||||
: Text(op.fileName ?? '');
|
: Text(op.fileName ?? '');
|
||||||
final amountLabel = formatMoneyUiWithL10n(
|
final amountLabel = formatAmountUi(
|
||||||
loc,
|
context,
|
||||||
Money(amount: amountToString(op.amount), currency: op.currency),
|
amount: op.amount,
|
||||||
|
currency: op.currency,
|
||||||
);
|
);
|
||||||
final toAmountLabel = formatMoneyUiWithL10n(
|
final toAmountLabel = formatAmountUi(
|
||||||
loc,
|
context,
|
||||||
Money(amount: amountToString(op.toAmount), currency: op.toCurrency),
|
amount: op.toAmount,
|
||||||
|
currency: op.toCurrency,
|
||||||
);
|
);
|
||||||
|
|
||||||
return DataRow(
|
return DataRow(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/money.dart';
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/payment/payment.dart';
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
import 'package:pshared/models/payment/quote/quote.dart';
|
import 'package:pshared/models/payment/quote/quote.dart';
|
||||||
import 'package:pshared/models/payment/quote/status_type.dart';
|
import 'package:pshared/models/payment/quote/status_type.dart';
|
||||||
@@ -89,14 +90,16 @@ class MultiplePayoutsProvider extends ChangeNotifier {
|
|||||||
Money? get requestedSentAmount {
|
Money? get requestedSentAmount {
|
||||||
if (_rows.isEmpty) return null;
|
if (_rows.isEmpty) return null;
|
||||||
const currency = 'RUB';
|
const currency = 'RUB';
|
||||||
|
final rubCurrency = money2CurrencyFromCode(currency);
|
||||||
|
if (rubCurrency == null) return null;
|
||||||
|
|
||||||
double total = 0;
|
Money? total;
|
||||||
for (final row in _rows) {
|
for (final row in _rows) {
|
||||||
final value = parseMoneyAmount(row.amount, fallback: double.nan);
|
final value = parseMoneyWithCurrency(row.amount, rubCurrency);
|
||||||
if (value.isNaN) return null;
|
if (value == null) return null;
|
||||||
total += value;
|
total = total == null ? value : total + value;
|
||||||
}
|
}
|
||||||
return Money(amount: amountToString(total), currency: currency);
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
Money? aggregateSettlementAmountForCurrency(String? sourceCurrencyCode) {
|
Money? aggregateSettlementAmountForCurrency(String? sourceCurrencyCode) {
|
||||||
@@ -118,11 +121,11 @@ class MultiplePayoutsProvider extends ChangeNotifier {
|
|||||||
final fee = aggregateFeeAmountForCurrency(sourceCurrencyCode);
|
final fee = aggregateFeeAmountForCurrency(sourceCurrencyCode);
|
||||||
if (debit == null || fee == null) return null;
|
if (debit == null || fee == null) return null;
|
||||||
|
|
||||||
final debitValue = parseMoneyAmount(debit.amount, fallback: double.nan);
|
final debitValue = debit;
|
||||||
final feeValue = parseMoneyAmount(fee.amount, fallback: double.nan);
|
final feeValue = fee;
|
||||||
if (debit.currency.toUpperCase() != fee.currency.toUpperCase()) return null;
|
if (debitValue.currency.isoCode != feeValue.currency.isoCode) return null;
|
||||||
if (debitValue.isNaN || feeValue.isNaN || debitValue <= 0) return null;
|
if (!debitValue.isPositive) return null;
|
||||||
return (feeValue / debitValue) * 100;
|
return (feeValue.toDecimal() / debitValue.toDecimal()).toDouble() * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> quoteFromCsv({
|
Future<void> quoteFromCsv({
|
||||||
@@ -279,8 +282,8 @@ class MultiplePayoutsProvider extends ChangeNotifier {
|
|||||||
final sentAmount = requestedSentAmount;
|
final sentAmount = requestedSentAmount;
|
||||||
if (sentAmount == null) return null;
|
if (sentAmount == null) return null;
|
||||||
return <String, String>{
|
return <String, String>{
|
||||||
UploadMetadataKeys.amount: sentAmount.amount,
|
UploadMetadataKeys.amount: sentAmount.toDecimal().toString(),
|
||||||
UploadMetadataKeys.currency: sentAmount.currency,
|
UploadMetadataKeys.currency: sentAmount.currency.isoCode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,10 +293,12 @@ class MultiplePayoutsProvider extends ChangeNotifier {
|
|||||||
) {
|
) {
|
||||||
if (values == null || values.isEmpty) return null;
|
if (values == null || values.isEmpty) return null;
|
||||||
|
|
||||||
if (sourceCurrencyCode != null && sourceCurrencyCode.isNotEmpty) {
|
final sourceCurrency =
|
||||||
final sourceCurrency = sourceCurrencyCode.trim().toUpperCase();
|
money2CurrencyFromCode(sourceCurrencyCode)?.isoCode ??
|
||||||
|
sourceCurrencyCode;
|
||||||
|
if (sourceCurrency != null) {
|
||||||
for (final value in values) {
|
for (final value in values) {
|
||||||
if (value.currency.toUpperCase() == sourceCurrency) {
|
if (value.currency.isoCode == sourceCurrency) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/models/wallet/wallet_transaction.dart';
|
|
||||||
import 'package:pweb/services/wallet_transactions.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class WalletTransactionsProvider extends ChangeNotifier {
|
|
||||||
final WalletTransactionsService _service;
|
|
||||||
|
|
||||||
WalletTransactionsProvider(this._service);
|
|
||||||
|
|
||||||
List<WalletTransaction> _transactions = const [];
|
|
||||||
bool _isLoading = false;
|
|
||||||
String? _error;
|
|
||||||
String? _walletId;
|
|
||||||
int _loadSeq = 0;
|
|
||||||
|
|
||||||
List<WalletTransaction> get transactions => List.unmodifiable(_transactions);
|
|
||||||
bool get isLoading => _isLoading;
|
|
||||||
String? get error => _error;
|
|
||||||
String? get walletId => _walletId;
|
|
||||||
|
|
||||||
Future<void> load({String? walletId}) async {
|
|
||||||
final targetWalletId = walletId ?? _walletId;
|
|
||||||
final requestSeq = ++_loadSeq;
|
|
||||||
_walletId = targetWalletId;
|
|
||||||
_isLoading = true;
|
|
||||||
_error = null;
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
try {
|
|
||||||
final fetched = await _service.fetchHistory(walletId: targetWalletId);
|
|
||||||
if (requestSeq != _loadSeq) return;
|
|
||||||
|
|
||||||
_transactions = targetWalletId == null
|
|
||||||
? fetched
|
|
||||||
: fetched.where((tx) => tx.walletId == targetWalletId).toList();
|
|
||||||
} catch (e) {
|
|
||||||
if (requestSeq != _loadSeq) return;
|
|
||||||
_error = e.toString();
|
|
||||||
} finally {
|
|
||||||
if (requestSeq == _loadSeq) {
|
|
||||||
_isLoading = false;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import 'package:pshared/models/payment/status.dart';
|
|
||||||
import 'package:pshared/models/currency.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/models/wallet/wallet_transaction.dart';
|
|
||||||
|
|
||||||
|
|
||||||
abstract class WalletTransactionsService {
|
|
||||||
Future<List<WalletTransaction>> fetchHistory({String? walletId});
|
|
||||||
}
|
|
||||||
|
|
||||||
class MockWalletTransactionsService implements WalletTransactionsService {
|
|
||||||
final List<WalletTransaction> _history = [
|
|
||||||
WalletTransaction(
|
|
||||||
id: 'wt-001',
|
|
||||||
walletId: '1124',
|
|
||||||
type: WalletTransactionType.topUp,
|
|
||||||
status: OperationStatus.success,
|
|
||||||
amount: 150000,
|
|
||||||
currency: Currency.rub,
|
|
||||||
date: DateTime(2024, 9, 14, 10, 12),
|
|
||||||
counterparty: 'VISA ••0019',
|
|
||||||
description: 'Top up via corporate card',
|
|
||||||
balanceAfter: 10150000,
|
|
||||||
),
|
|
||||||
WalletTransaction(
|
|
||||||
id: 'wt-002',
|
|
||||||
walletId: '1124',
|
|
||||||
type: WalletTransactionType.payout,
|
|
||||||
status: OperationStatus.processing,
|
|
||||||
amount: 2500,
|
|
||||||
currency: Currency.rub,
|
|
||||||
date: DateTime(2024, 9, 15, 12, 10),
|
|
||||||
counterparty: 'Bank transfer RU239',
|
|
||||||
description: 'Invoice #239 shipping',
|
|
||||||
balanceAfter: 10147500,
|
|
||||||
),
|
|
||||||
WalletTransaction(
|
|
||||||
id: 'wt-003',
|
|
||||||
walletId: '1124',
|
|
||||||
type: WalletTransactionType.payout,
|
|
||||||
status: OperationStatus.error,
|
|
||||||
amount: 1200,
|
|
||||||
currency: Currency.rub,
|
|
||||||
date: DateTime(2024, 9, 13, 16, 40),
|
|
||||||
counterparty: '4000 **** 0077',
|
|
||||||
description: 'Payout to card declined',
|
|
||||||
balanceAfter: 10000000,
|
|
||||||
),
|
|
||||||
WalletTransaction(
|
|
||||||
id: 'wt-004',
|
|
||||||
walletId: '2124',
|
|
||||||
type: WalletTransactionType.topUp,
|
|
||||||
status: OperationStatus.success,
|
|
||||||
amount: 1800,
|
|
||||||
currency: Currency.usd,
|
|
||||||
date: DateTime(2024, 9, 12, 9, 0),
|
|
||||||
counterparty: 'Wire payment 8831',
|
|
||||||
description: 'Top up via USD wire',
|
|
||||||
balanceAfter: 4300.5,
|
|
||||||
),
|
|
||||||
WalletTransaction(
|
|
||||||
id: 'wt-005',
|
|
||||||
walletId: '2124',
|
|
||||||
type: WalletTransactionType.payout,
|
|
||||||
status: OperationStatus.success,
|
|
||||||
amount: 400,
|
|
||||||
currency: Currency.usd,
|
|
||||||
date: DateTime(2024, 9, 16, 14, 30),
|
|
||||||
counterparty: 'IBAN DE09••1122',
|
|
||||||
description: 'Payout to John Snow',
|
|
||||||
balanceAfter: 3900.5,
|
|
||||||
),
|
|
||||||
WalletTransaction(
|
|
||||||
id: 'wt-006',
|
|
||||||
walletId: '1124',
|
|
||||||
type: WalletTransactionType.payout,
|
|
||||||
status: OperationStatus.success,
|
|
||||||
amount: 70000,
|
|
||||||
currency: Currency.rub,
|
|
||||||
date: DateTime(2024, 9, 17, 8, 45),
|
|
||||||
counterparty: 'Payroll batch',
|
|
||||||
description: 'Monthly reimbursements',
|
|
||||||
balanceAfter: 10080000,
|
|
||||||
),
|
|
||||||
WalletTransaction(
|
|
||||||
id: 'wt-007',
|
|
||||||
walletId: '1124',
|
|
||||||
type: WalletTransactionType.topUp,
|
|
||||||
status: OperationStatus.processing,
|
|
||||||
amount: 200000,
|
|
||||||
currency: Currency.rub,
|
|
||||||
date: DateTime(2024, 9, 18, 9, 30),
|
|
||||||
counterparty: 'Bank wire RU511',
|
|
||||||
description: 'Top up pending confirmation',
|
|
||||||
balanceAfter: 10280000,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<WalletTransaction>> fetchHistory({String? walletId}) async {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 350));
|
|
||||||
|
|
||||||
final source = walletId == null
|
|
||||||
? _history
|
|
||||||
: _history.where((tx) => tx.walletId == walletId).toList();
|
|
||||||
|
|
||||||
return List<WalletTransaction>.from(source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +1,27 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/asset.dart';
|
import 'package:money2/money2.dart';
|
||||||
import 'package:pshared/models/money.dart';
|
|
||||||
import 'package:pshared/utils/currency.dart';
|
import 'package:pshared/utils/currency.dart';
|
||||||
import 'package:pshared/utils/money.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
String unavailableMoneyValue(BuildContext context) {
|
String formatMoneyUi(BuildContext context, Money? money) {
|
||||||
return AppLocalizations.of(context)!.valueUnavailable;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
}
|
if (money == null) return l10n.valueUnavailable;
|
||||||
|
return money.toString();
|
||||||
String unavailableMoneyValueFromL10n(AppLocalizations l10n) {
|
|
||||||
return l10n.valueUnavailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
String formatMoneyUi(
|
|
||||||
BuildContext context,
|
|
||||||
Money? money, {
|
|
||||||
String separator = ' ',
|
|
||||||
}) {
|
|
||||||
return formatMoneyUiWithL10n(
|
|
||||||
AppLocalizations.of(context)!,
|
|
||||||
money,
|
|
||||||
separator: separator,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String formatMoneyUiWithL10n(
|
|
||||||
AppLocalizations l10n,
|
|
||||||
Money? money, {
|
|
||||||
String separator = ' ',
|
|
||||||
}) {
|
|
||||||
final unavailableValue = unavailableMoneyValueFromL10n(l10n);
|
|
||||||
return formatMoneyDisplay(
|
|
||||||
money,
|
|
||||||
fallback: unavailableValue,
|
|
||||||
invalidAmountFallback: unavailableValue,
|
|
||||||
separator: separator,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String formatAmountUi(
|
String formatAmountUi(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required double amount,
|
required double amount,
|
||||||
required String currency,
|
required String currency,
|
||||||
String separator = ' ',
|
|
||||||
}) {
|
}) {
|
||||||
return formatAmountUiWithL10n(
|
final l10n = AppLocalizations.of(context)!;
|
||||||
AppLocalizations.of(context)!,
|
final moneyCurrency = money2CurrencyFromCode(currency);
|
||||||
amount: amount,
|
if (moneyCurrency == null) return l10n.valueUnavailable;
|
||||||
currency: currency,
|
|
||||||
separator: separator,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String formatAmountUiWithL10n(
|
final money = Money.fromNumWithCurrency(amount, moneyCurrency);
|
||||||
AppLocalizations l10n, {
|
return money.toString();
|
||||||
required double amount,
|
|
||||||
required String currency,
|
|
||||||
String separator = ' ',
|
|
||||||
}) {
|
|
||||||
return formatMoneyUiWithL10n(
|
|
||||||
l10n,
|
|
||||||
Money(amount: amountToString(amount), currency: currency),
|
|
||||||
separator: separator,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String formatAssetUi(
|
|
||||||
BuildContext context,
|
|
||||||
Asset? asset, {
|
|
||||||
String separator = ' ',
|
|
||||||
}) {
|
|
||||||
return formatAssetUiWithL10n(
|
|
||||||
AppLocalizations.of(context)!,
|
|
||||||
asset,
|
|
||||||
separator: separator,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String formatAssetUiWithL10n(
|
|
||||||
AppLocalizations l10n,
|
|
||||||
Asset? asset, {
|
|
||||||
String separator = ' ',
|
|
||||||
}) {
|
|
||||||
if (asset == null) return unavailableMoneyValueFromL10n(l10n);
|
|
||||||
return formatAmountUiWithL10n(
|
|
||||||
l10n,
|
|
||||||
amount: asset.amount,
|
|
||||||
currency: currencyCodeToString(asset.currency),
|
|
||||||
separator: separator,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'package:pshared/utils/money.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
|
import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -82,8 +80,8 @@ class MultipleCsvParser {
|
|||||||
throw FormatException('CSV row ${i + 1}: amount is required');
|
throw FormatException('CSV row ${i + 1}: amount is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
final parsedAmount = parseMoneyAmount(amount, fallback: double.nan);
|
final parsedAmount = double.tryParse(amount);
|
||||||
if (parsedAmount.isNaN || parsedAmount <= 0) {
|
if (parsedAmount == null || parsedAmount <= 0) {
|
||||||
throw FormatException(
|
throw FormatException(
|
||||||
'CSV row ${i + 1}: amount must be greater than 0',
|
'CSV row ${i + 1}: amount must be greater than 0',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:pshared/models/money.dart';
|
|
||||||
import 'package:pshared/models/payment/customer.dart';
|
import 'package:pshared/models/payment/customer.dart';
|
||||||
import 'package:pshared/models/payment/fees/treatment.dart';
|
import 'package:pshared/models/payment/fees/treatment.dart';
|
||||||
import 'package:pshared/models/payment/intent.dart';
|
import 'package:pshared/models/payment/intent.dart';
|
||||||
@@ -6,6 +5,7 @@ import 'package:pshared/models/payment/kind.dart';
|
|||||||
import 'package:pshared/models/payment/methods/card.dart';
|
import 'package:pshared/models/payment/methods/card.dart';
|
||||||
import 'package:pshared/models/payment/methods/data.dart';
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
import 'package:pshared/models/payment/settlement_mode.dart';
|
import 'package:pshared/models/payment/settlement_mode.dart';
|
||||||
|
import 'package:pshared/utils/money.dart';
|
||||||
import 'package:pshared/utils/payment/fx_helpers.dart';
|
import 'package:pshared/utils/payment/fx_helpers.dart';
|
||||||
|
|
||||||
import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
|
import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
|
||||||
@@ -30,7 +30,12 @@ class MultipleIntentBuilder {
|
|||||||
.map((entry) {
|
.map((entry) {
|
||||||
final rowIndex = entry.key;
|
final rowIndex = entry.key;
|
||||||
final row = entry.value;
|
final row = entry.value;
|
||||||
final amount = Money(amount: row.amount, currency: _currency);
|
final amount = parseMoneyWithCurrencyCode(row.amount, _currency);
|
||||||
|
if (amount == null) {
|
||||||
|
throw FormatException(
|
||||||
|
'Invalid CSV amount at row ${rowIndex + 1}: ${row.amount}',
|
||||||
|
);
|
||||||
|
}
|
||||||
final destination = CardPaymentMethod(
|
final destination = CardPaymentMethod(
|
||||||
pan: row.pan,
|
pan: row.pan,
|
||||||
firstName: row.firstName,
|
firstName: row.firstName,
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/money.dart';
|
import 'package:money2/money2.dart';
|
||||||
|
|
||||||
import 'package:pshared/utils/localization.dart';
|
import 'package:pshared/utils/localization.dart';
|
||||||
|
|
||||||
import 'package:pweb/utils/money_display.dart';
|
import 'package:pweb/utils/money_display.dart';
|
||||||
|
|
||||||
|
|
||||||
String formatMoney(BuildContext context, Money? money) {
|
String formatMoney(BuildContext context, Money? money) {
|
||||||
if (money == null || money.amount.trim().isEmpty) {
|
|
||||||
return unavailableMoneyValue(context);
|
|
||||||
}
|
|
||||||
return formatMoneyUi(context, money);
|
return formatMoneyUi(context, money);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:pshared/models/payment/operation.dart';
|
|||||||
import 'package:pshared/models/payment/payment.dart';
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
import 'package:pshared/models/payment/state.dart';
|
import 'package:pshared/models/payment/state.dart';
|
||||||
import 'package:pshared/models/payment/status.dart';
|
import 'package:pshared/models/payment/status.dart';
|
||||||
import 'package:pshared/utils/money.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/utils/payment/upload_metadata.dart';
|
import 'package:pweb/utils/payment/upload_metadata.dart';
|
||||||
import 'package:pweb/utils/report/operations/document_rule.dart';
|
import 'package:pweb/utils/report/operations/document_rule.dart';
|
||||||
@@ -14,12 +13,12 @@ OperationItem mapPaymentToOperation(Payment payment) {
|
|||||||
final settlement = payment.lastQuote?.amounts?.destinationSettlement;
|
final settlement = payment.lastQuote?.amounts?.destinationSettlement;
|
||||||
final amountMoney = debit ?? settlement;
|
final amountMoney = debit ?? settlement;
|
||||||
|
|
||||||
final amount = parseMoneyAmount(amountMoney?.amount);
|
final amount = amountMoney?.toDouble() ?? 0;
|
||||||
final currency = amountMoney?.currency ?? '';
|
final currency = amountMoney?.currency.isoCode ?? '';
|
||||||
final toAmount = settlement == null
|
final toAmount = settlement == null
|
||||||
? amount
|
? amount
|
||||||
: parseMoneyAmount(settlement.amount);
|
: settlement.toDouble();
|
||||||
final toCurrency = settlement?.currency ?? currency;
|
final toCurrency = settlement?.currency.isoCode ?? currency;
|
||||||
|
|
||||||
final payId = _firstNonEmpty([payment.paymentRef]) ?? '-';
|
final payId = _firstNonEmpty([payment.paymentRef]) ?? '-';
|
||||||
final name =
|
final name =
|
||||||
|
|||||||
@@ -1,31 +1,28 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/ledger/account.dart';
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
import 'package:pshared/models/money.dart';
|
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
import 'package:pshared/utils/currency.dart';
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
import 'package:pshared/utils/money.dart';
|
||||||
|
|
||||||
import 'package:pweb/utils/money_display.dart';
|
import 'package:pweb/utils/money_display.dart';
|
||||||
|
|
||||||
|
|
||||||
String walletBalance(BuildContext context, Wallet wallet) {
|
String walletBalance(BuildContext context, Wallet wallet) {
|
||||||
return formatMoneyUi(
|
return formatAmountUi(
|
||||||
context,
|
context,
|
||||||
Money(
|
amount: wallet.balance,
|
||||||
amount: amountToString(wallet.balance),
|
currency: currencyCodeToString(wallet.currency),
|
||||||
currency: currencyCodeToString(wallet.currency),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String ledgerBalance(BuildContext context, LedgerAccount account) {
|
String ledgerBalance(BuildContext context, LedgerAccount account) {
|
||||||
final money = account.balance?.balance;
|
final money = account.balance?.balance;
|
||||||
final effectiveCurrency = (money?.currency.trim().isNotEmpty ?? false)
|
final effectiveCurrency = (money?.currency.isoCode.trim().isNotEmpty ?? false)
|
||||||
? money!.currency
|
? money!.currency.isoCode
|
||||||
: account.currency;
|
: account.currency;
|
||||||
|
final effectiveMoney =
|
||||||
|
money ?? parseMoneyWithCurrencyCode('0', effectiveCurrency);
|
||||||
|
|
||||||
return formatMoneyUi(
|
return formatMoneyUi(context, effectiveMoney);
|
||||||
context,
|
|
||||||
Money(amount: money?.amount ?? '', currency: effectiveCurrency),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ dependencies:
|
|||||||
qr_flutter: ^4.1.0
|
qr_flutter: ^4.1.0
|
||||||
duration: ^4.0.3
|
duration: ^4.0.3
|
||||||
universal_html: ^2.3.0
|
universal_html: ^2.3.0
|
||||||
|
money2: ^6.3.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user