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

@ -945,4 +945,8 @@ export interface WalletCurrencyInfo {
exchangeMasterPub: 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
.subcommand("handleUri", "handle-uri", {
help: "Handle a taler:// URI.",
@ -609,13 +624,13 @@ const currenciesCli = walletCli.subcommand("currencies", "currencies", {
});
currenciesCli
.subcommand("show", "show", { help: "Show currencies."})
.subcommand("show", "show", { help: "Show currencies." })
.action(async (args) => {
await withWallet(args, async (wallet) => {
const currencies = await wallet.getCurrencies();
console.log(JSON.stringify(currencies, undefined, 2));
});
})
});
const reservesCli = advancedCli.subcommand("reserves", "reserves", {
help: "Manage reserves.",

View File

@ -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> {

View File

@ -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<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,
Event,
IDBCursor,
IDBKeyPath,
} from "@gnu-taler/idb-bridge";
import { Logger } from "./logging";

View File

@ -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<void> {
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