gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 02/02: wallet-core: hide transient pay errors


From: gnunet
Subject: [taler-wallet-core] 02/02: wallet-core: hide transient pay errors
Date: Mon, 19 Sep 2022 12:13:38 +0200

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

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

commit fd752f3171a76129d2f615535b90c6bebb88d842
Author: Florian Dold <florian@dold.me>
AuthorDate: Mon Sep 19 12:13:31 2022 +0200

    wallet-core: hide transient pay errors
---
 packages/taler-wallet-core/src/errors.ts         | 23 ++++++---
 packages/taler-wallet-core/src/operations/pay.ts | 62 ++++++++++++++++++++++--
 packages/taler-wallet-core/src/util/retries.ts   |  3 ++
 3 files changed, 76 insertions(+), 12 deletions(-)

diff --git a/packages/taler-wallet-core/src/errors.ts 
b/packages/taler-wallet-core/src/errors.ts
index d56e936c0..62bde667d 100644
--- a/packages/taler-wallet-core/src/errors.ts
+++ b/packages/taler-wallet-core/src/errors.ts
@@ -70,6 +70,9 @@ export interface DetailsMap {
   [TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE]: {};
   [TalerErrorCode.WALLET_CORE_NOT_AVAILABLE]: {};
   [TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR]: {};
+  [TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR]: {
+    requestError: TalerErrorDetail;
+  };
 }
 
 type ErrBody<Y> = Y extends keyof DetailsMap ? DetailsMap[Y] : never;
@@ -79,7 +82,9 @@ export function makeErrorDetail<C extends TalerErrorCode>(
   detail: ErrBody<C>,
   hint?: string,
 ): TalerErrorDetail {
-  // FIXME: include default hint?
+  if (!hint && !(detail as any).hint) {
+    hint = getDefaultHint(code);
+  }
   return { code, hint, ...detail };
 }
 
@@ -99,6 +104,15 @@ export function summarizeTalerErrorDetail(ed: 
TalerErrorDetail): string {
   return `Error (${ed.code}/${errName})`;
 }
 
+function getDefaultHint(code: number): string {
+  const errName = TalerErrorCode[code];
+  if (errName) {
+    return `Error (${errName})`;
+  } else {
+    return `Error (<unknown>)`;
+  }
+}
+
 export class TalerError<T = any> extends Error {
   errorDetail: TalerErrorDetail & T;
   private constructor(d: TalerErrorDetail & T) {
@@ -113,12 +127,7 @@ export class TalerError<T = any> extends Error {
     hint?: string,
   ): TalerError {
     if (!hint) {
-      const errName = TalerErrorCode[code];
-      if (errName) {
-        hint = `Error (${errName})`;
-      } else {
-        hint = `Error (<unknown>)`;
-      }
+      hint = getDefaultHint(code);
     }
     return new TalerError<unknown>({ code, hint, ...detail });
   }
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index a498ab28d..fb22d0fad 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -83,6 +83,7 @@ import {
   EXCHANGE_COINS_LOCK,
   InternalWalletState,
 } from "../internal-wallet-state.js";
+import { PendingTaskType } from "../pending-types.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
 import {
   CoinSelectionTally,
@@ -105,7 +106,11 @@ import {
   RetryTags,
   scheduleRetry,
 } from "../util/retries.js";
-import { spendCoins } from "../wallet.js";
+import {
+  spendCoins,
+  storeOperationError,
+  storeOperationPending,
+} from "../wallet.js";
 import { getExchangeDetails } from "./exchanges.js";
 import { getTotalRefreshCost } from "./refresh.js";
 import { makeEventId } from "./transactions.js";
@@ -1519,10 +1524,43 @@ export async function runPayForConfirmPay(
         transactionId: makeEventId(TransactionType.Payment, proposalId),
       };
     }
-    case OperationAttemptResultType.Error:
-      // FIXME: allocate error code!
-      throw Error("payment failed");
+    case OperationAttemptResultType.Error: {
+      // We hide transient errors from the caller.
+      const opRetry = await ws.db
+        .mktx((x) => [x.operationRetries])
+        .runReadOnly(async (tx) =>
+          tx.operationRetries.get(RetryTags.byPaymentProposalId(proposalId)),
+        );
+      const maxRetry = 3;
+      const numRetry = opRetry?.retryInfo.retryCounter ?? 0;
+      if (
+        res.errorDetail.code ===
+          TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR &&
+        numRetry < maxRetry
+      ) {
+        // Pretend the operation is pending instead of reporting
+        // an error, but only up to maxRetry attempts.
+        await storeOperationPending(
+          ws,
+          RetryTags.byPaymentProposalId(proposalId),
+        );
+        return {
+          type: ConfirmPayResultType.Pending,
+          lastError: opRetry?.lastError,
+          transactionId: makeEventId(TransactionType.Payment, proposalId),
+        };
+      } else {
+        // FIXME: allocate error code!
+        await storeOperationError(
+          ws,
+          RetryTags.byPaymentProposalId(proposalId),
+          res.errorDetail,
+        );
+        throw Error("payment failed");
+      }
+    }
     case OperationAttemptResultType.Pending:
+      await storeOperationPending(ws, `${PendingTaskType.Pay}:${proposalId}`);
       return {
         type: ConfirmPayResultType.Pending,
         transactionId: makeEventId(TransactionType.Payment, proposalId),
@@ -1536,7 +1574,7 @@ export async function runPayForConfirmPay(
 }
 
 /**
- * Add a contract to the wallet and sign coins, and send them.
+ * Confirm payment for a proposal previously claimed by the wallet.
  */
 export async function confirmPay(
   ws: InternalWalletState,
@@ -1698,6 +1736,20 @@ export async function processPurchasePay(
     );
 
     logger.trace(`got resp ${JSON.stringify(resp)}`);
+
+    if (resp.status >= 500 && resp.status <= 599) {
+      const errDetails = await readUnexpectedResponseDetails(resp);
+      return {
+        type: OperationAttemptResultType.Error,
+        errorDetail: makeErrorDetail(
+          TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR,
+          {
+            requestError: errDetails,
+          },
+        ),
+      };
+    }
+
     if (resp.status === HttpStatusCode.BadRequest) {
       const errDetails = await readUnexpectedResponseDetails(resp);
       logger.warn("unexpected 400 response for /pay");
diff --git a/packages/taler-wallet-core/src/util/retries.ts 
b/packages/taler-wallet-core/src/util/retries.ts
index b13e9a27b..cef9e072c 100644
--- a/packages/taler-wallet-core/src/util/retries.ts
+++ b/packages/taler-wallet-core/src/util/retries.ts
@@ -205,6 +205,9 @@ export namespace RetryTags {
   export function forBackup(backupRecord: BackupProviderRecord): string {
     return `${PendingTaskType.Backup}:${backupRecord.baseUrl}`;
   }
+  export function byPaymentProposalId(proposalId: string): string {
+    return `${PendingTaskType.Pay}:${proposalId}`;
+  }
 }
 
 export async function scheduleRetryInTx(

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

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