diff options
author | Florian Dold <florian.dold@gmail.com> | 2019-12-06 02:52:16 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2019-12-06 02:52:16 +0100 |
commit | 4159367d8c04b96a05da8a5ded043f8296a83174 (patch) | |
tree | 5587538fb13aead06d639a03e96cc09adcd99dd7 /src/wallet-impl | |
parent | d8170c815913b4395febc4ce3402fe45ba0d2e87 (diff) | |
download | wallet-core-4159367d8c04b96a05da8a5ded043f8296a83174.tar.xz |
pending ops / history / notification tweaks
Diffstat (limited to 'src/wallet-impl')
-rw-r--r-- | src/wallet-impl/balance.ts | 2 | ||||
-rw-r--r-- | src/wallet-impl/history.ts | 307 | ||||
-rw-r--r-- | src/wallet-impl/pay.ts | 23 | ||||
-rw-r--r-- | src/wallet-impl/pending.ts | 2 | ||||
-rw-r--r-- | src/wallet-impl/refresh.ts | 1 | ||||
-rw-r--r-- | src/wallet-impl/reserves.ts | 5 | ||||
-rw-r--r-- | src/wallet-impl/tip.ts | 3 | ||||
-rw-r--r-- | src/wallet-impl/withdraw.ts | 14 |
8 files changed, 214 insertions, 143 deletions
diff --git a/src/wallet-impl/balance.ts b/src/wallet-impl/balance.ts index 082e62563..8ce91a173 100644 --- a/src/wallet-impl/balance.ts +++ b/src/wallet-impl/balance.ts @@ -138,7 +138,7 @@ export async function getBalances( }); await tx.iter(Stores.purchases).forEach(t => { - if (t.payFinished) { + if (t.firstSuccessfulPayTimestamp) { return; } for (const c of t.payReq.coins) { diff --git a/src/wallet-impl/history.ts b/src/wallet-impl/history.ts index 23887e895..99e51c8de 100644 --- a/src/wallet-impl/history.ts +++ b/src/wallet-impl/history.ts @@ -14,11 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - /** - * Imports. - */ +/** + * Imports. + */ import { HistoryQuery, HistoryEvent } from "../walletTypes"; -import { oneShotIter } from "../util/query"; +import { oneShotIter, runWithReadTransaction } from "../util/query"; import { InternalWalletState } from "./state"; import { Stores, TipRecord } from "../dbTypes"; import * as Amounts from "../util/amounts"; @@ -34,139 +34,186 @@ export async function getHistory( const history: HistoryEvent[] = []; // FIXME: do pagination instead of generating the full history - // We uniquely identify history rows via their timestamp. // This works as timestamps are guaranteed to be monotonically // increasing even - /* - const proposals = await oneShotIter(ws.db, Stores.proposals).toArray(); - for (const p of proposals) { - history.push({ - detail: { - contractTermsHash: p.contractTermsHash, - merchantName: p.contractTerms.merchant.name, - }, - timestamp: p.timestamp, - type: "claim-order", - explicit: false, - }); - } - */ - - const withdrawals = await oneShotIter( + await runWithReadTransaction( ws.db, - Stores.withdrawalSession, - ).toArray(); - for (const w of withdrawals) { - history.push({ - detail: { - withdrawalAmount: w.rawWithdrawalAmount, - }, - timestamp: w.startTimestamp, - type: "withdraw", - explicit: false, - }); - } - - const purchases = await oneShotIter(ws.db, Stores.purchases).toArray(); - for (const p of purchases) { - history.push({ - detail: { - amount: p.contractTerms.amount, - contractTermsHash: p.contractTermsHash, - fulfillmentUrl: p.contractTerms.fulfillment_url, - merchantName: p.contractTerms.merchant.name, - }, - timestamp: p.acceptTimestamp, - type: "pay", - explicit: false, - }); - if (p.lastRefundStatusTimestamp) { - const contractAmount = Amounts.parseOrThrow(p.contractTerms.amount); - const amountsPending = Object.keys(p.refundsPending).map(x => - Amounts.parseOrThrow(p.refundsPending[x].refund_amount), - ); - const amountsDone = Object.keys(p.refundsDone).map(x => - Amounts.parseOrThrow(p.refundsDone[x].refund_amount), - ); - const amounts: AmountJson[] = amountsPending.concat(amountsDone); - const amount = Amounts.add( - Amounts.getZero(contractAmount.currency), - ...amounts, - ).amount; - - history.push({ - detail: { - contractTermsHash: p.contractTermsHash, - fulfillmentUrl: p.contractTerms.fulfillment_url, - merchantName: p.contractTerms.merchant.name, - refundAmount: amount, - }, - timestamp: p.lastRefundStatusTimestamp, - type: "refund", - explicit: false, + [ + Stores.currencies, + Stores.coins, + Stores.denominations, + Stores.exchanges, + Stores.proposals, + Stores.purchases, + Stores.refresh, + Stores.reserves, + Stores.tips, + Stores.withdrawalSession, + ], + async tx => { + await tx.iter(Stores.proposals).forEach(p => { + history.push({ + detail: {}, + timestamp: p.timestamp, + type: "claim-order", + explicit: false, + }); + }); + + await tx.iter(Stores.withdrawalSession).forEach(w => { + history.push({ + detail: { + withdrawalAmount: w.rawWithdrawalAmount, + }, + timestamp: w.startTimestamp, + type: "withdraw-started", + explicit: false, + }); + if (w.finishTimestamp) { + history.push({ + detail: { + withdrawalAmount: w.rawWithdrawalAmount, + }, + timestamp: w.finishTimestamp, + type: "withdraw-finished", + explicit: false, + }); + } + }); + + await tx.iter(Stores.purchases).forEach(p => { + history.push({ + detail: { + amount: p.contractTerms.amount, + contractTermsHash: p.contractTermsHash, + fulfillmentUrl: p.contractTerms.fulfillment_url, + merchantName: p.contractTerms.merchant.name, + }, + timestamp: p.acceptTimestamp, + type: "pay-started", + explicit: false, + }); + if (p.firstSuccessfulPayTimestamp) { + history.push({ + detail: { + amount: p.contractTerms.amount, + contractTermsHash: p.contractTermsHash, + fulfillmentUrl: p.contractTerms.fulfillment_url, + merchantName: p.contractTerms.merchant.name, + }, + timestamp: p.firstSuccessfulPayTimestamp, + type: "pay-finished", + explicit: false, + }); + } + if (p.lastRefundStatusTimestamp) { + const contractAmount = Amounts.parseOrThrow(p.contractTerms.amount); + const amountsPending = Object.keys(p.refundsPending).map(x => + Amounts.parseOrThrow(p.refundsPending[x].refund_amount), + ); + const amountsDone = Object.keys(p.refundsDone).map(x => + Amounts.parseOrThrow(p.refundsDone[x].refund_amount), + ); + const amounts: AmountJson[] = amountsPending.concat(amountsDone); + const amount = Amounts.add( + Amounts.getZero(contractAmount.currency), + ...amounts, + ).amount; + + history.push({ + detail: { + contractTermsHash: p.contractTermsHash, + fulfillmentUrl: p.contractTerms.fulfillment_url, + merchantName: p.contractTerms.merchant.name, + refundAmount: amount, + }, + timestamp: p.lastRefundStatusTimestamp, + type: "refund", + explicit: false, + }); + } }); - } - } - - const reserves = await oneShotIter(ws.db, Stores.reserves).toArray(); - - for (const r of reserves) { - const reserveType = r.bankWithdrawStatusUrl ? "taler-bank" : "manual"; - history.push({ - detail: { - exchangeBaseUrl: r.exchangeBaseUrl, - requestedAmount: Amounts.toString(r.initiallyRequestedAmount), - reservePub: r.reservePub, - reserveType, - bankWithdrawStatusUrl: r.bankWithdrawStatusUrl, - }, - timestamp: r.created, - type: "reserve-created", - explicit: false, - }); - if (r.timestampConfirmed) { - history.push({ - detail: { - exchangeBaseUrl: r.exchangeBaseUrl, - requestedAmount: Amounts.toString(r.initiallyRequestedAmount), - reservePub: r.reservePub, - reserveType, - bankWithdrawStatusUrl: r.bankWithdrawStatusUrl, - }, - timestamp: r.created, - type: "reserve-confirmed", - explicit: false, + + await tx.iter(Stores.reserves).forEach(r => { + const reserveType = r.bankWithdrawStatusUrl ? "taler-bank" : "manual"; + history.push({ + detail: { + exchangeBaseUrl: r.exchangeBaseUrl, + requestedAmount: Amounts.toString(r.initiallyRequestedAmount), + reservePub: r.reservePub, + reserveType, + bankWithdrawStatusUrl: r.bankWithdrawStatusUrl, + }, + timestamp: r.created, + type: "reserve-created", + explicit: false, + }); + if (r.timestampConfirmed) { + history.push({ + detail: { + exchangeBaseUrl: r.exchangeBaseUrl, + requestedAmount: Amounts.toString(r.initiallyRequestedAmount), + reservePub: r.reservePub, + reserveType, + bankWithdrawStatusUrl: r.bankWithdrawStatusUrl, + }, + timestamp: r.created, + type: "reserve-confirmed", + explicit: false, + }); + } + }); + + await tx.iter(Stores.tips).forEach(tip => { + history.push({ + detail: { + accepted: tip.accepted, + amount: tip.amount, + merchantBaseUrl: tip.merchantBaseUrl, + tipId: tip.merchantTipId, + }, + timestamp: tip.createdTimestamp, + explicit: false, + type: "tip", + }); + }); + + await tx.iter(Stores.exchanges).forEach(exchange => { + history.push({ + type: "exchange-added", + explicit: false, + timestamp: exchange.timestampAdded, + detail: { + exchangeBaseUrl: exchange.baseUrl, + }, + }); + }); + + await tx.iter(Stores.refresh).forEach((r) => { + history.push({ + type: "refresh-started", + explicit: false, + timestamp: r.created, + detail: { + refreshSessionId: r.refreshSessionId, + }, + }); + if (r.finishedTimestamp) { + history.push({ + type: "refresh-finished", + explicit: false, + timestamp: r.finishedTimestamp, + detail: { + refreshSessionId: r.refreshSessionId, + }, + }); + } + }); - } - } - - const tips: TipRecord[] = await oneShotIter(ws.db, Stores.tips).toArray(); - for (const tip of tips) { - history.push({ - detail: { - accepted: tip.accepted, - amount: tip.amount, - merchantBaseUrl: tip.merchantBaseUrl, - tipId: tip.merchantTipId, - }, - timestamp: tip.createdTimestamp, - explicit: false, - type: "tip", - }); - } - - await oneShotIter(ws.db, Stores.exchanges).forEach(exchange => { - history.push({ - type: "exchange-added", - explicit: false, - timestamp: exchange.timestampAdded, - detail: { - exchangeBaseUrl: exchange.baseUrl, - }, - }); - }); + }, + ); history.sort((h1, h2) => Math.sign(h1.timestamp.t_ms - h2.timestamp.t_ms)); diff --git a/src/wallet-impl/pay.ts b/src/wallet-impl/pay.ts index 1375a22ca..4933098cd 100644 --- a/src/wallet-impl/pay.ts +++ b/src/wallet-impl/pay.ts @@ -343,7 +343,6 @@ async function recordConfirmPay( abortRequested: false, contractTerms: d.contractTerms, contractTermsHash: d.contractTermsHash, - payFinished: false, lastSessionId: undefined, merchantSig: d.merchantSig, payReq, @@ -359,6 +358,7 @@ async function recordConfirmPay( refundStatusRequested: false, lastRefundApplyError: undefined, refundApplyRetryInfo: initRetryInfo(), + firstSuccessfulPayTimestamp: undefined, }; await runWithWriteTransaction( @@ -405,7 +405,7 @@ export async function abortFailedPayment( if (!purchase) { throw Error("Purchase not found, unable to abort with refund"); } - if (purchase.payFinished) { + if (purchase.firstSuccessfulPayTimestamp) { throw Error("Purchase already finished, not aborting"); } if (purchase.abortDone) { @@ -465,6 +465,7 @@ async function incrementProposalRetry( pr.lastError = err; await tx.put(Stores.proposals, pr); }); + ws.notify({ type: NotificationType.ProposalOperationError }); } async function incrementPurchasePayRetry( @@ -486,6 +487,7 @@ async function incrementPurchasePayRetry( pr.lastPayError = err; await tx.put(Stores.purchases, pr); }); + ws.notify({ type: NotificationType.PayOperationError }); } async function incrementPurchaseQueryRefundRetry( @@ -507,6 +509,7 @@ async function incrementPurchaseQueryRefundRetry( pr.lastRefundStatusError = err; await tx.put(Stores.purchases, pr); }); + ws.notify({ type: NotificationType.RefundStatusOperationError }); } async function incrementPurchaseApplyRefundRetry( @@ -528,6 +531,7 @@ async function incrementPurchaseApplyRefundRetry( pr.lastRefundApplyError = err; await tx.put(Stores.purchases, pr); }); + ws.notify({ type: NotificationType.RefundApplyOperationError }); } export async function processDownloadProposal( @@ -698,7 +702,7 @@ export async function submitPay( // FIXME: properly display error throw Error("merchant payment signature invalid"); } - purchase.payFinished = true; + purchase.firstSuccessfulPayTimestamp = getTimestampNow(); purchase.lastPayError = undefined; purchase.payRetryInfo = initRetryInfo(false); const modifiedCoins: CoinRecord[] = []; @@ -1044,10 +1048,9 @@ async function acceptRefundResponse( throw Error("empty refund"); } - let numNewRefunds = 0; - await runWithWriteTransaction(ws.db, [Stores.purchases], async (tx) => { + await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => { const p = await tx.get(Stores.purchases, proposalId); if (!p) { console.error("purchase not found, not adding refunds"); @@ -1080,6 +1083,9 @@ async function acceptRefundResponse( await tx.put(Stores.purchases, p); }); + ws.notify({ + type: NotificationType.RefundQueried, + }); if (numNewRefunds > 0) { await processPurchaseApplyRefund(ws, proposalId); } @@ -1099,7 +1105,6 @@ async function startRefundQuery( return false; } if (p.refundStatusRequested) { - } p.refundStatusRequested = true; p.lastRefundStatusError = undefined; @@ -1113,6 +1118,10 @@ async function startRefundQuery( return; } + ws.notify({ + type: NotificationType.RefundStarted, + }); + await processPurchaseQueryRefund(ws, proposalId); } @@ -1169,7 +1178,7 @@ async function processPurchasePayImpl( return; } logger.trace(`processing purchase pay ${proposalId}`); - if (purchase.payFinished) { + if (purchase.firstSuccessfulPayTimestamp) { return; } await submitPay(ws, proposalId, purchase.lastSessionId); diff --git a/src/wallet-impl/pending.ts b/src/wallet-impl/pending.ts index 169c7e291..b0ae71f6b 100644 --- a/src/wallet-impl/pending.ts +++ b/src/wallet-impl/pending.ts @@ -353,7 +353,7 @@ async function gatherPurchasePending( onlyDue: boolean = false, ): Promise<void> { await tx.iter(Stores.purchases).forEach((pr) => { - if (!pr.payFinished) { + if (!pr.firstSuccessfulPayTimestamp) { resp.nextRetryDelay = updateRetryDelay( resp.nextRetryDelay, now, diff --git a/src/wallet-impl/refresh.ts b/src/wallet-impl/refresh.ts index a3b48919d..93be1435d 100644 --- a/src/wallet-impl/refresh.ts +++ b/src/wallet-impl/refresh.ts @@ -312,6 +312,7 @@ async function incrementRefreshRetry( r.lastError = err; await tx.put(Stores.refresh, r); }); + ws.notify({ type: NotificationType.RefreshOperationError }); } diff --git a/src/wallet-impl/reserves.ts b/src/wallet-impl/reserves.ts index f00956b46..8a700bebc 100644 --- a/src/wallet-impl/reserves.ts +++ b/src/wallet-impl/reserves.ts @@ -342,6 +342,7 @@ async function incrementReserveRetry( r.lastError = err; await tx.put(Stores.reserves, r); }); + ws.notify({ type: NotificationType.ReserveOperationError }); } /** @@ -606,6 +607,10 @@ async function depleteReserve( if (success) { console.log("processing new withdraw session"); + ws.notify({ + type: NotificationType.WithdrawSessionCreated, + withdrawSessionId: withdrawalSessionId, + }); await processWithdrawSession(ws, withdrawalSessionId); } else { console.trace("withdraw session already existed"); diff --git a/src/wallet-impl/tip.ts b/src/wallet-impl/tip.ts index 3ae931d45..9cfaed930 100644 --- a/src/wallet-impl/tip.ts +++ b/src/wallet-impl/tip.ts @@ -18,7 +18,7 @@ import { oneShotGet, oneShotPut, oneShotMutate, runWithWriteTransaction } from "../util/query"; import { InternalWalletState } from "./state"; import { parseTipUri } from "../util/taleruri"; -import { TipStatus, getTimestampNow, OperationError } from "../walletTypes"; +import { TipStatus, getTimestampNow, OperationError, NotificationType } from "../walletTypes"; import { TipPickupGetResponse, TipPlanchetDetail, TipResponse } from "../talerTypes"; import * as Amounts from "../util/amounts"; import { Stores, PlanchetRecord, WithdrawalSessionRecord, initRetryInfo, updateRetryInfoTimeout } from "../dbTypes"; @@ -122,6 +122,7 @@ async function incrementTipRetry( t.lastError = err; await tx.put(Stores.tips, t); }); + ws.notify({ type: NotificationType.TipOperationError }); } export async function processTip( diff --git a/src/wallet-impl/withdraw.ts b/src/wallet-impl/withdraw.ts index 3122a463c..5d89f64a9 100644 --- a/src/wallet-impl/withdraw.ts +++ b/src/wallet-impl/withdraw.ts @@ -260,17 +260,17 @@ async function processPlanchet( let withdrawSessionFinished = false; let reserveDepleted = false; - await runWithWriteTransaction( + const success = await runWithWriteTransaction( ws.db, [Stores.coins, Stores.withdrawalSession, Stores.reserves], async tx => { const ws = await tx.get(Stores.withdrawalSession, withdrawalSessionId); if (!ws) { - return; + return false; } if (ws.withdrawn[coinIdx]) { // Already withdrawn - return; + return false; } ws.withdrawn[coinIdx] = true; ws.lastCoinErrors[coinIdx] = undefined; @@ -301,9 +301,16 @@ async function processPlanchet( } } await tx.add(Stores.coins, coin); + return true; }, ); + if (success) { + ws.notify( { + type: NotificationType.CoinWithdrawn, + } ); + } + if (withdrawSessionFinished) { ws.notify({ type: NotificationType.WithdrawSessionFinished, @@ -503,6 +510,7 @@ async function incrementWithdrawalRetry( wsr.lastError = err; await tx.put(Stores.withdrawalSession, wsr); }); + ws.notify({ type: NotificationType.WithdrawOperationError }); } export async function processWithdrawSession( |