From 2a92ca8732195a3a317a0edc155efc0b72351272 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 30 May 2023 15:58:28 +0200 Subject: wallet-core: report possible actions in transactions list --- packages/taler-util/src/transactions-types.ts | 14 +++ packages/taler-wallet-core/src/db.ts | 2 +- .../src/operations/backup/export.ts | 2 +- .../src/operations/backup/import.ts | 2 +- .../taler-wallet-core/src/operations/deposits.ts | 59 +++++++++- .../src/operations/pay-merchant.ts | 77 ++++++++++-- .../taler-wallet-core/src/operations/pay-peer.ts | 131 +++++++++++++++++++-- .../taler-wallet-core/src/operations/refresh.ts | 44 +++++-- packages/taler-wallet-core/src/operations/tip.ts | 21 +++- .../src/operations/transactions.ts | 60 +++++++--- .../taler-wallet-core/src/operations/withdraw.ts | 46 +++++++- packages/taler-wallet-core/src/wallet.ts | 4 +- 12 files changed, 407 insertions(+), 55 deletions(-) diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts index c06bc7369..5f5b9d112 100644 --- a/packages/taler-util/src/transactions-types.ts +++ b/packages/taler-util/src/transactions-types.ts @@ -125,6 +125,15 @@ export enum TransactionMinorState { AcceptRefund = "accept-refund", } +export enum TransactionAction { + Delete = "delete", + Suspend = "suspend", + Resume = "resume", + Abort = "abort", + Fail = "fail", + Retry = "retry", +} + export interface TransactionsResponse { // a list of past and pending transactions sorted by pending, timestamp and transactionId. // In case two events are both pending and have the same timestamp, @@ -149,6 +158,11 @@ export interface TransactionCommon { */ txState: TransactionState; + /** + * Possible transitions based on the current state. + */ + txActions: string[]; + /** * Raw amount of the transaction (exclusive of fees or other extra costs). */ diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 3147cb9b9..74332de33 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1131,7 +1131,7 @@ export enum PurchaseStatus { /** * Proposal downloaded, but the user needs to accept/reject it. */ - Proposed = 30, + DialogProposed = 30, /** * The user has rejected the proposal. diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index 0aca45551..23c6e787a 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -418,7 +418,7 @@ export async function exportBackup( break; case PurchaseStatus.PendingPayingReplay: case PurchaseStatus.PendingDownloadingProposal: - case PurchaseStatus.Proposed: + case PurchaseStatus.DialogProposed: case PurchaseStatus.PendingPaying: propStatus = BackupProposalStatus.Proposed; break; diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 5efdb78c1..d3346a3a2 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -578,7 +578,7 @@ export async function importBackup( proposalStatus = PurchaseStatus.Done; break; case BackupProposalStatus.Proposed: - proposalStatus = PurchaseStatus.Proposed; + proposalStatus = PurchaseStatus.DialogProposed; break; case BackupProposalStatus.PermanentlyFailed: proposalStatus = PurchaseStatus.AbortedIncompletePayment; diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 1ed2a705e..6387fc9b7 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -59,11 +59,11 @@ import { TransactionType, URL, WireFee, + TransactionAction, } from "@gnu-taler/taler-util"; import { DenominationRecord, DepositGroupRecord, - OperationStatus, DepositElementStatus, } from "../db.js"; import { TalerError } from "@gnu-taler/taler-util"; @@ -115,6 +115,7 @@ export function computeDepositTransactionStatus( major: TransactionMajorState.Done, }; } + // FIXME: We should actually use separate pending states for this! case DepositOperationStatus.Pending: { const numTotal = dg.payCoinSelection.coinPubs.length; let numDeposited = 0; @@ -134,9 +135,6 @@ export function computeDepositTransactionStatus( } } - logger.info(`num total ${numTotal}`); - logger.info(`num deposited ${numDeposited}`); - if (numKycRequired > 0) { return { major: TransactionMajorState.Pending, @@ -181,6 +179,57 @@ export function computeDepositTransactionStatus( } } +export function computeDepositTransactionActions( + dg: DepositGroupRecord, +): TransactionAction[] { + switch (dg.operationStatus) { + case DepositOperationStatus.Finished: { + return [TransactionAction.Delete]; + } + case DepositOperationStatus.Pending: { + const numTotal = dg.payCoinSelection.coinPubs.length; + let numDeposited = 0; + let numKycRequired = 0; + let numWired = 0; + for (let i = 0; i < numTotal; i++) { + if (dg.depositedPerCoin[i]) { + numDeposited++; + } + switch (dg.transactionPerCoin[i]) { + case DepositElementStatus.KycRequired: + numKycRequired++; + break; + case DepositElementStatus.Wired: + numWired++; + break; + } + } + + if (numKycRequired > 0) { + return [TransactionAction.Suspend, TransactionAction.Fail]; + } + + if (numDeposited == numTotal) { + return [TransactionAction.Suspend, TransactionAction.Fail]; + } + + return [TransactionAction.Suspend, TransactionAction.Abort]; + } + case DepositOperationStatus.Suspended: + return [TransactionAction.Resume]; + case DepositOperationStatus.Aborting: + return [TransactionAction.Fail, TransactionAction.Suspend]; + case DepositOperationStatus.Aborted: + return [TransactionAction.Delete]; + case DepositOperationStatus.Failed: + return [TransactionAction.Delete]; + case DepositOperationStatus.SuspendedAborting: + return [TransactionAction.Resume, TransactionAction.Fail]; + default: + throw Error(`unexpected deposit group state (${dg.operationStatus})`); + } +} + export async function suspendDepositGroup( ws: InternalWalletState, depositGroupId: string, @@ -309,7 +358,7 @@ export async function abortDepositGroup( notifyTransition(ws, transactionId, transitionInfo); } -export async function cancelAbortingDepositGroup( +export async function failDepositTransaction( ws: InternalWalletState, depositGroupId: string, ): Promise { diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts index 8462f2fb9..dce2a30ed 100644 --- a/packages/taler-wallet-core/src/operations/pay-merchant.ts +++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts @@ -70,6 +70,7 @@ import { TalerProtocolTimestamp, TalerProtocolViolationError, TalerUriAction, + TransactionAction, TransactionMajorState, TransactionMinorState, TransactionState, @@ -547,7 +548,7 @@ async function processDownloadProposal( await tx.purchases.put(p); } } else { - p.purchaseStatus = PurchaseStatus.Proposed; + p.purchaseStatus = PurchaseStatus.DialogProposed; await tx.purchases.put(p); } const newTxState = computePayMerchantTransactionState(p); @@ -994,7 +995,7 @@ export async function checkPaymentByProposalId( return tx.purchases.get(proposalId); }); - if (!purchase || purchase.purchaseStatus === PurchaseStatus.Proposed) { + if (!purchase || purchase.purchaseStatus === PurchaseStatus.DialogProposed) { // If not already paid, check if we could pay for it. const res = await selectPayCoinsNew(ws, { auditors: [], @@ -1417,7 +1418,7 @@ export async function confirmPay( } const oldTxState = computePayMerchantTransactionState(p); switch (p.purchaseStatus) { - case PurchaseStatus.Proposed: + case PurchaseStatus.DialogProposed: p.payInfo = { payCoinSelection: coinSelection, payCoinSelectionUid: encodeCrock(getRandomBytes(16)), @@ -1498,7 +1499,7 @@ export async function processPurchase( case PurchaseStatus.FailedClaim: case PurchaseStatus.Done: case PurchaseStatus.RepurchaseDetected: - case PurchaseStatus.Proposed: + case PurchaseStatus.DialogProposed: case PurchaseStatus.AbortedProposalRefused: case PurchaseStatus.AbortedIncompletePayment: case PurchaseStatus.SuspendedAbortingWithRefund: @@ -1713,7 +1714,7 @@ export async function refuseProposal( logger.trace(`proposal ${proposalId} not found, won't refuse proposal`); return undefined; } - if (proposal.purchaseStatus !== PurchaseStatus.Proposed) { + if (proposal.purchaseStatus !== PurchaseStatus.DialogProposed) { return undefined; } const oldTxState = computePayMerchantTransactionState(proposal); @@ -1791,7 +1792,7 @@ export async function abortPayMerchant( ws.workAvailable.trigger(); } -export async function cancelAbortingPaymentTransaction( +export async function failPaymentTransaction( ws: InternalWalletState, proposalId: string, ): Promise { @@ -2023,7 +2024,7 @@ export function computePayMerchantTransactionState( major: TransactionMajorState.SuspendedAborting, }; // Dialog States - case PurchaseStatus.Proposed: + case PurchaseStatus.DialogProposed: return { major: TransactionMajorState.Dialog, minor: TransactionMinorState.MerchantOrderProposed, @@ -2060,6 +2061,68 @@ export function computePayMerchantTransactionState( } } +export function computePayMerchantTransactionActions( + purchaseRecord: PurchaseRecord, +): TransactionAction[] { + switch (purchaseRecord.purchaseStatus) { + // Pending States + case PurchaseStatus.PendingDownloadingProposal: + return [TransactionAction.Suspend, TransactionAction.Abort]; + case PurchaseStatus.PendingPaying: + return [TransactionAction.Suspend, TransactionAction.Abort]; + case PurchaseStatus.PendingPayingReplay: + // Special "abort" since it goes back to "done". + return [TransactionAction.Suspend, TransactionAction.Abort]; + case PurchaseStatus.PendingQueryingAutoRefund: + // Special "abort" since it goes back to "done". + return [TransactionAction.Suspend, TransactionAction.Abort]; + case PurchaseStatus.PendingQueryingRefund: + // Special "abort" since it goes back to "done". + return [TransactionAction.Suspend, TransactionAction.Abort]; + case PurchaseStatus.PendingAcceptRefund: + // Special "abort" since it goes back to "done". + return [TransactionAction.Suspend, TransactionAction.Abort]; + // Suspended Pending States + case PurchaseStatus.SuspendedDownloadingProposal: + return [TransactionAction.Resume, TransactionAction.Abort]; + case PurchaseStatus.SuspendedPaying: + return [TransactionAction.Resume, TransactionAction.Abort]; + case PurchaseStatus.SuspendedPayingReplay: + // Special "abort" since it goes back to "done". + return [TransactionAction.Resume, TransactionAction.Abort]; + case PurchaseStatus.SuspendedQueryingAutoRefund: + // Special "abort" since it goes back to "done". + return [TransactionAction.Resume, TransactionAction.Abort]; + case PurchaseStatus.SuspendedQueryingRefund: + // Special "abort" since it goes back to "done". + return [TransactionAction.Resume, TransactionAction.Abort]; + case PurchaseStatus.SuspendedPendingAcceptRefund: + // Special "abort" since it goes back to "done". + return [TransactionAction.Resume, TransactionAction.Abort]; + // Aborting States + case PurchaseStatus.AbortingWithRefund: + return [TransactionAction.Fail, TransactionAction.Suspend]; + case PurchaseStatus.SuspendedAbortingWithRefund: + return [TransactionAction.Fail, TransactionAction.Resume]; + // Dialog States + case PurchaseStatus.DialogProposed: + return []; + // Final States + case PurchaseStatus.AbortedProposalRefused: + return [TransactionAction.Delete]; + case PurchaseStatus.Done: + return [TransactionAction.Delete]; + case PurchaseStatus.RepurchaseDetected: + return [TransactionAction.Delete]; + case PurchaseStatus.AbortedIncompletePayment: + return [TransactionAction.Delete]; + case PurchaseStatus.FailedClaim: + return [TransactionAction.Delete]; + case PurchaseStatus.FailedAbort: + return [TransactionAction.Delete]; + } +} + async function processPurchaseAutoRefund( ws: InternalWalletState, purchase: PurchaseRecord, diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts index 031bdfb92..edebad65b 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer.ts @@ -79,6 +79,7 @@ import { TransactionMajorState, TransactionMinorState, TalerPreciseTimestamp, + TransactionAction, } from "@gnu-taler/taler-util"; import { SpendCoinDetails } from "../crypto/cryptoImplementation.js"; import { @@ -2008,7 +2009,36 @@ export function computePeerPushDebitTransactionState( case PeerPushPaymentInitiationStatus.Failed: return { major: TransactionMajorState.Failed, - } + }; + } +} + +export function computePeerPushDebitTransactionActions( + ppiRecord: PeerPushPaymentInitiationRecord, +): TransactionAction[] { + switch (ppiRecord.status) { + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPushPaymentInitiationStatus.PendingReady: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPushPaymentInitiationStatus.Aborted: + return [TransactionAction.Delete]; + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + return [TransactionAction.Suspend, TransactionAction.Fail]; + case PeerPushPaymentInitiationStatus.AbortingRefresh: + return [TransactionAction.Suspend, TransactionAction.Fail]; + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + return [TransactionAction.Resume, TransactionAction.Fail]; + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + return [TransactionAction.Resume, TransactionAction.Fail]; + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + return [TransactionAction.Resume, TransactionAction.Abort]; + case PeerPushPaymentInitiationStatus.SuspendedReady: + return [TransactionAction.Suspend, TransactionAction.Abort]; + case PeerPushPaymentInitiationStatus.Done: + return [TransactionAction.Delete]; + case PeerPushPaymentInitiationStatus.Failed: + return [TransactionAction.Delete]; } } @@ -2072,7 +2102,7 @@ export async function abortPeerPushDebitTransaction( notifyTransition(ws, transactionId, transitionInfo); } -export async function cancelAbortingPeerPushDebitTransaction( +export async function failPeerPushDebitTransaction( ws: InternalWalletState, pursePub: string, ) { @@ -2316,8 +2346,7 @@ export async function abortPeerPullDebitTransaction( notifyTransition(ws, transactionId, transitionInfo); } - -export async function cancelAbortingPeerPullDebitTransaction( +export async function failPeerPullDebitTransaction( ws: InternalWalletState, peerPullPaymentIncomingId: string, ) { @@ -2501,7 +2530,6 @@ export async function suspendPeerPushCreditTransaction( notifyTransition(ws, transactionId, transitionInfo); } - export async function abortPeerPushCreditTransaction( ws: InternalWalletState, peerPushPaymentIncomingId: string, @@ -2568,7 +2596,7 @@ export async function abortPeerPushCreditTransaction( notifyTransition(ws, transactionId, transitionInfo); } -export async function cancelAbortingPeerPushCreditTransaction( +export async function failPeerPushCreditTransaction( ws: InternalWalletState, peerPushPaymentIncomingId: string, ) { @@ -2765,7 +2793,7 @@ export async function abortPeerPullCreditTransaction( notifyTransition(ws, transactionId, transitionInfo); } -export async function cancelAbortingPeerPullCreditTransaction( +export async function failPeerPullCreditTransaction( ws: InternalWalletState, pursePub: string, ) { @@ -2996,12 +3024,41 @@ export function computePeerPushCreditTransactionState( }; case PeerPushPaymentIncomingStatus.Aborted: return { - major: TransactionMajorState.Aborted + major: TransactionMajorState.Aborted, }; case PeerPushPaymentIncomingStatus.Failed: return { major: TransactionMajorState.Failed, - } + }; + default: + assertUnreachable(pushCreditRecord.status); + } +} + +export function computePeerPushCreditTransactionActions( + pushCreditRecord: PeerPushPaymentIncomingRecord, +): TransactionAction[] { + switch (pushCreditRecord.status) { + case PeerPushPaymentIncomingStatus.DialogProposed: + return []; + case PeerPushPaymentIncomingStatus.PendingMerge: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPushPaymentIncomingStatus.Done: + return [TransactionAction.Delete]; + case PeerPushPaymentIncomingStatus.PendingMergeKycRequired: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPushPaymentIncomingStatus.PendingWithdrawing: + return [TransactionAction.Suspend, TransactionAction.Fail]; + case PeerPushPaymentIncomingStatus.SuspendedMerge: + return [TransactionAction.Resume, TransactionAction.Abort]; + case PeerPushPaymentIncomingStatus.SuspendedMergeKycRequired: + return [TransactionAction.Resume, TransactionAction.Abort]; + case PeerPushPaymentIncomingStatus.SuspendedWithdrawing: + return [TransactionAction.Resume, TransactionAction.Fail]; + case PeerPushPaymentIncomingStatus.Aborted: + return [TransactionAction.Delete]; + case PeerPushPaymentIncomingStatus.Failed: + return [TransactionAction.Delete]; default: assertUnreachable(pushCreditRecord.status); } @@ -3076,6 +3133,39 @@ export function computePeerPullCreditTransactionState( } } +export function computePeerPullCreditTransactionActions( + pullCreditRecord: PeerPullPaymentInitiationRecord, +): TransactionAction[] { + switch (pullCreditRecord.status) { + case PeerPullPaymentInitiationStatus.PendingCreatePurse: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPullPaymentInitiationStatus.PendingReady: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPullPaymentInitiationStatus.DonePurseDeposited: + return [TransactionAction.Delete]; + case PeerPullPaymentInitiationStatus.PendingWithdrawing: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPullPaymentInitiationStatus.SuspendedCreatePurse: + return [TransactionAction.Resume, TransactionAction.Abort]; + case PeerPullPaymentInitiationStatus.SuspendedReady: + return [TransactionAction.Abort, TransactionAction.Resume]; + case PeerPullPaymentInitiationStatus.SuspendedWithdrawing: + return [TransactionAction.Resume, TransactionAction.Fail]; + case PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired: + return [TransactionAction.Resume, TransactionAction.Fail]; + case PeerPullPaymentInitiationStatus.Aborted: + return [TransactionAction.Delete]; + case PeerPullPaymentInitiationStatus.AbortingDeletePurse: + return [TransactionAction.Suspend, TransactionAction.Fail]; + case PeerPullPaymentInitiationStatus.Failed: + return [TransactionAction.Delete]; + case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse: + return [TransactionAction.Resume, TransactionAction.Fail]; + } +} + export function computePeerPullDebitTransactionState( pullDebitRecord: PeerPullPaymentIncomingRecord, ): TransactionState { @@ -3119,3 +3209,26 @@ export function computePeerPullDebitTransactionState( }; } } + +export function computePeerPullDebitTransactionActions( + pullDebitRecord: PeerPullPaymentIncomingRecord, +): TransactionAction[] { + switch (pullDebitRecord.status) { + case PeerPullDebitRecordStatus.DialogProposed: + return []; + case PeerPullDebitRecordStatus.PendingDeposit: + return [TransactionAction.Abort, TransactionAction.Suspend]; + case PeerPullDebitRecordStatus.DonePaid: + return [TransactionAction.Delete]; + case PeerPullDebitRecordStatus.SuspendedDeposit: + return [TransactionAction.Resume, TransactionAction.Abort]; + case PeerPullDebitRecordStatus.Aborted: + return [TransactionAction.Delete]; + case PeerPullDebitRecordStatus.AbortingRefresh: + return [TransactionAction.Fail, TransactionAction.Suspend]; + case PeerPullDebitRecordStatus.Failed: + return [TransactionAction.Delete]; + case PeerPullDebitRecordStatus.SuspendedAbortingRefresh: + return [TransactionAction.Resume, TransactionAction.Fail]; + } +} diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 8437d2d0b..c2cf13857 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -50,6 +50,7 @@ import { TalerErrorDetail, TalerPreciseTimestamp, TalerProtocolTimestamp, + TransactionAction, TransactionMajorState, TransactionState, TransactionType, @@ -96,7 +97,10 @@ import { PendingTaskType, WalletConfig, } from "../index.js"; -import { constructTransactionIdentifier, notifyTransition } from "./transactions.js"; +import { + constructTransactionIdentifier, + notifyTransition, +} from "./transactions.js"; const logger = new Logger("refresh.ts"); @@ -1076,8 +1080,12 @@ export async function createRefreshGroup( * Timestamp after which the wallet would do the next check for an auto-refresh. */ function getAutoRefreshCheckThreshold(d: DenominationRecord): AbsoluteTime { - const expireWithdraw = AbsoluteTime.fromProtocolTimestamp(d.stampExpireWithdraw); - const expireDeposit = AbsoluteTime.fromProtocolTimestamp(d.stampExpireDeposit); + const expireWithdraw = AbsoluteTime.fromProtocolTimestamp( + d.stampExpireWithdraw, + ); + const expireDeposit = AbsoluteTime.fromProtocolTimestamp( + d.stampExpireDeposit, + ); const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit); const deltaDiv = durationMul(delta, 0.75); return AbsoluteTime.addDuration(expireWithdraw, deltaDiv); @@ -1087,8 +1095,12 @@ function getAutoRefreshCheckThreshold(d: DenominationRecord): AbsoluteTime { * Timestamp after which the wallet would do an auto-refresh. */ function getAutoRefreshExecuteThreshold(d: DenominationRecord): AbsoluteTime { - const expireWithdraw = AbsoluteTime.fromProtocolTimestamp(d.stampExpireWithdraw); - const expireDeposit = AbsoluteTime.fromProtocolTimestamp(d.stampExpireDeposit); + const expireWithdraw = AbsoluteTime.fromProtocolTimestamp( + d.stampExpireWithdraw, + ); + const expireDeposit = AbsoluteTime.fromProtocolTimestamp( + d.stampExpireDeposit, + ); const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit); const deltaDiv = durationMul(delta, 0.5); return AbsoluteTime.addDuration(expireWithdraw, deltaDiv); @@ -1175,7 +1187,8 @@ export async function autoRefresh( logger.info( `next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`, ); - exchange.nextRefreshCheck = AbsoluteTime.toPreciseTimestamp(minCheckThreshold); + exchange.nextRefreshCheck = + AbsoluteTime.toPreciseTimestamp(minCheckThreshold); await tx.exchanges.put(exchange); }); return OperationAttemptResult.finishedEmpty(); @@ -1204,6 +1217,21 @@ export function computeRefreshTransactionState( } } +export function computeRefreshTransactionActions( + rg: RefreshGroupRecord, +): TransactionAction[] { + switch (rg.operationStatus) { + case RefreshOperationStatus.Finished: + return [TransactionAction.Delete]; + case RefreshOperationStatus.Failed: + return [TransactionAction.Delete]; + case RefreshOperationStatus.Pending: + return [TransactionAction.Suspend, TransactionAction.Fail]; + case RefreshOperationStatus.Suspended: + return [TransactionAction.Resume, TransactionAction.Fail]; + } +} + export async function suspendRefreshGroup( ws: InternalWalletState, refreshGroupId: string, @@ -1292,7 +1320,7 @@ export async function resumeRefreshGroup( notifyTransition(ws, transactionId, transitionInfo); } -export async function cancelAbortingRefreshGroup( +export async function failRefreshGroup( ws: InternalWalletState, refreshGroupId: string, ): Promise { @@ -1321,7 +1349,7 @@ export async function abortRefreshGroup( let newStatus: RefreshOperationStatus | undefined; switch (dg.operationStatus) { case RefreshOperationStatus.Finished: - break;; + break; case RefreshOperationStatus.Pending: case RefreshOperationStatus.Suspended: newStatus = RefreshOperationStatus.Failed; diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 0bee2b406..02c933cba 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -36,6 +36,7 @@ import { TalerPreciseTimestamp, TalerProtocolTimestamp, TipPlanchetDetail, + TransactionAction, TransactionMajorState, TransactionMinorState, TransactionState, @@ -111,6 +112,24 @@ export function computeTipTransactionStatus( } } + +export function computeTipTransactionActions( + tipRecord: TipRecord, +): TransactionAction[] { + switch (tipRecord.status) { + case TipRecordStatus.Done: + return [TransactionAction.Delete]; + case TipRecordStatus.Aborted: + return [TransactionAction.Delete]; + case TipRecordStatus.PendingPickup: + return [TransactionAction.Suspend, TransactionAction.Fail]; + case TipRecordStatus.SuspendidPickup: + return [TransactionAction.Resume, TransactionAction.Fail]; + default: + assertUnreachable(tipRecord.status); + } +} + export async function prepareTip( ws: InternalWalletState, talerTipUri: string, @@ -526,7 +545,7 @@ export async function resumeTipTransaction( notifyTransition(ws, transactionId, transitionInfo); } -export async function cancelAbortingTipTransaction( +export async function failTipTransaction( ws: InternalWalletState, walletTipId: string, ): Promise { diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index d424019ac..a0da95799 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -73,32 +73,34 @@ import { constructTaskIdentifier, TaskIdentifiers } from "../util/retries.js"; import { resetOperationTimeout, TombstoneTag } from "./common.js"; import { abortDepositGroup, - cancelAbortingDepositGroup, + failDepositTransaction, computeDepositTransactionStatus, deleteDepositGroup, resumeDepositGroup, suspendDepositGroup, + computeDepositTransactionActions, } from "./deposits.js"; import { getExchangeDetails } from "./exchanges.js"; import { abortPayMerchant, - cancelAbortingPaymentTransaction, + failPaymentTransaction, computePayMerchantTransactionState, computeRefundTransactionState, expectProposalDownload, extractContractData, resumePayMerchant, suspendPayMerchant, + computePayMerchantTransactionActions, } from "./pay-merchant.js"; import { abortPeerPullCreditTransaction, abortPeerPullDebitTransaction, abortPeerPushCreditTransaction, abortPeerPushDebitTransaction, - cancelAbortingPeerPullCreditTransaction, - cancelAbortingPeerPullDebitTransaction, - cancelAbortingPeerPushCreditTransaction, - cancelAbortingPeerPushDebitTransaction, + failPeerPullCreditTransaction, + failPeerPullDebitTransaction, + failPeerPushCreditTransaction, + failPeerPushDebitTransaction, computePeerPullCreditTransactionState, computePeerPullDebitTransactionState, computePeerPushCreditTransactionState, @@ -111,28 +113,35 @@ import { suspendPeerPullDebitTransaction, suspendPeerPushCreditTransaction, suspendPeerPushDebitTransaction, + computePeerPushDebitTransactionActions, + computePeerPullDebitTransactionActions, + computePeerPullCreditTransactionActions, + computePeerPushCreditTransactionActions, } from "./pay-peer.js"; import { abortRefreshGroup, - cancelAbortingRefreshGroup, + failRefreshGroup, computeRefreshTransactionState, resumeRefreshGroup, suspendRefreshGroup, + computeRefreshTransactionActions, } from "./refresh.js"; import { abortTipTransaction, - cancelAbortingTipTransaction, + failTipTransaction, computeTipTransactionStatus, resumeTipTransaction, suspendTipTransaction, + computeTipTransactionActions, } from "./tip.js"; import { abortWithdrawalTransaction, augmentPaytoUrisForWithdrawal, - cancelAbortingWithdrawalTransaction, + failWithdrawalTransaction, computeWithdrawalTransactionStatus, resumeWithdrawalTransaction, suspendWithdrawalTransaction, + computeWithdrawalTransactionActions, } from "./withdraw.js"; const logger = new Logger("taler-wallet-core:transactions.ts"); @@ -429,6 +438,7 @@ function buildTransactionForPushPaymentDebit( return { type: TransactionType.PeerPushDebit, txState: computePeerPushDebitTransactionState(pi), + txActions: computePeerPushDebitTransactionActions(pi), amountEffective: pi.totalCost, amountRaw: pi.amount, exchangeBaseUrl: pi.exchangeBaseUrl, @@ -456,6 +466,7 @@ function buildTransactionForPullPaymentDebit( return { type: TransactionType.PeerPullDebit, txState: computePeerPullDebitTransactionState(pi), + txActions: computePeerPullDebitTransactionActions(pi), amountEffective: pi.coinSel?.totalCost ? pi.coinSel?.totalCost : Amounts.stringify(pi.contractTerms.amount), @@ -503,6 +514,7 @@ function buildTransactionForPeerPullCredit( return { type: TransactionType.PeerPullCredit, txState: computePeerPullCreditTransactionState(pullCredit), + txActions: computePeerPullCreditTransactionActions(pullCredit), amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.instructedAmount), exchangeBaseUrl: wsr.exchangeBaseUrl, @@ -533,6 +545,7 @@ function buildTransactionForPeerPullCredit( return { type: TransactionType.PeerPullCredit, txState: computePeerPullCreditTransactionState(pullCredit), + txActions: computePeerPullCreditTransactionActions(pullCredit), amountEffective: Amounts.stringify(pullCredit.estimatedAmountEffective), amountRaw: Amounts.stringify(peerContractTerms.amount), exchangeBaseUrl: pullCredit.exchangeBaseUrl, @@ -569,6 +582,7 @@ function buildTransactionForPeerPushCredit( return { type: TransactionType.PeerPushCredit, txState: computePeerPushCreditTransactionState(pushInc), + txActions: computePeerPushCreditTransactionActions(pushInc), amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.instructedAmount), exchangeBaseUrl: wsr.exchangeBaseUrl, @@ -588,6 +602,7 @@ function buildTransactionForPeerPushCredit( return { type: TransactionType.PeerPushCredit, txState: computePeerPushCreditTransactionState(pushInc), + txActions: computePeerPushCreditTransactionActions(pushInc), // FIXME: This is wrong, needs to consider fees! amountEffective: Amounts.stringify(peerContractTerms.amount), amountRaw: Amounts.stringify(peerContractTerms.amount), @@ -615,6 +630,7 @@ function buildTransactionForBankIntegratedWithdraw( return { type: TransactionType.Withdrawal, txState: computeWithdrawalTransactionStatus(wgRecord), + txActions: computeWithdrawalTransactionActions(wgRecord), amountEffective: Amounts.stringify(wgRecord.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wgRecord.instructedAmount), withdrawalDetails: { @@ -656,6 +672,7 @@ function buildTransactionForManualWithdraw( return { type: TransactionType.Withdrawal, txState: computeWithdrawalTransactionStatus(withdrawalGroup), + txActions: computeWithdrawalTransactionActions(withdrawalGroup), amountEffective: Amounts.stringify( withdrawalGroup.denomsSel.totalCoinValue, ), @@ -706,6 +723,7 @@ function buildTransactionForRefund( refundGroupId: refundRecord.refundGroupId, }), txState: computeRefundTransactionState(refundRecord), + txActions: [], paymentInfo, }; } @@ -725,6 +743,7 @@ function buildTransactionForRefresh( return { type: TransactionType.Refresh, txState: computeRefreshTransactionState(refreshGroupRecord), + txActions: computeRefreshTransactionActions(refreshGroupRecord), refreshReason: refreshGroupRecord.reason, amountEffective: Amounts.stringify( Amounts.zeroOfCurrency(refreshGroupRecord.currency), @@ -759,6 +778,7 @@ function buildTransactionForDeposit( return { type: TransactionType.Deposit, txState: computeDepositTransactionStatus(dg), + txActions: computeDepositTransactionActions(dg), amountRaw: Amounts.stringify(dg.effectiveDepositAmount), amountEffective: Amounts.stringify(dg.totalPayCost), timestamp: dg.timestampCreated, @@ -791,6 +811,7 @@ function buildTransactionForTip( return { type: TransactionType.Tip, txState: computeTipTransactionStatus(tipRecord), + txActions: computeTipTransactionActions(tipRecord), amountEffective: Amounts.stringify(tipRecord.tipAmountEffective), amountRaw: Amounts.stringify(tipRecord.tipAmountRaw), timestamp: tipRecord.acceptedTimestamp, @@ -860,6 +881,7 @@ async function buildTransactionForPurchase( return { type: TransactionType.Payment, txState: computePayMerchantTransactionState(purchaseRecord), + txActions: computePayMerchantTransactionActions(purchaseRecord), amountRaw: Amounts.stringify(contractData.amount), amountEffective: Amounts.stringify(purchaseRecord.payInfo.totalPayCost), totalRefundRaw: Amounts.stringify(zero), // FIXME! @@ -1493,7 +1515,7 @@ export async function suspendTransaction( } } -export async function cancelAbortingTransaction( +export async function failTransaction( ws: InternalWalletState, transactionId: string, ): Promise { @@ -1503,34 +1525,34 @@ export async function cancelAbortingTransaction( } switch (tx.tag) { case TransactionType.Deposit: - await cancelAbortingDepositGroup(ws, tx.depositGroupId); + await failDepositTransaction(ws, tx.depositGroupId); return; case TransactionType.InternalWithdrawal: case TransactionType.Withdrawal: - await cancelAbortingWithdrawalTransaction(ws, tx.withdrawalGroupId); + await failWithdrawalTransaction(ws, tx.withdrawalGroupId); return; case TransactionType.Payment: - await cancelAbortingPaymentTransaction(ws, tx.proposalId); + await failPaymentTransaction(ws, tx.proposalId); return; case TransactionType.Refund: throw Error("can't do cancel-aborting on refund transaction"); case TransactionType.Tip: - await cancelAbortingTipTransaction(ws, tx.walletTipId); + await failTipTransaction(ws, tx.walletTipId); return; case TransactionType.Refresh: - await cancelAbortingRefreshGroup(ws, tx.refreshGroupId); + await failRefreshGroup(ws, tx.refreshGroupId); return; case TransactionType.PeerPullCredit: - await cancelAbortingPeerPullCreditTransaction(ws, tx.pursePub); + await failPeerPullCreditTransaction(ws, tx.pursePub); return; case TransactionType.PeerPullDebit: - await cancelAbortingPeerPullDebitTransaction(ws, tx.peerPullPaymentIncomingId); + await failPeerPullDebitTransaction(ws, tx.peerPullPaymentIncomingId); return; case TransactionType.PeerPushCredit: - await cancelAbortingPeerPushCreditTransaction(ws, tx.peerPushPaymentIncomingId); + await failPeerPushCreditTransaction(ws, tx.peerPushPaymentIncomingId); return; case TransactionType.PeerPushDebit: - await cancelAbortingPeerPushDebitTransaction(ws, tx.pursePub); + await failPeerPushDebitTransaction(ws, tx.pursePub); return; default: assertUnreachable(tx); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index e08aa59a7..4801a67ee 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -67,6 +67,7 @@ import { TransactionMajorState, TransactionMinorState, TalerPreciseTimestamp, + TransactionAction, } from "@gnu-taler/taler-util"; import { EddsaKeypair } from "../crypto/cryptoImplementation.js"; import { @@ -338,7 +339,7 @@ export async function abortWithdrawalTransaction( notifyTransition(ws, transactionId, transitionInfo); } -export async function cancelAbortingWithdrawalTransaction( +export async function failWithdrawalTransaction( ws: InternalWalletState, withdrawalGroupId: string, ) { @@ -483,6 +484,49 @@ export function computeWithdrawalTransactionStatus( } } +export function computeWithdrawalTransactionActions( + wgRecord: WithdrawalGroupRecord, +): TransactionAction[] { + switch (wgRecord.status) { + case WithdrawalGroupStatus.FailedBankAborted: + return [TransactionAction.Delete]; + case WithdrawalGroupStatus.Finished: + return [TransactionAction.Delete]; + case WithdrawalGroupStatus.PendingRegisteringBank: + return [TransactionAction.Suspend, TransactionAction.Abort]; + case WithdrawalGroupStatus.PendingReady: + return [TransactionAction.Suspend, TransactionAction.Abort]; + case WithdrawalGroupStatus.PendingQueryingStatus: + return [TransactionAction.Suspend, TransactionAction.Abort]; + case WithdrawalGroupStatus.PendingWaitConfirmBank: + return [TransactionAction.Suspend, TransactionAction.Abort]; + case WithdrawalGroupStatus.AbortingBank: + return [TransactionAction.Suspend, TransactionAction.Fail]; + case WithdrawalGroupStatus.SuspendedAbortingBank: + return [TransactionAction.Resume, TransactionAction.Fail]; + case WithdrawalGroupStatus.SuspendedQueryingStatus: + return [TransactionAction.Resume, TransactionAction.Abort]; + case WithdrawalGroupStatus.SuspendedRegisteringBank: + return [TransactionAction.Resume, TransactionAction.Abort] + case WithdrawalGroupStatus.SuspendedWaitConfirmBank: + return [TransactionAction.Resume, TransactionAction.Abort]; + case WithdrawalGroupStatus.SuspendedReady: + return [TransactionAction.Resume, TransactionAction.Abort]; + case WithdrawalGroupStatus.PendingAml: + return [TransactionAction.Resume, TransactionAction.Abort]; + case WithdrawalGroupStatus.PendingKyc: + return [TransactionAction.Resume, TransactionAction.Abort]; + case WithdrawalGroupStatus.SuspendedAml: + return [TransactionAction.Resume, TransactionAction.Abort]; + case WithdrawalGroupStatus.SuspendedKyc: + return [TransactionAction.Resume, TransactionAction.Abort] + case WithdrawalGroupStatus.FailedAbortingBank: + return [TransactionAction.Delete]; + case WithdrawalGroupStatus.AbortedExchange: + return [TransactionAction.Delete]; + } +} + /** * Get information about a withdrawal from * a taler://withdraw URI by asking the bank. diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 85b0b9250..953353164 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -232,7 +232,7 @@ import { import { acceptTip, prepareTip, processTip } from "./operations/tip.js"; import { abortTransaction, - cancelAbortingTransaction, + failTransaction, deleteTransaction, getTransactionById, getTransactions, @@ -1233,7 +1233,7 @@ async function dispatchRequestInternal( } case WalletApiOperation.CancelAbortingTransaction: { const req = codecForCancelAbortingTransactionRequest().decode(payload); - await cancelAbortingTransaction(ws, req.transactionId); + await failTransaction(ws, req.transactionId); return {}; } case WalletApiOperation.ResumeTransaction: { -- cgit v1.2.3