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:
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);
},
);