From 6fc9a052b7f8e3ef0bd1b26279b11dc6bc12f5e4 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 20 May 2021 16:24:41 +0200 Subject: implement deletion of withdrawal transactions --- packages/taler-wallet-core/src/db.ts | 11 +++-- .../src/operations/transactions.ts | 55 +++++++++++++++++++++- packages/taler-wallet-core/src/util/query.ts | 1 + packages/taler-wallet-core/src/wallet.ts | 7 ++- 4 files changed, 69 insertions(+), 5 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 52fe5c3dc..609f43ea1 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -25,7 +25,6 @@ import { MerchantInfo, Product, RefreshReason, - ReserveTransaction, TalerErrorDetails, Timestamp, } from "@gnu-taler/taler-util"; @@ -1783,7 +1782,7 @@ class AuditorTrustStore extends Store<"auditorTrust", AuditorTrustRecord> { class ExchangeTrustStore extends Store<"exchangeTrust", ExchangeTrustRecord> { constructor() { super("exchangeTrust", { - keyPath: ["currency", "exchangeBaseUrl", "exchangePub"], + keyPath: ["currency", "exchangeBaseUrl", "exchangeMasterPub"], }); } exchangeMasterPubIndex = new Index< @@ -1791,7 +1790,7 @@ class ExchangeTrustStore extends Store<"exchangeTrust", ExchangeTrustRecord> { "exchangeMasterPubIndex", string, ExchangeTrustRecord - >(this, "exchangeMasterPubIndex", "exchangePub"); + >(this, "exchangeMasterPubIndex", "exchangeMasterPub"); uidIndex = new Index< "exchangeTrust", "uidIndex", @@ -1810,6 +1809,12 @@ class ReservesStore extends Store<"reserves", ReserveRecord> { constructor() { super("reserves", { keyPath: "reservePub" }); } + byInitialWithdrawalGroupId = new Index< + "reserves", + "initialWithdrawalGroupIdIndex", + string, + ReserveRecord + >(this, "initialWithdrawalGroupIdIndex", "initialWithdrawalGroupId"); } class TipsStore extends Store<"tips", TipRecord> { diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index dcd3ddbd9..48d0ffec5 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -24,6 +24,7 @@ import { RefundState, ReserveRecordStatus, AbortStatus, + ReserveRecord, } from "../db.js"; import { AmountJson, Amounts, timestampCmp } from "@gnu-taler/taler-util"; import { @@ -42,7 +43,7 @@ import { getFundingPaytoUris } from "./reserves"; * Create an event ID from the type and the primary key for the event. */ function makeEventId(type: TransactionType, ...args: string[]): string { - return type + ";" + args.map((x) => encodeURIComponent(x)).join(";"); + return type + ":" + args.map((x) => encodeURIComponent(x)).join(":"); } function shouldSkipCurrency( @@ -365,3 +366,55 @@ export async function getTransactions( return { transactions: [...txNotPending, ...txPending] }; } + +export enum TombstoneTag { + WithdrawalGroup = "withdrawal-group", + Reserve = "reserve", +} + +/** + * Permanentely delete a transaction based on the transaction ID. + */ +export async function deleteTransaction( + ws: InternalWalletState, + transactionId: string, +): Promise { + const [type, ...rest] = transactionId.split(":"); + + if (type === TransactionType.Withdrawal) { + const withdrawalGroupId = rest[0]; + ws.db.runWithWriteTransaction( + [Stores.withdrawalGroups, Stores.reserves, Stores.tombstones], + async (tx) => { + const withdrawalGroupRecord = await tx.get( + Stores.withdrawalGroups, + withdrawalGroupId, + ); + if (withdrawalGroupRecord) { + await tx.delete(Stores.withdrawalGroups, withdrawalGroupId); + await tx.put(Stores.tombstones, { + id: TombstoneTag.WithdrawalGroup + ":" + withdrawalGroupId, + }); + return; + } + const reserveRecord: ReserveRecord | undefined = await tx.getIndexed( + Stores.reserves.byInitialWithdrawalGroupId, + withdrawalGroupId, + ); + if (reserveRecord && !reserveRecord.initialWithdrawalStarted) { + const reservePub = reserveRecord.reservePub; + await tx.delete(Stores.reserves, reservePub); + await tx.put(Stores.tombstones, { + id: TombstoneTag.Reserve + ":" + reservePub, + }); + } + }, + ); + } else if (type === TransactionType.Refund) { + // To delete refund transactions, the whole + // purchase should be deleted. + throw Error("refunds cannot be deleted"); + } else { + throw Error(`can't delete a '${type}' transaction`); + } +} diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts index 4c37a7254..1ef75a420 100644 --- a/packages/taler-wallet-core/src/util/query.ts +++ b/packages/taler-wallet-core/src/util/query.ts @@ -33,6 +33,7 @@ import { IDBVersionChangeEvent, Event, IDBCursor, + IDBKeyPath, } from "@gnu-taler/idb-bridge"; import { Logger } from "./logging"; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 192b54926..c380786ab 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -25,6 +25,7 @@ import { BackupRecovery, codecForAny, + DeleteTransactionRequest, TalerErrorCode, WalletCurrencyInfo, } from "@gnu-taler/taler-util"; @@ -92,7 +93,7 @@ import { withdrawTestBalance, } from "./operations/testing"; import { acceptTip, prepareTip, processTip } from "./operations/tip"; -import { getTransactions } from "./operations/transactions"; +import { deleteTransaction, getTransactions } from "./operations/transactions"; import { getExchangeWithdrawalInfo, getWithdrawalDetailsForUri, @@ -578,6 +579,10 @@ export class Wallet { return getWithdrawalDetailsForUri(this.ws, talerWithdrawUri); } + async deleteTransaction(req: DeleteTransactionRequest): Promise { + return deleteTransaction(this.ws, req.transactionId); + } + /** * Update or add exchange DB entry by fetching the /keys and /wire information. * Optionally link the reserve entry to the new or existing -- cgit v1.2.3