gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: adopt new merchant refund API


From: gnunet
Subject: [taler-wallet-core] branch master updated: adopt new merchant refund API
Date: Mon, 27 Apr 2020 17:41:28 +0200

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 5be0708a adopt new merchant refund API
5be0708a is described below

commit 5be0708a10874be939e63ca82082cd665165823c
Author: Florian Dold <address@hidden>
AuthorDate: Mon Apr 27 21:11:20 2020 +0530

    adopt new merchant refund API
---
 src/operations/history.ts |   8 +-
 src/operations/pay.ts     |  76 +-------
 src/operations/pending.ts |  20 ---
 src/operations/refund.ts  | 448 +++++++++++++++++-----------------------------
 src/types/dbTypes.ts      |  58 +++---
 src/types/pending.ts      |  16 --
 src/types/talerTypes.ts   |  60 +++----
 src/wallet.ts             |  23 +--
 8 files changed, 224 insertions(+), 485 deletions(-)

diff --git a/src/operations/history.ts b/src/operations/history.ts
index efbfbf37..f32dbbe2 100644
--- a/src/operations/history.ts
+++ b/src/operations/history.ts
@@ -453,8 +453,8 @@ export async function getHistory(
         let amountRefundedRaw = Amounts.getZero(purchaseAmount.currency);
         let amountRefundedInvalid = Amounts.getZero(purchaseAmount.currency);
         let amountRefundedEffective = Amounts.getZero(purchaseAmount.currency);
-        Object.keys(purchase.refundState.refundsDone).forEach((x, i) => {
-          const r = purchase.refundState.refundsDone[x];
+        Object.keys(purchase.refundsDone).forEach((x, i) => {
+          const r = purchase.refundsDone[x];
           if (r.refundGroupId !== re.refundGroupId) {
             return;
           }
@@ -471,8 +471,8 @@ export async function getHistory(
             refundFee,
           ).amount;
         });
-        Object.keys(purchase.refundState.refundsFailed).forEach((x, i) => {
-          const r = purchase.refundState.refundsFailed[x];
+        Object.keys(purchase.refundsFailed).forEach((x, i) => {
+          const r = purchase.refundsFailed[x];
           if (r.refundGroupId !== re.refundGroupId) {
             return;
           }
diff --git a/src/operations/pay.ts b/src/operations/pay.ts
index 337068b5..a7528439 100644
--- a/src/operations/pay.ts
+++ b/src/operations/pay.ts
@@ -31,7 +31,6 @@ import {
   ProposalRecord,
   ProposalStatus,
   PurchaseRecord,
-  RefundReason,
   Stores,
   updateRetryInfoTimeout,
   PayEventRecord,
@@ -40,7 +39,6 @@ import {
 import { NotificationType } from "../types/notifications";
 import {
   PayReq,
-  codecForMerchantRefundResponse,
   codecForProposal,
   codecForContractTerms,
   CoinDepositPermission,
@@ -57,7 +55,6 @@ import { Logger } from "../util/logging";
 import { getOrderDownloadUrl, parsePayUri } from "../util/taleruri";
 import { guardOperationException } from "./errors";
 import { createRefreshGroup, getTotalRefreshCost } from "./refresh";
-import { acceptRefundResponse } from "./refund";
 import { InternalWalletState } from "./state";
 import { getTimestampNow, timestampAddDuration } from "../util/time";
 import { strcmp, canonicalJson } from "../util/helpers";
@@ -446,17 +443,13 @@ async function recordConfirmPay(
     payRetryInfo: initRetryInfo(),
     refundStatusRetryInfo: initRetryInfo(),
     refundStatusRequested: false,
-    lastRefundApplyError: undefined,
-    refundApplyRetryInfo: initRetryInfo(),
     timestampFirstSuccessfulPay: undefined,
     autoRefundDeadline: undefined,
     paymentSubmitPending: true,
-    refundState: {
-      refundGroups: [],
-      refundsDone: {},
-      refundsFailed: {},
-      refundsPending: {},
-    },
+    refundGroups: [],
+    refundsDone: {},
+    refundsFailed: {},
+    refundsPending: {},
   };
 
   await ws.db.runWithWriteTransaction(
@@ -511,67 +504,6 @@ function getNextUrl(contractData: WalletContractData): 
string {
   }
 }
 
-export async function abortFailedPayment(
-  ws: InternalWalletState,
-  proposalId: string,
-): Promise<void> {
-  const purchase = await ws.db.get(Stores.purchases, proposalId);
-  if (!purchase) {
-    throw Error("Purchase not found, unable to abort with refund");
-  }
-  if (purchase.timestampFirstSuccessfulPay) {
-    throw Error("Purchase already finished, not aborting");
-  }
-  if (purchase.abortDone) {
-    console.warn("abort requested on already aborted purchase");
-    return;
-  }
-
-  purchase.abortRequested = true;
-
-  // From now on, we can't retry payment anymore,
-  // so mark this in the DB in case the /pay abort
-  // does not complete on the first try.
-  await ws.db.put(Stores.purchases, purchase);
-
-  let resp;
-
-  const abortReq = { ...purchase.payReq, mode: "abort-refund" };
-
-  const payUrl = new URL("pay", purchase.contractData.merchantBaseUrl).href;
-
-  try {
-    resp = await ws.http.postJson(payUrl, abortReq);
-  } catch (e) {
-    // Gives the user the option to retry / abort and refresh
-    console.log("aborting payment failed", e);
-    throw e;
-  }
-
-  if (resp.status !== 200) {
-    throw Error(`unexpected status for /pay (${resp.status})`);
-  }
-
-  const refundResponse = codecForMerchantRefundResponse().decode(
-    await resp.json(),
-  );
-  await acceptRefundResponse(
-    ws,
-    purchase.proposalId,
-    refundResponse,
-    RefundReason.AbortRefund,
-  );
-
-  await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => {
-    const p = await tx.get(Stores.purchases, proposalId);
-    if (!p) {
-      return;
-    }
-    p.abortDone = true;
-    await tx.put(Stores.purchases, p);
-  });
-}
-
 async function incrementProposalRetry(
   ws: InternalWalletState,
   proposalId: string,
diff --git a/src/operations/pending.ts b/src/operations/pending.ts
index 3e548a27..a797763b 100644
--- a/src/operations/pending.ts
+++ b/src/operations/pending.ts
@@ -396,26 +396,6 @@ async function gatherPurchasePending(
         });
       }
     }
-    const numRefundsPending = 
Object.keys(pr.refundState.refundsPending).length;
-    if (numRefundsPending > 0) {
-      const numRefundsDone = Object.keys(pr.refundState.refundsDone).length;
-      resp.nextRetryDelay = updateRetryDelay(
-        resp.nextRetryDelay,
-        now,
-        pr.refundApplyRetryInfo.nextRetry,
-      );
-      if (!onlyDue || pr.refundApplyRetryInfo.nextRetry.t_ms <= now.t_ms) {
-        resp.pendingOperations.push({
-          type: PendingOperationType.RefundApply,
-          numRefundsDone,
-          numRefundsPending,
-          givesLifeness: true,
-          proposalId: pr.proposalId,
-          retryInfo: pr.refundApplyRetryInfo,
-          lastError: pr.lastRefundApplyError,
-        });
-      }
-    }
   });
 }
 
diff --git a/src/operations/refund.ts b/src/operations/refund.ts
index 8feb2bae..9b18cafd 100644
--- a/src/operations/refund.ts
+++ b/src/operations/refund.ts
@@ -43,16 +43,14 @@ import { parseRefundUri } from "../util/taleruri";
 import { createRefreshGroup, getTotalRefreshCost } from "./refresh";
 import { Amounts } from "../util/amounts";
 import {
-  MerchantRefundPermission,
+  MerchantRefundDetails,
   MerchantRefundResponse,
-  RefundRequest,
   codecForMerchantRefundResponse,
 } from "../types/talerTypes";
 import { AmountJson } from "../util/amounts";
 import { guardOperationException, OperationFailedError } from "./errors";
 import { randomBytes } from "../crypto/primitives/nacl-fast";
 import { encodeCrock } from "../crypto/talerCrypto";
-import { HttpResponseStatus } from "../util/http";
 import { getTimestampNow } from "../util/time";
 import { Logger } from "../util/logging";
 
@@ -80,31 +78,9 @@ async function incrementPurchaseQueryRefundRetry(
   ws.notify({ type: NotificationType.RefundStatusOperationError });
 }
 
-async function incrementPurchaseApplyRefundRetry(
-  ws: InternalWalletState,
-  proposalId: string,
-  err: OperationError | undefined,
-): Promise<void> {
-  console.log("incrementing purchase refund apply retry with error", err);
-  await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => {
-    const pr = await tx.get(Stores.purchases, proposalId);
-    if (!pr) {
-      return;
-    }
-    if (!pr.refundApplyRetryInfo) {
-      return;
-    }
-    pr.refundApplyRetryInfo.retryCounter++;
-    updateRetryInfoTimeout(pr.refundApplyRetryInfo);
-    pr.lastRefundApplyError = err;
-    await tx.put(Stores.purchases, pr);
-  });
-  ws.notify({ type: NotificationType.RefundApplyOperationError });
-}
-
 export async function getFullRefundFees(
   ws: InternalWalletState,
-  refundPermissions: MerchantRefundPermission[],
+  refundPermissions: MerchantRefundDetails[],
 ): Promise<AmountJson> {
   if (refundPermissions.length === 0) {
     throw Error("no refunds given");
@@ -149,88 +125,196 @@ export async function getFullRefundFees(
   return feeAcc;
 }
 
-export async function acceptRefundResponse(
+function getRefundKey(d: MerchantRefundDetails): string {
+  return `{d.coin_pub}-{d.rtransaction_id}`;
+}
+
+async function acceptRefundResponse(
   ws: InternalWalletState,
   proposalId: string,
   refundResponse: MerchantRefundResponse,
   reason: RefundReason,
 ): Promise<void> {
-  const refundPermissions = refundResponse.refund_permissions;
-
-  let numNewRefunds = 0;
+  const refunds = refundResponse.refunds;
 
   const refundGroupId = encodeCrock(randomBytes(32));
 
-  await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => {
-    const p = await tx.get(Stores.purchases, proposalId);
-    if (!p) {
-      console.error("purchase not found, not adding refunds");
-      return;
-    }
+  let numNewRefunds = 0;
 
-    if (!p.refundStatusRequested) {
-      return;
+  const finishedRefunds: MerchantRefundDetails[] = [];
+  const unfinishedRefunds: MerchantRefundDetails[] = [];
+  const failedRefunds: MerchantRefundDetails[] = [];
+
+  for (const rd of refunds) {
+    if (rd.exchange_http_status === 200) {
+      // FIXME: also verify signature if necessary.
+      finishedRefunds.push(rd);
+    } else if (
+      rd.exchange_http_status >= 400 &&
+      rd.exchange_http_status < 400
+    ) {
+      failedRefunds.push(rd);
+    } else {
+      unfinishedRefunds.push(rd);
     }
+  }
 
-    for (const perm of refundPermissions) {
-      const isDone = p.refundState.refundsDone[perm.merchant_sig];
-      const isPending = p.refundState.refundsPending[perm.merchant_sig];
-      if (!isDone && !isPending) {
-        p.refundState.refundsPending[perm.merchant_sig] = {
-          perm,
+  await ws.db.runWithWriteTransaction(
+    [Stores.purchases, Stores.coins, Stores.refreshGroups, 
Stores.refundEvents],
+    async (tx) => {
+      const p = await tx.get(Stores.purchases, proposalId);
+      if (!p) {
+        console.error("purchase not found, not adding refunds");
+        return;
+      }
+
+      // Groups that newly failed/succeeded
+      const changedGroups: { [refundGroupId: string]: boolean } = {};
+
+      for (const rd of failedRefunds) {
+        const refundKey = getRefundKey(rd);
+        if (p.refundsFailed[refundKey]) {
+          continue;
+        }
+        if (!p.refundsFailed[refundKey]) {
+          p.refundsFailed[refundKey] = {
+            perm: rd,
+            refundGroupId,
+          };
+          numNewRefunds++;
+          changedGroups[refundGroupId] = true;
+        }
+        const oldPending = p.refundsPending[refundKey];
+        if (oldPending) {
+          delete p.refundsPending[refundKey];
+          changedGroups[oldPending.refundGroupId] = true;
+        }
+      }
+
+      for (const rd of unfinishedRefunds) {
+        const refundKey = getRefundKey(rd);
+        if (!p.refundsPending[refundKey]) {
+          p.refundsPending[refundKey] = {
+            perm: rd,
+            refundGroupId,
+          };
+          numNewRefunds++;
+        }
+      }
+
+      // Avoid duplicates
+      const refreshCoinsMap: { [coinPub: string]: CoinPublicKey } = {};
+
+      for (const rd of finishedRefunds) {
+        const refundKey = getRefundKey(rd);
+        if (p.refundsDone[refundKey]) {
+          continue;
+        }
+        p.refundsDone[refundKey] = {
+          perm: rd,
           refundGroupId,
         };
-        numNewRefunds++;
+        const oldPending = p.refundsPending[refundKey];
+        if (oldPending) {
+          delete p.refundsPending[refundKey];
+          changedGroups[oldPending.refundGroupId] = true;
+        } else {
+          numNewRefunds++;
+        }
+
+        const c = await tx.get(Stores.coins, rd.coin_pub);
+
+        if (!c) {
+          console.warn("coin not found, can't apply refund");
+          return;
+        }
+        refreshCoinsMap[c.coinPub] = { coinPub: c.coinPub };
+        logger.trace(`commiting refund ${refundKey} to coin ${c.coinPub}`);
+        logger.trace(
+          `coin amount before is ${Amounts.stringify(c.currentAmount)}`,
+        );
+        logger.trace(`refund amount (via merchant) is ${refundKey}`);
+        logger.trace(`refund fee (via merchant) is ${refundKey}`);
+        const refundAmount = Amounts.parseOrThrow(rd.refund_amount);
+        const refundFee = Amounts.parseOrThrow(rd.refund_fee);
+        c.status = CoinStatus.Dormant;
+        c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount;
+        c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount;
+        logger.trace(
+          `coin amount after is ${Amounts.stringify(c.currentAmount)}`,
+        );
+        await tx.put(Stores.coins, c);
       }
-    }
 
-    // Are we done with querying yet, or do we need to do another round
-    // after a retry delay?
-    let queryDone = true;
+      // Are we done with querying yet, or do we need to do another round
+      // after a retry delay?
+      let queryDone = true;
 
-    if (numNewRefunds === 0) {
-      if (
-        p.autoRefundDeadline &&
-        p.autoRefundDeadline.t_ms > getTimestampNow().t_ms
-      ) {
+      if (numNewRefunds === 0) {
+        if (
+          p.autoRefundDeadline &&
+          p.autoRefundDeadline.t_ms > getTimestampNow().t_ms
+        ) {
+          queryDone = false;
+        }
+      }
+
+      if (Object.keys(unfinishedRefunds).length != 0) {
         queryDone = false;
       }
-    }
 
-    if (queryDone) {
-      p.timestampLastRefundStatus = getTimestampNow();
-      p.lastRefundStatusError = undefined;
-      p.refundStatusRetryInfo = initRetryInfo();
-      p.refundStatusRequested = false;
-      console.log("refund query done");
-    } else {
-      // No error, but we need to try again!
-      p.timestampLastRefundStatus = getTimestampNow();
-      p.refundStatusRetryInfo.retryCounter++;
-      updateRetryInfoTimeout(p.refundStatusRetryInfo);
-      p.lastRefundStatusError = undefined;
-      console.log("refund query not done");
-    }
+      if (queryDone) {
+        p.timestampLastRefundStatus = getTimestampNow();
+        p.lastRefundStatusError = undefined;
+        p.refundStatusRetryInfo = initRetryInfo(false);
+        p.refundStatusRequested = false;
+        console.log("refund query done");
+      } else {
+        // No error, but we need to try again!
+        p.timestampLastRefundStatus = getTimestampNow();
+        p.refundStatusRetryInfo.retryCounter++;
+        updateRetryInfoTimeout(p.refundStatusRetryInfo);
+        p.lastRefundStatusError = undefined;
+        console.log("refund query not done");
+      }
 
-    if (numNewRefunds > 0) {
-      const now = getTimestampNow();
-      p.lastRefundApplyError = undefined;
-      p.refundApplyRetryInfo = initRetryInfo();
-      p.refundState.refundGroups.push({
-        timestampQueried: now,
-        reason,
-      });
-    }
+      await tx.put(Stores.purchases, p);
 
-    await tx.put(Stores.purchases, p);
-  });
+      const coinsPubsToBeRefreshed = Object.values(refreshCoinsMap);
+      if (coinsPubsToBeRefreshed.length > 0) {
+        await createRefreshGroup(
+          tx,
+          coinsPubsToBeRefreshed,
+          RefreshReason.Refund,
+        );
+      }
+
+      // Check if any of the refund groups are done, and we
+      // can emit an corresponding event.
+      const now = getTimestampNow();
+      for (const g of Object.keys(changedGroups)) {
+        let groupDone = true;
+        for (const pk of Object.keys(p.refundsPending)) {
+          const r = p.refundsPending[pk];
+          if (r.refundGroupId == g) {
+            groupDone = false;
+          }
+        }
+        if (groupDone) {
+          const refundEvent: RefundEventRecord = {
+            proposalId,
+            refundGroupId: g,
+            timestamp: now,
+          };
+          await tx.put(Stores.refundEvents, refundEvent);
+        }
+      }
+    },
+  );
 
   ws.notify({
     type: NotificationType.RefundQueried,
   });
-  if (numNewRefunds > 0) {
-    await processPurchaseApplyRefund(ws, proposalId);
-  }
 }
 
 async function startRefundQuery(
@@ -362,201 +446,3 @@ async function processPurchaseQueryRefundImpl(
     RefundReason.NormalRefund,
   );
 }
-
-export async function processPurchaseApplyRefund(
-  ws: InternalWalletState,
-  proposalId: string,
-  forceNow = false,
-): Promise<void> {
-  const onOpErr = (e: OperationError): Promise<void> =>
-    incrementPurchaseApplyRefundRetry(ws, proposalId, e);
-  await guardOperationException(
-    () => processPurchaseApplyRefundImpl(ws, proposalId, forceNow),
-    onOpErr,
-  );
-}
-
-async function resetPurchaseApplyRefundRetry(
-  ws: InternalWalletState,
-  proposalId: string,
-): Promise<void> {
-  await ws.db.mutate(Stores.purchases, proposalId, (x) => {
-    if (x.refundApplyRetryInfo.active) {
-      x.refundApplyRetryInfo = initRetryInfo();
-    }
-    return x;
-  });
-}
-
-async function processPurchaseApplyRefundImpl(
-  ws: InternalWalletState,
-  proposalId: string,
-  forceNow: boolean,
-): Promise<void> {
-  if (forceNow) {
-    await resetPurchaseApplyRefundRetry(ws, proposalId);
-  }
-  const purchase = await ws.db.get(Stores.purchases, proposalId);
-  if (!purchase) {
-    console.error("not submitting refunds, payment not found:");
-    return;
-  }
-  const pendingKeys = Object.keys(purchase.refundState.refundsPending);
-  if (pendingKeys.length === 0) {
-    console.log("no pending refunds");
-    return;
-  }
-
-  const newRefundsDone: { [sig: string]: RefundInfo } = {};
-  const newRefundsFailed: { [sig: string]: RefundInfo } = {};
-  for (const pk of pendingKeys) {
-    const info = purchase.refundState.refundsPending[pk];
-    const perm = info.perm;
-    const req: RefundRequest = {
-      coin_pub: perm.coin_pub,
-      h_contract_terms: purchase.contractData.contractTermsHash,
-      merchant_pub: purchase.contractData.merchantPub,
-      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(`coins/${perm.coin_pub}/refund`, exchangeUrl);
-    const resp = await ws.http.postJson(reqUrl.href, req);
-    console.log("sent refund permission");
-    switch (resp.status) {
-      case HttpResponseStatus.Ok:
-        newRefundsDone[pk] = info;
-        break;
-      case HttpResponseStatus.Gone:
-        // We're too late, refund is expired.
-        newRefundsFailed[pk] = info;
-        break;
-      default: {
-        let body: string | null = null;
-        // FIXME: error handling!
-        body = await resp.json();
-        const m = "refund request (at exchange) failed";
-        throw new OperationFailedError({
-          message: m,
-          type: "network",
-          details: {
-            body,
-          },
-        });
-      }
-    }
-  }
-  let allRefundsProcessed = false;
-  await ws.db.runWithWriteTransaction(
-    [Stores.purchases, Stores.coins, Stores.refreshGroups, 
Stores.refundEvents],
-    async (tx) => {
-      const p = await tx.get(Stores.purchases, proposalId);
-      if (!p) {
-        return;
-      }
-
-      // Groups that failed/succeeded
-      const groups: { [refundGroupId: string]: boolean } = {};
-
-      // Avoid duplicates
-      const refreshCoinsMap: { [coinPub: string]: CoinPublicKey } = {};
-
-      const modCoin = async (perm: MerchantRefundPermission): Promise<void> => 
{
-        const c = await tx.get(Stores.coins, perm.coin_pub);
-        if (!c) {
-          console.warn("coin not found, can't apply refund");
-          return;
-        }
-        refreshCoinsMap[c.coinPub] = { coinPub: c.coinPub };
-        logger.trace(
-          `commiting refund ${perm.merchant_sig} to coin ${c.coinPub}`,
-        );
-        logger.trace(
-          `coin amount before is ${Amounts.stringify(c.currentAmount)}`,
-        );
-        logger.trace(`refund amount (via merchant) is ${perm.refund_amount}`);
-        logger.trace(`refund fee (via merchant) is ${perm.refund_fee}`);
-        const refundAmount = Amounts.parseOrThrow(perm.refund_amount);
-        const refundFee = Amounts.parseOrThrow(perm.refund_fee);
-        c.status = CoinStatus.Dormant;
-        c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount;
-        c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount;
-        logger.trace(
-          `coin amount after is ${Amounts.stringify(c.currentAmount)}`,
-        );
-        await tx.put(Stores.coins, c);
-      };
-
-      for (const pk of Object.keys(newRefundsFailed)) {
-        if (p.refundState.refundsDone[pk]) {
-          // We already processed this one.
-          break;
-        }
-        const r = newRefundsFailed[pk];
-        groups[r.refundGroupId] = true;
-        delete p.refundState.refundsPending[pk];
-        p.refundState.refundsFailed[pk] = r;
-      }
-
-      for (const pk of Object.keys(newRefundsDone)) {
-        if (p.refundState.refundsDone[pk]) {
-          // We already processed this one.
-          break;
-        }
-        const r = newRefundsDone[pk];
-        groups[r.refundGroupId] = true;
-        delete p.refundState.refundsPending[pk];
-        p.refundState.refundsDone[pk] = r;
-        await modCoin(r.perm);
-      }
-
-      const now = getTimestampNow();
-      for (const g of Object.keys(groups)) {
-        let groupDone = true;
-        for (const pk of Object.keys(p.refundState.refundsPending)) {
-          const r = p.refundState.refundsPending[pk];
-          if (r.refundGroupId == g) {
-            groupDone = false;
-          }
-        }
-        if (groupDone) {
-          const refundEvent: RefundEventRecord = {
-            proposalId,
-            refundGroupId: g,
-            timestamp: now,
-          };
-          await tx.put(Stores.refundEvents, refundEvent);
-        }
-      }
-
-      if (Object.keys(p.refundState.refundsPending).length === 0) {
-        p.refundStatusRetryInfo = initRetryInfo();
-        p.lastRefundStatusError = undefined;
-        allRefundsProcessed = true;
-      }
-      await tx.put(Stores.purchases, p);
-      const coinsPubsToBeRefreshed = Object.values(refreshCoinsMap);
-      if (coinsPubsToBeRefreshed.length > 0) {
-        await createRefreshGroup(
-          tx,
-          coinsPubsToBeRefreshed,
-          RefreshReason.Refund,
-        );
-      }
-    },
-  );
-  if (allRefundsProcessed) {
-    ws.notify({
-      type: NotificationType.RefundFinished,
-    });
-  }
-
-  ws.notify({
-    type: NotificationType.RefundsSubmitted,
-    proposalId,
-  });
-}
diff --git a/src/types/dbTypes.ts b/src/types/dbTypes.ts
index a9344c04..158d438c 100644
--- a/src/types/dbTypes.ts
+++ b/src/types/dbTypes.ts
@@ -27,7 +27,7 @@ import { AmountJson } from "../util/amounts";
 import {
   Auditor,
   CoinDepositPermission,
-  MerchantRefundPermission,
+  MerchantRefundDetails,
   PayReq,
   TipResponse,
   ExchangeSignKeyJson,
@@ -1091,7 +1091,7 @@ export interface RefundEventRecord {
 
 export interface RefundInfo {
   refundGroupId: string;
-  perm: MerchantRefundPermission;
+  perm: MerchantRefundDetails;
 }
 
 export const enum RefundReason {
@@ -1102,7 +1102,7 @@ export const enum RefundReason {
   /**
    * Refund from an aborted payment.
    */
-  AbortRefund = "abort-refund",
+  AbortRefund = "abort-pay-refund",
 }
 
 export interface RefundGroupInfo {
@@ -1110,28 +1110,6 @@ export interface RefundGroupInfo {
   reason: RefundReason;
 }
 
-export interface PurchaseRefundState {
-  /**
-   * Information regarding each group of refunds we receive at once.
-   */
-  refundGroups: RefundGroupInfo[];
-
-  /**
-   * Pending refunds for the purchase.
-   */
-  refundsPending: { [refundSig: string]: RefundInfo };
-
-  /**
-   * Applied refunds for the purchase.
-   */
-  refundsDone: { [refundSig: string]: RefundInfo };
-
-  /**
-   * Submitted refunds for the purchase.
-   */
-  refundsFailed: { [refundSig: string]: RefundInfo };
-}
-
 /**
  * Record stored for every time we successfully submitted
  * a payment to the merchant (both first time and re-play).
@@ -1230,9 +1208,25 @@ export interface PurchaseRecord {
   timestampAccept: Timestamp;
 
   /**
-   * State of refunds for this proposal.
+   * Information regarding each group of refunds we receive at once.
+   */
+  refundGroups: RefundGroupInfo[];
+
+  /**
+   * Pending refunds for the purchase.  A refund is pending
+   * when the merchant reports a transient error from the exchange.
+   */
+  refundsPending: { [refundKey: string]: RefundInfo };
+
+  /**
+   * Applied refunds for the purchase.
+   */
+  refundsDone: { [refundKey: string]: RefundInfo };
+
+  /**
+   * Refunds that permanently failed.
    */
-  refundState: PurchaseRefundState;
+  refundsFailed: { [refundKey: string]: RefundInfo };
 
   /**
    * When was the last refund made?
@@ -1280,16 +1274,6 @@ export interface PurchaseRecord {
    */
   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;
-
   /**
    * Continue querying the refund status until this deadline has expired.
    */
diff --git a/src/types/pending.ts b/src/types/pending.ts
index 4ff82f55..f949b7c1 100644
--- a/src/types/pending.ts
+++ b/src/types/pending.ts
@@ -35,7 +35,6 @@ export const enum PendingOperationType {
   Refresh = "refresh",
   Reserve = "reserve",
   Recoup = "recoup",
-  RefundApply = "refund-apply",
   RefundQuery = "refund-query",
   TipChoice = "tip-choice",
   TipPickup = "tip-pickup",
@@ -53,7 +52,6 @@ export type PendingOperationInfo = PendingOperationInfoCommon 
&
     | PendingProposalChoiceOperation
     | PendingProposalDownloadOperation
     | PendingRefreshOperation
-    | PendingRefundApplyOperation
     | PendingRefundQueryOperation
     | PendingReserveOperation
     | PendingTipChoiceOperation
@@ -188,20 +186,6 @@ export interface PendingRefundQueryOperation {
   lastError: OperationError | undefined;
 }
 
-/**
- * The wallet is processing refunds that it received from a merchant.
- * During this operation, the wallet checks the refund permissions and sends
- * them to the exchange to obtain a refund on a coin.
- */
-export interface PendingRefundApplyOperation {
-  type: PendingOperationType.RefundApply;
-  proposalId: string;
-  retryInfo: RetryInfo;
-  lastError: OperationError | undefined;
-  numRefundsPending: number;
-  numRefundsDone: number;
-}
-
 export interface PendingRecoupOperation {
   type: PendingOperationType.Recoup;
   recoupGroupId: string;
diff --git a/src/types/talerTypes.ts b/src/types/talerTypes.ts
index 799c84dc..17d11eea 100644
--- a/src/types/talerTypes.ts
+++ b/src/types/talerTypes.ts
@@ -411,7 +411,7 @@ export interface PayReq {
 /**
  * Refund permission in the format that the merchant gives it to us.
  */
-export class MerchantRefundPermission {
+export class MerchantRefundDetails {
   /**
    * Amount to be refunded.
    */
@@ -433,52 +433,30 @@ export class MerchantRefundPermission {
   rtransaction_id: number;
 
   /**
-   * Signature made by the merchant over the refund permission.
+   * Exchange's key used for the signature.
    */
-  merchant_sig: string;
-}
-
-/**
- * Refund request sent to the exchange.
- */
-export interface RefundRequest {
-  /**
-   * Amount to be refunded, can be a fraction of the
-   * coin's total deposit value (including deposit fee);
-   * must be larger than the refund fee.
-   */
-  refund_amount: string;
-
-  /**
-   * Refund fee associated with the given coin.
-   * must be smaller than the refund amount.
-   */
-  refund_fee: string;
+  exchange_pub?: string;
 
   /**
-   * SHA-512 hash of the contact of the merchant with the customer.
+   * Exchange's signature to confirm the refund.
    */
-  h_contract_terms: string;
+  exchange_sig?: string;
 
   /**
-   * coin's public key, both ECDHE and EdDSA.
+   * Error replay from the exchange (if any).
    */
-  coin_pub: string;
+  exchange_reply?: any;
 
   /**
-   * 64-bit transaction id of the refund transaction between merchant and 
customer
+   * Error code from the exchange (if any).
    */
-  rtransaction_id: number;
-
-  /**
-   * EdDSA public key of the merchant.
-   */
-  merchant_pub: string;
+  exchange_code?: number;
 
   /**
-   * EdDSA signature of the merchant affirming the refund.
+   * HTTP status code of the exchange's response
+   * to the merchant's refund request.
    */
-  merchant_sig: string;
+  exchange_http_status: number;
 }
 
 /**
@@ -499,7 +477,7 @@ export class MerchantRefundResponse {
   /**
    * The signed refund permissions, to be sent to the exchange.
    */
-  refund_permissions: MerchantRefundPermission[];
+  refunds: MerchantRefundDetails[];
 }
 
 /**
@@ -854,14 +832,18 @@ export const codecForContractTerms = (): 
Codec<ContractTerms> =>
     .build("ContractTerms");
 
 export const codecForMerchantRefundPermission = (): Codec<
-  MerchantRefundPermission
+  MerchantRefundDetails
 > =>
-  makeCodecForObject<MerchantRefundPermission>()
+  makeCodecForObject<MerchantRefundDetails>()
     .property("refund_amount", codecForString)
     .property("refund_fee", codecForString)
     .property("coin_pub", codecForString)
     .property("rtransaction_id", codecForNumber)
-    .property("merchant_sig", codecForString)
+    .property("exchange_http_status", codecForNumber)
+    .property("exchange_code", makeCodecOptional(codecForNumber))
+    .property("exchange_reply", makeCodecOptional(codecForAny))
+    .property("exchange_sig", makeCodecOptional(codecForString))
+    .property("exchange_pub", makeCodecOptional(codecForString))
     .build("MerchantRefundPermission");
 
 export const codecForMerchantRefundResponse = (): Codec<
@@ -871,7 +853,7 @@ export const codecForMerchantRefundResponse = (): Codec<
     .property("merchant_pub", codecForString)
     .property("h_contract_terms", codecForString)
     .property(
-      "refund_permissions",
+      "refunds",
       makeCodecForList(codecForMerchantRefundPermission()),
     )
     .build("MerchantRefundResponse");
diff --git a/src/wallet.ts b/src/wallet.ts
index 273a9f87..41569a44 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -34,7 +34,6 @@ import {
 } from "./operations/withdraw";
 
 import {
-  abortFailedPayment,
   preparePayForUri,
   refuseProposal,
   confirmPay,
@@ -53,7 +52,7 @@ import {
   ReserveRecordStatus,
   CoinSourceType,
 } from "./types/dbTypes";
-import { MerchantRefundPermission, CoinDumpJson } from "./types/talerTypes";
+import { MerchantRefundDetails, CoinDumpJson } from "./types/talerTypes";
 import {
   BenchmarkResult,
   ConfirmPayResult,
@@ -107,7 +106,6 @@ import { WalletNotification, NotificationType } from 
"./types/notifications";
 import { HistoryQuery, HistoryEvent } from "./types/history";
 import {
   processPurchaseQueryRefund,
-  processPurchaseApplyRefund,
   getFullRefundFees,
   applyRefund,
 } from "./operations/refund";
@@ -218,9 +216,6 @@ export class Wallet {
       case PendingOperationType.RefundQuery:
         await processPurchaseQueryRefund(this.ws, pending.proposalId, 
forceNow);
         break;
-      case PendingOperationType.RefundApply:
-        await processPurchaseApplyRefund(this.ws, pending.proposalId, 
forceNow);
-        break;
       case PendingOperationType.Recoup:
         await processRecoupGroup(this.ws, pending.recoupGroupId, forceNow);
         break;
@@ -658,7 +653,7 @@ export class Wallet {
   }
 
   async getFullRefundFees(
-    refundPermissions: MerchantRefundPermission[],
+    refundPermissions: MerchantRefundDetails[],
   ): Promise<AmountJson> {
     return getFullRefundFees(this.ws, refundPermissions);
   }
@@ -676,11 +671,7 @@ export class Wallet {
   }
 
   async abortFailedPayment(contractTermsHash: string): Promise<void> {
-    try {
-      return abortFailedPayment(this.ws, contractTermsHash);
-    } finally {
-      this.latch.trigger();
-    }
+    throw Error("not implemented");
   }
 
   /**
@@ -745,20 +736,20 @@ export class Wallet {
       throw Error("unknown purchase");
     }
     const refundsDoneAmounts = Object.values(
-      purchase.refundState.refundsDone,
+      purchase.refundsDone,
     ).map((x) => Amounts.parseOrThrow(x.perm.refund_amount));
     const refundsPendingAmounts = Object.values(
-      purchase.refundState.refundsPending,
+      purchase.refundsPending,
     ).map((x) => Amounts.parseOrThrow(x.perm.refund_amount));
     const totalRefundAmount = Amounts.sum([
       ...refundsDoneAmounts,
       ...refundsPendingAmounts,
     ]).amount;
     const refundsDoneFees = Object.values(
-      purchase.refundState.refundsDone,
+      purchase.refundsDone,
     ).map((x) => Amounts.parseOrThrow(x.perm.refund_amount));
     const refundsPendingFees = Object.values(
-      purchase.refundState.refundsPending,
+      purchase.refundsPending,
     ).map((x) => Amounts.parseOrThrow(x.perm.refund_amount));
     const totalRefundFees = Amounts.sum([
       ...refundsDoneFees,

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



reply via email to

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