ledger top up functionality and few small fixes for project architechture and design
This commit is contained in:
@@ -1,87 +0,0 @@
|
|||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tech/sendico/gateway/tgsettle/storage"
|
|
||||||
"github.com/tech/sendico/gateway/tgsettle/storage/model"
|
|
||||||
"github.com/tech/sendico/pkg/db/repository"
|
|
||||||
ri "github.com/tech/sendico/pkg/db/repository/index"
|
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
|
||||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
treasuryTelegramUsersCollection = "treasury_telegram_users"
|
|
||||||
fieldTreasuryTelegramUserID = "telegramUserId"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TreasuryTelegramUsers struct {
|
|
||||||
logger mlogger.Logger
|
|
||||||
repo repository.Repository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTreasuryTelegramUsers(logger mlogger.Logger, db *mongo.Database) (*TreasuryTelegramUsers, error) {
|
|
||||||
if db == nil {
|
|
||||||
return nil, merrors.InvalidArgument("mongo database is nil")
|
|
||||||
}
|
|
||||||
if logger == nil {
|
|
||||||
logger = zap.NewNop()
|
|
||||||
}
|
|
||||||
logger = logger.Named("treasury_telegram_users").With(zap.String("collection", treasuryTelegramUsersCollection))
|
|
||||||
|
|
||||||
repo := repository.CreateMongoRepository(db, treasuryTelegramUsersCollection)
|
|
||||||
if err := repo.CreateIndex(&ri.Definition{
|
|
||||||
Keys: []ri.Key{{Field: fieldTreasuryTelegramUserID, Sort: ri.Asc}},
|
|
||||||
Unique: true,
|
|
||||||
}); err != nil {
|
|
||||||
logger.Error("Failed to create treasury telegram users user_id index", zap.Error(err), zap.String("index_field", fieldTreasuryTelegramUserID))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TreasuryTelegramUsers{
|
|
||||||
logger: logger,
|
|
||||||
repo: repo,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TreasuryTelegramUsers) FindByTelegramUserID(ctx context.Context, telegramUserID string) (*model.TreasuryTelegramUser, error) {
|
|
||||||
telegramUserID = strings.TrimSpace(telegramUserID)
|
|
||||||
if telegramUserID == "" {
|
|
||||||
return nil, merrors.InvalidArgument("telegram_user_id is required", "telegram_user_id")
|
|
||||||
}
|
|
||||||
var result model.TreasuryTelegramUser
|
|
||||||
err := t.repo.FindOneByFilter(ctx, repository.Filter(fieldTreasuryTelegramUserID, telegramUserID), &result)
|
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) {
|
|
||||||
t.logger.Warn("Failed to load treasury telegram user", zap.Error(err), zap.String("telegram_user_id", telegramUserID))
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result.TelegramUserID = strings.TrimSpace(result.TelegramUserID)
|
|
||||||
result.LedgerAccountID = strings.TrimSpace(result.LedgerAccountID)
|
|
||||||
if len(result.AllowedChatIDs) > 0 {
|
|
||||||
normalized := make([]string, 0, len(result.AllowedChatIDs))
|
|
||||||
for _, next := range result.AllowedChatIDs {
|
|
||||||
next = strings.TrimSpace(next)
|
|
||||||
if next == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
normalized = append(normalized, next)
|
|
||||||
}
|
|
||||||
result.AllowedChatIDs = normalized
|
|
||||||
}
|
|
||||||
if result.TelegramUserID == "" || result.LedgerAccountID == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return &result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ storage.TreasuryTelegramUsersStore = (*TreasuryTelegramUsers)(nil)
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
class OperationDocumentInfo {
|
class OperationDocumentRef {
|
||||||
final String operationRef;
|
|
||||||
final String gatewayService;
|
final String gatewayService;
|
||||||
|
final String operationRef;
|
||||||
|
|
||||||
const OperationDocumentInfo({
|
const OperationDocumentRef({
|
||||||
required this.operationRef,
|
|
||||||
required this.gatewayService,
|
required this.gatewayService,
|
||||||
|
required this.operationRef,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,7 @@ class PayoutRoutes {
|
|||||||
static const walletTopUp = 'payout-wallet-top-up';
|
static const walletTopUp = 'payout-wallet-top-up';
|
||||||
|
|
||||||
static const paymentTypeQuery = 'paymentType';
|
static const paymentTypeQuery = 'paymentType';
|
||||||
|
static const destinationLedgerAccountRefQuery = 'destinationLedgerAccountRef';
|
||||||
static const reportPaymentIdQuery = 'paymentId';
|
static const reportPaymentIdQuery = 'paymentId';
|
||||||
|
|
||||||
static const dashboardPath = '/dashboard';
|
static const dashboardPath = '/dashboard';
|
||||||
@@ -40,7 +41,6 @@ class PayoutRoutes {
|
|||||||
static const editWalletPath = '/methods/edit';
|
static const editWalletPath = '/methods/edit';
|
||||||
static const walletTopUpPath = '/wallet/top-up';
|
static const walletTopUpPath = '/wallet/top-up';
|
||||||
|
|
||||||
|
|
||||||
static String nameFor(PayoutDestination destination) {
|
static String nameFor(PayoutDestination destination) {
|
||||||
switch (destination) {
|
switch (destination) {
|
||||||
case PayoutDestination.dashboard:
|
case PayoutDestination.dashboard:
|
||||||
@@ -126,9 +126,13 @@ class PayoutRoutes {
|
|||||||
|
|
||||||
static Map<String, String> buildQueryParameters({
|
static Map<String, String> buildQueryParameters({
|
||||||
PaymentType? paymentType,
|
PaymentType? paymentType,
|
||||||
|
String? destinationLedgerAccountRef,
|
||||||
}) {
|
}) {
|
||||||
final params = <String, String>{
|
final params = <String, String>{
|
||||||
if (paymentType != null) paymentTypeQuery: paymentType.name,
|
if (paymentType != null) paymentTypeQuery: paymentType.name,
|
||||||
|
if (destinationLedgerAccountRef != null &&
|
||||||
|
destinationLedgerAccountRef.trim().isNotEmpty)
|
||||||
|
destinationLedgerAccountRefQuery: destinationLedgerAccountRef.trim(),
|
||||||
};
|
};
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
@@ -140,35 +144,44 @@ class PayoutRoutes {
|
|||||||
? null
|
? null
|
||||||
: PaymentType.values.firstWhereOrNull((type) => type.name == raw);
|
: PaymentType.values.firstWhereOrNull((type) => type.name == raw);
|
||||||
|
|
||||||
|
static String? destinationLedgerAccountRefFromState(GoRouterState state) =>
|
||||||
|
destinationLedgerAccountRefFromRaw(
|
||||||
|
state.uri.queryParameters[destinationLedgerAccountRefQuery],
|
||||||
|
);
|
||||||
|
|
||||||
|
static String? destinationLedgerAccountRefFromRaw(String? raw) {
|
||||||
|
final value = raw?.trim();
|
||||||
|
if (value == null || value.isEmpty) return null;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PayoutNavigation on BuildContext {
|
extension PayoutNavigation on BuildContext {
|
||||||
void goToPayout(PayoutDestination destination) => goNamed(PayoutRoutes.nameFor(destination));
|
void goToPayout(PayoutDestination destination) =>
|
||||||
|
goNamed(PayoutRoutes.nameFor(destination));
|
||||||
|
|
||||||
void pushToPayout(PayoutDestination destination) => pushNamed(PayoutRoutes.nameFor(destination));
|
void pushToPayout(PayoutDestination destination) =>
|
||||||
|
pushNamed(PayoutRoutes.nameFor(destination));
|
||||||
|
|
||||||
void goToPayment({
|
void goToPayment({
|
||||||
PaymentType? paymentType,
|
PaymentType? paymentType,
|
||||||
}) =>
|
String? destinationLedgerAccountRef,
|
||||||
goNamed(
|
}) => goNamed(
|
||||||
PayoutRoutes.payment,
|
PayoutRoutes.payment,
|
||||||
queryParameters: PayoutRoutes.buildQueryParameters(
|
queryParameters: PayoutRoutes.buildQueryParameters(
|
||||||
paymentType: paymentType,
|
paymentType: paymentType,
|
||||||
|
destinationLedgerAccountRef: destinationLedgerAccountRef,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
void goToReportPayment(String paymentId) => goNamed(
|
void goToReportPayment(String paymentId) => goNamed(
|
||||||
PayoutRoutes.reportPayment,
|
PayoutRoutes.reportPayment,
|
||||||
queryParameters: {
|
queryParameters: {PayoutRoutes.reportPaymentIdQuery: paymentId},
|
||||||
PayoutRoutes.reportPaymentIdQuery: paymentId,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
void pushToReportPayment(String paymentId) => pushNamed(
|
void pushToReportPayment(String paymentId) => pushNamed(
|
||||||
PayoutRoutes.reportPayment,
|
PayoutRoutes.reportPayment,
|
||||||
queryParameters: {
|
queryParameters: {PayoutRoutes.reportPaymentIdQuery: paymentId},
|
||||||
PayoutRoutes.reportPaymentIdQuery: paymentId,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
void pushToWalletTopUp() => pushNamed(PayoutRoutes.walletTopUp);
|
void pushToWalletTopUp() => pushNamed(PayoutRoutes.walletTopUp);
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
onGoToPaymentWithoutRecipient: (type) =>
|
onGoToPaymentWithoutRecipient: (type) =>
|
||||||
_startPayment(context, recipient: null, paymentType: type),
|
_startPayment(context, recipient: null, paymentType: type),
|
||||||
onTopUp: (wallet) => _openWalletTopUp(context, wallet),
|
onTopUp: (wallet) => _openWalletTopUp(context, wallet),
|
||||||
|
onLedgerAddFunds: (account) => _openLedgerAddFunds(context, account),
|
||||||
onWalletTap: (wallet) => _openWalletEdit(context, wallet),
|
onWalletTap: (wallet) => _openWalletEdit(context, wallet),
|
||||||
onLedgerTap: (account) => _openLedgerEdit(context, account),
|
onLedgerTap: (account) => _openLedgerEdit(context, account),
|
||||||
),
|
),
|
||||||
@@ -306,6 +307,8 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
child: PaymentPage(
|
child: PaymentPage(
|
||||||
onBack: (_) => _popOrGo(context),
|
onBack: (_) => _popOrGo(context),
|
||||||
initialPaymentType: PayoutRoutes.paymentTypeFromState(state),
|
initialPaymentType: PayoutRoutes.paymentTypeFromState(state),
|
||||||
|
initialDestinationLedgerAccountRef:
|
||||||
|
PayoutRoutes.destinationLedgerAccountRefFromState(state),
|
||||||
fallbackDestination: fallbackDestination,
|
fallbackDestination: fallbackDestination,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -395,6 +398,20 @@ void _openLedgerEdit(BuildContext context, LedgerAccount account) {
|
|||||||
context.pushToEditWallet();
|
context.pushToEditWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _openLedgerAddFunds(BuildContext context, LedgerAccount account) {
|
||||||
|
context.read<PaymentSourceController>().selectLedgerByRef(
|
||||||
|
account.ledgerAccountRef,
|
||||||
|
);
|
||||||
|
context.read<RecipientsProvider>().setCurrentObject(null);
|
||||||
|
context.pushNamed(
|
||||||
|
PayoutRoutes.payment,
|
||||||
|
queryParameters: PayoutRoutes.buildQueryParameters(
|
||||||
|
paymentType: PaymentType.ledger,
|
||||||
|
destinationLedgerAccountRef: account.ledgerAccountRef,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _openWalletTopUp(BuildContext context, Wallet wallet) {
|
void _openWalletTopUp(BuildContext context, Wallet wallet) {
|
||||||
context.read<WalletsController>().selectWallet(wallet);
|
context.read<WalletsController>().selectWallet(wallet);
|
||||||
context.pushToWalletTopUp();
|
context.pushToWalletTopUp();
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/operation_document.dart';
|
||||||
import 'package:pshared/models/payment/execution_operation.dart';
|
import 'package:pshared/models/payment/execution_operation.dart';
|
||||||
import 'package:pshared/models/payment/payment.dart';
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
import 'package:pshared/provider/payment/payments.dart';
|
import 'package:pshared/provider/payment/payments.dart';
|
||||||
import 'package:pweb/models/documents/operation.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/utils/report/operations/document_rule.dart';
|
import 'package:pweb/utils/report/operations/document_rule.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentDetailsController extends ChangeNotifier {
|
class PaymentDetailsController extends ChangeNotifier {
|
||||||
PaymentDetailsController({required String paymentId})
|
PaymentDetailsController({required String paymentId})
|
||||||
: _paymentId = paymentId;
|
: _paymentId = paymentId;
|
||||||
@@ -20,7 +21,7 @@ class PaymentDetailsController extends ChangeNotifier {
|
|||||||
bool get isLoading => _payments?.isLoading ?? false;
|
bool get isLoading => _payments?.isLoading ?? false;
|
||||||
Exception? get error => _payments?.error;
|
Exception? get error => _payments?.error;
|
||||||
|
|
||||||
OperationDocumentRequestModel? operationDocumentRequest(
|
OperationDocumentRef? operationDocumentRequest(
|
||||||
PaymentExecutionOperation operation,
|
PaymentExecutionOperation operation,
|
||||||
) {
|
) {
|
||||||
final current = _payment;
|
final current = _payment;
|
||||||
@@ -33,7 +34,7 @@ class PaymentDetailsController extends ChangeNotifier {
|
|||||||
|
|
||||||
if (!isOperationDocumentEligible(operation.code)) return null;
|
if (!isOperationDocumentEligible(operation.code)) return null;
|
||||||
|
|
||||||
return OperationDocumentRequestModel(
|
return OperationDocumentRef(
|
||||||
gatewayService: gatewayService,
|
gatewayService: gatewayService,
|
||||||
operationRef: operationRef,
|
operationRef: operationRef,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -638,7 +638,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noFee": "No fee",
|
"noFee": "None",
|
||||||
|
|
||||||
"recipientWillReceive": "Recipient will receive: {amount}",
|
"recipientWillReceive": "Recipient will receive: {amount}",
|
||||||
"@recipientWillReceive": {
|
"@recipientWillReceive": {
|
||||||
|
|||||||
@@ -638,7 +638,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noFee": "Нет комиссии",
|
"noFee": "Без оплаты",
|
||||||
|
|
||||||
"recipientWillReceive": "Получатель получит: {amount}",
|
"recipientWillReceive": "Получатель получит: {amount}",
|
||||||
"@recipientWillReceive": {
|
"@recipientWillReceive": {
|
||||||
|
|||||||
26
frontend/pweb/lib/models/dashboard/balance_item.dart
Normal file
26
frontend/pweb/lib/models/dashboard/balance_item.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
|
|
||||||
|
sealed class BalanceItem {
|
||||||
|
const BalanceItem();
|
||||||
|
|
||||||
|
const factory BalanceItem.wallet(Wallet wallet) = WalletBalanceItem;
|
||||||
|
const factory BalanceItem.ledger(LedgerAccount account) = LedgerBalanceItem;
|
||||||
|
const factory BalanceItem.addAction() = AddBalanceActionItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WalletBalanceItem extends BalanceItem {
|
||||||
|
final Wallet wallet;
|
||||||
|
|
||||||
|
const WalletBalanceItem(this.wallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LedgerBalanceItem extends BalanceItem {
|
||||||
|
final LedgerAccount account;
|
||||||
|
|
||||||
|
const LedgerBalanceItem(this.account);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class AddBalanceActionItem extends BalanceItem {
|
||||||
|
const AddBalanceActionItem();
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
class OperationDocumentRequestModel {
|
|
||||||
final String gatewayService;
|
|
||||||
final String operationRef;
|
|
||||||
|
|
||||||
const OperationDocumentRequestModel({
|
|
||||||
required this.gatewayService,
|
|
||||||
required this.operationRef,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
enum PaymentState {
|
|
||||||
success,
|
|
||||||
failed,
|
|
||||||
cancelled,
|
|
||||||
processing,
|
|
||||||
unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
PaymentState paymentStateFromRaw(String? raw) {
|
|
||||||
final trimmed = (raw ?? '').trim().toUpperCase();
|
|
||||||
final normalized = trimmed.startsWith('PAYMENT_STATE_')
|
|
||||||
? trimmed.substring('PAYMENT_STATE_'.length)
|
|
||||||
: trimmed;
|
|
||||||
|
|
||||||
switch (normalized) {
|
|
||||||
case 'SUCCESS':
|
|
||||||
return PaymentState.success;
|
|
||||||
case 'FAILED':
|
|
||||||
return PaymentState.failed;
|
|
||||||
case 'CANCELLED':
|
|
||||||
return PaymentState.cancelled;
|
|
||||||
case 'PROCESSING':
|
|
||||||
return PaymentState.processing;
|
|
||||||
default:
|
|
||||||
return PaymentState.unknown;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import 'package:pshared/models/ledger/account.dart';
|
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
|
||||||
|
|
||||||
|
|
||||||
enum BalanceItemType { wallet, ledger, addAction }
|
|
||||||
|
|
||||||
class BalanceItem {
|
|
||||||
final BalanceItemType type;
|
|
||||||
final Wallet? wallet;
|
|
||||||
final LedgerAccount? account;
|
|
||||||
|
|
||||||
const BalanceItem.wallet(this.wallet) : type = BalanceItemType.wallet, account = null;
|
|
||||||
|
|
||||||
const BalanceItem.ledger(this.account) : type = BalanceItemType.ledger, wallet = null;
|
|
||||||
|
|
||||||
const BalanceItem.addAction() : type = BalanceItemType.addAction, wallet = null, account = null;
|
|
||||||
|
|
||||||
bool get isWallet => type == BalanceItemType.wallet;
|
|
||||||
bool get isLedger => type == BalanceItemType.ledger;
|
|
||||||
bool get isAdd => type == BalanceItemType.addAction;
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
import 'package:pshared/models/payment/chain_network.dart';
|
|
||||||
import 'package:pshared/utils/l10n/chain.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/add_funds.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/source/card.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/amount.dart';
|
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/header.dart';
|
|
||||||
import 'package:pweb/widgets/refresh_balance/wallet.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class WalletCard extends StatelessWidget {
|
class WalletCard extends StatelessWidget {
|
||||||
@@ -28,56 +19,10 @@ class WalletCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final networkLabel = (wallet.network == null || wallet.network == ChainNetwork.unspecified)
|
return BalanceSourceCard.wallet(
|
||||||
? null
|
|
||||||
: wallet.network!.localizedName(context);
|
|
||||||
final symbol = wallet.tokenSymbol?.trim();
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
color: Theme.of(context).colorScheme.onSecondary,
|
|
||||||
elevation: WalletCardConfig.elevation,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
|
||||||
),
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
|
||||||
onTap: onTap,
|
|
||||||
child: SizedBox.expand(
|
|
||||||
child: Padding(
|
|
||||||
padding: WalletCardConfig.contentPadding,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
BalanceHeader(
|
|
||||||
title: wallet.name,
|
|
||||||
subtitle: networkLabel,
|
|
||||||
badge: (symbol == null || symbol.isEmpty) ? null : symbol,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
BalanceAmount(
|
|
||||||
wallet: wallet,
|
wallet: wallet,
|
||||||
onToggleMask: () {
|
onTap: onTap,
|
||||||
context.read<WalletsController>().toggleBalanceMask(wallet.id);
|
onAddFunds: onTopUp,
|
||||||
},
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
WalletBalanceRefreshButton(
|
|
||||||
walletRef: wallet.id,
|
|
||||||
),
|
|
||||||
BalanceAddFunds(onTopUp: onTopUp),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,20 @@ import 'package:flutter/gestures.dart';
|
|||||||
import 'package:pshared/models/ledger/account.dart';
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/models/dashboard/balance_item.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/add/card.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/add/card.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/balance_item.dart';
|
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/card.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/card.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/indicator.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/indicator.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/ledger.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/ledger.dart';
|
||||||
|
|
||||||
|
|
||||||
class BalanceCarousel extends StatefulWidget {
|
class BalanceCarousel extends StatefulWidget {
|
||||||
final List<BalanceItem> items;
|
final List<BalanceItem> items;
|
||||||
final int currentIndex;
|
final int currentIndex;
|
||||||
final ValueChanged<int> onIndexChanged;
|
final ValueChanged<int> onIndexChanged;
|
||||||
final ValueChanged<Wallet> onTopUp;
|
final ValueChanged<Wallet> onTopUp;
|
||||||
|
final ValueChanged<LedgerAccount> onLedgerAddFunds;
|
||||||
final ValueChanged<Wallet> onWalletTap;
|
final ValueChanged<Wallet> onWalletTap;
|
||||||
final ValueChanged<LedgerAccount> onLedgerTap;
|
final ValueChanged<LedgerAccount> onLedgerTap;
|
||||||
|
|
||||||
@@ -25,6 +27,7 @@ class BalanceCarousel extends StatefulWidget {
|
|||||||
required this.currentIndex,
|
required this.currentIndex,
|
||||||
required this.onIndexChanged,
|
required this.onIndexChanged,
|
||||||
required this.onTopUp,
|
required this.onTopUp,
|
||||||
|
required this.onLedgerAddFunds,
|
||||||
required this.onWalletTap,
|
required this.onWalletTap,
|
||||||
required this.onLedgerTap,
|
required this.onLedgerTap,
|
||||||
});
|
});
|
||||||
@@ -101,17 +104,18 @@ class _BalanceCarouselState extends State<BalanceCarousel> {
|
|||||||
itemCount: widget.items.length,
|
itemCount: widget.items.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = widget.items[index];
|
final item = widget.items[index];
|
||||||
final Widget card = switch (item.type) {
|
final Widget card = switch (item) {
|
||||||
BalanceItemType.wallet => WalletCard(
|
WalletBalanceItem(:final wallet) => WalletCard(
|
||||||
wallet: item.wallet!,
|
wallet: wallet,
|
||||||
onTopUp: () => widget.onTopUp(item.wallet!),
|
onTopUp: () => widget.onTopUp(wallet),
|
||||||
onTap: () => widget.onWalletTap(item.wallet!),
|
onTap: () => widget.onWalletTap(wallet),
|
||||||
),
|
),
|
||||||
BalanceItemType.ledger => LedgerAccountCard(
|
LedgerBalanceItem(:final account) => LedgerAccountCard(
|
||||||
account: item.account!,
|
account: account,
|
||||||
onTap: () => widget.onLedgerTap(item.account!),
|
onTap: () => widget.onLedgerTap(account),
|
||||||
|
onAddFunds: () => widget.onLedgerAddFunds(account),
|
||||||
),
|
),
|
||||||
BalanceItemType.addAction => const AddBalanceCard(),
|
AddBalanceActionItem() => const AddBalanceCard(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||||
import 'package:pshared/provider/ledger.dart';
|
import 'package:pshared/provider/ledger.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/balance_item.dart';
|
import 'package:pweb/models/dashboard/balance_item.dart';
|
||||||
|
|
||||||
|
|
||||||
class BalanceCarouselController with ChangeNotifier {
|
class BalanceCarouselController with ChangeNotifier {
|
||||||
WalletsController? _walletsController;
|
WalletsController? _walletsController;
|
||||||
@@ -73,14 +74,19 @@ class BalanceCarouselController with ChangeNotifier {
|
|||||||
String? _currentWalletRef(List<BalanceItem> items, int index) {
|
String? _currentWalletRef(List<BalanceItem> items, int index) {
|
||||||
if (items.isEmpty || index < 0 || index >= items.length) return null;
|
if (items.isEmpty || index < 0 || index >= items.length) return null;
|
||||||
final current = items[index];
|
final current = items[index];
|
||||||
if (!current.isWallet) return null;
|
return switch (current) {
|
||||||
return current.wallet?.id;
|
WalletBalanceItem(:final wallet) => wallet.id,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
int? _walletIndexByRef(List<BalanceItem> items, String? walletRef) {
|
int? _walletIndexByRef(List<BalanceItem> items, String? walletRef) {
|
||||||
if (walletRef == null || walletRef.isEmpty) return null;
|
if (walletRef == null || walletRef.isEmpty) return null;
|
||||||
final idx = items.indexWhere(
|
final idx = items.indexWhere(
|
||||||
(item) => item.isWallet && item.wallet?.id == walletRef,
|
(item) => switch (item) {
|
||||||
|
WalletBalanceItem(:final wallet) => wallet.id == walletRef,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (idx < 0) return null;
|
if (idx < 0) return null;
|
||||||
return idx;
|
return idx;
|
||||||
@@ -97,17 +103,17 @@ class BalanceCarouselController with ChangeNotifier {
|
|||||||
for (var i = 0; i < left.length; i++) {
|
for (var i = 0; i < left.length; i++) {
|
||||||
final a = left[i];
|
final a = left[i];
|
||||||
final b = right[i];
|
final b = right[i];
|
||||||
if (a.type != b.type) return false;
|
if (a.runtimeType != b.runtimeType) return false;
|
||||||
if (_itemIdentity(a) != _itemIdentity(b)) return false;
|
if (_itemIdentity(a) != _itemIdentity(b)) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _itemIdentity(BalanceItem item) => switch (item.type) {
|
String _itemIdentity(BalanceItem item) => switch (item) {
|
||||||
BalanceItemType.wallet => item.wallet?.id ?? '',
|
WalletBalanceItem(:final wallet) => wallet.id,
|
||||||
BalanceItemType.ledger => item.account?.ledgerAccountRef ?? '',
|
LedgerBalanceItem(:final account) => account.ledgerAccountRef,
|
||||||
BalanceItemType.addAction => 'add',
|
AddBalanceActionItem() => 'add',
|
||||||
};
|
};
|
||||||
|
|
||||||
void _syncSelectedWallet() {
|
void _syncSelectedWallet() {
|
||||||
@@ -115,9 +121,8 @@ class BalanceCarouselController with ChangeNotifier {
|
|||||||
if (walletsController == null || _items.isEmpty) return;
|
if (walletsController == null || _items.isEmpty) return;
|
||||||
|
|
||||||
final current = _items[_index];
|
final current = _items[_index];
|
||||||
if (!current.isWallet || current.wallet == null) return;
|
if (current is! WalletBalanceItem) return;
|
||||||
|
final wallet = current.wallet;
|
||||||
final wallet = current.wallet!;
|
|
||||||
if (walletsController.selectedWallet?.id == wallet.id) return;
|
if (walletsController.selectedWallet?.id == wallet.id) return;
|
||||||
walletsController.selectWallet(wallet);
|
walletsController.selectWallet(wallet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,133 +1,27 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/ledger_accounts.dart';
|
|
||||||
import 'package:pshared/models/ledger/account.dart';
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
import 'package:pshared/utils/currency.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/source/card.dart';
|
||||||
import 'package:pshared/utils/money.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/header.dart';
|
|
||||||
import 'package:pweb/widgets/refresh_balance/ledger.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class LedgerAccountCard extends StatelessWidget {
|
class LedgerAccountCard extends StatelessWidget {
|
||||||
final LedgerAccount account;
|
final LedgerAccount account;
|
||||||
|
final VoidCallback onAddFunds;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
const LedgerAccountCard({super.key, required this.account, this.onTap});
|
const LedgerAccountCard({
|
||||||
|
super.key,
|
||||||
String _formatBalance() {
|
required this.account,
|
||||||
final money = account.balance?.balance;
|
required this.onAddFunds,
|
||||||
if (money == null) return '--';
|
this.onTap,
|
||||||
|
});
|
||||||
final amount = parseMoneyAmount(money.amount, fallback: double.nan);
|
|
||||||
if (amount.isNaN) {
|
|
||||||
return '${money.amount} ${money.currency}';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final currency = currencyStringToCode(money.currency);
|
|
||||||
final symbol = currencyCodeToSymbol(currency);
|
|
||||||
if (symbol.trim().isEmpty) {
|
|
||||||
return '${amountToString(amount)} ${money.currency}';
|
|
||||||
}
|
|
||||||
return '${amountToString(amount)} $symbol';
|
|
||||||
} catch (_) {
|
|
||||||
return '${amountToString(amount)} ${money.currency}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatMaskedBalance() {
|
|
||||||
final currency = account.currency.trim();
|
|
||||||
if (currency.isEmpty) return '••••';
|
|
||||||
try {
|
|
||||||
final symbol = currencyCodeToSymbol(currencyStringToCode(currency));
|
|
||||||
if (symbol.trim().isEmpty) {
|
|
||||||
return '•••• $currency';
|
|
||||||
}
|
|
||||||
return '•••• $symbol';
|
|
||||||
} catch (_) {
|
|
||||||
return '•••• $currency';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textTheme = Theme.of(context).textTheme;
|
return BalanceSourceCard.ledger(
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
account: account,
|
||||||
final loc = AppLocalizations.of(context)!;
|
onTap: onTap ?? () {},
|
||||||
final accountName = account.name.trim();
|
onAddFunds: onAddFunds,
|
||||||
final accountCode = account.accountCode.trim();
|
|
||||||
final title = accountName.isNotEmpty ? accountName : loc.paymentTypeLedger;
|
|
||||||
final subtitle = accountCode.isNotEmpty ? accountCode : null;
|
|
||||||
final badge = account.currency.trim().isEmpty
|
|
||||||
? null
|
|
||||||
: account.currency.toUpperCase();
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
color: colorScheme.onSecondary,
|
|
||||||
elevation: WalletCardConfig.elevation,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
|
||||||
),
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
|
||||||
onTap: onTap,
|
|
||||||
child: Padding(
|
|
||||||
padding: WalletCardConfig.contentPadding,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
BalanceHeader(title: title, subtitle: subtitle, badge: badge),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Consumer<LedgerBalanceMaskController>(
|
|
||||||
builder: (context, controller, _) {
|
|
||||||
final isMasked = controller.isBalanceMasked(
|
|
||||||
account.ledgerAccountRef,
|
|
||||||
);
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
isMasked
|
|
||||||
? _formatMaskedBalance()
|
|
||||||
: _formatBalance(),
|
|
||||||
style: textTheme.headlineSmall?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => controller.toggleBalanceMask(
|
|
||||||
account.ledgerAccountRef,
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
isMasked
|
|
||||||
? Icons.visibility_off
|
|
||||||
: Icons.visibility,
|
|
||||||
size: 24,
|
|
||||||
color: colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
LedgerBalanceRefreshButton(
|
|
||||||
ledgerAccountRef: account.ledgerAccountRef,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/controllers/balance_mask/ledger_accounts.dart';
|
||||||
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/payout_page/wallet/edit/fields/ledger/balance_formatter.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class LedgerBalanceAmount extends StatelessWidget {
|
||||||
|
final LedgerAccount account;
|
||||||
|
|
||||||
|
const LedgerBalanceAmount({super.key, required this.account});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Consumer<LedgerBalanceMaskController>(
|
||||||
|
builder: (context, controller, _) {
|
||||||
|
final isMasked = controller.isBalanceMasked(account.ledgerAccountRef);
|
||||||
|
final balance = isMasked
|
||||||
|
? LedgerBalanceFormatter.formatMasked(account)
|
||||||
|
: LedgerBalanceFormatter.format(account);
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
balance,
|
||||||
|
style: textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
controller.toggleBalanceMask(account.ledgerAccountRef);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
isMasked ? Icons.visibility_off : Icons.visibility,
|
||||||
|
size: 24,
|
||||||
|
color: colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/ledger.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/buttons/balance/actions/bar.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class LedgerSourceActions extends StatelessWidget {
|
||||||
|
final String ledgerAccountRef;
|
||||||
|
final VoidCallback onAddFunds;
|
||||||
|
|
||||||
|
const LedgerSourceActions({
|
||||||
|
super.key,
|
||||||
|
required this.ledgerAccountRef,
|
||||||
|
required this.onAddFunds,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ledgerProvider = context.watch<LedgerAccountsProvider>();
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
final isBusy =
|
||||||
|
ledgerProvider.isWalletRefreshing(ledgerAccountRef) ||
|
||||||
|
ledgerProvider.isLoading;
|
||||||
|
final hasTarget = ledgerProvider.accounts.any(
|
||||||
|
(a) => a.ledgerAccountRef == ledgerAccountRef,
|
||||||
|
);
|
||||||
|
|
||||||
|
return BalanceActionsBar(
|
||||||
|
isRefreshBusy: isBusy,
|
||||||
|
canRefresh: hasTarget,
|
||||||
|
onRefresh: () {
|
||||||
|
context.read<LedgerAccountsProvider>().refreshBalance(ledgerAccountRef);
|
||||||
|
},
|
||||||
|
onAddFunds: onAddFunds,
|
||||||
|
refreshLabel: loc.refreshBalance,
|
||||||
|
addFundsLabel: loc.addFunds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/payment/wallets.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/buttons/balance/actions/bar.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class WalletSourceActions extends StatelessWidget {
|
||||||
|
final String walletRef;
|
||||||
|
final VoidCallback onAddFunds;
|
||||||
|
|
||||||
|
const WalletSourceActions({
|
||||||
|
super.key,
|
||||||
|
required this.walletRef,
|
||||||
|
required this.onAddFunds,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final walletsProvider = context.watch<WalletsProvider>();
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
final isBusy =
|
||||||
|
walletsProvider.isWalletRefreshing(walletRef) ||
|
||||||
|
walletsProvider.isLoading;
|
||||||
|
final hasTarget = walletsProvider.wallets.any((w) => w.id == walletRef);
|
||||||
|
|
||||||
|
return BalanceActionsBar(
|
||||||
|
isRefreshBusy: isBusy,
|
||||||
|
canRefresh: hasTarget,
|
||||||
|
onRefresh: () {
|
||||||
|
context.read<WalletsProvider>().refreshBalance(walletRef);
|
||||||
|
},
|
||||||
|
onAddFunds: onAddFunds,
|
||||||
|
refreshLabel: loc.refreshBalance,
|
||||||
|
addFundsLabel: loc.addFunds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||||
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
|
import 'package:pshared/models/payment/chain_network.dart';
|
||||||
|
import 'package:pshared/models/payment/source_type.dart';
|
||||||
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
|
import 'package:pshared/utils/l10n/chain.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/buttons/balance/amount.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/buttons/balance/ledger_amount.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/buttons/balance/source/actions/ledger.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/buttons/balance/source/actions/wallet.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/buttons/balance/source/card_layout.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class BalanceSourceCard extends StatelessWidget {
|
||||||
|
final PaymentSourceType _type;
|
||||||
|
final Wallet? _wallet;
|
||||||
|
final LedgerAccount? _ledgerAccount;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final VoidCallback onAddFunds;
|
||||||
|
|
||||||
|
const BalanceSourceCard.wallet({
|
||||||
|
super.key,
|
||||||
|
required Wallet wallet,
|
||||||
|
required this.onTap,
|
||||||
|
required this.onAddFunds,
|
||||||
|
}) : _type = PaymentSourceType.wallet,
|
||||||
|
_wallet = wallet,
|
||||||
|
_ledgerAccount = null;
|
||||||
|
|
||||||
|
const BalanceSourceCard.ledger({
|
||||||
|
super.key,
|
||||||
|
required LedgerAccount account,
|
||||||
|
required this.onTap,
|
||||||
|
required this.onAddFunds,
|
||||||
|
}) : _type = PaymentSourceType.ledger,
|
||||||
|
_wallet = null,
|
||||||
|
_ledgerAccount = account;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => switch (_type) {
|
||||||
|
PaymentSourceType.wallet => _buildWalletCard(context, _wallet!),
|
||||||
|
PaymentSourceType.ledger => _buildLedgerCard(context, _ledgerAccount!),
|
||||||
|
};
|
||||||
|
|
||||||
|
Widget _buildWalletCard(BuildContext context, Wallet wallet) {
|
||||||
|
final networkLabel =
|
||||||
|
(wallet.network == null || wallet.network == ChainNetwork.unspecified)
|
||||||
|
? null
|
||||||
|
: wallet.network!.localizedName(context);
|
||||||
|
final symbol = wallet.tokenSymbol?.trim();
|
||||||
|
|
||||||
|
return BalanceSourceCardLayout(
|
||||||
|
title: wallet.name,
|
||||||
|
subtitle: networkLabel,
|
||||||
|
badge: (symbol == null || symbol.isEmpty) ? null : symbol,
|
||||||
|
onTap: onTap,
|
||||||
|
actions: WalletSourceActions(
|
||||||
|
walletRef: wallet.id,
|
||||||
|
onAddFunds: onAddFunds,
|
||||||
|
),
|
||||||
|
amount: BalanceAmount(
|
||||||
|
wallet: wallet,
|
||||||
|
onToggleMask: () {
|
||||||
|
context.read<WalletsController>().toggleBalanceMask(wallet.id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLedgerCard(BuildContext context, LedgerAccount account) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
final accountName = account.name.trim();
|
||||||
|
final accountCode = account.accountCode.trim();
|
||||||
|
final title = accountName.isNotEmpty ? accountName : loc.paymentTypeLedger;
|
||||||
|
final subtitle = accountCode.isNotEmpty ? accountCode : null;
|
||||||
|
final badge = account.currency.trim().isEmpty
|
||||||
|
? null
|
||||||
|
: account.currency.toUpperCase();
|
||||||
|
|
||||||
|
return BalanceSourceCardLayout(
|
||||||
|
title: title,
|
||||||
|
subtitle: subtitle,
|
||||||
|
badge: badge,
|
||||||
|
onTap: onTap,
|
||||||
|
actions: LedgerSourceActions(
|
||||||
|
ledgerAccountRef: account.ledgerAccountRef,
|
||||||
|
onAddFunds: onAddFunds,
|
||||||
|
),
|
||||||
|
amount: LedgerBalanceAmount(account: account),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/buttons/balance/header.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class BalanceSourceCardLayout extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String? subtitle;
|
||||||
|
final String? badge;
|
||||||
|
final Widget amount;
|
||||||
|
final Widget actions;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const BalanceSourceCardLayout({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.subtitle,
|
||||||
|
required this.badge,
|
||||||
|
required this.amount,
|
||||||
|
required this.actions,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
color: colorScheme.onSecondary,
|
||||||
|
elevation: WalletCardConfig.elevation,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
||||||
|
onTap: onTap,
|
||||||
|
child: SizedBox.expand(
|
||||||
|
child: Padding(
|
||||||
|
padding: WalletCardConfig.contentPadding,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
BalanceHeader(title: title, subtitle: subtitle, badge: badge),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Expanded(child: amount),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
actions,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,15 +12,16 @@ import 'package:pweb/pages/dashboard/buttons/balance/controller.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class BalanceWidget extends StatelessWidget {
|
class BalanceWidget extends StatelessWidget {
|
||||||
final ValueChanged<Wallet> onTopUp;
|
final ValueChanged<Wallet> onTopUp;
|
||||||
|
final ValueChanged<LedgerAccount> onLedgerAddFunds;
|
||||||
final ValueChanged<Wallet> onWalletTap;
|
final ValueChanged<Wallet> onWalletTap;
|
||||||
final ValueChanged<LedgerAccount> onLedgerTap;
|
final ValueChanged<LedgerAccount> onLedgerTap;
|
||||||
|
|
||||||
const BalanceWidget({
|
const BalanceWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onTopUp,
|
required this.onTopUp,
|
||||||
|
required this.onLedgerAddFunds,
|
||||||
required this.onWalletTap,
|
required this.onWalletTap,
|
||||||
required this.onLedgerTap,
|
required this.onLedgerTap,
|
||||||
});
|
});
|
||||||
@@ -49,6 +50,7 @@ class BalanceWidget extends StatelessWidget {
|
|||||||
currentIndex: carousel.index,
|
currentIndex: carousel.index,
|
||||||
onIndexChanged: carousel.onPageChanged,
|
onIndexChanged: carousel.onPageChanged,
|
||||||
onTopUp: onTopUp,
|
onTopUp: onTopUp,
|
||||||
|
onLedgerAddFunds: onLedgerAddFunds,
|
||||||
onWalletTap: onWalletTap,
|
onWalletTap: onWalletTap,
|
||||||
onLedgerTap: onLedgerTap,
|
onLedgerTap: onLedgerTap,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class DashboardPage extends StatefulWidget {
|
|||||||
final ValueChanged<Recipient> onRecipientSelected;
|
final ValueChanged<Recipient> onRecipientSelected;
|
||||||
final void Function(PaymentType type) onGoToPaymentWithoutRecipient;
|
final void Function(PaymentType type) onGoToPaymentWithoutRecipient;
|
||||||
final ValueChanged<Wallet> onTopUp;
|
final ValueChanged<Wallet> onTopUp;
|
||||||
|
final ValueChanged<LedgerAccount> onLedgerAddFunds;
|
||||||
final ValueChanged<Wallet> onWalletTap;
|
final ValueChanged<Wallet> onWalletTap;
|
||||||
final ValueChanged<LedgerAccount> onLedgerTap;
|
final ValueChanged<LedgerAccount> onLedgerTap;
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ class DashboardPage extends StatefulWidget {
|
|||||||
required this.onRecipientSelected,
|
required this.onRecipientSelected,
|
||||||
required this.onGoToPaymentWithoutRecipient,
|
required this.onGoToPaymentWithoutRecipient,
|
||||||
required this.onTopUp,
|
required this.onTopUp,
|
||||||
|
required this.onLedgerAddFunds,
|
||||||
required this.onWalletTap,
|
required this.onWalletTap,
|
||||||
required this.onLedgerTap,
|
required this.onLedgerTap,
|
||||||
});
|
});
|
||||||
@@ -90,6 +92,7 @@ class _DashboardPageState extends State<DashboardPage> {
|
|||||||
BalanceWidgetProviders(
|
BalanceWidgetProviders(
|
||||||
child: BalanceWidget(
|
child: BalanceWidget(
|
||||||
onTopUp: widget.onTopUp,
|
onTopUp: widget.onTopUp,
|
||||||
|
onLedgerAddFunds: widget.onLedgerAddFunds,
|
||||||
onWalletTap: widget.onWalletTap,
|
onWalletTap: widget.onWalletTap,
|
||||||
onLedgerTap: widget.onLedgerTap,
|
onLedgerTap: widget.onLedgerTap,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:pshared/utils/currency.dart';
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
import 'package:pweb/controllers/payments/amount_field.dart';
|
import 'package:pweb/controllers/payments/amount_field.dart';
|
||||||
import 'package:pweb/models/payment/amount/mode.dart';
|
|
||||||
import 'package:pweb/pages/dashboard/payouts/amount/mode/selector.dart';
|
import 'package:pweb/pages/dashboard/payouts/amount/mode/selector.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:pshared/provider/payment/multiple/quotation.dart';
|
|||||||
|
|
||||||
import 'package:pweb/controllers/payouts/multiple_payouts.dart';
|
import 'package:pweb/controllers/payouts/multiple_payouts.dart';
|
||||||
import 'package:pweb/controllers/payouts/payout_verification.dart';
|
import 'package:pweb/controllers/payouts/payout_verification.dart';
|
||||||
import 'package:pweb/utils/payment/payout_verification_flow.dart';
|
import 'package:pweb/utils/payment/verification_flow.dart';
|
||||||
import 'package:pweb/widgets/dialogs/payment_status_dialog.dart';
|
import 'package:pweb/widgets/dialogs/payment_status_dialog.dart';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class RecipientAvatar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textColor = Theme.of(context).colorScheme.onPrimary;
|
final textColor = Theme.of(context).colorScheme.onSecondary;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -31,7 +31,7 @@ class RecipientAvatar extends StatelessWidget {
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: avatarRadius,
|
radius: avatarRadius,
|
||||||
backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl!) : null,
|
backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl!) : null,
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primaryFixed,
|
||||||
child: avatarUrl == null
|
child: avatarUrl == null
|
||||||
? Text(
|
? Text(
|
||||||
getInitials(name),
|
getInitials(name),
|
||||||
|
|||||||
@@ -7,34 +7,32 @@ import 'package:pweb/pages/dashboard/payouts/single/address_book/avatar.dart';
|
|||||||
class ShortListAddressBookPayout extends StatelessWidget {
|
class ShortListAddressBookPayout extends StatelessWidget {
|
||||||
final List<Recipient> recipients;
|
final List<Recipient> recipients;
|
||||||
final ValueChanged<Recipient> onSelected;
|
final ValueChanged<Recipient> onSelected;
|
||||||
final Widget? trailing;
|
final Widget? leading;
|
||||||
|
|
||||||
const ShortListAddressBookPayout({
|
const ShortListAddressBookPayout({
|
||||||
super.key,
|
super.key,
|
||||||
required this.recipients,
|
required this.recipients,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
this.trailing,
|
this.leading,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const double _avatarRadius = 20;
|
static const double _avatarRadius = 20;
|
||||||
static const double _avatarSize = 80;
|
static const double _avatarSize = 80;
|
||||||
static const EdgeInsets _padding = EdgeInsets.symmetric(horizontal: 10, vertical: 8);
|
static const EdgeInsets _padding = EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 8,
|
||||||
|
);
|
||||||
static const TextStyle _nameStyle = TextStyle(fontSize: 12);
|
static const TextStyle _nameStyle = TextStyle(fontSize: 12);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final trailingWidget = trailing;
|
final leadingWidget = leading;
|
||||||
|
final recipientItems = recipients.map((recipient) {
|
||||||
return SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: Row(
|
|
||||||
children:
|
|
||||||
recipients.map((recipient) {
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: _padding,
|
padding: _padding,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
hoverColor: Theme.of(context).colorScheme.primaryContainer,
|
hoverColor: Theme.of(context).colorScheme.onTertiary,
|
||||||
onTap: () => onSelected(recipient),
|
onTap: () => onSelected(recipient),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: _avatarSize,
|
height: _avatarSize,
|
||||||
@@ -49,12 +47,16 @@ class ShortListAddressBookPayout extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList()
|
});
|
||||||
..addAll(
|
|
||||||
trailingWidget == null
|
return SingleChildScrollView(
|
||||||
? const []
|
scrollDirection: Axis.horizontal,
|
||||||
: [Padding(padding: _padding, child: trailingWidget)],
|
child: Row(
|
||||||
),
|
children: [
|
||||||
|
if (leadingWidget != null)
|
||||||
|
Padding(padding: _padding, child: leadingWidget),
|
||||||
|
...recipientItems,
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,7 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
|||||||
class AddressBookPayout extends StatefulWidget {
|
class AddressBookPayout extends StatefulWidget {
|
||||||
final ValueChanged<Recipient> onSelected;
|
final ValueChanged<Recipient> onSelected;
|
||||||
|
|
||||||
const AddressBookPayout({
|
const AddressBookPayout({super.key, required this.onSelected});
|
||||||
super.key,
|
|
||||||
required this.onSelected,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AddressBookPayout> createState() => _AddressBookPayoutState();
|
State<AddressBookPayout> createState() => _AddressBookPayoutState();
|
||||||
@@ -71,6 +68,7 @@ class _AddressBookPayoutState extends State<AddressBookPayout> {
|
|||||||
provider.setCurrentObject(null);
|
provider.setCurrentObject(null);
|
||||||
context.pushNamed(PayoutRoutes.addRecipient);
|
context.pushNamed(PayoutRoutes.addRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
final filteredRecipients = filterRecipients(
|
final filteredRecipients = filterRecipients(
|
||||||
recipients: recipients,
|
recipients: recipients,
|
||||||
query: _query,
|
query: _query,
|
||||||
@@ -81,16 +79,18 @@ class _AddressBookPayoutState extends State<AddressBookPayout> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (provider.error != null) {
|
if (provider.error != null) {
|
||||||
return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
loc.notificationError(provider.error ?? loc.noErrorInformation),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: _isExpanded ? _expandedHeight : _collapsedHeight,
|
height: _isExpanded ? _expandedHeight : _collapsedHeight,
|
||||||
child: Card(
|
child: Card(
|
||||||
margin: const EdgeInsets.all(_cardMargin),
|
margin: const EdgeInsets.all(_cardMargin),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
color: Theme.of(context).colorScheme.onSecondary,
|
color: Theme.of(context).colorScheme.onSecondary,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -121,7 +121,7 @@ class _AddressBookPayoutState extends State<AddressBookPayout> {
|
|||||||
: ShortListAddressBookPayout(
|
: ShortListAddressBookPayout(
|
||||||
recipients: recipients,
|
recipients: recipients,
|
||||||
onSelected: widget.onSelected,
|
onSelected: widget.onSelected,
|
||||||
trailing: AddRecipientTile(
|
leading: AddRecipientTile(
|
||||||
label: loc.addRecipient,
|
label: loc.addRecipient,
|
||||||
onTap: onAddRecipient,
|
onTap: onAddRecipient,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,19 +5,21 @@ import 'package:pshared/models/recipient/recipient.dart';
|
|||||||
|
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
import 'package:pweb/controllers/payments/page_ui.dart';
|
import 'package:pweb/controllers/payments/page_ui.dart';
|
||||||
import 'package:pweb/pages/payout_page/send/page_handlers.dart';
|
import 'package:pweb/utils/payment/page_handlers.dart';
|
||||||
import 'package:pweb/pages/payout_page/send/page_view.dart';
|
import 'package:pweb/pages/payout_page/send/page_view.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentPage extends StatefulWidget {
|
class PaymentPage extends StatefulWidget {
|
||||||
final ValueChanged<Recipient?>? onBack;
|
final ValueChanged<Recipient?>? onBack;
|
||||||
final PaymentType? initialPaymentType;
|
final PaymentType? initialPaymentType;
|
||||||
|
final String? initialDestinationLedgerAccountRef;
|
||||||
final PayoutDestination fallbackDestination;
|
final PayoutDestination fallbackDestination;
|
||||||
|
|
||||||
const PaymentPage({
|
const PaymentPage({
|
||||||
super.key,
|
super.key,
|
||||||
this.onBack,
|
this.onBack,
|
||||||
this.initialPaymentType,
|
this.initialPaymentType,
|
||||||
|
this.initialDestinationLedgerAccountRef,
|
||||||
this.fallbackDestination = PayoutDestination.dashboard,
|
this.fallbackDestination = PayoutDestination.dashboard,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,7 +36,11 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
_uiController = PaymentPageUiController();
|
_uiController = PaymentPageUiController();
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
(_) => initializePaymentPage(context, widget.initialPaymentType),
|
(_) => initializePaymentPage(
|
||||||
|
context,
|
||||||
|
widget.initialPaymentType,
|
||||||
|
destinationLedgerAccountRef: widget.initialDestinationLedgerAccountRef,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.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/quotation/quotation.dart';
|
import 'package:pshared/provider/payment/quotation/quotation.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';
|
||||||
@@ -14,6 +15,7 @@ import 'package:pweb/controllers/payments/page_ui.dart';
|
|||||||
import 'package:pweb/controllers/payouts/payout_verification.dart';
|
import 'package:pweb/controllers/payouts/payout_verification.dart';
|
||||||
import 'package:pweb/models/state/control_state.dart';
|
import 'package:pweb/models/state/control_state.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentPageView extends StatelessWidget {
|
class PaymentPageView extends StatelessWidget {
|
||||||
final PaymentPageUiController uiController;
|
final PaymentPageUiController uiController;
|
||||||
final ValueChanged<Recipient?>? onBack;
|
final ValueChanged<Recipient?>? onBack;
|
||||||
@@ -47,6 +49,7 @@ class PaymentPageView extends StatelessWidget {
|
|||||||
final uiController = context.watch<PaymentPageUiController>();
|
final uiController = context.watch<PaymentPageUiController>();
|
||||||
final methodsProvider = context.watch<PaymentMethodsProvider>();
|
final methodsProvider = context.watch<PaymentMethodsProvider>();
|
||||||
final recipientProvider = context.watch<RecipientsProvider>();
|
final recipientProvider = context.watch<RecipientsProvider>();
|
||||||
|
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||||
final quotationProvider = context.watch<QuotationProvider>();
|
final quotationProvider = context.watch<QuotationProvider>();
|
||||||
final verificationController = context
|
final verificationController = context
|
||||||
.watch<PayoutVerificationController>();
|
.watch<PayoutVerificationController>();
|
||||||
@@ -58,10 +61,12 @@ class PaymentPageView extends StatelessWidget {
|
|||||||
recipients: recipientProvider.recipients,
|
recipients: recipientProvider.recipients,
|
||||||
query: uiController.query,
|
query: uiController.query,
|
||||||
);
|
);
|
||||||
|
final hasDestinationSelection =
|
||||||
|
flowProvider.selectedPaymentData != null;
|
||||||
final sendState =
|
final sendState =
|
||||||
verificationController.isCooldownActiveFor(verificationContextKey)
|
verificationController.isCooldownActiveFor(verificationContextKey)
|
||||||
? ControlState.disabled
|
? ControlState.disabled
|
||||||
: (recipient == null
|
: (!hasDestinationSelection
|
||||||
? ControlState.disabled
|
? ControlState.disabled
|
||||||
: ControlState.enabled);
|
: ControlState.enabled);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
|
import 'package:pshared/models/recipient/payment_method_draft.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel.dart';
|
||||||
|
import 'package:pweb/pages/payout_page/send/widgets/payment_info/header.dart';
|
||||||
|
import 'package:pweb/models/state/control_state.dart';
|
||||||
|
import 'package:pweb/models/state/visibility.dart';
|
||||||
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentInfoManualDetailsSection extends StatelessWidget {
|
||||||
|
final AppDimensions dimensions;
|
||||||
|
final String title;
|
||||||
|
final VisibilityState titleVisibility;
|
||||||
|
final PaymentMethodData data;
|
||||||
|
|
||||||
|
const PaymentInfoManualDetailsSection({
|
||||||
|
super.key,
|
||||||
|
required this.dimensions,
|
||||||
|
required this.title,
|
||||||
|
required this.titleVisibility,
|
||||||
|
required this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entry = RecipientMethodDraft(type: data.type, data: data);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
PaymentInfoHeader(
|
||||||
|
dimensions: dimensions,
|
||||||
|
title: title,
|
||||||
|
visibility: titleVisibility,
|
||||||
|
),
|
||||||
|
PaymentMethodPanel(
|
||||||
|
selectedType: data.type,
|
||||||
|
selectedIndex: 0,
|
||||||
|
entries: [entry],
|
||||||
|
onRemove: (_) {},
|
||||||
|
onChanged: (_, ignored) {},
|
||||||
|
editState: ControlState.disabled,
|
||||||
|
deleteVisibility: VisibilityState.hidden,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import 'package:pshared/provider/payment/flow.dart';
|
|||||||
|
|
||||||
import 'package:pweb/pages/payout_page/send/widgets/payment_info/methods_section.dart';
|
import 'package:pweb/pages/payout_page/send/widgets/payment_info/methods_section.dart';
|
||||||
import 'package:pweb/pages/payout_page/send/widgets/payment_info/methods_state.dart';
|
import 'package:pweb/pages/payout_page/send/widgets/payment_info/methods_state.dart';
|
||||||
|
import 'package:pweb/pages/payout_page/send/widgets/payment_info/manual_details.dart';
|
||||||
import 'package:pweb/pages/payout_page/send/widgets/payment_info/no_methods.dart';
|
import 'package:pweb/pages/payout_page/send/widgets/payment_info/no_methods.dart';
|
||||||
import 'package:pweb/pages/payout_page/send/widgets/payment_info/no_recipient.dart';
|
import 'package:pweb/pages/payout_page/send/widgets/payment_info/no_recipient.dart';
|
||||||
import 'package:pweb/models/state/visibility.dart';
|
import 'package:pweb/models/state/visibility.dart';
|
||||||
@@ -35,8 +36,9 @@ class PaymentInfoSection extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
final flowProvider = context.watch<PaymentFlowProvider>();
|
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||||
|
final manualData = flowProvider.manualPaymentData;
|
||||||
|
|
||||||
if (!flowProvider.hasRecipient) {
|
if (!flowProvider.hasRecipient && manualData == null) {
|
||||||
return PaymentInfoNoRecipientSection(
|
return PaymentInfoNoRecipientSection(
|
||||||
dimensions: dimensions,
|
dimensions: dimensions,
|
||||||
title: loc.paymentInfo,
|
title: loc.paymentInfo,
|
||||||
@@ -44,6 +46,15 @@ class PaymentInfoSection extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!flowProvider.hasRecipient && manualData != null) {
|
||||||
|
return PaymentInfoManualDetailsSection(
|
||||||
|
dimensions: dimensions,
|
||||||
|
title: loc.paymentInfo,
|
||||||
|
titleVisibility: titleVisibility,
|
||||||
|
data: manualData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final methods = flowProvider.methodsForRecipient;
|
final methods = flowProvider.methodsForRecipient;
|
||||||
final types = visiblePaymentTypes;
|
final types = visiblePaymentTypes;
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class RecipientSection extends StatelessWidget {
|
|||||||
ShortListAddressBookPayout(
|
ShortListAddressBookPayout(
|
||||||
recipients: recipientProvider.recipients,
|
recipients: recipientProvider.recipients,
|
||||||
onSelected: onRecipientSelected,
|
onSelected: onRecipientSelected,
|
||||||
trailing: AddRecipientTile(
|
leading: AddRecipientTile(
|
||||||
label: loc.addRecipient,
|
label: loc.addRecipient,
|
||||||
onTap: onAddRecipient,
|
onTap: onAddRecipient,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.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/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/payout_page/send/widgets/payment_info/section.dart';
|
import 'package:pweb/pages/payout_page/send/widgets/payment_info/section.dart';
|
||||||
@@ -46,10 +49,15 @@ class PaymentRecipientDetailsCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||||
|
final isRecipientSelectionLocked =
|
||||||
|
!flowProvider.hasRecipient && flowProvider.manualPaymentData != null;
|
||||||
|
|
||||||
return PaymentSectionCard(
|
return PaymentSectionCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
if (!isRecipientSelectionLocked) ...[
|
||||||
RecipientSection(
|
RecipientSection(
|
||||||
recipient: recipient,
|
recipient: recipient,
|
||||||
dimensions: dimensions,
|
dimensions: dimensions,
|
||||||
@@ -64,6 +72,7 @@ class PaymentRecipientDetailsCard extends StatelessWidget {
|
|||||||
onAddRecipient: onAddRecipient,
|
onAddRecipient: onAddRecipient,
|
||||||
),
|
),
|
||||||
SizedBox(height: dimensions.paddingMedium),
|
SizedBox(height: dimensions.paddingMedium),
|
||||||
|
],
|
||||||
PaymentInfoSection(
|
PaymentInfoSection(
|
||||||
dimensions: dimensions,
|
dimensions: dimensions,
|
||||||
titleVisibility: VisibilityState.hidden,
|
titleVisibility: VisibilityState.hidden,
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/payment/source.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
import 'package:pshared/models/payment/source_type.dart';
|
import 'package:pshared/models/payment/source_type.dart';
|
||||||
|
import 'package:pshared/models/payment/type.dart';
|
||||||
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/payout_routes.dart';
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
|
|
||||||
@@ -17,11 +21,35 @@ class TopUpButton extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
final source = context.watch<PaymentSourceController>();
|
final source = context.watch<PaymentSourceController>();
|
||||||
final canTopUp = source.selectedType == PaymentSourceType.wallet;
|
final selectedType = source.selectedType;
|
||||||
|
final selectedLedger = source.selectedLedgerAccount;
|
||||||
|
final canTopUp =
|
||||||
|
selectedType == PaymentSourceType.wallet ||
|
||||||
|
(selectedType == PaymentSourceType.ledger && selectedLedger != null);
|
||||||
|
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(shadowColor: null, elevation: 0),
|
style: ElevatedButton.styleFrom(shadowColor: null, elevation: 0),
|
||||||
onPressed: canTopUp ? () => context.pushToWalletTopUp() : null,
|
onPressed: !canTopUp
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
if (selectedType == PaymentSourceType.wallet) {
|
||||||
|
context.pushToWalletTopUp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedType == PaymentSourceType.ledger &&
|
||||||
|
selectedLedger != null) {
|
||||||
|
context.read<RecipientsProvider>().setCurrentObject(null);
|
||||||
|
context.pushNamed(
|
||||||
|
PayoutRoutes.payment,
|
||||||
|
queryParameters: PayoutRoutes.buildQueryParameters(
|
||||||
|
paymentType: PaymentType.ledger,
|
||||||
|
destinationLedgerAccountRef:
|
||||||
|
selectedLedger.ledgerAccountRef,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Text(loc.topUpBalance),
|
child: Text(loc.topUpBalance),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ import 'package:pshared/utils/payment/quote_helpers.dart';
|
|||||||
|
|
||||||
import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
|
import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
|
||||||
import 'package:pweb/models/payment/multiple_payouts/state.dart';
|
import 'package:pweb/models/payment/multiple_payouts/state.dart';
|
||||||
import 'package:pweb/utils/payment/multiple_csv_parser.dart';
|
import 'package:pweb/utils/payment/multiple/csv_parser.dart';
|
||||||
import 'package:pweb/utils/payment/multiple_intent_builder.dart';
|
import 'package:pweb/utils/payment/multiple/intent_builder.dart';
|
||||||
|
|
||||||
|
|
||||||
class MultiplePayoutsProvider extends ChangeNotifier {
|
class MultiplePayoutsProvider extends ChangeNotifier {
|
||||||
final MultipleCsvParser _csvParser;
|
final MultipleCsvParser _csvParser;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/methods/ledger.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';
|
||||||
@@ -17,14 +18,30 @@ import 'package:pweb/widgets/dialogs/payment_status_dialog.dart';
|
|||||||
import 'package:pweb/controllers/payments/page.dart';
|
import 'package:pweb/controllers/payments/page.dart';
|
||||||
import 'package:pweb/controllers/payments/page_ui.dart';
|
import 'package:pweb/controllers/payments/page_ui.dart';
|
||||||
import 'package:pweb/controllers/payouts/payout_verification.dart';
|
import 'package:pweb/controllers/payouts/payout_verification.dart';
|
||||||
import 'package:pweb/utils/payment/payout_verification_flow.dart';
|
import 'package:pweb/utils/payment/verification_flow.dart';
|
||||||
|
|
||||||
|
|
||||||
void initializePaymentPage(
|
void initializePaymentPage(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
PaymentType? initialPaymentType,
|
PaymentType? initialPaymentType, {
|
||||||
) {
|
String? destinationLedgerAccountRef,
|
||||||
|
}) {
|
||||||
final flowProvider = context.read<PaymentFlowProvider>();
|
final flowProvider = context.read<PaymentFlowProvider>();
|
||||||
|
final recipientsProvider = context.read<RecipientsProvider>();
|
||||||
|
|
||||||
flowProvider.setPreferredType(initialPaymentType);
|
flowProvider.setPreferredType(initialPaymentType);
|
||||||
|
|
||||||
|
final destinationRef = destinationLedgerAccountRef?.trim();
|
||||||
|
if (destinationRef != null && destinationRef.isNotEmpty) {
|
||||||
|
recipientsProvider.setCurrentObject(null);
|
||||||
|
flowProvider.setPreferredType(PaymentType.ledger);
|
||||||
|
flowProvider.setManualPaymentData(
|
||||||
|
LedgerPaymentMethod(ledgerAccountRef: destinationRef),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
flowProvider.setManualPaymentData(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleSearchChanged(PaymentPageUiController uiController, String query) {
|
void handleSearchChanged(PaymentPageUiController uiController, String query) {
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
|
import 'package:pshared/models/payment/operation_document.dart';
|
||||||
import 'package:pshared/models/payment/operation.dart';
|
import 'package:pshared/models/payment/operation.dart';
|
||||||
import 'package:pshared/models/payment/payment.dart';
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
import 'package:pshared/models/payment/state.dart';
|
import 'package:pshared/models/payment/state.dart';
|
||||||
import 'package:pshared/models/payment/status.dart';
|
import 'package:pshared/models/payment/status.dart';
|
||||||
import 'package:pshared/utils/money.dart';
|
import 'package:pshared/utils/money.dart';
|
||||||
|
|
||||||
import 'package:pweb/models/report/operation/document.dart';
|
|
||||||
import 'package:pweb/utils/report/operations/document_rule.dart';
|
import 'package:pweb/utils/report/operations/document_rule.dart';
|
||||||
|
|
||||||
|
|
||||||
OperationItem mapPaymentToOperation(Payment payment) {
|
OperationItem mapPaymentToOperation(Payment payment) {
|
||||||
final debit = payment.lastQuote?.amounts?.sourceDebitTotal;
|
final debit = payment.lastQuote?.amounts?.sourceDebitTotal;
|
||||||
final settlement = payment.lastQuote?.amounts?.destinationSettlement;
|
final settlement = payment.lastQuote?.amounts?.destinationSettlement;
|
||||||
@@ -55,7 +56,7 @@ OperationItem mapPaymentToOperation(Payment payment) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OperationDocumentInfo? _resolveOperationDocument(Payment payment) {
|
OperationDocumentRef? _resolveOperationDocument(Payment payment) {
|
||||||
for (final operation in payment.operations) {
|
for (final operation in payment.operations) {
|
||||||
final operationRef = operation.operationRef;
|
final operationRef = operation.operationRef;
|
||||||
final gatewayService = operation.gateway;
|
final gatewayService = operation.gateway;
|
||||||
@@ -64,7 +65,7 @@ OperationDocumentInfo? _resolveOperationDocument(Payment payment) {
|
|||||||
|
|
||||||
if (!isOperationDocumentEligible(operation.code)) continue;
|
if (!isOperationDocumentEligible(operation.code)) continue;
|
||||||
|
|
||||||
return OperationDocumentInfo(
|
return OperationDocumentRef(
|
||||||
operationRef: operationRef,
|
operationRef: operationRef,
|
||||||
gatewayService: gatewayService,
|
gatewayService: gatewayService,
|
||||||
);
|
);
|
||||||
|
|||||||
112
frontend/pweb/lib/utils/report/source_filter.dart
Normal file
112
frontend/pweb/lib/utils/report/source_filter.dart
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import 'package:pshared/models/payment/intent.dart';
|
||||||
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
|
import 'package:pshared/models/payment/methods/ledger.dart';
|
||||||
|
import 'package:pshared/models/payment/methods/managed_wallet.dart';
|
||||||
|
import 'package:pshared/models/payment/methods/wallet.dart';
|
||||||
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
|
import 'package:pshared/models/payment/source_type.dart';
|
||||||
|
|
||||||
|
|
||||||
|
bool paymentMatchesSource(
|
||||||
|
Payment payment, {
|
||||||
|
required PaymentSourceType sourceType,
|
||||||
|
required String sourceRef,
|
||||||
|
}) {
|
||||||
|
final normalizedSourceRef = _normalize(sourceRef);
|
||||||
|
if (normalizedSourceRef == null) return false;
|
||||||
|
|
||||||
|
final paymentSourceRef = _paymentSourceRef(payment, sourceType);
|
||||||
|
return paymentSourceRef != null && paymentSourceRef == normalizedSourceRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _paymentSourceRef(Payment payment, PaymentSourceType sourceType) {
|
||||||
|
final fromIntent = _sourceRefFromIntent(payment.intent, sourceType);
|
||||||
|
if (fromIntent != null) return fromIntent;
|
||||||
|
return _sourceRefFromMetadata(payment.metadata, sourceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _sourceRefFromIntent(
|
||||||
|
PaymentIntent? intent,
|
||||||
|
PaymentSourceType sourceType,
|
||||||
|
) {
|
||||||
|
final source = intent?.source;
|
||||||
|
if (source == null) return null;
|
||||||
|
|
||||||
|
final fromIntentAttributes = _sourceRefFromMetadata(
|
||||||
|
intent?.attributes,
|
||||||
|
sourceType,
|
||||||
|
);
|
||||||
|
if (fromIntentAttributes != null) return fromIntentAttributes;
|
||||||
|
|
||||||
|
switch (sourceType) {
|
||||||
|
case PaymentSourceType.wallet:
|
||||||
|
return _walletSourceRef(source);
|
||||||
|
case PaymentSourceType.ledger:
|
||||||
|
return _ledgerSourceRef(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _walletSourceRef(PaymentMethodData source) {
|
||||||
|
if (source is ManagedWalletPaymentMethod) {
|
||||||
|
return _normalize(source.managedWalletRef) ??
|
||||||
|
_sourceRefFromMetadata(source.metadata, PaymentSourceType.wallet);
|
||||||
|
}
|
||||||
|
if (source is WalletPaymentMethod) {
|
||||||
|
return _normalize(source.walletId) ??
|
||||||
|
_sourceRefFromMetadata(source.metadata, PaymentSourceType.wallet);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _ledgerSourceRef(PaymentMethodData source) {
|
||||||
|
if (source is LedgerPaymentMethod) {
|
||||||
|
return _normalize(source.ledgerAccountRef) ??
|
||||||
|
_sourceRefFromMetadata(source.metadata, PaymentSourceType.ledger);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _sourceRefFromMetadata(
|
||||||
|
Map<String, String>? metadata,
|
||||||
|
PaymentSourceType sourceType,
|
||||||
|
) {
|
||||||
|
if (metadata == null || metadata.isEmpty) return null;
|
||||||
|
|
||||||
|
final keys = switch (sourceType) {
|
||||||
|
PaymentSourceType.wallet => const <String>[
|
||||||
|
'source_wallet_ref',
|
||||||
|
'managed_wallet_ref',
|
||||||
|
'wallet_ref',
|
||||||
|
'wallet_id',
|
||||||
|
'source_wallet_id',
|
||||||
|
'source_wallet_user_id',
|
||||||
|
'wallet_user_id',
|
||||||
|
'wallet_user_ref',
|
||||||
|
'wallet_number',
|
||||||
|
'source_wallet_number',
|
||||||
|
'source_managed_wallet_ref',
|
||||||
|
'source_ref',
|
||||||
|
],
|
||||||
|
PaymentSourceType.ledger => const <String>[
|
||||||
|
'source_ledger_account_ref',
|
||||||
|
'ledger_account_ref',
|
||||||
|
'source_account_code',
|
||||||
|
'ledger_account_code',
|
||||||
|
'account_code',
|
||||||
|
'source_ref',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (final key in keys) {
|
||||||
|
final value = _normalize(metadata[key]);
|
||||||
|
if (value != null) return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _normalize(String? value) {
|
||||||
|
final normalized = value?.trim();
|
||||||
|
if (normalized == null || normalized.isEmpty) return null;
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user