aboutsummaryrefslogtreecommitdiff
path: root/src/wallet-impl
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-12-06 02:52:16 +0100
committerFlorian Dold <florian.dold@gmail.com>2019-12-06 02:52:16 +0100
commit4159367d8c04b96a05da8a5ded043f8296a83174 (patch)
tree5587538fb13aead06d639a03e96cc09adcd99dd7 /src/wallet-impl
parentd8170c815913b4395febc4ce3402fe45ba0d2e87 (diff)
downloadwallet-core-4159367d8c04b96a05da8a5ded043f8296a83174.tar.xz
pending ops / history / notification tweaks
Diffstat (limited to 'src/wallet-impl')
-rw-r--r--src/wallet-impl/balance.ts2
-rw-r--r--src/wallet-impl/history.ts307
-rw-r--r--src/wallet-impl/pay.ts23
-rw-r--r--src/wallet-impl/pending.ts2
-rw-r--r--src/wallet-impl/refresh.ts1
-rw-r--r--src/wallet-impl/reserves.ts5
-rw-r--r--src/wallet-impl/tip.ts3
-rw-r--r--src/wallet-impl/withdraw.ts14
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(