[infra][rebuild]Merge pull request 'Orchestration / payments v2' (#554) from pqpov2-547 into main
All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/billing_documents Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/gateway_tron Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_methods Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/payments_quotation Pipeline was successful

[infra][rebuild]Reviewed-on: #554
This commit was merged in pull request #554.
This commit is contained in:
2026-02-26 22:45:54 +00:00
499 changed files with 30817 additions and 21315 deletions

View File

@@ -7,7 +7,7 @@ import 'package:pshared/models/payment/type.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/asset_type_field.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/description.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/employees_loading_indicator.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/ledger_fields.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/ledger/fields.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/managed_wallet_fields.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/name.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/owner.dart';

View File

@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/currency.dart';
import 'package:pshared/utils/currency.dart';
DropdownMenuItem<Currency> currencyItem(Currency currency) => DropdownMenuItem(
value: currency,
child: Text(currencyCodeToString(currency)),
);

View File

@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/currency.dart';
import 'package:pshared/utils/currency.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/constants.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/ledger/currency_item.dart';
import 'package:pweb/utils/text_field_styles.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
@@ -24,10 +24,8 @@ class LedgerFields extends StatelessWidget {
initialValue: currency,
decoration: getInputDecoration(context, AppLocalizations.of(context)!.currency, true),
items: [
DropdownMenuItem(
value: ledgerCurrencyDefault,
child: Text(currencyCodeToString(ledgerCurrencyDefault)),
),
currencyItem(ledgerCurrencyDefault),
currencyItem(managedCurrencyDefault),
],
onChanged: onCurrencyChanged,
);

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/payment.dart';
import 'package:pshared/utils/payment/quote_helpers.dart';
import 'package:pweb/pages/report/details/summary_card/amount_headline.dart';
import 'package:pweb/pages/report/details/summary_card/copy_id.dart';
@@ -13,7 +14,6 @@ import 'package:pweb/utils/clipboard.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentSummaryCard extends StatelessWidget {
final Payment payment;
final VoidCallback? onDownloadAct;
@@ -31,11 +31,11 @@ class PaymentSummaryCard extends StatelessWidget {
final status = statusFromPayment(payment);
final dateLabel = formatDateLabel(context, resolvePaymentDate(payment));
final primaryAmount = payment.lastQuote?.debitAmount ??
payment.lastQuote?.expectedSettlementAmount;
final toAmount = payment.lastQuote?.expectedSettlementAmount;
final fee = payment.lastQuote?.expectedFeeTotal ??
payment.lastQuote?.networkFee?.networkFee;
final primaryAmount =
payment.lastQuote?.amounts?.sourceDebitTotal ??
payment.lastQuote?.amounts?.destinationSettlement;
final toAmount = payment.lastQuote?.amounts?.destinationSettlement;
final fee = quoteFeeTotal(payment.lastQuote);
final amountLabel = formatMoney(primaryAmount);
final toAmountLabel = formatMoney(toAmount);
@@ -108,11 +108,8 @@ class PaymentSummaryCard extends StatelessWidget {
child: CopyableId(
label: loc.paymentIdLabel,
value: paymentRef,
onCopy: () => copyToClipboard(
context,
paymentRef,
loc.paymentIdCopied,
),
onCopy: () =>
copyToClipboard(context, paymentRef, loc.paymentIdCopied),
),
),
],

View File

@@ -2,19 +2,20 @@ import 'package:flutter/foundation.dart';
import 'package:pshared/models/money.dart';
import 'package:pshared/models/payment/payment.dart';
import 'package:pshared/models/payment/quote/quote.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:pshared/utils/payment/quote_helpers.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;
@@ -34,10 +35,7 @@ class MultiplePayoutsProvider extends ChangeNotifier {
}) : _csvParser = csvParser ?? MultipleCsvParser(),
_intentBuilder = intentBuilder ?? MultipleIntentBuilder();
void update(
MultiQuotationProvider quotation,
MultiPaymentProvider payment,
) {
void update(MultiQuotationProvider quotation, MultiPaymentProvider payment) {
_bindQuotation(quotation);
_payment = payment;
}
@@ -60,7 +58,9 @@ class MultiplePayoutsProvider extends ChangeNotifier {
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;
if (_isQuoteExpired(quotation.quoteExpiresAt)) {
return QuoteStatusType.expired;
}
return QuoteStatusType.active;
}
@@ -78,10 +78,10 @@ class MultiplePayoutsProvider extends ChangeNotifier {
Money? aggregateDebitAmountFor(Wallet? sourceWallet) {
if (_rows.isEmpty) return null;
return _moneyForSourceCurrency(
_quotation?.quotation?.aggregate?.debitAmounts,
sourceWallet,
final totals = aggregateMoneyByCurrency(
_quoteItems().map((quote) => quote.amounts?.sourceDebitTotal),
);
return _moneyForSourceCurrency(totals, sourceWallet);
}
Money? get requestedSentAmount {
@@ -99,18 +99,16 @@ class MultiplePayoutsProvider extends ChangeNotifier {
Money? aggregateSettlementAmountFor(Wallet? sourceWallet) {
if (_rows.isEmpty) return null;
return _moneyForSourceCurrency(
_quotation?.quotation?.aggregate?.expectedSettlementAmounts,
sourceWallet,
final totals = aggregateMoneyByCurrency(
_quoteItems().map((quote) => quote.amounts?.destinationSettlement),
);
return _moneyForSourceCurrency(totals, sourceWallet);
}
Money? aggregateFeeAmountFor(Wallet? sourceWallet) {
if (_rows.isEmpty) return null;
return _moneyForSourceCurrency(
_quotation?.quotation?.aggregate?.expectedFeeTotals,
sourceWallet,
);
final totals = aggregateMoneyByCurrency(_quoteItems().map(quoteFeeTotal));
return _moneyForSourceCurrency(totals, sourceWallet);
}
double? aggregateFeePercentFor(Wallet? sourceWallet) {
@@ -256,10 +254,7 @@ class MultiplePayoutsProvider extends ChangeNotifier {
};
}
Money? _moneyForSourceCurrency(
List<Money>? values,
Wallet? sourceWallet,
) {
Money? _moneyForSourceCurrency(List<Money>? values, Wallet? sourceWallet) {
if (values == null || values.isEmpty) return null;
if (sourceWallet != null) {
@@ -274,6 +269,9 @@ class MultiplePayoutsProvider extends ChangeNotifier {
return values.first;
}
List<PaymentQuote> _quoteItems() =>
_quotation?.quotation?.items ?? const <PaymentQuote>[];
@override
void dispose() {
_quotation?.removeListener(_onQuotationChanged);

View File

@@ -1,6 +1,7 @@
import 'package:pshared/models/money.dart';
import 'package:pshared/models/payment/asset.dart';
import 'package:pshared/models/payment/chain_network.dart';
import 'package:pshared/models/payment/fees/treatment.dart';
import 'package:pshared/models/payment/intent.dart';
import 'package:pshared/models/payment/kind.dart';
import 'package:pshared/models/payment/methods/card.dart';
@@ -12,7 +13,6 @@ import 'package:pshared/utils/payment/fx_helpers.dart';
import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
class MultipleIntentBuilder {
static const String _currency = 'RUB';
@@ -35,32 +35,27 @@ class MultipleIntentBuilder {
);
return rows
.map(
(row) {
final amount = Money(amount: row.amount, currency: _currency);
return PaymentIntent(
kind: PaymentKind.payout,
source: ManagedWalletPaymentMethod(
managedWalletRef: sourceWallet.id,
asset: sourceAsset,
),
destination: CardPaymentMethod(
pan: row.pan,
firstName: row.firstName,
lastName: row.lastName,
expMonth: row.expMonth,
expYear: row.expYear,
),
amount: amount,
settlementMode: SettlementMode.fixReceived,
settlementCurrency: FxIntentHelper.resolveSettlementCurrency(
amount: amount,
fx: fxIntent,
),
fx: fxIntent,
);
},
)
.map((row) {
final amount = Money(amount: row.amount, currency: _currency);
return PaymentIntent(
kind: PaymentKind.payout,
source: ManagedWalletPaymentMethod(
managedWalletRef: sourceWallet.id,
asset: sourceAsset,
),
destination: CardPaymentMethod(
pan: row.pan,
firstName: row.firstName,
lastName: row.lastName,
expMonth: row.expMonth,
expYear: row.expYear,
),
amount: amount,
feeTreatment: FeeTreatment.addToSource,
settlementMode: SettlementMode.fixReceived,
fx: fxIntent,
);
})
.toList(growable: false);
}
}

View File

@@ -5,10 +5,9 @@ import 'package:pshared/utils/money.dart';
import 'package:pweb/models/payment/payment_state.dart';
OperationItem mapPaymentToOperation(Payment payment) {
final debit = payment.lastQuote?.debitAmount;
final settlement = payment.lastQuote?.expectedSettlementAmount;
final debit = payment.lastQuote?.amounts?.sourceDebitTotal;
final settlement = payment.lastQuote?.amounts?.destinationSettlement;
final amountMoney = debit ?? settlement;
final amount = parseMoneyAmount(amountMoney?.amount);
@@ -18,18 +17,17 @@ OperationItem mapPaymentToOperation(Payment payment) {
: parseMoneyAmount(settlement.amount);
final toCurrency = settlement?.currency ?? currency;
final payId = _firstNonEmpty([
payment.paymentRef,
payment.idempotencyKey,
]) ??
'-';
final name = _firstNonEmpty([
final payId =
_firstNonEmpty([payment.paymentRef, payment.idempotencyKey]) ?? '-';
final name =
_firstNonEmpty([
payment.lastQuote?.quoteRef,
payment.paymentRef,
payment.idempotencyKey,
]) ??
'-';
final comment = _firstNonEmpty([
final comment =
_firstNonEmpty([
payment.failureReason,
payment.failureCode,
payment.state,
@@ -72,17 +70,17 @@ DateTime resolvePaymentDate(Payment payment) {
final expiresAt = payment.lastQuote?.fxQuote?.expiresAtUnixMs;
if (expiresAt != null && expiresAt > 0) {
return DateTime.fromMillisecondsSinceEpoch(expiresAt, isUtc: true).toLocal();
return DateTime.fromMillisecondsSinceEpoch(
expiresAt,
isUtc: true,
).toLocal();
}
return DateTime.fromMillisecondsSinceEpoch(0);
}
String? paymentIdFromOperation(OperationItem operation) {
final candidates = [
operation.paymentRef,
operation.payId,
];
final candidates = [operation.paymentRef, operation.payId];
for (final candidate in candidates) {
final trimmed = candidate?.trim();
if (trimmed != null && trimmed.isNotEmpty && trimmed != '-') {

View File

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.6.0+507
version: 3.0.0+605
environment:
sdk: ^3.8.1
@@ -52,7 +52,7 @@ dependencies:
share_plus: ^12.0.1
collection: ^1.18.0
flutter_timezone: ^5.0.1
json_annotation: ^4.10.0
json_annotation: ^4.11.0
go_router: ^17.0.0
jovial_svg: ^1.1.23
cached_network_image: ^3.4.1

View File

@@ -1,30 +1,33 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:pweb/app/app.dart';
import 'package:pweb/providers/account.dart';
import 'package:pweb/providers/locale.dart';
import 'package:pshared/provider/account.dart';
import 'package:pshared/provider/locale.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const PayApp());
testWidgets('PayApp builds with required providers', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MultiProvider(
providers: [
ChangeNotifierProvider<LocaleProvider>(
create: (_) => PwebLocaleProvider(null),
),
ChangeNotifierProxyProvider<LocaleProvider, AccountProvider>(
create: (_) => PwebAccountProvider(),
update: (context, localeProvider, provider) =>
provider!..updateProvider(localeProvider),
),
],
child: const PayApp(),
),
);
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
await tester.pumpAndSettle();
expect(find.byType(PayApp), findsOneWidget);
});
}