From df9144129653cf596fc819cf9e7c96f6d0470a69 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 4 Nov 2020 12:07:34 +0100 Subject: handle transient pay errors (fixes #6607) Also add a test case for the behavior. --- packages/taler-wallet-core/src/operations/pay.ts | 53 +++++++++++++++++++--- .../taler-wallet-core/src/types/notifications.ts | 16 ++++++- 2 files changed, 61 insertions(+), 8 deletions(-) (limited to 'packages/taler-wallet-core') diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 6079ea08f..442aeca71 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -435,7 +435,7 @@ async function recordConfirmPay( } else { sessionId = proposal.downloadSessionId; } - logger.trace(`recording payment with session ID ${sessionId}`); + logger.trace(`recording payment on ${proposal.orderId} with session ID ${sessionId}`); const payCostInfo = await getTotalPaymentCost(ws, coinSelection); const t: PurchaseRecord = { abortStatus: AbortStatus.None, @@ -530,10 +530,6 @@ async function incrementProposalRetry( } } -/** - * FIXME: currently pay operations aren't ever automatically retried. - * But we still keep a payRetryInfo around in the database. - */ async function incrementPurchasePayRetry( ws: InternalWalletState, proposalId: string, @@ -947,7 +943,7 @@ async function submitPay( session_id: purchase.lastSessionId, }; - logger.trace("making pay request", JSON.stringify(reqBody, undefined, 2)); + logger.trace("making pay request ... ", JSON.stringify(reqBody, undefined, 2)); const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () => ws.http.postJson(payUrl, reqBody, { @@ -955,6 +951,27 @@ async function submitPay( }), ); + logger.trace(`got resp ${JSON.stringify(resp)}`); + + // Hide transient errors. + if ( + purchase.payRetryInfo.retryCounter <= 5 && + resp.status >= 500 && + resp.status <= 599 + ) { + logger.trace("treating /pay error as transient"); + const err = makeErrorDetails( + TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, + "/pay failed", + getHttpResponseErrorDetails(resp), + ); + incrementPurchasePayRetry(ws, proposalId, undefined); + return { + type: ConfirmPayResultType.Pending, + lastError: err, + }; + } + const merchantResp = await readSuccessResponseJsonOrThrow( resp, codecForMerchantPayResponse(), @@ -989,6 +1006,23 @@ async function submitPay( const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () => ws.http.postJson(payAgainUrl, reqBody), ); + // Hide transient errors. + if ( + purchase.payRetryInfo.retryCounter <= 5 && + resp.status >= 500 && + resp.status <= 599 + ) { + const err = makeErrorDetails( + TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, + "/paid failed", + getHttpResponseErrorDetails(resp), + ); + incrementPurchasePayRetry(ws, proposalId, undefined); + return { + type: ConfirmPayResultType.Pending, + lastError: err, + }; + } if (resp.status !== 204) { throw OperationFailedError.fromCode( TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, @@ -999,6 +1033,11 @@ async function submitPay( await storePayReplaySuccess(ws, proposalId, sessionId); } + ws.notify({ + type: NotificationType.PayOperationSuccess, + proposalId: purchase.proposalId, + }); + return { type: ConfirmPayResultType.Done, contractTerms: JSON.parse(purchase.contractTermsRaw), @@ -1171,7 +1210,7 @@ export async function confirmPay( let purchase = await ws.db.get( Stores.purchases, - d.contractData.contractTermsHash, + proposalId, ); if (purchase) { diff --git a/packages/taler-wallet-core/src/types/notifications.ts b/packages/taler-wallet-core/src/types/notifications.ts index d86c5ae59..7faf730ef 100644 --- a/packages/taler-wallet-core/src/types/notifications.ts +++ b/packages/taler-wallet-core/src/types/notifications.ts @@ -53,6 +53,7 @@ export enum NotificationType { ProposalOperationError = "proposal-error", TipOperationError = "tip-error", PayOperationError = "pay-error", + PayOperationSuccess = "pay-operation-success", WithdrawOperationError = "withdraw-error", ReserveNotYetFound = "reserve-not-yet-found", ReserveOperationError = "reserve-error", @@ -220,6 +221,18 @@ export interface ReserveRegisteredWithBankNotification { type: NotificationType.ReserveRegisteredWithBank; } +/** + * Notification sent when a pay (or pay replay) operation succeeded. + * + * We send this notification because the confirmPay request can return + * a "confirmed" response that indicates that the payment has been confirmed + * by the user, but we're still waiting for the payment to succeed or fail. + */ +export interface PayOperationSuccessNotification { + type: NotificationType.PayOperationSuccess; + proposalId: string; +} + export type WalletNotification = | WithdrawOperationErrorNotification | ReserveOperationErrorNotification @@ -254,4 +267,5 @@ export type WalletNotification = | PendingOperationProcessedNotification | ProposalRefusedNotification | ReserveRegisteredWithBankNotification - | ReserveNotYetFoundNotification; + | ReserveNotYetFoundNotification + | PayOperationSuccessNotification; -- cgit v1.2.3