added wallet source to quotation preparation

This commit is contained in:
Stephan D
2025-12-24 19:59:50 +01:00
parent 68b82cbca2
commit e0820c47c2
35 changed files with 151 additions and 138 deletions

View File

@@ -1,6 +1,6 @@
import 'package:pshared/data/dto/wallet/asset.dart';
import 'package:pshared/data/mapper/payment/enums.dart';
import 'package:pshared/models/wallet/wallet.dart';
import 'package:pshared/models/wallet/asset.dart';
extension WalletAssetDTOMapper on WalletAssetDTO {

View File

@@ -0,0 +1,26 @@
import 'package:pshared/models/currency.dart';
import 'package:pshared/models/wallet/wallet.dart' as domain;
import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/utils/currency.dart';
extension WalletUiMapper on domain.WalletModel {
Wallet toUi() {
final amountStr = availableMoney?.amount ?? balance?.available?.amount ?? '0';
final parsedAmount = double.tryParse(amountStr) ?? 0;
final currency = currencyStringToCode(asset.tokenSymbol);
return Wallet(
id: walletRef,
walletUserID: walletRef,
balance: parsedAmount,
currency: currency,
isHidden: true,
calculatedAt: balance?.calculatedAt ?? DateTime.now(),
depositAddress: depositAddress,
network: asset.chain,
tokenSymbol: asset.tokenSymbol,
contractAddress: asset.contractAddress,
describable: describable,
);
}
}

View File

@@ -3,6 +3,7 @@ import 'package:pshared/models/payment/methods/card.dart';
import 'package:pshared/models/payment/methods/crypto_address.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/methods/iban.dart';
import 'package:pshared/models/payment/methods/managed_wallet.dart';
import 'package:pshared/models/payment/methods/russian_bank.dart';
import 'package:pshared/models/payment/methods/wallet.dart';
import 'package:pshared/models/payment/type.dart';
@@ -43,6 +44,7 @@ class PaymentMethod implements PermissionBoundStorable, Describable {
IbanPaymentMethod? get ibanData => dataAsOrNull<IbanPaymentMethod>();
RussianBankAccountPaymentMethod? get bankAccountData => dataAsOrNull<RussianBankAccountPaymentMethod>();
WalletPaymentMethod? get walletData => dataAsOrNull<WalletPaymentMethod>();
ManagedWalletPaymentMethod? get managedWalletData => dataAsOrNull<ManagedWalletPaymentMethod>();
CryptoAddressPaymentMethod? get cryptoAddressData => dataAsOrNull<CryptoAddressPaymentMethod>();
@override

View File

@@ -0,0 +1,68 @@
import 'package:pshared/models/currency.dart';
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/payment/chain_network.dart';
class Wallet implements Describable {
final String id;
final String walletUserID; // ID or number that we show the user
final double balance;
final Currency currency;
final bool isHidden;
final DateTime calculatedAt;
final String? depositAddress;
final ChainNetwork? network;
final String? tokenSymbol;
final String? contractAddress;
final Describable describable;
@override
String get name => describable.name;
@override
String? get description => describable.description;
Wallet({
required this.id,
required this.walletUserID,
required this.balance,
required this.currency,
required this.calculatedAt,
required this.describable,
this.isHidden = true,
this.depositAddress,
this.network,
this.tokenSymbol,
this.contractAddress,
});
Wallet copyWith({
String? id,
double? balance,
Currency? currency,
String? walletUserID,
bool? isHidden,
String? depositAddress,
ChainNetwork? network,
String? tokenSymbol,
String? contractAddress,
Describable? describable,
String? name,
String? Function()? description,
}) => Wallet(
id: id ?? this.id,
balance: balance ?? this.balance,
currency: currency ?? this.currency,
walletUserID: walletUserID ?? this.walletUserID,
isHidden: isHidden ?? this.isHidden,
calculatedAt: calculatedAt,
depositAddress: depositAddress ?? this.depositAddress,
network: network ?? this.network,
tokenSymbol: tokenSymbol ?? this.tokenSymbol,
contractAddress: contractAddress ?? this.contractAddress,
describable: describable
?? (name != null || description != null
? this.describable.copyWith(name: name, description: description)
: this.describable),
);
}

View File

@@ -0,0 +1,14 @@
import 'package:pshared/models/payment/chain_network.dart';
class WalletAsset {
final ChainNetwork chain;
final String tokenSymbol;
final String contractAddress;
const WalletAsset({
required this.chain,
required this.tokenSymbol,
required this.contractAddress,
});
}

View File

@@ -1,21 +1,9 @@
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/payment/chain_network.dart';
import 'package:pshared/models/wallet/asset.dart';
import 'package:pshared/models/wallet/balance.dart';
import 'package:pshared/models/wallet/money.dart';
class WalletAsset {
final ChainNetwork chain;
final String tokenSymbol;
final String contractAddress;
const WalletAsset({
required this.chain,
required this.tokenSymbol,
required this.contractAddress,
});
}
class WalletModel implements Describable {
final String walletRef;
final String organizationRef;
@@ -55,20 +43,18 @@ class WalletModel implements Describable {
WalletBalance? balance,
WalletMoney? availableMoney,
Describable? describable,
}) {
return WalletModel(
walletRef: walletRef,
organizationRef: organizationRef,
ownerRef: ownerRef,
asset: asset,
depositAddress: depositAddress,
status: status,
metadata: metadata,
createdAt: createdAt,
updatedAt: updatedAt,
balance: balance ?? this.balance,
availableMoney: availableMoney ?? this.availableMoney,
describable: describable ?? this.describable,
);
}
}) => WalletModel(
walletRef: walletRef,
organizationRef: organizationRef,
ownerRef: ownerRef,
asset: asset,
depositAddress: depositAddress,
status: status,
metadata: metadata,
createdAt: createdAt,
updatedAt: updatedAt,
balance: balance ?? this.balance,
availableMoney: availableMoney ?? this.availableMoney,
describable: describable ?? this.describable,
);
}

View File

@@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/asset.dart';
import 'package:uuid/uuid.dart';
import 'package:pshared/api/requests/payment/quote.dart';
import 'package:pshared/data/mapper/payment/intent/payment.dart';
import 'package:pshared/models/asset.dart';
import 'package:pshared/models/payment/currency_pair.dart';
import 'package:pshared/models/payment/fx/intent.dart';
import 'package:pshared/models/payment/fx/side.dart';
@@ -11,14 +13,14 @@ import 'package:pshared/models/payment/methods/card.dart';
import 'package:pshared/models/payment/methods/managed_wallet.dart';
import 'package:pshared/models/payment/money.dart';
import 'package:pshared/models/payment/settlement_mode.dart';
import 'package:pshared/provider/payment/amount.dart';
import 'package:pshared/api/requests/payment/quote.dart';
import 'package:pshared/data/mapper/payment/intent/payment.dart';
import 'package:pshared/models/payment/intent.dart';
import 'package:pshared/models/payment/quote.dart';
import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/payment/amount.dart';
import 'package:pshared/provider/payment/wallets.dart';
import 'package:pshared/provider/resource.dart';
import 'package:pshared/service/payment/quotation.dart';
import 'package:pshared/utils/currency.dart';
class QuotationProvider extends ChangeNotifier {
@@ -26,31 +28,38 @@ class QuotationProvider extends ChangeNotifier {
late OrganizationsProvider _organizations;
bool _isLoaded = false;
void update(OrganizationsProvider venue, PaymentAmountProvider payment) {
void update(
OrganizationsProvider venue,
PaymentAmountProvider payment,
WalletsProvider wallets,
) {
_organizations = venue;
getQuotation(PaymentIntent(
kind: PaymentKind.payout,
amount: Money(
amount: payment.amount.toString(),
currency: 'USDT',
),
destination: CardPaymentMethod(
pan: '4000000000000077',
firstName: 'John',
lastName: 'Doe',
),
source: ManagedWalletPaymentMethod(
managedWalletRef: '',
),
fx: FxIntent(
pair: CurrencyPair(
base: 'USDT',
quote: 'RUB',
if (wallets.selectedWallet != null) {
getQuotation(PaymentIntent(
kind: PaymentKind.payout,
amount: Money(
amount: payment.amount.toString(),
// TODO: adapt to possible other sources
currency: currencyCodeToString(wallets.selectedWallet!.currency),
),
side: FxSide.sellBaseBuyQuote,
),
settlementMode: payment.payerCoversFee ? SettlementMode.fixReceived : SettlementMode.fixSource,
));
destination: CardPaymentMethod(
pan: '4000000000000077',
firstName: 'John',
lastName: 'Doe',
),
source: ManagedWalletPaymentMethod(
managedWalletRef: wallets.selectedWallet!.id,
),
fx: FxIntent(
pair: CurrencyPair(
base: currencyCodeToString(wallets.selectedWallet!.currency),
quote: 'RUB',
),
side: FxSide.sellBaseBuyQuote,
),
settlementMode: payment.payerCoversFee ? SettlementMode.fixReceived : SettlementMode.fixSource,
));
}
}
PaymentQuote? get quotation => _quotation.data;

View File

@@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/resource.dart';
import 'package:pshared/service/payment/wallets.dart';
import 'package:pshared/utils/exception.dart';
class WalletsProvider with ChangeNotifier {
final WalletsService _service;
late OrganizationsProvider _organizations;
WalletsProvider(this._service);
Resource<List<Wallet>> _resource = Resource(data: []);
Resource<List<Wallet>> get resource => _resource;
List<Wallet> get wallets => _resource.data ?? [];
bool get isLoading => _resource.isLoading;
Exception? get error => _resource.error;
Wallet? _selectedWallet;
Wallet? get selectedWallet => _selectedWallet;
final bool _isHidden = true;
bool get isHidden => _isHidden;
bool _isRefreshingBalances = false;
bool get isRefreshingBalances => _isRefreshingBalances;
void update(OrganizationsProvider organizations) {
_organizations = organizations;
if (_organizations.isOrganizationSet) loadWalletsWithBalances();
}
Future<Wallet> updateWallet(Wallet newWallet) {
throw Exception('update wallet is not implemented');
}
void selectWallet(Wallet wallet) {
_selectedWallet = wallet;
notifyListeners();
}
Future<void> loadWalletsWithBalances() async {
_setResource(_resource.copyWith(isLoading: true, error: null));
try {
final base = await _service.getWallets(_organizations.current.id);
final withBalances = <Wallet>[];
for (final wallet in base) {
try {
final balance = await _service.getBalance(_organizations.current.id, wallet.id);
withBalances.add(wallet.copyWith(balance: balance));
} catch (e) {
_setResource(_resource.copyWith(error: toException(e)));
withBalances.add(wallet);
}
}
_setResource(Resource(data: withBalances, isLoading: false, error: _resource.error));
} catch (e) {
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
}
}
Future<void> refreshBalances() async {
if (wallets.isEmpty) return;
_isRefreshingBalances = true;
notifyListeners();
try {
final updated = <Wallet>[];
for (final wallet in wallets) {
final balance = await _service.getBalance(_organizations.current.id, wallet.id);
updated.add(wallet.copyWith(balance: balance));
}
_setResource(_resource.copyWith(data: updated));
} catch (e) {
_setResource(_resource.copyWith(error: toException(e)));
} finally {
_isRefreshingBalances = false;
notifyListeners();
}
}
void toggleVisibility(String walletId) {
final index = wallets.indexWhere((w) => w.id == walletId);
if (index < 0) return;
final wallet = wallets[index];
final updated = wallet.copyWith(isHidden: !wallet.isHidden);
final next = List<Wallet>.from(wallets);
next[index] = updated;
_setResource(_resource.copyWith(data: next));
if (_selectedWallet?.id == walletId) {
_selectedWallet = updated;
}
}
void _setResource(Resource<List<Wallet>> newResource) {
_resource = newResource;
notifyListeners();
}
}

View File

@@ -0,0 +1,27 @@
import 'package:pshared/data/mapper/wallet/ui.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/service/wallet.dart' as shared_wallet_service;
abstract class WalletsService {
Future<List<Wallet>> getWallets(String organizationRef);
Future<double> getBalance(String organizationRef, String walletRef);
}
class ApiWalletsService implements WalletsService {
@override
Future<List<Wallet>> getWallets(String organizationRef) async {
final models = await shared_wallet_service.WalletService.list(organizationRef);
return models.map((m) => m.toUi()).toList();
}
@override
Future<double> getBalance(String organizationRef, String walletRef) async {
final balance = await shared_wallet_service.WalletService.getBalance(
organizationRef: organizationRef,
walletRef: walletRef,
);
final amount = balance.available?.amount;
return amount == null ? 0 : double.tryParse(amount) ?? 0;
}
}

View File

@@ -48,6 +48,21 @@ Currency currencyStringToCode(String currencyCode) {
}
}
String currencyCodeToString(Currency currencyCode) {
switch (currencyCode) {
case Currency.usd:
return 'USD';
case Currency.usdt:
return 'USDT';
case Currency.usdc:
return 'USDC';
case Currency.rub:
return 'RUB';
case Currency.eur:
return 'EUR';
}
}
IconData iconForCurrencyType(Currency currencyCode) {
switch (currencyCode) {
case Currency.usd: