gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: separate operations for pay,


From: gnunet
Subject: [taler-wallet-core] branch master updated: separate operations for pay, refund status query and refund submission
Date: Fri, 06 Dec 2019 00:24:41 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 65bccbd1 separate operations for pay, refund status query and refund 
submission
65bccbd1 is described below

commit 65bccbd139c53a2baccec442a680373125488102
Author: Florian Dold <address@hidden>
AuthorDate: Fri Dec 6 00:24:34 2019 +0100

    separate operations for pay, refund status query and refund submission
---
 src/dbTypes.ts               |  50 +++---
 src/wallet-impl/balance.ts   |   2 +-
 src/wallet-impl/exchanges.ts |  19 +-
 src/wallet-impl/history.ts   |   4 +-
 src/wallet-impl/pay.ts       | 418 ++++++++++++++++++++++++-------------------
 src/wallet-impl/pending.ts   |  66 ++++---
 src/wallet.ts                |  14 +-
 src/walletTypes.ts           |  20 ++-
 8 files changed, 359 insertions(+), 234 deletions(-)

diff --git a/src/dbTypes.ts b/src/dbTypes.ts
index 096c3f04..e39d7367 100644
--- a/src/dbTypes.ts
+++ b/src/dbTypes.ts
@@ -970,18 +970,6 @@ export interface WireFee {
   sig: string;
 }
 
-export enum PurchaseStatus {
-  /**
-   * We're currently paying, either for the first
-   * time or as a re-play potentially with a different
-   * session ID.
-   */
-  SubmitPay = "submit-pay",
-  QueryRefund = "query-refund",
-  ProcessRefund = "process-refund",
-  Abort = "abort",
-  Dormant = "dormant",
-}
 
 /**
  * Record that stores status information about one purchase, starting from when
@@ -994,11 +982,6 @@ export interface PurchaseRecord {
    */
   proposalId: string;
 
-  /**
-   * Status of this purchase.
-   */
-  status: PurchaseStatus;
-
   /**
    * Hash of the contract terms.
    */
@@ -1021,10 +1004,9 @@ export interface PurchaseRecord {
   merchantSig: string;
 
   /**
-   * The purchase isn't active anymore, it's either successfully paid or
-   * refunded/aborted.
+   * A successful payment has been made.
    */
-  finished: boolean;
+  payFinished: boolean;
 
   /**
    * Pending refunds for the purchase.
@@ -1046,13 +1028,15 @@ export interface PurchaseRecord {
    * When was the last refund made?
    * Set to 0 if no refund was made on the purchase.
    */
-  lastRefundTimestamp: Timestamp | undefined;
+  lastRefundStatusTimestamp: Timestamp | undefined;
 
   /**
    * Last session signature that we submitted to /pay (if any).
    */
   lastSessionId: string | undefined;
 
+  refundStatusRequested: boolean;
+
   /**
    * An abort (with refund) was requested for this (incomplete!) purchase.
    */
@@ -1063,9 +1047,29 @@ export interface PurchaseRecord {
    */
   abortDone: boolean;
 
-  retryInfo: RetryInfo;
+  payRetryInfo: RetryInfo;
 
-  lastError: OperationError | undefined;
+  lastPayError: OperationError | undefined;
+
+  /**
+   * Retry information for querying the refund status with the merchant.
+   */
+  refundStatusRetryInfo: RetryInfo;
+
+  /**
+   * Last error (or undefined) for querying the refund status with the 
merchant.
+   */
+  lastRefundStatusError: OperationError | undefined;
+
+    /**
+   * Retry information for querying the refund status with the merchant.
+   */
+  refundApplyRetryInfo: RetryInfo;
+
+  /**
+   * Last error (or undefined) for querying the refund status with the 
merchant.
+   */
+  lastRefundApplyError: OperationError | undefined;
 }
 
 /**
diff --git a/src/wallet-impl/balance.ts b/src/wallet-impl/balance.ts
index a1351014..082e6256 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.finished) {
+        if (t.payFinished) {
           return;
         }
         for (const c of t.payReq.coins) {
diff --git a/src/wallet-impl/exchanges.ts b/src/wallet-impl/exchanges.ts
index b6a2f1c8..3814971a 100644
--- a/src/wallet-impl/exchanges.ts
+++ b/src/wallet-impl/exchanges.ts
@@ -44,7 +44,10 @@ import {
 } from "../util/query";
 import * as Amounts from "../util/amounts";
 import { parsePaytoUri } from "../util/payto";
-import { OperationFailedAndReportedError } from "./errors";
+import {
+  OperationFailedAndReportedError,
+  guardOperationException,
+} from "./errors";
 
 async function denominationRecordFromKeys(
   ws: InternalWalletState,
@@ -307,12 +310,24 @@ async function updateExchangeWithWireInfo(
   });
 }
 
+export async function updateExchangeFromUrl(
+  ws: InternalWalletState,
+  baseUrl: string,
+  force: boolean = false,
+): Promise<ExchangeRecord> {
+  const onOpErr = (e: OperationError) => setExchangeError(ws, baseUrl, e);
+  return await guardOperationException(
+    () => updateExchangeFromUrlImpl(ws, baseUrl, force),
+    onOpErr,
+  );
+}
+
 /**
  * Update or add exchange DB entry by fetching the /keys and /wire information.
  * Optionally link the reserve entry to the new or existing
  * exchange entry in then DB.
  */
-export async function updateExchangeFromUrl(
+async function updateExchangeFromUrlImpl(
   ws: InternalWalletState,
   baseUrl: string,
   force: boolean = false,
diff --git a/src/wallet-impl/history.ts b/src/wallet-impl/history.ts
index 5e93ab87..23887e89 100644
--- a/src/wallet-impl/history.ts
+++ b/src/wallet-impl/history.ts
@@ -82,7 +82,7 @@ export async function getHistory(
       type: "pay",
       explicit: false,
     });
-    if (p.lastRefundTimestamp) {
+    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),
@@ -103,7 +103,7 @@ export async function getHistory(
           merchantName: p.contractTerms.merchant.name,
           refundAmount: amount,
         },
-        timestamp: p.lastRefundTimestamp,
+        timestamp: p.lastRefundStatusTimestamp,
         type: "refund",
         explicit: false,
       });
diff --git a/src/wallet-impl/pay.ts b/src/wallet-impl/pay.ts
index f07b0328..cec1b6bc 100644
--- a/src/wallet-impl/pay.ts
+++ b/src/wallet-impl/pay.ts
@@ -55,7 +55,6 @@ import {
   ProposalStatus,
   initRetryInfo,
   updateRetryInfoTimeout,
-  PurchaseStatus,
 } from "../dbTypes";
 import * as Amounts from "../util/amounts";
 import {
@@ -344,18 +343,22 @@ async function recordConfirmPay(
     abortRequested: false,
     contractTerms: d.contractTerms,
     contractTermsHash: d.contractTermsHash,
-    finished: false,
+    payFinished: false,
     lastSessionId: undefined,
     merchantSig: d.merchantSig,
     payReq,
     refundsDone: {},
     refundsPending: {},
     acceptTimestamp: getTimestampNow(),
-    lastRefundTimestamp: undefined,
+    lastRefundStatusTimestamp: undefined,
     proposalId: proposal.proposalId,
-    retryInfo: initRetryInfo(),
-    lastError: undefined,
-    status: PurchaseStatus.SubmitPay,
+    lastPayError: undefined,
+    lastRefundStatusError: undefined,
+    payRetryInfo: initRetryInfo(),
+    refundStatusRetryInfo: initRetryInfo(),
+    refundStatusRequested: false,
+    lastRefundApplyError: undefined,
+    refundApplyRetryInfo: initRetryInfo(),
   };
 
   await runWithWriteTransaction(
@@ -402,7 +405,7 @@ export async function abortFailedPayment(
   if (!purchase) {
     throw Error("Purchase not found, unable to abort with refund");
   }
-  if (purchase.finished) {
+  if (purchase.payFinished) {
     throw Error("Purchase already finished, not aborting");
   }
   if (purchase.abortDone) {
@@ -464,23 +467,65 @@ async function incrementProposalRetry(
   });
 }
 
-async function incrementPurchaseRetry(
+async function incrementPurchasePayRetry(
   ws: InternalWalletState,
   proposalId: string,
   err: OperationError | undefined,
 ): Promise<void> {
-  console.log("incrementing purchase retry with error", err);
+  console.log("incrementing purchase pay retry with error", err);
   await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => {
     const pr = await tx.get(Stores.purchases, proposalId);
     if (!pr) {
       return;
     }
-    if (!pr.retryInfo) {
+    if (!pr.payRetryInfo) {
       return;
     }
-    pr.retryInfo.retryCounter++;
-    updateRetryInfoTimeout(pr.retryInfo);
-    pr.lastError = err;
+    pr.payRetryInfo.retryCounter++;
+    updateRetryInfoTimeout(pr.payRetryInfo);
+    pr.lastPayError = err;
+    await tx.put(Stores.purchases, pr);
+  });
+}
+
+async function incrementPurchaseQueryRefundRetry(
+  ws: InternalWalletState,
+  proposalId: string,
+  err: OperationError | undefined,
+): Promise<void> {
+  console.log("incrementing purchase refund query retry with error", err);
+  await runWithWriteTransaction(ws.db, [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);
+  });
+}
+
+async function incrementPurchaseApplyRefundRetry(
+  ws: InternalWalletState,
+  proposalId: string,
+  err: OperationError | undefined,
+): Promise<void> {
+  console.log("incrementing purchase refund apply retry with error", err);
+  await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => {
+    const pr = await tx.get(Stores.purchases, proposalId);
+    if (!pr) {
+      return;
+    }
+    if (!pr.refundApplyRetryInfo) {
+      return;
+    }
+    pr.refundApplyRetryInfo.retryCounter++;
+    updateRetryInfoTimeout(pr.refundStatusRetryInfo);
+    pr.lastRefundApplyError = err;
     await tx.put(Stores.purchases, pr);
   });
 }
@@ -652,10 +697,9 @@ export async function submitPay(
     // FIXME: properly display error
     throw Error("merchant payment signature invalid");
   }
-  purchase.finished = true;
-  purchase.status = PurchaseStatus.Dormant;
-  purchase.lastError = undefined;
-  purchase.retryInfo = initRetryInfo(false);
+  purchase.payFinished = true;
+  purchase.lastPayError = undefined;
+  purchase.payRetryInfo = initRetryInfo(false);
   const modifiedCoins: CoinRecord[] = [];
   for (const pc of purchase.payReq.coins) {
     const c = await oneShotGet(ws.db, Stores.coins, pc.coin_pub);
@@ -986,90 +1030,6 @@ export async function getFullRefundFees(
   return feeAcc;
 }
 
-async function submitRefundsToExchange(
-  ws: InternalWalletState,
-  proposalId: string,
-): Promise<void> {
-  const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
-  if (!purchase) {
-    console.error("not submitting refunds, payment not found:");
-    return;
-  }
-  const pendingKeys = Object.keys(purchase.refundsPending);
-  if (pendingKeys.length === 0) {
-    console.log("no pending refunds");
-    return;
-  }
-  for (const pk of pendingKeys) {
-    const perm = purchase.refundsPending[pk];
-    const req: RefundRequest = {
-      coin_pub: perm.coin_pub,
-      h_contract_terms: purchase.contractTermsHash,
-      merchant_pub: purchase.contractTerms.merchant_pub,
-      merchant_sig: perm.merchant_sig,
-      refund_amount: perm.refund_amount,
-      refund_fee: perm.refund_fee,
-      rtransaction_id: perm.rtransaction_id,
-    };
-    console.log("sending refund permission", perm);
-    // FIXME: not correct once we support multiple exchanges per payment
-    const exchangeUrl = purchase.payReq.coins[0].exchange_url;
-    const reqUrl = new URL("refund", exchangeUrl);
-    const resp = await ws.http.postJson(reqUrl.href, req);
-    console.log("sent refund permission");
-    if (resp.status !== 200) {
-      console.error("refund failed", resp);
-      continue;
-    }
-
-    let allRefundsProcessed = false;
-
-    await runWithWriteTransaction(
-      ws.db,
-      [Stores.purchases, Stores.coins],
-      async tx => {
-        const p = await tx.get(Stores.purchases, proposalId);
-        if (!p) {
-          return;
-        }
-        if (p.refundsPending[pk]) {
-          p.refundsDone[pk] = p.refundsPending[pk];
-          delete p.refundsPending[pk];
-        }
-        if (Object.keys(p.refundsPending).length === 0) {
-          p.retryInfo = initRetryInfo();
-          p.lastError = undefined;
-          p.status = PurchaseStatus.Dormant;
-          allRefundsProcessed = true;
-        }
-        await tx.put(Stores.purchases, p);
-        const c = await tx.get(Stores.coins, perm.coin_pub);
-        if (!c) {
-          console.warn("coin not found, can't apply refund");
-          return;
-        }
-        const refundAmount = Amounts.parseOrThrow(perm.refund_amount);
-        const refundFee = Amounts.parseOrThrow(perm.refund_fee);
-        c.status = CoinStatus.Dirty;
-        c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount;
-        c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount;
-        await tx.put(Stores.coins, c);
-      },
-    );
-    if (allRefundsProcessed) {
-      ws.notify({
-        type: NotificationType.RefundFinished,
-      })
-    }
-    await refresh(ws, perm.coin_pub);
-  }
-
-  ws.notify({
-    type: NotificationType.RefundsSubmitted,
-    proposalId,
-  });
-}
-
 async function acceptRefundResponse(
   ws: InternalWalletState,
   proposalId: string,
@@ -1082,60 +1042,45 @@ async function acceptRefundResponse(
     throw Error("empty refund");
   }
 
-  /**
-   * Add refund to purchase if not already added.
-   */
-  function f(t: PurchaseRecord | undefined): PurchaseRecord | undefined {
-    if (!t) {
+
+  let numNewRefunds = 0;
+
+  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");
       return;
     }
 
-    t.lastRefundTimestamp = getTimestampNow();
-    t.status = PurchaseStatus.ProcessRefund;
-    t.lastError = undefined;
-    t.retryInfo = initRetryInfo();
+    if (!p.refundStatusRequested) {
+      return;
+    }
+
+    p.lastRefundStatusTimestamp = getTimestampNow();
+    p.lastRefundStatusError = undefined;
+    p.refundStatusRetryInfo = initRetryInfo();
+    p.refundStatusRequested = false;
 
     for (const perm of refundPermissions) {
       if (
-        !t.refundsPending[perm.merchant_sig] &&
-        !t.refundsDone[perm.merchant_sig]
+        !p.refundsPending[perm.merchant_sig] &&
+        !p.refundsDone[perm.merchant_sig]
       ) {
-        t.refundsPending[perm.merchant_sig] = perm;
+        p.refundsPending[perm.merchant_sig] = perm;
+        numNewRefunds++;
       }
     }
-    return t;
-  }
-  // Add the refund permissions to the purchase within a DB transaction
-  await oneShotMutate(ws.db, Stores.purchases, proposalId, f);
-  await submitRefundsToExchange(ws, proposalId);
-}
 
-async function queryRefund(
-  ws: InternalWalletState,
-  proposalId: string,
-): Promise<void> {
-  const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
-  if (purchase?.status !== PurchaseStatus.QueryRefund) {
-    return;
-  }
+    if (numNewRefunds) {
+      p.lastRefundApplyError = undefined;
+      p.refundApplyRetryInfo = initRetryInfo();
+    }
 
-  const refundUrlObj = new URL(
-    "refund",
-    purchase.contractTerms.merchant_base_url,
-  );
-  refundUrlObj.searchParams.set("order_id", purchase.contractTerms.order_id);
-  const refundUrl = refundUrlObj.href;
-  let resp;
-  try {
-    resp = await ws.http.get(refundUrl);
-  } catch (e) {
-    console.error("error downloading refund permission", e);
-    throw e;
+    await tx.put(Stores.purchases, p);
+  });
+  if (numNewRefunds > 0) {
+    await processPurchaseApplyRefund(ws, proposalId);
   }
-
-  const refundResponse = MerchantRefundResponse.checked(resp.responseJson);
-  await acceptRefundResponse(ws, proposalId, refundResponse);
 }
 
 async function startRefundQuery(
@@ -1151,21 +1096,12 @@ async function startRefundQuery(
         console.log("no purchase found for refund URL");
         return false;
       }
-      if (p.status === PurchaseStatus.QueryRefund) {
-        return true;
-      }
-      if (p.status === PurchaseStatus.ProcessRefund) {
-        return true;
-      }
-      if (p.status !== PurchaseStatus.Dormant) {
-        console.log(
-          `can't apply refund, as payment isn't done (status ${p.status})`,
-        );
-        return false;
+      if (p.refundStatusRequested) {
+
       }
-      p.lastError = undefined;
-      p.status = PurchaseStatus.QueryRefund;
-      p.retryInfo = initRetryInfo();
+      p.refundStatusRequested = true;
+      p.lastRefundStatusError = undefined;
+      p.refundStatusRetryInfo = initRetryInfo();
       await tx.put(Stores.purchases, p);
       return true;
     },
@@ -1175,7 +1111,7 @@ async function startRefundQuery(
     return;
   }
 
-  await processPurchase(ws, proposalId);
+  await processPurchaseQueryRefund(ws, proposalId);
 }
 
 /**
@@ -1210,19 +1146,19 @@ export async function applyRefund(
   return purchase.contractTermsHash;
 }
 
-export async function processPurchase(
+export async function processPurchasePay(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<void> {
   const onOpErr = (e: OperationError) =>
-    incrementPurchaseRetry(ws, proposalId, e);
+    incrementPurchasePayRetry(ws, proposalId, e);
   await guardOperationException(
-    () => processPurchaseImpl(ws, proposalId),
+    () => processPurchasePayImpl(ws, proposalId),
     onOpErr,
   );
 }
 
-async function processPurchaseImpl(
+async function processPurchasePayImpl(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<void> {
@@ -1230,24 +1166,146 @@ async function processPurchaseImpl(
   if (!purchase) {
     return;
   }
-  logger.trace(`processing purchase ${proposalId}`);
-  switch (purchase.status) {
-    case PurchaseStatus.Dormant:
-      return;
-    case PurchaseStatus.Abort:
-      // FIXME
-      break;
-    case PurchaseStatus.SubmitPay:
-      break;
-    case PurchaseStatus.QueryRefund:
-      await queryRefund(ws, proposalId);
-      break;
-    case PurchaseStatus.ProcessRefund:
-      console.log("submitting refunds to exchange (toplvl)");
-      await submitRefundsToExchange(ws, proposalId);
-      console.log("after submitting refunds to exchange (toplvl)");
-      break;
-    default:
-      throw assertUnreachable(purchase.status);
+  logger.trace(`processing purchase pay ${proposalId}`);
+  if (purchase.payFinished) {
+    return;
+  }
+  await submitPay(ws, proposalId, purchase.lastSessionId);
+}
+
+export async function processPurchaseQueryRefund(
+  ws: InternalWalletState,
+  proposalId: string,
+): Promise<void> {
+  const onOpErr = (e: OperationError) =>
+    incrementPurchaseQueryRefundRetry(ws, proposalId, e);
+  await guardOperationException(
+    () => processPurchaseQueryRefundImpl(ws, proposalId),
+    onOpErr,
+  );
+}
+
+async function processPurchaseQueryRefundImpl(
+  ws: InternalWalletState,
+  proposalId: string,
+): Promise<void> {
+  const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
+  if (!purchase) {
+    return;
+  }
+  if (!purchase.refundStatusRequested) {
+    return;
+  }
+
+  const refundUrlObj = new URL(
+    "refund",
+    purchase.contractTerms.merchant_base_url,
+  );
+  refundUrlObj.searchParams.set("order_id", purchase.contractTerms.order_id);
+  const refundUrl = refundUrlObj.href;
+  let resp;
+  try {
+    resp = await ws.http.get(refundUrl);
+  } catch (e) {
+    console.error("error downloading refund permission", e);
+    throw e;
+  }
+
+  const refundResponse = MerchantRefundResponse.checked(resp.responseJson);
+  await acceptRefundResponse(ws, proposalId, refundResponse);
+}
+
+export async function processPurchaseApplyRefund(
+  ws: InternalWalletState,
+  proposalId: string,
+): Promise<void> {
+  const onOpErr = (e: OperationError) =>
+    incrementPurchaseApplyRefundRetry(ws, proposalId, e);
+  await guardOperationException(
+    () => processPurchaseApplyRefundImpl(ws, proposalId),
+    onOpErr,
+  );
+}
+
+async function processPurchaseApplyRefundImpl(
+  ws: InternalWalletState,
+  proposalId: string,
+): Promise<void> {
+  const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
+  if (!purchase) {
+    console.error("not submitting refunds, payment not found:");
+    return;
+  }
+  const pendingKeys = Object.keys(purchase.refundsPending);
+  if (pendingKeys.length === 0) {
+    console.log("no pending refunds");
+    return;
+  }
+  for (const pk of pendingKeys) {
+    const perm = purchase.refundsPending[pk];
+    const req: RefundRequest = {
+      coin_pub: perm.coin_pub,
+      h_contract_terms: purchase.contractTermsHash,
+      merchant_pub: purchase.contractTerms.merchant_pub,
+      merchant_sig: perm.merchant_sig,
+      refund_amount: perm.refund_amount,
+      refund_fee: perm.refund_fee,
+      rtransaction_id: perm.rtransaction_id,
+    };
+    console.log("sending refund permission", perm);
+    // FIXME: not correct once we support multiple exchanges per payment
+    const exchangeUrl = purchase.payReq.coins[0].exchange_url;
+    const reqUrl = new URL("refund", exchangeUrl);
+    const resp = await ws.http.postJson(reqUrl.href, req);
+    console.log("sent refund permission");
+    if (resp.status !== 200) {
+      console.error("refund failed", resp);
+      continue;
+    }
+
+    let allRefundsProcessed = false;
+
+    await runWithWriteTransaction(
+      ws.db,
+      [Stores.purchases, Stores.coins],
+      async tx => {
+        const p = await tx.get(Stores.purchases, proposalId);
+        if (!p) {
+          return;
+        }
+        if (p.refundsPending[pk]) {
+          p.refundsDone[pk] = p.refundsPending[pk];
+          delete p.refundsPending[pk];
+        }
+        if (Object.keys(p.refundsPending).length === 0) {
+          p.refundStatusRetryInfo = initRetryInfo();
+          p.lastRefundStatusError = undefined;
+          allRefundsProcessed = true;
+        }
+        await tx.put(Stores.purchases, p);
+        const c = await tx.get(Stores.coins, perm.coin_pub);
+        if (!c) {
+          console.warn("coin not found, can't apply refund");
+          return;
+        }
+        const refundAmount = Amounts.parseOrThrow(perm.refund_amount);
+        const refundFee = Amounts.parseOrThrow(perm.refund_fee);
+        c.status = CoinStatus.Dirty;
+        c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount;
+        c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount;
+        await tx.put(Stores.coins, c);
+      },
+    );
+    if (allRefundsProcessed) {
+      ws.notify({
+        type: NotificationType.RefundFinished,
+      });
+    }
+    await refresh(ws, perm.coin_pub);
   }
+
+  ws.notify({
+    type: NotificationType.RefundsSubmitted,
+    proposalId,
+  });
 }
diff --git a/src/wallet-impl/pending.ts b/src/wallet-impl/pending.ts
index c86ed695..169c7e29 100644
--- a/src/wallet-impl/pending.ts
+++ b/src/wallet-impl/pending.ts
@@ -32,9 +32,7 @@ import {
   ReserveRecordStatus,
   CoinStatus,
   ProposalStatus,
-  PurchaseStatus,
 } from "../dbTypes";
-import { assertUnreachable } from "../util/assertUnreachable";
 
 function updateRetryDelay(
   oldDelay: Duration,
@@ -355,28 +353,54 @@ async function gatherPurchasePending(
   onlyDue: boolean = false,
 ): Promise<void> {
   await tx.iter(Stores.purchases).forEach((pr) => {
-    if (pr.status === PurchaseStatus.Dormant) {
-      return;
+    if (!pr.payFinished) {
+      resp.nextRetryDelay = updateRetryDelay(
+        resp.nextRetryDelay,
+        now,
+        pr.payRetryInfo.nextRetry,
+      );
+      resp.pendingOperations.push({
+        type: "pay",
+        givesLifeness: true,
+        isReplay: false,
+        proposalId: pr.proposalId,
+        retryInfo: pr.payRetryInfo,
+        lastError: pr.lastPayError,
+      });
     }
-    resp.nextRetryDelay = updateRetryDelay(
-      resp.nextRetryDelay,
-      now,
-      pr.retryInfo.nextRetry,
-    );
-    if (onlyDue && pr.retryInfo.nextRetry.t_ms > now.t_ms) {
-      return;
+    if (pr.refundStatusRequested) {
+      resp.nextRetryDelay = updateRetryDelay(
+        resp.nextRetryDelay,
+        now,
+        pr.refundStatusRetryInfo.nextRetry,
+      );
+      resp.pendingOperations.push({
+        type: "refund-query",
+        givesLifeness: true,
+        proposalId: pr.proposalId,
+        retryInfo: pr.refundStatusRetryInfo,
+        lastError: pr.lastRefundStatusError,
+      });
+    }
+    const numRefundsPending = Object.keys(pr.refundsPending).length;
+    if (numRefundsPending > 0) {
+      const numRefundsDone = Object.keys(pr.refundsDone).length;
+      resp.nextRetryDelay = updateRetryDelay(
+        resp.nextRetryDelay,
+        now,
+        pr.refundApplyRetryInfo.nextRetry,
+      );
+      resp.pendingOperations.push({
+        type: "refund-apply",
+        numRefundsDone,
+        numRefundsPending,
+        givesLifeness: true,
+        proposalId: pr.proposalId,
+        retryInfo: pr.refundApplyRetryInfo,
+        lastError: pr.lastRefundApplyError,
+      });
     }
-    resp.pendingOperations.push({
-      type: "pay",
-      givesLifeness: true,
-      isReplay: false,
-      proposalId: pr.proposalId,
-      status: pr.status,
-      retryInfo: pr.retryInfo,
-      lastError: pr.lastError,
-    });
   });
-
 }
 
 export async function getPendingOperations(
diff --git a/src/wallet.ts b/src/wallet.ts
index 489bb2af..276e3c37 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -49,7 +49,9 @@ import {
   processDownloadProposal,
   applyRefund,
   getFullRefundFees,
-  processPurchase,
+  processPurchasePay,
+  processPurchaseQueryRefund,
+  processPurchaseApplyRefund,
 } from "./wallet-impl/pay";
 
 import {
@@ -210,7 +212,13 @@ export class Wallet {
         await processTip(this.ws, pending.tipId);
         break;
       case "pay":
-        await processPurchase(this.ws, pending.proposalId);
+        await processPurchasePay(this.ws, pending.proposalId);
+        break;
+      case "refund-query":
+        await processPurchaseQueryRefund(this.ws, pending.proposalId);
+        break;
+      case "refund-apply":
+        await processPurchaseApplyRefund(this.ws, pending.proposalId);
         break;
       default:
         assertUnreachable(pending);
@@ -710,7 +718,7 @@ export class Wallet {
     const totalFees = totalRefundFees;
     return {
       contractTerms: purchase.contractTerms,
-      hasRefund: purchase.lastRefundTimestamp !== undefined,
+      hasRefund: purchase.lastRefundStatusTimestamp !== undefined,
       totalRefundAmount: totalRefundAmount,
       totalRefundAndRefreshFees: totalFees,
     };
diff --git a/src/walletTypes.ts b/src/walletTypes.ts
index 2413234e..f2797033 100644
--- a/src/walletTypes.ts
+++ b/src/walletTypes.ts
@@ -37,7 +37,6 @@ import {
   ExchangeWireInfo,
   WithdrawalSource,
   RetryInfo,
-  PurchaseStatus,
 } from "./dbTypes";
 import { CoinPaySig, ContractTerms, PayReq } from "./talerTypes";
 
@@ -681,11 +680,26 @@ export interface PendingPayOperation {
   type: "pay";
   proposalId: string;
   isReplay: boolean;
-  status: PurchaseStatus;
   retryInfo: RetryInfo,
   lastError: OperationError | undefined;
 }
 
+export interface PendingRefundQueryOperation {
+  type: "refund-query";
+  proposalId: string;
+  retryInfo: RetryInfo,
+  lastError: OperationError | undefined;
+}
+
+export interface PendingRefundApplyOperation {
+  type: "refund-apply";
+  proposalId: string;
+  retryInfo: RetryInfo,
+  lastError: OperationError | undefined;
+  numRefundsPending: number;
+  numRefundsDone: number;
+}
+
 export interface PendingOperationInfoCommon {
   type: string;
   givesLifeness: boolean;
@@ -703,6 +717,8 @@ export type PendingOperationInfo = 
PendingOperationInfoCommon &
     | PendingProposalDownloadOperation
     | PendingProposalChoiceOperation
     | PendingPayOperation
+    | PendingRefundQueryOperation
+    | PendingRefundApplyOperation
   );
 
 export interface PendingOperationsResponse {

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]