From 1d86bb8e9c74f09fd7dbeb2f806d857c8b8b5ea8 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 13 Feb 2024 12:09:28 -0300 Subject: fixes #8228 --- packages/taler-util/src/transactions-types.ts | 13 +++ .../src/operations/transactions.ts | 86 +++++++++++--- .../src/wallet/History.stories.tsx | 16 ++- .../src/wallet/History.tsx | 128 ++++++++++----------- 4 files changed, 153 insertions(+), 90 deletions(-) (limited to 'packages') diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts index a0bc2a89d..3460d2d87 100644 --- a/packages/taler-util/src/transactions-types.ts +++ b/packages/taler-util/src/transactions-types.ts @@ -48,18 +48,29 @@ import { } from "./codec.js"; import { RefreshReason, + ScopeInfo, TalerErrorDetail, TransactionIdStr, TransactionStateFilter, WithdrawalExchangeAccountDetails, + codecForScopeInfo, } from "./wallet-types.js"; export interface TransactionsRequest { /** * return only transactions in the given currency + * + * it will be removed in next release + * + * @deprecated use scopeInfo */ currency?: string; + /** + * return only transactions in the given scopeInfo + */ + scopeInfo?: ScopeInfo; + /** * if present, results will be limited to transactions related to the given search string */ @@ -77,6 +88,7 @@ export interface TransactionsRequest { */ includeRefreshes?: boolean; + filterByState?: TransactionStateFilter; } @@ -730,6 +742,7 @@ export const codecForWithdrawalTransactionByURIRequest = export const codecForTransactionsRequest = (): Codec => buildCodecForObject() .property("currency", codecOptional(codecForString())) + .property("scopeInfo", codecOptional(codecForScopeInfo())) .property("search", codecOptional(codecForString())) .property( "sort", diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 8fd7afae6..13eda7a92 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -28,6 +28,7 @@ import { PeerContractTerms, RefundInfoShort, RefundPaymentInfo, + ScopeType, stringifyPayPullUri, stringifyPayPushUri, TalerErrorCode, @@ -155,11 +156,30 @@ const logger = new Logger("taler-wallet-core:transactions.ts"); function shouldSkipCurrency( transactionsRequest: TransactionsRequest | undefined, currency: string, + exchangesInTransaction: string[], ): boolean { - if (!transactionsRequest?.currency) { - return false; + if (transactionsRequest?.scopeInfo) { + const sameCurrency = transactionsRequest.scopeInfo.currency.toLowerCase() === currency.toLowerCase() + switch (transactionsRequest.scopeInfo.type) { + case ScopeType.Global: { + return !sameCurrency + } + case ScopeType.Exchange: { + const exchangeInvolveInTransaction = exchangesInTransaction.indexOf(transactionsRequest.scopeInfo.url) !== -1 + return !sameCurrency || !exchangeInvolveInTransaction + } + case ScopeType.Auditor: { + // same currency and same auditor + throw Error("filering balance in auditor scope is not implemented") + } + default: assertUnreachable(transactionsRequest.scopeInfo) + } } - return transactionsRequest.currency.toLowerCase() !== currency.toLowerCase(); + // FIXME: remove next release + if (transactionsRequest?.currency) { + return transactionsRequest.currency.toLowerCase() !== currency.toLowerCase(); + } + return false; } function shouldSkipSearch( @@ -539,7 +559,7 @@ function buildTransactionForPeerPullCredit( const silentWithdrawalErrorForInvoice = wsrOrt?.lastError && wsrOrt.lastError.code === - TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE && + TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE && Object.values(wsrOrt.lastError.errorsPerCoin ?? {}).every((e) => { return ( e.code === TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR && @@ -569,10 +589,10 @@ function buildTransactionForPeerPullCredit( kycUrl: pullCredit.kycUrl, ...(wsrOrt?.lastError ? { - error: silentWithdrawalErrorForInvoice - ? undefined - : wsrOrt.lastError, - } + error: silentWithdrawalErrorForInvoice + ? undefined + : wsrOrt.lastError, + } : {}), }; } @@ -1052,8 +1072,8 @@ export async function getTransactions( .runReadOnly(async (tx) => { await iterRecordsForPeerPushDebit(tx, filter, async (pi) => { const amount = Amounts.parseOrThrow(pi.amount); - - if (shouldSkipCurrency(transactionsRequest, amount.currency)) { + const exchangesInTx = [pi.exchangeBaseUrl] + if (shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)) { return; } if (shouldSkipSearch(transactionsRequest, [])) { @@ -1068,7 +1088,8 @@ export async function getTransactions( await iterRecordsForPeerPullDebit(tx, filter, async (pi) => { const amount = Amounts.parseOrThrow(pi.amount); - if (shouldSkipCurrency(transactionsRequest, amount.currency)) { + const exchangesInTx = [pi.exchangeBaseUrl] + if (shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)) { return; } if (shouldSkipSearch(transactionsRequest, [])) { @@ -1102,7 +1123,8 @@ export async function getTransactions( // Legacy transaction return; } - if (shouldSkipCurrency(transactionsRequest, pi.currency)) { + const exchangesInTx = [pi.exchangeBaseUrl] + if (shouldSkipCurrency(transactionsRequest, pi.currency, exchangesInTx)) { return; } if (shouldSkipSearch(transactionsRequest, [])) { @@ -1140,7 +1162,8 @@ export async function getTransactions( await iterRecordsForPeerPullCredit(tx, filter, async (pi) => { const currency = Amounts.currencyOf(pi.amount); - if (shouldSkipCurrency(transactionsRequest, currency)) { + const exchangesInTx = [pi.exchangeBaseUrl] + if (shouldSkipCurrency(transactionsRequest, currency, exchangesInTx)) { return; } if (shouldSkipSearch(transactionsRequest, [])) { @@ -1173,7 +1196,19 @@ export async function getTransactions( await iterRecordsForRefund(tx, filter, async (refundGroup) => { const currency = Amounts.currencyOf(refundGroup.amountRaw); - if (shouldSkipCurrency(transactionsRequest, currency)) { + + const exchangesInTx: string[] = [] + const p = await tx.purchases.get(refundGroup.proposalId) + if (!p || !p.payInfo) return; //refund with no payment + + p.payInfo.payCoinSelection.coinPubs.forEach(async (cp) => { + const c = await tx.coins.get(cp) + if (c?.exchangeBaseUrl) { + exchangesInTx.push(c.exchangeBaseUrl) + } + }) + + if (shouldSkipCurrency(transactionsRequest, currency, exchangesInTx)) { return; } const contractData = await lookupMaybeContractData( @@ -1184,7 +1219,8 @@ export async function getTransactions( }); await iterRecordsForRefresh(tx, filter, async (rg) => { - if (shouldSkipCurrency(transactionsRequest, rg.currency)) { + const exchangesInTx = rg.infoPerExchange ? Object.keys(rg.infoPerExchange) : [] + if (shouldSkipCurrency(transactionsRequest, rg.currency, exchangesInTx)) { return; } let required = false; @@ -1204,10 +1240,12 @@ export async function getTransactions( }); await iterRecordsForWithdrawal(tx, filter, async (wsr) => { + const exchangesInTx = [wsr.exchangeBaseUrl] if ( shouldSkipCurrency( transactionsRequest, Amounts.currencyOf(wsr.rawWithdrawalAmount), + exchangesInTx, ) ) { return; @@ -1259,7 +1297,8 @@ export async function getTransactions( await iterRecordsForDeposit(tx, filter, async (dg) => { const amount = Amounts.parseOrThrow(dg.amount); - if (shouldSkipCurrency(transactionsRequest, amount.currency)) { + const exchangesInTx = dg.infoPerExchange ? Object.keys(dg.infoPerExchange) : [] + if (shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)) { return; } const opId = TaskIdentifiers.forDeposit(dg); @@ -1276,7 +1315,16 @@ export async function getTransactions( if (!purchase.payInfo) { return; } - if (shouldSkipCurrency(transactionsRequest, download.currency)) { + + const exchangesInTx: string[] = [] + purchase.payInfo.payCoinSelection.coinPubs.forEach(async (cp) => { + const c = await tx.coins.get(cp) + if (c?.exchangeBaseUrl) { + exchangesInTx.push(c.exchangeBaseUrl) + } + }) + + if (shouldSkipCurrency(transactionsRequest, download.currency, exchangesInTx)) { return; } const contractTermsRecord = await tx.contractTerms.get( @@ -1316,11 +1364,13 @@ export async function getTransactions( ); }); + //FIXME: remove rewards await iterRecordsForReward(tx, filter, async (tipRecord) => { if ( shouldSkipCurrency( transactionsRequest, Amounts.parseOrThrow(tipRecord.rewardAmountRaw).currency, + [tipRecord.exchangeBaseUrl], ) ) { return; @@ -1332,6 +1382,8 @@ export async function getTransactions( const retryRecord = await tx.operationRetries.get(opId); transactions.push(buildTransactionForTip(tipRecord, retryRecord)); }); + //ends REMOVE REWARDS + }); // One-off checks, because of a bug where the wallet previously diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx index cc87cb992..c28e4188f 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx @@ -162,11 +162,6 @@ const exampleData = { } as TransactionPeerPullDebit, }; -export const NoBalance = tests.createExample(TestedComponent, { - transactions: [], - balances: [], -}); - export const SomeBalanceWithNoTransactions = tests.createExample( TestedComponent, { @@ -186,6 +181,7 @@ export const SomeBalanceWithNoTransactions = tests.createExample( }, }, ], + balanceIndex: 0, }, ); @@ -206,6 +202,8 @@ export const OneSimpleTransaction = tests.createExample(TestedComponent, { }, }, ], + balanceIndex: 0, + }); export const TwoTransactionsAndZeroBalance = tests.createExample( @@ -227,6 +225,7 @@ export const TwoTransactionsAndZeroBalance = tests.createExample( }, }, ], + balanceIndex: 0, }, ); @@ -254,6 +253,7 @@ export const OneTransactionPending = tests.createExample(TestedComponent, { }, }, ], + balanceIndex: 0, }); export const SomeTransactions = tests.createExample(TestedComponent, { @@ -288,6 +288,7 @@ export const SomeTransactions = tests.createExample(TestedComponent, { }, }, ], + balanceIndex: 0, }); export const SomeTransactionsInDifferentStates = tests.createExample( @@ -381,6 +382,7 @@ export const SomeTransactionsInDifferentStates = tests.createExample( }, }, ], + balanceIndex: 0, }, ); @@ -424,6 +426,7 @@ export const SomeTransactionsWithTwoCurrencies = tests.createExample( }, }, ], + balanceIndex: 0, }, ); @@ -496,6 +499,7 @@ export const FiveOfficialCurrencies = tests.createExample(TestedComponent, { }, }, ], + balanceIndex: 0, }); export const FiveOfficialCurrenciesWithHighValue = tests.createExample( @@ -569,6 +573,7 @@ export const FiveOfficialCurrenciesWithHighValue = tests.createExample( }, }, ], + balanceIndex: 0, }, ); @@ -594,4 +599,5 @@ export const PeerToPeer = tests.createExample(TestedComponent, { }, }, ], + balanceIndex: 0, }); diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx index 41b03424c..1cf7021d5 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -18,6 +18,7 @@ import { AbsoluteTime, Amounts, NotificationType, + ScopeInfo, ScopeType, Transaction, WalletBalance, @@ -57,10 +58,16 @@ export function HistoryPage({ }: Props): VNode { const { i18n } = useTranslationContext(); const api = useBackendContext(); - const state = useAsyncAsHook(async () => ({ - b: await api.wallet.call(WalletApiOperation.GetBalances, {}), - tx: await api.wallet.call(WalletApiOperation.GetTransactions, {}), - })); + const [balanceIndex, setBalanceIndex] = useState(0); + + const state = useAsyncAsHook(async () => { + const b = await api.wallet.call(WalletApiOperation.GetBalances, {}) + const balance = b.balances.length > 0 ? b.balances[balanceIndex] : undefined + const tx = await api.wallet.call(WalletApiOperation.GetTransactions, { + scopeInfo: balance?.scopeInfo + }) + return { b, tx } + }, [balanceIndex]); useEffect(() => { return api.listener.onUpdateNotification( @@ -68,6 +75,7 @@ export function HistoryPage({ state?.retry, ); }); + const { pushAlertOnError } = useAlertContext(); if (!state) { return ; @@ -84,10 +92,20 @@ export function HistoryPage({ ); } + if (!state.response.b.balances.length) { + return ( + + ); + } return ( setBalanceIndex(b)} balances={state.response.b.balances} - defaultCurrency={currency} goToWalletManualWithdraw={goToWalletManualWithdraw} goToWalletDeposit={goToWalletDeposit} transactions={[...state.response.tx.transactions].reverse()} @@ -95,66 +113,49 @@ export function HistoryPage({ ); } -const term = 1000 * 60 * 60 * 24; -function normalizeToDay(x: number): number { - return Math.round(x / term) * term; -} - export function HistoryView({ - defaultCurrency, - transactions, balances, + balanceIndex, + changeBalanceIndex, + transactions, goToWalletManualWithdraw, goToWalletDeposit, }: { + balanceIndex: number, + changeBalanceIndex: (s: number) => void; goToWalletDeposit: (currency: string) => Promise; goToWalletManualWithdraw: (currency?: string) => Promise; - defaultCurrency?: string; transactions: Transaction[]; balances: WalletBalance[]; }): VNode { const { i18n } = useTranslationContext(); - const { pushAlertOnError } = useAlertContext(); - const transactionByCurrency = transactions.reduce((prev, cur) => { - const c = Amounts.parseOrThrow(cur.amountEffective).currency; - if (!prev[c]) { - prev[c] = []; - } - prev[c].push(cur); - return prev; - }, {} as Record); + // const currencies = balances + // .filter((b) => { + // const av = Amounts.parseOrThrow(b.available); + // return ( + // Amounts.isNonZero(av) || + // (transactionByCurrency[av.currency] && + // transactionByCurrency[av.currency].length > 0) + // ); + // }); - const currencies = balances - .filter((b) => { - const av = Amounts.parseOrThrow(b.available); - return ( - Amounts.isNonZero(av) || - (transactionByCurrency[av.currency] && - transactionByCurrency[av.currency].length > 0) - ); - }); + // const defaultCurrencyIndex = balances.findIndex( + // (c) => c.scopeInfo.currency === defaultCurrency, + // ); + // const [currencyIndex, setCurrencyIndex] = useState( + // defaultCurrencyIndex === -1 ? 0 : defaultCurrencyIndex, + // ); + // const selectedCurrency = + // balances.length > 0 ? balances[currencyIndex] : undefined; + const balance = balances[balanceIndex]; - const defaultCurrencyIndex = currencies.findIndex( - (c) => c.scopeInfo.currency === defaultCurrency, - ); - const [currencyIndex, setCurrencyIndex] = useState( - defaultCurrencyIndex === -1 ? 0 : defaultCurrencyIndex, - ); - const selectedCurrency = - currencies.length > 0 ? currencies[currencyIndex] : undefined; - - const currencyAmount = balances[currencyIndex] - ? Amounts.jsonifyAmount(balances[currencyIndex].available) + const available = balance + ? Amounts.jsonifyAmount(balance.available) : undefined; - const ts = - selectedCurrency === undefined - ? [] - : transactionByCurrency[selectedCurrency.scopeInfo.currency] ?? []; - const datesWithTransaction: string[] = []; - const byDate = ts.reduce((rv, x) => { + const byDate = transactions.reduce((rv, x) => { const startDay = x.timestamp.t_s === "never" ? 0 : startOfDay(x.timestamp.t_s * 1000).getTime(); if (startDay) { @@ -168,15 +169,6 @@ export function HistoryView({ return rv; }, {} as { [x: string]: Transaction[] }); - if (balances.length === 0 || !selectedCurrency) { - return ( - - ); - } return (
@@ -194,9 +186,9 @@ export function HistoryView({ display: "flex", }} > - {currencies.length === 1 ? ( + {balances.length === 1 ? ( - {selectedCurrency} + {balance.scopeInfo.currency} ) : ( @@ -204,12 +196,12 @@ export function HistoryView({ style={{ fontSize: "x-large", }} - value={currencyIndex} + value={balanceIndex} onChange={(e) => { - setCurrencyIndex(Number(e.currentTarget.value)); + changeBalanceIndex(Number.parseInt(e.currentTarget.value, 10)); }} > - {currencies.map((entry, index) => { + {balances.map((entry, index) => { return ( )} - {currencyAmount && ( + {available && ( - {Amounts.stringifyValue(currencyAmount, 2)} + {Amounts.stringifyValue(available, 2)} )} @@ -239,17 +231,17 @@ export function HistoryView({ tooltip="Transfer money to the wallet" startIcon={DownloadIcon} variant="contained" - onClick={() => goToWalletManualWithdraw(selectedCurrency.scopeInfo.currency)} + onClick={() => goToWalletManualWithdraw(balance.scopeInfo.currency)} > Add - {currencyAmount && Amounts.isNonZero(currencyAmount) && ( + {available && Amounts.isNonZero(available) && ( -- cgit v1.2.3