From 2e2cf4049a771c82fcc520686de3ace7603baa05 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 15 Jan 2024 17:34:19 -0300 Subject: fixes #8083 --- .../src/operations/transactions.ts | 62 ++++++++++++++++-- .../taler-wallet-core/src/operations/withdraw.ts | 75 ++++++++++++++-------- packages/taler-wallet-core/src/wallet-api-types.ts | 22 +++++-- packages/taler-wallet-core/src/wallet.ts | 16 +++-- 4 files changed, 131 insertions(+), 44 deletions(-) (limited to 'packages/taler-wallet-core') diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 908aa540a..d93396ca5 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -41,7 +41,9 @@ import { TransactionsResponse, TransactionState, TransactionType, + TransactionWithdrawal, WalletContractData, + WithdrawalTransactionByURIRequest, WithdrawalType, } from "@gnu-taler/taler-util"; import { @@ -520,7 +522,7 @@ function buildTransactionForPeerPullCredit( const silentWithdrawalErrorForInvoice = wsrOrt?.lastError && wsrOrt.lastError.code === - TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE && + TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE && Object.values(wsrOrt.lastError.errorsPerCoin ?? {}).every((e) => { return ( e.code === TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR && @@ -550,10 +552,10 @@ function buildTransactionForPeerPullCredit( kycUrl: pullCredit.kycUrl, ...(wsrOrt?.lastError ? { - error: silentWithdrawalErrorForInvoice - ? undefined - : wsrOrt.lastError, - } + error: silentWithdrawalErrorForInvoice + ? undefined + : wsrOrt.lastError, + } : {}), }; } @@ -641,7 +643,7 @@ function buildTransactionForPeerPushCredit( function buildTransactionForBankIntegratedWithdraw( wgRecord: WithdrawalGroupRecord, ort?: OperationRetryRecord, -): Transaction { +): TransactionWithdrawal { if (wgRecord.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) throw Error(""); @@ -676,7 +678,7 @@ function buildTransactionForManualWithdraw( withdrawalGroup: WithdrawalGroupRecord, exchangeDetails: ExchangeWireDetails, ort?: OperationRetryRecord, -): Transaction { +): TransactionWithdrawal { if (withdrawalGroup.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual) throw Error(""); @@ -948,6 +950,52 @@ async function buildTransactionForPurchase( }; } +export async function getWithdrawalTransactionByUri( + ws: InternalWalletState, + request: WithdrawalTransactionByURIRequest, +): Promise { + return await ws.db + .mktx((x) => [ + x.withdrawalGroups, + x.exchangeDetails, + x.exchanges, + x.operationRetries, + ]) + .runReadWrite(async (tx) => { + const withdrawalGroupRecord = await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get( + request.talerWithdrawUri, + ); + + if (!withdrawalGroupRecord) { + return undefined; + } + + const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord); + const ort = await tx.operationRetries.get(opId); + + if ( + withdrawalGroupRecord.wgInfo.withdrawalType === + WithdrawalRecordType.BankIntegrated + ) { + return buildTransactionForBankIntegratedWithdraw( + withdrawalGroupRecord, + ort, + ); + } + const exchangeDetails = await getExchangeWireDetailsInTx( + tx, + withdrawalGroupRecord.exchangeBaseUrl, + ); + if (!exchangeDetails) throw Error("not exchange details"); + + return buildTransactionForManualWithdraw( + withdrawalGroupRecord, + exchangeDetails, + ort, + ); + }); +} + /** * Retrieve the full event history for this wallet. */ diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 58df75964..6c7e8c37a 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -45,6 +45,7 @@ import { LibtoolVersion, Logger, NotificationType, + TalerBankIntegrationHttpClient, TalerError, TalerErrorCode, TalerErrorDetail, @@ -556,19 +557,11 @@ export async function getBankWithdrawalInfo( throw Error(`can't parse URL ${talerWithdrawUri}`); } - const configReqUrl = new URL("config", uriResult.bankIntegrationApiBaseUrl); + const bankApi = new TalerBankIntegrationHttpClient(uriResult.bankIntegrationApiBaseUrl, http); - const configResp = await http.fetch(configReqUrl.href); - const config = await readSuccessResponseJsonOrThrow( - configResp, - codecForIntegrationBankConfig(), - ); + const { body: config } = await bankApi.getConfig() - const versionRes = LibtoolVersion.compare( - WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, - config.version, - ); - if (versionRes?.compatible != true) { + if (!bankApi.isCompatible(config.version)) { throw TalerError.fromDetail( TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE, { @@ -579,29 +572,24 @@ export async function getBankWithdrawalInfo( ); } - const reqUrl = new URL( - `withdrawal-operation/${uriResult.withdrawalOperationId}`, - uriResult.bankIntegrationApiBaseUrl, - ); - - logger.info(`bank withdrawal status URL: ${reqUrl.href}}`); + const resp = await bankApi.getWithdrawalOperationById(uriResult.withdrawalOperationId) - const resp = await http.fetch(reqUrl.href); - const status = await readSuccessResponseJsonOrThrow( - resp, - codecForWithdrawOperationStatusResponse(), - ); + if (resp.type === "fail") { + throw TalerError.fromUncheckedDetail(resp.detail); + } + const { body: status } = resp logger.info(`bank withdrawal operation status: ${j2s(status)}`); return { + operationId: uriResult.withdrawalOperationId, + apiBaseUrl: uriResult.bankIntegrationApiBaseUrl, amount: Amounts.parseOrThrow(status.amount), confirmTransferUrl: status.confirm_transfer_url, - selectionDone: status.selection_done, senderWire: status.sender_wire, suggestedExchange: status.suggested_exchange, - transferDone: status.transfer_done, wireTypes: status.wire_types, + status: status.status, }; } @@ -1226,8 +1214,7 @@ export async function updateWithdrawalDenoms( denom.verificationStatus === DenominationVerificationStatus.Unverified ) { logger.trace( - `Validating denomination (${current + 1}/${ - denominations.length + `Validating denomination (${current + 1}/${denominations.length }) signature of ${denom.denomPubHash}`, ); let valid = false; @@ -1872,7 +1859,7 @@ export async function getExchangeWithdrawalInfo( ) { logger.warn( `wallet's support for exchange protocol version ${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` + - `(exchange has ${exchange.protocolVersionRange}), checking for updates`, + `(exchange has ${exchange.protocolVersionRange}), checking for updates`, ); } } @@ -1915,6 +1902,7 @@ export async function getExchangeWithdrawalInfo( export interface GetWithdrawalDetailsForUriOpts { restrictAge?: number; + notifyChangeFromPendingTimeoutMs?: number; } /** @@ -1957,7 +1945,40 @@ export async function getWithdrawalDetailsForUri( ); }); + if (info.status === "pending" && opts.notifyChangeFromPendingTimeoutMs !== undefined) { + const bankApi = new TalerBankIntegrationHttpClient(info.apiBaseUrl, ws.http); + console.log( + `waiting operation (${info.operationId}) to change from pending`, + ); + bankApi.getWithdrawalOperationById(info.operationId, { + old_state: "pending", + timeoutMs: opts.notifyChangeFromPendingTimeoutMs + }).then(resp => { + console.log( + `operation (${info.operationId}) to change to ${JSON.stringify(resp, undefined, 2)}`, + ); + if (resp.type === "fail") { + //not found, this is rare since the previous request succeed + ws.notify({ + type: NotificationType.WithdrawalOperationTransition, + operationId: info.operationId, + state: info.status, + }) + return; + } + + ws.notify({ + type: NotificationType.WithdrawalOperationTransition, + operationId: info.operationId, + state: resp.body.status, + }); + }) + } + return { + operationId: info.operationId, + confirmTransferUrl: info.confirmTransferUrl, + status: info.status, amount: Amounts.stringify(info.amount), defaultExchangeBaseUrl: info.suggestedExchange, possibleExchanges, diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 7ac347b6d..7d3dc86a3 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -128,6 +128,8 @@ import { WalletCurrencyInfo, WithdrawTestBalanceRequest, WithdrawUriInfoResponse, + WithdrawalTransactionByURIRequest, + TransactionWithdrawal, } from "@gnu-taler/taler-util"; import { AddBackupProviderRequest, @@ -154,6 +156,7 @@ export enum WalletApiOperation { AddExchange = "addExchange", GetTransactions = "getTransactions", GetTransactionById = "getTransactionById", + GetWithdrawalTransactionByUri = "getWithdrawalTransactionByUri", TestingGetSampleTransactions = "testingGetSampleTransactions", ListExchanges = "listExchanges", GetExchangeEntryByUrl = "getExchangeEntryByUrl", @@ -377,6 +380,12 @@ export type GetTransactionByIdOp = { response: Transaction; }; +export type GetWithdrawalTransactionByUriOp = { + op: WalletApiOperation.GetWithdrawalTransactionByUri; + request: WithdrawalTransactionByURIRequest; + response: TransactionWithdrawal | undefined; +}; + export type RetryPendingNowOp = { op: WalletApiOperation.RetryPendingNow; request: EmptyObject; @@ -1124,6 +1133,7 @@ export type WalletOperations = { [WalletApiOperation.GetTransactions]: GetTransactionsOp; [WalletApiOperation.TestingGetSampleTransactions]: TestingGetSampleTransactionsOp; [WalletApiOperation.GetTransactionById]: GetTransactionByIdOp; + [WalletApiOperation.GetWithdrawalTransactionByUri]: GetWithdrawalTransactionByUriOp; [WalletApiOperation.RetryPendingNow]: RetryPendingNowOp; [WalletApiOperation.GetPendingOperations]: GetPendingTasksOp; [WalletApiOperation.GetUserAttentionRequests]: GetUserAttentionRequests; @@ -1219,10 +1229,10 @@ type Primitives = string | number | boolean; type RecursivePartial = { [P in keyof T]?: T[P] extends Array - ? Array> - : T[P] extends Array - ? Array - : T[P] extends object - ? RecursivePartial - : T[P]; + ? Array> + : T[P] extends Array + ? Array + : T[P] extends object + ? RecursivePartial + : T[P]; } & object; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index d6da2250a..1a876b2c8 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -264,6 +264,7 @@ import { failTransaction, getTransactionById, getTransactions, + getWithdrawalTransactionByUri, parseTransactionIdentifier, resumeTransaction, retryTransaction, @@ -725,9 +726,9 @@ async function dumpCoins(ws: InternalWalletState): Promise { ageCommitmentProof: c.ageCommitmentProof, spend_allocation: c.spendAllocation ? { - amount: c.spendAllocation.amount, - id: c.spendAllocation.id, - } + amount: c.spendAllocation.amount, + id: c.spendAllocation.id, + } : undefined, }); } @@ -938,6 +939,10 @@ async function dispatchRequestInternal( const req = codecForTransactionByIdRequest().decode(payload); return await getTransactionById(ws, req); } + case WalletApiOperation.GetWithdrawalTransactionByUri: { + const req = codecForGetWithdrawalDetailsForUri().decode(payload); + return await getWithdrawalTransactionByUri(ws, req); + } case WalletApiOperation.AddExchange: { const req = codecForAddExchangeRequest().decode(payload); await fetchFreshExchange(ws, req.exchangeBaseUrl, { @@ -997,7 +1002,10 @@ async function dispatchRequestInternal( } case WalletApiOperation.GetWithdrawalDetailsForUri: { const req = codecForGetWithdrawalDetailsForUri().decode(payload); - return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri); + return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri, { + notifyChangeFromPendingTimeoutMs: req.notifyChangeFromPendingTimeoutMs, + restrictAge: req.restrictAge, + }); } case WalletApiOperation.AcceptManualWithdrawal: { const req = codecForAcceptManualWithdrawalRequet().decode(payload); -- cgit v1.2.3