implement deletion of withdrawal transactions

This commit is contained in:
Florian Dold 2021-05-20 16:24:41 +02:00
parent 1fb1827002
commit 6fc9a052b7
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
6 changed files with 90 additions and 7 deletions

View File

@ -946,3 +946,7 @@ export interface WalletCurrencyInfo {
exchangeBaseUrl: string; exchangeBaseUrl: string;
}[]; }[];
} }
export interface DeleteTransactionRequest {
transactionId: string;
}

View File

@ -284,6 +284,21 @@ walletCli
}); });
}); });
walletCli
.subcommand("deleteTransaction", "delete-transaction", {
help: "Permanently delete a transaction from the transaction list.",
})
.requiredArgument("transactionId", clk.STRING, {
help: "Identifier of the transaction to delete",
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.deleteTransaction({
transactionId: args.deleteTransaction.transactionId,
});
});
});
walletCli walletCli
.subcommand("handleUri", "handle-uri", { .subcommand("handleUri", "handle-uri", {
help: "Handle a taler:// URI.", help: "Handle a taler:// URI.",
@ -609,13 +624,13 @@ const currenciesCli = walletCli.subcommand("currencies", "currencies", {
}); });
currenciesCli currenciesCli
.subcommand("show", "show", { help: "Show currencies."}) .subcommand("show", "show", { help: "Show currencies." })
.action(async (args) => { .action(async (args) => {
await withWallet(args, async (wallet) => { await withWallet(args, async (wallet) => {
const currencies = await wallet.getCurrencies(); const currencies = await wallet.getCurrencies();
console.log(JSON.stringify(currencies, undefined, 2)); console.log(JSON.stringify(currencies, undefined, 2));
}); });
}) });
const reservesCli = advancedCli.subcommand("reserves", "reserves", { const reservesCli = advancedCli.subcommand("reserves", "reserves", {
help: "Manage reserves.", help: "Manage reserves.",

View File

@ -25,7 +25,6 @@ import {
MerchantInfo, MerchantInfo,
Product, Product,
RefreshReason, RefreshReason,
ReserveTransaction,
TalerErrorDetails, TalerErrorDetails,
Timestamp, Timestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
@ -1783,7 +1782,7 @@ class AuditorTrustStore extends Store<"auditorTrust", AuditorTrustRecord> {
class ExchangeTrustStore extends Store<"exchangeTrust", ExchangeTrustRecord> { class ExchangeTrustStore extends Store<"exchangeTrust", ExchangeTrustRecord> {
constructor() { constructor() {
super("exchangeTrust", { super("exchangeTrust", {
keyPath: ["currency", "exchangeBaseUrl", "exchangePub"], keyPath: ["currency", "exchangeBaseUrl", "exchangeMasterPub"],
}); });
} }
exchangeMasterPubIndex = new Index< exchangeMasterPubIndex = new Index<
@ -1791,7 +1790,7 @@ class ExchangeTrustStore extends Store<"exchangeTrust", ExchangeTrustRecord> {
"exchangeMasterPubIndex", "exchangeMasterPubIndex",
string, string,
ExchangeTrustRecord ExchangeTrustRecord
>(this, "exchangeMasterPubIndex", "exchangePub"); >(this, "exchangeMasterPubIndex", "exchangeMasterPub");
uidIndex = new Index< uidIndex = new Index<
"exchangeTrust", "exchangeTrust",
"uidIndex", "uidIndex",
@ -1810,6 +1809,12 @@ class ReservesStore extends Store<"reserves", ReserveRecord> {
constructor() { constructor() {
super("reserves", { keyPath: "reservePub" }); super("reserves", { keyPath: "reservePub" });
} }
byInitialWithdrawalGroupId = new Index<
"reserves",
"initialWithdrawalGroupIdIndex",
string,
ReserveRecord
>(this, "initialWithdrawalGroupIdIndex", "initialWithdrawalGroupId");
} }
class TipsStore extends Store<"tips", TipRecord> { class TipsStore extends Store<"tips", TipRecord> {

View File

@ -24,6 +24,7 @@ import {
RefundState, RefundState,
ReserveRecordStatus, ReserveRecordStatus,
AbortStatus, AbortStatus,
ReserveRecord,
} from "../db.js"; } from "../db.js";
import { AmountJson, Amounts, timestampCmp } from "@gnu-taler/taler-util"; import { AmountJson, Amounts, timestampCmp } from "@gnu-taler/taler-util";
import { import {
@ -42,7 +43,7 @@ import { getFundingPaytoUris } from "./reserves";
* Create an event ID from the type and the primary key for the event. * Create an event ID from the type and the primary key for the event.
*/ */
function makeEventId(type: TransactionType, ...args: string[]): string { 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( function shouldSkipCurrency(
@ -365,3 +366,55 @@ export async function getTransactions(
return { transactions: [...txNotPending, ...txPending] }; 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<void> {
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`);
}
}

View File

@ -33,6 +33,7 @@ import {
IDBVersionChangeEvent, IDBVersionChangeEvent,
Event, Event,
IDBCursor, IDBCursor,
IDBKeyPath,
} from "@gnu-taler/idb-bridge"; } from "@gnu-taler/idb-bridge";
import { Logger } from "./logging"; import { Logger } from "./logging";

View File

@ -25,6 +25,7 @@
import { import {
BackupRecovery, BackupRecovery,
codecForAny, codecForAny,
DeleteTransactionRequest,
TalerErrorCode, TalerErrorCode,
WalletCurrencyInfo, WalletCurrencyInfo,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
@ -92,7 +93,7 @@ import {
withdrawTestBalance, withdrawTestBalance,
} from "./operations/testing"; } from "./operations/testing";
import { acceptTip, prepareTip, processTip } from "./operations/tip"; import { acceptTip, prepareTip, processTip } from "./operations/tip";
import { getTransactions } from "./operations/transactions"; import { deleteTransaction, getTransactions } from "./operations/transactions";
import { import {
getExchangeWithdrawalInfo, getExchangeWithdrawalInfo,
getWithdrawalDetailsForUri, getWithdrawalDetailsForUri,
@ -578,6 +579,10 @@ export class Wallet {
return getWithdrawalDetailsForUri(this.ws, talerWithdrawUri); return getWithdrawalDetailsForUri(this.ws, talerWithdrawUri);
} }
async deleteTransaction(req: DeleteTransactionRequest): Promise<void> {
return deleteTransaction(this.ws, req.transactionId);
}
/** /**
* Update or add exchange DB entry by fetching the /keys and /wire information. * Update or add exchange DB entry by fetching the /keys and /wire information.
* Optionally link the reserve entry to the new or existing * Optionally link the reserve entry to the new or existing