diff options
author | Florian Dold <florian@dold.me> | 2021-06-09 15:14:17 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2021-06-09 15:24:19 +0200 |
commit | 5c26461247040c07c86291babf0c87631df638b5 (patch) | |
tree | 8ff93454d0c21d2675d6734f210d4e8ff91d2bfb /packages/taler-wallet-core/src/operations/refund.ts | |
parent | 68dddc848f2f650d74697bb3a5c05d649e5db3c7 (diff) | |
download | wallet-core-5c26461247040c07c86291babf0c87631df638b5.tar.xz |
database access refactor
Diffstat (limited to 'packages/taler-wallet-core/src/operations/refund.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/refund.ts | 257 |
1 files changed, 151 insertions, 106 deletions
diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts index 2e2ab7803..ba0674f06 100644 --- a/packages/taler-wallet-core/src/operations/refund.ts +++ b/packages/taler-wallet-core/src/operations/refund.ts @@ -48,13 +48,21 @@ import { } from "@gnu-taler/taler-util"; import { Logger } from "@gnu-taler/taler-util"; import { readSuccessResponseJsonOrThrow } from "../util/http"; -import { TransactionHandle } from "../util/query"; import { URL } from "../util/url"; import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries"; import { checkDbInvariant } from "../util/invariants"; import { TalerErrorCode } from "@gnu-taler/taler-util"; -import { Stores, PurchaseRecord, CoinStatus, RefundState, AbortStatus, RefundReason } from "../db.js"; +import { + PurchaseRecord, + CoinStatus, + RefundState, + AbortStatus, + RefundReason, + WalletStoresV1, +} from "../db.js"; import { getTotalRefreshCost, createRefreshGroup } from "./refresh.js"; +import { GetReadWriteAccess } from "../util/query.js"; +import { Wallet } from "../wallet.js"; const logger = new Logger("refund.ts"); @@ -66,19 +74,23 @@ async function incrementPurchaseQueryRefundRetry( proposalId: string, err: TalerErrorDetails | undefined, ): Promise<void> { - await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => { - const pr = await tx.get(Stores.purchases, proposalId); - if (!pr) { - return; - } - if (!pr.refundStatusRetryInfo) { - return; - } - pr.refundStatusRetryInfo.retryCounter++; - updateRetryInfoTimeout(pr.refundStatusRetryInfo); - pr.lastRefundStatusError = err; - await tx.put(Stores.purchases, pr); - }); + await ws.db + .mktx((x) => ({ + purchases: x.purchases, + })) + .runReadWrite(async (tx) => { + const pr = await tx.purchases.get(proposalId); + if (!pr) { + return; + } + if (!pr.refundStatusRetryInfo) { + return; + } + pr.refundStatusRetryInfo.retryCounter++; + updateRetryInfoTimeout(pr.refundStatusRetryInfo); + pr.lastRefundStatusError = err; + await tx.purchases.put(pr); + }); if (err) { ws.notify({ type: NotificationType.RefundStatusOperationError, @@ -92,7 +104,10 @@ function getRefundKey(d: MerchantCoinRefundStatus): string { } async function applySuccessfulRefund( - tx: TransactionHandle<typeof Stores.coins | typeof Stores.denominations>, + tx: GetReadWriteAccess<{ + coins: typeof WalletStoresV1.coins; + denominations: typeof WalletStoresV1.denominations; + }>, p: PurchaseRecord, refreshCoinsMap: Record<string, { coinPub: string }>, r: MerchantCoinRefundSuccessStatus, @@ -100,12 +115,12 @@ async function applySuccessfulRefund( // FIXME: check signature before storing it as valid! const refundKey = getRefundKey(r); - const coin = await tx.get(Stores.coins, r.coin_pub); + const coin = await tx.coins.get(r.coin_pub); if (!coin) { logger.warn("coin not found, can't apply refund"); return; } - const denom = await tx.get(Stores.denominations, [ + const denom = await tx.denominations.get([ coin.exchangeBaseUrl, coin.denomPubHash, ]); @@ -119,13 +134,10 @@ async function applySuccessfulRefund( coin.currentAmount = Amounts.add(coin.currentAmount, refundAmount).amount; coin.currentAmount = Amounts.sub(coin.currentAmount, refundFee).amount; logger.trace(`coin amount after is ${Amounts.stringify(coin.currentAmount)}`); - await tx.put(Stores.coins, coin); + await tx.coins.put(coin); - const allDenoms = await tx - .iterIndexed( - Stores.denominations.exchangeBaseUrlIndex, - coin.exchangeBaseUrl, - ) + const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl + .iter(coin.exchangeBaseUrl) .toArray(); const amountLeft = Amounts.sub( @@ -153,18 +165,21 @@ async function applySuccessfulRefund( } async function storePendingRefund( - tx: TransactionHandle<typeof Stores.denominations | typeof Stores.coins>, + tx: GetReadWriteAccess<{ + denominations: typeof WalletStoresV1.denominations; + coins: typeof WalletStoresV1.coins; + }>, p: PurchaseRecord, r: MerchantCoinRefundFailureStatus, ): Promise<void> { const refundKey = getRefundKey(r); - const coin = await tx.get(Stores.coins, r.coin_pub); + const coin = await tx.coins.get(r.coin_pub); if (!coin) { logger.warn("coin not found, can't apply refund"); return; } - const denom = await tx.get(Stores.denominations, [ + const denom = await tx.denominations.get([ coin.exchangeBaseUrl, coin.denomPubHash, ]); @@ -173,11 +188,8 @@ async function storePendingRefund( throw Error("inconsistent database"); } - const allDenoms = await tx - .iterIndexed( - Stores.denominations.exchangeBaseUrlIndex, - coin.exchangeBaseUrl, - ) + const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl + .iter(coin.exchangeBaseUrl) .toArray(); const amountLeft = Amounts.sub( @@ -205,19 +217,22 @@ async function storePendingRefund( } async function storeFailedRefund( - tx: TransactionHandle<typeof Stores.coins | typeof Stores.denominations>, + tx: GetReadWriteAccess<{ + coins: typeof WalletStoresV1.coins; + denominations: typeof WalletStoresV1.denominations; + }>, p: PurchaseRecord, refreshCoinsMap: Record<string, { coinPub: string }>, r: MerchantCoinRefundFailureStatus, ): Promise<void> { const refundKey = getRefundKey(r); - const coin = await tx.get(Stores.coins, r.coin_pub); + const coin = await tx.coins.get(r.coin_pub); if (!coin) { logger.warn("coin not found, can't apply refund"); return; } - const denom = await tx.get(Stores.denominations, [ + const denom = await tx.denominations.get([ coin.exchangeBaseUrl, coin.denomPubHash, ]); @@ -226,11 +241,8 @@ async function storeFailedRefund( throw Error("inconsistent database"); } - const allDenoms = await tx - .iterIndexed( - Stores.denominations.exchangeBaseUrlIndex, - coin.exchangeBaseUrl, - ) + const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl + .iter(coin.exchangeBaseUrl) .toArray(); const amountLeft = Amounts.sub( @@ -260,12 +272,12 @@ async function storeFailedRefund( // Refund failed because the merchant didn't even try to deposit // the coin yet, so we try to refresh. if (r.exchange_code === TalerErrorCode.EXCHANGE_REFUND_DEPOSIT_NOT_FOUND) { - const coin = await tx.get(Stores.coins, r.coin_pub); + const coin = await tx.coins.get(r.coin_pub); if (!coin) { logger.warn("coin not found, can't apply refund"); return; } - const denom = await tx.get(Stores.denominations, [ + const denom = await tx.denominations.get([ coin.exchangeBaseUrl, coin.denomPubHash, ]); @@ -287,7 +299,7 @@ async function storeFailedRefund( ).amount; } refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub }; - await tx.put(Stores.coins, coin); + await tx.coins.put(coin); } } } @@ -301,15 +313,15 @@ async function acceptRefunds( logger.trace("handling refunds", refunds); const now = getTimestampNow(); - await ws.db.runWithWriteTransaction( - [ - Stores.purchases, - Stores.coins, - Stores.denominations, - Stores.refreshGroups, - ], - async (tx) => { - const p = await tx.get(Stores.purchases, proposalId); + await ws.db + .mktx((x) => ({ + purchases: x.purchases, + coins: x.coins, + denominations: x.denominations, + refreshGroups: x.refreshGroups, + })) + .runReadWrite(async (tx) => { + const p = await tx.purchases.get(proposalId); if (!p) { logger.error("purchase not found, not adding refunds"); return; @@ -409,9 +421,8 @@ async function acceptRefunds( logger.trace("refund query not done"); } - await tx.put(Stores.purchases, p); - }, - ); + await tx.purchases.put(p); + }); ws.notify({ type: NotificationType.RefundQueried, @@ -444,10 +455,16 @@ export async function applyRefund( throw Error("invalid refund URI"); } - let purchase = await ws.db.getIndexed(Stores.purchases.orderIdIndex, [ - parseResult.merchantBaseUrl, - parseResult.orderId, - ]); + let purchase = await ws.db + .mktx((x) => ({ + purchases: x.purchases, + })) + .runReadOnly(async (tx) => { + return tx.purchases.indexes.byMerchantUrlAndOrderId.get([ + parseResult.merchantBaseUrl, + parseResult.orderId, + ]); + }); if (!purchase) { throw Error( @@ -458,10 +475,12 @@ export async function applyRefund( const proposalId = purchase.proposalId; logger.info("processing purchase for refund"); - const success = await ws.db.runWithWriteTransaction( - [Stores.purchases], - async (tx) => { - const p = await tx.get(Stores.purchases, proposalId); + const success = await ws.db + .mktx((x) => ({ + purchases: x.purchases, + })) + .runReadWrite(async (tx) => { + const p = await tx.purchases.get(proposalId); if (!p) { logger.error("no purchase found for refund URL"); return false; @@ -469,10 +488,9 @@ export async function applyRefund( p.refundQueryRequested = true; p.lastRefundStatusError = undefined; p.refundStatusRetryInfo = initRetryInfo(); - await tx.put(Stores.purchases, p); + await tx.purchases.put(p); return true; - }, - ); + }); if (success) { ws.notify({ @@ -481,7 +499,13 @@ export async function applyRefund( await processPurchaseQueryRefund(ws, proposalId); } - purchase = await ws.db.get(Stores.purchases, proposalId); + purchase = await ws.db + .mktx((x) => ({ + purchases: x.purchases, + })) + .runReadOnly(async (tx) => { + return tx.purchases.get(proposalId); + }); if (!purchase) { throw Error("purchase no longer exists"); @@ -559,12 +583,17 @@ async function resetPurchaseQueryRefundRetry( ws: InternalWalletState, proposalId: string, ): Promise<void> { - await ws.db.mutate(Stores.purchases, proposalId, (x) => { - if (x.refundStatusRetryInfo.active) { - x.refundStatusRetryInfo = initRetryInfo(); - } - return x; - }); + await ws.db + .mktx((x) => ({ + purchases: x.purchases, + })) + .runReadWrite(async (tx) => { + const x = await tx.purchases.get(proposalId); + if (x && x.refundStatusRetryInfo.active) { + x.refundStatusRetryInfo = initRetryInfo(); + await tx.purchases.put(x); + } + }); } async function processPurchaseQueryRefundImpl( @@ -575,7 +604,13 @@ async function processPurchaseQueryRefundImpl( if (forceNow) { await resetPurchaseQueryRefundRetry(ws, proposalId); } - const purchase = await ws.db.get(Stores.purchases, proposalId); + const purchase = await ws.db + .mktx((x) => ({ + purchases: x.purchases, + })) + .runReadOnly(async (tx) => { + return tx.purchases.get(proposalId); + }); if (!purchase) { return; } @@ -589,7 +624,6 @@ async function processPurchaseQueryRefundImpl( `orders/${purchase.download.contractData.orderId}/refund`, purchase.download.contractData.merchantBaseUrl, ); - logger.trace(`making refund request to ${requestUrl.href}`); @@ -620,18 +654,25 @@ async function processPurchaseQueryRefundImpl( ); const abortingCoins: AbortingCoin[] = []; - for (let i = 0; i < purchase.payCoinSelection.coinPubs.length; i++) { - const coinPub = purchase.payCoinSelection.coinPubs[i]; - const coin = await ws.db.get(Stores.coins, coinPub); - checkDbInvariant(!!coin, "expected coin to be present"); - abortingCoins.push({ - coin_pub: coinPub, - contribution: Amounts.stringify( - purchase.payCoinSelection.coinContributions[i], - ), - exchange_url: coin.exchangeBaseUrl, + + await ws.db + .mktx((x) => ({ + coins: x.coins, + })) + .runReadOnly(async (tx) => { + for (let i = 0; i < purchase.payCoinSelection.coinPubs.length; i++) { + const coinPub = purchase.payCoinSelection.coinPubs[i]; + const coin = await tx.coins.get(coinPub); + checkDbInvariant(!!coin, "expected coin to be present"); + abortingCoins.push({ + coin_pub: coinPub, + contribution: Amounts.stringify( + purchase.payCoinSelection.coinContributions[i], + ), + exchange_url: coin.exchangeBaseUrl, + }); + } }); - } const abortReq: AbortRequest = { h_contract: purchase.download.contractData.contractTermsHash, @@ -678,26 +719,30 @@ export async function abortFailedPayWithRefund( ws: InternalWalletState, proposalId: string, ): Promise<void> { - await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => { - const purchase = await tx.get(Stores.purchases, proposalId); - if (!purchase) { - throw Error("purchase not found"); - } - if (purchase.timestampFirstSuccessfulPay) { - // No point in aborting it. We don't even report an error. - logger.warn(`tried to abort successful payment`); - return; - } - if (purchase.abortStatus !== AbortStatus.None) { - return; - } - purchase.refundQueryRequested = true; - purchase.paymentSubmitPending = false; - purchase.abortStatus = AbortStatus.AbortRefund; - purchase.lastPayError = undefined; - purchase.payRetryInfo = initRetryInfo(false); - await tx.put(Stores.purchases, purchase); - }); + await ws.db + .mktx((x) => ({ + purchases: x.purchases, + })) + .runReadWrite(async (tx) => { + const purchase = await tx.purchases.get(proposalId); + if (!purchase) { + throw Error("purchase not found"); + } + if (purchase.timestampFirstSuccessfulPay) { + // No point in aborting it. We don't even report an error. + logger.warn(`tried to abort successful payment`); + return; + } + if (purchase.abortStatus !== AbortStatus.None) { + return; + } + purchase.refundQueryRequested = true; + purchase.paymentSubmitPending = false; + purchase.abortStatus = AbortStatus.AbortRefund; + purchase.lastPayError = undefined; + purchase.payRetryInfo = initRetryInfo(false); + await tx.purchases.put(purchase); + }); processPurchaseQueryRefund(ws, proposalId, true).catch((e) => { logger.trace(`error during refund processing after abort pay: ${e}`); }); |