summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-02-13 12:09:28 -0300
committerSebastian <sebasjm@gmail.com>2024-02-13 12:09:47 -0300
commit1d86bb8e9c74f09fd7dbeb2f806d857c8b8b5ea8 (patch)
treee24a4b43dfb9ea0ba3ca1575089904ccef05f199
parent9efe5429c2e6dbf97122f2ae6fc4ae2fdd64da7b (diff)
downloadwallet-core-1d86bb8e9c74f09fd7dbeb2f806d857c8b8b5ea8.tar.gz
wallet-core-1d86bb8e9c74f09fd7dbeb2f806d857c8b8b5ea8.tar.bz2
wallet-core-1d86bb8e9c74f09fd7dbeb2f806d857c8b8b5ea8.zip
fixes #8228
-rw-r--r--packages/taler-util/src/transactions-types.ts13
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts86
-rw-r--r--packages/taler-wallet-webextension/src/wallet/History.stories.tsx16
-rw-r--r--packages/taler-wallet-webextension/src/wallet/History.tsx128
4 files changed, 153 insertions, 90 deletions
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,19 +48,30 @@ 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
*/
search?: string;
@@ -77,6 +88,7 @@ export interface TransactionsRequest {
*/
includeRefreshes?: boolean;
+
filterByState?: TransactionStateFilter;
}
@@ -730,6 +742,7 @@ export const codecForWithdrawalTransactionByURIRequest =
export const codecForTransactionsRequest = (): Codec<TransactionsRequest> =>
buildCodecForObject<TransactionsRequest>()
.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<number>(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 <Loading />;
@@ -84,10 +92,20 @@ export function HistoryPage({
);
}
+ if (!state.response.b.balances.length) {
+ return (
+ <NoBalanceHelp
+ goToWalletManualWithdraw={{
+ onClick: pushAlertOnError(goToWalletManualWithdraw),
+ }}
+ />
+ );
+ }
return (
<HistoryView
+ balanceIndex={balanceIndex}
+ changeBalanceIndex={b => 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<void>;
goToWalletManualWithdraw: (currency?: string) => Promise<void>;
- 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<string, Transaction[]>);
+ // 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 (
- <NoBalanceHelp
- goToWalletManualWithdraw={{
- onClick: pushAlertOnError(goToWalletManualWithdraw),
- }}
- />
- );
- }
return (
<Fragment>
<section>
@@ -194,9 +186,9 @@ export function HistoryView({
display: "flex",
}}
>
- {currencies.length === 1 ? (
+ {balances.length === 1 ? (
<CenteredText style={{ fontSize: "x-large", margin: 8 }}>
- {selectedCurrency}
+ {balance.scopeInfo.currency}
</CenteredText>
) : (
<NiceSelect style={{ flexDirection: "column" }}>
@@ -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 (
<option value={index} key={entry.scopeInfo.currency}>
{entry.scopeInfo.currency}
@@ -218,11 +210,11 @@ export function HistoryView({
})}
</select>
<div style={{ fontSize: "small", color: "grey" }}>
- {selectedCurrency.scopeInfo.type === ScopeType.Exchange || selectedCurrency.scopeInfo.type === ScopeType.Auditor ? selectedCurrency.scopeInfo.url : undefined}
+ {balance.scopeInfo.type === ScopeType.Exchange || balance.scopeInfo.type === ScopeType.Auditor ? balance.scopeInfo.url : undefined}
</div>
</NiceSelect>
)}
- {currencyAmount && (
+ {available && (
<CenteredBoldText
style={{
display: "inline-block",
@@ -230,7 +222,7 @@ export function HistoryView({
margin: 8,
}}
>
- {Amounts.stringifyValue(currencyAmount, 2)}
+ {Amounts.stringifyValue(available, 2)}
</CenteredBoldText>
)}
</div>
@@ -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)}
>
<i18n.Translate>Add</i18n.Translate>
</Button>
- {currencyAmount && Amounts.isNonZero(currencyAmount) && (
+ {available && Amounts.isNonZero(available) && (
<Button
tooltip="Transfer money from the wallet"
startIcon={UploadIcon}
variant="outlined"
color="primary"
- onClick={() => goToWalletDeposit(selectedCurrency.scopeInfo.currency)}
+ onClick={() => goToWalletDeposit(balance.scopeInfo.currency)}
>
<i18n.Translate>Send</i18n.Translate>
</Button>