diff options
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( | 
