From 530432e3f8a9a5528f18e68f1da7d8de1f31c00b Mon Sep 17 00:00:00 2001 From: Arseni Date: Fri, 13 Mar 2026 16:07:22 +0300 Subject: [PATCH] fix for quote and operations addition --- .../lib/data/dto/payment/operation.dart | 8 +- .../lib/data/dto/payment/operation_money.dart | 31 +++ .../lib/data/mapper/payment/operation.dart | 36 ++- .../models/payment/execution_operation.dart | 31 ++- .../pshared/lib/models/payment/operation.dart | 4 +- .../provider/payment/multiple/quotation.dart | 14 ++ .../provider/payment/quotation/quotation.dart | 36 ++- .../test/payment/request_dto_format_test.dart | 225 ------------------ .../details/sections/operations/tile.dart | 35 ++- frontend/pweb/lib/pages/report/table/row.dart | 2 +- frontend/pweb/lib/services/operations.dart | 85 ------- .../pweb/lib/utils/report/payment_mapper.dart | 19 +- interface/models/payment/payment.yaml | 33 ++- 13 files changed, 200 insertions(+), 359 deletions(-) create mode 100644 frontend/pshared/lib/data/dto/payment/operation_money.dart delete mode 100644 frontend/pshared/test/payment/request_dto_format_test.dart delete mode 100644 frontend/pweb/lib/services/operations.dart diff --git a/frontend/pshared/lib/data/dto/payment/operation.dart b/frontend/pshared/lib/data/dto/payment/operation.dart index 232560d4..fed5a2ce 100644 --- a/frontend/pshared/lib/data/dto/payment/operation.dart +++ b/frontend/pshared/lib/data/dto/payment/operation.dart @@ -1,6 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:pshared/data/dto/money.dart'; +import 'package:pshared/data/dto/payment/operation_money.dart'; part 'operation.g.dart'; @@ -13,8 +13,7 @@ class PaymentOperationDTO { final String? code; final String? state; final String? label; - final MoneyDTO? amount; - final MoneyDTO? convertedAmount; + final PaymentOperationMoneyDTO? money; final String? failureCode; final String? failureReason; final String? startedAt; @@ -27,8 +26,7 @@ class PaymentOperationDTO { this.code, this.state, this.label, - this.amount, - this.convertedAmount, + this.money, this.failureCode, this.failureReason, this.startedAt, diff --git a/frontend/pshared/lib/data/dto/payment/operation_money.dart b/frontend/pshared/lib/data/dto/payment/operation_money.dart new file mode 100644 index 00000000..c3f2229a --- /dev/null +++ b/frontend/pshared/lib/data/dto/payment/operation_money.dart @@ -0,0 +1,31 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:pshared/data/dto/money.dart'; + +part 'operation_money.g.dart'; + + +@JsonSerializable() +class PaymentOperationMoneySnapshotDTO { + final MoneyDTO? amount; + final MoneyDTO? convertedAmount; + + const PaymentOperationMoneySnapshotDTO({this.amount, this.convertedAmount}); + + factory PaymentOperationMoneySnapshotDTO.fromJson( + Map json, + ) => _$PaymentOperationMoneySnapshotDTOFromJson(json); + Map toJson() => + _$PaymentOperationMoneySnapshotDTOToJson(this); +} + +@JsonSerializable() +class PaymentOperationMoneyDTO { + final PaymentOperationMoneySnapshotDTO? planned; + final PaymentOperationMoneySnapshotDTO? executed; + + const PaymentOperationMoneyDTO({this.planned, this.executed}); + + factory PaymentOperationMoneyDTO.fromJson(Map json) => + _$PaymentOperationMoneyDTOFromJson(json); + Map toJson() => _$PaymentOperationMoneyDTOToJson(this); +} diff --git a/frontend/pshared/lib/data/mapper/payment/operation.dart b/frontend/pshared/lib/data/mapper/payment/operation.dart index 3dec1220..a6b25e6f 100644 --- a/frontend/pshared/lib/data/mapper/payment/operation.dart +++ b/frontend/pshared/lib/data/mapper/payment/operation.dart @@ -1,4 +1,5 @@ import 'package:pshared/data/dto/payment/operation.dart'; +import 'package:pshared/data/dto/payment/operation_money.dart'; import 'package:pshared/data/mapper/money.dart'; import 'package:pshared/models/payment/execution_operation.dart'; @@ -11,8 +12,7 @@ extension PaymentOperationDTOMapper on PaymentOperationDTO { code: code, state: state, label: label, - amount: amount?.toDomain(), - convertedAmount: convertedAmount?.toDomain(), + money: money?.toDomain(), failureCode: failureCode, failureReason: failureReason, startedAt: _parseDateTime(startedAt), @@ -28,8 +28,7 @@ extension PaymentExecutionOperationMapper on PaymentExecutionOperation { code: code, state: state, label: label, - amount: amount?.toDTO(), - convertedAmount: convertedAmount?.toDTO(), + money: money?.toDTO(), failureCode: failureCode, failureReason: failureReason, startedAt: startedAt?.toUtc().toIso8601String(), @@ -37,6 +36,35 @@ extension PaymentExecutionOperationMapper on PaymentExecutionOperation { ); } +extension PaymentOperationMoneyDTOMapper on PaymentOperationMoneyDTO { + PaymentOperationMoney toDomain() => PaymentOperationMoney( + planned: planned?.toDomain(), + executed: executed?.toDomain(), + ); +} + +extension PaymentOperationMoneyMapper on PaymentOperationMoney { + PaymentOperationMoneyDTO toDTO() => PaymentOperationMoneyDTO( + planned: planned?.toDTO(), + executed: executed?.toDTO(), + ); +} + +extension PaymentOperationMoneySnapshotDTOMapper + on PaymentOperationMoneySnapshotDTO { + PaymentOperationMoneySnapshot toDomain() => PaymentOperationMoneySnapshot( + amount: amount?.toDomain(), + convertedAmount: convertedAmount?.toDomain(), + ); +} + +extension PaymentOperationMoneySnapshotMapper on PaymentOperationMoneySnapshot { + PaymentOperationMoneySnapshotDTO toDTO() => PaymentOperationMoneySnapshotDTO( + amount: amount?.toDTO(), + convertedAmount: convertedAmount?.toDTO(), + ); +} + DateTime? _parseDateTime(String? value) { final normalized = value?.trim(); if (normalized == null || normalized.isEmpty) return null; diff --git a/frontend/pshared/lib/models/payment/execution_operation.dart b/frontend/pshared/lib/models/payment/execution_operation.dart index 1ee99028..d0f1c7f5 100644 --- a/frontend/pshared/lib/models/payment/execution_operation.dart +++ b/frontend/pshared/lib/models/payment/execution_operation.dart @@ -1,6 +1,23 @@ import 'package:money2/money2.dart'; +class PaymentOperationMoneySnapshot { + final Money? amount; + final Money? convertedAmount; + + const PaymentOperationMoneySnapshot({ + required this.amount, + required this.convertedAmount, + }); +} + +class PaymentOperationMoney { + final PaymentOperationMoneySnapshot? planned; + final PaymentOperationMoneySnapshot? executed; + + const PaymentOperationMoney({required this.planned, required this.executed}); +} + class PaymentExecutionOperation { final String? stepRef; final String? operationRef; @@ -8,8 +25,7 @@ class PaymentExecutionOperation { final String? code; final String? state; final String? label; - final Money? amount; - final Money? convertedAmount; + final PaymentOperationMoney? money; final String? failureCode; final String? failureReason; final DateTime? startedAt; @@ -22,11 +38,18 @@ class PaymentExecutionOperation { required this.code, required this.state, required this.label, - required this.amount, - required this.convertedAmount, + required this.money, required this.failureCode, required this.failureReason, required this.startedAt, required this.completedAt, }); + + Money? get amount => money?.executed?.amount ?? money?.planned?.amount; + Money? get convertedAmount => + money?.executed?.convertedAmount ?? money?.planned?.convertedAmount; + Money? get plannedAmount => money?.planned?.amount; + Money? get plannedConvertedAmount => money?.planned?.convertedAmount; + Money? get executedAmount => money?.executed?.amount; + Money? get executedConvertedAmount => money?.executed?.convertedAmount; } diff --git a/frontend/pshared/lib/models/payment/operation.dart b/frontend/pshared/lib/models/payment/operation.dart index 66d00a2e..9071c35c 100644 --- a/frontend/pshared/lib/models/payment/operation.dart +++ b/frontend/pshared/lib/models/payment/operation.dart @@ -17,7 +17,7 @@ class OperationItem { final PaymentMethod? paymentMethod; final String name; final DateTime date; - final String comment; + // final String comment; OperationItem({ required this.status, @@ -34,6 +34,6 @@ class OperationItem { this.paymentMethod, required this.name, required this.date, - required this.comment, + // required this.comment, }); } diff --git a/frontend/pshared/lib/provider/payment/multiple/quotation.dart b/frontend/pshared/lib/provider/payment/multiple/quotation.dart index 077cbff2..99a0e7b1 100644 --- a/frontend/pshared/lib/provider/payment/multiple/quotation.dart +++ b/frontend/pshared/lib/provider/payment/multiple/quotation.dart @@ -5,6 +5,7 @@ import 'package:uuid/uuid.dart'; import 'package:pshared/api/requests/payment/quotes.dart'; import 'package:pshared/data/mapper/payment/intent/payment.dart'; import 'package:pshared/models/payment/intent.dart'; +import 'package:pshared/models/payment/kind.dart'; import 'package:pshared/models/payment/quote/quotes.dart'; import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/payment/auto_refresh.dart'; @@ -12,6 +13,7 @@ import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/payment/multiple.dart'; import 'package:pshared/utils/exception.dart'; + class MultiQuotationProvider extends ChangeNotifier { static const Duration _autoRefreshLead = Duration(seconds: 5); @@ -77,6 +79,11 @@ class MultiQuotationProvider extends ChangeNotifier { if (intents.isEmpty) { throw StateError('At least one payment intent is required'); } + if (intents.any((intent) => !_isQuotable(intent))) { + throw StateError( + 'Each payment intent must include kind, source, destination, and amount', + ); + } _lastIntents = List.from(intents); _lastPreviewOnly = previewOnly; @@ -135,6 +142,13 @@ class MultiQuotationProvider extends ChangeNotifier { notifyListeners(); } + bool _isQuotable(PaymentIntent intent) { + if (intent.kind == PaymentKind.unspecified) return false; + if (intent.source == null) return false; + if (intent.destination == null) return false; + return intent.amount != null; + } + void _setResource(Resource quotation) { _quotation = quotation; _syncAutoRefresh(); diff --git a/frontend/pshared/lib/provider/payment/quotation/quotation.dart b/frontend/pshared/lib/provider/payment/quotation/quotation.dart index bd871e78..53294d50 100644 --- a/frontend/pshared/lib/provider/payment/quotation/quotation.dart +++ b/frontend/pshared/lib/provider/payment/quotation/quotation.dart @@ -12,6 +12,7 @@ import 'package:pshared/api/requests/payment/quote.dart'; import 'package:pshared/controllers/payment/source.dart'; import 'package:pshared/data/mapper/payment/intent/payment.dart'; import 'package:pshared/models/payment/intent.dart'; +import 'package:pshared/models/payment/kind.dart'; import 'package:pshared/models/payment/quote/quote.dart'; import 'package:pshared/models/auto_refresh_mode.dart'; import 'package:pshared/provider/organizations.dart'; @@ -52,13 +53,17 @@ class QuotationProvider extends ChangeNotifier { ) { _organizations = venue; _sourceCurrencyCode = source.selectedCurrencyCode; - final intent = _intentBuilder.build( + final builtIntent = _intentBuilder.build( payment: payment, source: source, flow: flow, recipients: recipients, ); - if (intent == null) return; + if (!_isQuotable(builtIntent)) { + _clearQuotation(); + return; + } + final intent = builtIntent!; final intentKey = _buildIntentKey(intent); final lastIntent = _lastIntent; if (lastIntent != null && intentKey == _buildIntentKey(lastIntent)) return; @@ -100,12 +105,19 @@ class QuotationProvider extends ChangeNotifier { } Future refreshQuotation() async { - final intent = _lastIntent; - if (intent == null) return null; - return getQuotation(intent); + final lastIntent = _lastIntent; + if (!_isQuotable(lastIntent)) { + _clearQuotation(); + return null; + } + return getQuotation(lastIntent!); } Future getQuotation(PaymentIntent intent) async { + if (!_isQuotable(intent)) { + _clearQuotation(); + return null; + } if (!_organizations.isOrganizationSet) { throw StateError('Organization is not set'); } @@ -138,8 +150,20 @@ class QuotationProvider extends ChangeNotifier { void reset() { _isLoaded = false; - _lastIntent = null; _sourceCurrencyCode = null; + _clearQuotation(); + } + + bool _isQuotable(PaymentIntent? intent) { + if (intent == null) return false; + if (intent.kind == PaymentKind.unspecified) return false; + if (intent.source == null) return false; + if (intent.destination == null) return false; + return intent.amount != null; + } + + void _clearQuotation() { + _lastIntent = null; _setResource(Resource(data: null, isLoading: false, error: null)); } diff --git a/frontend/pshared/test/payment/request_dto_format_test.dart b/frontend/pshared/test/payment/request_dto_format_test.dart deleted file mode 100644 index 06eff0e2..00000000 --- a/frontend/pshared/test/payment/request_dto_format_test.dart +++ /dev/null @@ -1,225 +0,0 @@ -import 'dart:convert'; - -import 'package:test/test.dart'; - -import 'package:pshared/api/requests/payment/initiate.dart'; -import 'package:pshared/api/requests/payment/initiate_payments.dart'; -import 'package:pshared/api/requests/payment/quote.dart'; -import 'package:pshared/api/responses/payment/payment.dart'; -import 'package:pshared/api/responses/payment/quotation.dart'; -import 'package:pshared/data/dto/money.dart'; -import 'package:pshared/data/dto/payment/currency_pair.dart'; -import 'package:pshared/data/dto/payment/endpoint.dart'; -import 'package:pshared/data/dto/payment/intent/fx.dart'; -import 'package:pshared/data/dto/payment/intent/payment.dart'; -import 'package:pshared/data/mapper/payment/payment.dart'; -import 'package:pshared/data/mapper/payment/payment_response.dart'; -import 'package:pshared/models/payment/asset.dart'; -import 'package:pshared/models/payment/chain_network.dart'; -import 'package:pshared/models/payment/methods/card_token.dart'; -import 'package:pshared/models/payment/methods/crypto_address.dart'; -import 'package:pshared/models/payment/methods/managed_wallet.dart'; -import 'package:pshared/models/payment/methods/wallet.dart'; - -void main() { - group('Payment request DTO contract', () { - test('serializes endpoint types to backend canonical values', () { - final managed = ManagedWalletPaymentMethod( - managedWalletRef: 'mw-1', - ).toDTO(); - final external = CryptoAddressPaymentMethod( - asset: const PaymentAsset( - chain: ChainNetwork.tronMainnet, - tokenSymbol: 'USDT', - ), - address: 'TXYZ', - ).toDTO(); - final cardToken = CardTokenPaymentMethod( - token: 'tok_1', - maskedPan: '4111', - ).toDTO(); - - expect(managed.type, equals('managedWallet')); - expect(external.type, equals('cryptoAddress')); - expect(cardToken.type, equals('cardToken')); - }); - - test('quote payment request uses expected backend field names', () { - final request = QuotePaymentRequest( - idempotencyKey: '', - previewOnly: true, - intent: const PaymentIntentDTO( - kind: 'payout', - source: PaymentEndpointDTO( - type: 'ledger', - data: {'ledger_account_ref': 'ledger:src'}, - ), - destination: PaymentEndpointDTO( - type: 'cardToken', - data: {'token': 'tok_1', 'masked_pan': '4111'}, - ), - amount: MoneyDTO(amount: '10', currency: 'USD'), - settlementMode: 'fix_received', - comment: 'invoice-7', - ), - ); - - final json = - jsonDecode(jsonEncode(request.toJson())) as Map; - - expect(json['idempotencyKey'], equals('')); - expect(json['previewOnly'], isTrue); - expect(json['intent'], isA>()); - - final intent = json['intent'] as Map; - expect(intent['kind'], equals('payout')); - expect(intent['settlement_mode'], equals('fix_received')); - expect(intent['comment'], equals('invoice-7')); - expect(intent.containsKey('settlement_currency'), isFalse); - - final source = intent['source'] as Map; - final destination = intent['destination'] as Map; - expect(source['type'], equals('ledger')); - expect(destination['type'], equals('cardToken')); - }); - - test('quote payment request serializes fx side to backend value', () { - final request = QuotePaymentRequest( - idempotencyKey: '', - previewOnly: true, - intent: const PaymentIntentDTO( - kind: 'payout', - source: PaymentEndpointDTO( - type: 'managedWallet', - data: {'managed_wallet_ref': 'mw-1'}, - ), - destination: PaymentEndpointDTO( - type: 'cardToken', - data: {'token': 'tok_1', 'masked_pan': '4111'}, - ), - amount: MoneyDTO(amount: '10', currency: 'USDT'), - settlementMode: 'fix_source', - fx: FxIntentDTO( - pair: CurrencyPairDTO(base: 'RUB', quote: 'USDT'), - side: 'buy_base_sell_quote', - ), - ), - ); - - final json = - jsonDecode(jsonEncode(request.toJson())) as Map; - final intent = json['intent'] as Map; - final fx = intent['fx'] as Map; - expect(fx['side'], equals('buy_base_sell_quote')); - }); - - test('quote response parses backend fx quote pricedAtUnixMs', () { - final response = PaymentQuoteResponse.fromJson({ - 'accessToken': {'token': 'token', 'expiration': '2026-02-25T00:00:00Z'}, - 'idempotencyKey': 'idem-1', - 'quote': { - 'quoteRef': 'q-1', - 'intentRef': 'intent-1', - 'amounts': { - 'sourcePrincipal': {'amount': '10', 'currency': 'USDT'}, - 'sourceDebitTotal': {'amount': '10.75', 'currency': 'USDT'}, - 'destinationSettlement': {'amount': '760', 'currency': 'RUB'}, - }, - 'fees': { - 'lines': [ - { - 'ledgerAccountRef': 'ledger:fees', - 'amount': {'amount': '0.75', 'currency': 'USDT'}, - 'lineType': 'posting_line_type_fee', - 'side': 'entry_side_debit', - 'meta': {'fee_target': 'wallet'}, - }, - ], - }, - 'fxQuote': { - 'quoteRef': 'fx-1', - 'baseCurrency': 'USDT', - 'quoteCurrency': 'RUB', - 'side': 'sell_base_buy_quote', - 'price': '76', - 'baseAmount': {'amount': '10', 'currency': 'USDT'}, - 'quoteAmount': {'amount': '760', 'currency': 'RUB'}, - 'expiresAtUnixMs': 1771945907749, - 'pricedAtUnixMs': 1771945907000, - 'provider': 'binance', - 'rateRef': 'rate-1', - 'firm': false, - }, - }, - }); - - expect(response.quote.fxQuote?.pricedAtUnixMs, equals(1771945907000)); - expect(response.quote.intentRef, equals('intent-1')); - expect(response.quote.amounts?.sourceDebitTotal?.amount, equals('10.75')); - expect(response.quote.fees?.lines?.length, equals(1)); - }); - - test('initiate payment by quote keeps expected fields', () { - final request = InitiatePaymentRequest( - idempotencyKey: 'idem-2', - quoteRef: 'q-1', - clientPaymentRef: 'cp-1', - ); - - final json = request.toJson(); - expect(json['idempotencyKey'], equals('idem-2')); - expect(json['quoteRef'], equals('q-1')); - expect(json['clientPaymentRef'], equals('cp-1')); - expect(json.containsKey('intent'), isTrue); - expect(json['intent'], isNull); - }); - - test('initiate multi payments request keeps expected fields', () { - final request = InitiatePaymentsRequest( - idempotencyKey: 'idem-3', - quoteRef: 'q-2', - clientPaymentRef: 'cp-1', - ); - - final json = request.toJson(); - expect(json['idempotencyKey'], equals('idem-3')); - expect(json['quoteRef'], equals('q-2')); - expect(json['clientPaymentRef'], equals('cp-1')); - expect(json.containsKey('intentRef'), isFalse); - expect(json.containsKey('intentRefs'), isFalse); - }); - - test( - 'payment response parses source and destination endpoint snapshots', - () { - final response = PaymentResponse.fromJson({ - 'accessToken': { - 'token': 'token', - 'expiration': '2026-02-25T00:00:00Z', - }, - 'payment': { - 'paymentRef': 'pay-1', - 'state': 'orchestration_state_created', - 'source': { - 'type': 'wallet', - 'data': {'walletId': 'wallet-1'}, - }, - 'destination': {'paymentMethodRef': 'pm-123'}, - 'operations': [], - 'meta': {'quotationRef': 'quote-1'}, - }, - }); - - final payment = response.payment.toDomain(); - expect(payment.paymentRef, equals('pay-1')); - expect(payment.source, isNotNull); - expect(payment.destination, isNotNull); - expect(payment.destination?.paymentMethodRef, equals('pm-123')); - expect(payment.source?.method, isA()); - - final sourceMethod = payment.source?.method as WalletPaymentMethod; - expect(sourceMethod.walletId, equals('wallet-1')); - }, - ); - }); -} diff --git a/frontend/pweb/lib/pages/report/details/sections/operations/tile.dart b/frontend/pweb/lib/pages/report/details/sections/operations/tile.dart index 4c867008..0cc9b689 100644 --- a/frontend/pweb/lib/pages/report/details/sections/operations/tile.dart +++ b/frontend/pweb/lib/pages/report/details/sections/operations/tile.dart @@ -27,11 +27,15 @@ class OperationHistoryTile extends StatelessWidget { Widget build(BuildContext context) { final loc = AppLocalizations.of(context)!; final theme = Theme.of(context); - final title = resolveOperationTitle(loc, operation.code); final operationLabel = operation.label?.trim(); + final title = (operationLabel != null && operationLabel.isNotEmpty) + ? operationLabel + : resolveOperationTitle(loc, operation.code); + // final operationComment = operation.comment?.trim(); final stateView = resolveStepStateView(context, operation.state); final completedAt = formatCompletedAt(context, operation.completedAt); final amount = formatMoneyUi(context, operation.amount); + final convertedAmount = formatMoneyUi(context, operation.convertedAmount); final canDownload = canDownloadDocument && onDownloadDocument != null; return Column( @@ -52,17 +56,6 @@ class OperationHistoryTile extends StatelessWidget { StepStateChip(view: stateView), ], ), - if (operationLabel != null && - operationLabel.isNotEmpty && - operationLabel != title) ...[ - const SizedBox(height: 4), - Text( - operationLabel, - style: theme.textTheme.bodySmall?.copyWith( - color: theme.colorScheme.onSurfaceVariant, - ), - ), - ], const SizedBox(height: 6), Text( '${loc.completedAtLabel}: $completedAt', @@ -77,6 +70,24 @@ class OperationHistoryTile extends StatelessWidget { color: theme.colorScheme.onSurfaceVariant, ), ), + if (operation.convertedAmount != null) ...[ + const SizedBox(height: 4), + Text( + '${loc.toAmountColumn}: $convertedAmount', + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + // if (operationComment != null && operationComment.isNotEmpty) ...[ + // const SizedBox(height: 4), + // Text( + // '${loc.comment}: $operationComment', + // style: theme.textTheme.bodySmall?.copyWith( + // color: theme.colorScheme.onSurfaceVariant, + // ), + // ), + // ], if (canDownload) ...[ const SizedBox(height: 8), TextButton.icon( diff --git a/frontend/pweb/lib/pages/report/table/row.dart b/frontend/pweb/lib/pages/report/table/row.dart index d316001c..ab064e80 100644 --- a/frontend/pweb/lib/pages/report/table/row.dart +++ b/frontend/pweb/lib/pages/report/table/row.dart @@ -58,7 +58,7 @@ class OperationRow { DataCell(Text(op.cardNumber ?? '-')), DataCell(Text(op.name)), DataCell(Text(dateLabel)), - DataCell(Text(op.comment)), + // DataCell(Text(op.comment)), ], ); } diff --git a/frontend/pweb/lib/services/operations.dart b/frontend/pweb/lib/services/operations.dart deleted file mode 100644 index 4115c80b..00000000 --- a/frontend/pweb/lib/services/operations.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:pshared/models/payment/operation.dart'; -import 'package:pshared/models/payment/status.dart'; - - -class OperationService { - Future> fetchOperations() async { - await Future.delayed(const Duration(milliseconds: 500)); - - return [ - OperationItem( - status: OperationStatus.error, - fileName: 'cards_payout_sample_june.csv', - amount: 10, - currency: 'EUR', - toAmount: 10, - toCurrency: 'EUR', - payId: '860163800', - cardNumber: null, - name: 'John Snow', - date: DateTime(2025, 7, 14, 19, 59, 2), - comment: 'EUR visa', - ), - OperationItem( - status: OperationStatus.processing, - fileName: 'cards_payout_sample_june.csv', - amount: 10, - currency: 'EUR', - toAmount: 10, - toCurrency: 'EUR', - payId: '860163700', - cardNumber: null, - name: 'Baltasar Gelt', - date: DateTime(2025, 7, 14, 19, 59, 2), - comment: 'EUR master', - ), - OperationItem( - status: OperationStatus.error, - fileName: 'cards_payout_sample_june.csv', - amount: 10, - currency: 'EUR', - toAmount: 10, - toCurrency: 'EUR', - payId: '40000000****0077', - cardNumber: '40000000****0077', - name: 'John Snow', - date: DateTime(2025, 7, 14, 19, 23, 22), - comment: 'EUR visa', - ), - OperationItem( - status: OperationStatus.success, - fileName: null, - amount: 10, - currency: 'EUR', - toAmount: 10, - toCurrency: 'EUR', - payId: '54133300****0019', - cardNumber: '54133300****0019', - name: 'Baltasar Gelt', - date: DateTime(2025, 7, 14, 19, 23, 21), - comment: 'EUR master', - ), - OperationItem( - status: OperationStatus.success, - fileName: null, - amount: 130, - currency: 'EUR', - toAmount: 130, - toCurrency: 'EUR', - payId: '54134300****0019', - cardNumber: '54153300****0019', - name: 'Ivan Brokov', - date: DateTime(2025, 7, 15, 19, 23, 21), - comment: 'EUR master 2', - ), - ]; - } - - // Add real API: - // Future> fetchOperations() async { - // final response = await _httpClient.get('/api/operations'); - // return (response.data as List) - // .map((json) => OperationItem.fromJson(json)) - // .toList(); - // } -} diff --git a/frontend/pweb/lib/utils/report/payment_mapper.dart b/frontend/pweb/lib/utils/report/payment_mapper.dart index 68025e70..127527c3 100644 --- a/frontend/pweb/lib/utils/report/payment_mapper.dart +++ b/frontend/pweb/lib/utils/report/payment_mapper.dart @@ -23,14 +23,15 @@ OperationItem mapPaymentToOperation(Payment payment) { final payId = _firstNonEmpty([payment.paymentRef]) ?? '-'; final name = _firstNonEmpty([payment.lastQuote?.quoteRef, payment.paymentRef]) ?? '-'; - final comment = - _firstNonEmpty([ - payment.comment, - payment.failureReason, - payment.failureCode, - payment.state, - ]) ?? - ''; + // final comment = + // _firstNonEmpty([ + // ...payment.operations.map((operation) => operation.comment), + // payment.comment, + // payment.failureReason, + // payment.failureCode, + // payment.state, + // ]) ?? + // ''; final operationDocument = _resolveOperationDocument(payment); return OperationItem( @@ -47,7 +48,7 @@ OperationItem mapPaymentToOperation(Payment payment) { cardNumber: null, name: name, date: resolvePaymentDate(payment), - comment: comment, + // comment: comment, ); } diff --git a/interface/models/payment/payment.yaml b/interface/models/payment/payment.yaml index c8f0b3bc..c6c474fe 100644 --- a/interface/models/payment/payment.yaml +++ b/interface/models/payment/payment.yaml @@ -480,12 +480,9 @@ components: label: description: Human-readable operation label. type: string - amount: - description: Primary money amount associated with the operation. - $ref: ../common/money.yaml#/components/schemas/Money - convertedAmount: - description: Secondary amount for conversion operations (for example FX convert output amount). - $ref: ../common/money.yaml#/components/schemas/Money + money: + description: Planned and executed monetary snapshots for the operation. + $ref: ./payment.yaml#/components/schemas/PaymentOperationMoney operationRef: description: External operation reference identifier reported by the gateway. type: string @@ -507,6 +504,30 @@ components: type: string format: date-time + PaymentOperationMoney: + description: Planned and executed monetary snapshots associated with the operation. + type: object + additionalProperties: false + properties: + planned: + description: Monetary values planned for the operation before execution. + $ref: ./payment.yaml#/components/schemas/PaymentOperationMoneySnapshot + executed: + description: Monetary values observed after the operation was executed. + $ref: ./payment.yaml#/components/schemas/PaymentOperationMoneySnapshot + + PaymentOperationMoneySnapshot: + description: Monetary snapshot containing base and converted amounts for an operation state. + type: object + additionalProperties: false + properties: + amount: + description: Primary money amount associated with the operation snapshot. + $ref: ../common/money.yaml#/components/schemas/Money + convertedAmount: + description: Secondary amount for conversion operations in the same snapshot. + $ref: ../common/money.yaml#/components/schemas/Money + Payment: description: Persisted payment aggregate with status, quote, and operation history. type: object