diff --git a/frontend/pshared/lib/data/dto/payment/intent/customer.dart b/frontend/pshared/lib/data/dto/payment/intent/customer.dart new file mode 100644 index 0000000..a327318 --- /dev/null +++ b/frontend/pshared/lib/data/dto/payment/intent/customer.dart @@ -0,0 +1,41 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'customer.g.dart'; + + +@JsonSerializable() +class CustomerDTO { + final String id; + + @JsonKey(name: 'first_name') + final String? firstName; + + @JsonKey(name: 'middle_name') + final String? middleName; + + @JsonKey(name: 'last_name') + final String? lastName; + + final String? ip; + final String? zip; + final String? country; + final String? state; + final String? city; + final String? address; + + const CustomerDTO({ + required this.id, + this.firstName, + this.middleName, + this.lastName, + this.ip, + this.zip, + this.country, + this.state, + this.city, + this.address, + }); + + factory CustomerDTO.fromJson(Map json) => _$CustomerDTOFromJson(json); + Map toJson() => _$CustomerDTOToJson(this); +} diff --git a/frontend/pshared/lib/data/dto/payment/intent/payment.dart b/frontend/pshared/lib/data/dto/payment/intent/payment.dart index 5ce9c38..34cf653 100644 --- a/frontend/pshared/lib/data/dto/payment/intent/payment.dart +++ b/frontend/pshared/lib/data/dto/payment/intent/payment.dart @@ -1,6 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:pshared/data/dto/payment/endpoint.dart'; +import 'package:pshared/data/dto/payment/intent/customer.dart'; import 'package:pshared/data/dto/payment/intent/fx.dart'; import 'package:pshared/data/dto/payment/money.dart'; @@ -20,6 +21,7 @@ class PaymentIntentDTO { final String? settlementMode; final Map? attributes; + final CustomerDTO? customer; const PaymentIntentDTO({ this.kind, @@ -29,6 +31,7 @@ class PaymentIntentDTO { this.fx, this.settlementMode, this.attributes, + this.customer, }); factory PaymentIntentDTO.fromJson(Map json) => _$PaymentIntentDTOFromJson(json); diff --git a/frontend/pshared/lib/data/mapper/payment/intent/customer.dart b/frontend/pshared/lib/data/mapper/payment/intent/customer.dart new file mode 100644 index 0000000..e6d3e00 --- /dev/null +++ b/frontend/pshared/lib/data/mapper/payment/intent/customer.dart @@ -0,0 +1,33 @@ +import 'package:pshared/data/dto/payment/intent/customer.dart'; +import 'package:pshared/models/payment/customer.dart'; + + +extension CustomerMapper on Customer { + CustomerDTO toDTO() => CustomerDTO( + id: id, + firstName: firstName, + middleName: middleName, + lastName: lastName, + ip: ip, + zip: zip, + country: country, + state: state, + city: city, + address: address, + ); +} + +extension CustomerDTOMapper on CustomerDTO { + Customer toDomain() => Customer( + id: id, + firstName: firstName, + middleName: middleName, + lastName: lastName, + ip: ip, + zip: zip, + country: country, + state: state, + city: city, + address: address, + ); +} diff --git a/frontend/pshared/lib/data/mapper/payment/intent/payment.dart b/frontend/pshared/lib/data/mapper/payment/intent/payment.dart index 0086294..06286f3 100644 --- a/frontend/pshared/lib/data/mapper/payment/intent/payment.dart +++ b/frontend/pshared/lib/data/mapper/payment/intent/payment.dart @@ -1,30 +1,34 @@ import 'package:pshared/data/dto/payment/intent/payment.dart'; import 'package:pshared/data/mapper/payment/payment.dart'; import 'package:pshared/data/mapper/payment/enums.dart'; +import 'package:pshared/data/mapper/payment/intent/customer.dart'; import 'package:pshared/data/mapper/payment/intent/fx.dart'; import 'package:pshared/data/mapper/payment/money.dart'; import 'package:pshared/models/payment/intent.dart'; + extension PaymentIntentMapper on PaymentIntent { PaymentIntentDTO toDTO() => PaymentIntentDTO( - kind: paymentKindToValue(kind), - source: source?.toDTO(), - destination: destination?.toDTO(), - amount: amount?.toDTO(), - fx: fx?.toDTO(), - settlementMode: settlementModeToValue(settlementMode), - attributes: attributes, - ); + kind: paymentKindToValue(kind), + source: source?.toDTO(), + destination: destination?.toDTO(), + amount: amount?.toDTO(), + fx: fx?.toDTO(), + settlementMode: settlementModeToValue(settlementMode), + attributes: attributes, + customer: customer?.toDTO(), + ); } extension PaymentIntentDTOMapper on PaymentIntentDTO { PaymentIntent toDomain() => PaymentIntent( - kind: paymentKindFromValue(kind), - source: source?.toDomain(), - destination: destination?.toDomain(), - amount: amount?.toDomain(), - fx: fx?.toDomain(), - settlementMode: settlementModeFromValue(settlementMode), - attributes: attributes, - ); + kind: paymentKindFromValue(kind), + source: source?.toDomain(), + destination: destination?.toDomain(), + amount: amount?.toDomain(), + fx: fx?.toDomain(), + settlementMode: settlementModeFromValue(settlementMode), + attributes: attributes, + customer: customer?.toDomain(), + ); } diff --git a/frontend/pshared/lib/models/payment/customer.dart b/frontend/pshared/lib/models/payment/customer.dart new file mode 100644 index 0000000..726601f --- /dev/null +++ b/frontend/pshared/lib/models/payment/customer.dart @@ -0,0 +1,25 @@ +class Customer { + final String id; + final String? firstName; + final String? middleName; + final String? lastName; + final String? ip; + final String? zip; + final String? country; + final String? state; + final String? city; + final String? address; + + const Customer({ + required this.id, + this.firstName, + this.middleName, + this.lastName, + this.ip, + this.zip, + this.country, + this.state, + this.city, + this.address, + }); +} diff --git a/frontend/pshared/lib/models/payment/intent.dart b/frontend/pshared/lib/models/payment/intent.dart index 943c61e..24278b1 100644 --- a/frontend/pshared/lib/models/payment/intent.dart +++ b/frontend/pshared/lib/models/payment/intent.dart @@ -1,5 +1,6 @@ import 'package:pshared/models/payment/fx/intent.dart'; import 'package:pshared/models/payment/kind.dart'; +import 'package:pshared/models/payment/customer.dart'; import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/payment/money.dart'; import 'package:pshared/models/payment/settlement_mode.dart'; @@ -13,6 +14,7 @@ class PaymentIntent { final FxIntent? fx; final SettlementMode settlementMode; final Map? attributes; + final Customer? customer; const PaymentIntent({ this.kind = PaymentKind.unspecified, @@ -22,5 +24,6 @@ class PaymentIntent { this.fx, this.settlementMode = SettlementMode.unspecified, this.attributes, + this.customer, }); } diff --git a/frontend/pshared/lib/provider/payment/quotation.dart b/frontend/pshared/lib/provider/payment/quotation.dart index d093f49..7141de9 100644 --- a/frontend/pshared/lib/provider/payment/quotation.dart +++ b/frontend/pshared/lib/provider/payment/quotation.dart @@ -1,23 +1,30 @@ import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; + import 'package:uuid/uuid.dart'; import 'package:pshared/api/requests/payment/quote.dart'; import 'package:pshared/data/mapper/payment/intent/payment.dart'; import 'package:pshared/models/asset.dart'; import 'package:pshared/models/payment/currency_pair.dart'; +import 'package:pshared/models/payment/customer.dart'; import 'package:pshared/models/payment/fx/intent.dart'; import 'package:pshared/models/payment/fx/side.dart'; import 'package:pshared/models/payment/kind.dart'; import 'package:pshared/models/payment/methods/managed_wallet.dart'; +import 'package:pshared/models/payment/methods/type.dart'; import 'package:pshared/models/payment/money.dart'; import 'package:pshared/models/payment/settlement_mode.dart'; import 'package:pshared/models/payment/intent.dart'; import 'package:pshared/models/payment/quote.dart'; +import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/provider/payment/flow.dart'; import 'package:pshared/provider/payment/wallets.dart'; +import 'package:pshared/provider/recipient/provider.dart'; +import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/payment/quotation.dart'; import 'package:pshared/utils/currency.dart'; @@ -33,10 +40,17 @@ class QuotationProvider extends ChangeNotifier { PaymentAmountProvider payment, WalletsProvider wallets, PaymentFlowProvider flow, + RecipientsProvider recipients, + PaymentMethodsProvider methods, ) { _organizations = venue; - final destination = flow.selectedPaymentData; - if ((wallets.selectedWallet != null) && (destination != null)) { + final t = flow.selectedType; + final method = methods.methods.firstWhereOrNull((m) => m.type == t); + if ((wallets.selectedWallet != null) && (method != null)) { + final customer = _buildCustomer( + recipient: recipients.currentObject, + method: method, + ); getQuotation(PaymentIntent( kind: PaymentKind.payout, amount: Money( @@ -44,7 +58,7 @@ class QuotationProvider extends ChangeNotifier { // TODO: adapt to possible other sources currency: currencyCodeToString(wallets.selectedWallet!.currency), ), - destination: destination, + destination: method.data, source: ManagedWalletPaymentMethod( managedWalletRef: wallets.selectedWallet!.id, ), @@ -56,6 +70,7 @@ class QuotationProvider extends ChangeNotifier { side: FxSide.sellBaseBuyQuote, ), settlementMode: payment.payerCoversFee ? SettlementMode.fixReceived : SettlementMode.fixSource, + customer: customer, )); } } @@ -68,6 +83,58 @@ class QuotationProvider extends ChangeNotifier { Asset? get total => quotation == null ? null : createAsset(quotation!.debitAmount!.currency, quotation!.debitAmount!.amount); Asset? get recipientGets => quotation == null ? null : createAsset(quotation!.expectedSettlementAmount!.currency, quotation!.expectedSettlementAmount!.amount); + Customer _buildCustomer({ + required Recipient? recipient, + required PaymentMethod method, + }) { + final name = _resolveCustomerName(method, recipient); + String? firstName; + String? middleName; + String? lastName; + + if (name != null && name.isNotEmpty) { + final parts = name.split(RegExp(r'\s+')); + if (parts.length == 1) { + firstName = parts.first; + } else if (parts.length == 2) { + firstName = parts.first; + lastName = parts.last; + } else { + firstName = parts.first; + lastName = parts.last; + middleName = parts.sublist(1, parts.length - 1).join(' '); + } + } + + return Customer( + id: recipient?.id ?? method.recipientRef, + firstName: firstName, + middleName: middleName, + lastName: lastName, + country: method.cardData?.country, + ); + } + + String? _resolveCustomerName(PaymentMethod method, Recipient? recipient) { + final card = method.cardData; + if (card != null) { + return '${card.firstName} ${card.lastName}'.trim(); + } + + final iban = method.ibanData; + if (iban != null && iban.accountHolder.trim().isNotEmpty) { + return iban.accountHolder.trim(); + } + + final bank = method.bankAccountData; + if (bank != null && bank.recipientName.trim().isNotEmpty) { + return bank.recipientName.trim(); + } + + final recipientName = recipient?.name.trim(); + return recipientName?.isNotEmpty == true ? recipientName : null; + } + void _setResource(Resource quotation) { _quotation = quotation; notifyListeners(); diff --git a/frontend/pweb/lib/main.dart b/frontend/pweb/lib/main.dart index 4ab6802..2c6c34d 100644 --- a/frontend/pweb/lib/main.dart +++ b/frontend/pweb/lib/main.dart @@ -105,14 +105,10 @@ void main() async { ChangeNotifierProvider( create: (_) => PaymentAmountProvider(), ), - ChangeNotifierProxyProvider4( + ChangeNotifierProxyProvider6( create: (_) => QuotationProvider(), - update: (context, organization, payment, wallets, flow, provider) => provider!..update( - organization, - payment, - wallets, - flow, - ), + update: (_, organization, payment, wallet, flow, recipients, methods, provider) => + provider!..update(organization, payment, wallet, flow, recipients, methods), ), ChangeNotifierProxyProvider2( create: (_) => PaymentProvider(),