taler-typescript-core

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

commit fa0b2336d29ef7419158b647b3f304aaf5bc87f2
parent 4554fb0fb2575958fd5f47615067dbde6aea148b
Author: Florian Dold <dold@taler.net>
Date:   Sat, 18 Jul 2026 21:16:14 +0200

db: add purchase and refund accessors

Diffstat:
Mpackages/taler-wallet-core/src/common.ts | 4++--
Mpackages/taler-wallet-core/src/db-common.ts | 277+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-wallet-core/src/db-indexeddb.ts | 291+++----------------------------------------------------------------------------
Mpackages/taler-wallet-core/src/dbtx-indexeddb.ts | 116++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mpackages/taler-wallet-core/src/dbtx.ts | 55++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mpackages/taler-wallet-core/src/dev-experiments.ts | 4++--
Mpackages/taler-wallet-core/src/donau.ts | 4++--
Mpackages/taler-wallet-core/src/exchanges.ts | 4++--
Mpackages/taler-wallet-core/src/pay-merchant.ts | 184+++++++++++++++++++++++++++++++++++++------------------------------------------
9 files changed, 547 insertions(+), 392 deletions(-)

diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts @@ -72,6 +72,7 @@ import { WalletRecoupGroup, WalletRefreshGroup, WalletWithdrawalGroup, + WalletPurchase, } from "./db-common.js"; import { ExchangeEntryRecord, @@ -79,7 +80,6 @@ import { PeerPullPaymentIncomingRecord, PeerPushCreditRecord, PeerPushDebitRecord, - PurchaseRecord, } from "./db-indexeddb.js"; import { WalletDbTransaction } from "./dbtx.js"; import { ReadyExchangeSummary } from "./exchanges.js"; @@ -826,7 +826,7 @@ export namespace TaskIdentifiers { ): TaskIdStr { return `${PendingTaskType.Refresh}:${refreshGroupRecord.refreshGroupId}` as TaskIdStr; } - export function forPay(purchaseRecord: PurchaseRecord): TaskIdStr { + export function forPay(purchaseRecord: WalletPurchase): TaskIdStr { return `${PendingTaskType.Purchase}:${purchaseRecord.proposalId}` as TaskIdStr; } export function forRecoup(recoupRecord: WalletRecoupGroup): TaskIdStr { diff --git a/packages/taler-wallet-core/src/db-common.ts b/packages/taler-wallet-core/src/db-common.ts @@ -40,6 +40,7 @@ import { DonationReceiptSignature, HashCodeString, BlindedUniqueDonationIdentifier, + SignedTokenEnvelope, CoinStatus, AgeCommitmentProof, TransactionIdStr, @@ -829,6 +830,282 @@ export interface WalletDonationPlanchet { value: AmountString; } +/** + * Partial information about the downloaded proposal. + * Only contains data that is relevant for indexing on the + * "purchases" object stores. + */ +export interface WalletProposalDownloadInfo { + contractTermsHash: string; + fulfillmentUrl?: string; + currency: string; + contractTermsMerchantSig: string; +} + +export interface WalletTokenSelection { + tokenPubs: string[]; +} + +export interface WalletPurchasePayInfo { + /** + * Undefined if payment is blocked by a pending refund. + */ + payCoinSelection?: WalletCoinSelection; + /** + * Undefined if payment is blocked by a pending refund. + */ + payCoinSelectionUid?: string; + + payTokenSelection?: WalletTokenSelection; + + /** + * Token signatures from merchant. + */ + slateTokenSigs?: SignedTokenEnvelope[]; + + /** + * Whether token selection should be forced + * e.g. when merchant URL is not in `expected_domains' + */ + payTokenForcedSel?: boolean; + + totalPayCost: AmountString; +} + +/** + * Record that stores status information about one purchase, starting from when + * the customer accepts a proposal. Includes refund status if applicable. + * + * Key: {@link proposalId} + * Operation status: {@link purchaseStatus} + */ +export interface WalletPurchase { + /** + * Proposal ID for this purchase. Uniquely identifies the + * purchase and the proposal. + * Assigned by the wallet. + */ + proposalId: string; + + /** + * Order ID, assigned by the merchant. + */ + orderId: string; + + merchantBaseUrl: string; + + /** + * Claim token used when downloading the contract terms. + */ + claimToken: string | undefined; + + /** + * Session ID we got when downloading the contract. + */ + downloadSessionId: string | undefined; + + /** + * If this purchase is a repurchase, this field identifies the original purchase. + */ + repurchaseProposalId: string | undefined; + + purchaseStatus: PurchaseStatus; + + /** + * Refresh group ID of the refresh transaction that + * has been created to abort the payment. + */ + abortRefreshGroupId?: string; + + abortReason?: TalerErrorDetail; + failReason?: TalerErrorDetail; + + /** + * Private key for the nonce. + */ + noncePriv: string; + + /** + * Public key for the nonce. + */ + noncePub: string; + + /** + * Index of selected choice in the choices array. + */ + choiceIndex?: number | undefined; + + /** + * Secret seed used to derive slates. + * Stored since slates are created lazily. + */ + secretSeed: string | undefined; + + /** + * Downloaded and parsed proposal data. + */ + download: WalletProposalDownloadInfo | undefined; + + payInfo: WalletPurchasePayInfo | undefined; + + /** + * Exchanges involved in this purchase. + * Used as a multiEntry index to find all purchases for + * an exchange. + */ + exchanges?: string[]; + + /** + * Pending removals from pay coin selection. + * + * Used when a the pay coin selection needs to be changed + * because a coin became known as double-spent or invalid, + * but a new coin selection can't immediately be done, as + * there is not enough balance (e.g. when waiting for a refresh). + */ + pendingRemovedCoinPubs?: string[]; + + /** + * Timestamp of the first time that sending a payment to the merchant + * for this purchase was successful. + */ + timestampFirstSuccessfulPay: DbPreciseTimestamp | undefined; + + merchantPaySig: string | undefined; + + posConfirmation: string | undefined; + + donauOutputIndex?: number; + donauBaseUrl?: string; + donauAmount?: AmountString; + donauTaxIdHash?: string; + donauTaxIdSalt?: string; + donauTaxId?: string; + donauYear?: number; + + /** + * This purchase was shared with another wallet + * that is now supposed to finish the payment. + */ + shared: boolean; + + /** + * This purchase was created by reading + * a payment share or the wallet + * the nonce public by a payment share + * + * Defaults to false. + */ + createdFromShared?: boolean; + + /** + * When was the purchase record created? + */ + timestamp: DbPreciseTimestamp; + + /** + * When was the purchase made? + * Refers to the time that the user accepted. + */ + timestampAccept: DbPreciseTimestamp | undefined; + + /** + * When was the last refund made? + * Set to 0 if no refund was made on the purchase. + */ + timestampLastRefundStatus: DbPreciseTimestamp | undefined; + + /** + * Timestamp when the wallet noticed that the transaction expired. + * May be later than the pay deadline. + */ + timestampExpired?: DbPreciseTimestamp; + + /** + * Last session signature that we submitted to /pay (if any). + */ + lastSessionId: string | undefined; + + /** + * Continue querying the refund status until this deadline has expired. + */ + autoRefundDeadline: DbProtocolTimestamp | undefined; + + /** + * How much merchant has refund to be taken but the wallet + * did not picked up yet + */ + refundAmountAwaiting: AmountString | undefined; +} + +/** + * Metadata about a group of refunds with the merchant. + */ +export interface WalletRefundGroup { + status: RefundGroupStatus; + + /** + * Timestamp when the refund group was created. + */ + timestampCreated: DbPreciseTimestamp; + + proposalId: string; + + refundGroupId: string; + + refreshGroupId?: string; + + amountRaw: AmountString; + + /** + * Estimated effective amount, based on + * refund fees and refresh costs. + */ + amountEffective: AmountString; +} + +/** + * Refund for a single coin in a payment with a merchant. + */ +export interface WalletRefundItem { + /** + * Auto-increment DB record ID. + */ + id?: number; + + status: RefundItemStatus; + + /** + * Mandatory since DB minor version 15. + */ + proposalId?: string; + + refundGroupId: string; + + /** + * Execution time as claimed by the merchant + */ + executionTime: DbProtocolTimestamp; + + /** + * Time when the wallet became aware of the refund. + */ + obtainedTime: DbPreciseTimestamp; + + refundAmount: AmountString; + + coinPub: string; + + rtxid: number; +} + +export interface WalletTombstone { + /** + * Tombstone ID, with the syntax "tmb:<type>:<key>". + */ + id: string; +} + 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 @@ -134,6 +134,13 @@ import { WalletWithdrawalGroup, WalletPlanchet, WalletDonationSummary, + WalletProposalDownloadInfo, + WalletTokenSelection, + WalletPurchasePayInfo, + WalletPurchase, + WalletRefundGroup, + WalletRefundItem, + WalletTombstone, WalletDonationReceipt, WalletDonationPlanchet, WalletRefreshSession, @@ -634,214 +641,6 @@ export namespace TokenRecord { } } -/** - * Partial information about the downloaded proposal. - * Only contains data that is relevant for indexing on the - * "purchases" object stores. - */ -export interface ProposalDownloadInfo { - contractTermsHash: string; - fulfillmentUrl?: string; - currency: string; - contractTermsMerchantSig: string; -} - -export interface DbTokenSelection { - tokenPubs: string[]; -} - -export interface PurchasePayInfo { - /** - * Undefined if payment is blocked by a pending refund. - */ - payCoinSelection?: WalletCoinSelection; - /** - * Undefined if payment is blocked by a pending refund. - */ - payCoinSelectionUid?: string; - - payTokenSelection?: DbTokenSelection; - - /** - * Token signatures from merchant. - */ - slateTokenSigs?: SignedTokenEnvelope[]; - - /** - * Whether token selection should be forced - * e.g. when merchant URL is not in `expected_domains' - */ - payTokenForcedSel?: boolean; - - totalPayCost: AmountString; -} - -/** - * Record that stores status information about one purchase, starting from when - * the customer accepts a proposal. Includes refund status if applicable. - * - * Key: {@link proposalId} - * Operation status: {@link purchaseStatus} - */ -export interface PurchaseRecord { - /** - * Proposal ID for this purchase. Uniquely identifies the - * purchase and the proposal. - * Assigned by the wallet. - */ - proposalId: string; - - /** - * Order ID, assigned by the merchant. - */ - orderId: string; - - merchantBaseUrl: string; - - /** - * Claim token used when downloading the contract terms. - */ - claimToken: string | undefined; - - /** - * Session ID we got when downloading the contract. - */ - downloadSessionId: string | undefined; - - /** - * If this purchase is a repurchase, this field identifies the original purchase. - */ - repurchaseProposalId: string | undefined; - - purchaseStatus: PurchaseStatus; - - /** - * Refresh group ID of the refresh transaction that - * has been created to abort the payment. - */ - abortRefreshGroupId?: string; - - abortReason?: TalerErrorDetail; - failReason?: TalerErrorDetail; - - /** - * Private key for the nonce. - */ - noncePriv: string; - - /** - * Public key for the nonce. - */ - noncePub: string; - - /** - * Index of selected choice in the choices array. - */ - choiceIndex?: number | undefined; - - /** - * Secret seed used to derive slates. - * Stored since slates are created lazily. - */ - secretSeed: string | undefined; - - /** - * Downloaded and parsed proposal data. - */ - download: ProposalDownloadInfo | undefined; - - payInfo: PurchasePayInfo | undefined; - - /** - * Exchanges involved in this purchase. - * Used as a multiEntry index to find all purchases for - * an exchange. - */ - exchanges?: string[]; - - /** - * Pending removals from pay coin selection. - * - * Used when a the pay coin selection needs to be changed - * because a coin became known as double-spent or invalid, - * but a new coin selection can't immediately be done, as - * there is not enough balance (e.g. when waiting for a refresh). - */ - pendingRemovedCoinPubs?: string[]; - - /** - * Timestamp of the first time that sending a payment to the merchant - * for this purchase was successful. - */ - timestampFirstSuccessfulPay: DbPreciseTimestamp | undefined; - - merchantPaySig: string | undefined; - - posConfirmation: string | undefined; - - donauOutputIndex?: number; - donauBaseUrl?: string; - donauAmount?: AmountString; - donauTaxIdHash?: string; - donauTaxIdSalt?: string; - donauTaxId?: string; - donauYear?: number; - - /** - * This purchase was shared with another wallet - * that is now supposed to finish the payment. - */ - shared: boolean; - - /** - * This purchase was created by reading - * a payment share or the wallet - * the nonce public by a payment share - * - * Defaults to false. - */ - createdFromShared?: boolean; - - /** - * When was the purchase record created? - */ - timestamp: DbPreciseTimestamp; - - /** - * When was the purchase made? - * Refers to the time that the user accepted. - */ - timestampAccept: DbPreciseTimestamp | undefined; - - /** - * When was the last refund made? - * Set to 0 if no refund was made on the purchase. - */ - timestampLastRefundStatus: DbPreciseTimestamp | undefined; - - /** - * Timestamp when the wallet noticed that the transaction expired. - * May be later than the pay deadline. - */ - timestampExpired?: DbPreciseTimestamp; - - /** - * Last session signature that we submitted to /pay (if any). - */ - lastSessionId: string | undefined; - - /** - * Continue querying the refund status until this deadline has expired. - */ - autoRefundDeadline: DbProtocolTimestamp | undefined; - - /** - * How much merchant has refund to be taken but the wallet - * did not picked up yet - */ - refundAmountAwaiting: AmountString | undefined; -} - // FIXME: Should these be numeric codes? export type KycUserType = "individual" | "business"; @@ -857,13 +656,6 @@ export interface BankWithdrawUriRecord { reservePub: string; } -export interface TombstoneRecord { - /** - * Tombstone ID, with the syntax "tmb:<type>:<key>". - */ - id: string; -} - export interface DbPeerPushPaymentCoinSelection { contributions: AmountString[]; coinPubs: CoinPublicKeyString[]; @@ -1127,67 +919,6 @@ export interface DbAuditorHandle { auditorPub: string; } -/** - * Metadata about a group of refunds with the merchant. - */ -export interface RefundGroupRecord { - status: RefundGroupStatus; - - /** - * Timestamp when the refund group was created. - */ - timestampCreated: DbPreciseTimestamp; - - proposalId: string; - - refundGroupId: string; - - refreshGroupId?: string; - - amountRaw: AmountString; - - /** - * Estimated effective amount, based on - * refund fees and refresh costs. - */ - amountEffective: AmountString; -} - -/** - * Refund for a single coin in a payment with a merchant. - */ -export interface RefundItemRecord { - /** - * Auto-increment DB record ID. - */ - id?: number; - - status: RefundItemStatus; - - /** - * Mandatory since DB minor version 15. - */ - proposalId?: string; - - refundGroupId: string; - - /** - * Execution time as claimed by the merchant - */ - executionTime: DbProtocolTimestamp; - - /** - * Time when the wallet became aware of the refund. - */ - obtainedTime: DbPreciseTimestamp; - - refundAmount: AmountString; - - coinPub: string; - - rtxid: number; -} - export function passthroughCodec<T>(): Codec<T> { return codecForAny(); } @@ -1649,7 +1380,7 @@ export const WalletIndexedDbStoresV1 = { ), purchases: describeStore( "purchases", - describeContents<PurchaseRecord>({ keyPath: "proposalId" }), + describeContents<WalletPurchase>({ keyPath: "proposalId" }), { byStatus: describeIndex("byStatus", "purchaseStatus"), byFulfillmentUrl: describeIndex( @@ -1756,7 +1487,7 @@ export const WalletIndexedDbStoresV1 = { ), tombstones: describeStore( "tombstones", - describeContents<TombstoneRecord>({ keyPath: "id" }), + describeContents<WalletTombstone>({ keyPath: "id" }), {}, ), operationRetries: describeStore( @@ -1855,7 +1586,7 @@ export const WalletIndexedDbStoresV1 = { ), refundGroups: describeStore( "refundGroups", - describeContents<RefundGroupRecord>({ + describeContents<WalletRefundGroup>({ keyPath: "refundGroupId", }), { @@ -1865,7 +1596,7 @@ export const WalletIndexedDbStoresV1 = { ), refundItems: describeStore( "refundItems", - describeContents<RefundItemRecord>({ + describeContents<WalletRefundItem>({ keyPath: "id", autoIncrement: true, }), diff --git a/packages/taler-wallet-core/src/dbtx-indexeddb.ts b/packages/taler-wallet-core/src/dbtx-indexeddb.ts @@ -65,13 +65,16 @@ import { WalletDonationReceipt, WalletDonationPlanchet, DonationReceiptStatus, + WalletPurchase, + WalletRefundGroup, + WalletRefundItem, + WalletTombstone, } from "./db-common.js"; import type { ExchangeEntryRecord, ExchangeDetailsRecord, } from "./db-indexeddb.js"; import { - PurchaseRecord, WalletIndexedDbTransaction, } from "./db-indexeddb.js"; import type { @@ -216,7 +219,7 @@ export class IdbWalletTransaction implements WalletDbTransaction { await tx.mailboxConfigurations.put(mailboxConf); } - async getPurchase(proposalId: string): Promise<PurchaseRecord | undefined> { + async getPurchase(proposalId: string): Promise<WalletPurchase | undefined> { const tx = this.tx; return await tx.purchases.get(proposalId); } @@ -273,6 +276,113 @@ export class IdbWalletTransaction implements WalletDbTransaction { }); } + async upsertPurchase(rec: WalletPurchase): Promise<void> { + const tx = this.tx; + await tx.purchases.put(rec); + } + + async deletePurchase(proposalId: string): Promise<void> { + const tx = this.tx; + await tx.purchases.delete(proposalId); + } + + async getPurchaseByUrlAndOrderId( + merchantBaseUrl: string, + orderId: string, + ): Promise<WalletPurchase | undefined> { + const tx = this.tx; + return await tx.purchases.indexes.byUrlAndOrderId.get([ + merchantBaseUrl, + orderId, + ]); + } + + async getPurchasesByUrlAndOrderId( + merchantBaseUrl: string, + orderId: string, + ): Promise<WalletPurchase[]> { + const tx = this.tx; + return await tx.purchases.indexes.byUrlAndOrderId.getAll([ + merchantBaseUrl, + orderId, + ]); + } + + async getPurchasesByFulfillmentUrl( + fulfillmentUrl: string, + ): Promise<WalletPurchase[]> { + const tx = this.tx; + return await tx.purchases.indexes.byFulfillmentUrl.getAll(fulfillmentUrl); + } + + async getPurchasesByExchange( + exchangeBaseUrl: string, + ): Promise<WalletPurchase[]> { + const tx = this.tx; + return await tx.purchases.indexes.byExchange.getAll(exchangeBaseUrl); + } + + async getRefundGroup( + refundGroupId: string, + ): Promise<WalletRefundGroup | undefined> { + const tx = this.tx; + return await tx.refundGroups.get(refundGroupId); + } + + async upsertRefundGroup(rec: WalletRefundGroup): Promise<void> { + const tx = this.tx; + await tx.refundGroups.put(rec); + } + + async deleteRefundGroup(refundGroupId: string): Promise<void> { + const tx = this.tx; + await tx.refundGroups.delete(refundGroupId); + } + + async getRefundGroupsByProposal( + proposalId: string, + ): Promise<WalletRefundGroup[]> { + const tx = this.tx; + return await tx.refundGroups.indexes.byProposalId.getAll(proposalId); + } + + async getRefundItemsByGroup( + refundGroupId: string, + ): Promise<WalletRefundItem[]> { + const tx = this.tx; + // byRefundGroupId has an array keyPath (["refundGroupId"]), so its keys + // are single-element arrays and a bare string matches nothing. + return await tx.refundItems.indexes.byRefundGroupId.getAll([refundGroupId]); + } + + async upsertRefundItem(rec: WalletRefundItem): Promise<number> { + const tx = this.tx; + const res = await tx.refundItems.put(rec); + checkDbInvariant( + typeof res.key === "number", + "refund item row id must be a number", + ); + return res.key; + } + + async deleteRefundItem(id: number): Promise<void> { + const tx = this.tx; + await tx.refundItems.delete(id); + } + + async getRefundItemByCoinAndRtxid( + coinPub: string, + rtxid: number, + ): Promise<WalletRefundItem | undefined> { + const tx = this.tx; + return await tx.refundItems.indexes.byCoinPubAndRtxid.get([coinPub, rtxid]); + } + + async upsertTombstone(rec: WalletTombstone): Promise<void> { + const tx = this.tx; + await tx.tombstones.put(rec); + } + async getDonationSummary( donauBaseUrl: string, year: number, @@ -1005,7 +1115,7 @@ export class IdbWalletTransaction implements WalletDbTransaction { ); } - async getActivePurchases(): Promise<PurchaseRecord[]> { + async getActivePurchases(): Promise<WalletPurchase[]> { return await this.tx.purchases.indexes.byStatus.getAll(getActiveKeyRange()); } diff --git a/packages/taler-wallet-core/src/dbtx.ts b/packages/taler-wallet-core/src/dbtx.ts @@ -69,11 +69,14 @@ import { WalletDonationReceipt, WalletDonationPlanchet, DonationReceiptStatus, + WalletPurchase, + WalletRefundGroup, + WalletRefundItem, + WalletTombstone, } from "./db-common.js"; import type { ExchangeEntryRecord, ExchangeDetailsRecord, - PurchaseRecord, } from "./db-indexeddb.js"; export interface GetCurrencyInfoDbResult { @@ -137,7 +140,7 @@ export interface WalletDbTransaction { upsertMailboxConfiguration(mailboxConf: MailboxConfiguration): Promise<void>; - getPurchase(proposalId: string): Promise<PurchaseRecord | undefined>; + getPurchase(proposalId: string): Promise<WalletPurchase | undefined>; /** * Create or update the transaction metadata for a transaction. @@ -181,6 +184,52 @@ export interface WalletDbTransaction { */ upsertContractTerms(rec: WalletContractTerms): Promise<void>; + upsertPurchase(rec: WalletPurchase): Promise<void>; + + deletePurchase(proposalId: string): Promise<void>; + + getPurchaseByUrlAndOrderId( + merchantBaseUrl: string, + orderId: string, + ): Promise<WalletPurchase | undefined>; + + getPurchasesByUrlAndOrderId( + merchantBaseUrl: string, + orderId: string, + ): Promise<WalletPurchase[]>; + + getPurchasesByFulfillmentUrl( + fulfillmentUrl: string, + ): Promise<WalletPurchase[]>; + + getPurchasesByExchange(exchangeBaseUrl: string): Promise<WalletPurchase[]>; + + getRefundGroup(refundGroupId: string): Promise<WalletRefundGroup | undefined>; + + upsertRefundGroup(rec: WalletRefundGroup): Promise<void>; + + deleteRefundGroup(refundGroupId: string): Promise<void>; + + getRefundGroupsByProposal(proposalId: string): Promise<WalletRefundGroup[]>; + + getRefundItemsByGroup(refundGroupId: string): Promise<WalletRefundItem[]>; + + /** + * Create or update a refund item, returning its row id. + * + * The refundItems store is auto-incrementing on id. + */ + upsertRefundItem(rec: WalletRefundItem): Promise<number>; + + deleteRefundItem(id: number): Promise<void>; + + getRefundItemByCoinAndRtxid( + coinPub: string, + rtxid: number, + ): Promise<WalletRefundItem | undefined>; + + upsertTombstone(rec: WalletTombstone): Promise<void>; + getDonationSummary( donauBaseUrl: string, year: number, @@ -480,7 +529,7 @@ export interface WalletDbTransaction { getActivePeerPullDebits(): Promise<WalletPeerPullDebit[]>; - getActivePurchases(): Promise<PurchaseRecord[]>; + getActivePurchases(): Promise<WalletPurchase[]>; getCoinsByPubs(coinPubs: string[]): Promise<WalletCoin[]>; diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts @@ -362,7 +362,7 @@ export async function applyDevExperiment( } rec.exchanges = [...exchangeSet]; rec.exchanges.sort(); - await tx.purchases.put(rec); + await tx.wtx.upsertPurchase(rec); } }); await rematerializeTransactions(wex, tx); @@ -666,7 +666,7 @@ async function addFakeTx( contractTermsRaw: ct, h: contractTermsHash, }); - await tx.purchases.put({ + await tx.wtx.upsertPurchase({ autoRefundDeadline: undefined, claimToken: encodeCrock(getRandomBytes(32)), downloadSessionId: encodeCrock(getRandomBytes(32)), diff --git a/packages/taler-wallet-core/src/donau.ts b/packages/taler-wallet-core/src/donau.ts @@ -391,7 +391,7 @@ export async function generateDonauPlanchets( proposalId: string, ): Promise<void> { const res = await wex.runLegacyWalletDbTx(async (tx) => { - const rec = await tx.purchases.get(proposalId); + const rec = await tx.wtx.getPurchase(proposalId); if (!rec) { return undefined; } @@ -516,7 +516,7 @@ export async function generateDonauPlanchets( logger.trace(`donau planchets: ${j2s(donauPlanchets)}`); await wex.runLegacyWalletDbTx(async (tx) => { - const rec = await tx.purchases.get(proposalId); + const rec = await tx.wtx.getPurchase(proposalId); if (!rec) { return undefined; } diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts @@ -3290,9 +3290,9 @@ async function purgeExchange( // Remove from purchases, refundGroups, refundItems { const purchases = - await tx.purchases.indexes.byExchange.getAll(exchangeBaseUrl); + await tx.wtx.getPurchasesByExchange(exchangeBaseUrl); for (const purch of purchases) { - const refunds = await tx.refundGroups.indexes.byProposalId.getAll( + const refunds = await tx.wtx.getRefundGroupsByProposal( purch.proposalId, ); for (const r of refunds) { diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -154,11 +154,11 @@ import { WalletCoin, WalletCoinSelection, WalletDonationPlanchet, + WalletPurchase, + WalletRefundGroup, + WalletRefundItem, } from "./db-common.js"; import { - PurchaseRecord, - RefundGroupRecord, - RefundItemRecord, RefundReason, SlateRecord, TokenRecord, @@ -229,7 +229,7 @@ export class PayMerchantTransactionContext implements TransactionContext { * Must be called each time the DB record for the transaction is updated. */ async updateTransactionMeta(tx: LegacyWalletTxHandle): Promise<void> { - const purchaseRec = await tx.purchases.get(this.proposalId); + const purchaseRec = await tx.wtx.getPurchase(this.proposalId); if (!purchaseRec) { await tx.wtx.deleteTransactionMeta(this.transactionId); return; @@ -254,7 +254,7 @@ export class PayMerchantTransactionContext implements TransactionContext { req?: LookupFullTransactionOpts, ): Promise<Transaction | undefined> { const proposalId = this.proposalId; - const purchaseRec = await tx.purchases.get(proposalId); + const purchaseRec = await tx.wtx.getPurchase(proposalId); if (!purchaseRec) { throw Error("not found"); } @@ -301,7 +301,7 @@ export class PayMerchantTransactionContext implements TransactionContext { ); const contractData = download.contractTerms; - const refundsInfo = await tx.refundGroups.indexes.byProposalId.getAll( + const refundsInfo = await tx.wtx.getRefundGroupsByProposal( purchaseRec.proposalId, ); @@ -425,7 +425,7 @@ export class PayMerchantTransactionContext implements TransactionContext { if (!rec) { return; } - let relatedTransactions: PurchaseRecord[] = []; + let relatedTransactions: WalletPurchase[] = []; // Automatically delete transactions that are a repurchase of this transaction, // as they're typically hidden. if ( @@ -434,7 +434,7 @@ export class PayMerchantTransactionContext implements TransactionContext { rec.purchaseStatus !== PurchaseStatus.DoneRepurchaseDetected ) { const otherTransactions = - await tx.purchases.indexes.byFulfillmentUrl.getAll( + await tx.wtx.getPurchasesByFulfillmentUrl( rec.download.fulfillmentUrl, ); for (const otx of otherTransactions) { @@ -568,15 +568,15 @@ export class PayMerchantTransactionContext implements TransactionContext { async getRecordHandle( tx: LegacyWalletTxHandle, - ): Promise<[PurchaseRecord | undefined, RecordHandle<PurchaseRecord>]> { - return getGenericRecordHandle<PurchaseRecord>( + ): Promise<[WalletPurchase | undefined, RecordHandle<WalletPurchase>]> { + return getGenericRecordHandle<WalletPurchase>( this, tx.wtx, - async () => tx.purchases.get(this.proposalId), + async () => tx.wtx.getPurchase(this.proposalId), async (r) => { - await tx.purchases.put(r); + await tx.wtx.upsertPurchase(r); }, - async () => tx.purchases.delete(this.proposalId), + async () => tx.wtx.deletePurchase(this.proposalId), (r) => computePayMerchantTransactionState(r), (r) => r.purchaseStatus, () => this.updateTransactionMeta(tx), @@ -586,7 +586,7 @@ export class PayMerchantTransactionContext implements TransactionContext { async function computePayMerchantExchangesInTx( tx: LegacyWalletTxHandle, - purchaseRecord: PurchaseRecord, + purchaseRecord: WalletPurchase, ): Promise<string[]> { if (purchaseRecord?.exchanges) { return purchaseRecord.exchanges; @@ -606,7 +606,7 @@ async function computePayMerchantExchangesInTx( async function computePayMerchantTransactionScopesInTx( tx: LegacyWalletTxHandle, - purchaseRec: PurchaseRecord, + purchaseRec: WalletPurchase, ): Promise<ScopeInfo[]> { if (purchaseRec.exchanges && purchaseRec.exchanges.length > 0) { return await getScopeForAllExchanges(tx, purchaseRec.exchanges); @@ -657,12 +657,12 @@ export class RefundTransactionContext implements TransactionContext { * Must be called each time the DB record for the transaction is updated. */ async updateTransactionMeta(tx: LegacyWalletTxHandle): Promise<void> { - const refundRec = await tx.refundGroups.get(this.refundGroupId); + const refundRec = await tx.wtx.getRefundGroup(this.refundGroupId); if (!refundRec) { await tx.wtx.deleteTransactionMeta(this.transactionId); return; } - const purchaseRecord = await tx.purchases.get(refundRec.proposalId); + const purchaseRecord = await tx.wtx.getPurchase(refundRec.proposalId); let exchanges: string[] = []; if (purchaseRecord) { exchanges = await computePayMerchantExchangesInTx(tx, purchaseRecord); @@ -679,7 +679,7 @@ export class RefundTransactionContext implements TransactionContext { async lookupFullTransaction( tx: LegacyWalletTxHandle, ): Promise<Transaction | undefined> { - const refundRecord = await tx.refundGroups.get(this.refundGroupId); + const refundRecord = await tx.wtx.getRefundGroup(this.refundGroupId); if (!refundRecord) { throw Error("not found"); } @@ -697,7 +697,7 @@ export class RefundTransactionContext implements TransactionContext { summary_i18n: maybeContractData.contractTerms.summary_i18n, }; } - const purchaseRecord = await tx.purchases.get(refundRecord.proposalId); + const purchaseRecord = await tx.wtx.getPurchase(refundRecord.proposalId); let scopes: ScopeInfo[]; if (purchaseRecord) { @@ -753,19 +753,17 @@ export class RefundTransactionContext implements TransactionContext { tx: LegacyWalletTxHandle, ): Promise<{ notifs: WalletNotification[] }> { const notifs: WalletNotification[] = []; - const refundRecord = await tx.refundGroups.get(this.refundGroupId); + const refundRecord = await tx.wtx.getRefundGroup(this.refundGroupId); if (!refundRecord) { return { notifs }; } - await tx.refundGroups.delete(this.refundGroupId); + await tx.wtx.deleteRefundGroup(this.refundGroupId); await this.updateTransactionMeta(tx); - await tx.tombstones.put({ id: this.transactionId }); - const items = await tx.refundItems.indexes.byRefundGroupId.getAll([ - refundRecord.refundGroupId, - ]); + await tx.wtx.upsertTombstone({ id: this.transactionId }); + const items = await tx.wtx.getRefundItemsByGroup(refundRecord.refundGroupId); for (const item of items) { if (item.id != null) { - await tx.refundItems.delete(item.id); + await tx.wtx.deleteRefundItem(item.id); } } return { notifs }; @@ -793,7 +791,7 @@ async function lookupMaybeContractData( proposalId: string, ): Promise<DownloadedContractData | undefined> { let contractData: DownloadedContractData | undefined = undefined; - const purchaseTx = await tx.purchases.get(proposalId); + const purchaseTx = await tx.wtx.getPurchase(proposalId); if (purchaseTx && purchaseTx.download) { const download = purchaseTx.download; const contractTermsRecord = await tx.wtx.getContractTerms( @@ -879,7 +877,7 @@ async function failProposalClaimPermanently( }); } -function getPayRequestTimeout(purchase: PurchaseRecord): Duration { +function getPayRequestTimeout(purchase: WalletPurchase): Duration { return Duration.multiply( { d_ms: 15000 }, 1 + (purchase.payInfo?.payCoinSelection?.coinPubs.length ?? 0) / 5, @@ -891,7 +889,7 @@ export async function expectProposalDownloadByIdInTx( tx: LegacyWalletTxHandle, proposalId: string, ): Promise<DownloadedContractData> { - const rec = await tx.purchases.get(proposalId); + const rec = await tx.wtx.getPurchase(proposalId); if (!rec) { throw Error("purchase record not found"); } @@ -901,7 +899,7 @@ export async function expectProposalDownloadByIdInTx( export async function expectProposalDownloadInTx( wex: WalletExecutionContext, tx: WalletDbTransaction, - p: PurchaseRecord, + p: WalletPurchase, ): Promise<DownloadedContractData> { if (!p.download) { throw Error("expected proposal to be downloaded"); @@ -929,7 +927,7 @@ export async function expectProposalDownloadInTx( */ async function expectProposalDownload( wex: WalletExecutionContext, - p: PurchaseRecord, + p: WalletPurchase, ): Promise<DownloadedContractData> { return await wex.runWalletDbTx(async (tx) => { return expectProposalDownloadInTx(wex, tx, p); @@ -941,7 +939,7 @@ async function processDownloadProposal( proposalId: string, ): Promise<TaskRunResult> { const proposal = await wex.runLegacyWalletDbTx(async (tx) => { - return await tx.purchases.get(proposalId); + return await tx.wtx.getPurchase(proposalId); }); if (!proposal) { @@ -1196,9 +1194,13 @@ async function processDownloadProposal( fulfillmentUrl && (fulfillmentUrl.startsWith("http://") || fulfillmentUrl.startsWith("https://")); - let repurchase: PurchaseRecord | undefined = undefined; - const otherPurchases = - await tx.purchases.indexes.byFulfillmentUrl.getAll(fulfillmentUrl); + let repurchase: WalletPurchase | undefined = undefined; + // Only consumed under isResourceFulfillmentUrl, which implies + // fulfillmentUrl is set. Previously this passed undefined straight to + // the index, which scans the whole store for a result that is then unused. + const otherPurchases = fulfillmentUrl + ? await tx.wtx.getPurchasesByFulfillmentUrl(fulfillmentUrl) + : []; if (isResourceFulfillmentUrl) { for (const otherPurchase of otherPurchases) { if ( @@ -1253,7 +1255,7 @@ async function startPayReplay( async function generateSlate( wex: WalletExecutionContext, - purchase: PurchaseRecord, + purchase: WalletPurchase, contractData: MerchantContractTermsV1, contractTermsRaw: any, choiceIndex: number, @@ -1354,10 +1356,7 @@ async function createOrReusePurchase( // Find existing proposals from the same merchant // with the same order ID. const oldProposals = await wex.runLegacyWalletDbTx(async (tx) => { - return tx.purchases.indexes.byUrlAndOrderId.getAll([ - merchantBaseUrl, - orderId, - ]); + return tx.wtx.getPurchasesByUrlAndOrderId(merchantBaseUrl, orderId); }); if (oldProposals.length > 1) { @@ -1463,7 +1462,7 @@ async function createOrReusePurchase( const secretSeed = encodeCrock(getRandomBytes(32)); - const proposalRecord: PurchaseRecord = { + const proposalRecord: WalletPurchase = { download: undefined, noncePriv: priv, noncePub: pub, @@ -1493,7 +1492,7 @@ async function createOrReusePurchase( const ctx = new PayMerchantTransactionContext(wex, proposalId); await wex.runLegacyWalletDbTx(async (tx) => { - await tx.purchases.put(proposalRecord); + await tx.wtx.upsertPurchase(proposalRecord); await ctx.updateTransactionMeta(tx); const oldTxState: TransactionState = { major: TransactionMajorState.None, @@ -1600,7 +1599,7 @@ async function storePayReplaySuccess( }); } -function setCoinSel(rec: PurchaseRecord, coinSel: PayCoinSelection): void { +function setCoinSel(rec: WalletPurchase, coinSel: PayCoinSelection): void { checkLogicInvariant(!!rec.payInfo); rec.payInfo.payCoinSelection = { coinContributions: coinSel.coins.map((x) => x.contribution), @@ -1616,7 +1615,7 @@ async function reselectCoinsTx( ctx: PayMerchantTransactionContext, excludeCoinPub?: string, ): Promise<void> { - const p = await tx.purchases.get(ctx.proposalId); + const p = await tx.wtx.getPurchase(ctx.proposalId); if (!p) { return; } @@ -1717,7 +1716,7 @@ async function reselectCoinsTx( }; } - await tx.purchases.put(p); + await tx.wtx.upsertPurchase(p); await ctx.updateTransactionMeta(tx); if (p.payInfo.payCoinSelection) { @@ -1757,7 +1756,7 @@ async function handleInsufficientFunds( const ctx = new PayMerchantTransactionContext(wex, proposalId); const proposal = await wex.runLegacyWalletDbTx(async (tx) => { - return tx.purchases.get(proposalId); + return tx.wtx.getPurchase(proposalId); }); if (!proposal) { return TaskRunResult.finished(); @@ -1844,14 +1843,14 @@ async function lookupProposalOrRepurchase( ): Promise< | { proposalId: string; - purchaseRec: PurchaseRecord; + purchaseRec: WalletPurchase; contractTerms: MerchantContractTerms; contractTermsHash: string; } | undefined > { return await wex.runLegacyWalletDbTx(async (tx) => { - let purchaseRec = await tx.purchases.get(proposalId); + let purchaseRec = await tx.wtx.getPurchase(proposalId); if (!purchaseRec) { return undefined; @@ -1861,7 +1860,7 @@ async function lookupProposalOrRepurchase( const existingProposalId = purchaseRec.repurchaseProposalId; if (existingProposalId) { logger.trace("using existing purchase for same product"); - const oldProposal = await tx.purchases.get(existingProposalId); + const oldProposal = await tx.wtx.getPurchase(existingProposalId); if (oldProposal) { purchaseRec = oldProposal; } @@ -2144,7 +2143,7 @@ async function checkPaymentByProposalId( } } -function isPurchasePaid(purchase: PurchaseRecord): boolean { +function isPurchasePaid(purchase: WalletPurchase): boolean { return ( purchase.purchaseStatus === PurchaseStatus.Done || purchase.purchaseStatus === PurchaseStatus.PendingQueryingRefund || @@ -2249,7 +2248,7 @@ async function waitProposalDownloaded( const { purchase, retryInfo } = await ctx.wex.runLegacyWalletDbTx( async (tx) => { return { - purchase: await tx.purchases.get(ctx.proposalId), + purchase: await tx.wtx.getPurchase(ctx.proposalId), retryInfo: await tx.wtx.getOperationRetry(ctx.taskId), }; }, @@ -2382,7 +2381,7 @@ async function waitPaymentResult( }, async checkState() { const txRes = await ctx.wex.runLegacyWalletDbTx(async (tx) => { - const purchase = await tx.purchases.get(ctx.proposalId); + const purchase = await tx.wtx.getPurchase(ctx.proposalId); const retryRecord = await tx.wtx.getOperationRetry(ctx.taskId); return { purchase, retryRecord }; }); @@ -2449,7 +2448,7 @@ export async function getChoicesForPayment( } const proposalId = parsedTx.proposalId; const { proposal, d } = await wex.runLegacyWalletDbTx(async (tx) => { - const proposal = await tx.purchases.get(proposalId); + const proposal = await tx.wtx.getPurchase(proposalId); let d: DownloadedContractData | undefined = undefined; if (proposal) { const download = proposal.download; @@ -2739,7 +2738,7 @@ export async function confirmPay( `executing confirmPay with proposalId ${proposalId} and sessionIdOverride ${sessionIdOverride}`, ); const proposal = await wex.runLegacyWalletDbTx(async (tx) => { - return tx.purchases.get(proposalId); + return tx.wtx.getPurchase(proposalId); }); if (!proposal) { @@ -2752,7 +2751,7 @@ export async function confirmPay( } const existingPurchase = await wex.runLegacyWalletDbTx(async (tx) => { - const purchase = await tx.purchases.get(proposalId); + const purchase = await tx.wtx.getPurchase(proposalId); if ( purchase && sessionIdOverride !== undefined && @@ -2763,7 +2762,7 @@ export async function confirmPay( if (purchase.purchaseStatus === PurchaseStatus.Done) { purchase.purchaseStatus = PurchaseStatus.PendingPayingReplay; } - await tx.purchases.put(purchase); + await tx.wtx.upsertPurchase(purchase); await ctx.updateTransactionMeta(tx); } return purchase; @@ -3031,7 +3030,7 @@ export async function processPurchase( proposalId: string, ): Promise<TaskRunResult> { const purchase = await wex.runLegacyWalletDbTx(async (tx) => { - return tx.purchases.get(proposalId); + return tx.wtx.getPurchase(proposalId); }); if (!purchase) { return { @@ -3099,7 +3098,7 @@ async function processPurchasePay( proposalId: string, ): Promise<TaskRunResult> { const purchase = await wex.runLegacyWalletDbTx(async (tx) => { - return tx.purchases.get(proposalId); + return tx.wtx.getPurchase(proposalId); }); if (!purchase) { return { @@ -3206,7 +3205,7 @@ async function processPurchasePay( ); const transitionDone = await wex.runLegacyWalletDbTx(async (tx) => { - const p = await tx.purchases.get(proposalId); + const p = await tx.wtx.getPurchase(proposalId); if (!p) { return false; } @@ -3228,7 +3227,7 @@ async function processPurchasePay( }; p.payInfo.payCoinSelectionUid = encodeCrock(getRandomBytes(16)); p.purchaseStatus = PurchaseStatus.PendingPaying; - await tx.purchases.put(p); + await tx.wtx.upsertPurchase(p); await ctx.updateTransactionMeta(tx); await spendCoins(wex, tx, { transactionId: ctx.transactionId, @@ -3495,7 +3494,7 @@ async function processPurchasePay( return; } purchase.payInfo.slateTokenSigs = tokenSigs; - tx.purchases.put(purchase); + tx.wtx.upsertPurchase(purchase); }); } @@ -3753,7 +3752,7 @@ const transitionResume: { }; export function computePayMerchantTransactionState( - purchaseRecord: PurchaseRecord, + purchaseRecord: WalletPurchase, ): TransactionState { switch (purchaseRecord.purchaseStatus) { // Pending States @@ -3910,7 +3909,7 @@ export function computePayMerchantTransactionState( } export function computePayMerchantTransactionActions( - purchaseRecord: PurchaseRecord, + purchaseRecord: WalletPurchase, ): TransactionAction[] { switch (purchaseRecord.purchaseStatus) { // Pending States @@ -4026,10 +4025,7 @@ export async function sharePayment( ): Promise<SharePaymentResult> { // First, translate the order ID into a proposal ID const proposalId = await wex.runLegacyWalletDbTx(async (tx) => { - const p = await tx.purchases.indexes.byUrlAndOrderId.get([ - merchantBaseUrl, - orderId, - ]); + const p = await tx.wtx.getPurchaseByUrlAndOrderId(merchantBaseUrl, orderId); return p?.proposalId; }); @@ -4118,12 +4114,12 @@ async function checkIfOrderIsAlreadyPaid( async function processPurchaseDialogProposed( wex: WalletExecutionContext, - purchase: PurchaseRecord, + purchase: WalletPurchase, ): Promise<TaskRunResult> { const proposalId = purchase.proposalId; const ctx = new PayMerchantTransactionContext(wex, proposalId); const txRes = await wex.runLegacyWalletDbTx(async (tx) => { - const rec = await tx.purchases.get(proposalId); + const rec = await tx.wtx.getPurchase(proposalId); if (!rec) { return undefined; } @@ -4165,13 +4161,13 @@ async function processPurchaseDialogProposed( */ async function processPurchaseDialogShared( wex: WalletExecutionContext, - purchase: PurchaseRecord, + purchase: WalletPurchase, ): Promise<TaskRunResult> { const proposalId = purchase.proposalId; logger.trace(`processing dialog-shared for proposal ${proposalId}`); const ctx = new PayMerchantTransactionContext(wex, proposalId); const txRes = await wex.runLegacyWalletDbTx(async (tx) => { - const rec = await tx.purchases.get(proposalId); + const rec = await tx.wtx.getPurchase(proposalId); if (!rec) { return undefined; } @@ -4241,7 +4237,7 @@ async function processPurchaseDialogShared( async function processPurchaseAutoRefund( wex: WalletExecutionContext, - purchase: PurchaseRecord, + purchase: WalletPurchase, ): Promise<TaskRunResult> { const proposalId = purchase.proposalId; const ctx = new PayMerchantTransactionContext(wex, proposalId); @@ -4266,7 +4262,7 @@ async function processPurchaseAutoRefund( ); const totalKnownRefund = await wex.runLegacyWalletDbTx(async (tx) => { - const refunds = await tx.refundGroups.indexes.byProposalId.getAll( + const refunds = await tx.wtx.getRefundGroupsByProposal( purchase.proposalId, ); const am = Amounts.parseOrThrow(amountRaw); @@ -4341,7 +4337,7 @@ async function processPurchaseAutoRefund( async function processPurchaseAbortingRefund( wex: WalletExecutionContext, - purchase: PurchaseRecord, + purchase: WalletPurchase, ): Promise<TaskRunResult> { const proposalId = purchase.proposalId; const ctx = new PayMerchantTransactionContext(wex, proposalId); @@ -4479,7 +4475,7 @@ async function processPurchaseAbortingRefund( async function processPurchaseQueryRefund( wex: WalletExecutionContext, - purchase: PurchaseRecord, + purchase: WalletPurchase, ): Promise<TaskRunResult> { const proposalId = purchase.proposalId; logger.trace(`processing query-refund for proposal ${proposalId}`); @@ -4531,7 +4527,7 @@ async function processPurchaseQueryRefund( async function processPurchaseAcceptRefund( wex: WalletExecutionContext, - purchase: PurchaseRecord, + purchase: WalletPurchase, ): Promise<TaskRunResult> { const download = await expectProposalDownload(wex, purchase); @@ -4573,10 +4569,7 @@ export async function startRefundQueryForUri( throw Error("expected taler://refund URI"); } const purchaseRecord = await wex.runLegacyWalletDbTx(async (tx) => { - return tx.purchases.indexes.byUrlAndOrderId.get([ - parsedUri.merchantBaseUrl, - parsedUri.orderId, - ]); + return tx.wtx.getPurchaseByUrlAndOrderId(parsedUri.merchantBaseUrl, parsedUri.orderId); }); if (!purchaseRecord) { logger.error( @@ -4618,7 +4611,7 @@ export async function startQueryRefund( async function computeRefreshRequest( wex: WalletExecutionContext, tx: LegacyWalletTxHandle, - items: RefundItemRecord[], + items: WalletRefundItem[], ): Promise<CoinRefreshRequest[]> { const refreshCoins: CoinRefreshRequest[] = []; for (const item of items) { @@ -4669,7 +4662,7 @@ function getItemStatus(rf: MerchantCoinRefundStatus): RefundItemStatus { */ async function storeRefunds( wex: WalletExecutionContext, - purchase: PurchaseRecord, + purchase: WalletPurchase, refunds: MerchantCoinRefundStatus[], reason: RefundReason, ): Promise<TaskRunResult> { @@ -4710,16 +4703,13 @@ async function storeRefunds( return; } - let newGroup: RefundGroupRecord | undefined = undefined; + let newGroup: WalletRefundGroup | undefined = undefined; // Pending, but not part of an aborted refund group. let numPendingItemsTotal = 0; - const newGroupRefunds: RefundItemRecord[] = []; + const newGroupRefunds: WalletRefundItem[] = []; for (const rf of refunds) { - const oldItem = await tx.refundItems.indexes.byCoinPubAndRtxid.get([ - rf.coin_pub, - rf.rtransaction_id, - ]); + const oldItem = await tx.wtx.getRefundItemByCoinAndRtxid(rf.coin_pub, rf.rtransaction_id); if (oldItem) { logger.info("already have refund in database"); if (oldItem.status === RefundItemStatus.Done) { @@ -4735,7 +4725,7 @@ async function storeRefunds( oldItem.status = RefundItemStatus.Failed; } } - await tx.refundItems.put(oldItem); + await tx.wtx.upsertRefundItem(oldItem); } else { // Put refund item into a new group! if (!newGroup) { @@ -4751,7 +4741,7 @@ async function storeRefunds( }; } const status: RefundItemStatus = getItemStatus(rf); - const newItem: RefundItemRecord = { + const newItem: WalletRefundItem = { coinPub: rf.coin_pub, executionTime: timestampProtocolToDb(rf.execution_time), obtainedTime: timestampPreciseToDb(now), @@ -4764,7 +4754,7 @@ async function storeRefunds( numPendingItemsTotal += 1; } newGroupRefunds.push(newItem); - await tx.refundItems.put(newItem); + await tx.wtx.upsertRefundItem(newItem); } } @@ -4793,7 +4783,7 @@ async function storeRefunds( wex, newGroup.refundGroupId, ); - await tx.refundGroups.put(newGroup); + await tx.wtx.upsertRefundGroup(newGroup); await refundCtx.updateTransactionMeta(tx); applyNotifyTransition(tx.notify, refundCtx.transactionId, { oldTxState: { major: TransactionMajorState.None }, @@ -4804,7 +4794,7 @@ async function storeRefunds( }); } - const refundGroups = await tx.refundGroups.indexes.byProposalId.getAll( + const refundGroups = await tx.wtx.getRefundGroupsByProposal( myPurchase.proposalId, ); @@ -4824,9 +4814,7 @@ async function storeRefunds( default: assertUnreachable(refundGroup.status); } - const items = await tx.refundItems.indexes.byRefundGroupId.getAll([ - refundGroup.refundGroupId, - ]); + const items = await tx.wtx.getRefundItemsByGroup(refundGroup.refundGroupId); let numPending = 0; let numFailed = 0; for (const item of items) { @@ -4847,7 +4835,7 @@ async function storeRefunds( } else { refundGroup.status = RefundGroupStatus.Failed; } - await tx.refundGroups.put(refundGroup); + await tx.wtx.upsertRefundGroup(refundGroup); await refundCtx.updateTransactionMeta(tx); const refreshCoins = await computeRefreshRequest(wex, tx, items); const newTxState: TransactionState = @@ -4912,7 +4900,7 @@ async function storeRefunds( } export function computeRefundTransactionState( - refundGroupRecord: RefundGroupRecord, + refundGroupRecord: WalletRefundGroup, ): TransactionState { switch (refundGroupRecord.status) { case RefundGroupStatus.Aborted: