From a09359bd3930f2a4550de22bd018122a2c7846e6 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 24 Aug 2021 15:08:34 +0200 Subject: implement freezing for payments --- packages/taler-util/src/transactionsTypes.ts | 6 ++++++ .../src/integrationtests/test-denom-unoffered.ts | 7 ------- packages/taler-wallet-core/src/db.ts | 6 ++++++ packages/taler-wallet-core/src/operations/pay.ts | 21 +++++++++++++++++++++ .../taler-wallet-core/src/operations/pending.ts | 6 +++++- .../src/operations/transactions.ts | 6 ++++++ packages/taler-wallet-core/src/util/http.ts | 1 + 7 files changed, 45 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/taler-util/src/transactionsTypes.ts b/packages/taler-util/src/transactionsTypes.ts index e29a5549d..2ee34022f 100644 --- a/packages/taler-util/src/transactionsTypes.ts +++ b/packages/taler-util/src/transactionsTypes.ts @@ -80,6 +80,12 @@ export interface TransactionCommon { // but its transactionId will remain unchanged pending: boolean; + /** + * True if the transaction encountered a problem that might be + * permanent. A frozen transaction won't be automatically retried. + */ + frozen: boolean; + // Raw amount of the transaction (exclusive of fees or other extra costs) amountRaw: AmountString; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts b/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts index 7a1ff669a..430a1ac93 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts @@ -112,13 +112,6 @@ export async function runDenomUnofferedTest(t: GlobalTestState) { merchantErrorCode, TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND, ); - - const purchId = makeEventId(TransactionType.Payment, preparePayResult.proposalId); - await wallet.client.call(WalletApiOperation.DeleteTransaction, { - transactionId: purchId, - }); - - // Now, delete the purchase and refresh operation. } await wallet.client.call(WalletApiOperation.AddExchange, { diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index ef6b45c11..66d79ebc0 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1276,6 +1276,12 @@ export interface PurchaseRecord { * Continue querying the refund status until this deadline has expired. */ autoRefundDeadline: Timestamp | undefined; + + /** + * Is the payment frozen? I.e. did we encounter + * an error where it doesn't make sense to retry. + */ + payFrozen?: boolean; } export const WALLET_BACKUP_STATE_KEY = "walletBackupState"; diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index a201e6f8d..9a7b0d069 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -93,6 +93,7 @@ import { readSuccessResponseJsonOrErrorCode, readSuccessResponseJsonOrThrow, readTalerErrorResponse, + readUnexpectedResponseDetails, throwUnexpectedRequestError, } from "../util/http.js"; import { @@ -1209,6 +1210,26 @@ async function submitPay( }; } + if (resp.status === HttpResponseStatus.BadRequest) { + const errDetails = await readUnexpectedResponseDetails(resp); + logger.warn("unexpected 400 response for /pay"); + logger.warn(j2s(errDetails)); + await ws.db + .mktx((x) => ({ purchases: x.purchases })) + .runReadWrite(async (tx) => { + const purch = await tx.purchases.get(proposalId); + if (!purch) { + return; + } + purch.payFrozen = true; + purch.lastPayError = errDetails; + delete purch.payRetryInfo; + await tx.purchases.put(purch); + }); + // FIXME: Maybe introduce a new return type for this instead of throwing? + throw new OperationFailedAndReportedError(errDetails); + } + if (resp.status === HttpResponseStatus.Conflict) { const err = await readTalerErrorResponse(resp); if ( diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index a4ca972a7..a87b1c8b1 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -235,7 +235,11 @@ async function gatherPurchasePending( resp: PendingOperationsResponse, ): Promise { await tx.purchases.iter().forEach((pr) => { - if (pr.paymentSubmitPending && pr.abortStatus === AbortStatus.None) { + if ( + pr.paymentSubmitPending && + pr.abortStatus === AbortStatus.None && + !pr.payFrozen + ) { const timestampDue = pr.payRetryInfo?.nextRetry ?? getTimestampNow(); resp.pendingOperations.push({ type: PendingTaskType.Pay, diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index a21dbe8b8..dc738b77f 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -168,6 +168,7 @@ export async function getTransactions( TransactionType.Withdrawal, wsr.withdrawalGroupId, ), + frozen: false, ...(wsr.lastError ? { error: wsr.lastError } : {}), }); }); @@ -215,6 +216,7 @@ export async function getTransactions( TransactionType.Withdrawal, r.initialWithdrawalGroupId, ), + frozen: false, ...(r.lastError ? { error: r.lastError } : {}), }); }); @@ -230,6 +232,7 @@ export async function getTransactions( amountRaw: Amounts.stringify(dg.effectiveDepositAmount), amountEffective: Amounts.stringify(dg.totalPayCost), pending: !dg.timestampFinished, + frozen: false, timestamp: dg.timestampCreated, targetPaytoUri: dg.wire.payto_uri, transactionId: makeEventId( @@ -288,6 +291,7 @@ export async function getTransactions( transactionId: paymentTransactionId, proposalId: pr.proposalId, info: info, + frozen: pr.payFrozen ?? false, ...(err ? { error: err } : {}), }); @@ -351,6 +355,7 @@ export async function getTransactions( amountEffective: Amounts.stringify(amountEffective), amountRaw: Amounts.stringify(amountRaw), pending: false, + frozen: false, }); } }); @@ -372,6 +377,7 @@ export async function getTransactions( amountEffective: Amounts.stringify(tipRecord.tipAmountEffective), amountRaw: Amounts.stringify(tipRecord.tipAmountRaw), pending: !tipRecord.pickedUpTimestamp, + frozen: false, timestamp: tipRecord.acceptedTimestamp, transactionId: makeEventId( TransactionType.Tip, diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts index ce507465a..e056ffcee 100644 --- a/packages/taler-wallet-core/src/util/http.ts +++ b/packages/taler-wallet-core/src/util/http.ts @@ -64,6 +64,7 @@ export enum HttpResponseStatus { NoContent = 204, Gone = 210, NotModified = 304, + BadRequest = 400, PaymentRequired = 402, NotFound = 404, Conflict = 409, -- cgit v1.2.3