complete MECE request

This commit is contained in:
Stephan D
2026-02-24 18:33:12 +01:00
parent 4c5677202a
commit a998b59072
34 changed files with 957 additions and 156 deletions

View File

@@ -4,7 +4,6 @@ import 'package:pshared/data/dto/money.dart';
part 'fx_quote.g.dart';
@JsonSerializable()
class FxQuoteDTO {
final String? quoteRef;
@@ -15,6 +14,7 @@ class FxQuoteDTO {
final MoneyDTO? baseAmount;
final MoneyDTO? quoteAmount;
final int? expiresAtUnixMs;
final int? pricedAtUnixMs;
final String? provider;
final String? rateRef;
@@ -30,11 +30,13 @@ class FxQuoteDTO {
this.baseAmount,
this.quoteAmount,
this.expiresAtUnixMs,
this.pricedAtUnixMs,
this.provider,
this.rateRef,
this.firm = false,
});
factory FxQuoteDTO.fromJson(Map<String, dynamic> json) => _$FxQuoteDTOFromJson(json);
factory FxQuoteDTO.fromJson(Map<String, dynamic> json) =>
_$FxQuoteDTOFromJson(json);
Map<String, dynamic> toJson() => _$FxQuoteDTOToJson(this);
}

View File

@@ -2,35 +2,36 @@ import 'package:pshared/data/dto/payment/fx_quote.dart';
import 'package:pshared/data/mapper/money.dart';
import 'package:pshared/models/payment/fx/quote.dart';
extension FxQuoteDTOMapper on FxQuoteDTO {
FxQuote toDomain() => FxQuote(
quoteRef: quoteRef,
baseCurrency: baseCurrency,
quoteCurrency: quoteCurrency,
side: side,
price: price,
baseAmount: baseAmount?.toDomain(),
quoteAmount: quoteAmount?.toDomain(),
expiresAtUnixMs: expiresAtUnixMs,
provider: provider,
rateRef: rateRef,
firm: firm ?? false,
);
quoteRef: quoteRef,
baseCurrency: baseCurrency,
quoteCurrency: quoteCurrency,
side: side,
price: price,
baseAmount: baseAmount?.toDomain(),
quoteAmount: quoteAmount?.toDomain(),
expiresAtUnixMs: expiresAtUnixMs,
pricedAtUnixMs: pricedAtUnixMs,
provider: provider,
rateRef: rateRef,
firm: firm ?? false,
);
}
extension FxQuoteMapper on FxQuote {
FxQuoteDTO toDTO() => FxQuoteDTO(
quoteRef: quoteRef,
baseCurrency: baseCurrency,
quoteCurrency: quoteCurrency,
side: side,
price: price,
baseAmount: baseAmount?.toDTO(),
quoteAmount: quoteAmount?.toDTO(),
expiresAtUnixMs: expiresAtUnixMs,
provider: provider,
rateRef: rateRef,
firm: firm,
);
quoteRef: quoteRef,
baseCurrency: baseCurrency,
quoteCurrency: quoteCurrency,
side: side,
price: price,
baseAmount: baseAmount?.toDTO(),
quoteAmount: quoteAmount?.toDTO(),
expiresAtUnixMs: expiresAtUnixMs,
pricedAtUnixMs: pricedAtUnixMs,
provider: provider,
rateRef: rateRef,
firm: firm,
);
}

View File

@@ -1,5 +1,5 @@
import 'package:pshared/data/dto/payment/payment.dart';
import 'package:pshared/data/mapper/payment/payment_quote.dart';
import 'package:pshared/data/mapper/payment/quote.dart';
import 'package:pshared/models/payment/payment.dart';
import 'package:pshared/models/payment/state.dart';

View File

@@ -1,5 +1,5 @@
import 'package:pshared/data/dto/payment/payment_quote.dart';
import 'package:pshared/data/mapper/payment/fee_line.dart';
import 'package:pshared/data/mapper/payment/fees/line.dart';
import 'package:pshared/data/mapper/payment/fx_quote.dart';
import 'package:pshared/data/mapper/money.dart';
import 'package:pshared/data/mapper/payment/network_fee.dart';

View File

@@ -1,5 +1,5 @@
import 'package:pshared/data/dto/payment/quotes.dart';
import 'package:pshared/data/mapper/payment/payment_quote.dart';
import 'package:pshared/data/mapper/payment/quote.dart';
import 'package:pshared/data/mapper/payment/quote/aggregate.dart';
import 'package:pshared/models/payment/quote/quotes.dart';

View File

@@ -1,6 +1,5 @@
import 'package:pshared/models/money.dart';
class FxQuote {
final String? quoteRef;
final String? baseCurrency;
@@ -10,6 +9,7 @@ class FxQuote {
final Money? baseAmount;
final Money? quoteAmount;
final int? expiresAtUnixMs;
final int? pricedAtUnixMs;
final String? provider;
final String? rateRef;
final bool firm;
@@ -23,6 +23,7 @@ class FxQuote {
required this.baseAmount,
required this.quoteAmount,
required this.expiresAtUnixMs,
required this.pricedAtUnixMs,
required this.provider,
required this.rateRef,
this.firm = false,

View File

@@ -12,7 +12,6 @@ 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);
@@ -87,10 +86,13 @@ class MultiQuotationProvider extends ChangeNotifier {
_setResource(_quotation.copyWith(isLoading: true, error: null));
try {
final effectiveIdempotencyKey = previewOnly
? ''
: (idempotencyKey ?? const Uuid().v4());
final response = await MultiplePaymentsService.getQuotation(
organization.current.id,
QuotePaymentsRequest(
idempotencyKey: idempotencyKey ?? const Uuid().v4(),
idempotencyKey: effectiveIdempotencyKey,
metadata: metadata,
intents: intents.map((intent) => intent.toDTO()).toList(),
previewOnly: previewOnly,

View File

@@ -4,7 +4,7 @@ import 'package:pshared/api/requests/payment/quote.dart';
import 'package:pshared/api/requests/payment/quotes.dart';
import 'package:pshared/api/responses/payment/quotation.dart';
import 'package:pshared/api/responses/payment/quotes.dart';
import 'package:pshared/data/mapper/payment/payment_quote.dart';
import 'package:pshared/data/mapper/payment/quote.dart';
import 'package:pshared/data/mapper/payment/quote/quotes.dart';
import 'package:pshared/models/payment/quote/quote.dart';
import 'package:pshared/models/payment/quote/quotes.dart';

View File

@@ -5,6 +5,7 @@ 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/quotation.dart';
import 'package:pshared/data/dto/money.dart';
import 'package:pshared/data/dto/payment/endpoint.dart';
import 'package:pshared/data/dto/payment/intent/payment.dart';
@@ -40,7 +41,7 @@ void main() {
test('quote payment request uses expected backend field names', () {
final request = QuotePaymentRequest(
idempotencyKey: 'idem-1',
idempotencyKey: '',
previewOnly: true,
intent: const PaymentIntentDTO(
kind: 'payout',
@@ -60,7 +61,7 @@ void main() {
final json =
jsonDecode(jsonEncode(request.toJson())) as Map<String, dynamic>;
expect(json['idempotencyKey'], equals('idem-1'));
expect(json['idempotencyKey'], equals(''));
expect(json['previewOnly'], isTrue);
expect(json['intent'], isA<Map<String, dynamic>>());
@@ -75,6 +76,34 @@ void main() {
expect(destination['type'], equals('cardToken'));
});
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',
'debitAmount': {'amount': '10', 'currency': 'USDT'},
'expectedSettlementAmount': {'amount': '760', 'currency': 'RUB'},
'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));
});
test('initiate payment by quote keeps expected fields', () {
final request = InitiatePaymentRequest(
idempotencyKey: 'idem-2',