commit 4554fb0fb2575958fd5f47615067dbde6aea148b
parent 30d82873dbb6deb305797d758a8238af0e06206d
Author: Florian Dold <dold@taler.net>
Date: Sat, 18 Jul 2026 21:13:06 +0200
db: add donation accessors
Diffstat:
7 files changed, 199 insertions(+), 100 deletions(-)
diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts
@@ -91,9 +91,9 @@ import {
WithdrawalGroupStatus,
WalletRefreshGroup,
WithdrawalRecordType,
+ WalletDonationSummary,
} from "./db-common.js";
import {
- DonationSummaryRecord,
} from "./db-indexeddb.js";
import { WalletDbTransaction } from "./dbtx.js";
import {
@@ -300,7 +300,7 @@ class BalancesStore {
b.flagOutgoingKyc = true;
}
- addDonauItem(rec: DonationSummaryRecord): void {
+ addDonauItem(rec: WalletDonationSummary): void {
if (!this.donauSummaryItems) {
this.donauSummaryItems = [];
}
diff --git a/packages/taler-wallet-core/src/db-common.ts b/packages/taler-wallet-core/src/db-common.ts
@@ -37,6 +37,9 @@ import {
CoinEnvelope,
TalerProtocolDuration,
DenomSelectionState,
+ DonationReceiptSignature,
+ HashCodeString,
+ BlindedUniqueDonationIdentifier,
CoinStatus,
AgeCommitmentProof,
TransactionIdStr,
@@ -778,6 +781,54 @@ export interface WalletPlanchet {
ageCommitmentProof?: AgeCommitmentProof;
}
+export interface WalletDonationSummary {
+ donauBaseUrl: string;
+ legalDomain?: string;
+ year: number;
+ currency: string;
+ amountReceiptsAvailable: AmountString;
+ amountReceiptsSubmitted: AmountString;
+}
+
+/**
+ * Record for donation receipts.
+ */
+export interface WalletDonationReceipt {
+ status: DonationReceiptStatus;
+ donauBaseUrl: string;
+ udiNonce: HashCodeString;
+ proposalId: string;
+ donationYear: number;
+ donationUnitPubHash: HashCodeString;
+ donationUnitSig: DonationReceiptSignature;
+ donorTaxIdHash: HashCodeString;
+ donorHashSalt: string;
+ donorTaxId: string;
+ value: AmountString;
+ /** Index of this udi within the selected donation units for the purchase. */
+ udiIndex: number;
+}
+
+/**
+ * Record for donation planchets.
+ */
+export interface WalletDonationPlanchet {
+ donauBaseUrl: string;
+ udiNonce: HashCodeString;
+ donorTaxIdHash: HashCodeString;
+ donorHashSalt: string;
+ donorTaxId: string;
+ donationYear: number;
+ proposalId: string;
+ /** Index of this udi within the selected donation units for the purchase. */
+ udiIndex: number;
+ blindedUdi: BlindedUniqueDonationIdentifier;
+ /** blinding key secret */
+ bks: string;
+ donationUnitPubHash: HashCodeString;
+ value: AmountString;
+}
+
export interface WalletWithdrawCoinSource {
type: CoinSourceType.Withdraw;
diff --git a/packages/taler-wallet-core/src/db-indexeddb.ts b/packages/taler-wallet-core/src/db-indexeddb.ts
@@ -133,6 +133,9 @@ import {
WgInfoBankRecoup,
WalletWithdrawalGroup,
WalletPlanchet,
+ WalletDonationSummary,
+ WalletDonationReceipt,
+ WalletDonationPlanchet,
WalletRefreshSession,
WalletRefreshGroupPerExchangeInfo,
WalletReserve,
@@ -1246,54 +1249,6 @@ export interface ExchangeBaseUrlFixupRecord {
replacement: string;
}
-/**
- * Record for donation planchets.
- */
-export interface DonationPlanchetRecord {
- donauBaseUrl: string;
- udiNonce: HashCodeString;
- donorTaxIdHash: HashCodeString;
- donorHashSalt: string;
- donorTaxId: string;
- donationYear: number;
- proposalId: string;
- /** Index of this udi within the selected donation units for the purchase. */
- udiIndex: number;
- blindedUdi: BlindedUniqueDonationIdentifier;
- /** blinding key secret */
- bks: string;
- donationUnitPubHash: HashCodeString;
- value: AmountString;
-}
-
-/**
- * Record for donation receipts.
- */
-export interface DonationReceiptRecord {
- status: DonationReceiptStatus;
- donauBaseUrl: string;
- udiNonce: HashCodeString;
- proposalId: string;
- donationYear: number;
- donationUnitPubHash: HashCodeString;
- donationUnitSig: DonationReceiptSignature;
- donorTaxIdHash: HashCodeString;
- donorHashSalt: string;
- donorTaxId: string;
- value: AmountString;
- /** Index of this udi within the selected donation units for the purchase. */
- udiIndex: number;
-}
-
-export interface DonationSummaryRecord {
- donauBaseUrl: string;
- legalDomain?: string;
- year: number;
- currency: string;
- amountReceiptsAvailable: AmountString;
- amountReceiptsSubmitted: AmountString;
-}
-
export interface ContactRecord {
/**
* The mailbox URI of this contact
@@ -1712,7 +1667,7 @@ export const WalletIndexedDbStoresV1 = {
},
),
donationPlanchets: describeStoreV2({
- recordCodec: passthroughCodec<DonationPlanchetRecord>(),
+ recordCodec: passthroughCodec<WalletDonationPlanchet>(),
storeName: "donationPlanchets",
keyPath: "udiNonce",
versionAdded: 20,
@@ -1723,7 +1678,7 @@ export const WalletIndexedDbStoresV1 = {
},
}),
donationReceipts: describeStoreV2({
- recordCodec: passthroughCodec<DonationReceiptRecord>(),
+ recordCodec: passthroughCodec<WalletDonationReceipt>(),
storeName: "donationReceipts",
keyPath: "udiNonce",
versionAdded: 20,
@@ -1744,7 +1699,7 @@ export const WalletIndexedDbStoresV1 = {
},
}),
donationSummaries: describeStoreV2({
- recordCodec: passthroughCodec<DonationSummaryRecord>(),
+ recordCodec: passthroughCodec<WalletDonationSummary>(),
storeName: "donationSummaries",
keyPath: ["donauBaseUrl", "year", "currency"],
versionAdded: 22,
diff --git a/packages/taler-wallet-core/src/dbtx-indexeddb.ts b/packages/taler-wallet-core/src/dbtx-indexeddb.ts
@@ -61,11 +61,14 @@ import {
WalletRefreshSession,
WalletWithdrawalGroup,
WalletPlanchet,
+ WalletDonationSummary,
+ WalletDonationReceipt,
+ WalletDonationPlanchet,
+ DonationReceiptStatus,
} from "./db-common.js";
import type {
ExchangeEntryRecord,
ExchangeDetailsRecord,
- DonationSummaryRecord,
} from "./db-indexeddb.js";
import {
PurchaseRecord,
@@ -270,6 +273,69 @@ export class IdbWalletTransaction implements WalletDbTransaction {
});
}
+ async getDonationSummary(
+ donauBaseUrl: string,
+ year: number,
+ currency: string,
+ ): Promise<WalletDonationSummary | undefined> {
+ const tx = this.tx;
+ return await tx.donationSummaries.get([donauBaseUrl, year, currency]);
+ }
+
+ async upsertDonationSummary(rec: WalletDonationSummary): Promise<void> {
+ const tx = this.tx;
+ await tx.donationSummaries.put(rec);
+ }
+
+ async getDonationReceipt(
+ udiNonce: string,
+ ): Promise<WalletDonationReceipt | undefined> {
+ const tx = this.tx;
+ return await tx.donationReceipts.get(udiNonce);
+ }
+
+ async upsertDonationReceipt(rec: WalletDonationReceipt): Promise<void> {
+ const tx = this.tx;
+ await tx.donationReceipts.put(rec);
+ }
+
+ async getDonationReceiptsByStatus(
+ status: DonationReceiptStatus,
+ ): Promise<WalletDonationReceipt[]> {
+ const tx = this.tx;
+ return await tx.donationReceipts.indexes.byStatus.getAll(status);
+ }
+
+ async getDonationReceiptsByStatusAndDonau(
+ status: DonationReceiptStatus,
+ donauBaseUrl: string,
+ ): Promise<WalletDonationReceipt[]> {
+ const tx = this.tx;
+ return await tx.donationReceipts.indexes.byStatusAndDonauBaseUrl.getAll([
+ status,
+ donauBaseUrl,
+ ]);
+ }
+
+ async upsertDonationPlanchet(rec: WalletDonationPlanchet): Promise<void> {
+ const tx = this.tx;
+ await tx.donationPlanchets.put(rec);
+ }
+
+ async getDonationPlanchetsByProposal(
+ proposalId: string,
+ ): Promise<WalletDonationPlanchet[]> {
+ const tx = this.tx;
+ return await tx.donationPlanchets.indexes.byProposalId.getAll(proposalId);
+ }
+
+ async countDonationPlanchetsByProposal(proposalId: string): Promise<number> {
+ const tx = this.tx;
+ const keys =
+ await tx.donationPlanchets.indexes.byProposalId.getAllKeys(proposalId);
+ return keys.length;
+ }
+
async getWithdrawalGroup(
withdrawalGroupId: string,
): Promise<WalletWithdrawalGroup | undefined> {
@@ -891,7 +957,7 @@ export class IdbWalletTransaction implements WalletDbTransaction {
);
}
- async getDonationSummaries(): Promise<DonationSummaryRecord[]> {
+ async getDonationSummaries(): Promise<WalletDonationSummary[]> {
return await this.tx.donationSummaries.iter().toArray();
}
diff --git a/packages/taler-wallet-core/src/dbtx.ts b/packages/taler-wallet-core/src/dbtx.ts
@@ -65,11 +65,14 @@ import {
WalletRefreshSession,
WalletWithdrawalGroup,
WalletPlanchet,
+ WalletDonationSummary,
+ WalletDonationReceipt,
+ WalletDonationPlanchet,
+ DonationReceiptStatus,
} from "./db-common.js";
import type {
ExchangeEntryRecord,
ExchangeDetailsRecord,
- DonationSummaryRecord,
PurchaseRecord,
} from "./db-indexeddb.js";
@@ -178,6 +181,37 @@ export interface WalletDbTransaction {
*/
upsertContractTerms(rec: WalletContractTerms): Promise<void>;
+ getDonationSummary(
+ donauBaseUrl: string,
+ year: number,
+ currency: string,
+ ): Promise<WalletDonationSummary | undefined>;
+
+ upsertDonationSummary(rec: WalletDonationSummary): Promise<void>;
+
+ getDonationReceipt(
+ udiNonce: string,
+ ): Promise<WalletDonationReceipt | undefined>;
+
+ upsertDonationReceipt(rec: WalletDonationReceipt): Promise<void>;
+
+ getDonationReceiptsByStatus(
+ status: DonationReceiptStatus,
+ ): Promise<WalletDonationReceipt[]>;
+
+ getDonationReceiptsByStatusAndDonau(
+ status: DonationReceiptStatus,
+ donauBaseUrl: string,
+ ): Promise<WalletDonationReceipt[]>;
+
+ upsertDonationPlanchet(rec: WalletDonationPlanchet): Promise<void>;
+
+ getDonationPlanchetsByProposal(
+ proposalId: string,
+ ): Promise<WalletDonationPlanchet[]>;
+
+ countDonationPlanchetsByProposal(proposalId: string): Promise<number>;
+
getWithdrawalGroup(
withdrawalGroupId: string,
): Promise<WalletWithdrawalGroup | undefined>;
@@ -428,7 +462,7 @@ export interface WalletDbTransaction {
verificationStatus: DenominationVerificationStatus,
): Promise<WalletDenomination[]>;
- getDonationSummaries(): Promise<DonationSummaryRecord[]>;
+ getDonationSummaries(): Promise<WalletDonationSummary[]>;
getExchanges(): Promise<ExchangeEntryRecord[]>;
diff --git a/packages/taler-wallet-core/src/donau.ts b/packages/taler-wallet-core/src/donau.ts
@@ -53,11 +53,12 @@ import {
stringToBytes,
succeedOrThrow,
} from "@gnu-taler/taler-util";
-import { ConfigRecordKey, DonationReceiptStatus } from "./db-common.js";
import {
- DonationPlanchetRecord,
- DonationReceiptRecord,
-} from "./db-indexeddb.js";
+ ConfigRecordKey,
+ DonationReceiptStatus,
+ WalletDonationPlanchet,
+ WalletDonationReceipt,
+} from "./db-common.js";
import { WalletExecutionContext } from "./index.js";
/**
@@ -66,7 +67,7 @@ import { WalletExecutionContext } from "./index.js";
const logger = new Logger("donau.ts");
interface DonationReceiptGroup {
- receipts: DonationReceiptRecord[];
+ receipts: WalletDonationReceipt[];
donauBaseUrl: string;
donorTaxId: string;
donorTaxIdHash: string;
@@ -96,13 +97,12 @@ async function submitDonationReceipts(
const receipts = await wex.runLegacyWalletDbTx(async (tx) => {
let receipts;
if (donauBaseUrl) {
- receipts =
- await tx.donationReceipts.indexes.byStatusAndDonauBaseUrl.getAll([
- DonationReceiptStatus.Pending,
- donauBaseUrl,
- ]);
+ receipts = await tx.wtx.getDonationReceiptsByStatusAndDonau(
+ DonationReceiptStatus.Pending,
+ donauBaseUrl,
+ );
} else {
- receipts = await tx.donationReceipts.indexes.byStatus.getAll(
+ receipts = await tx.wtx.getDonationReceiptsByStatus(
DonationReceiptStatus.Pending,
);
}
@@ -130,11 +130,7 @@ async function submitDonationReceipts(
);
await wex.runLegacyWalletDbTx(async (tx) => {
- let donauSummary = await tx.donationSummaries.get([
- group.donauBaseUrl,
- group.year,
- group.currency,
- ]);
+ let donauSummary = await tx.wtx.getDonationSummary(group.donauBaseUrl, group.year, group.currency);
if (!donauSummary) {
logger.warn(
`no donau summary (your database might be an old development version, upgrade not supported)`,
@@ -145,14 +141,14 @@ async function submitDonationReceipts(
donauSummary.legalDomain = conf.legal_domain;
for (const bi of group.receipts) {
- const receipt = await tx.donationReceipts.get(bi.udiNonce);
+ const receipt = await tx.wtx.getDonationReceipt(bi.udiNonce);
if (!receipt) {
continue;
}
switch (receipt.status) {
case DonationReceiptStatus.Pending: {
receipt.status = DonationReceiptStatus.DoneSubmitted;
- await tx.donationReceipts.put(receipt);
+ await tx.wtx.upsertDonationReceipt(receipt);
donauSummary.amountReceiptsSubmitted = Amounts.stringify(
Amounts.add(donauSummary.amountReceiptsSubmitted, receipt.value)
.amount,
@@ -162,7 +158,7 @@ async function submitDonationReceipts(
}
}
- await tx.donationSummaries.put(donauSummary);
+ await tx.wtx.upsertDonationSummary(donauSummary);
tx.notify({
type: NotificationType.BalanceChange,
hintTransactionId: "donau",
@@ -178,13 +174,12 @@ async function fetchDonauStatements(
const receipts = await wex.runLegacyWalletDbTx(async (tx) => {
let receipts;
if (donauBaseUrl) {
- receipts =
- await tx.donationReceipts.indexes.byStatusAndDonauBaseUrl.getAll([
- DonationReceiptStatus.DoneSubmitted,
- donauBaseUrl,
- ]);
+ receipts = await tx.wtx.getDonationReceiptsByStatusAndDonau(
+ DonationReceiptStatus.DoneSubmitted,
+ donauBaseUrl,
+ );
} else {
- receipts = await tx.donationReceipts.indexes.byStatus.getAll(
+ receipts = await tx.wtx.getDonationReceiptsByStatus(
DonationReceiptStatus.DoneSubmitted,
);
}
@@ -220,20 +215,20 @@ async function fetchDonauStatements(
}
function groupDonauReceipts(
- receipts: DonationReceiptRecord[],
+ receipts: WalletDonationReceipt[],
): DonationReceiptGroup[] {
const donauUrlSet = new Set<string>(receipts.map((x) => x.donauBaseUrl));
const donauUrls = [...donauUrlSet];
const groups: DonationReceiptGroup[] = [];
for (const donauUrl of donauUrls) {
- const buckets: Map<string, DonationReceiptRecord[]> = new Map();
+ const buckets: Map<string, WalletDonationReceipt[]> = new Map();
for (const receipt of receipts) {
if (receipt.donauBaseUrl != donauUrl) {
continue;
}
const key = `${receipt.donorTaxIdHash}-${receipt.donationYear}`;
- let bucket: DonationReceiptRecord[] | undefined;
+ let bucket: WalletDonationReceipt[] | undefined;
bucket = buckets.get(key);
if (!bucket) {
bucket = [];
@@ -469,7 +464,7 @@ export async function generateDonauPlanchets(
}
}
- const donauPlanchets: DonationPlanchetRecord[] = [];
+ const donauPlanchets: WalletDonationPlanchet[] = [];
for (let udiIndex = 0; udiIndex < selection.length; udiIndex++) {
const sel = selection[udiIndex];
@@ -526,14 +521,12 @@ export async function generateDonauPlanchets(
return undefined;
}
const existingPlanchets =
- await tx.donationPlanchets.indexes.byProposalId.getAllKeys([
- rec.proposalId,
- ]);
- if (existingPlanchets.length > 0) {
+ await tx.wtx.countDonationPlanchetsByProposal(rec.proposalId);
+ if (existingPlanchets > 0) {
return;
}
for (const dp of donauPlanchets) {
- await tx.donationPlanchets.put(dp);
+ await tx.wtx.upsertDonationPlanchet(dp);
}
});
}
@@ -546,7 +539,7 @@ export async function generateDonauPlanchets(
export async function acceptDonauBlindSigs(
wex: WalletExecutionContext,
donauBaseUrl: string,
- donauPlanchets: DonationPlanchetRecord[],
+ donauPlanchets: WalletDonationPlanchet[],
donauBlindedSigs: SignedTokenEnvelope[],
): Promise<void> {
if (donauPlanchets.length != donauBlindedSigs.length) {
@@ -617,7 +610,7 @@ export async function acceptDonauBlindSigs(
await wex.runLegacyWalletDbTx(async (tx) => {
for (let i = 0; i < donauBlindedSigs.length; i++) {
const myPlanchet = donauPlanchets[i];
- const existingReceipt = await tx.donationReceipts.get(
+ const existingReceipt = await tx.wtx.getDonationReceipt(
myPlanchet.udiNonce,
);
if (existingReceipt) {
@@ -625,11 +618,11 @@ export async function acceptDonauBlindSigs(
}
const year = myPlanchet.donationYear;
const currency = Amounts.currencyOf(myPlanchet.value);
- let donauSummary = await tx.donationSummaries.get([
+ let donauSummary = await tx.wtx.getDonationSummary(
donauBaseUrl,
year,
Amounts.currencyOf(myPlanchet.value),
- ]);
+ );
const zero = Amounts.stringify(Amounts.zeroOfCurrency(currency));
if (!donauSummary) {
donauSummary = {
@@ -644,7 +637,7 @@ export async function acceptDonauBlindSigs(
Amounts.add(donauSummary.amountReceiptsAvailable, myPlanchet.value)
.amount,
);
- await tx.donationReceipts.put({
+ await tx.wtx.upsertDonationReceipt({
donationUnitSig: sigs[i],
donationUnitPubHash: myPlanchet.donationUnitPubHash,
donauBaseUrl: myPlanchet.donauBaseUrl,
@@ -659,7 +652,7 @@ export async function acceptDonauBlindSigs(
value: myPlanchet.value,
});
donauSummary.legalDomain = conf.legal_domain;
- await tx.donationSummaries.put(donauSummary);
+ await tx.wtx.upsertDonationSummary(donauSummary);
tx.notify({
type: NotificationType.BalanceChange,
hintTransactionId: "donau",
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -153,9 +153,9 @@ import {
WalletDenomination,
WalletCoin,
WalletCoinSelection,
+ WalletDonationPlanchet,
} from "./db-common.js";
import {
- DonationPlanchetRecord,
PurchaseRecord,
RefundGroupRecord,
RefundItemRecord,
@@ -3260,7 +3260,7 @@ async function processPurchasePay(
);
let slates: SlateRecord[] | undefined = undefined;
- let donauPlanchets: DonationPlanchetRecord[] | undefined = undefined;
+ let donauPlanchets: WalletDonationPlanchet[] | undefined = undefined;
let wallet_data: PayWalletData | undefined = undefined;
if (
download.contractTerms.version === MerchantContractVersion.V1 &&
@@ -3298,7 +3298,7 @@ async function processPurchasePay(
// FIXME: Merge with transaction above
const res = await wex.runLegacyWalletDbTx(async (tx) => {
const recs =
- await tx.donationPlanchets.indexes.byProposalId.getAll(proposalId);
+ await tx.wtx.getDonationPlanchetsByProposal(proposalId);
for (const rec of recs) {
budikeypairs.push({
blinded_udi: rec.blindedUdi,