small fixes for single payout and big chunck for multiple payouts
This commit is contained in:
244
frontend/pweb/lib/controllers/multiple_payouts.dart
Normal file
244
frontend/pweb/lib/controllers/multiple_payouts.dart
Normal file
@@ -0,0 +1,244 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/models/payment/payment.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:pweb/models/multiple_payouts/csv_row.dart';
|
||||
import 'package:pweb/models/multiple_payouts/state.dart';
|
||||
import 'package:pweb/services/payments/csv_input.dart';
|
||||
import 'package:pweb/utils/payment/multiple_csv_parser.dart';
|
||||
import 'package:pweb/utils/payment/multiple_intent_builder.dart';
|
||||
|
||||
|
||||
class MultiplePayoutsController extends ChangeNotifier {
|
||||
final CsvInputService _csvInput;
|
||||
final MultipleCsvParser _csvParser;
|
||||
final MultipleIntentBuilder _intentBuilder;
|
||||
|
||||
WalletsController? _wallets;
|
||||
MultiQuotationProvider? _quotation;
|
||||
MultiPaymentProvider? _payment;
|
||||
|
||||
MultiplePayoutsState _state = MultiplePayoutsState.idle;
|
||||
String? _selectedFileName;
|
||||
List<CsvPayoutRow> _rows = const <CsvPayoutRow>[];
|
||||
int _sentCount = 0;
|
||||
Exception? _error;
|
||||
|
||||
MultiplePayoutsController({
|
||||
required CsvInputService csvInput,
|
||||
MultipleCsvParser? csvParser,
|
||||
MultipleIntentBuilder? intentBuilder,
|
||||
}) : _csvInput = csvInput,
|
||||
_csvParser = csvParser ?? MultipleCsvParser(),
|
||||
_intentBuilder = intentBuilder ?? MultipleIntentBuilder();
|
||||
|
||||
void update(
|
||||
WalletsController wallets,
|
||||
MultiQuotationProvider quotation,
|
||||
MultiPaymentProvider payment,
|
||||
) {
|
||||
_wallets = wallets;
|
||||
_quotation = 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 canSend {
|
||||
if (isBusy || _rows.isEmpty) return false;
|
||||
final quoteRef = _quotation?.quotation?.quoteRef;
|
||||
return quoteRef != null && quoteRef.isNotEmpty;
|
||||
}
|
||||
|
||||
Money? get aggregateDebitAmount {
|
||||
if (_rows.isEmpty) return null;
|
||||
return _moneyForSourceCurrency(
|
||||
_quotation?.quotation?.aggregate?.debitAmounts,
|
||||
);
|
||||
}
|
||||
|
||||
Money? get requestedSentAmount {
|
||||
if (_rows.isEmpty) return null;
|
||||
const currency = 'RUB';
|
||||
|
||||
double total = 0;
|
||||
for (final row in _rows) {
|
||||
final value = double.tryParse(row.amount);
|
||||
if (value == null) return null;
|
||||
total += value;
|
||||
}
|
||||
return Money(amount: amountToString(total), currency: currency);
|
||||
}
|
||||
|
||||
Money? get aggregateSettlementAmount {
|
||||
if (_rows.isEmpty) return null;
|
||||
return _moneyForSourceCurrency(
|
||||
_quotation?.quotation?.aggregate?.expectedSettlementAmounts,
|
||||
);
|
||||
}
|
||||
|
||||
Money? get aggregateFeeAmount {
|
||||
if (_rows.isEmpty) return null;
|
||||
return _moneyForSourceCurrency(
|
||||
_quotation?.quotation?.aggregate?.expectedFeeTotals,
|
||||
);
|
||||
}
|
||||
|
||||
double? get aggregateFeePercent {
|
||||
final debit = aggregateDebitAmount;
|
||||
final fee = aggregateFeeAmount;
|
||||
if (debit == null || fee == null) return null;
|
||||
|
||||
final debitValue = double.tryParse(debit.amount);
|
||||
final feeValue = double.tryParse(fee.amount);
|
||||
if (debit.currency.toUpperCase() != fee.currency.toUpperCase()) return null;
|
||||
if (debitValue == null || feeValue == null || debitValue <= 0) return null;
|
||||
return (feeValue / debitValue) * 100;
|
||||
}
|
||||
|
||||
Future<void> pickAndQuote() async {
|
||||
if (isBusy) return;
|
||||
|
||||
final wallets = _wallets;
|
||||
final quotation = _quotation;
|
||||
if (wallets == null || quotation == null) {
|
||||
_setErrorObject(
|
||||
StateError('Multiple payouts dependencies are not ready'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_setState(MultiplePayoutsState.quoting);
|
||||
_error = null;
|
||||
_sentCount = 0;
|
||||
|
||||
final picked = await _csvInput.pickCsv();
|
||||
if (picked == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final rows = _csvParser.parseRows(picked.content);
|
||||
final intents = _intentBuilder.buildIntents(wallets, rows);
|
||||
|
||||
_selectedFileName = picked.name;
|
||||
_rows = rows;
|
||||
|
||||
await quotation.quotePayments(
|
||||
intents,
|
||||
metadata: <String, String>{
|
||||
'upload_filename': picked.name,
|
||||
'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();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (values == null || values.isEmpty) return null;
|
||||
|
||||
final selectedWallet = _wallets?.selectedWallet;
|
||||
if (selectedWallet != null) {
|
||||
final sourceCurrency = currencyCodeToString(selectedWallet.currency);
|
||||
for (final value in values) {
|
||||
if (value.currency.toUpperCase() == sourceCurrency.toUpperCase()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values.first;
|
||||
}
|
||||
}
|
||||
71
frontend/pweb/lib/controllers/payment_page.dart
Normal file
71
frontend/pweb/lib/controllers/payment_page.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/flow.dart';
|
||||
import 'package:pshared/provider/payment/provider.dart';
|
||||
import 'package:pshared/provider/payment/quotation/quotation.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
|
||||
|
||||
class PaymentPageController extends ChangeNotifier {
|
||||
PaymentProvider? _payment;
|
||||
QuotationProvider? _quotation;
|
||||
PaymentFlowProvider? _flow;
|
||||
RecipientsProvider? _recipients;
|
||||
|
||||
bool _isSending = false;
|
||||
Exception? _error;
|
||||
|
||||
bool get isSending => _isSending;
|
||||
Exception? get error => _error;
|
||||
|
||||
void update(
|
||||
PaymentProvider payment,
|
||||
QuotationProvider quotation,
|
||||
PaymentFlowProvider flow,
|
||||
RecipientsProvider recipients,
|
||||
) {
|
||||
_payment = payment;
|
||||
_quotation = quotation;
|
||||
_flow = flow;
|
||||
_recipients = recipients;
|
||||
}
|
||||
|
||||
Future<bool> sendPayment() async {
|
||||
if (_isSending) return false;
|
||||
final payment = _payment;
|
||||
if (payment == null) {
|
||||
_setError(StateError('Payment provider is not ready'));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
_setSending(true);
|
||||
_error = null;
|
||||
final result = await payment.pay();
|
||||
return result != null && payment.error == null;
|
||||
} catch (e) {
|
||||
_setError(e);
|
||||
return false;
|
||||
} finally {
|
||||
_setSending(false);
|
||||
}
|
||||
}
|
||||
|
||||
void resetAfterSuccess() {
|
||||
_quotation?.reset();
|
||||
_payment?.reset();
|
||||
_flow?.setManualPaymentData(null);
|
||||
_recipients?.setCurrentObject(null);
|
||||
}
|
||||
|
||||
void _setSending(bool value) {
|
||||
if (_isSending == value) return;
|
||||
_isSending = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _setError(Object error) {
|
||||
_error = error is Exception ? error : Exception(error.toString());
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user