From 5d23eb36354d07508a015531f298b3e261bbafce Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 22 Mar 2022 21:16:38 +0100 Subject: wallet: improve error handling and error codes --- .../src/operations/backup/index.ts | 10 +- .../taler-wallet-core/src/operations/deposits.ts | 6 +- .../taler-wallet-core/src/operations/exchanges.ts | 22 ++--- packages/taler-wallet-core/src/operations/pay.ts | 106 ++++++++++++--------- .../taler-wallet-core/src/operations/recoup.ts | 6 +- .../taler-wallet-core/src/operations/refresh.ts | 6 +- .../taler-wallet-core/src/operations/refund.ts | 6 +- .../taler-wallet-core/src/operations/reserves.ts | 13 ++- packages/taler-wallet-core/src/operations/tip.ts | 16 ++-- .../taler-wallet-core/src/operations/withdraw.ts | 35 ++++--- 10 files changed, 116 insertions(+), 110 deletions(-) (limited to 'packages/taler-wallet-core/src/operations') diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 48eea56ad..400406ce3 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -48,7 +48,7 @@ import { PreparePayResultType, RecoveryLoadRequest, RecoveryMergeStrategy, - TalerErrorDetails, + TalerErrorDetail, AbsoluteTime, URL, WalletBackupContentV1, @@ -464,7 +464,7 @@ async function incrementBackupRetryInTx( backupProviders: typeof WalletStoresV1.backupProviders; }>, backupProviderBaseUrl: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { const pr = await tx.backupProviders.get(backupProviderBaseUrl); if (!pr) { @@ -487,7 +487,7 @@ async function incrementBackupRetryInTx( async function incrementBackupRetry( ws: InternalWalletState, backupProviderBaseUrl: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ backupProviders: x.backupProviders })) @@ -509,7 +509,7 @@ export async function processBackupForProvider( throw Error("unknown backup provider"); } - const onOpErr = (err: TalerErrorDetails): Promise => + const onOpErr = (err: TalerErrorDetail): Promise => incrementBackupRetry(ws, backupProviderBaseUrl, err); const run = async () => { @@ -700,7 +700,7 @@ export interface ProviderInfo { /** * Last communication issue with the provider. */ - lastError?: TalerErrorDetails; + lastError?: TalerErrorDetail; lastSuccessfulBackupTimestamp?: TalerProtocolTimestamp; lastAttemptedBackupTimestamp?: TalerProtocolTimestamp; paymentProposalIds: string[]; diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 4b976011b..42ce5e7c9 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -36,7 +36,7 @@ import { Logger, NotificationType, parsePaytoUri, - TalerErrorDetails, + TalerErrorDetail, TalerProtocolTimestamp, TrackDepositGroupRequest, TrackDepositGroupResponse, @@ -83,7 +83,7 @@ async function resetDepositGroupRetry( async function incrementDepositRetry( ws: InternalWalletState, depositGroupId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ depositGroups: x.depositGroups })) @@ -111,7 +111,7 @@ export async function processDepositGroup( forceNow = false, ): Promise { await ws.memoProcessDeposit.memo(depositGroupId, async () => { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementDepositRetry(ws, depositGroupId, e); return await guardOperationException( async () => await processDepositGroupImpl(ws, depositGroupId, forceNow), diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index df7eee76d..bbed42288 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -34,7 +34,7 @@ import { Recoup, TalerErrorCode, URL, - TalerErrorDetails, + TalerErrorDetail, AbsoluteTime, hashDenomPub, LibtoolVersion, @@ -64,11 +64,7 @@ import { } from "../util/http.js"; import { DbAccess, GetReadOnlyAccess } from "../util/query.js"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; -import { - guardOperationException, - makeErrorDetails, - OperationFailedError, -} from "../errors.js"; +import { guardOperationException, TalerError } from "../errors.js"; import { InternalWalletState, TrustInfo } from "../common.js"; import { WALLET_CACHE_BREAKER_CLIENT_VERSION, @@ -112,7 +108,7 @@ function denominationRecordFromKeys( async function handleExchangeUpdateError( ws: InternalWalletState, baseUrl: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ exchanges: x.exchanges })) @@ -353,7 +349,7 @@ export async function updateExchangeFromUrl( exchange: ExchangeRecord; exchangeDetails: ExchangeDetailsRecord; }> { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => handleExchangeUpdateError(ws, baseUrl, e); return await guardOperationException( () => updateExchangeFromUrlImpl(ws, baseUrl, acceptedFormat, forceNow), @@ -429,14 +425,13 @@ async function downloadExchangeKeysInfo( logger.info("received /keys response"); if (exchangeKeysJsonUnchecked.denoms.length === 0) { - const opErr = makeErrorDetails( + throw TalerError.fromDetail( TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT, - "exchange doesn't offer any denominations", { exchangeBaseUrl: baseUrl, }, + "exchange doesn't offer any denominations", ); - throw new OperationFailedError(opErr); } const protocolVersion = exchangeKeysJsonUnchecked.version; @@ -446,15 +441,14 @@ async function downloadExchangeKeysInfo( protocolVersion, ); if (versionRes?.compatible != true) { - const opErr = makeErrorDetails( + throw TalerError.fromDetail( TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE, - "exchange protocol version not compatible with wallet", { exchangeProtocolVersion: protocolVersion, walletProtocolVersion: WALLET_EXCHANGE_PROTOCOL_VERSION, }, + "exchange protocol version not compatible with wallet", ); - throw new OperationFailedError(opErr); } const currency = Amounts.parseOrThrow( diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 9521d544f..ce3a26c34 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -25,6 +25,7 @@ * Imports. */ import { + AbsoluteTime, AmountJson, Amounts, codecForContractTerms, @@ -34,7 +35,6 @@ import { ConfirmPayResult, ConfirmPayResultType, ContractTerms, - decodeCrock, Duration, durationMax, durationMin, @@ -43,19 +43,17 @@ import { getRandomBytes, HttpStatusCode, j2s, - kdf, Logger, NotificationType, parsePayUri, PreparePayResult, PreparePayResultType, RefreshReason, - stringToBytes, TalerErrorCode, - TalerErrorDetails, - AbsoluteTime, - URL, + TalerErrorDetail, TalerProtocolTimestamp, + TransactionType, + URL, } from "@gnu-taler/taler-util"; import { EXCHANGE_COINS_LOCK, InternalWalletState } from "../common.js"; import { @@ -74,9 +72,9 @@ import { } from "../db.js"; import { guardOperationException, - makeErrorDetails, - OperationFailedAndReportedError, - OperationFailedError, + makeErrorDetail, + makePendingOperationFailedError, + TalerError, } from "../errors.js"; import { AvailableCoinInfo, @@ -467,7 +465,7 @@ async function recordConfirmPay( async function reportProposalError( ws: InternalWalletState, proposalId: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ proposals: x.proposals })) @@ -550,7 +548,7 @@ async function incrementPurchasePayRetry( async function reportPurchasePayError( ws: InternalWalletState, proposalId: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ purchases: x.purchases })) @@ -575,7 +573,7 @@ export async function processDownloadProposal( proposalId: string, forceNow = false, ): Promise { - const onOpErr = (err: TalerErrorDetails): Promise => + const onOpErr = (err: TalerErrorDetail): Promise => reportProposalError(ws, proposalId, err); await guardOperationException( () => processDownloadProposalImpl(ws, proposalId, forceNow), @@ -602,7 +600,7 @@ async function resetDownloadProposalRetry( async function failProposalPermanently( ws: InternalWalletState, proposalId: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ proposals: x.proposals })) @@ -727,13 +725,13 @@ async function processDownloadProposalImpl( if (r.isError) { switch (r.talerErrorResponse.code) { case TalerErrorCode.MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED: - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED, - "order already claimed (likely by other wallet)", { orderId: proposal.orderId, claimUrl: orderClaimUrl, }, + "order already claimed (likely by other wallet)", ); default: throwUnexpectedRequestError(httpResponse, r.talerErrorResponse); @@ -758,13 +756,17 @@ async function processDownloadProposalImpl( logger.trace( `malformed contract terms: ${j2s(proposalResp.contract_terms)}`, ); - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED, - "validation for well-formedness failed", {}, + "validation for well-formedness failed", ); await failProposalPermanently(ws, proposalId, err); - throw new OperationFailedAndReportedError(err); + throw makePendingOperationFailedError( + err, + TransactionType.Payment, + proposalId, + ); } const contractTermsHash = ContractTermsUtil.hashContractTerms( @@ -780,13 +782,17 @@ async function processDownloadProposalImpl( proposalResp.contract_terms, ); } catch (e) { - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED, - `schema validation failed: ${e}`, {}, + `schema validation failed: ${e}`, ); await failProposalPermanently(ws, proposalId, err); - throw new OperationFailedAndReportedError(err); + throw makePendingOperationFailedError( + err, + TransactionType.Payment, + proposalId, + ); } const sigValid = await ws.cryptoApi.isValidContractTermsSignature( @@ -796,16 +802,20 @@ async function processDownloadProposalImpl( ); if (!sigValid) { - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_CONTRACT_TERMS_SIGNATURE_INVALID, - "merchant's signature on contract terms is invalid", { merchantPub: parsedContractTerms.merchant_pub, orderId: parsedContractTerms.order_id, }, + "merchant's signature on contract terms is invalid", ); await failProposalPermanently(ws, proposalId, err); - throw new OperationFailedAndReportedError(err); + throw makePendingOperationFailedError( + err, + TransactionType.Payment, + proposalId, + ); } const fulfillmentUrl = parsedContractTerms.fulfillment_url; @@ -814,16 +824,20 @@ async function processDownloadProposalImpl( const baseUrlFromContractTerms = parsedContractTerms.merchant_base_url; if (baseUrlForDownload !== baseUrlFromContractTerms) { - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH, - "merchant base URL mismatch", { baseUrlForDownload, baseUrlFromContractTerms, }, + "merchant base URL mismatch", ); await failProposalPermanently(ws, proposalId, err); - throw new OperationFailedAndReportedError(err); + throw makePendingOperationFailedError( + err, + TransactionType.Payment, + proposalId, + ); } const contractData = extractContractData( @@ -895,10 +909,8 @@ async function startDownloadProposal( ]); }); - /** - * If we have already claimed this proposal with the same sessionId - * nonce and claim token, reuse it. - */ + /* If we have already claimed this proposal with the same sessionId + * nonce and claim token, reuse it. */ if ( oldProposal && oldProposal.downloadSessionId === sessionId && @@ -1029,7 +1041,7 @@ async function storePayReplaySuccess( async function handleInsufficientFunds( ws: InternalWalletState, proposalId: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { logger.trace("handling insufficient funds, trying to re-select coins"); @@ -1319,12 +1331,12 @@ export async function preparePayForUri( const uriResult = parsePayUri(talerPayUri); if (!uriResult) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_INVALID_TALER_PAY_URI, - `invalid taler://pay URI (${talerPayUri})`, { talerPayUri, }, + `invalid taler://pay URI (${talerPayUri})`, ); } @@ -1503,7 +1515,7 @@ export async function processPurchasePay( proposalId: string, forceNow = false, ): Promise { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => reportPurchasePayError(ws, proposalId, e); return await guardOperationException( () => processPurchasePayImpl(ws, proposalId, forceNow), @@ -1527,9 +1539,8 @@ async function processPurchasePayImpl( lastError: { // FIXME: allocate more specific error code code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - message: `trying to pay for purchase that is not in the database`, - hint: `proposal ID is ${proposalId}`, - details: {}, + hint: `trying to pay for purchase that is not in the database`, + proposalId: proposalId, }, }; } @@ -1594,10 +1605,10 @@ async function processPurchasePayImpl( resp.status <= 599 ) { logger.trace("treating /pay error as transient"); - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - "/pay failed", getHttpResponseErrorDetails(resp), + "/pay failed", ); return { type: ConfirmPayResultType.Pending, @@ -1621,8 +1632,11 @@ async function processPurchasePayImpl( delete purch.payRetryInfo; await tx.purchases.put(purch); }); - // FIXME: Maybe introduce a new return type for this instead of throwing? - throw new OperationFailedAndReportedError(errDetails); + throw makePendingOperationFailedError( + errDetails, + TransactionType.Payment, + proposalId, + ); } if (resp.status === HttpStatusCode.Conflict) { @@ -1692,10 +1706,10 @@ async function processPurchasePayImpl( resp.status >= 500 && resp.status <= 599 ) { - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - "/paid failed", getHttpResponseErrorDetails(resp), + "/paid failed", ); return { type: ConfirmPayResultType.Pending, @@ -1703,10 +1717,10 @@ async function processPurchasePayImpl( }; } if (resp.status !== 204) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - "/paid failed", getHttpResponseErrorDetails(resp), + "/paid failed", ); } await storePayReplaySuccess(ws, proposalId, sessionId); diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 84a27966d..56c13f1b0 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -30,7 +30,7 @@ import { j2s, NotificationType, RefreshReason, - TalerErrorDetails, + TalerErrorDetail, TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; @@ -60,7 +60,7 @@ const logger = new Logger("operations/recoup.ts"); async function incrementRecoupRetry( ws: InternalWalletState, recoupGroupId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ @@ -384,7 +384,7 @@ export async function processRecoupGroup( forceNow = false, ): Promise { await ws.memoProcessRecoup.memo(recoupGroupId, async () => { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementRecoupRetry(ws, recoupGroupId, e); return await guardOperationException( async () => await processRecoupGroupImpl(ws, recoupGroupId, forceNow), diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 11f0f6c51..7753992f7 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -43,7 +43,7 @@ import { NotificationType, RefreshGroupId, RefreshReason, - TalerErrorDetails, + TalerErrorDetail, } from "@gnu-taler/taler-util"; import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { amountToPretty } from "@gnu-taler/taler-util"; @@ -714,7 +714,7 @@ async function refreshReveal( async function incrementRefreshRetry( ws: InternalWalletState, refreshGroupId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ @@ -747,7 +747,7 @@ export async function processRefreshGroup( forceNow = false, ): Promise { await ws.memoProcessRefresh.memo(refreshGroupId, async () => { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementRefreshRetry(ws, refreshGroupId, e); return await guardOperationException( async () => await processRefreshGroupImpl(ws, refreshGroupId, forceNow), diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts index 686d545df..d888ff015 100644 --- a/packages/taler-wallet-core/src/operations/refund.ts +++ b/packages/taler-wallet-core/src/operations/refund.ts @@ -40,7 +40,7 @@ import { parseRefundUri, RefreshReason, TalerErrorCode, - TalerErrorDetails, + TalerErrorDetail, URL, codecForMerchantOrderStatusPaid, AbsoluteTime, @@ -88,7 +88,7 @@ async function resetPurchaseQueryRefundRetry( async function incrementPurchaseQueryRefundRetry( ws: InternalWalletState, proposalId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ @@ -592,7 +592,7 @@ export async function processPurchaseQueryRefund( proposalId: string, forceNow = false, ): Promise { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementPurchaseQueryRefundRetry(ws, proposalId, e); await guardOperationException( () => processPurchaseQueryRefundImpl(ws, proposalId, forceNow, true), diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index ac9483631..baa977033 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -34,7 +34,7 @@ import { NotificationType, randomBytes, TalerErrorCode, - TalerErrorDetails, + TalerErrorDetail, AbsoluteTime, URL, } from "@gnu-taler/taler-util"; @@ -47,7 +47,7 @@ import { WalletStoresV1, WithdrawalGroupRecord, } from "../db.js"; -import { guardOperationException, OperationFailedError } from "../errors.js"; +import { guardOperationException, TalerError } from "../errors.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; import { readSuccessResponseJsonOrErrorCode, @@ -135,7 +135,7 @@ async function incrementReserveRetry( async function reportReserveError( ws: InternalWalletState, reservePub: string, - err: TalerErrorDetails, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ @@ -338,7 +338,7 @@ export async function processReserve( forceNow = false, ): Promise { return ws.memoProcessReserve.memo(reservePub, async () => { - const onOpError = (err: TalerErrorDetails): Promise => + const onOpError = (err: TalerErrorDetail): Promise => reportReserveError(ws, reservePub, err); await guardOperationException( () => processReserveImpl(ws, reservePub, forceNow), @@ -571,7 +571,7 @@ async function updateReserve( if ( resp.status === 404 && result.talerErrorResponse.code === - TalerErrorCode.EXCHANGE_RESERVES_GET_STATUS_UNKNOWN + TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN ) { ws.notify({ type: NotificationType.ReserveNotYetFound, @@ -803,9 +803,8 @@ export async function createTalerWithdrawReserve( return tx.reserves.get(reserve.reservePub); }); if (processedReserve?.reserveStatus === ReserveRecordStatus.BankAborted) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, - "withdrawal aborted by bank", {}, ); } diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 765120294..7b3d36a7c 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -22,7 +22,7 @@ import { parseTipUri, codecForTipPickupGetResponse, Amounts, - TalerErrorDetails, + TalerErrorDetail, NotificationType, TipPlanchetDetail, TalerErrorCode, @@ -44,7 +44,7 @@ import { import { j2s } from "@gnu-taler/taler-util"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; -import { guardOperationException, makeErrorDetails } from "../errors.js"; +import { guardOperationException, makeErrorDetail } from "../errors.js"; import { updateExchangeFromUrl } from "./exchanges.js"; import { InternalWalletState } from "../common.js"; import { @@ -163,7 +163,7 @@ export async function prepareTip( async function incrementTipRetry( ws: InternalWalletState, walletTipId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ @@ -192,7 +192,7 @@ export async function processTip( tipId: string, forceNow = false, ): Promise { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementTipRetry(ws, tipId, e); await guardOperationException( () => processTipImpl(ws, tipId, forceNow), @@ -296,10 +296,10 @@ async function processTipImpl( merchantResp.status === 424) ) { logger.trace(`got transient tip error`); - const err = makeErrorDetails( + const err = makeErrorDetail( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, - "tip pickup failed (transient)", getHttpResponseErrorDetails(merchantResp), + "tip pickup failed (transient)", ); await incrementTipRetry(ws, tipRecord.walletTipId, err); // FIXME: Maybe we want to signal to the caller that the transient error happened? @@ -355,10 +355,10 @@ async function processTipImpl( if (!tipRecord) { return; } - tipRecord.lastError = makeErrorDetails( + tipRecord.lastError = makeErrorDetail( TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID, - "invalid signature from the exchange (via merchant tip) after unblinding", {}, + "invalid signature from the exchange (via merchant tip) after unblinding", ); await tx.tips.put(tipRecord); }); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index e4c6f2a6a..1d7bf9303 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -30,7 +30,7 @@ import { NotificationType, parseWithdrawUri, TalerErrorCode, - TalerErrorDetails, + TalerErrorDetail, AbsoluteTime, WithdrawResponse, URL, @@ -42,6 +42,7 @@ import { ExchangeWithdrawRequest, Duration, TalerProtocolTimestamp, + TransactionType, } from "@gnu-taler/taler-util"; import { CoinRecord, @@ -63,9 +64,11 @@ import { } from "../util/http.js"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; import { + getErrorDetailFromException, guardOperationException, - makeErrorDetails, - OperationFailedError, + makeErrorDetail, + makePendingOperationFailedError, + TalerError, } from "../errors.js"; import { InternalWalletState } from "../common.js"; import { @@ -299,15 +302,14 @@ export async function getBankWithdrawalInfo( config.version, ); if (versionRes?.compatible != true) { - const opErr = makeErrorDetails( + throw TalerError.fromDetail( TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE, - "bank integration protocol version not compatible with wallet", { exchangeProtocolVersion: config.version, walletProtocolVersion: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, }, + "bank integration protocol version not compatible with wallet", ); - throw new OperationFailedError(opErr); } const reqUrl = new URL( @@ -526,12 +528,9 @@ async function processPlanchetExchangeRequest( ); return r; } catch (e) { + const errDetail = getErrorDetailFromException(e); logger.trace("withdrawal request failed", e); logger.trace(e); - if (!(e instanceof OperationFailedError)) { - throw e; - } - const errDetails = e.operationError; await ws.db .mktx((x) => ({ planchets: x.planchets })) .runReadWrite(async (tx) => { @@ -542,7 +541,7 @@ async function processPlanchetExchangeRequest( if (!planchet) { return; } - planchet.lastError = errDetails; + planchet.lastError = errDetail; await tx.planchets.put(planchet); }); return; @@ -628,10 +627,10 @@ async function processPlanchetVerifyAndStoreCoin( if (!planchet) { return; } - planchet.lastError = makeErrorDetails( + planchet.lastError = makeErrorDetail( TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID, - "invalid signature from the exchange after unblinding", {}, + "invalid signature from the exchange after unblinding", ); await tx.planchets.put(planchet); }); @@ -797,7 +796,7 @@ export async function updateWithdrawalDenoms( async function incrementWithdrawalRetry( ws: InternalWalletState, withdrawalGroupId: string, - err: TalerErrorDetails | undefined, + err: TalerErrorDetail | undefined, ): Promise { await ws.db .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups })) @@ -821,7 +820,7 @@ export async function processWithdrawGroup( withdrawalGroupId: string, forceNow = false, ): Promise { - const onOpErr = (e: TalerErrorDetails): Promise => + const onOpErr = (e: TalerErrorDetail): Promise => incrementWithdrawalRetry(ws, withdrawalGroupId, e); await guardOperationException( () => processWithdrawGroupImpl(ws, withdrawalGroupId, forceNow), @@ -919,7 +918,7 @@ async function processWithdrawGroupImpl( let numFinished = 0; let finishedForFirstTime = false; - let errorsPerCoin: Record = {}; + let errorsPerCoin: Record = {}; await ws.db .mktx((x) => ({ @@ -957,12 +956,12 @@ async function processWithdrawGroupImpl( }); if (numFinished != numTotalCoins) { - throw OperationFailedError.fromCode( + throw TalerError.fromDetail( TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE, - `withdrawal did not finish (${numFinished} / ${numTotalCoins} coins withdrawn)`, { errorsPerCoin, }, + `withdrawal did not finish (${numFinished} / ${numTotalCoins} coins withdrawn)`, ); } -- cgit v1.2.3