+source +destination in payments

This commit is contained in:
Stephan D
2026-03-10 19:15:20 +01:00
parent 9c2b3bf8bd
commit e5b4de5d48
16 changed files with 716 additions and 56 deletions

View File

@@ -2,15 +2,16 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:pshared/data/dto/payment/operation.dart';
import 'package:pshared/data/dto/payment/payment_quote.dart';
import 'package:pshared/data/dto/payment/response_endpoint.dart';
part 'payment.g.dart';
@JsonSerializable()
class PaymentDTO {
final String? paymentRef;
final String? idempotencyKey;
final String? state;
final PaymentResponseEndpointDTO? source;
final PaymentResponseEndpointDTO? destination;
final String? failureCode;
final String? failureReason;
final List<PaymentOperationDTO> operations;
@@ -20,8 +21,9 @@ class PaymentDTO {
const PaymentDTO({
this.paymentRef,
this.idempotencyKey,
this.state,
this.source,
this.destination,
this.failureCode,
this.failureReason,
this.operations = const <PaymentOperationDTO>[],

View File

@@ -0,0 +1,22 @@
import 'package:json_annotation/json_annotation.dart';
part 'response_endpoint.g.dart';
@JsonSerializable()
class PaymentResponseEndpointDTO {
final String? type;
final Map<String, dynamic>? data;
final String? paymentMethodRef;
final String? payeeRef;
const PaymentResponseEndpointDTO({
this.type,
this.data,
this.paymentMethodRef,
this.payeeRef,
});
factory PaymentResponseEndpointDTO.fromJson(Map<String, dynamic> json) =>
_$PaymentResponseEndpointDTOFromJson(json);
Map<String, dynamic> toJson() => _$PaymentResponseEndpointDTOToJson(this);
}

View File

@@ -2,20 +2,24 @@ import 'package:pshared/data/dto/payment/card.dart';
import 'package:pshared/data/dto/payment/card_token.dart';
import 'package:pshared/data/dto/payment/endpoint.dart';
import 'package:pshared/data/dto/payment/external_chain.dart';
import 'package:pshared/data/dto/payment/iban.dart';
import 'package:pshared/data/dto/payment/ledger.dart';
import 'package:pshared/data/dto/payment/managed_wallet.dart';
import 'package:pshared/data/dto/payment/russian_bank.dart';
import 'package:pshared/data/dto/payment/wallet.dart';
import 'package:pshared/data/mapper/payment/asset.dart';
import 'package:pshared/data/mapper/payment/enums.dart';
import 'package:pshared/data/mapper/payment/type.dart';
import 'package:pshared/models/payment/methods/card.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/data.dart';
import 'package:pshared/models/payment/methods/iban.dart';
import 'package:pshared/models/payment/methods/ledger.dart';
import 'package:pshared/models/payment/methods/managed_wallet.dart';
import 'package:pshared/models/payment/methods/russian_bank.dart';
import 'package:pshared/models/payment/methods/wallet.dart';
import 'package:pshared/models/payment/type.dart';
extension PaymentMethodDataEndpointMapper on PaymentMethodData {
PaymentEndpointDTO toDTO() {
final metadata = this.metadata;
@@ -76,8 +80,40 @@ extension PaymentMethodDataEndpointMapper on PaymentMethodData {
).toJson(),
metadata: metadata,
);
default:
throw UnsupportedError('Unsupported payment endpoint type: $type');
case PaymentType.wallet:
final payload = this as WalletPaymentMethod;
return PaymentEndpointDTO(
type: endpointTypeToValue(type),
data: WalletPaymentDataDTO(walletId: payload.walletId).toJson(),
metadata: metadata,
);
case PaymentType.bankAccount:
final payload = this as RussianBankAccountPaymentMethod;
return PaymentEndpointDTO(
type: endpointTypeToValue(type),
data: RussianBankAccountPaymentDataDTO(
recipientName: payload.recipientName,
inn: payload.inn,
kpp: payload.kpp,
bankName: payload.bankName,
bik: payload.bik,
accountNumber: payload.accountNumber,
correspondentAccount: payload.correspondentAccount,
).toJson(),
metadata: metadata,
);
case PaymentType.iban:
final payload = this as IbanPaymentMethod;
return PaymentEndpointDTO(
type: endpointTypeToValue(type),
data: IbanPaymentDataDTO(
iban: payload.iban,
accountHolder: payload.accountHolder,
bic: payload.bic,
bankName: payload.bankName,
).toJson(),
metadata: metadata,
);
}
}
}
@@ -127,14 +163,40 @@ extension PaymentEndpointDTOMapper on PaymentEndpointDTO {
maskedPan: payload.maskedPan,
metadata: metadata,
);
default:
throw UnsupportedError('Unsupported payment endpoint type: ${paymentTypeFromValue(type)}');
case PaymentType.wallet:
final payload = WalletPaymentDataDTO.fromJson(data);
return WalletPaymentMethod(
walletId: payload.walletId,
metadata: metadata,
);
case PaymentType.bankAccount:
final payload = RussianBankAccountPaymentDataDTO.fromJson(data);
return RussianBankAccountPaymentMethod(
recipientName: payload.recipientName,
inn: payload.inn,
kpp: payload.kpp,
bankName: payload.bankName,
bik: payload.bik,
accountNumber: payload.accountNumber,
correspondentAccount: payload.correspondentAccount,
metadata: metadata,
);
case PaymentType.iban:
final payload = IbanPaymentDataDTO.fromJson(data);
return IbanPaymentMethod(
iban: payload.iban,
accountHolder: payload.accountHolder,
bic: payload.bic,
bankName: payload.bankName,
metadata: metadata,
);
}
}
}
PaymentType _resolveEndpointType(String type, Map<String, dynamic> data) {
if (type == 'card' && (data.containsKey('token') || data.containsKey('masked_pan'))) {
if (type == 'card' &&
(data.containsKey('token') || data.containsKey('masked_pan'))) {
return PaymentType.cardToken;
}
return endpointTypeFromValue(type);

View File

@@ -1,15 +1,16 @@
import 'package:pshared/data/dto/payment/payment.dart';
import 'package:pshared/data/mapper/payment/operation.dart';
import 'package:pshared/data/mapper/payment/quote.dart';
import 'package:pshared/data/mapper/payment/response_endpoint.dart';
import 'package:pshared/models/payment/payment.dart';
import 'package:pshared/models/payment/state.dart';
extension PaymentDTOMapper on PaymentDTO {
Payment toDomain() => Payment(
paymentRef: paymentRef,
idempotencyKey: idempotencyKey,
state: state,
source: source?.toDomain(),
destination: destination?.toDomain(),
orchestrationState: paymentOrchestrationStateFromValue(state),
failureCode: failureCode,
failureReason: failureReason,
@@ -23,8 +24,9 @@ extension PaymentDTOMapper on PaymentDTO {
extension PaymentMapper on Payment {
PaymentDTO toDTO() => PaymentDTO(
paymentRef: paymentRef,
idempotencyKey: idempotencyKey,
state: state ?? paymentOrchestrationStateToValue(orchestrationState),
source: source?.toDTO(),
destination: destination?.toDTO(),
failureCode: failureCode,
failureReason: failureReason,
operations: operations.map((item) => item.toDTO()).toList(),

View File

@@ -0,0 +1,66 @@
import 'package:pshared/data/dto/payment/endpoint.dart';
import 'package:pshared/data/dto/payment/response_endpoint.dart';
import 'package:pshared/data/mapper/payment/payment.dart';
import 'package:pshared/models/payment/endpoint.dart';
import 'package:pshared/models/payment/methods/data.dart';
extension PaymentResponseEndpointDTOMapper on PaymentResponseEndpointDTO {
PaymentEndpoint toDomain() {
final normalizedType = _normalize(type);
final normalizedData = _cloneData(data);
return PaymentEndpoint(
method: _tryParseMethod(normalizedType, normalizedData),
paymentMethodRef: _normalize(paymentMethodRef),
payeeRef: _normalize(payeeRef),
type: normalizedType,
rawData: normalizedData,
);
}
}
extension PaymentEndpointMapper on PaymentEndpoint {
PaymentResponseEndpointDTO toDTO() {
final methodData = method;
if (methodData != null) {
final endpoint = methodData.toDTO();
return PaymentResponseEndpointDTO(
type: endpoint.type,
data: endpoint.data,
paymentMethodRef: _normalize(paymentMethodRef),
payeeRef: _normalize(payeeRef),
);
}
return PaymentResponseEndpointDTO(
type: _normalize(type),
data: _cloneData(rawData),
paymentMethodRef: _normalize(paymentMethodRef),
payeeRef: _normalize(payeeRef),
);
}
}
PaymentMethodData? _tryParseMethod(String? type, Map<String, dynamic>? data) {
if (type == null || data == null) {
return null;
}
try {
return PaymentEndpointDTO(type: type, data: data).toDomain();
} catch (_) {
return null;
}
}
String? _normalize(String? value) {
final trimmed = value?.trim();
if (trimmed == null || trimmed.isEmpty) return null;
return trimmed;
}
Map<String, dynamic>? _cloneData(Map<String, dynamic>? data) {
if (data == null) return null;
if (data.isEmpty) return <String, dynamic>{};
return Map<String, dynamic>.from(data);
}

View File

@@ -0,0 +1,17 @@
import 'package:pshared/models/payment/methods/data.dart';
class PaymentEndpoint {
final PaymentMethodData? method;
final String? paymentMethodRef;
final String? payeeRef;
final String? type;
final Map<String, dynamic>? rawData;
const PaymentEndpoint({
required this.method,
required this.paymentMethodRef,
required this.payeeRef,
required this.type,
required this.rawData,
});
}

View File

@@ -1,11 +1,13 @@
import 'package:pshared/models/payment/endpoint.dart';
import 'package:pshared/models/payment/execution_operation.dart';
import 'package:pshared/models/payment/quote/quote.dart';
import 'package:pshared/models/payment/state.dart';
class Payment {
final String? paymentRef;
final String? idempotencyKey;
final String? state;
final PaymentEndpoint? source;
final PaymentEndpoint? destination;
final PaymentOrchestrationState orchestrationState;
final String? failureCode;
final String? failureReason;
@@ -16,8 +18,9 @@ class Payment {
const Payment({
required this.paymentRef,
required this.idempotencyKey,
required this.state,
required this.source,
required this.destination,
required this.orchestrationState,
required this.failureCode,
required this.failureReason,

View File

@@ -254,9 +254,7 @@ class PaymentsProvider with ChangeNotifier {
}
String? _paymentKey(Payment payment) {
final ref = _normalize(payment.paymentRef);
if (ref != null) return ref;
return _normalize(payment.idempotencyKey);
return _normalize(payment.paymentRef);
}
List<String>? _normalizeStates(List<String>? states) {

View File

@@ -79,8 +79,6 @@ class PaymentsUpdatesProvider extends ChangeNotifier {
String? _key(Payment payment) {
final ref = payment.paymentRef?.trim();
if (ref != null && ref.isNotEmpty) return ref;
final idempotency = payment.idempotencyKey?.trim();
if (idempotency != null && idempotency.isNotEmpty) return idempotency;
return null;
}