diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations')
10 files changed, 685 insertions, 188 deletions
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 32b90e126..296517162 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -553,7 +553,7 @@ export async function importBackup( reservePub, status: backupWg.timestamp_finish ? WithdrawalGroupStatus.Finished - : WithdrawalGroupStatus.QueryingStatus, // FIXME! + : WithdrawalGroupStatus.PendingQueryingStatus, // FIXME! timestampStart: backupWg.timestamp_created, wgInfo, restrictAge: backupWg.restrict_age, diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts index 539632b02..55015f2e0 100644 --- a/packages/taler-wallet-core/src/operations/common.ts +++ b/packages/taler-wallet-core/src/operations/common.ts @@ -492,7 +492,7 @@ export function runLongpollAsync( if (!res.ready) { await storeOperationPending(ws, retryTag); } - ws.latch.trigger(); + ws.workAvailable.trigger(); }; asyncFn(); } diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index e1d699775..b08f03bd1 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -248,7 +248,7 @@ export async function resumeDepositGroup( } return undefined; }); - ws.latch.trigger(); + ws.workAvailable.trigger(); if (res) { ws.notify({ type: NotificationType.TransactionStateTransition, @@ -301,7 +301,7 @@ export async function abortDepositGroup( }); stopLongpolling(ws, retryTag); // Need to process the operation again. - ws.latch.trigger(); + ws.workAvailable.trigger(); if (res) { ws.notify({ type: NotificationType.TransactionStateTransition, diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts index d2713dc9d..67343d69a 100644 --- a/packages/taler-wallet-core/src/operations/pay-merchant.ts +++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts @@ -2410,7 +2410,7 @@ export async function processPurchaseQueryRefund( return OperationAttemptResult.finishedEmpty(); } -export async function abortPay( +export async function abortPayMerchant( ws: InternalWalletState, proposalId: string, cancelImmediately?: boolean, @@ -2499,6 +2499,7 @@ export function computePayMerchantTransactionState( case PurchaseStatus.Proposed: return { major: TransactionMajorState.Dialog, + minor: TransactionMinorState.MerchantOrderProposed, }; case PurchaseStatus.ProposalDownloadFailed: return { diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts index 33659afe0..31e395cab 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer.ts @@ -75,20 +75,21 @@ import { NotificationType, HttpStatusCode, codecForWalletKycUuid, - WalletKycUuid, + TransactionState, + TransactionMajorState, + TransactionMinorState, } from "@gnu-taler/taler-util"; import { SpendCoinDetails } from "../crypto/cryptoImplementation.js"; import { DenominationRecord, - KycPendingInfo, - KycUserType, - OperationStatus, + PeerPullPaymentIncomingRecord, PeerPullPaymentIncomingStatus, PeerPullPaymentInitiationRecord, PeerPullPaymentInitiationStatus, PeerPushPaymentCoinSelection, PeerPushPaymentIncomingRecord, PeerPushPaymentIncomingStatus, + PeerPushPaymentInitiationRecord, PeerPushPaymentInitiationStatus, ReserveRecord, WithdrawalGroupStatus, @@ -98,7 +99,6 @@ import { TalerError } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; import { LongpollResult, - makeTransactionId, resetOperationTimeout, runLongpollAsync, runOperationWithErrorReporting, @@ -128,8 +128,10 @@ import { import { PendingTaskType } from "../pending-types.js"; import { constructTransactionIdentifier, + notifyTransition, stopLongpolling, } from "./transactions.js"; +import { assertUnreachable } from "../util/assertUnreachable.js"; const logger = new Logger("operations/peer-to-peer.ts"); @@ -451,19 +453,11 @@ export async function checkPeerPushDebit( }; } -export async function processPeerPushInitiation( +async function processPeerPushDebitCreateReserve( ws: InternalWalletState, - pursePub: string, + peerPushInitiation: PeerPushPaymentInitiationRecord, ): Promise<OperationAttemptResult> { - const peerPushInitiation = await ws.db - .mktx((x) => [x.peerPushPaymentInitiations]) - .runReadOnly(async (tx) => { - return tx.peerPushPaymentInitiations.get(pursePub); - }); - if (!peerPushInitiation) { - throw Error("peer push payment not found"); - } - + const pursePub = peerPushInitiation.pursePub; const purseExpiration = peerPushInitiation.purseExpiration; const hContractTerms = peerPushInitiation.contractTermsHash; @@ -501,22 +495,25 @@ export async function processPeerPushInitiation( peerPushInitiation.exchangeBaseUrl, ); - const httpResp = await ws.http.postJson(createPurseUrl.href, { - amount: peerPushInitiation.amount, - merge_pub: peerPushInitiation.mergePub, - purse_sig: purseSigResp.sig, - h_contract_terms: hContractTerms, - purse_expiration: purseExpiration, - deposits: depositSigsResp.deposits, - min_age: 0, - econtract: econtractResp.econtract, + const httpResp = await ws.http.fetch(createPurseUrl.href, { + method: "POST", + body: { + amount: peerPushInitiation.amount, + merge_pub: peerPushInitiation.mergePub, + purse_sig: purseSigResp.sig, + h_contract_terms: hContractTerms, + purse_expiration: purseExpiration, + deposits: depositSigsResp.deposits, + min_age: 0, + econtract: econtractResp.econtract, + }, }); const resp = await httpResp.json(); logger.info(`resp: ${j2s(resp)}`); - if (httpResp.status !== 200) { + if (httpResp.status !== HttpStatusCode.Ok) { throw Error("got error response from exchange"); } @@ -527,7 +524,7 @@ export async function processPeerPushInitiation( if (!ppi) { return; } - ppi.status = PeerPushPaymentInitiationStatus.PurseCreated; + ppi.status = PeerPushPaymentInitiationStatus.Done; await tx.peerPushPaymentInitiations.put(ppi); }); @@ -537,6 +534,122 @@ export async function processPeerPushInitiation( }; } +async function transitionPeerPushDebitFromReadyToDone( + ws: InternalWalletState, + pursePub: string, +): Promise<void> { + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const ppiRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!ppiRec) { + return undefined; + } + if (ppiRec.status !== PeerPushPaymentInitiationStatus.PendingReady) { + return undefined; + } + const oldTxState = computePeerPushDebitTransactionState(ppiRec); + ppiRec.status = PeerPushPaymentInitiationStatus.Done; + const newTxState = computePeerPushDebitTransactionState(ppiRec); + return { + oldTxState, + newTxState, + }; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +/** + * Process the "pending(ready)" state of a peer-push-debit transaction. + */ +async function processPeerPushDebitReady( + ws: InternalWalletState, + peerPushInitiation: PeerPushPaymentInitiationRecord, +): Promise<OperationAttemptResult> { + const pursePub = peerPushInitiation.pursePub; + const retryTag = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + runLongpollAsync(ws, retryTag, async (ct) => { + const mergeUrl = new URL(`purses/${pursePub}/merge`); + mergeUrl.searchParams.set("timeout_ms", "30000"); + const resp = await ws.http.fetch(mergeUrl.href, { + // timeout: getReserveRequestTimeout(withdrawalGroup), + cancellationToken: ct, + }); + if (resp.status === HttpStatusCode.Ok) { + const purseStatus = await readSuccessResponseJsonOrThrow( + resp, + codecForExchangePurseStatus(), + ); + if (purseStatus.deposit_timestamp) { + await transitionPeerPushDebitFromReadyToDone( + ws, + peerPushInitiation.pursePub, + ); + return { + ready: true, + }; + } + } else if (resp.status === HttpStatusCode.Gone) { + // FIXME: transition the reserve into the expired state + } + return { + ready: false, + }; + }); + logger.trace( + "returning early from withdrawal for long-polling in background", + ); + return { + type: OperationAttemptResultType.Longpoll, + }; +} + +export async function processPeerPushDebit( + ws: InternalWalletState, + pursePub: string, +): Promise<OperationAttemptResult> { + const peerPushInitiation = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadOnly(async (tx) => { + return tx.peerPushPaymentInitiations.get(pursePub); + }); + if (!peerPushInitiation) { + throw Error("peer push payment not found"); + } + + const retryTag = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + + // We're already running! + if (ws.activeLongpoll[retryTag]) { + logger.info("peer-push-debit task already in long-polling, returning!"); + return { + type: OperationAttemptResultType.Longpoll, + }; + } + + switch (peerPushInitiation.status) { + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + return processPeerPushDebitCreateReserve(ws, peerPushInitiation); + case PeerPushPaymentInitiationStatus.PendingReady: + return processPeerPushDebitReady(ws, peerPushInitiation); + } + + return { + type: OperationAttemptResultType.Finished, + result: undefined, + }; +} + /** * Initiate sending a peer-to-peer push payment. */ @@ -612,7 +725,7 @@ export async function initiatePeerPushPayment( pursePriv: pursePair.priv, pursePub: pursePair.pub, timestampCreated: TalerProtocolTimestamp.now(), - status: PeerPushPaymentInitiationStatus.Initiated, + status: PeerPushPaymentInitiationStatus.PendingCreatePurse, contractTerms: contractTerms, coinSel: { coinPubs: sel.coins.map((x) => x.coinPub), @@ -628,12 +741,12 @@ export async function initiatePeerPushPayment( }); const taskId = constructTaskIdentifier({ - tag: PendingTaskType.PeerPushInitiation, + tag: PendingTaskType.PeerPushDebit, pursePub: pursePair.pub, }); await runOperationWithErrorReporting(ws, taskId, async () => { - return await processPeerPushInitiation(ws, pursePair.pub); + return await processPeerPushDebit(ws, pursePair.pub); }); return { @@ -645,22 +758,24 @@ export async function initiatePeerPushPayment( exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl, contractPriv: contractKeyPair.priv, }), - transactionId: makeTransactionId( - TransactionType.PeerPushDebit, - pursePair.pub, - ), + transactionId: constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub: pursePair.pub, + }), }; } interface ExchangePurseStatus { balance: AmountString; deposit_timestamp?: TalerProtocolTimestamp; + merge_timestamp?: TalerProtocolTimestamp; } export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> => buildCodecForObject<ExchangePurseStatus>() .property("balance", codecForAmountString()) .property("deposit_timestamp", codecOptional(codecForTimestamp)) + .property("merge_timestamp", codecOptional(codecForTimestamp)) .build("ExchangePurseStatus"); export async function preparePeerPushCredit( @@ -879,13 +994,13 @@ export async function processPeerPushCredit( const amount = Amounts.parseOrThrow(contractTerms.amount); if ( - peerInc.status === PeerPushPaymentIncomingStatus.KycRequired && + peerInc.status === PeerPushPaymentIncomingStatus.MergeKycRequired && peerInc.kycInfo ) { - const txId = makeTransactionId( - TransactionType.PeerPushCredit, - peerInc.peerPushPaymentIncomingId, - ); + const txId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushCredit, + peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId, + }); await checkWithdrawalKycStatus( ws, peerInc.exchangeBaseUrl, @@ -951,7 +1066,7 @@ export async function processPeerPushCredit( paytoHash: kycPending.h_payto, requirementRow: kycPending.requirement_row, }; - peerInc.status = PeerPushPaymentIncomingStatus.KycRequired; + peerInc.status = PeerPushPaymentIncomingStatus.MergeKycRequired; await tx.peerPushPaymentIncoming.put(peerInc); }); return { @@ -975,7 +1090,7 @@ export async function processPeerPushCredit( }, forcedWithdrawalGroupId: peerInc.withdrawalGroupId, exchangeBaseUrl: peerInc.exchangeBaseUrl, - reserveStatus: WithdrawalGroupStatus.QueryingStatus, + reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus, reserveKeyPair: { priv: mergeReserveInfo.reservePriv, pub: mergeReserveInfo.reservePub, @@ -993,9 +1108,9 @@ export async function processPeerPushCredit( } if ( peerInc.status === PeerPushPaymentIncomingStatus.Accepted || - peerInc.status === PeerPushPaymentIncomingStatus.KycRequired + peerInc.status === PeerPushPaymentIncomingStatus.MergeKycRequired ) { - peerInc.status = PeerPushPaymentIncomingStatus.WithdrawalCreated; + peerInc.status = PeerPushPaymentIncomingStatus.Done; } await tx.peerPushPaymentIncoming.put(peerInc); }); @@ -1011,7 +1126,7 @@ export async function confirmPeerPushCredit( req: ConfirmPeerPushCreditRequest, ): Promise<AcceptPeerPushPaymentResponse> { let peerInc: PeerPushPaymentIncomingRecord | undefined; - let contractTerms: PeerContractTerms | undefined; + await ws.db .mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming]) .runReadWrite(async (tx) => { @@ -1021,10 +1136,6 @@ export async function confirmPeerPushCredit( if (!peerInc) { return; } - const ctRec = await tx.contractTerms.get(peerInc.contractTermsHash); - if (ctRec) { - contractTerms = ctRec.contractTermsRaw; - } if (peerInc.status === PeerPushPaymentIncomingStatus.Proposed) { peerInc.status = PeerPushPaymentIncomingStatus.Accepted; } @@ -1037,21 +1148,15 @@ export async function confirmPeerPushCredit( ); } - checkDbInvariant(!!contractTerms); - - await updateExchangeFromUrl(ws, peerInc.exchangeBaseUrl); - - const retryTag = TaskIdentifiers.forPeerPushCredit(peerInc); + ws.workAvailable.trigger(); - await runOperationWithErrorReporting(ws, retryTag, () => - processPeerPushCredit(ws, req.peerPushPaymentIncomingId), - ); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushCredit, + peerPushPaymentIncomingId: req.peerPushPaymentIncomingId, + }); return { - transactionId: makeTransactionId( - TransactionType.PeerPushCredit, - req.peerPushPaymentIncomingId, - ), + transactionId, }; } @@ -1209,11 +1314,13 @@ export async function confirmPeerPullDebit( }, ); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPullDebit, + peerPullPaymentIncomingId: req.peerPullPaymentIncomingId, + }); + return { - transactionId: makeTransactionId( - TransactionType.PeerPullDebit, - req.peerPullPaymentIncomingId, - ), + transactionId, }; } @@ -1395,7 +1502,7 @@ export async function queryPurseForPeerPullCredit( }, forcedWithdrawalGroupId: pullIni.withdrawalGroupId, exchangeBaseUrl: pullIni.exchangeBaseUrl, - reserveStatus: WithdrawalGroupStatus.QueryingStatus, + reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus, reserveKeyPair: { priv: reserve.reservePriv, pub: reserve.reservePub, @@ -1410,8 +1517,8 @@ export async function queryPurseForPeerPullCredit( logger.warn("peerPullPaymentInitiation not found anymore"); return; } - if (finPi.status === PeerPullPaymentInitiationStatus.PurseCreated) { - finPi.status = PeerPullPaymentInitiationStatus.PurseDeposited; + if (finPi.status === PeerPullPaymentInitiationStatus.PendingReady) { + finPi.status = PeerPullPaymentInitiationStatus.DonePurseDeposited; } await tx.peerPullPaymentInitiations.put(finPi); }); @@ -1434,7 +1541,7 @@ export async function processPeerPullCredit( } const retryTag = constructTaskIdentifier({ - tag: PendingTaskType.PeerPullInitiation, + tag: PendingTaskType.PeerPullCredit, pursePub, }); @@ -1449,7 +1556,7 @@ export async function processPeerPullCredit( logger.trace(`processing ${retryTag}, status=${pullIni.status}`); switch (pullIni.status) { - case PeerPullPaymentInitiationStatus.PurseDeposited: { + case PeerPullPaymentInitiationStatus.DonePurseDeposited: { // We implement this case so that the "retry" action on a peer-pull-credit transaction // also retries the withdrawal task. @@ -1475,7 +1582,7 @@ export async function processPeerPullCredit( result: undefined, }; } - case PeerPullPaymentInitiationStatus.PurseCreated: + case PeerPullPaymentInitiationStatus.PendingReady: runLongpollAsync(ws, retryTag, async (cancellationToken) => queryPurseForPeerPullCredit(ws, pullIni, cancellationToken), ); @@ -1485,23 +1592,23 @@ export async function processPeerPullCredit( return { type: OperationAttemptResultType.Longpoll, }; - case PeerPullPaymentInitiationStatus.KycRequired: { + case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: { + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPullCredit, + pursePub: pullIni.pursePub, + }); if (pullIni.kycInfo) { - const txId = makeTransactionId( - TransactionType.PeerPullCredit, - pullIni.pursePub, - ); await checkWithdrawalKycStatus( ws, pullIni.exchangeBaseUrl, - txId, + transactionId, pullIni.kycInfo, "individual", ); } break; } - case PeerPullPaymentInitiationStatus.Initial: + case PeerPullPaymentInitiationStatus.PendingCreatePurse: break; default: throw Error(`unknown PeerPullPaymentInitiationStatus ${pullIni.status}`); @@ -1590,7 +1697,8 @@ export async function processPeerPullCredit( paytoHash: kycPending.h_payto, requirementRow: kycPending.requirement_row, }; - peerIni.status = PeerPullPaymentInitiationStatus.KycRequired; + peerIni.status = + PeerPullPaymentInitiationStatus.PendingMergeKycRequired; await tx.peerPullPaymentInitiations.put(peerIni); }); return { @@ -1610,7 +1718,7 @@ export async function processPeerPullCredit( if (!pi2) { return; } - pi2.status = PeerPullPaymentInitiationStatus.PurseCreated; + pi2.status = PeerPullPaymentInitiationStatus.PendingReady; await tx.peerPullPaymentInitiations.put(pi2); }); @@ -1776,7 +1884,7 @@ export async function initiatePeerPullPayment( pursePub: pursePair.pub, mergePriv: mergePair.priv, mergePub: mergePair.pub, - status: PeerPullPaymentInitiationStatus.Initial, + status: PeerPullPaymentInitiationStatus.PendingCreatePurse, contractTerms: contractTerms, mergeTimestamp, mergeReserveRowId: mergeReserveRowId, @@ -1796,7 +1904,7 @@ export async function initiatePeerPullPayment( // check this asynchronously from the transaction status? const taskId = constructTaskIdentifier({ - tag: PendingTaskType.PeerPullInitiation, + tag: PendingTaskType.PeerPullCredit, pursePub: pursePair.pub, }); @@ -1804,14 +1912,408 @@ export async function initiatePeerPullPayment( return processPeerPullCredit(ws, pursePair.pub); }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPullCredit, + pursePub: pursePair.pub, + }); + return { talerUri: constructPayPullUri({ exchangeBaseUrl: exchangeBaseUrl, contractPriv: contractKeyPair.priv, }), - transactionId: makeTransactionId( - TransactionType.PeerPullCredit, - pursePair.pub, - ), + transactionId, }; } + +export function computePeerPushDebitTransactionState( + ppiRecord: PeerPushPaymentInitiationRecord, +): TransactionState { + switch (ppiRecord.status) { + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.CreatePurse, + }; + case PeerPushPaymentInitiationStatus.PendingReady: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Ready, + }; + case PeerPushPaymentInitiationStatus.Aborted: + return { + major: TransactionMajorState.Aborted, + }; + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + return { + major: TransactionMajorState.Aborting, + minor: TransactionMinorState.DeletePurse, + }; + case PeerPushPaymentInitiationStatus.AbortingRefresh: + return { + major: TransactionMajorState.Aborting, + minor: TransactionMinorState.Refresh, + }; + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + return { + major: TransactionMajorState.SuspendedAborting, + minor: TransactionMinorState.DeletePurse, + }; + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + return { + major: TransactionMajorState.SuspendedAborting, + minor: TransactionMinorState.Refresh, + }; + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.CreatePurse, + }; + case PeerPushPaymentInitiationStatus.SuspendedReady: + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.Ready, + }; + case PeerPushPaymentInitiationStatus.Done: + return { + major: TransactionMajorState.Done, + }; + } +} + +export async function abortPeerPushDebitTransaction( + ws: InternalWalletState, + pursePub: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!pushDebitRec) { + logger.warn(`peer push debit ${pursePub} not found`); + return; + } + let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined; + switch (pushDebitRec.status) { + case PeerPushPaymentInitiationStatus.PendingReady: + case PeerPushPaymentInitiationStatus.SuspendedReady: + newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse; + break; + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + // Network request might already be in-flight! + newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse; + break; + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + case PeerPushPaymentInitiationStatus.AbortingRefresh: + case PeerPushPaymentInitiationStatus.Done: + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + case PeerPushPaymentInitiationStatus.Aborted: + // Do nothing + break; + default: + assertUnreachable(pushDebitRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPushDebitTransactionState(pushDebitRec); + pushDebitRec.status = newStatus; + const newTxState = computePeerPushDebitTransactionState(pushDebitRec); + await tx.peerPushPaymentInitiations.put(pushDebitRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function cancelAbortingPeerPushDebitTransaction( + ws: InternalWalletState, + pursePub: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!pushDebitRec) { + logger.warn(`peer push debit ${pursePub} not found`); + return; + } + let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined; + switch (pushDebitRec.status) { + case PeerPushPaymentInitiationStatus.AbortingRefresh: + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + // FIXME: We also need to abort the refresh group! + newStatus = PeerPushPaymentInitiationStatus.Aborted; + break; + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + newStatus = PeerPushPaymentInitiationStatus.Aborted; + break; + case PeerPushPaymentInitiationStatus.PendingReady: + case PeerPushPaymentInitiationStatus.SuspendedReady: + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + case PeerPushPaymentInitiationStatus.Done: + case PeerPushPaymentInitiationStatus.Aborted: + // Do nothing + break; + default: + assertUnreachable(pushDebitRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPushDebitTransactionState(pushDebitRec); + pushDebitRec.status = newStatus; + const newTxState = computePeerPushDebitTransactionState(pushDebitRec); + await tx.peerPushPaymentInitiations.put(pushDebitRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function suspendPeerPushDebitTransaction( + ws: InternalWalletState, + pursePub: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!pushDebitRec) { + logger.warn(`peer push debit ${pursePub} not found`); + return; + } + let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined; + switch (pushDebitRec.status) { + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + newStatus = PeerPushPaymentInitiationStatus.SuspendedCreatePurse; + break; + case PeerPushPaymentInitiationStatus.AbortingRefresh: + newStatus = PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh; + break; + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + newStatus = + PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse; + break; + case PeerPushPaymentInitiationStatus.PendingReady: + newStatus = PeerPushPaymentInitiationStatus.SuspendedReady; + break; + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + case PeerPushPaymentInitiationStatus.SuspendedReady: + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + case PeerPushPaymentInitiationStatus.Done: + case PeerPushPaymentInitiationStatus.Aborted: + // Do nothing + break; + default: + assertUnreachable(pushDebitRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPushDebitTransactionState(pushDebitRec); + pushDebitRec.status = newStatus; + const newTxState = computePeerPushDebitTransactionState(pushDebitRec); + await tx.peerPushPaymentInitiations.put(pushDebitRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export async function resumePeerPushDebitTransaction( + ws: InternalWalletState, + pursePub: string, +) { + const taskId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub, + }); + stopLongpolling(ws, taskId); + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentInitiations]) + .runReadWrite(async (tx) => { + const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub); + if (!pushDebitRec) { + logger.warn(`peer push debit ${pursePub} not found`); + return; + } + let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined; + switch (pushDebitRec.status) { + case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse: + newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse; + break; + case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh: + newStatus = PeerPushPaymentInitiationStatus.AbortingRefresh; + break; + case PeerPushPaymentInitiationStatus.SuspendedReady: + newStatus = PeerPushPaymentInitiationStatus.PendingReady; + break; + case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: + newStatus = PeerPushPaymentInitiationStatus.PendingCreatePurse; + break; + case PeerPushPaymentInitiationStatus.PendingCreatePurse: + case PeerPushPaymentInitiationStatus.AbortingRefresh: + case PeerPushPaymentInitiationStatus.AbortingDeletePurse: + case PeerPushPaymentInitiationStatus.PendingReady: + case PeerPushPaymentInitiationStatus.Done: + case PeerPushPaymentInitiationStatus.Aborted: + // Do nothing + break; + default: + assertUnreachable(pushDebitRec.status); + } + if (newStatus != null) { + const oldTxState = computePeerPushDebitTransactionState(pushDebitRec); + pushDebitRec.status = newStatus; + const newTxState = computePeerPushDebitTransactionState(pushDebitRec); + await tx.peerPushPaymentInitiations.put(pushDebitRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); +} + +export function computePeerPushCreditTransactionState( + pushCreditRecord: PeerPushPaymentIncomingRecord, +): TransactionState { + switch (pushCreditRecord.status) { + case PeerPushPaymentIncomingStatus.Proposed: + return { + major: TransactionMajorState.Dialog, + minor: TransactionMinorState.Proposed, + }; + case PeerPushPaymentIncomingStatus.Accepted: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Merge, + }; + case PeerPushPaymentIncomingStatus.Done: + return { + major: TransactionMajorState.Done, + }; + case PeerPushPaymentIncomingStatus.MergeKycRequired: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycRequired, + }; + case PeerPushPaymentIncomingStatus.Withdrawing: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Withdraw, + }; + } +} + +export function computePeerPullCreditTransactionState( + pullCreditRecord: PeerPullPaymentInitiationRecord, +): TransactionState { + switch (pullCreditRecord.status) { + case PeerPullPaymentInitiationStatus.PendingCreatePurse: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.CreatePurse, + }; + case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.MergeKycRequired, + }; + case PeerPullPaymentInitiationStatus.PendingReady: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Ready, + }; + case PeerPullPaymentInitiationStatus.DonePurseDeposited: + return { + major: TransactionMajorState.Done, + }; + case PeerPullPaymentInitiationStatus.PendingWithdrawing: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Withdraw, + }; + case PeerPullPaymentInitiationStatus.SuspendedCreatePurse: + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.CreatePurse, + }; + case PeerPullPaymentInitiationStatus.SuspendedReady: + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.Ready, + }; + case PeerPullPaymentInitiationStatus.SuspendedWithdrawing: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Withdraw, + }; + } +} + +export function computePeerPullDebitTransactionState( + pullDebitRecord: PeerPullPaymentIncomingRecord, +): TransactionState { + switch (pullDebitRecord.status) { + case PeerPullPaymentIncomingStatus.Proposed: + return { + major: TransactionMajorState.Dialog, + minor: TransactionMinorState.Proposed, + }; + case PeerPullPaymentIncomingStatus.Accepted: + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Deposit, + }; + case PeerPullPaymentIncomingStatus.Paid: + return { + major: TransactionMajorState.Done, + }; + } +} diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 5e14721f8..084f807f2 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -364,14 +364,14 @@ async function gatherPeerPullInitiationPending( resp: PendingOperationsResponse, ): Promise<void> { await tx.peerPullPaymentInitiations.iter().forEachAsync(async (pi) => { - if (pi.status === PeerPullPaymentInitiationStatus.PurseDeposited) { + if (pi.status === PeerPullPaymentInitiationStatus.DonePurseDeposited) { return; } const opId = TaskIdentifiers.forPeerPullPaymentInitiation(pi); const retryRecord = await tx.operationRetries.get(opId); const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ - type: PendingTaskType.PeerPullInitiation, + type: PendingTaskType.PeerPullCredit, ...getPendingCommon(ws, opId, timestampDue), givesLifeness: true, retryInfo: retryRecord?.retryInfo, @@ -421,14 +421,14 @@ async function gatherPeerPushInitiationPending( resp: PendingOperationsResponse, ): Promise<void> { await tx.peerPushPaymentInitiations.iter().forEachAsync(async (pi) => { - if (pi.status === PeerPushPaymentInitiationStatus.PurseCreated) { + if (pi.status === PeerPushPaymentInitiationStatus.Done) { return; } const opId = TaskIdentifiers.forPeerPushPaymentInitiation(pi); const retryRecord = await tx.operationRetries.get(opId); const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ - type: PendingTaskType.PeerPushInitiation, + type: PendingTaskType.PeerPushDebit, ...getPendingCommon(ws, opId, timestampDue), givesLifeness: true, retryInfo: retryRecord?.retryInfo, @@ -450,7 +450,7 @@ async function gatherPeerPushCreditPending( switch (pi.status) { case PeerPushPaymentIncomingStatus.Proposed: return; - case PeerPushPaymentIncomingStatus.WithdrawalCreated: + case PeerPushPaymentIncomingStatus.Done: return; } const opId = TaskIdentifiers.forPeerPushCredit(pi); diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 3b423474b..982f3cf8c 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -400,7 +400,7 @@ export async function processRecoupGroupHandler( await internalCreateWithdrawalGroup(ws, { amount: Amounts.parseOrThrow(result.balance), exchangeBaseUrl: recoupGroup.exchangeBaseUrl, - reserveStatus: WithdrawalGroupStatus.QueryingStatus, + reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus, reserveKeyPair: { pub: reservePub, priv: reservePrivMap[reservePub], diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 748c929c2..fda9a886a 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -1125,7 +1125,7 @@ export async function autoRefresh( return OperationAttemptResult.finishedEmpty(); } -export function computeRefreshTransactionStatus( +export function computeRefreshTransactionState( rg: RefreshGroupRecord, ): TransactionState { switch (rg.operationStatus) { @@ -1170,7 +1170,7 @@ export async function suspendRefreshGroup( ); return undefined; } - const oldState = computeRefreshTransactionStatus(dg); + const oldState = computeRefreshTransactionState(dg); switch (dg.operationStatus) { case RefreshOperationStatus.Finished: return undefined; @@ -1179,7 +1179,7 @@ export async function suspendRefreshGroup( await tx.refreshGroups.put(dg); return { oldTxState: oldState, - newTxState: computeRefreshTransactionStatus(dg), + newTxState: computeRefreshTransactionState(dg), }; } case RefreshOperationStatus.Suspended: @@ -1215,7 +1215,7 @@ export async function resumeRefreshGroup( ); return; } - const oldState = computeRefreshTransactionStatus(dg); + const oldState = computeRefreshTransactionState(dg); switch (dg.operationStatus) { case RefreshOperationStatus.Finished: return; @@ -1227,12 +1227,12 @@ export async function resumeRefreshGroup( await tx.refreshGroups.put(dg); return { oldTxState: oldState, - newTxState: computeRefreshTransactionStatus(dg), + newTxState: computeRefreshTransactionState(dg), }; } return undefined; }); - ws.latch.trigger(); + ws.workAvailable.trigger(); if (res) { ws.notify({ type: NotificationType.TransactionStateTransition, diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index c122bb651..02f11d82d 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -87,14 +87,14 @@ import { } from "./deposits.js"; import { getExchangeDetails } from "./exchanges.js"; import { - abortPay, + abortPayMerchant, computePayMerchantTransactionState, expectProposalDownload, extractContractData, processPurchasePay, } from "./pay-merchant.js"; -import { processPeerPullCredit } from "./pay-peer.js"; -import { processRefreshGroup } from "./refresh.js"; +import { computePeerPullCreditTransactionState, computePeerPullDebitTransactionState, computePeerPushCreditTransactionState, computePeerPushDebitTransactionState, processPeerPullCredit } from "./pay-peer.js"; +import { computeRefreshTransactionState, processRefreshGroup } from "./refresh.js"; import { computeTipTransactionStatus, processTip } from "./tip.js"; import { abortWithdrawalTransaction, @@ -445,7 +445,7 @@ function buildTransactionForPushPaymentDebit( ): Transaction { return { type: TransactionType.PeerPushDebit, - txState: mkTxStateUnknown(), + txState: computePeerPushDebitTransactionState(pi), amountEffective: pi.totalCost, amountRaw: pi.amount, exchangeBaseUrl: pi.exchangeBaseUrl, @@ -455,10 +455,10 @@ function buildTransactionForPushPaymentDebit( }, frozen: false, extendedStatus: - pi.status != PeerPushPaymentInitiationStatus.PurseCreated + pi.status != PeerPushPaymentInitiationStatus.Done ? ExtendedStatus.Pending : ExtendedStatus.Done, - pending: pi.status != PeerPushPaymentInitiationStatus.PurseCreated, + pending: pi.status != PeerPushPaymentInitiationStatus.Done, timestamp: pi.timestampCreated, talerUri: constructPayPushUri({ exchangeBaseUrl: pi.exchangeBaseUrl, @@ -478,7 +478,7 @@ function buildTransactionForPullPaymentDebit( ): Transaction { return { type: TransactionType.PeerPullDebit, - txState: mkTxStateUnknown(), + txState: computePeerPullDebitTransactionState(pi), amountEffective: pi.coinSel?.totalCost ? pi.coinSel?.totalCost : Amounts.stringify(pi.contractTerms.amount), @@ -528,7 +528,7 @@ function buildTransactionForPeerPullCredit( }); return { type: TransactionType.PeerPullCredit, - txState: mkTxStateUnknown(), + txState: computePeerPullCreditTransactionState(pullCredit), amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.instructedAmount), exchangeBaseUrl: wsr.exchangeBaseUrl, @@ -563,7 +563,7 @@ function buildTransactionForPeerPullCredit( return { type: TransactionType.PeerPullCredit, - txState: mkTxStateUnknown(), + txState: computePeerPullCreditTransactionState(pullCredit), amountEffective: Amounts.stringify(pullCredit.estimatedAmountEffective), amountRaw: Amounts.stringify(peerContractTerms.amount), exchangeBaseUrl: pullCredit.exchangeBaseUrl, @@ -602,7 +602,7 @@ function buildTransactionForPeerPushCredit( return { type: TransactionType.PeerPushCredit, - txState: mkTxStateUnknown(), + txState: computePeerPushCreditTransactionState(pushInc), amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.instructedAmount), exchangeBaseUrl: wsr.exchangeBaseUrl, @@ -626,7 +626,7 @@ function buildTransactionForPeerPushCredit( return { type: TransactionType.PeerPushCredit, - txState: mkTxStateUnknown(), + txState: computePeerPushCreditTransactionState(pushInc), // FIXME: This is wrong, needs to consider fees! amountEffective: Amounts.stringify(peerContractTerms.amount), amountRaw: Amounts.stringify(peerContractTerms.amount), @@ -666,7 +666,7 @@ function buildTransactionForBankIntegratedWithdraw( bankConfirmationUrl: wgRecord.wgInfo.bankInfo.confirmUrl, reserveIsReady: wgRecord.status === WithdrawalGroupStatus.Finished || - wgRecord.status === WithdrawalGroupStatus.Ready, + wgRecord.status === WithdrawalGroupStatus.PendingReady, }, exchangeBaseUrl: wgRecord.exchangeBaseUrl, extendedStatus: wgRecord.timestampFinish @@ -713,7 +713,7 @@ function buildTransactionForManualWithdraw( exchangePaytoUris, reserveIsReady: withdrawalGroup.status === WithdrawalGroupStatus.Finished || - withdrawalGroup.status === WithdrawalGroupStatus.Ready, + withdrawalGroup.status === WithdrawalGroupStatus.PendingReady, }, exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl, extendedStatus: withdrawalGroup.timestampFinish @@ -753,7 +753,7 @@ function buildTransactionForRefresh( ).amount; return { type: TransactionType.Refresh, - txState: mkTxStateUnknown(), + txState: computeRefreshTransactionState(refreshGroupRecord), refreshReason: refreshGroupRecord.reason, amountEffective: Amounts.stringify( Amounts.zeroOfCurrency(refreshGroupRecord.currency), @@ -1538,7 +1538,7 @@ export async function retryTransaction( switch (parsedTx.tag) { case TransactionType.PeerPullCredit: { const taskId = constructTaskIdentifier({ - tag: PendingTaskType.PeerPullInitiation, + tag: PendingTaskType.PeerPullCredit, pursePub: parsedTx.pursePub, }); await resetOperationTimeout(ws, taskId); @@ -1866,7 +1866,7 @@ export async function abortTransaction( switch (txId.tag) { case TransactionType.Payment: { - await abortPay(ws, txId.proposalId); + await abortPayMerchant(ws, txId.proposalId); break; } case TransactionType.Withdrawal: { diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index d1816de03..d0c4d453f 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -135,6 +135,7 @@ import { notifyTransition, stopLongpolling, } from "./transactions.js"; +import { assertUnreachable } from "../util/assertUnreachable.js"; /** * Logger for this file. @@ -160,25 +161,25 @@ export async function suspendWithdrawalTransaction( } let newStatus: WithdrawalGroupStatus | undefined = undefined; switch (wg.status) { - case WithdrawalGroupStatus.Ready: + case WithdrawalGroupStatus.PendingReady: newStatus = WithdrawalGroupStatus.SuspendedReady; break; case WithdrawalGroupStatus.AbortingBank: newStatus = WithdrawalGroupStatus.SuspendedAbortingBank; break; - case WithdrawalGroupStatus.WaitConfirmBank: + case WithdrawalGroupStatus.PendingWaitConfirmBank: newStatus = WithdrawalGroupStatus.SuspendedWaitConfirmBank; break; - case WithdrawalGroupStatus.RegisteringBank: + case WithdrawalGroupStatus.PendingRegisteringBank: newStatus = WithdrawalGroupStatus.SuspendedRegisteringBank; break; - case WithdrawalGroupStatus.QueryingStatus: - newStatus = WithdrawalGroupStatus.QueryingStatus; + case WithdrawalGroupStatus.PendingQueryingStatus: + newStatus = WithdrawalGroupStatus.SuspendedQueryingStatus; break; - case WithdrawalGroupStatus.Kyc: + case WithdrawalGroupStatus.PendingKyc: newStatus = WithdrawalGroupStatus.SuspendedKyc; break; - case WithdrawalGroupStatus.Aml: + case WithdrawalGroupStatus.PendingAml: newStatus = WithdrawalGroupStatus.SuspendedAml; break; default: @@ -221,25 +222,25 @@ export async function resumeWithdrawalTransaction( let newStatus: WithdrawalGroupStatus | undefined = undefined; switch (wg.status) { case WithdrawalGroupStatus.SuspendedReady: - newStatus = WithdrawalGroupStatus.Ready; + newStatus = WithdrawalGroupStatus.PendingReady; break; case WithdrawalGroupStatus.SuspendedAbortingBank: newStatus = WithdrawalGroupStatus.AbortingBank; break; case WithdrawalGroupStatus.SuspendedWaitConfirmBank: - newStatus = WithdrawalGroupStatus.WaitConfirmBank; + newStatus = WithdrawalGroupStatus.PendingWaitConfirmBank; break; case WithdrawalGroupStatus.SuspendedQueryingStatus: - newStatus = WithdrawalGroupStatus.QueryingStatus; + newStatus = WithdrawalGroupStatus.PendingQueryingStatus; break; case WithdrawalGroupStatus.SuspendedRegisteringBank: - newStatus = WithdrawalGroupStatus.RegisteringBank; + newStatus = WithdrawalGroupStatus.PendingRegisteringBank; break; case WithdrawalGroupStatus.SuspendedAml: - newStatus = WithdrawalGroupStatus.Aml; + newStatus = WithdrawalGroupStatus.PendingAml; break; case WithdrawalGroupStatus.SuspendedKyc: - newStatus = WithdrawalGroupStatus.Kyc; + newStatus = WithdrawalGroupStatus.PendingKyc; break; default: logger.warn( @@ -289,21 +290,21 @@ export async function abortWithdrawalTransaction( } let newStatus: WithdrawalGroupStatus | undefined = undefined; switch (wg.status) { - case WithdrawalGroupStatus.WaitConfirmBank: - case WithdrawalGroupStatus.RegisteringBank: + case WithdrawalGroupStatus.PendingWaitConfirmBank: + case WithdrawalGroupStatus.PendingRegisteringBank: case WithdrawalGroupStatus.AbortingBank: newStatus = WithdrawalGroupStatus.AbortingBank; break; - case WithdrawalGroupStatus.Aml: + case WithdrawalGroupStatus.PendingAml: newStatus = WithdrawalGroupStatus.SuspendedAml; break; - case WithdrawalGroupStatus.Kyc: + case WithdrawalGroupStatus.PendingKyc: newStatus = WithdrawalGroupStatus.SuspendedKyc; break; - case WithdrawalGroupStatus.QueryingStatus: + case WithdrawalGroupStatus.PendingQueryingStatus: newStatus = WithdrawalGroupStatus.SuspendedQueryingStatus; break; - case WithdrawalGroupStatus.Ready: + case WithdrawalGroupStatus.PendingReady: newStatus = WithdrawalGroupStatus.SuspendedReady; break; case WithdrawalGroupStatus.SuspendedAbortingBank: @@ -316,9 +317,13 @@ export async function abortWithdrawalTransaction( case WithdrawalGroupStatus.SuspendedRegisteringBank: case WithdrawalGroupStatus.SuspendedWaitConfirmBank: case WithdrawalGroupStatus.Finished: - case WithdrawalGroupStatus.BankAborted: + case WithdrawalGroupStatus.FailedBankAborted: + case WithdrawalGroupStatus.AbortedExchange: + case WithdrawalGroupStatus.FailedAbortingBank: // Not allowed break; + default: + assertUnreachable(wg.status); } if (newStatus != null) { const oldTxState = computeWithdrawalTransactionStatus(wg); @@ -385,7 +390,7 @@ export function computeWithdrawalTransactionStatus( wgRecord: WithdrawalGroupRecord, ): TransactionState { switch (wgRecord.status) { - case WithdrawalGroupStatus.BankAborted: + case WithdrawalGroupStatus.FailedBankAborted: return { major: TransactionMajorState.Aborted, }; @@ -393,22 +398,22 @@ export function computeWithdrawalTransactionStatus( return { major: TransactionMajorState.Done, }; - case WithdrawalGroupStatus.RegisteringBank: + case WithdrawalGroupStatus.PendingRegisteringBank: return { major: TransactionMajorState.Pending, minor: TransactionMinorState.BankRegisterReserve, }; - case WithdrawalGroupStatus.Ready: + case WithdrawalGroupStatus.PendingReady: return { major: TransactionMajorState.Pending, minor: TransactionMinorState.WithdrawCoins, }; - case WithdrawalGroupStatus.QueryingStatus: + case WithdrawalGroupStatus.PendingQueryingStatus: return { major: TransactionMajorState.Pending, minor: TransactionMinorState.ExchangeWaitReserve, }; - case WithdrawalGroupStatus.WaitConfirmBank: + case WithdrawalGroupStatus.PendingWaitConfirmBank: return { major: TransactionMajorState.Pending, minor: TransactionMinorState.BankConfirmTransfer, @@ -444,13 +449,13 @@ export function computeWithdrawalTransactionStatus( minor: TransactionMinorState.WithdrawCoins, }; } - case WithdrawalGroupStatus.Aml: { + case WithdrawalGroupStatus.PendingAml: { return { major: TransactionMajorState.Pending, minor: TransactionMinorState.AmlRequired, }; } - case WithdrawalGroupStatus.Kyc: { + case WithdrawalGroupStatus.PendingKyc: { return { major: TransactionMajorState.Pending, minor: TransactionMinorState.KycRequired, @@ -473,6 +478,11 @@ export function computeWithdrawalTransactionStatus( major: TransactionMajorState.Failed, minor: TransactionMinorState.AbortingBank, }; + case WithdrawalGroupStatus.AbortedExchange: + return { + major: TransactionMajorState.Aborted, + minor: TransactionMinorState.Exchange, + } } } @@ -1122,7 +1132,7 @@ async function queryReserve( withdrawalGroupId, }); checkDbInvariant(!!withdrawalGroup); - if (withdrawalGroup.status !== WithdrawalGroupStatus.QueryingStatus) { + if (withdrawalGroup.status !== WithdrawalGroupStatus.PendingQueryingStatus) { return { ready: true }; } const reservePub = withdrawalGroup.reservePub; @@ -1135,7 +1145,7 @@ async function queryReserve( logger.info(`querying reserve status via ${reserveUrl.href}`); - const resp = await ws.http.get(reserveUrl.href, { + const resp = await ws.http.fetch(reserveUrl.href, { timeout: getReserveRequestTimeout(withdrawalGroup), cancellationToken, }); @@ -1177,7 +1187,7 @@ async function queryReserve( return undefined; } const txStateOld = computeWithdrawalTransactionStatus(wg); - wg.status = WithdrawalGroupStatus.Ready; + wg.status = WithdrawalGroupStatus.PendingReady; const txStateNew = computeWithdrawalTransactionStatus(wg); wg.reserveBalanceAmount = Amounts.stringify(result.response.balance); await tx.withdrawalGroups.put(wg); @@ -1250,12 +1260,12 @@ export async function processWithdrawalGroup( } switch (withdrawalGroup.status) { - case WithdrawalGroupStatus.RegisteringBank: + case WithdrawalGroupStatus.PendingRegisteringBank: await processReserveBankStatus(ws, withdrawalGroupId); return await processWithdrawalGroup(ws, withdrawalGroupId, { forceNow: true, }); - case WithdrawalGroupStatus.QueryingStatus: { + case WithdrawalGroupStatus.PendingQueryingStatus: { runLongpollAsync(ws, retryTag, (ct) => { return queryReserve(ws, withdrawalGroupId, ct); }); @@ -1266,7 +1276,7 @@ export async function processWithdrawalGroup( type: OperationAttemptResultType.Longpoll, }; } - case WithdrawalGroupStatus.WaitConfirmBank: { + case WithdrawalGroupStatus.PendingWaitConfirmBank: { const res = await processReserveBankStatus(ws, withdrawalGroupId); switch (res.status) { case BankStatusResultCode.Aborted: @@ -1284,7 +1294,7 @@ export async function processWithdrawalGroup( } break; } - case WithdrawalGroupStatus.BankAborted: { + case WithdrawalGroupStatus.FailedBankAborted: { // FIXME return { type: OperationAttemptResultType.Pending, @@ -1294,7 +1304,7 @@ export async function processWithdrawalGroup( case WithdrawalGroupStatus.Finished: // We can try to withdraw, nothing needs to be done with the reserve. break; - case WithdrawalGroupStatus.Ready: + case WithdrawalGroupStatus.PendingReady: // Continue with the actual withdrawal! break; default: @@ -1847,8 +1857,8 @@ async function registerReserveWithBank( withdrawalGroupId, }); switch (withdrawalGroup?.status) { - case WithdrawalGroupStatus.WaitConfirmBank: - case WithdrawalGroupStatus.RegisteringBank: + case WithdrawalGroupStatus.PendingWaitConfirmBank: + case WithdrawalGroupStatus.PendingRegisteringBank: break; default: return; @@ -1885,8 +1895,8 @@ async function registerReserveWithBank( return undefined; } switch (r.status) { - case WithdrawalGroupStatus.RegisteringBank: - case WithdrawalGroupStatus.WaitConfirmBank: + case WithdrawalGroupStatus.PendingRegisteringBank: + case WithdrawalGroupStatus.PendingWaitConfirmBank: break; default: return; @@ -1898,7 +1908,7 @@ async function registerReserveWithBank( AbsoluteTime.now(), ); const oldTxState = computeWithdrawalTransactionStatus(r); - r.status = WithdrawalGroupStatus.WaitConfirmBank; + r.status = WithdrawalGroupStatus.PendingWaitConfirmBank; const newTxState = computeWithdrawalTransactionStatus(r); await tx.withdrawalGroups.put(r); return { @@ -1928,8 +1938,8 @@ async function processReserveBankStatus( withdrawalGroupId, }); switch (withdrawalGroup?.status) { - case WithdrawalGroupStatus.WaitConfirmBank: - case WithdrawalGroupStatus.RegisteringBank: + case WithdrawalGroupStatus.PendingWaitConfirmBank: + case WithdrawalGroupStatus.PendingRegisteringBank: break; default: return { @@ -1969,8 +1979,8 @@ async function processReserveBankStatus( return; } switch (r.status) { - case WithdrawalGroupStatus.RegisteringBank: - case WithdrawalGroupStatus.WaitConfirmBank: + case WithdrawalGroupStatus.PendingRegisteringBank: + case WithdrawalGroupStatus.PendingWaitConfirmBank: break; default: return; @@ -1981,7 +1991,7 @@ async function processReserveBankStatus( const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); const oldTxState = computeWithdrawalTransactionStatus(r); r.wgInfo.bankInfo.timestampBankConfirmed = now; - r.status = WithdrawalGroupStatus.BankAborted; + r.status = WithdrawalGroupStatus.FailedBankAborted; const newTxState = computeWithdrawalTransactionStatus(r); await tx.withdrawalGroups.put(r); return { @@ -2002,7 +2012,7 @@ async function processReserveBankStatus( } // FIXME: Why do we do this?! - if (withdrawalGroup.status === WithdrawalGroupStatus.RegisteringBank) { + if (withdrawalGroup.status === WithdrawalGroupStatus.PendingRegisteringBank) { await registerReserveWithBank(ws, withdrawalGroupId); return await processReserveBankStatus(ws, withdrawalGroupId); } @@ -2016,8 +2026,8 @@ async function processReserveBankStatus( } // Re-check reserve status within transaction switch (r.status) { - case WithdrawalGroupStatus.RegisteringBank: - case WithdrawalGroupStatus.WaitConfirmBank: + case WithdrawalGroupStatus.PendingRegisteringBank: + case WithdrawalGroupStatus.PendingWaitConfirmBank: break; default: return undefined; @@ -2030,7 +2040,7 @@ async function processReserveBankStatus( logger.info("withdrawal: transfer confirmed by bank."); const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); r.wgInfo.bankInfo.timestampBankConfirmed = now; - r.status = WithdrawalGroupStatus.QueryingStatus; + r.status = WithdrawalGroupStatus.PendingQueryingStatus; // FIXME: Notification is deprecated with DD37. ws.notify({ type: NotificationType.WithdrawalGroupBankConfirmed, @@ -2276,7 +2286,7 @@ export async function acceptWithdrawalFromUri( }, restrictAge: req.restrictAge, forcedDenomSel: req.forcedDenomSel, - reserveStatus: WithdrawalGroupStatus.RegisteringBank, + reserveStatus: WithdrawalGroupStatus.PendingRegisteringBank, }); const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; @@ -2291,19 +2301,14 @@ export async function acceptWithdrawalFromUri( const processedWithdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, { withdrawalGroupId, }); - if (processedWithdrawalGroup?.status === WithdrawalGroupStatus.BankAborted) { + if (processedWithdrawalGroup?.status === WithdrawalGroupStatus.FailedBankAborted) { throw TalerError.fromDetail( TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, {}, ); } - // Start withdrawal in the background - processWithdrawalGroup(ws, withdrawalGroupId, { - forceNow: true, - }).catch((err) => { - logger.error("Processing withdrawal (after creation) failed:", err); - }); + ws.workAvailable.trigger(); return { reservePub: withdrawalGroup.reservePub, @@ -2337,7 +2342,7 @@ export async function createManualWithdrawal( exchangeBaseUrl: req.exchangeBaseUrl, forcedDenomSel: req.forcedDenomSel, restrictAge: req.restrictAge, - reserveStatus: WithdrawalGroupStatus.QueryingStatus, + reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus, }); const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; @@ -2357,18 +2362,7 @@ export async function createManualWithdrawal( return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId); }); - // Start withdrawal in the background (do not await!) - // FIXME: We could also interrupt the task look if it is waiting and - // rely on retry handling to re-process the withdrawal group. - runOperationWithErrorReporting( - ws, - TaskIdentifiers.forWithdrawal(withdrawalGroup), - async () => { - return await processWithdrawalGroup(ws, withdrawalGroupId, { - forceNow: true, - }); - }, - ); + ws.workAvailable.trigger(); return { reservePub: withdrawalGroup.reservePub, |