From 3ddd7718c2e100b0cb4545e22b6b89adaf110d04 Mon Sep 17 00:00:00 2001 From: Arseni Date: Thu, 25 Dec 2025 19:24:45 +0300 Subject: [PATCH] Moved payment data preparation into providers --- .../pshared/lib/provider/payment/flow.dart | 111 +++++++++++------- .../lib/provider/payment/quotation.dart | 11 +- .../lib/provider/recipient/pmethods.dart | 20 ++++ .../lib/pages/dashboard/payouts/widget.dart | 7 +- .../pweb/lib/pages/payment_methods/page.dart | 83 ++++--------- .../payment_methods/payment_page/body.dart | 4 - .../payment_methods/payment_page/content.dart | 14 +-- .../payment_methods/payment_page/page.dart | 14 +-- .../widgets/payment_info_section.dart | 18 +-- 9 files changed, 126 insertions(+), 156 deletions(-) diff --git a/frontend/pshared/lib/provider/payment/flow.dart b/frontend/pshared/lib/provider/payment/flow.dart index e4f3f73..12b909f 100644 --- a/frontend/pshared/lib/provider/payment/flow.dart +++ b/frontend/pshared/lib/provider/payment/flow.dart @@ -3,11 +3,14 @@ import 'package:flutter/foundation.dart'; import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/recipient/recipient.dart'; +import 'package:pshared/provider/recipient/pmethods.dart'; class PaymentFlowProvider extends ChangeNotifier { PaymentType _selectedType; PaymentMethodData? _manualPaymentData; + MethodMap _availableTypes = {}; + Recipient? _recipient; PaymentFlowProvider({ required PaymentType initialType, @@ -15,57 +18,40 @@ class PaymentFlowProvider extends ChangeNotifier { PaymentType get selectedType => _selectedType; PaymentMethodData? get manualPaymentData => _manualPaymentData; + Recipient? get recipient => _recipient; - void sync({ + bool get hasRecipient => _recipient != null; + + MethodMap get availableTypes => hasRecipient + ? _availableTypes + : {for (final type in PaymentType.values) type: null}; + + PaymentMethodData? get selectedPaymentData => + hasRecipient ? _availableTypes[_selectedType] : _manualPaymentData; + + void syncWith({ required Recipient? recipient, - required MethodMap availableTypes, + required PaymentMethodsProvider methodsProvider, PaymentType? preferredType, - }) { - final resolvedType = _resolveSelectedType( - recipient: recipient, - availableTypes: availableTypes, - preferredType: preferredType, - ); - - var hasChanges = false; - if (resolvedType != _selectedType) { - _selectedType = resolvedType; - hasChanges = true; - } - - if (recipient != null && _manualPaymentData != null) { - _manualPaymentData = null; - hasChanges = true; - } - - if (hasChanges) notifyListeners(); - } + }) => + _applyState( + recipient: recipient, + availableTypes: methodsProvider.availableTypesForRecipient(recipient), + preferredType: preferredType, + forceResetManualData: false, + ); void reset({ required Recipient? recipient, - required MethodMap availableTypes, + required PaymentMethodsProvider methodsProvider, PaymentType? preferredType, - }) { - final resolvedType = _resolveSelectedType( - recipient: recipient, - availableTypes: availableTypes, - preferredType: preferredType, - ); - - var hasChanges = false; - - if (resolvedType != _selectedType) { - _selectedType = resolvedType; - hasChanges = true; - } - - if (_manualPaymentData != null) { - _manualPaymentData = null; - hasChanges = true; - } - - if (hasChanges) notifyListeners(); - } + }) => + _applyState( + recipient: recipient, + availableTypes: methodsProvider.availableTypesForRecipient(recipient), + preferredType: preferredType, + forceResetManualData: true, + ); void selectType(PaymentType type, {bool resetManualData = false}) { if (_selectedType == type && (!resetManualData || _manualPaymentData == null)) { @@ -107,4 +93,41 @@ class PaymentFlowProvider extends ChangeNotifier { return availableTypes.keys.first; } + + void _applyState({ + required Recipient? recipient, + required MethodMap availableTypes, + required PaymentType? preferredType, + required bool forceResetManualData, + }) { + final resolvedType = _resolveSelectedType( + recipient: recipient, + availableTypes: availableTypes, + preferredType: preferredType, + ); + + var hasChanges = false; + + if (_recipient != recipient) { + _recipient = recipient; + hasChanges = true; + } + + if (!mapEquals(_availableTypes, availableTypes)) { + _availableTypes = availableTypes; + hasChanges = true; + } + + if (resolvedType != _selectedType) { + _selectedType = resolvedType; + hasChanges = true; + } + + if ((recipient != null || forceResetManualData) && _manualPaymentData != null) { + _manualPaymentData = null; + hasChanges = true; + } + + if (hasChanges) notifyListeners(); + } } diff --git a/frontend/pshared/lib/provider/payment/quotation.dart b/frontend/pshared/lib/provider/payment/quotation.dart index a36bacc..d093f49 100644 --- a/frontend/pshared/lib/provider/payment/quotation.dart +++ b/frontend/pshared/lib/provider/payment/quotation.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:collection/collection.dart'; - import 'package:uuid/uuid.dart'; import 'package:pshared/api/requests/payment/quote.dart'; @@ -20,7 +18,6 @@ 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/pmethods.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/payment/quotation.dart'; import 'package:pshared/utils/currency.dart'; @@ -36,12 +33,10 @@ class QuotationProvider extends ChangeNotifier { PaymentAmountProvider payment, WalletsProvider wallets, PaymentFlowProvider flow, - PaymentMethodsProvider methods, ) { _organizations = venue; - final t = flow.selectedType; - final method = methods.methods.firstWhereOrNull((m) => m.type == t); - if ((wallets.selectedWallet != null) && (method != null)) { + final destination = flow.selectedPaymentData; + if ((wallets.selectedWallet != null) && (destination != null)) { getQuotation(PaymentIntent( kind: PaymentKind.payout, amount: Money( @@ -49,7 +44,7 @@ class QuotationProvider extends ChangeNotifier { // TODO: adapt to possible other sources currency: currencyCodeToString(wallets.selectedWallet!.currency), ), - destination: method.data, + destination: destination, source: ManagedWalletPaymentMethod( managedWalletRef: wallets.selectedWallet!.id, ), diff --git a/frontend/pshared/lib/provider/recipient/pmethods.dart b/frontend/pshared/lib/provider/recipient/pmethods.dart index a4ecc87..9af60e4 100644 --- a/frontend/pshared/lib/provider/recipient/pmethods.dart +++ b/frontend/pshared/lib/provider/recipient/pmethods.dart @@ -5,6 +5,8 @@ import 'package:pshared/models/describable.dart'; import 'package:pshared/models/organization/bound.dart'; import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/payment/methods/type.dart'; +import 'package:pshared/models/payment/type.dart'; +import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/permissions/bound.dart'; import 'package:pshared/models/storable.dart'; import 'package:pshared/provider/organizations.dart'; @@ -20,6 +22,24 @@ class PaymentMethodsProvider extends GenericProvider { List get methods => List.unmodifiable(items.toList()..sort((a, b) => a.storable.createdAt.compareTo(b.storable.createdAt))); + List methodsForRecipient(Recipient? recipient) { + if (recipient == null || !isReady) return []; + + return methods + .where((method) => !method.isArchived && method.recipientRef == recipient.id) + .toList(); + } + + MethodMap availableTypesForRecipient(Recipient? recipient) => { + for (final method in methodsForRecipient(recipient)) method.type: method.data, + }; + + PaymentMethod? findMethodByType({ + required PaymentType type, + required Recipient? recipient, + }) => + methodsForRecipient(recipient).firstWhereOrNull((method) => method.type == type); + void updateProviders(OrganizationsProvider organizations, RecipientsProvider recipients) { if (recipients.currentObject != null) loadMethods(organizations, recipients.currentObject?.id); } diff --git a/frontend/pweb/lib/pages/dashboard/payouts/widget.dart b/frontend/pweb/lib/pages/dashboard/payouts/widget.dart index 2e7e89f..a88849e 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/widget.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/widget.dart @@ -7,7 +7,6 @@ import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/provider/payment/flow.dart'; import 'package:pshared/provider/payment/quotation.dart'; import 'package:pshared/provider/payment/wallets.dart'; -import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pweb/pages/dashboard/payouts/form.dart'; @@ -21,11 +20,11 @@ class PaymentFromWrappingWidget extends StatelessWidget { ChangeNotifierProvider( create: (_) => PaymentAmountProvider(), ), - ChangeNotifierProxyProvider5( + ChangeNotifierProxyProvider4( create: (_) => QuotationProvider(), - update: (context, orgnization, payment, wallet, flow, methods, provider) => provider!..update(orgnization, payment, wallet, flow, methods), + update: (context, orgnization, payment, wallet, flow, provider) => provider!..update(orgnization, payment, wallet, flow), ), ], child: const PaymentFormWidget(), ); -} \ No newline at end of file +} diff --git a/frontend/pweb/lib/pages/payment_methods/page.dart b/frontend/pweb/lib/pages/payment_methods/page.dart index 98aa77b..968b3c6 100644 --- a/frontend/pweb/lib/pages/payment_methods/page.dart +++ b/frontend/pweb/lib/pages/payment_methods/page.dart @@ -4,17 +4,17 @@ import 'package:collection/collection.dart'; import 'package:provider/provider.dart'; -import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/payment/methods/type.dart'; import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/provider/payment/flow.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; - import 'package:pshared/models/payment/wallet.dart'; -import 'package:pweb/pages/payment_methods/payment_page/body.dart'; import 'package:pshared/provider/payment/wallets.dart'; + + +import 'package:pweb/pages/payment_methods/payment_page/body.dart'; import 'package:pweb/widgets/sidebar/destinations.dart'; import 'package:pweb/services/posthog.dart'; @@ -38,16 +38,12 @@ class PaymentPage extends StatefulWidget { class _PaymentPageState extends State { late final TextEditingController _searchController; late final FocusNode _searchFocusNode; - late final PaymentFlowProvider _flowProvider; @override void initState() { super.initState(); _searchController = TextEditingController(); _searchFocusNode = FocusNode(); - _flowProvider = PaymentFlowProvider( - initialType: widget.initialPaymentType ?? PaymentType.bankAccount, - ); WidgetsBinding.instance.addPostFrameCallback((_) => _initializePaymentPage()); } @@ -56,20 +52,12 @@ class _PaymentPageState extends State { void dispose() { _searchController.dispose(); _searchFocusNode.dispose(); - _flowProvider.dispose(); super.dispose(); } void _initializePaymentPage() { final methodsProvider = context.read(); _handleWalletAutoSelection(methodsProvider); - - final recipient = context.read().currentObject; - _syncFlowProvider( - recipient: recipient, - methodsProvider: methodsProvider, - preferredType: widget.initialPaymentType, - ); } void _handleSearchChanged(String query) { @@ -79,11 +67,12 @@ class _PaymentPageState extends State { void _handleRecipientSelected(Recipient recipient) { final recipientProvider = context.read(); final methodsProvider = context.read(); + final flowProvider = context.read(); recipientProvider.setCurrentObject(recipient.id); - _flowProvider.reset( + flowProvider.reset( recipient: recipient, - availableTypes: _availablePaymentTypes(recipient, methodsProvider), + methodsProvider: methodsProvider, preferredType: widget.initialPaymentType, ); _clearSearchField(); @@ -92,11 +81,12 @@ class _PaymentPageState extends State { void _handleRecipientCleared() { final recipientProvider = context.read(); final methodsProvider = context.read(); + final flowProvider = context.read(); recipientProvider.setCurrentObject(null); - _flowProvider.reset( + flowProvider.reset( recipient: null, - availableTypes: _availablePaymentTypes(null, methodsProvider), + methodsProvider: methodsProvider, preferredType: widget.initialPaymentType, ); _clearSearchField(); @@ -110,7 +100,7 @@ class _PaymentPageState extends State { void _handleSendPayment() { // TODO: Handle Payment logic - PosthogService.paymentInitiated(method: _flowProvider.selectedType); + PosthogService.paymentInitiated(method: context.read().selectedType); } @override @@ -118,23 +108,29 @@ class _PaymentPageState extends State { final methodsProvider = context.watch(); final recipientProvider = context.watch(); final recipient = recipientProvider.currentObject; - final availableTypes = _availablePaymentTypes(recipient, methodsProvider); - _syncFlowProvider( - recipient: recipient, - methodsProvider: methodsProvider, - preferredType: recipient != null ? widget.initialPaymentType : null, - ); - - return ChangeNotifierProvider.value( - value: _flowProvider, + return ChangeNotifierProxyProvider2( + create: (_) => PaymentFlowProvider( + initialType: widget.initialPaymentType ?? PaymentType.bankAccount, + ), + update: (_, recipients, methods, flow) { + final provider = flow ?? PaymentFlowProvider( + initialType: widget.initialPaymentType ?? PaymentType.bankAccount, + ); + final currentRecipient = recipients.currentObject; + provider.syncWith( + recipient: currentRecipient, + methodsProvider: methods, + preferredType: currentRecipient != null ? widget.initialPaymentType : null, + ); + return provider; + }, child: PaymentPageBody( onBack: widget.onBack, fallbackDestination: widget.fallbackDestination, recipient: recipient, recipientProvider: recipientProvider, methodsProvider: methodsProvider, - availablePaymentTypes: availableTypes, searchController: _searchController, searchFocusNode: _searchFocusNode, onSearchChanged: _handleSearchChanged, @@ -155,33 +151,6 @@ class _PaymentPageState extends State { } } - void _syncFlowProvider({ - required Recipient? recipient, - required PaymentMethodsProvider methodsProvider, - PaymentType? preferredType, - }) { - _flowProvider.sync( - recipient: recipient, - availableTypes: _availablePaymentTypes(recipient, methodsProvider), - preferredType: preferredType, - ); - } - - MethodMap _availablePaymentTypes( - Recipient? recipient, - PaymentMethodsProvider methodsProvider, - ) { - if (recipient == null || !methodsProvider.isReady) return {}; - - final methodsForRecipient = methodsProvider.methods.where( - (method) => !method.isArchived && method.recipientRef == recipient.id, - ); - - return { - for (final method in methodsForRecipient) method.type: method.data, - }; - } - PaymentMethod? _getPaymentMethodForWallet( Wallet wallet, PaymentMethodsProvider methodsProvider, diff --git a/frontend/pweb/lib/pages/payment_methods/payment_page/body.dart b/frontend/pweb/lib/pages/payment_methods/payment_page/body.dart index 2caf9da..6d58a6a 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/body.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/body.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; @@ -17,7 +16,6 @@ class PaymentPageBody extends StatelessWidget { final Recipient? recipient; final RecipientsProvider recipientProvider; final PaymentMethodsProvider methodsProvider; - final MethodMap availablePaymentTypes; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -32,7 +30,6 @@ class PaymentPageBody extends StatelessWidget { required this.recipient, required this.recipientProvider, required this.methodsProvider, - required this.availablePaymentTypes, required this.fallbackDestination, required this.searchController, required this.searchFocusNode, @@ -61,7 +58,6 @@ class PaymentPageBody extends StatelessWidget { recipient: recipient, recipientProvider: recipientProvider, methodsProvider: methodsProvider, - availablePaymentTypes: availablePaymentTypes, fallbackDestination: fallbackDestination, searchController: searchController, searchFocusNode: searchFocusNode, diff --git a/frontend/pweb/lib/pages/payment_methods/payment_page/content.dart b/frontend/pweb/lib/pages/payment_methods/payment_page/content.dart index 2638330..ebb9f3e 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/content.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/content.dart @@ -1,12 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; -import 'package:pshared/provider/payment/flow.dart'; import 'package:pweb/pages/payment_methods/payment_page/back_button.dart'; import 'package:pweb/pages/payment_methods/payment_page/header.dart'; @@ -27,7 +23,6 @@ class PaymentPageContent extends StatelessWidget { final Recipient? recipient; final RecipientsProvider recipientProvider; final PaymentMethodsProvider methodsProvider; - final MethodMap availablePaymentTypes; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -42,7 +37,6 @@ class PaymentPageContent extends StatelessWidget { required this.recipient, required this.recipientProvider, required this.methodsProvider, - required this.availablePaymentTypes, required this.fallbackDestination, required this.searchController, required this.searchFocusNode, @@ -55,7 +49,6 @@ class PaymentPageContent extends StatelessWidget { @override Widget build(BuildContext context) { final dimensions = AppDimensions(); - final flowProvider = context.watch(); final loc = AppLocalizations.of(context)!; return Align( @@ -98,12 +91,7 @@ class PaymentPageContent extends StatelessWidget { onRecipientCleared: onRecipientCleared, ), SizedBox(height: dimensions.paddingXLarge), - PaymentInfoSection( - dimensions: dimensions, - flowProvider: flowProvider, - recipient: recipient, - availableTypes: availablePaymentTypes, - ), + PaymentInfoSection(dimensions: dimensions), SizedBox(height: dimensions.paddingLarge), const PaymentFormWidget(), SizedBox(height: dimensions.paddingXXXLarge), diff --git a/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart b/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart index d0ff6f5..7d23770 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart @@ -1,12 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; -import 'package:pshared/provider/payment/flow.dart'; import 'package:pweb/pages/dashboard/payouts/widget.dart'; import 'package:pweb/pages/payment_methods/payment_page/back_button.dart'; @@ -27,7 +23,6 @@ class PaymentPageContent extends StatelessWidget { final Recipient? recipient; final RecipientsProvider recipientProvider; final PaymentMethodsProvider methodsProvider; - final MethodMap availablePaymentTypes; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -42,7 +37,6 @@ class PaymentPageContent extends StatelessWidget { required this.recipient, required this.recipientProvider, required this.methodsProvider, - required this.availablePaymentTypes, required this.fallbackDestination, required this.searchController, required this.searchFocusNode, @@ -55,7 +49,6 @@ class PaymentPageContent extends StatelessWidget { @override Widget build(BuildContext context) { final dimensions = AppDimensions(); - final flowProvider = context.watch(); final loc = AppLocalizations.of(context)!; return Align( @@ -98,12 +91,7 @@ class PaymentPageContent extends StatelessWidget { onRecipientCleared: onRecipientCleared, ), SizedBox(height: dimensions.paddingXLarge), - PaymentInfoSection( - dimensions: dimensions, - flowProvider: flowProvider, - recipient: recipient, - availableTypes: availablePaymentTypes, - ), + PaymentInfoSection(dimensions: dimensions), SizedBox(height: dimensions.paddingLarge), const PaymentFromWrappingWidget(), SizedBox(height: dimensions.paddingXXXLarge), diff --git a/frontend/pweb/lib/pages/payment_methods/widgets/payment_info_section.dart b/frontend/pweb/lib/pages/payment_methods/widgets/payment_info_section.dart index 600184e..d65e394 100644 --- a/frontend/pweb/lib/pages/payment_methods/widgets/payment_info_section.dart +++ b/frontend/pweb/lib/pages/payment_methods/widgets/payment_info_section.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:pshared/models/payment/methods/data.dart'; -import 'package:pshared/models/payment/type.dart'; -import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/provider/payment/flow.dart'; import 'package:pweb/pages/payment_methods/form.dart'; @@ -15,25 +14,18 @@ import 'package:pweb/generated/i18n/app_localizations.dart'; class PaymentInfoSection extends StatelessWidget { final AppDimensions dimensions; - final MethodMap availableTypes; - final PaymentFlowProvider flowProvider; - final Recipient? recipient; const PaymentInfoSection({ super.key, required this.dimensions, - required this.availableTypes, - required this.flowProvider, - required this.recipient, }); @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context)!; - final hasRecipient = recipient != null; - final MethodMap resolvedAvailableTypes = hasRecipient - ? availableTypes - : {for (final type in PaymentType.values) type: null}; + final flowProvider = context.watch(); + final hasRecipient = flowProvider.hasRecipient; + final MethodMap resolvedAvailableTypes = flowProvider.availableTypes; if (hasRecipient && resolvedAvailableTypes.isEmpty) { return Text(loc.recipientNoPaymentDetails); @@ -62,7 +54,7 @@ class PaymentInfoSection extends StatelessWidget { flowProvider.setManualPaymentData(data); } }, - initialData: hasRecipient ? resolvedAvailableTypes[selectedType] : flowProvider.manualPaymentData, + initialData: flowProvider.selectedPaymentData, isEditable: !hasRecipient, ), ],