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_GRPC_PORT=50075
MNTX_GATEWAY_METRICS_PORT=9404
MNTX_GATEWAY_HTTP_PORT=8080
MNTX_GATEWAY_HTTP_PORT=8084
MONETIX_BASE_URL=https://api.txflux.com

View File

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

View File

@@ -1,7 +1,12 @@
import 'package:collection/collection.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/permissions/bound.dart';
import 'package:pshared/models/storable.dart';
import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pshared/provider/template.dart';
@@ -49,6 +54,23 @@ class PaymentMethodsProvider extends GenericProvider<PaymentMethod> {
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) {
// 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/recipient.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/template.dart';
import 'package:pshared/service/recipient/service.dart';
@@ -51,6 +53,20 @@ class RecipientsProvider extends GenericProvider<Recipient> {
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) {
_organizations = organizations;
if (_organizations.isOrganizationSet) {

View File

@@ -429,6 +429,11 @@
"companyName": "Name of your company",
"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",
"companyDescription": "Company Description",
"companyDescriptionHint": "Describe any of the fields of the Company's business",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pweb/pages/payment_methods/form.dart';
@@ -12,7 +13,7 @@ class PaymentDetailsSection extends StatelessWidget {
final bool isEditable;
final VoidCallback? onToggle;
final PaymentType? selectedType;
final Object? data;
final PaymentMethodData? data;
const PaymentDetailsSection({
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/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/russian_bank.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 {
final PaymentType? selectedType;
final ValueChanged<Object?> onChanged;
final Object? initialData;
final ValueChanged<PaymentMethodData?> onChanged;
final PaymentMethodData? initialData;
final bool isEditable;
const PaymentMethodForm({

View File

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

View File

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

View File

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

View File

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

View File

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