Fixed search field in payment page and cleaned up paymentFlow
This commit is contained in:
20
frontend/pshared/lib/api/responses/payment/payment.dart
Normal file
20
frontend/pshared/lib/api/responses/payment/payment.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/api/responses/base.dart';
|
||||||
|
import 'package:pshared/api/responses/token.dart';
|
||||||
|
import 'package:pshared/data/dto/payment/payment.dart';
|
||||||
|
|
||||||
|
part 'payment.g.dart';
|
||||||
|
|
||||||
|
|
||||||
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
class PaymentResponse extends BaseAuthorizedResponse {
|
||||||
|
|
||||||
|
final PaymentDTO payment;
|
||||||
|
|
||||||
|
const PaymentResponse({required super.accessToken, required this.payment});
|
||||||
|
|
||||||
|
factory PaymentResponse.fromJson(Map<String, dynamic> json) => _$PaymentResponseFromJson(json);
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() => _$PaymentResponseToJson(this);
|
||||||
|
}
|
||||||
64
frontend/pshared/lib/provider/payment/provider.dart
Normal file
64
frontend/pshared/lib/provider/payment/provider.dart
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
|
import 'package:pshared/provider/organizations.dart';
|
||||||
|
import 'package:pshared/provider/payment/quotation.dart';
|
||||||
|
import 'package:pshared/provider/resource.dart';
|
||||||
|
import 'package:pshared/service/payment/service.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentProvider extends ChangeNotifier {
|
||||||
|
late OrganizationsProvider _organization;
|
||||||
|
late QuotationProvider _quotation;
|
||||||
|
|
||||||
|
Resource<Payment> _payment = Resource(data: null, isLoading: false, error: null);
|
||||||
|
bool _isLoaded = false;
|
||||||
|
|
||||||
|
void update(OrganizationsProvider organization, QuotationProvider quotation) {
|
||||||
|
_quotation = quotation;
|
||||||
|
_organization = organization;
|
||||||
|
}
|
||||||
|
|
||||||
|
Payment? get payment => _payment.data;
|
||||||
|
bool get isLoading => _payment.isLoading;
|
||||||
|
Exception? get error => _payment.error;
|
||||||
|
bool get isReady => _isLoaded && !_payment.isLoading && _payment.error == null;
|
||||||
|
|
||||||
|
void _setResource(Resource<Payment> payment) {
|
||||||
|
_payment = payment;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Payment?> pay({String? idempotencyKey, Map<String, String>? metadata}) async {
|
||||||
|
if (!_organization.isOrganizationSet) throw StateError('Organization is not set');
|
||||||
|
if (!_quotation.isReady) throw StateError('Quotation is not ready');
|
||||||
|
final quoteRef = _quotation.quotation?.quoteRef;
|
||||||
|
if (quoteRef == null || quoteRef.isEmpty) {
|
||||||
|
throw StateError('Quotation reference is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
_setResource(_payment.copyWith(isLoading: true, error: null));
|
||||||
|
try {
|
||||||
|
final response = await PaymentService.pay(
|
||||||
|
_organization.current.id,
|
||||||
|
quoteRef,
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
|
metadata: metadata,
|
||||||
|
);
|
||||||
|
_isLoaded = true;
|
||||||
|
_setResource(_payment.copyWith(data: response, isLoading: false, error: null));
|
||||||
|
} catch (e) {
|
||||||
|
_setResource(_payment.copyWith(
|
||||||
|
data: null,
|
||||||
|
error: e is Exception ? e : Exception(e.toString()),
|
||||||
|
isLoading: false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return _payment.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
_setResource(Resource(data: null, isLoading: false, error: null));
|
||||||
|
_isLoaded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
frontend/pshared/lib/service/payment/service.dart
Normal file
35
frontend/pshared/lib/service/payment/service.dart
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/api/requests/payment/initiate.dart';
|
||||||
|
import 'package:pshared/api/responses/payment/payment.dart';
|
||||||
|
import 'package:pshared/data/mapper/payment/payment_response.dart';
|
||||||
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
|
import 'package:pshared/service/authorization/service.dart';
|
||||||
|
import 'package:pshared/service/services.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentService {
|
||||||
|
static final _logger = Logger('service.payment');
|
||||||
|
static const String _objectType = Services.payments;
|
||||||
|
|
||||||
|
static Future<Payment> pay(
|
||||||
|
String organizationRef,
|
||||||
|
String quotationRef, {
|
||||||
|
String? idempotencyKey,
|
||||||
|
Map<String, String>? metadata,
|
||||||
|
}) async {
|
||||||
|
_logger.fine('Executing payment for quotation $quotationRef in $organizationRef');
|
||||||
|
final request = InitiatePaymentRequest(
|
||||||
|
idempotencyKey: idempotencyKey ?? Uuid().v4(),
|
||||||
|
quoteRef: quotationRef,
|
||||||
|
metadata: metadata,
|
||||||
|
);
|
||||||
|
final response = await AuthorizationService.getPOSTResponse(
|
||||||
|
_objectType,
|
||||||
|
'/by-quote/$organizationRef',
|
||||||
|
request.toJson(),
|
||||||
|
);
|
||||||
|
return PaymentResponse.fromJson(response).payment.toDomain();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,9 @@ import 'package:pshared/provider/permissions.dart';
|
|||||||
import 'package:pshared/provider/account.dart';
|
import 'package:pshared/provider/account.dart';
|
||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
import 'package:pshared/provider/payment/amount.dart';
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
|
import 'package:pshared/provider/payment/flow.dart';
|
||||||
|
import 'package:pshared/provider/payment/provider.dart';
|
||||||
|
import 'package:pshared/provider/payment/quotation.dart';
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||||
import 'package:pshared/provider/payment/wallets.dart';
|
import 'package:pshared/provider/payment/wallets.dart';
|
||||||
@@ -21,7 +24,7 @@ import 'package:pshared/service/payment/wallets.dart';
|
|||||||
import 'package:pweb/app/app.dart';
|
import 'package:pweb/app/app.dart';
|
||||||
import 'package:pweb/app/timeago.dart';
|
import 'package:pweb/app/timeago.dart';
|
||||||
import 'package:pweb/providers/carousel.dart';
|
import 'package:pweb/providers/carousel.dart';
|
||||||
import 'package:pweb/providers/mock_payment.dart';
|
import 'package:pshared/models/payment/type.dart';
|
||||||
import 'package:pweb/providers/operatioins.dart';
|
import 'package:pweb/providers/operatioins.dart';
|
||||||
import 'package:pweb/providers/two_factor.dart';
|
import 'package:pweb/providers/two_factor.dart';
|
||||||
import 'package:pweb/providers/upload_history.dart';
|
import 'package:pweb/providers/upload_history.dart';
|
||||||
@@ -90,7 +93,7 @@ void main() async {
|
|||||||
create: (_) => WalletTransactionsProvider(MockWalletTransactionsService())..load(),
|
create: (_) => WalletTransactionsProvider(MockWalletTransactionsService())..load(),
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => MockPaymentProvider(),
|
create: (_) => PaymentFlowProvider(initialType: PaymentType.bankAccount),
|
||||||
),
|
),
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
@@ -99,6 +102,22 @@ void main() async {
|
|||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => PaymentAmountProvider(),
|
create: (_) => PaymentAmountProvider(),
|
||||||
),
|
),
|
||||||
|
ChangeNotifierProxyProvider4<OrganizationsProvider, PaymentAmountProvider, WalletsProvider, PaymentFlowProvider, QuotationProvider>(
|
||||||
|
create: (_) => QuotationProvider(),
|
||||||
|
update: (context, organization, payment, wallets, flow, provider) => provider!..update(
|
||||||
|
organization,
|
||||||
|
payment,
|
||||||
|
wallets,
|
||||||
|
flow,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ChangeNotifierProxyProvider2<OrganizationsProvider, QuotationProvider, PaymentProvider>(
|
||||||
|
create: (_) => PaymentProvider(),
|
||||||
|
update: (context, organization, quotation, provider) => provider!..update(
|
||||||
|
organization,
|
||||||
|
quotation,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: const PayApp(),
|
child: const PayApp(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.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/quotation.dart';
|
|
||||||
import 'package:pshared/provider/payment/wallets.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/payouts/form.dart';
|
import 'package:pweb/pages/dashboard/payouts/form.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -15,16 +7,5 @@ class PaymentFromWrappingWidget extends StatelessWidget {
|
|||||||
const PaymentFromWrappingWidget({super.key});
|
const PaymentFromWrappingWidget({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => MultiProvider(
|
Widget build(BuildContext context) => const PaymentFormWidget();
|
||||||
providers: [
|
|
||||||
ChangeNotifierProvider(
|
|
||||||
create: (_) => PaymentAmountProvider(),
|
|
||||||
),
|
|
||||||
ChangeNotifierProxyProvider4<OrganizationsProvider, PaymentAmountProvider, WalletsProvider, PaymentFlowProvider, QuotationProvider>(
|
|
||||||
create: (_) => QuotationProvider(),
|
|
||||||
update: (context, orgnization, payment, wallet, flow, provider) => provider!..update(orgnization, payment, wallet, flow),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const PaymentFormWidget(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:pshared/models/payment/methods/type.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/provider/payment/flow.dart';
|
import 'package:pshared/provider/payment/flow.dart';
|
||||||
|
import 'package:pshared/provider/payment/provider.dart';
|
||||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
@@ -38,16 +39,12 @@ class PaymentPage extends StatefulWidget {
|
|||||||
class _PaymentPageState extends State<PaymentPage> {
|
class _PaymentPageState extends State<PaymentPage> {
|
||||||
late final TextEditingController _searchController;
|
late final TextEditingController _searchController;
|
||||||
late final FocusNode _searchFocusNode;
|
late final FocusNode _searchFocusNode;
|
||||||
late final PaymentFlowProvider _flowProvider;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_searchController = TextEditingController();
|
_searchController = TextEditingController();
|
||||||
_searchFocusNode = FocusNode();
|
_searchFocusNode = FocusNode();
|
||||||
_flowProvider = PaymentFlowProvider(
|
|
||||||
initialType: widget.initialPaymentType ?? PaymentType.bankAccount,
|
|
||||||
);
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _initializePaymentPage());
|
WidgetsBinding.instance.addPostFrameCallback((_) => _initializePaymentPage());
|
||||||
}
|
}
|
||||||
@@ -56,16 +53,16 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_searchController.dispose();
|
_searchController.dispose();
|
||||||
_searchFocusNode.dispose();
|
_searchFocusNode.dispose();
|
||||||
_flowProvider.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initializePaymentPage() {
|
void _initializePaymentPage() {
|
||||||
|
final flowProvider = context.read<PaymentFlowProvider>();
|
||||||
final methodsProvider = context.read<PaymentMethodsProvider>();
|
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||||
_handleWalletAutoSelection(methodsProvider);
|
_handleWalletAutoSelection(methodsProvider, flowProvider);
|
||||||
|
|
||||||
final recipient = context.read<RecipientsProvider>().currentObject;
|
final recipient = context.read<RecipientsProvider>().currentObject;
|
||||||
_flowProvider.syncWith(
|
flowProvider.syncWith(
|
||||||
recipient: recipient,
|
recipient: recipient,
|
||||||
methodsProvider: methodsProvider,
|
methodsProvider: methodsProvider,
|
||||||
preferredType: widget.initialPaymentType,
|
preferredType: widget.initialPaymentType,
|
||||||
@@ -77,11 +74,12 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleRecipientSelected(Recipient recipient) {
|
void _handleRecipientSelected(Recipient recipient) {
|
||||||
|
final flowProvider = context.read<PaymentFlowProvider>();
|
||||||
final recipientProvider = context.read<RecipientsProvider>();
|
final recipientProvider = context.read<RecipientsProvider>();
|
||||||
final methodsProvider = context.read<PaymentMethodsProvider>();
|
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||||
|
|
||||||
recipientProvider.setCurrentObject(recipient.id);
|
recipientProvider.setCurrentObject(recipient.id);
|
||||||
_flowProvider.reset(
|
flowProvider.reset(
|
||||||
recipient: recipient,
|
recipient: recipient,
|
||||||
methodsProvider: methodsProvider,
|
methodsProvider: methodsProvider,
|
||||||
preferredType: widget.initialPaymentType,
|
preferredType: widget.initialPaymentType,
|
||||||
@@ -90,11 +88,12 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleRecipientCleared() {
|
void _handleRecipientCleared() {
|
||||||
|
final flowProvider = context.read<PaymentFlowProvider>();
|
||||||
final recipientProvider = context.read<RecipientsProvider>();
|
final recipientProvider = context.read<RecipientsProvider>();
|
||||||
final methodsProvider = context.read<PaymentMethodsProvider>();
|
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||||
|
|
||||||
recipientProvider.setCurrentObject(null);
|
recipientProvider.setCurrentObject(null);
|
||||||
_flowProvider.reset(
|
flowProvider.reset(
|
||||||
recipient: null,
|
recipient: null,
|
||||||
methodsProvider: methodsProvider,
|
methodsProvider: methodsProvider,
|
||||||
preferredType: widget.initialPaymentType,
|
preferredType: widget.initialPaymentType,
|
||||||
@@ -109,8 +108,18 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleSendPayment() {
|
void _handleSendPayment() {
|
||||||
// TODO: Handle Payment logic
|
final flowProvider = context.read<PaymentFlowProvider>();
|
||||||
PosthogService.paymentInitiated(method: _flowProvider.selectedType);
|
final paymentProvider = context.read<PaymentProvider>();
|
||||||
|
if (paymentProvider.isLoading) return;
|
||||||
|
|
||||||
|
paymentProvider.pay().then((_) {
|
||||||
|
PosthogService.paymentInitiated(method: flowProvider.selectedType);
|
||||||
|
}).catchError((error) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(error.toString())),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -120,38 +129,41 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
final recipient = context.select<RecipientsProvider, Recipient?>(
|
final recipient = context.select<RecipientsProvider, Recipient?>(
|
||||||
(provider) => provider.currentObject,
|
(provider) => provider.currentObject,
|
||||||
);
|
);
|
||||||
|
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||||
|
|
||||||
_flowProvider.syncWith(
|
flowProvider.syncWith(
|
||||||
recipient: recipient,
|
recipient: recipient,
|
||||||
methodsProvider: methodsProvider,
|
methodsProvider: methodsProvider,
|
||||||
preferredType: recipient != null ? widget.initialPaymentType : null,
|
preferredType: recipient != null ? widget.initialPaymentType : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
return ChangeNotifierProvider.value(
|
return PaymentPageBody(
|
||||||
value: _flowProvider,
|
onBack: widget.onBack,
|
||||||
child: PaymentPageBody(
|
fallbackDestination: widget.fallbackDestination,
|
||||||
onBack: widget.onBack,
|
recipient: recipient,
|
||||||
fallbackDestination: widget.fallbackDestination,
|
recipientProvider: recipientProvider,
|
||||||
recipient: recipient,
|
methodsProvider: methodsProvider,
|
||||||
recipientProvider: recipientProvider,
|
searchController: _searchController,
|
||||||
methodsProvider: methodsProvider,
|
searchFocusNode: _searchFocusNode,
|
||||||
searchController: _searchController,
|
onSearchChanged: _handleSearchChanged,
|
||||||
searchFocusNode: _searchFocusNode,
|
onRecipientSelected: _handleRecipientSelected,
|
||||||
onSearchChanged: _handleSearchChanged,
|
onRecipientCleared: _handleRecipientCleared,
|
||||||
onRecipientSelected: _handleRecipientSelected,
|
onSend: _handleSendPayment,
|
||||||
onRecipientCleared: _handleRecipientCleared,
|
|
||||||
onSend: _handleSendPayment,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleWalletAutoSelection(PaymentMethodsProvider methodsProvider) {
|
void _handleWalletAutoSelection(PaymentMethodsProvider methodsProvider, PaymentFlowProvider flowProvider) {
|
||||||
final wallet = context.read<WalletsProvider>().selectedWallet;
|
final wallet = context.read<WalletsProvider>().selectedWallet;
|
||||||
if (wallet == null) return;
|
if (wallet == null) return;
|
||||||
|
|
||||||
final matchingMethod = _getPaymentMethodForWallet(wallet, methodsProvider);
|
final matchingMethod = _getPaymentMethodForWallet(wallet, methodsProvider);
|
||||||
if (matchingMethod != null) {
|
if (matchingMethod != null) {
|
||||||
methodsProvider.setCurrentObject(matchingMethod.id);
|
methodsProvider.setCurrentObject(matchingMethod.id);
|
||||||
|
flowProvider.syncWith(
|
||||||
|
recipient: context.read<RecipientsProvider>().currentObject,
|
||||||
|
methodsProvider: methodsProvider,
|
||||||
|
preferredType: widget.initialPaymentType,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user