taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 4554fb0fb2575958fd5f47615067dbde6aea148b
parent 30d82873dbb6deb305797d758a8238af0e06206d
Author: Florian Dold <dold@taler.net>
Date:   Sat, 18 Jul 2026 21:13:06 +0200

db: add donation accessors

Diffstat:
Mpackages/taler-wallet-core/src/balance.ts | 4++--
Mpackages/taler-wallet-core/src/db-common.ts | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-wallet-core/src/db-indexeddb.ts | 57++++++---------------------------------------------------
Mpackages/taler-wallet-core/src/dbtx-indexeddb.ts | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mpackages/taler-wallet-core/src/dbtx.ts | 38++++++++++++++++++++++++++++++++++++--
Mpackages/taler-wallet-core/src/donau.ts | 73+++++++++++++++++++++++++++++++++----------------------------------------
Mpackages/taler-wallet-core/src/pay-merchant.ts | 6+++---
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,