diff options
author | Florian Dold <florian@dold.me> | 2022-08-24 19:44:24 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2022-08-24 19:44:24 +0200 |
commit | 42c2b7508f898ccce4305d56084ced971d99ed52 (patch) | |
tree | 712257afe6d0a5ce9af079074e6615f32c295a97 /packages/taler-wallet-core/src | |
parent | d32d2895cedad656d7130522722f40642c439de6 (diff) | |
download | wallet-core-42c2b7508f898ccce4305d56084ced971d99ed52.tar.xz |
wallet-core: fix withdrawal idempotency
Diffstat (limited to 'packages/taler-wallet-core/src')
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 10 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/dbless.ts | 11 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/withdraw.ts | 77 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 5 |
4 files changed, 67 insertions, 36 deletions
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index bc0bb4f65..3d59ce0a7 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -112,11 +112,7 @@ export enum ReserveRecordStatus { * with a bank-integrated withdrawal. */ export interface ReserveBankInfo { - /** - * Status URL that the wallet will use to query the status - * of the Taler withdrawal operation on the bank's side. - */ - statusUrl: string; + talerWithdrawUri: string; /** * URL that the user can be redirected to, and allows @@ -1799,6 +1795,10 @@ export const WalletStoresV1 = { { byReservePub: describeIndex("byReservePub", "reservePub"), byStatus: describeIndex("byStatus", "operationStatus"), + byTalerWithdrawUri: describeIndex( + "byTalerWithdrawUri", + "bankInfo.talerWithdrawUri", + ), }, ), planchets: describeStore( diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index 9bc9c36bf..4669b0be7 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -46,6 +46,8 @@ import { parsePaytoUri, AbsoluteTime, UnblindedSignature, + BankWithdrawDetails, + parseWithdrawUri, } from "@gnu-taler/taler-util"; import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; import { DenominationRecord } from "./db.js"; @@ -57,7 +59,12 @@ import { isWithdrawableDenom, readSuccessResponseJsonOrThrow, } from "./index.browser.js"; -import { BankAccessApi, BankApi, BankServiceHandle } from "./index.js"; +import { + BankAccessApi, + BankApi, + BankServiceHandle, + getBankStatusUrl, +} from "./index.js"; const logger = new Logger("dbless.ts"); @@ -119,7 +126,7 @@ export async function topupReserveWithDemobank( amount, ); const bankInfo = await getBankWithdrawalInfo(http, wopi.taler_withdraw_uri); - const bankStatusUrl = bankInfo.extractedStatusUrl; + const bankStatusUrl = getBankStatusUrl(wopi.taler_withdraw_uri); if (!bankInfo.suggestedExchange) { throw Error("no suggested exchange"); } diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 721a043d7..3c4e2d98c 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -36,7 +36,8 @@ import { codecForWithdrawResponse, DenomKeyType, Duration, - durationFromSpec, encodeCrock, + durationFromSpec, + encodeCrock, ExchangeListItem, ExchangeWithdrawRequest, ForcedDenomSel, @@ -54,7 +55,8 @@ import { VersionMatchResult, WithdrawBatchResponse, WithdrawResponse, - WithdrawUriInfoResponse + WithdrawUriInfoResponse, + WithdrawUriResult, } from "@gnu-taler/taler-util"; import { EddsaKeypair } from "../crypto/cryptoImplementation.js"; import { @@ -71,12 +73,12 @@ import { ReserveBankInfo, ReserveRecordStatus, WalletStoresV1, - WithdrawalGroupRecord + WithdrawalGroupRecord, } from "../db.js"; import { getErrorDetailFromException, makeErrorDetail, - TalerError + TalerError, } from "../errors.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; @@ -85,24 +87,21 @@ import { HttpRequestLibrary, readSuccessResponseJsonOrErrorCode, readSuccessResponseJsonOrThrow, - throwUnexpectedRequestError + throwUnexpectedRequestError, } from "../util/http.js"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; -import { - DbAccess, - GetReadOnlyAccess -} from "../util/query.js"; +import { DbAccess, GetReadOnlyAccess } from "../util/query.js"; import { RetryInfo } from "../util/retries.js"; import { WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, - WALLET_EXCHANGE_PROTOCOL_VERSION + WALLET_EXCHANGE_PROTOCOL_VERSION, } from "../versions.js"; import { guardOperationException } from "./common.js"; import { getExchangeDetails, getExchangePaytoUri, getExchangeTrust, - updateExchangeFromUrl + updateExchangeFromUrl, } from "./exchanges.js"; /** @@ -241,7 +240,7 @@ export function selectWithdrawalDenominations( for (const d of denoms) { let count = 0; const cost = Amounts.add(d.value, d.feeWithdraw).amount; - for (; ;) { + for (;;) { if (Amounts.cmp(remaining, cost) < 0) { break; } @@ -384,7 +383,6 @@ export async function getBankWithdrawalInfo( return { amount: Amounts.parseOrThrow(status.amount), confirmTransferUrl: status.confirm_transfer_url, - extractedStatusUrl: reqUrl.href, selectionDone: status.selection_done, senderWire: status.sender_wire, suggestedExchange: status.suggested_exchange, @@ -898,7 +896,8 @@ 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; @@ -1025,7 +1024,7 @@ async function queryReserve( if ( resp.status === 404 && result.talerErrorResponse.code === - TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN + TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN ) { ws.notify({ type: NotificationType.ReserveNotYetFound, @@ -1315,7 +1314,7 @@ export async function getExchangeWithdrawalInfo( ) { logger.warn( `wallet's support for exchange protocol version ${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` + - `(exchange has ${exchangeDetails.protocolVersion}), checking for updates`, + `(exchange has ${exchangeDetails.protocolVersion}), checking for updates`, ); } } @@ -1400,11 +1399,10 @@ export async function getWithdrawalDetailsForUri( const exchangeRecords = await tx.exchanges.iter().toArray(); for (const r of exchangeRecords) { const details = await ws.exchangeOps.getExchangeDetails(tx, r.baseUrl); - const denominations = await tx.denominations.indexes - .byExchangeBaseUrl.iter(r.baseUrl).toArray(); + const denominations = await tx.denominations.indexes.byExchangeBaseUrl + .iter(r.baseUrl) + .toArray(); if (details && denominations) { - - exchanges.push({ exchangeBaseUrl: details.exchangeBaseUrl, currency: details.currency, @@ -1417,7 +1415,7 @@ export async function getWithdrawalDetailsForUri( paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri), auditors: details.auditors, wireInfo: details.wireInfo, - denominations: denominations + denominations: denominations, }); } } @@ -1502,6 +1500,18 @@ export function getReserveRequestTimeout(r: WithdrawalGroupRecord): Duration { ); } +export function getBankStatusUrl(talerWithdrawUri: string): string { + const uriResult = parseWithdrawUri(talerWithdrawUri); + if (!uriResult) { + throw Error(`can't parse withdrawal URL ${talerWithdrawUri}`); + } + const url = new URL( + `withdrawal-operation/${uriResult.withdrawalOperationId}`, + uriResult.bankIntegrationApiBaseUrl, + ); + return url.href; +} + async function registerReserveWithBank( ws: InternalWalletState, withdrawalGroupId: string, @@ -1524,7 +1534,7 @@ async function registerReserveWithBank( if (!bankInfo) { return; } - const bankStatusUrl = bankInfo.statusUrl; + const bankStatusUrl = getBankStatusUrl(bankInfo.talerWithdrawUri); const httpResp = await ws.http.postJson( bankStatusUrl, { @@ -1584,10 +1594,12 @@ async function processReserveBankStatus( default: return; } - const bankStatusUrl = withdrawalGroup.bankInfo?.statusUrl; - if (!bankStatusUrl) { + if (!withdrawalGroup.bankInfo) { return; } + const bankStatusUrl = getBankStatusUrl( + withdrawalGroup.bankInfo.talerWithdrawUri, + ); const statusResp = await ws.http.get(bankStatusUrl, { timeout: getReserveRequestTimeout(withdrawalGroup), @@ -1778,6 +1790,21 @@ export async function acceptWithdrawalFromUri( restrictAge?: number; }, ): Promise<AcceptWithdrawalResponse> { + const existingWithdrawalGroup = await ws.db + .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups })) + .runReadOnly(async (tx) => { + return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get( + req.talerWithdrawUri, + ); + }); + + if (existingWithdrawalGroup) { + return { + reservePub: existingWithdrawalGroup.reservePub, + confirmTransferUrl: existingWithdrawalGroup.bankInfo?.confirmUrl, + }; + } + await updateExchangeFromUrl(ws, req.selectedExchange); const withdrawInfo = await getBankWithdrawalInfo( ws.http, @@ -1796,7 +1823,7 @@ export async function acceptWithdrawalFromUri( reserveStatus: ReserveRecordStatus.RegisteringBank, bankInfo: { exchangePaytoUri, - statusUrl: withdrawInfo.extractedStatusUrl, + talerWithdrawUri: req.talerWithdrawUri, confirmUrl: withdrawInfo.confirmTransferUrl, }, }); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index ac81660d2..3c83cea6e 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -24,7 +24,6 @@ */ import { AbsoluteTime, - AcceptManualWithdrawalResult, AmountJson, Amounts, BalancesResponse, @@ -98,7 +97,6 @@ import { CoinSourceType, exportDb, importDb, - ReserveRecordStatus, WalletStoresV1, } from "./db.js"; import { getErrorDetailFromException, TalerError } from "./errors.js"; @@ -187,9 +185,8 @@ import { acceptWithdrawalFromUri, createManualWithdrawal, getExchangeWithdrawalInfo, - getFundingPaytoUrisTx, getWithdrawalDetailsForUri, - processWithdrawalGroup as processWithdrawalGroup, + processWithdrawalGroup, } from "./operations/withdraw.js"; import { PendingOperationsResponse, |