taler-typescript-core

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

commit 4887e9f19e7e1c4ad988c865980b6de3bff39c23
parent ca516cd0906f28e11e88281398786738ab86530a
Author: Florian Dold <florian@dold.me>
Date:   Tue, 24 Sep 2024 13:28:13 +0200

wallet-core: persist fail and abort reason

Diffstat:
Mpackages/taler-util/src/taler-error-codes.ts | 24++++++++++++++++++++++++
Mpackages/taler-wallet-core/src/common.ts | 4++--
Mpackages/taler-wallet-core/src/db.ts | 21+++++++++++++++++++++
Mpackages/taler-wallet-core/src/deposits.ts | 38++++++++++++++++++++------------------
Mpackages/taler-wallet-core/src/pay-merchant.ts | 3++-
Mpackages/taler-wallet-core/src/pay-peer-pull-credit.ts | 12++++++++++--
Mpackages/taler-wallet-core/src/pay-peer-pull-debit.ts | 19+++++++++++++++----
Mpackages/taler-wallet-core/src/pay-peer-push-credit.ts | 33+++++++++++++++++----------------
Mpackages/taler-wallet-core/src/pay-peer-push-debit.ts | 35+++++++++++++++++++----------------
Mpackages/taler-wallet-core/src/refresh.ts | 4+++-
Mpackages/taler-wallet-core/src/transactions.ts | 13+++++++++++--
Mpackages/taler-wallet-core/src/withdraw.ts | 9+++++++--
12 files changed, 151 insertions(+), 64 deletions(-)

diff --git a/packages/taler-util/src/taler-error-codes.ts b/packages/taler-util/src/taler-error-codes.ts @@ -4361,6 +4361,30 @@ export enum TalerErrorCode { /** + * A peer-pull-debit transaction was aborted because the exchange reported the purse as gone. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_PEER_PULL_DEBIT_PURSE_GONE = 7041, + + + /** + * A transaction was aborted on explicit request by the user. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_TRANSACTION_ABORTED_BY_USER = 7042, + + + /** + * A transaction was abandoned on explicit request by the user. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_TRANSACTION_ABANDONED_BY_USER = 7043, + + + /** * We encountered a timeout with our payment backend. * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504). * (A value of 0 indicates that the error is generated client-side). diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts @@ -799,10 +799,10 @@ export const TransitionResult = { export interface TransactionContext { get taskId(): TaskIdStr | undefined; get transactionId(): TransactionIdStr; - abortTransaction(): Promise<void>; + abortTransaction(reason?: TalerErrorDetail): Promise<void>; suspendTransaction(): Promise<void>; resumeTransaction(): Promise<void>; - failTransaction(): Promise<void>; + failTransaction(reason?: TalerErrorDetail): Promise<void>; deleteTransaction(): Promise<void>; lookupFullTransaction( tx: WalletDbAllStoresReadOnlyTransaction, diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts @@ -1070,6 +1070,8 @@ export interface RefreshGroupRecord { timestampCreated: DbPreciseTimestamp; + failReason?: TalerErrorDetail; + /** * Timestamp when the refresh session finished. */ @@ -1288,6 +1290,7 @@ export interface PurchaseRecord { purchaseStatus: PurchaseStatus; abortReason?: TalerErrorDetail; + failReason?: TalerErrorDetail; /** * Private key for the nonce. @@ -1584,6 +1587,9 @@ export interface WithdrawalGroupRecord { * FIXME: Should this not also include a timestamp for more logical merging? */ denomSelUid: string; + + abortReason?: TalerErrorDetail; + failReason?: TalerErrorDetail; } export interface BankWithdrawUriRecord { @@ -1835,6 +1841,9 @@ export interface DepositGroupRecord { */ abortRefreshGroupId?: string; + abortReason?: TalerErrorDetail; + failReason?: TalerErrorDetail; + kycInfo?: DepositKycInfo; // FIXME: Do we need this and should it be in this object store? @@ -1956,6 +1965,9 @@ export interface PeerPushDebitRecord { abortRefreshGroupId?: string; + abortReason?: TalerErrorDetail; + failReason?: TalerErrorDetail; + /** * Status of the peer push payment initiation. */ @@ -2050,6 +2062,9 @@ export interface PeerPullCreditRecord { kycAccessToken?: string; + abortReason?: TalerErrorDetail; + failReason?: TalerErrorDetail; + withdrawalGroupId: string | undefined; } @@ -2111,6 +2126,9 @@ export interface PeerPushPaymentIncomingRecord { */ status: PeerPushCreditStatus; + abortReason?: TalerErrorDetail; + failReason?: TalerErrorDetail; + /** * Associated withdrawal group. */ @@ -2187,6 +2205,9 @@ export interface PeerPullPaymentIncomingRecord { abortRefreshGroupId?: string; + abortReason?: TalerErrorDetail; + failReason?: TalerErrorDetail; + coinSel?: PeerPullPaymentCoinSelection; } diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts @@ -47,6 +47,7 @@ import { SelectedProspectiveCoin, TalerError, TalerErrorCode, + TalerErrorDetail, TalerPreciseTimestamp, TalerProtocolTimestamp, TrackTransaction, @@ -263,6 +264,8 @@ export class DepositTransactionContext implements TransactionContext { depositGroupId: dg.depositGroupId, trackingState, deposited, + abortReason: dg.abortReason, + failReason: dg.failReason, kycAuthTransferInfo, kycPaytoHash: dg.kycInfo?.paytoHash, kycAccessToken: dg.kycInfo?.accessToken, @@ -380,7 +383,7 @@ export class DepositTransactionContext implements TransactionContext { notifyTransition(wex, transactionId, transitionInfo); } - async abortTransaction(): Promise<void> { + async abortTransaction(reason?: TalerErrorDetail): Promise<void> { const { wex, depositGroupId, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( { storeNames: ["depositGroups", "transactionsMeta"] }, @@ -401,6 +404,7 @@ export class DepositTransactionContext implements TransactionContext { case DepositOperationStatus.PendingDeposit: case DepositOperationStatus.SuspendedDeposit: { dg.operationStatus = DepositOperationStatus.Aborting; + dg.abortReason = reason; await tx.depositGroups.put(dg); await this.updateTransactionMeta(tx); return { @@ -497,7 +501,7 @@ export class DepositTransactionContext implements TransactionContext { wex.taskScheduler.startShepherdTask(retryTag); } - async failTransaction(): Promise<void> { + async failTransaction(reason?: TalerErrorDetail): Promise<void> { const { wex, depositGroupId, transactionId, taskId } = this; const transitionInfo = await wex.db.runReadWriteTx( { storeNames: ["depositGroups", "transactionsMeta"] }, @@ -510,28 +514,19 @@ export class DepositTransactionContext implements TransactionContext { return undefined; } const oldState = computeDepositTransactionStatus(dg); + let newState: DepositOperationStatus; switch (dg.operationStatus) { case DepositOperationStatus.PendingAggregateKyc: case DepositOperationStatus.SuspendedAggregateKyc: case DepositOperationStatus.SuspendedAborting: case DepositOperationStatus.Aborting: { - dg.operationStatus = DepositOperationStatus.FailedDeposit; - await tx.depositGroups.put(dg); - await this.updateTransactionMeta(tx); - return { - oldTxState: oldState, - newTxState: computeDepositTransactionStatus(dg), - }; + newState = DepositOperationStatus.FailedDeposit; + break; } case DepositOperationStatus.PendingTrack: case DepositOperationStatus.SuspendedTrack: { - dg.operationStatus = DepositOperationStatus.FailedTrack; - await tx.depositGroups.put(dg); - await this.updateTransactionMeta(tx); - return { - oldTxState: oldState, - newTxState: computeDepositTransactionStatus(dg), - }; + newState = DepositOperationStatus.FailedTrack; + break; } case DepositOperationStatus.AbortedDeposit: case DepositOperationStatus.FailedDeposit: @@ -543,11 +538,18 @@ export class DepositTransactionContext implements TransactionContext { case DepositOperationStatus.SuspendedDeposit: case DepositOperationStatus.SuspendedDepositKyc: case DepositOperationStatus.SuspendedDepositKycAuth: - break; + throw Error("failing not supported in current state"); default: assertUnreachable(dg.operationStatus); } - return undefined; + dg.operationStatus = newState; + dg.failReason = reason; + await tx.depositGroups.put(dg); + await this.updateTransactionMeta(tx); + return { + oldTxState: oldState, + newTxState: computeDepositTransactionStatus(dg), + }; }, ); wex.taskScheduler.stopShepherdTask(taskId); diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -529,7 +529,7 @@ export class PayMerchantTransactionContext implements TransactionContext { wex.taskScheduler.startShepherdTask(this.taskId); } - async failTransaction(): Promise<void> { + async failTransaction(reason?: TalerErrorDetail): Promise<void> { const { wex, proposalId, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( { @@ -557,6 +557,7 @@ export class PayMerchantTransactionContext implements TransactionContext { } if (newState) { purchase.purchaseStatus = newState; + purchase.failReason = reason; await tx.purchases.put(purchase); } await this.updateTransactionMeta(tx); diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -32,6 +32,7 @@ import { NotificationType, PeerContractTerms, TalerErrorCode, + TalerErrorDetail, TalerPreciseTimestamp, TalerProtocolTimestamp, TalerUriAction, @@ -318,6 +319,8 @@ export class PeerPullCreditTransactionContext implements TransactionContext { tag: TransactionType.PeerPullCredit, pursePub: pullCredit.pursePub, }), + abortReason: pullCredit.abortReason, + failReason: pullCredit.failReason, // FIXME: Is this the KYC URL of the withdrawal group?! kycUrl: kycUrl, ...(wsrOrt?.lastError @@ -357,6 +360,8 @@ export class PeerPullCreditTransactionContext implements TransactionContext { kycUrl, kycAccessToken: pullCredit.kycAccessToken, kycPaytoHash: pullCredit.kycPaytoHash, + abortReason: pullCredit.abortReason, + failReason: pullCredit.failReason, ...(pullCreditOrt?.lastError ? { error: pullCreditOrt.lastError } : {}), }; } @@ -468,7 +473,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { notifyTransition(wex, transactionId, transitionInfo); } - async failTransaction(): Promise<void> { + async failTransaction(reason?: TalerErrorDetail): Promise<void> { const { wex, pursePub, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( { storeNames: ["peerPullCredit", "transactionsMeta"] }, @@ -508,6 +513,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { const oldTxState = computePeerPullCreditTransactionState(pullCreditRec); pullCreditRec.status = newStatus; + pullCreditRec.failReason = reason; const newTxState = computePeerPullCreditTransactionState(pullCreditRec); await tx.peerPullCredit.put(pullCreditRec); @@ -592,7 +598,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { wex.taskScheduler.startShepherdTask(retryTag); } - async abortTransaction(): Promise<void> { + async abortTransaction(reason?: TalerErrorDetail): Promise<void> { const { wex, pursePub, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( { storeNames: ["peerPullCredit", "transactionsMeta"] }, @@ -611,10 +617,12 @@ export class PeerPullCreditTransactionContext implements TransactionContext { case PeerPullPaymentCreditStatus.PendingCreatePurse: case PeerPullPaymentCreditStatus.PendingMergeKycRequired: newStatus = PeerPullPaymentCreditStatus.AbortingDeletePurse; + pullCreditRec.abortReason = reason; break; case PeerPullPaymentCreditStatus.PendingWithdrawing: throw Error("can't abort anymore"); case PeerPullPaymentCreditStatus.PendingReady: + pullCreditRec.abortReason = reason; newStatus = PeerPullPaymentCreditStatus.AbortingDeletePurse; break; case PeerPullPaymentCreditStatus.Done: diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -41,6 +41,7 @@ import { SelectedProspectiveCoin, TalerError, TalerErrorCode, + TalerErrorDetail, TalerPreciseTimestamp, TalerProtocolViolationError, Transaction, @@ -61,6 +62,7 @@ import { encodeCrock, getRandomBytes, j2s, + makeTalerErrorDetail, parsePayPullUri, } from "@gnu-taler/taler-util"; import { @@ -89,6 +91,7 @@ import { timestampPreciseFromDb, timestampPreciseToDb, } from "./db.js"; +import { getScopeForAllExchanges } from "./exchanges.js"; import { codecForExchangePurseStatus, getTotalPeerPaymentCost, @@ -103,7 +106,6 @@ import { parseTransactionIdentifier, } from "./transactions.js"; import { WalletExecutionContext } from "./wallet.js"; -import { getScopeForAllExchanges } from "./exchanges.js"; const logger = new Logger("pay-peer-pull-debit.ts"); @@ -180,6 +182,8 @@ export class PeerPullDebitTransactionContext implements TransactionContext { expiration: contractTerms.purse_expiration, summary: contractTerms.summary, }, + abortReason: pi.abortReason, + failReason: pi.failReason, timestamp: timestampPreciseFromDb(pi.timestampCreated), transactionId: constructTransactionIdentifier({ tag: TransactionType.PeerPullDebit, @@ -282,7 +286,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { this.wex.taskScheduler.startShepherdTask(this.taskId); } - async failTransaction(): Promise<void> { + async failTransaction(reason?: TalerErrorDetail): Promise<void> { const ctx = this; await ctx.transition(async (pi) => { switch (pi.status) { @@ -292,6 +296,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { case PeerPullDebitRecordStatus.SuspendedAbortingRefresh: // FIXME: Should we also abort the corresponding refresh session?! pi.status = PeerPullDebitRecordStatus.Failed; + pi.failReason = reason; return TransitionResultType.Transition; default: return TransitionResultType.Stay; @@ -300,7 +305,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { this.wex.taskScheduler.stopShepherdTask(this.taskId); } - async abortTransaction(): Promise<void> { + async abortTransaction(reason?: TalerErrorDetail): Promise<void> { const ctx = this; await ctx.transitionExtra( { @@ -347,6 +352,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { pi.status = PeerPullDebitRecordStatus.AbortingRefresh; pi.abortRefreshGroupId = refresh.refreshGroupId; + pi.abortReason = reason; return TransitionResultType.Transition; }, ); @@ -657,7 +663,12 @@ async function processPeerPullDebitPendingDeposit( continue; } case HttpStatusCode.Gone: { - await ctx.abortTransaction(); + await ctx.abortTransaction( + makeTalerErrorDetail( + TalerErrorCode.WALLET_PEER_PULL_DEBIT_PURSE_GONE, + {}, + ), + ); return TaskRunResult.backoff(); } case HttpStatusCode.Conflict: { diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -29,6 +29,7 @@ import { PeerContractTerms, PreparePeerPushCreditRequest, PreparePeerPushCreditResponse, + TalerErrorDetail, TalerPreciseTimestamp, Transaction, TransactionAction, @@ -299,6 +300,8 @@ export class PeerPushCreditTransactionContext implements TransactionContext { tag: TransactionType.PeerPushCredit, peerPushCreditId: pushInc.peerPushCreditId, }), + abortReason: pushInc.abortReason, + failReason: pushInc.failReason, kycUrl, kycPaytoHash: wg.kycPaytoHash, ...(wgRetryRecord?.lastError ? { error: wgRetryRecord.lastError } : {}), @@ -328,6 +331,8 @@ export class PeerPushCreditTransactionContext implements TransactionContext { tag: TransactionType.PeerPushCredit, peerPushCreditId: pushInc.peerPushCreditId, }), + abortReason: pushInc.abortReason, + failReason: pushInc.failReason, ...(pushRetryRecord?.lastError ? { error: pushRetryRecord.lastError } : {}), @@ -529,7 +534,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { wex.taskScheduler.startShepherdTask(retryTag); } - async failTransaction(): Promise<void> { + async failTransaction(reason?: TalerErrorDetail): Promise<void> { const { wex, peerPushCreditId, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( { storeNames: ["peerPushCredit", "transactionsMeta"] }, @@ -539,7 +544,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { logger.warn(`peer push credit ${peerPushCreditId} not found`); return; } - let newStatus: PeerPushCreditStatus | undefined = undefined; + let newStatus: PeerPushCreditStatus; switch (pushCreditRec.status) { case PeerPushCreditStatus.Done: case PeerPushCreditStatus.Aborted: @@ -562,20 +567,16 @@ export class PeerPushCreditTransactionContext implements TransactionContext { default: assertUnreachable(pushCreditRec.status); } - if (newStatus != null) { - const oldTxState = - computePeerPushCreditTransactionState(pushCreditRec); - pushCreditRec.status = newStatus; - const newTxState = - computePeerPushCreditTransactionState(pushCreditRec); - await tx.peerPushCredit.put(pushCreditRec); - await this.updateTransactionMeta(tx); - return { - oldTxState, - newTxState, - }; - } - return undefined; + const oldTxState = computePeerPushCreditTransactionState(pushCreditRec); + pushCreditRec.status = newStatus; + pushCreditRec.failReason = reason; + const newTxState = computePeerPushCreditTransactionState(pushCreditRec); + await tx.peerPushCredit.put(pushCreditRec); + await this.updateTransactionMeta(tx); + return { + oldTxState, + newTxState, + }; }, ); wex.taskScheduler.stopShepherdTask(retryTag); diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -30,6 +30,7 @@ import { SelectedProspectiveCoin, TalerError, TalerErrorCode, + TalerErrorDetail, TalerPreciseTimestamp, TalerProtocolTimestamp, TalerProtocolViolationError, @@ -185,6 +186,8 @@ export class PeerPushDebitTransactionContext implements TransactionContext { tag: TransactionType.PeerPushDebit, pursePub: pushDebitRec.pursePub, }), + failReason: pushDebitRec.failReason, + abortReason: pushDebitRec.abortReason, ...(retryRec?.lastError ? { error: retryRec.lastError } : {}), }; } @@ -263,7 +266,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { notifyTransition(wex, transactionId, transitionInfo); } - async abortTransaction(): Promise<void> { + async abortTransaction(reason?: TalerErrorDetail): Promise<void> { const { wex, pursePub, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( { storeNames: ["peerPushDebit", "transactionsMeta"] }, @@ -277,11 +280,13 @@ export class PeerPushDebitTransactionContext implements TransactionContext { switch (pushDebitRec.status) { case PeerPushDebitStatus.PendingReady: case PeerPushDebitStatus.SuspendedReady: + pushDebitRec.abortReason = reason; newStatus = PeerPushDebitStatus.AbortingDeletePurse; break; case PeerPushDebitStatus.SuspendedCreatePurse: case PeerPushDebitStatus.PendingCreatePurse: // Network request might already be in-flight! + pushDebitRec.abortReason = reason; newStatus = PeerPushDebitStatus.AbortingDeletePurse; break; case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted: @@ -377,7 +382,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { notifyTransition(wex, transactionId, transitionInfo); } - async failTransaction(): Promise<void> { + async failTransaction(reason?: TalerErrorDetail): Promise<void> { const { wex, pursePub, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( { storeNames: ["peerPushDebit", "transactionsMeta"] }, @@ -387,7 +392,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { logger.warn(`peer push debit ${pursePub} not found`); return; } - let newStatus: PeerPushDebitStatus | undefined = undefined; + let newStatus: PeerPushDebitStatus; switch (pushDebitRec.status) { case PeerPushDebitStatus.AbortingRefreshDeleted: case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted: @@ -409,22 +414,20 @@ export class PeerPushDebitTransactionContext implements TransactionContext { case PeerPushDebitStatus.Failed: case PeerPushDebitStatus.Expired: // Do nothing - break; + return undefined; default: assertUnreachable(pushDebitRec.status); } - if (newStatus != null) { - const oldTxState = computePeerPushDebitTransactionState(pushDebitRec); - pushDebitRec.status = newStatus; - const newTxState = computePeerPushDebitTransactionState(pushDebitRec); - await tx.peerPushDebit.put(pushDebitRec); - await this.updateTransactionMeta(tx); - return { - oldTxState, - newTxState, - }; - } - return undefined; + const oldTxState = computePeerPushDebitTransactionState(pushDebitRec); + pushDebitRec.status = newStatus; + pushDebitRec.failReason = reason; + const newTxState = computePeerPushDebitTransactionState(pushDebitRec); + await tx.peerPushDebit.put(pushDebitRec); + await this.updateTransactionMeta(tx); + return { + oldTxState, + newTxState, + }; }, ); wex.taskScheduler.stopShepherdTask(retryTag); diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts @@ -209,6 +209,7 @@ export class RefreshTransactionContext implements TransactionContext { tag: TransactionType.Refresh, refreshGroupId: refreshGroupRecord.refreshGroupId, }), + failReason: refreshGroupRecord.failReason, ...(ort?.lastError ? { error: ort.lastError } : {}), }; } @@ -346,7 +347,7 @@ export class RefreshTransactionContext implements TransactionContext { }); } - async failTransaction(): Promise<void> { + async failTransaction(reason?: TalerErrorDetail): Promise<void> { await this.transition({}, async (rec, tx) => { if (!rec) { return TransitionResult.stay(); @@ -358,6 +359,7 @@ export class RefreshTransactionContext implements TransactionContext { case RefreshOperationStatus.Pending: case RefreshOperationStatus.Suspended: { rec.operationStatus = RefreshOperationStatus.Failed; + rec.failReason = reason; return TransitionResult.transition(rec); } default: diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts @@ -24,8 +24,10 @@ import { assertUnreachable, j2s, Logger, + makeTalerErrorDetail, NotificationType, ScopeType, + TalerErrorCode, Transaction, TransactionByIdRequest, TransactionIdStr, @@ -598,7 +600,12 @@ export async function failTransaction( transactionId: string, ): Promise<void> { const ctx = await getContextForTransaction(wex, transactionId); - await ctx.failTransaction(); + await ctx.failTransaction( + makeTalerErrorDetail( + TalerErrorCode.WALLET_TRANSACTION_ABANDONED_BY_USER, + {}, + ), + ); } /** @@ -631,7 +638,9 @@ export async function abortTransaction( transactionId: string, ): Promise<void> { const ctx = await getContextForTransaction(wex, transactionId); - await ctx.abortTransaction(); + await ctx.abortTransaction( + makeTalerErrorDetail(TalerErrorCode.WALLET_TRANSACTION_ABORTED_BY_USER, {}), + ); } export interface TransitionInfo { diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -245,6 +245,8 @@ function buildTransactionForBankIntegratedWithdraw( kycUrl: kycDetails?.kycUrl, kycAccessToken: wg.kycAccessToken, kycPaytoHash: wg.kycPaytoHash, + abortReason: wg.abortReason, + failReason: wg.failReason, timestamp: timestampPreciseFromDb(wg.timestampStart), transactionId: constructTransactionIdentifier({ tag: TransactionType.Withdrawal, @@ -310,6 +312,7 @@ function buildTransactionForManualWithdraw( tag: TransactionType.Withdrawal, withdrawalGroupId: wg.withdrawalGroupId, }), + abortReason: wg.abortReason, ...(ort?.lastError ? { error: ort.lastError } : {}), }; if (ort?.lastError) { @@ -610,7 +613,7 @@ export class WithdrawTransactionContext implements TransactionContext { ); } - async abortTransaction(): Promise<void> { + async abortTransaction(reason?: TalerErrorDetail): Promise<void> { const { withdrawalGroupId } = this; await this.transition( { @@ -662,6 +665,7 @@ export class WithdrawTransactionContext implements TransactionContext { default: assertUnreachable(wg.status); } + wg.abortReason = reason; wg.status = newStatus; return TransitionResult.transition(wg); }, @@ -711,7 +715,7 @@ export class WithdrawTransactionContext implements TransactionContext { ); } - async failTransaction(): Promise<void> { + async failTransaction(reason?: TalerErrorDetail): Promise<void> { const { withdrawalGroupId } = this; await this.transition( { @@ -732,6 +736,7 @@ export class WithdrawTransactionContext implements TransactionContext { return TransitionResult.stay(); } wg.status = newStatus; + wg.failReason = reason; return TransitionResult.transition(wg); }, );