From 000359a5e746d9b704b05f2f3eb8442e10a31f75 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 30 May 2023 12:28:21 +0200 Subject: finish implementation of abort / cancelAborting on all tx types --- packages/taler-wallet-core/src/db.ts | 21 +- .../taler-wallet-core/src/operations/deposits.ts | 2 +- .../src/operations/pay-merchant.ts | 83 ++++- .../taler-wallet-core/src/operations/pay-peer.ts | 410 +++++++++++++++++++++ .../taler-wallet-core/src/operations/refresh.ts | 64 +++- packages/taler-wallet-core/src/operations/tip.ts | 97 ++++- .../src/operations/transactions.ts | 67 +++- 7 files changed, 689 insertions(+), 55 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 3edaf8af5..3147cb9b9 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -879,6 +879,7 @@ export enum TipRecordStatus { SuspendidPickup = 21, Done = 50, + Aborted = 51, } export enum RefreshCoinStatus { @@ -899,9 +900,10 @@ export enum OperationStatus { export enum RefreshOperationStatus { Pending = 10 /* ACTIVE_START */, + Suspended = 20 /* DORMANT_START + 2 */, + Finished = 50 /* DORMANT_START */, - FinishedWithError = 51 /* DORMANT_START + 1 */, - Suspended = 52 /* DORMANT_START + 2 */, + Failed = 51 /* DORMANT_START + 1 */, } export enum DepositGroupOperationStatus { @@ -1155,6 +1157,8 @@ export enum PurchaseStatus { * Payment was successful. */ Done = 54, + + FailedAbort = 55, } /** @@ -1778,6 +1782,7 @@ export enum PeerPushPaymentInitiationStatus { Done = 50 /* DORMANT_START */, Aborted = 51, + Failed = 52, } export interface PeerPushPaymentCoinSelection { @@ -1850,13 +1855,17 @@ export enum PeerPullPaymentInitiationStatus { PendingReady = 11 /* ACTIVE_START + 1 */, PendingMergeKycRequired = 12 /* ACTIVE_START + 2 */, PendingWithdrawing = 13, + AbortingDeletePurse = 14, SuspendedCreatePurse = 30, SuspendedReady = 31, SuspendedMergeKycRequired = 32, SuspendedWithdrawing = 33, + SuspendedAbortingDeletePurse = 34, DonePurseDeposited = 50 /* DORMANT_START */, + Failed = 51, + Aborted = 52, } export interface PeerPullPaymentInitiationRecord { @@ -1927,6 +1936,8 @@ export enum PeerPushPaymentIncomingStatus { DialogProposed = 30 /* USER_ATTENTION_START */, Done = 50 /* DORMANT_START */, + Aborted = 51, + Failed = 52, } /** @@ -1978,12 +1989,16 @@ export interface PeerPushPaymentIncomingRecord { export enum PeerPullDebitRecordStatus { PendingDeposit = 10 /* ACTIVE_START */, + AbortingRefresh = 11, - SuspendedDeposit = 11, + SuspendedDeposit = 20, + SuspendedAbortingRefresh = 21, DialogProposed = 30 /* USER_ATTENTION_START */, DonePaid = 50 /* DORMANT_START */, + Aborted = 51, + Failed = 52, } export interface PeerPullPaymentCoinSelection { diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index de881ddd2..1ed2a705e 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -447,7 +447,7 @@ async function waitForRefreshOnDepositGroup( newOpState = DepositOperationStatus.Aborted; } else if ( refreshGroup.operationStatus === - RefreshOperationStatus.FinishedWithError + RefreshOperationStatus.Failed ) { newOpState = DepositOperationStatus.Aborted; } diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts index 30c75f695..8462f2fb9 100644 --- a/packages/taler-wallet-core/src/operations/pay-merchant.ts +++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts @@ -1508,6 +1508,7 @@ export async function processPurchase( case PurchaseStatus.SuspendedPendingAcceptRefund: case PurchaseStatus.SuspendedQueryingAutoRefund: case PurchaseStatus.SuspendedQueryingRefund: + case PurchaseStatus.FailedAbort: return { type: OperationAttemptResultType.Finished, result: undefined, @@ -1790,10 +1791,55 @@ export async function abortPayMerchant( ws.workAvailable.trigger(); } +export async function cancelAbortingPaymentTransaction( + ws: InternalWalletState, + proposalId: string, +): Promise { + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.Payment, + proposalId, + }); + const opId = constructTaskIdentifier({ + tag: PendingTaskType.Purchase, + proposalId, + }); + const transitionInfo = await ws.db + .mktx((x) => [ + x.purchases, + x.refreshGroups, + x.denominations, + x.coinAvailability, + x.coins, + x.operationRetries, + ]) + .runReadWrite(async (tx) => { + const purchase = await tx.purchases.get(proposalId); + if (!purchase) { + throw Error("purchase not found"); + } + const oldTxState = computePayMerchantTransactionState(purchase); + let newState: PurchaseStatus | undefined = undefined; + switch (purchase.purchaseStatus) { + case PurchaseStatus.AbortingWithRefund: + newState = PurchaseStatus.FailedAbort; + break; + } + if (newState) { + purchase.purchaseStatus = newState; + await tx.purchases.put(purchase); + } + const newTxState = computePayMerchantTransactionState(purchase); + return { oldTxState, newTxState }; + }); + notifyTransition(ws, transactionId, transitionInfo); + ws.workAvailable.trigger(); +} -const transitionSuspend: { [x in PurchaseStatus]?: { - next: PurchaseStatus | undefined, -} } = { +const transitionSuspend: { + [x in PurchaseStatus]?: { + next: PurchaseStatus | undefined; + }; +} = { [PurchaseStatus.PendingDownloadingProposal]: { next: PurchaseStatus.SuspendedDownloadingProposal, }, @@ -1808,12 +1854,14 @@ const transitionSuspend: { [x in PurchaseStatus]?: { }, [PurchaseStatus.PendingQueryingAutoRefund]: { next: PurchaseStatus.SuspendedQueryingAutoRefund, - } -} + }, +}; -const transitionResume: { [x in PurchaseStatus]?: { - next: PurchaseStatus | undefined, -} } = { +const transitionResume: { + [x in PurchaseStatus]?: { + next: PurchaseStatus | undefined; + }; +} = { [PurchaseStatus.SuspendedDownloadingProposal]: { next: PurchaseStatus.PendingDownloadingProposal, }, @@ -1828,9 +1876,8 @@ const transitionResume: { [x in PurchaseStatus]?: { }, [PurchaseStatus.SuspendedQueryingAutoRefund]: { next: PurchaseStatus.PendingQueryingAutoRefund, - } -} - + }, +}; export async function suspendPayMerchant( ws: InternalWalletState, @@ -1846,9 +1893,7 @@ export async function suspendPayMerchant( }); stopLongpolling(ws, opId); const transitionInfo = await ws.db - .mktx((x) => [ - x.purchases, - ]) + .mktx((x) => [x.purchases]) .runReadWrite(async (tx) => { const purchase = await tx.purchases.get(proposalId); if (!purchase) { @@ -1867,7 +1912,6 @@ export async function suspendPayMerchant( ws.workAvailable.trigger(); } - export async function resumePayMerchant( ws: InternalWalletState, proposalId: string, @@ -1882,9 +1926,7 @@ export async function resumePayMerchant( }); stopLongpolling(ws, opId); const transitionInfo = await ws.db - .mktx((x) => [ - x.purchases, - ]) + .mktx((x) => [x.purchases]) .runReadWrite(async (tx) => { const purchase = await tx.purchases.get(proposalId); if (!purchase) { @@ -2010,6 +2052,11 @@ export function computePayMerchantTransactionState( major: TransactionMajorState.Failed, minor: TransactionMinorState.ClaimProposal, }; + case PurchaseStatus.FailedAbort: + return { + major: TransactionMajorState.Failed, + minor: TransactionMinorState.AbortingBank, + }; } } diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts index 95878543b..031bdfb92 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer.ts @@ -2005,6 +2005,10 @@ export function computePeerPushDebitTransactionState( return { major: TransactionMajorState.Done, }; + case PeerPushPaymentInitiationStatus.Failed: + return { + major: TransactionMajorState.Failed, + } } } @@ -2048,6 +2052,8 @@ export async function abortPeerPushDebitTransaction( case PeerPushPaymentInitiationStatus.Aborted: // Do nothing break; + case PeerPushPaymentInitiationStatus.Failed: + break; default: assertUnreachable(pushDebitRec.status); } @@ -2104,6 +2110,7 @@ export async function cancelAbortingPeerPushDebitTransaction( case PeerPushPaymentInitiationStatus.PendingCreatePurse: case PeerPushPaymentInitiationStatus.Done: case PeerPushPaymentInitiationStatus.Aborted: + case PeerPushPaymentInitiationStatus.Failed: // Do nothing break; default: @@ -2166,6 +2173,7 @@ export async function suspendPeerPushDebitTransaction( case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: case PeerPushPaymentInitiationStatus.Done: case PeerPushPaymentInitiationStatus.Aborted: + case PeerPushPaymentInitiationStatus.Failed: // Do nothing break; default: @@ -2220,6 +2228,138 @@ export async function suspendPeerPullDebitTransaction( break; case PeerPullDebitRecordStatus.SuspendedDeposit: break; + case PeerPullDebitRecordStatus.Aborted: + break; + case PeerPullDebitRecordStatus.AbortingRefresh: + newStatus = PeerPullDebitRecordStatus.SuspendedAbortingRefresh; + break; + case PeerPullDebitRecordStatus.Failed: + break; + case PeerPullDebitRecordStatus.SuspendedAbortingRefresh: + break; + default: + assertUnreachable(pullDebitRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPullDebitTransactionState(pullDebitRec); + pullDebitRec.status = newStatus; + const newTxState = computePeerPullDebitTransactionState(pullDebitRec); + await tx.peerPullPaymentIncoming.put(pullDebitRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function abortPeerPullDebitTransaction( + ws: InternalWalletState, + peerPullPaymentIncomingId: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPullDebit, + peerPullPaymentIncomingId, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPullDebit, + peerPullPaymentIncomingId, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPullPaymentIncoming]) + .runReadWrite(async (tx) => { + const pullDebitRec = await tx.peerPullPaymentIncoming.get( + peerPullPaymentIncomingId, + ); + if (!pullDebitRec) { + logger.warn(`peer pull debit ${peerPullPaymentIncomingId} not found`); + return; + } + let newStatus: PeerPullDebitRecordStatus | undefined = undefined; + switch (pullDebitRec.status) { + case PeerPullDebitRecordStatus.DialogProposed: + newStatus = PeerPullDebitRecordStatus.Aborted; + break; + case PeerPullDebitRecordStatus.DonePaid: + break; + case PeerPullDebitRecordStatus.PendingDeposit: + newStatus = PeerPullDebitRecordStatus.AbortingRefresh; + break; + case PeerPullDebitRecordStatus.SuspendedDeposit: + break; + case PeerPullDebitRecordStatus.Aborted: + break; + case PeerPullDebitRecordStatus.AbortingRefresh: + break; + case PeerPullDebitRecordStatus.Failed: + break; + case PeerPullDebitRecordStatus.SuspendedAbortingRefresh: + break; + default: + assertUnreachable(pullDebitRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPullDebitTransactionState(pullDebitRec); + pullDebitRec.status = newStatus; + const newTxState = computePeerPullDebitTransactionState(pullDebitRec); + await tx.peerPullPaymentIncoming.put(pullDebitRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + + +export async function cancelAbortingPeerPullDebitTransaction( + ws: InternalWalletState, + peerPullPaymentIncomingId: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPullDebit, + peerPullPaymentIncomingId, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPullDebit, + peerPullPaymentIncomingId, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPullPaymentIncoming]) + .runReadWrite(async (tx) => { + const pullDebitRec = await tx.peerPullPaymentIncoming.get( + peerPullPaymentIncomingId, + ); + if (!pullDebitRec) { + logger.warn(`peer pull debit ${peerPullPaymentIncomingId} not found`); + return; + } + let newStatus: PeerPullDebitRecordStatus | undefined = undefined; + switch (pullDebitRec.status) { + case PeerPullDebitRecordStatus.DialogProposed: + newStatus = PeerPullDebitRecordStatus.Aborted; + break; + case PeerPullDebitRecordStatus.DonePaid: + break; + case PeerPullDebitRecordStatus.PendingDeposit: + break; + case PeerPullDebitRecordStatus.SuspendedDeposit: + break; + case PeerPullDebitRecordStatus.Aborted: + break; + case PeerPullDebitRecordStatus.Failed: + break; + case PeerPullDebitRecordStatus.SuspendedAbortingRefresh: + case PeerPullDebitRecordStatus.AbortingRefresh: + // FIXME: abort underlying refresh! + newStatus = PeerPullDebitRecordStatus.Failed; + break; default: assertUnreachable(pullDebitRec.status); } @@ -2270,6 +2410,15 @@ export async function resumePeerPullDebitTransaction( case PeerPullDebitRecordStatus.SuspendedDeposit: newStatus = PeerPullDebitRecordStatus.PendingDeposit; break; + case PeerPullDebitRecordStatus.Aborted: + break; + case PeerPullDebitRecordStatus.AbortingRefresh: + break; + case PeerPullDebitRecordStatus.Failed: + break; + case PeerPullDebitRecordStatus.SuspendedAbortingRefresh: + newStatus = PeerPullDebitRecordStatus.AbortingRefresh; + break; default: assertUnreachable(pullDebitRec.status); } @@ -2330,6 +2479,10 @@ export async function suspendPeerPushCreditTransaction( // FIXME: Suspend internal withdrawal transaction! newStatus = PeerPushPaymentIncomingStatus.SuspendedWithdrawing; break; + case PeerPushPaymentIncomingStatus.Aborted: + break; + case PeerPushPaymentIncomingStatus.Failed: + break; default: assertUnreachable(pushCreditRec.status); } @@ -2348,6 +2501,81 @@ export async function suspendPeerPushCreditTransaction( notifyTransition(ws, transactionId, transitionInfo); } + +export async function abortPeerPushCreditTransaction( + ws: InternalWalletState, + peerPushPaymentIncomingId: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushCredit, + peerPushPaymentIncomingId, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushCredit, + peerPushPaymentIncomingId, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentIncoming]) + .runReadWrite(async (tx) => { + const pushCreditRec = await tx.peerPushPaymentIncoming.get( + peerPushPaymentIncomingId, + ); + if (!pushCreditRec) { + logger.warn(`peer push credit ${peerPushPaymentIncomingId} not found`); + return; + } + let newStatus: PeerPushPaymentIncomingStatus | undefined = undefined; + switch (pushCreditRec.status) { + case PeerPushPaymentIncomingStatus.DialogProposed: + newStatus = PeerPushPaymentIncomingStatus.Aborted; + break; + case PeerPushPaymentIncomingStatus.Done: + break; + case PeerPushPaymentIncomingStatus.SuspendedMerge: + case PeerPushPaymentIncomingStatus.SuspendedMergeKycRequired: + case PeerPushPaymentIncomingStatus.SuspendedWithdrawing: + newStatus = PeerPushPaymentIncomingStatus.Aborted; + break; + case PeerPushPaymentIncomingStatus.PendingMergeKycRequired: + newStatus = PeerPushPaymentIncomingStatus.Aborted; + break; + case PeerPushPaymentIncomingStatus.PendingMerge: + newStatus = PeerPushPaymentIncomingStatus.Aborted; + break; + case PeerPushPaymentIncomingStatus.PendingWithdrawing: + newStatus = PeerPushPaymentIncomingStatus.Aborted; + break; + case PeerPushPaymentIncomingStatus.Aborted: + break; + case PeerPushPaymentIncomingStatus.Failed: + break; + default: + assertUnreachable(pushCreditRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPushCreditTransactionState(pushCreditRec); + pushCreditRec.status = newStatus; + const newTxState = computePeerPushCreditTransactionState(pushCreditRec); + await tx.peerPushPaymentIncoming.put(pushCreditRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function cancelAbortingPeerPushCreditTransaction( + ws: InternalWalletState, + peerPushPaymentIncomingId: string, +) { + // We don't have any "aborting" states! + throw Error("can't run cancel-aborting on peer-push-credit transaction"); +} + export async function resumePeerPushCreditTransaction( ws: InternalWalletState, peerPushPaymentIncomingId: string, @@ -2388,6 +2616,10 @@ export async function resumePeerPushCreditTransaction( // FIXME: resume underlying "internal-withdrawal" transaction. newStatus = PeerPushPaymentIncomingStatus.PendingWithdrawing; break; + case PeerPushPaymentIncomingStatus.Aborted: + break; + case PeerPushPaymentIncomingStatus.Failed: + break; default: assertUnreachable(pushCreditRec.status); } @@ -2442,11 +2674,135 @@ export async function suspendPeerPullCreditTransaction( case PeerPullPaymentInitiationStatus.PendingReady: newStatus = PeerPullPaymentInitiationStatus.SuspendedReady; break; + case PeerPullPaymentInitiationStatus.AbortingDeletePurse: + newStatus = + PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse; + break; + case PeerPullPaymentInitiationStatus.DonePurseDeposited: + case PeerPullPaymentInitiationStatus.SuspendedCreatePurse: + case PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired: + case PeerPullPaymentInitiationStatus.SuspendedReady: + case PeerPullPaymentInitiationStatus.SuspendedWithdrawing: + case PeerPullPaymentInitiationStatus.Aborted: + case PeerPullPaymentInitiationStatus.Failed: + case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse: + break; + default: + assertUnreachable(pullCreditRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPullCreditTransactionState(pullCreditRec); + pullCreditRec.status = newStatus; + const newTxState = computePeerPullCreditTransactionState(pullCreditRec); + await tx.peerPullPaymentInitiations.put(pullCreditRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function abortPeerPullCreditTransaction( + ws: InternalWalletState, + pursePub: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPullCredit, + pursePub, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPullCredit, + pursePub, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPullPaymentInitiations]) + .runReadWrite(async (tx) => { + const pullCreditRec = await tx.peerPullPaymentInitiations.get(pursePub); + if (!pullCreditRec) { + logger.warn(`peer pull credit ${pursePub} not found`); + return; + } + let newStatus: PeerPullPaymentInitiationStatus | undefined = undefined; + switch (pullCreditRec.status) { + case PeerPullPaymentInitiationStatus.PendingCreatePurse: + case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: + newStatus = PeerPullPaymentInitiationStatus.AbortingDeletePurse; + break; + case PeerPullPaymentInitiationStatus.PendingWithdrawing: + throw Error("can't abort anymore"); + case PeerPullPaymentInitiationStatus.PendingReady: + newStatus = PeerPullPaymentInitiationStatus.AbortingDeletePurse; + break; + case PeerPullPaymentInitiationStatus.DonePurseDeposited: + case PeerPullPaymentInitiationStatus.SuspendedCreatePurse: + case PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired: + case PeerPullPaymentInitiationStatus.SuspendedReady: + case PeerPullPaymentInitiationStatus.SuspendedWithdrawing: + case PeerPullPaymentInitiationStatus.Aborted: + case PeerPullPaymentInitiationStatus.AbortingDeletePurse: + case PeerPullPaymentInitiationStatus.Failed: + case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse: + break; + default: + assertUnreachable(pullCreditRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPullCreditTransactionState(pullCreditRec); + pullCreditRec.status = newStatus; + const newTxState = computePeerPullCreditTransactionState(pullCreditRec); + await tx.peerPullPaymentInitiations.put(pullCreditRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function cancelAbortingPeerPullCreditTransaction( + ws: InternalWalletState, + pursePub: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPullCredit, + pursePub, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPullCredit, + pursePub, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPullPaymentInitiations]) + .runReadWrite(async (tx) => { + const pullCreditRec = await tx.peerPullPaymentInitiations.get(pursePub); + if (!pullCreditRec) { + logger.warn(`peer pull credit ${pursePub} not found`); + return; + } + let newStatus: PeerPullPaymentInitiationStatus | undefined = undefined; + switch (pullCreditRec.status) { + case PeerPullPaymentInitiationStatus.PendingCreatePurse: + case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: + case PeerPullPaymentInitiationStatus.PendingWithdrawing: + case PeerPullPaymentInitiationStatus.PendingReady: case PeerPullPaymentInitiationStatus.DonePurseDeposited: case PeerPullPaymentInitiationStatus.SuspendedCreatePurse: case PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired: case PeerPullPaymentInitiationStatus.SuspendedReady: case PeerPullPaymentInitiationStatus.SuspendedWithdrawing: + case PeerPullPaymentInitiationStatus.Aborted: + case PeerPullPaymentInitiationStatus.Failed: + break; + case PeerPullPaymentInitiationStatus.AbortingDeletePurse: + case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse: + newStatus = PeerPullPaymentInitiationStatus.Failed; break; default: assertUnreachable(pullCreditRec.status); @@ -2493,7 +2849,11 @@ export async function resumePeerPullCreditTransaction( case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: case PeerPullPaymentInitiationStatus.PendingWithdrawing: case PeerPullPaymentInitiationStatus.PendingReady: + case PeerPullPaymentInitiationStatus.AbortingDeletePurse: case PeerPullPaymentInitiationStatus.DonePurseDeposited: + case PeerPullPaymentInitiationStatus.Failed: + case PeerPullPaymentInitiationStatus.Aborted: + break; case PeerPullPaymentInitiationStatus.SuspendedCreatePurse: newStatus = PeerPullPaymentInitiationStatus.PendingCreatePurse; break; @@ -2506,6 +2866,9 @@ export async function resumePeerPullCreditTransaction( case PeerPullPaymentInitiationStatus.SuspendedWithdrawing: newStatus = PeerPullPaymentInitiationStatus.PendingWithdrawing; break; + case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse: + newStatus = PeerPullPaymentInitiationStatus.AbortingDeletePurse; + break; default: assertUnreachable(pullCreditRec.status); } @@ -2566,6 +2929,7 @@ export async function resumePeerPushDebitTransaction( case PeerPushPaymentInitiationStatus.PendingReady: case PeerPushPaymentInitiationStatus.Done: case PeerPushPaymentInitiationStatus.Aborted: + case PeerPushPaymentInitiationStatus.Failed: // Do nothing break; default: @@ -2630,6 +2994,16 @@ export function computePeerPushCreditTransactionState( major: TransactionMajorState.Suspended, minor: TransactionMinorState.Withdraw, }; + case PeerPushPaymentIncomingStatus.Aborted: + return { + major: TransactionMajorState.Aborted + }; + case PeerPushPaymentIncomingStatus.Failed: + return { + major: TransactionMajorState.Failed, + } + default: + assertUnreachable(pushCreditRecord.status); } } @@ -2681,6 +3055,24 @@ export function computePeerPullCreditTransactionState( major: TransactionMajorState.Suspended, minor: TransactionMinorState.MergeKycRequired, }; + case PeerPullPaymentInitiationStatus.Aborted: + return { + major: TransactionMajorState.Aborted, + }; + case PeerPullPaymentInitiationStatus.AbortingDeletePurse: + return { + major: TransactionMajorState.Aborting, + minor: TransactionMinorState.DeletePurse, + }; + case PeerPullPaymentInitiationStatus.Failed: + return { + major: TransactionMajorState.Failed, + }; + case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse: + return { + major: TransactionMajorState.Aborting, + minor: TransactionMinorState.DeletePurse, + }; } } @@ -2707,5 +3099,23 @@ export function computePeerPullDebitTransactionState( major: TransactionMajorState.Suspended, minor: TransactionMinorState.Deposit, }; + case PeerPullDebitRecordStatus.Aborted: + return { + major: TransactionMajorState.Aborted, + }; + case PeerPullDebitRecordStatus.AbortingRefresh: + return { + major: TransactionMajorState.Aborting, + minor: TransactionMinorState.Refresh, + }; + case PeerPullDebitRecordStatus.Failed: + return { + major: TransactionMajorState.Failed, + }; + case PeerPullDebitRecordStatus.SuspendedAbortingRefresh: + return { + major: TransactionMajorState.SuspendedAborting, + minor: TransactionMinorState.Refresh, + }; } } diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index c46344313..8437d2d0b 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -96,7 +96,7 @@ import { PendingTaskType, WalletConfig, } from "../index.js"; -import { constructTransactionIdentifier } from "./transactions.js"; +import { constructTransactionIdentifier, notifyTransition } from "./transactions.js"; const logger = new Logger("refresh.ts"); @@ -158,7 +158,7 @@ function updateGroupStatus(rg: RefreshGroupRecord): void { if (allDone) { if (anyFrozen) { rg.timestampFinished = TalerPreciseTimestamp.now(); - rg.operationStatus = RefreshOperationStatus.FinishedWithError; + rg.operationStatus = RefreshOperationStatus.Failed; } else { rg.timestampFinished = TalerPreciseTimestamp.now(); rg.operationStatus = RefreshOperationStatus.Finished; @@ -1189,7 +1189,7 @@ export function computeRefreshTransactionState( return { major: TransactionMajorState.Done, }; - case RefreshOperationStatus.FinishedWithError: + case RefreshOperationStatus.Failed: return { major: TransactionMajorState.Failed, }; @@ -1261,7 +1261,7 @@ export async function resumeRefreshGroup( tag: TransactionType.Refresh, refreshGroupId, }); - let res = await ws.db + const transitionInfo = await ws.db .mktx((x) => [x.refreshGroups]) .runReadWrite(async (tx) => { const dg = await tx.refreshGroups.get(refreshGroupId); @@ -1289,19 +1289,57 @@ export async function resumeRefreshGroup( return undefined; }); ws.workAvailable.trigger(); - if (res) { - ws.notify({ - type: NotificationType.TransactionStateTransition, - transactionId, - oldTxState: res.oldTxState, - newTxState: res.newTxState, - }); - } + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function cancelAbortingRefreshGroup( + ws: InternalWalletState, + refreshGroupId: string, +): Promise { + throw Error("action cancel-aborting not allowed on refreshes"); } export async function abortRefreshGroup( ws: InternalWalletState, refreshGroupId: string, ): Promise { - throw Error("can't abort refresh groups."); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.Refresh, + refreshGroupId, + }); + const transitionInfo = await ws.db + .mktx((x) => [x.refreshGroups]) + .runReadWrite(async (tx) => { + const dg = await tx.refreshGroups.get(refreshGroupId); + if (!dg) { + logger.warn( + `can't resume refresh group, refreshGroupId=${refreshGroupId} not found`, + ); + return; + } + const oldState = computeRefreshTransactionState(dg); + let newStatus: RefreshOperationStatus | undefined; + switch (dg.operationStatus) { + case RefreshOperationStatus.Finished: + break;; + case RefreshOperationStatus.Pending: + case RefreshOperationStatus.Suspended: + newStatus = RefreshOperationStatus.Failed; + break; + case RefreshOperationStatus.Failed: + break; + default: + assertUnreachable(dg.operationStatus); + } + if (newStatus) { + dg.operationStatus = newStatus; + await tx.refreshGroups.put(dg); + } + return { + oldTxState: oldState, + newTxState: computeRefreshTransactionState(dg), + }; + }); + ws.workAvailable.trigger(); + notifyTransition(ws, transactionId, transitionInfo); } diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 70b595c2f..0bee2b406 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -87,21 +87,28 @@ const logger = new Logger("operations/tip.ts"); export function computeTipTransactionStatus( tipRecord: TipRecord, ): TransactionState { - if (tipRecord.pickedUpTimestamp) { - return { - major: TransactionMajorState.Done, - }; - } - if (tipRecord.acceptedTimestamp) { - return { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.Pickup, - }; + switch (tipRecord.status) { + case TipRecordStatus.Done: + return { + major: TransactionMajorState.Done, + }; + case TipRecordStatus.Aborted: + return { + major: TransactionMajorState.Aborted, + }; + case TipRecordStatus.PendingPickup: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Pickup, + }; + case TipRecordStatus.SuspendidPickup: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.User, + }; + default: + assertUnreachable(tipRecord.status); } - return { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.User, - }; } export async function prepareTip( @@ -445,6 +452,7 @@ export async function suspendTipTransaction( switch (tipRec.status) { case TipRecordStatus.Done: case TipRecordStatus.SuspendidPickup: + case TipRecordStatus.Aborted: break; case TipRecordStatus.PendingPickup: newStatus = TipRecordStatus.SuspendidPickup; @@ -492,11 +500,72 @@ export async function resumeTipTransaction( let newStatus: TipRecordStatus | undefined = undefined; switch (tipRec.status) { case TipRecordStatus.Done: + break; case TipRecordStatus.SuspendidPickup: newStatus = TipRecordStatus.PendingPickup; break; case TipRecordStatus.PendingPickup: break; + case TipRecordStatus.Aborted: + break; + default: + assertUnreachable(tipRec.status); + } + if (newStatus != null) { + const oldTxState = computeTipTransactionStatus(tipRec); + tipRec.status = newStatus; + const newTxState = computeTipTransactionStatus(tipRec); + await tx.tips.put(tipRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function cancelAbortingTipTransaction( + ws: InternalWalletState, + walletTipId: string, +): Promise { + // We don't have an "aborting" state, so this should never happen! + throw Error("can't run cance-aborting on tip transaction"); +} + +export async function abortTipTransaction( + ws: InternalWalletState, + walletTipId: string, +): Promise { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.TipPickup, + walletTipId, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.Tip, + walletTipId, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.tips]) + .runReadWrite(async (tx) => { + const tipRec = await tx.tips.get(walletTipId); + if (!tipRec) { + logger.warn(`transaction tip ${walletTipId} not found`); + return; + } + let newStatus: TipRecordStatus | undefined = undefined; + switch (tipRec.status) { + case TipRecordStatus.Done: + break; + case TipRecordStatus.SuspendidPickup: + newStatus = TipRecordStatus.Aborted; + break; + case TipRecordStatus.PendingPickup: + break; + case TipRecordStatus.Aborted: + break; default: assertUnreachable(tipRec.status); } diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index f1cfaed45..d424019ac 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -82,6 +82,7 @@ import { import { getExchangeDetails } from "./exchanges.js"; import { abortPayMerchant, + cancelAbortingPaymentTransaction, computePayMerchantTransactionState, computeRefundTransactionState, expectProposalDownload, @@ -90,6 +91,14 @@ import { suspendPayMerchant, } from "./pay-merchant.js"; import { + abortPeerPullCreditTransaction, + abortPeerPullDebitTransaction, + abortPeerPushCreditTransaction, + abortPeerPushDebitTransaction, + cancelAbortingPeerPullCreditTransaction, + cancelAbortingPeerPullDebitTransaction, + cancelAbortingPeerPushCreditTransaction, + cancelAbortingPeerPushDebitTransaction, computePeerPullCreditTransactionState, computePeerPullDebitTransactionState, computePeerPushCreditTransactionState, @@ -104,11 +113,15 @@ import { suspendPeerPushDebitTransaction, } from "./pay-peer.js"; import { + abortRefreshGroup, + cancelAbortingRefreshGroup, computeRefreshTransactionState, resumeRefreshGroup, suspendRefreshGroup, } from "./refresh.js"; import { + abortTipTransaction, + cancelAbortingTipTransaction, computeTipTransactionStatus, resumeTipTransaction, suspendTipTransaction, @@ -1492,11 +1505,35 @@ export async function cancelAbortingTransaction( case TransactionType.Deposit: await cancelAbortingDepositGroup(ws, tx.depositGroupId); return; + case TransactionType.InternalWithdrawal: case TransactionType.Withdrawal: await cancelAbortingWithdrawalTransaction(ws, tx.withdrawalGroupId); return; + case TransactionType.Payment: + await cancelAbortingPaymentTransaction(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); + return; + case TransactionType.Refresh: + await cancelAbortingRefreshGroup(ws, tx.refreshGroupId); + return; + case TransactionType.PeerPullCredit: + await cancelAbortingPeerPullCreditTransaction(ws, tx.pursePub); + return; + case TransactionType.PeerPullDebit: + await cancelAbortingPeerPullDebitTransaction(ws, tx.peerPullPaymentIncomingId); + return; + case TransactionType.PeerPushCredit: + await cancelAbortingPeerPushCreditTransaction(ws, tx.peerPushPaymentIncomingId); + return; + case TransactionType.PeerPushDebit: + await cancelAbortingPeerPushDebitTransaction(ws, tx.pursePub); + return; default: - logger.warn(`unable to suspend transaction of type '${tx.tag}'`); + assertUnreachable(tx); } } @@ -1774,18 +1811,36 @@ export async function abortTransaction( await abortPayMerchant(ws, txId.proposalId); break; } - case TransactionType.Withdrawal: { + case TransactionType.Withdrawal: + case TransactionType.InternalWithdrawal: { await abortWithdrawalTransaction(ws, txId.withdrawalGroupId); break; } case TransactionType.Deposit: await abortDepositGroup(ws, txId.depositGroupId); break; + case TransactionType.Tip: + await abortTipTransaction(ws, txId.walletTipId); + break; + case TransactionType.Refund: + throw Error("can't abort refund transactions"); + case TransactionType.Refresh: + await abortRefreshGroup(ws, txId.refreshGroupId); + break; + case TransactionType.PeerPullCredit: + await abortPeerPullCreditTransaction(ws, txId.pursePub); + break; + case TransactionType.PeerPullDebit: + await abortPeerPullDebitTransaction(ws, txId.peerPullPaymentIncomingId); + break; + case TransactionType.PeerPushCredit: + await abortPeerPushCreditTransaction(ws, txId.peerPushPaymentIncomingId); + break; + case TransactionType.PeerPushDebit: + await abortPeerPushDebitTransaction(ws, txId.pursePub); + break; default: { - const unknownTxType: any = txId.tag; - throw Error( - `can't abort a '${unknownTxType}' transaction: not yet implemented`, - ); + assertUnreachable(txId); } } } -- cgit v1.2.3