283 lines
8.0 KiB
Dart
283 lines
8.0 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:pshared/models/money.dart';
|
|
import 'package:pshared/models/payment/payment.dart';
|
|
import 'package:pshared/models/payment/quote/status_type.dart';
|
|
import 'package:pshared/models/payment/wallet.dart';
|
|
import 'package:pshared/provider/payment/multiple/provider.dart';
|
|
import 'package:pshared/provider/payment/multiple/quotation.dart';
|
|
import 'package:pshared/utils/currency.dart';
|
|
import 'package:pshared/utils/money.dart';
|
|
|
|
import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
|
|
import 'package:pweb/models/payment/multiple_payouts/state.dart';
|
|
import 'package:pweb/utils/payment/multiple_csv_parser.dart';
|
|
import 'package:pweb/utils/payment/multiple_intent_builder.dart';
|
|
|
|
|
|
class MultiplePayoutsProvider extends ChangeNotifier {
|
|
final MultipleCsvParser _csvParser;
|
|
final MultipleIntentBuilder _intentBuilder;
|
|
|
|
MultiQuotationProvider? _quotation;
|
|
MultiPaymentProvider? _payment;
|
|
|
|
MultiplePayoutsState _state = MultiplePayoutsState.idle;
|
|
String? _selectedFileName;
|
|
List<CsvPayoutRow> _rows = const <CsvPayoutRow>[];
|
|
int _sentCount = 0;
|
|
Exception? _error;
|
|
|
|
MultiplePayoutsProvider({
|
|
MultipleCsvParser? csvParser,
|
|
MultipleIntentBuilder? intentBuilder,
|
|
}) : _csvParser = csvParser ?? MultipleCsvParser(),
|
|
_intentBuilder = intentBuilder ?? MultipleIntentBuilder();
|
|
|
|
void update(
|
|
MultiQuotationProvider quotation,
|
|
MultiPaymentProvider payment,
|
|
) {
|
|
_bindQuotation(quotation);
|
|
_payment = payment;
|
|
}
|
|
|
|
MultiplePayoutsState get state => _state;
|
|
String? get selectedFileName => _selectedFileName;
|
|
List<CsvPayoutRow> get rows => List.unmodifiable(_rows);
|
|
int get sentCount => _sentCount;
|
|
Exception? get error => _error;
|
|
|
|
bool get isQuoting => _state == MultiplePayoutsState.quoting;
|
|
bool get isSending => _state == MultiplePayoutsState.sending;
|
|
bool get isBusy => isQuoting || isSending;
|
|
|
|
bool get quoteIsLoading => _quotation?.isLoading ?? false;
|
|
|
|
QuoteStatusType get quoteStatusType {
|
|
final quotation = _quotation;
|
|
if (quotation == null) return QuoteStatusType.missing;
|
|
if (quotation.isLoading) return QuoteStatusType.loading;
|
|
if (quotation.error != null) return QuoteStatusType.error;
|
|
if (quotation.quotation == null) return QuoteStatusType.missing;
|
|
if (_isQuoteExpired(quotation.quoteExpiresAt)) return QuoteStatusType.expired;
|
|
return QuoteStatusType.active;
|
|
}
|
|
|
|
Duration? get quoteTimeLeft {
|
|
final expiresAt = _quotation?.quoteExpiresAt;
|
|
if (expiresAt == null) return null;
|
|
return expiresAt.difference(DateTime.now().toUtc());
|
|
}
|
|
|
|
bool get canSend {
|
|
if (isBusy || _rows.isEmpty) return false;
|
|
final quoteRef = _quotation?.quotation?.quoteRef;
|
|
return quoteRef != null && quoteRef.isNotEmpty;
|
|
}
|
|
|
|
Money? aggregateDebitAmountFor(Wallet? sourceWallet) {
|
|
if (_rows.isEmpty) return null;
|
|
return _moneyForSourceCurrency(
|
|
_quotation?.quotation?.aggregate?.debitAmounts,
|
|
sourceWallet,
|
|
);
|
|
}
|
|
|
|
Money? get requestedSentAmount {
|
|
if (_rows.isEmpty) return null;
|
|
const currency = 'RUB';
|
|
|
|
double total = 0;
|
|
for (final row in _rows) {
|
|
final value = parseMoneyAmount(row.amount, fallback: double.nan);
|
|
if (value.isNaN) return null;
|
|
total += value;
|
|
}
|
|
return Money(amount: amountToString(total), currency: currency);
|
|
}
|
|
|
|
Money? aggregateSettlementAmountFor(Wallet? sourceWallet) {
|
|
if (_rows.isEmpty) return null;
|
|
return _moneyForSourceCurrency(
|
|
_quotation?.quotation?.aggregate?.expectedSettlementAmounts,
|
|
sourceWallet,
|
|
);
|
|
}
|
|
|
|
Money? aggregateFeeAmountFor(Wallet? sourceWallet) {
|
|
if (_rows.isEmpty) return null;
|
|
return _moneyForSourceCurrency(
|
|
_quotation?.quotation?.aggregate?.expectedFeeTotals,
|
|
sourceWallet,
|
|
);
|
|
}
|
|
|
|
double? aggregateFeePercentFor(Wallet? sourceWallet) {
|
|
final debit = aggregateDebitAmountFor(sourceWallet);
|
|
final fee = aggregateFeeAmountFor(sourceWallet);
|
|
if (debit == null || fee == null) return null;
|
|
|
|
final debitValue = parseMoneyAmount(debit.amount, fallback: double.nan);
|
|
final feeValue = parseMoneyAmount(fee.amount, fallback: double.nan);
|
|
if (debit.currency.toUpperCase() != fee.currency.toUpperCase()) return null;
|
|
if (debitValue.isNaN || feeValue.isNaN || debitValue <= 0) return null;
|
|
return (feeValue / debitValue) * 100;
|
|
}
|
|
|
|
Future<void> quoteFromCsv({
|
|
required String fileName,
|
|
required String content,
|
|
required Wallet sourceWallet,
|
|
}) async {
|
|
if (isBusy) return;
|
|
|
|
final quotation = _quotation;
|
|
if (quotation == null) {
|
|
_setErrorObject(
|
|
StateError('Multiple payouts dependencies are not ready'),
|
|
);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
_setState(MultiplePayoutsState.quoting);
|
|
_error = null;
|
|
_sentCount = 0;
|
|
|
|
final rows = _csvParser.parseRows(content);
|
|
final intents = _intentBuilder.buildIntents(sourceWallet, rows);
|
|
|
|
_selectedFileName = fileName;
|
|
_rows = rows;
|
|
|
|
await quotation.quotePayments(
|
|
intents,
|
|
metadata: <String, String>{
|
|
'upload_filename': fileName,
|
|
'upload_rows': rows.length.toString(),
|
|
...?_uploadAmountMetadata(),
|
|
},
|
|
);
|
|
|
|
if (quotation.error != null) {
|
|
_setErrorObject(quotation.error!);
|
|
}
|
|
} catch (e) {
|
|
_setErrorObject(e);
|
|
} finally {
|
|
_setState(MultiplePayoutsState.idle);
|
|
}
|
|
}
|
|
|
|
Future<List<Payment>> send() async {
|
|
if (isBusy) return const <Payment>[];
|
|
|
|
final payment = _payment;
|
|
if (payment == null) {
|
|
_setErrorObject(
|
|
StateError('Multiple payouts payment provider is not ready'),
|
|
);
|
|
return const <Payment>[];
|
|
}
|
|
if (!canSend) {
|
|
_setErrorObject(
|
|
StateError('Upload CSV and wait for quote before sending'),
|
|
);
|
|
return const <Payment>[];
|
|
}
|
|
|
|
try {
|
|
_setState(MultiplePayoutsState.sending);
|
|
_error = null;
|
|
|
|
final result = await payment.pay(
|
|
metadata: <String, String>{
|
|
...?_selectedFileName == null
|
|
? null
|
|
: <String, String>{'upload_filename': _selectedFileName!},
|
|
'upload_rows': _rows.length.toString(),
|
|
...?_uploadAmountMetadata(),
|
|
},
|
|
);
|
|
|
|
_sentCount = result.length;
|
|
return result;
|
|
} catch (e) {
|
|
_setErrorObject(e);
|
|
return const <Payment>[];
|
|
} finally {
|
|
_setState(MultiplePayoutsState.idle);
|
|
}
|
|
}
|
|
|
|
void removeUploadedFile() {
|
|
if (isBusy) return;
|
|
|
|
_selectedFileName = null;
|
|
_rows = const <CsvPayoutRow>[];
|
|
_sentCount = 0;
|
|
_error = null;
|
|
notifyListeners();
|
|
}
|
|
|
|
void _setState(MultiplePayoutsState value) {
|
|
_state = value;
|
|
notifyListeners();
|
|
}
|
|
|
|
void _setErrorObject(Object error) {
|
|
_error = error is Exception ? error : Exception(error.toString());
|
|
notifyListeners();
|
|
}
|
|
|
|
void _bindQuotation(MultiQuotationProvider quotation) {
|
|
if (identical(_quotation, quotation)) return;
|
|
_quotation?.removeListener(_onQuotationChanged);
|
|
_quotation = quotation;
|
|
_quotation?.addListener(_onQuotationChanged);
|
|
}
|
|
|
|
void _onQuotationChanged() {
|
|
notifyListeners();
|
|
}
|
|
|
|
bool _isQuoteExpired(DateTime? expiresAt) {
|
|
if (expiresAt == null) return false;
|
|
return expiresAt.difference(DateTime.now().toUtc()) <= Duration.zero;
|
|
}
|
|
|
|
Map<String, String>? _uploadAmountMetadata() {
|
|
final sentAmount = requestedSentAmount;
|
|
if (sentAmount == null) return null;
|
|
return <String, String>{
|
|
'upload_amount': sentAmount.amount,
|
|
'upload_currency': sentAmount.currency,
|
|
};
|
|
}
|
|
|
|
Money? _moneyForSourceCurrency(
|
|
List<Money>? values,
|
|
Wallet? sourceWallet,
|
|
) {
|
|
if (values == null || values.isEmpty) return null;
|
|
|
|
if (sourceWallet != null) {
|
|
final sourceCurrency = currencyCodeToString(sourceWallet.currency);
|
|
for (final value in values) {
|
|
if (value.currency.toUpperCase() == sourceCurrency.toUpperCase()) {
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return values.first;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_quotation?.removeListener(_onQuotationChanged);
|
|
super.dispose();
|
|
}
|
|
}
|