recipient saving

This commit is contained in:
Stephan D
2025-12-05 04:34:11 +01:00
parent 85fb567ed9
commit e1da16448b
17 changed files with 121 additions and 40 deletions

View File

@@ -67,7 +67,7 @@ MNTX_GATEWAY_COMPOSE_PROJECT=sendico-mntx-gateway
MNTX_GATEWAY_SERVICE_NAME=sendico_mntx_gateway MNTX_GATEWAY_SERVICE_NAME=sendico_mntx_gateway
MNTX_GATEWAY_GRPC_PORT=50075 MNTX_GATEWAY_GRPC_PORT=50075
MNTX_GATEWAY_METRICS_PORT=9404 MNTX_GATEWAY_METRICS_PORT=9404
MNTX_GATEWAY_HTTP_PORT=8080 MNTX_GATEWAY_HTTP_PORT=8084
MONETIX_BASE_URL=https://api.txflux.com MONETIX_BASE_URL=https://api.txflux.com

View File

@@ -3,4 +3,6 @@ import 'package:pshared/models/payment/type.dart';
abstract class PaymentMethodData { abstract class PaymentMethodData {
PaymentType get type; PaymentType get type;
} }
typedef MethodMap = Map<PaymentType, PaymentMethodData?>;

View File

@@ -1,7 +1,12 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:pshared/data/mapper/payment/method.dart'; import 'package:pshared/data/mapper/payment/method.dart';
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/methods/type.dart';
import 'package:pshared/models/permissions/bound.dart';
import 'package:pshared/models/storable.dart';
import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/recipient/provider.dart'; import 'package:pshared/provider/recipient/provider.dart';
import 'package:pshared/provider/template.dart'; import 'package:pshared/provider/template.dart';
@@ -49,6 +54,23 @@ class PaymentMethodsProvider extends GenericProvider<PaymentMethod> {
cascade: true, cascade: true,
); );
Future<PaymentMethod> create({
required String reacipientRef,
required PaymentMethodData data,
required String name,
}) => createObject(
_organizations.current.id,
PaymentMethod(
storable: newStorable(),
permissionBound: newPermissionBound(
organizationBound: newOrganizationBound(organizationRef: _organizations.current.id),
),
recipientRef: reacipientRef,
data: data,
describable: newDescribable(name: name),
).toDTO().toJson(),
);
Future<void> makeMain(PaymentMethod method) { Future<void> makeMain(PaymentMethod method) {
// TODO: create separate backend method to manage main payment method // TODO: create separate backend method to manage main payment method

View File

@@ -1,7 +1,9 @@
import 'package:pshared/data/mapper/organization/bound.dart';
import 'package:pshared/models/recipient/filter.dart'; import 'package:pshared/models/recipient/filter.dart';
import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/models/recipient/status.dart'; import 'package:pshared/models/recipient/status.dart';
import 'package:pshared/models/recipient/type.dart';
import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/template.dart'; import 'package:pshared/provider/template.dart';
import 'package:pshared/service/recipient/service.dart'; import 'package:pshared/service/recipient/service.dart';
@@ -51,6 +53,20 @@ class RecipientsProvider extends GenericProvider<Recipient> {
notifyListeners(); notifyListeners();
} }
Future<Recipient> create({
required String name,
required String email,
}) async => createObject(
_organizations.current.id,
newRecipient(
organizationRef: _organizations.current.id,
email: email,
name: name,
status: RecipientStatus.ready,
type: RecipientType.internal,
).toDTO().toJson(),
);
void updateProviders(OrganizationsProvider organizations) { void updateProviders(OrganizationsProvider organizations) {
_organizations = organizations; _organizations = organizations;
if (_organizations.isOrganizationSet) { if (_organizations.isOrganizationSet) {

View File

@@ -429,6 +429,11 @@
"companyName": "Name of your company", "companyName": "Name of your company",
"companynameRequired": "Company name required", "companynameRequired": "Company name required",
"errorSaveRecipient": "Failed to save recipient",
"@errorSaveRecipient": {
"description": "Error message displayed when saving a recipient fails"
},
"errorSignUp": "Error occured while signing up, try again later", "errorSignUp": "Error occured while signing up, try again later",
"companyDescription": "Company Description", "companyDescription": "Company Description",
"companyDescriptionHint": "Describe any of the fields of the Company's business", "companyDescriptionHint": "Describe any of the fields of the Company's business",

View File

@@ -429,6 +429,11 @@
"companyName": "Название вашей компании", "companyName": "Название вашей компании",
"companynameRequired": "Необходимо указать название компании", "companynameRequired": "Необходимо указать название компании",
"errorSaveRecipient": "Не удалось сохранить получателя",
"@errorSaveRecipient": {
"description": "Сообщение об ошибке при неудачном сохранении получателя"
},
"errorSignUp": "Произошла ошибка при регистрации, попробуйте позже", "errorSignUp": "Произошла ошибка при регистрации, попробуйте позже",
"companyDescription": "Описание компании", "companyDescription": "Описание компании",
"companyDescriptionHint": "Опишите любую из сфер деятельности компании", "companyDescriptionHint": "Опишите любую из сфер деятельности компании",

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/payment/type.dart';
@@ -9,8 +10,8 @@ import 'package:pweb/pages/payment_methods/icon.dart';
class AdressBookPaymentMethodTile extends StatefulWidget { class AdressBookPaymentMethodTile extends StatefulWidget {
final PaymentType type; final PaymentType type;
final String title; final String title;
final Map<PaymentType, Object?> methods; final MethodMap methods;
final ValueChanged<Object?> onChanged; final ValueChanged<PaymentMethodData?> onChanged;
final double spacingM; final double spacingM;
final double spacingS; final double spacingS;

View File

@@ -2,17 +2,22 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.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/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/models/recipient/status.dart'; import 'package:pshared/models/recipient/status.dart';
import 'package:pshared/models/recipient/type.dart'; import 'package:pshared/models/recipient/type.dart';
import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/pmethods.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pweb/pages/address_book/form/view.dart'; import 'package:pweb/pages/address_book/form/view.dart';
// import 'package:pweb/services/amplitude.dart'; // import 'package:pweb/services/amplitude.dart';
import 'package:pweb/utils/error/snackbar.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
import 'package:pweb/utils/payment/label.dart';
import 'package:pweb/utils/snackbar.dart';
class AdressBookRecipientForm extends StatefulWidget { class AdressBookRecipientForm extends StatefulWidget {
@@ -31,7 +36,7 @@ class _AdressBookRecipientFormState extends State<AdressBookRecipientForm> {
late TextEditingController _emailCtrl; late TextEditingController _emailCtrl;
RecipientType _type = RecipientType.internal; RecipientType _type = RecipientType.internal;
RecipientStatus _status = RecipientStatus.ready; RecipientStatus _status = RecipientStatus.ready;
final Map<PaymentType, Object?> _methods = {}; final MethodMap _methods = {};
late PaymentMethodsProvider _methodsProvider; late PaymentMethodsProvider _methodsProvider;
Future<void> _loadMethods() async { Future<void> _loadMethods() async {
@@ -63,31 +68,43 @@ class _AdressBookRecipientFormState extends State<AdressBookRecipientForm> {
_loadMethods(); _loadMethods();
} }
Future<Recipient?> _doSave() async {
final recipients = context.read<RecipientsProvider>();
final methods = context.read<PaymentMethodsProvider>();
final recipient = await recipients.create(
name: _nameCtrl.text,
email: _emailCtrl.text,
);
if (methods.currentObject == null) return recipient;
final data = methods.currentObject!;
await methods.create(
reacipientRef: recipient.id,
name: getPaymentTypeLabel(context, data.type),
data: data.data,
);
return recipient;
}
//TODO: Change when registration is ready //TODO: Change when registration is ready
void _save() { Future<void> _save() async {
if (!_formKey.currentState!.validate() || _methods.isEmpty) { if (!_formKey.currentState!.validate() || _methods.isEmpty) {
// AmplitudeService.recipientAddCompleted( notifyUser(context, AppLocalizations.of(context)!.errorSaveRecipient);
// _type,
// _status,
// _methods.keys.toSet(),
// );
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.recipientFormRule),
),
);
return; return;
} }
final recipient = newRecipient( // AmplitudeService.recipientAddCompleted(
name: _nameCtrl.text, // _type,
email: _emailCtrl.text, // _status,
type: _type, // _methods.keys.toSet(),
status: _status, // );
avatarUrl: null, final recipient = await executeActionWithNotification(
organizationRef: context.read<OrganizationsProvider>().current.id context: context,
action: _doSave,
errorMessage: AppLocalizations.of(context)!.errorSaveRecipient,
successMessage: AppLocalizations.of(context)!.recipientFormRule,
); );
widget.onSaved?.call(recipient); widget.onSaved?.call(recipient);
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/status.dart'; import 'package:pshared/models/recipient/status.dart';
@@ -20,10 +21,10 @@ class FormView extends StatelessWidget {
final TextEditingController emailCtrl; final TextEditingController emailCtrl;
final RecipientType type; final RecipientType type;
final RecipientStatus status; final RecipientStatus status;
final Map<PaymentType, Object?> methods; final MethodMap methods;
final ValueChanged<RecipientType> onTypeChanged; final ValueChanged<RecipientType> onTypeChanged;
final ValueChanged<RecipientStatus> onStatusChanged; final ValueChanged<RecipientStatus> onStatusChanged;
final void Function(PaymentType, Object?) onMethodsChanged; final void Function(PaymentType, PaymentMethodData?) onMethodsChanged;
final VoidCallback onSave; final VoidCallback onSave;
final bool isEditing; final bool isEditing;
final VoidCallback onBack; final VoidCallback onBack;

View File

@@ -42,10 +42,10 @@ class SaveButton extends StatelessWidget {
child: Text( child: Text(
text ?? AppLocalizations.of(context)!.saveRecipient, text ?? AppLocalizations.of(context)!.saveRecipient,
style: textStyle ?? style: textStyle ??
theme.textTheme.labelLarge?.copyWith( theme.textTheme.labelLarge?.copyWith(
color: theme.colorScheme.onPrimary, color: theme.colorScheme.onPrimary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
), ),
), ),

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/payment/type.dart';
import 'package:pweb/pages/payment_methods/form.dart'; import 'package:pweb/pages/payment_methods/form.dart';
@@ -12,7 +13,7 @@ class PaymentDetailsSection extends StatelessWidget {
final bool isEditable; final bool isEditable;
final VoidCallback? onToggle; final VoidCallback? onToggle;
final PaymentType? selectedType; final PaymentType? selectedType;
final Object? data; final PaymentMethodData? data;
const PaymentDetailsSection({ const PaymentDetailsSection({
super.key, super.key,

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:pshared/models/payment/methods/card.dart'; import 'package:pshared/models/payment/methods/card.dart';
import 'package:pshared/models/payment/methods/crypto_address.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/iban.dart';
import 'package:pshared/models/payment/methods/russian_bank.dart'; import 'package:pshared/models/payment/methods/russian_bank.dart';
import 'package:pshared/models/payment/methods/wallet.dart'; import 'package:pshared/models/payment/methods/wallet.dart';
@@ -16,8 +17,8 @@ import 'package:pweb/pages/payment_methods/add/wallet.dart';
class PaymentMethodForm extends StatelessWidget { class PaymentMethodForm extends StatelessWidget {
final PaymentType? selectedType; final PaymentType? selectedType;
final ValueChanged<Object?> onChanged; final ValueChanged<PaymentMethodData?> onChanged;
final Object? initialData; final PaymentMethodData? initialData;
final bool isEditable; final bool isEditable;
const PaymentMethodForm({ const PaymentMethodForm({

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/recipient/recipient.dart';
@@ -31,9 +32,9 @@ class PaymentInfoSection extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!; final loc = AppLocalizations.of(context)!;
final hasRecipient = recipient != null; final hasRecipient = recipient != null;
final availableTypes = hasRecipient final MethodMap availableTypes = hasRecipient
? pageSelector.getAvailablePaymentTypes() ? pageSelector.getAvailablePaymentTypes()
: {for (final type in PaymentType.values) type: type}; : {for (final type in PaymentType.values) type: null};
if (hasRecipient && availableTypes.isEmpty) { if (hasRecipient && availableTypes.isEmpty) {
return Text(loc.recipientNoPaymentDetails); return Text(loc.recipientNoPaymentDetails);

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/methods/type.dart'; import 'package:pshared/models/payment/methods/type.dart';
import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/payment/type.dart';
@@ -117,7 +118,7 @@ class PageSelectorProvider extends ChangeNotifier {
); );
} }
Map<PaymentType, Object> getAvailablePaymentTypes() { MethodMap getAvailablePaymentTypes() {
final recipient = selectedRecipient; final recipient = selectedRecipient;
if ((recipient == null) || !methodsProvider.isReady) return {}; if ((recipient == null) || !methodsProvider.isReady) return {};

View File

@@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/recipient/recipient.dart';
@@ -8,14 +9,14 @@ import 'package:pweb/providers/page_selector.dart';
class PaymentFlowProvider extends ChangeNotifier { class PaymentFlowProvider extends ChangeNotifier {
PaymentType _selectedType; PaymentType _selectedType;
Object? _manualPaymentData; PaymentMethodData? _manualPaymentData;
PaymentFlowProvider({ PaymentFlowProvider({
required PaymentType initialType, required PaymentType initialType,
}) : _selectedType = initialType; }) : _selectedType = initialType;
PaymentType get selectedType => _selectedType; PaymentType get selectedType => _selectedType;
Object? get manualPaymentData => _manualPaymentData; PaymentMethodData? get manualPaymentData => _manualPaymentData;
void syncWithSelector(PageSelectorProvider selector) { void syncWithSelector(PageSelectorProvider selector) {
final recipient = selector.selectedRecipient; final recipient = selector.selectedRecipient;
@@ -53,7 +54,7 @@ class PaymentFlowProvider extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void setManualPaymentData(Object? data) { void setManualPaymentData(PaymentMethodData? data) {
_manualPaymentData = data; _manualPaymentData = data;
notifyListeners(); notifyListeners();
} }

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pshared/utils/snackbar.dart';
import 'package:pweb/utils/error/handler.dart'; import 'package:pweb/utils/error/handler.dart';
import 'package:pweb/widgets/error/content.dart'; import 'package:pweb/widgets/error/content.dart';
@@ -52,13 +53,18 @@ Future<T?> executeActionWithNotification<T>({
required BuildContext context, required BuildContext context,
required Future<T> Function() action, required Future<T> Function() action,
required String errorMessage, required String errorMessage,
String? successMessage,
int delaySeconds = 3, int delaySeconds = 3,
}) async { }) async {
final scaffoldMessenger = ScaffoldMessenger.of(context); final scaffoldMessenger = ScaffoldMessenger.of(context);
final localizations = AppLocalizations.of(context)!; final localizations = AppLocalizations.of(context)!;
try { try {
return await action(); final result = await action();
if (successMessage != null) {
notifyUser(context, successMessage, delaySeconds: delaySeconds);
}
return result;
} catch (e) { } catch (e) {
// Report the error using your existing notifier. // Report the error using your existing notifier.
notifyUserOfErrorX( notifyUserOfErrorX(

View File

@@ -1,12 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/payment/type.dart';
import 'package:pweb/utils/payment/label.dart'; import 'package:pweb/utils/payment/label.dart';
class PaymentTypeSelector extends StatelessWidget { class PaymentTypeSelector extends StatelessWidget {
final Map<PaymentType, Object> availableTypes; final MethodMap availableTypes;
final PaymentType selectedType; final PaymentType selectedType;
final ValueChanged<PaymentType> onSelected; final ValueChanged<PaymentType> onSelected;