taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit ed409e0d56e913c8a999aa5e92afc4e6b15c570e
parent 3a93b7b405acd984e9da7349812c1328b6d1bbf5
Author: Florian Dold <florian@dold.me>
Date:   Thu,  9 Jan 2025 17:52:16 +0100

wallet-core: add better tracking of balance change effects

We now keep track whether a transaction transition (a) doesn't change
the balance, (b) changes the balance, but preserves the user-visible
balance or (c) can also change the user-visible balance.

Diffstat:
Mpackages/taler-wallet-core/src/common.ts | 13+++++++++++--
Mpackages/taler-wallet-core/src/deposits.ts | 23+++++++++++++++--------
Mpackages/taler-wallet-core/src/exchanges.ts | 2++
Mpackages/taler-wallet-core/src/pay-merchant.ts | 51+++++++++++++++++++++++++++++++--------------------
Mpackages/taler-wallet-core/src/pay-peer-pull-credit.ts | 25+++++++++++++++++++------
Mpackages/taler-wallet-core/src/pay-peer-pull-debit.ts | 8++++++--
Mpackages/taler-wallet-core/src/pay-peer-push-credit.ts | 22+++++++++++++++++++---
Mpackages/taler-wallet-core/src/pay-peer-push-debit.ts | 9+++++++++
Mpackages/taler-wallet-core/src/refresh.ts | 14+++++++++++---
Mpackages/taler-wallet-core/src/transactions.ts | 45+++++++++++++++------------------------------
Mpackages/taler-wallet-core/src/withdraw.ts | 6+++++-
11 files changed, 143 insertions(+), 75 deletions(-)

diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts @@ -69,6 +69,7 @@ import { } from "./db.js"; import { ReadyExchangeSummary } from "./exchanges.js"; import { createRefreshGroup } from "./refresh.js"; +import { BalanceEffect } from "./transactions.js"; import { WalletExecutionContext, getDenomInfo } from "./wallet.js"; const logger = new Logger("operations/common.ts"); @@ -774,7 +775,11 @@ export enum TransitionResultType { export type TransitionResult<R> = | { type: TransitionResultType.Stay } - | { type: TransitionResultType.Transition; rec: R } + | { + type: TransitionResultType.Transition; + rec: R; + balanceEffect: BalanceEffect; + } | { type: TransitionResultType.Delete }; export const TransitionResult = { @@ -784,10 +789,14 @@ export const TransitionResult = { delete<T>(): TransitionResult<T> { return { type: TransitionResultType.Delete }; }, - transition<T>(rec: T): TransitionResult<T> { + transition<T>( + rec: T, + balanceEffect: BalanceEffect = BalanceEffect.Any, + ): TransitionResult<T> { return { type: TransitionResultType.Transition, rec, + balanceEffect, }; }, }; diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts @@ -128,6 +128,8 @@ import { getTotalRefreshCost, } from "./refresh.js"; import { + BalanceEffect, + TransitionInfo, constructTransactionIdentifier, isUnsuccessfulTransaction, notifyTransition, @@ -380,7 +382,8 @@ export class DepositTransactionContext implements TransactionContext { return { oldTxState: oldState, newTxState: computeDepositTransactionStatus(dg), - }; + balanceEffect: BalanceEffect.None, + } satisfies TransitionInfo; }, ); wex.taskScheduler.stopShepherdTask(retryTag); @@ -414,6 +417,7 @@ export class DepositTransactionContext implements TransactionContext { return { oldTxState: oldState, newTxState: computeDepositTransactionStatus(dg), + balanceEffect: BalanceEffect.Any, }; } case DepositOperationStatus.PendingTrack: @@ -494,6 +498,7 @@ export class DepositTransactionContext implements TransactionContext { return { oldTxState: oldState, newTxState: computeDepositTransactionStatus(dg), + balanceEffect: BalanceEffect.None, }; }, ); @@ -549,6 +554,7 @@ export class DepositTransactionContext implements TransactionContext { return { oldTxState: oldState, newTxState: computeDepositTransactionStatus(dg), + balanceEffect: BalanceEffect.Any, }; }, ); @@ -904,7 +910,7 @@ async function waitForRefreshOnDepositGroup( const newTxState = computeDepositTransactionStatus(newDg); await tx.depositGroups.put(newDg); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; } return undefined; }, @@ -1000,7 +1006,7 @@ async function processDepositGroupPendingKyc( await tx.depositGroups.put(newDg); await ctx.updateTransactionMeta(tx); const newTxState = computeDepositTransactionStatus(newDg); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -1109,7 +1115,7 @@ async function transitionKycAuthSuccess( await tx.depositGroups.put(newDg); await ctx.updateTransactionMeta(tx); const newTxState = computeDepositTransactionStatus(newDg); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.None }; }, ); notifyTransition(ctx.wex, ctx.transactionId, transitionInfo); @@ -1219,7 +1225,7 @@ async function transitionToKycRequired( await tx.depositGroups.put(dg); await ctx.updateTransactionMeta(tx); const newTxState = computeDepositTransactionStatus(dg); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -1267,7 +1273,7 @@ async function transitionToKycAuthRequired( await tx.depositGroups.put(dg); await ctx.updateTransactionMeta(tx); const newTxState = computeDepositTransactionStatus(dg); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.None }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -1433,7 +1439,7 @@ async function processDepositGroupPendingTrack( await ctx.updateTransactionMeta(tx); } const newTxState = computeDepositTransactionStatus(dg); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -1692,7 +1698,7 @@ async function processDepositGroupPendingDeposit( await tx.depositGroups.put(dg); await ctx.updateTransactionMeta(tx); const newTxState = computeDepositTransactionStatus(dg); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.None }; }, ); @@ -2238,6 +2244,7 @@ export async function createDepositGroup( major: TransactionMajorState.None, }, newTxState, + balanceEffect: BalanceEffect.Any, }); wex.taskScheduler.startShepherdTask(ctx.taskId); diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts @@ -171,6 +171,7 @@ import { DbReadOnlyTransaction } from "./query.js"; import { createRecoupGroup } from "./recoup.js"; import { createRefreshGroup } from "./refresh.js"; import { + BalanceEffect, constructTransactionIdentifier, notifyTransition, rematerializeTransactions, @@ -2284,6 +2285,7 @@ export class DenomLossTransactionContext implements TransactionContext { newTxState: { major: TransactionMajorState.Deleted, }, + balanceEffect: BalanceEffect.Any, }; } return undefined; diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -155,6 +155,7 @@ import { getTotalRefreshCost, } from "./refresh.js"; import { + BalanceEffect, constructTransactionIdentifier, isUnsuccessfulTransaction, notifyTransition, @@ -366,6 +367,9 @@ export class PayMerchantTransactionContext implements TransactionContext { return { oldTxState, newTxState, + // FIXME: The transition function should really return the effect + // and not just the status. + balanceEffect: BalanceEffect.Any, }; } case TransitionResultType.Delete: @@ -376,6 +380,7 @@ export class PayMerchantTransactionContext implements TransactionContext { newTxState: { major: TransactionMajorState.None, }, + balanceEffect: BalanceEffect.Any, }; default: return undefined; @@ -424,7 +429,7 @@ export class PayMerchantTransactionContext implements TransactionContext { await tx.purchases.put(purchase); await this.updateTransactionMeta(tx); const newTxState = computePayMerchantTransactionState(purchase); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.None }; }, ); notifyTransition(wex, transactionId, transitionInfo); @@ -503,7 +508,7 @@ export class PayMerchantTransactionContext implements TransactionContext { await tx.purchases.put(purchase); await this.updateTransactionMeta(tx); const newTxState = computePayMerchantTransactionState(purchase); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); wex.taskScheduler.stopShepherdTask(this.taskId); @@ -528,7 +533,7 @@ export class PayMerchantTransactionContext implements TransactionContext { await tx.purchases.put(purchase); await this.updateTransactionMeta(tx); const newTxState = computePayMerchantTransactionState(purchase); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, transactionId, transitionInfo); @@ -568,7 +573,7 @@ export class PayMerchantTransactionContext implements TransactionContext { } await this.updateTransactionMeta(tx); const newTxState = computePayMerchantTransactionState(purchase); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, transactionId, transitionInfo); @@ -791,7 +796,7 @@ async function failProposalPermanently( const newTxState = computePayMerchantTransactionState(p); await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -1101,7 +1106,6 @@ async function processDownloadProposal( // FIXME: Adjust this to account for refunds, don't count as repurchase // if original order is refunded. - if (repurchase) { logger.warn("repurchase detected"); p.purchaseStatus = PurchaseStatus.DoneRepurchaseDetected; @@ -1118,7 +1122,8 @@ async function processDownloadProposal( return { oldTxState, newTxState, - }; + balanceEffect: BalanceEffect.None, + } satisfies TransitionInfo; }, ); @@ -1198,7 +1203,7 @@ async function createOrReusePurchase( const newTxState = computePayMerchantTransactionState(p); await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); @@ -1260,7 +1265,7 @@ async function createOrReusePurchase( const transitionInfo = await wex.db.runReadWriteTx( { storeNames: ["purchases", "transactionsMeta"] }, - async (tx) => { + async (tx): Promise<TransitionInfo> => { await tx.purchases.put(proposalRecord); await ctx.updateTransactionMeta(tx); const oldTxState: TransactionState = { @@ -1270,6 +1275,7 @@ async function createOrReusePurchase( return { oldTxState, newTxState, + balanceEffect: BalanceEffect.None, }; }, ); @@ -1342,6 +1348,7 @@ async function storeFirstPaySuccess( return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; }, ); @@ -1378,7 +1385,7 @@ async function storePayReplaySuccess( await tx.purchases.put(purchase); await ctx.updateTransactionMeta(tx); const newTxState = computePayMerchantTransactionState(purchase); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -1691,7 +1698,7 @@ async function checkPaymentByProposalId( await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); const newTxState = computePayMerchantTransactionState(p); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, transactionId, transitionInfo); @@ -2293,7 +2300,7 @@ export async function confirmPay( break; } const newTxState = computePayMerchantTransactionState(p); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); @@ -2436,7 +2443,7 @@ async function processPurchasePay( const newTxState = computePayMerchantTransactionState(p); await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); const transactionId = constructTransactionIdentifier({ @@ -2731,7 +2738,7 @@ export async function refuseProposal( const newTxState = computePayMerchantTransactionState(proposal); await tx.purchases.put(proposal); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); @@ -3094,6 +3101,7 @@ export async function sharePayment( transitionInfo: { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }, }; }, @@ -3194,7 +3202,7 @@ async function processPurchaseDialogShared( const newTxState = computePayMerchantTransactionState(p); await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); const transactionId = constructTransactionIdentifier({ @@ -3274,7 +3282,7 @@ async function processPurchaseAutoRefund( const newTxState = computePayMerchantTransactionState(p); await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -3331,7 +3339,7 @@ async function processPurchaseAutoRefund( const newTxState = computePayMerchantTransactionState(p); await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -3489,7 +3497,7 @@ async function processPurchaseQueryRefund( const newTxState = computePayMerchantTransactionState(p); await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -3517,7 +3525,7 @@ async function processPurchaseQueryRefund( const newTxState = computePayMerchantTransactionState(p); await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -3616,7 +3624,7 @@ export async function startQueryRefund( const newTxState = computePayMerchantTransactionState(p); await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -3823,6 +3831,7 @@ async function storeRefunds( transitionInfo: { oldTxState: { major: TransactionMajorState.None }, newTxState: computeRefundTransactionState(newGroup), + balanceEffect: BalanceEffect.Any, }, }); } @@ -3879,6 +3888,7 @@ async function storeRefunds( transitionInfo: { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }, }); await createRefreshGroup( @@ -3927,6 +3937,7 @@ async function storeRefunds( transitionInfo: { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }, }; }, diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -102,6 +102,7 @@ import { getMergeReserveInfo, } from "./pay-peer-common.js"; import { + BalanceEffect, TransitionInfo, constructTransactionIdentifier, isUnsuccessfulTransaction, @@ -196,6 +197,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: res.balanceEffect, }; } case TransitionResultType.Delete: @@ -206,6 +208,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { newTxState: { major: TransactionMajorState.None, }, + balanceEffect: BalanceEffect.None, }; default: return undefined; @@ -466,6 +469,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.None, }; } return undefined; @@ -523,6 +527,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; } return undefined; @@ -591,6 +596,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.None, }; } return undefined; @@ -652,6 +658,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; } return undefined; @@ -705,7 +712,7 @@ async function queryPurseForPeerPullCredit( await tx.peerPullCredit.put(finPi); await ctx.updateTransactionMeta(tx); const newTxState = computePeerPullCreditTransactionState(finPi); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -770,7 +777,7 @@ async function queryPurseForPeerPullCredit( await tx.peerPullCredit.put(finPi); await ctx.updateTransactionMeta(tx); const newTxState = computePeerPullCreditTransactionState(finPi); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -833,7 +840,7 @@ async function longpollKycStatus( const newTxState = computePeerPullCreditTransactionState(peerIni); await tx.peerPullCredit.put(peerIni); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -892,6 +899,7 @@ async function processPeerPullCreditAbortingDeletePurse( return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; }, ); @@ -942,6 +950,7 @@ async function handlePeerPullCreditWithdrawing( return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; }, ); @@ -1093,7 +1102,7 @@ async function handlePeerPullCreditCreatePurse( await tx.peerPullCredit.put(pi2); await ctx.updateTransactionMeta(tx); const newTxState = computePeerPullCreditTransactionState(pi2); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -1311,7 +1320,11 @@ async function processPeerPullCreditKycRequired( await tx.peerPullCredit.put(peerInc); await ctx.updateTransactionMeta(tx); return { - transitionInfo: { oldTxState, newTxState }, + transitionInfo: { + oldTxState, + newTxState, + balanceEffect: BalanceEffect.Any, + }, result: TaskRunResult.progress(), }; }, @@ -1509,7 +1522,7 @@ export async function initiatePeerPullPayment( contractTermsRaw: contractTerms, h: hContractTerms, }); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -99,6 +99,7 @@ import { import { DbReadWriteTransaction, StoreNames } from "./query.js"; import { createRefreshGroup } from "./refresh.js"; import { + BalanceEffect, constructTransactionIdentifier, isUnsuccessfulTransaction, notifyTransition, @@ -254,6 +255,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.None, }; } return undefined; @@ -399,6 +401,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; } case TransitionResultType.Delete: { @@ -409,6 +412,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { newTxState: { major: TransactionMajorState.None, }, + balanceEffect: BalanceEffect.Any, }; } default: @@ -732,7 +736,7 @@ async function processPeerPullDebitAbortingRefresh( const newTxState = computePeerPullDebitTransactionState(newDg); await tx.peerPullDebit.put(newDg); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; } return undefined; }, @@ -869,7 +873,7 @@ export async function confirmPeerPullDebit( await ctx.updateTransactionMeta(tx); await tx.peerPullDebit.put(pi); const newTxState = computePeerPullDebitTransactionState(pi); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -101,6 +101,7 @@ import { getMergeReserveInfo, } from "./pay-peer-common.js"; import { + BalanceEffect, TransitionInfo, constructTransactionIdentifier, isUnsuccessfulTransaction, @@ -198,6 +199,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: res.balanceEffect, }; } case TransitionResultType.Delete: @@ -208,6 +210,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { newTxState: { major: TransactionMajorState.None, }, + balanceEffect: BalanceEffect.Any, }; default: return undefined; @@ -469,6 +472,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; } return undefined; @@ -530,6 +534,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.None, }; } return undefined; @@ -581,6 +586,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; }, ); @@ -745,6 +751,7 @@ export async function preparePeerPushCredit( major: TransactionMajorState.None, }, newTxState, + balanceEffect: BalanceEffect.Any, } satisfies TransitionInfo; }, ); @@ -824,7 +831,7 @@ async function longpollKycStatus( const newTxState = computePeerPushCreditTransactionState(peerInc); await tx.peerPushCredit.put(peerInc); await ctx.updateTransactionMeta(tx); - return { oldTxState, newTxState }; + return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); @@ -908,7 +915,11 @@ async function processPeerPushCreditKycRequired( await tx.peerPushCredit.put(peerInc); await ctx.updateTransactionMeta(tx); return { - transitionInfo: { oldTxState, newTxState }, + transitionInfo: { + oldTxState, + newTxState, + balanceEffect: BalanceEffect.Any, + }, result: TaskRunResult.progress(), }; }, @@ -1071,7 +1082,11 @@ async function handlePendingMerge( await ctx.updateTransactionMeta(tx); const newTxState = computePeerPushCreditTransactionState(peerInc); return { - peerPushCreditTransition: { oldTxState, newTxState }, + peerPushCreditTransition: { + oldTxState, + newTxState, + balanceEffect: BalanceEffect.Any, + }, wgCreateRes, }; }, @@ -1135,6 +1150,7 @@ async function handlePendingWithdrawing( return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; }, ); diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -92,6 +92,7 @@ import { } from "./pay-peer-common.js"; import { createRefreshGroup } from "./refresh.js"; import { + BalanceEffect, constructTransactionIdentifier, isUnsuccessfulTransaction, notifyTransition, @@ -251,6 +252,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.None, }; } return undefined; @@ -303,6 +305,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; } return undefined; @@ -355,6 +358,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.None, }; } return undefined; @@ -402,6 +406,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; }, ); @@ -917,6 +922,7 @@ async function processPeerPushDebitAbortingDeletePurse( return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; }, ); @@ -955,6 +961,7 @@ async function transitionPeerPushDebitTransaction( return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; }, ); @@ -1060,6 +1067,7 @@ async function processPeerPushDebitReady( return { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; }, ); @@ -1246,6 +1254,7 @@ export async function initiatePeerPushDebit( transitionInfo: { oldTxState: { major: TransactionMajorState.None }, newTxState, + balanceEffect: BalanceEffect.Any, }, exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl, }; diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts @@ -112,6 +112,7 @@ import { import { selectWithdrawalDenominations } from "./denomSelection.js"; import { getScopeForAllExchanges } from "./exchanges.js"; import { + BalanceEffect, constructTransactionIdentifier, isUnsuccessfulTransaction, notifyTransition, @@ -265,7 +266,8 @@ export class RefreshTransactionContext implements TransactionContext { return { oldTxState, newTxState, - }; + balanceEffect: BalanceEffect.None, + } satisfies TransitionInfo; } case TransitionResultType.Delete: await tx.refreshGroups.delete(this.refreshGroupId); @@ -275,7 +277,9 @@ export class RefreshTransactionContext implements TransactionContext { newTxState: { major: TransactionMajorState.None, }, - }; + // Deletion will affect balance + balanceEffect: BalanceEffect.Any, + } satisfies TransitionInfo; default: return undefined; } @@ -1510,7 +1514,11 @@ export async function processRefreshGroup( return { oldTxState, newTxState, - }; + balanceEffect: + rg.operationStatus === RefreshOperationStatus.Failed + ? BalanceEffect.Any + : BalanceEffect.PreserveUserVisible, + } satisfies TransitionInfo; } return undefined; }, diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts @@ -37,7 +37,6 @@ import { TransactionByIdRequest, TransactionIdStr, TransactionMajorState, - TransactionMinorState, TransactionsRequest, TransactionsResponse, TransactionState, @@ -905,6 +904,13 @@ export async function abortTransaction( export interface TransitionInfo { oldTxState: TransactionState; newTxState: TransactionState; + balanceEffect: BalanceEffect; +} + +export enum BalanceEffect { + None = 0, + PreserveUserVisible = 1, + Any = 2, } /** @@ -931,34 +937,13 @@ export function notifyTransition( experimentalUserData, }); - wex.ws.notify({ - type: NotificationType.BalanceChange, - hintTransactionId: transactionId, - isInternal: !couldChangeVisibleBalance(transitionInfo), - }); - } -} - -function couldChangeVisibleBalance(ti: TransitionInfo): boolean { - // We emit a balance change notification unless we're sure that - // the transition does not affect the balance. - if (ti.newTxState.major == ti.oldTxState.major) { - return false; - } - - if ( - ti.newTxState.major === TransactionMajorState.Dialog && - ti.newTxState.minor === TransactionMinorState.MerchantOrderProposed - ) { - return false; - } - - if ( - ti.newTxState.major === TransactionMajorState.Pending && - ti.newTxState.minor === TransactionMinorState.ClaimProposal - ) { - return false; + if (transitionInfo.balanceEffect > BalanceEffect.None) { + wex.ws.notify({ + type: NotificationType.BalanceChange, + hintTransactionId: transactionId, + isInternal: + transitionInfo.balanceEffect <= BalanceEffect.PreserveUserVisible, + }); + } } - - return true; } diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -178,6 +178,7 @@ import { } from "./kyc.js"; import { DbAccess } from "./query.js"; import { + BalanceEffect, TransitionInfo, constructTransactionIdentifier, isUnsuccessfulTransaction, @@ -533,6 +534,7 @@ export class WithdrawTransactionContext implements TransactionContext { return { oldTxState, newTxState, + balanceEffect: res.balanceEffect, }; } case TransitionResultType.Delete: @@ -543,6 +545,7 @@ export class WithdrawTransactionContext implements TransactionContext { newTxState: { major: TransactionMajorState.None, }, + balanceEffect: BalanceEffect.Any, }; default: return undefined; @@ -3237,7 +3240,7 @@ export async function internalPerformCreateWithdrawalGroup( ); } -export async function internalPerformExchangeWasUsed( +async function internalPerformExchangeWasUsed( wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction<["exchanges"]>, canonExchange: string, @@ -3257,6 +3260,7 @@ export async function internalPerformExchangeWasUsed( const transitionInfo = { oldTxState, newTxState, + balanceEffect: BalanceEffect.Any, }; const exchangeUsedRes = await markExchangeUsed(wex, tx, canonExchange);