diff options
author | Florian Dold <florian@dold.me> | 2024-06-28 11:48:54 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-06-28 11:48:54 +0200 |
commit | ad98210b79f350de631e593ef520f5a57a44812e (patch) | |
tree | 48bafb0531c709b45dde86213c635fbf14426473 /packages | |
parent | 64cb9fcf0da36d5ced33bedc382faa9a64e84719 (diff) | |
download | wallet-core-ad98210b79f350de631e593ef520f5a57a44812e.tar.xz |
wallet-core: support externally confirmed bank-integrated withdrawals
Diffstat (limited to 'packages')
-rw-r--r-- | packages/taler-harness/src/integrationtests/test-withdrawal-external.ts | 101 | ||||
-rw-r--r-- | packages/taler-harness/src/integrationtests/testrunner.ts | 4 | ||||
-rw-r--r-- | packages/taler-util/src/taleruri.test.ts | 12 | ||||
-rw-r--r-- | packages/taler-util/src/taleruri.ts | 9 | ||||
-rw-r--r-- | packages/taler-util/src/transactions-types.ts | 5 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 6 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/transactions.ts | 5 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/withdraw.ts | 36 |
8 files changed, 158 insertions, 20 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-external.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-external.ts new file mode 100644 index 000000000..3cd02882b --- /dev/null +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-external.ts @@ -0,0 +1,101 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { + TransactionMajorState, + TransactionMinorState, + TransactionType, + WithdrawalType, +} from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { GlobalTestState } from "../harness/harness.js"; +import { createSimpleTestkudosEnvironmentV3 } from "../harness/helpers.js"; + +/** + * Test for a withdrawal that is externally confirmed. + */ +export async function runWithdrawalExternalTest(t: GlobalTestState) { + // Set up test environment + + const { walletClient, bankClient, exchange } = + await createSimpleTestkudosEnvironmentV3(t); + + // Create a withdrawal operation + + const bankUser = await bankClient.createRandomBankUser(); + bankClient.setAuth(bankUser); + const wop = await bankClient.createWithdrawalOperation( + bankUser.username, + "TESTKUDOS:10", + ); + + const talerWithdrawUri = wop.taler_withdraw_uri + "?external-confirmation=1"; + + // Hand it to the wallet + + const detResp = await walletClient.call( + WalletApiOperation.GetWithdrawalDetailsForUri, + { + talerWithdrawUri: talerWithdrawUri, + }, + ); + + const acceptResp = await walletClient.call( + WalletApiOperation.AcceptBankIntegratedWithdrawal, + { + exchangeBaseUrl: detResp.defaultExchangeBaseUrl!!, + talerWithdrawUri, + }, + ); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptResp.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + + const txDetails = await walletClient.call( + WalletApiOperation.GetTransactionById, + { + transactionId: acceptResp.transactionId, + }, + ); + + // Now we check that the external-confirmation=1 flag actually did something! + + t.assertDeepEqual(txDetails.type, TransactionType.Withdrawal); + t.assertDeepEqual( + txDetails.withdrawalDetails.type, + WithdrawalType.TalerBankIntegrationApi, + ); + t.assertDeepEqual(txDetails.withdrawalDetails.externalConfirmation, true); + t.assertDeepEqual(txDetails.withdrawalDetails.bankConfirmationUrl, undefined); + + t.logStep("confirming withdrawal operation"); + + await bankClient.confirmWithdrawalOperation(bankUser.username, { + withdrawalOperationId: wop.withdrawal_id, + }); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); +} + +runWithdrawalExternalTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts index 238bf3b98..eb71396e7 100644 --- a/packages/taler-harness/src/integrationtests/testrunner.ts +++ b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -108,6 +108,7 @@ import { runWalletDevExperimentsTest } from "./test-wallet-dev-experiments.js"; import { runWalletExchangeUpdateTest } from "./test-wallet-exchange-update.js"; import { runWalletGenDbTest } from "./test-wallet-gendb.js"; import { runWalletInsufficientBalanceTest } from "./test-wallet-insufficient-balance.js"; +import { runWalletNetworkAvailabilityTest } from "./test-wallet-network-availability.js"; import { runWalletNotificationsTest } from "./test-wallet-notifications.js"; import { runWalletObservabilityTest } from "./test-wallet-observability.js"; import { runWalletRefreshErrorsTest } from "./test-wallet-refresh-errors.js"; @@ -118,13 +119,13 @@ import { runWithdrawalAbortBankTest } from "./test-withdrawal-abort-bank.js"; import { runWithdrawalAmountTest } from "./test-withdrawal-amount.js"; import { runWithdrawalBankIntegratedTest } from "./test-withdrawal-bank-integrated.js"; import { runWithdrawalConversionTest } from "./test-withdrawal-conversion.js"; +import { runWithdrawalExternalTest } from "./test-withdrawal-external.js"; import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js"; import { runWithdrawalFeesTest } from "./test-withdrawal-fees.js"; import { runWithdrawalFlexTest } from "./test-withdrawal-flex.js"; import { runWithdrawalHandoverTest } from "./test-withdrawal-handover.js"; import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js"; import { runWithdrawalManualTest } from "./test-withdrawal-manual.js"; -import { runWalletNetworkAvailabilityTest } from "./test-wallet-network-availability.js"; /** * Test runner. @@ -240,6 +241,7 @@ const allTests: TestMainFunction[] = [ runWithdrawalFlexTest, runExchangeMasterPubChangeTest, runMerchantCategoriesTest, + runWithdrawalExternalTest, ]; export interface TestRunSpec { diff --git a/packages/taler-util/src/taleruri.test.ts b/packages/taler-util/src/taleruri.test.ts index b92366fb3..d80470dab 100644 --- a/packages/taler-util/src/taleruri.test.ts +++ b/packages/taler-util/src/taleruri.test.ts @@ -54,6 +54,18 @@ test("taler withdraw uri parsing", (t) => { t.is(r1.bankIntegrationApiBaseUrl, "https://bank.example.com/"); }); +test("taler withdraw uri parsing with external confirmation", (t) => { + const url1 = "taler://withdraw/bank.example.com/12345?external-confirmation=1"; + const r1 = parseWithdrawUri(url1); + if (!r1) { + t.fail(); + return; + } + t.is(r1.externalConfirmation, true); + t.is(r1.withdrawalOperationId, "12345"); + t.is(r1.bankIntegrationApiBaseUrl, "https://bank.example.com/"); +}); + test("taler withdraw uri parsing (http)", (t) => { const url1 = "taler+http://withdraw/bank.example.com/12345"; const r1 = parseWithdrawUri(url1); diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts index 54b7525e3..b22dc3c59 100644 --- a/packages/taler-util/src/taleruri.ts +++ b/packages/taler-util/src/taleruri.ts @@ -89,6 +89,7 @@ export interface WithdrawUriResult { type: TalerUriAction.Withdraw; bankIntegrationApiBaseUrl: string; withdrawalOperationId: string; + externalConfirmation?: boolean; } export interface RefundUriResult { @@ -140,7 +141,12 @@ export function parseWithdrawUriWithError(s: string) { if (pi.type === "fail") { return pi; } - const parts = pi.body.rest.split("/"); + + const c = pi.body.rest.split("?", 2); + const path = c[0]; + const q = new URLSearchParams(c[1] ?? ""); + + const parts = path.split("/"); if (parts.length < 2) { return opKnownTalerFailure(TalerErrorCode.WALLET_TALER_URI_MALFORMED, { @@ -166,6 +172,7 @@ export function parseWithdrawUriWithError(s: string) { `${pi.body.innerProto}://${p}/`, ), withdrawalOperationId: withdrawId, + externalConfirmation: q.get("external-confirmation") == "1", }; return opFixedSuccess(result); } diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts index a6ac5aec6..b4e2738ee 100644 --- a/packages/taler-util/src/transactions-types.ts +++ b/packages/taler-util/src/transactions-types.ts @@ -299,6 +299,11 @@ interface WithdrawalDetailsForTalerBankIntegrationApi { */ reserveIsReady: boolean; + /** + * Is the bank transfer for the withdrawal externally confirmed? + */ + externalConfirmation?: boolean; + exchangeCreditAccountDetails?: WithdrawalExchangeAccountDetails[]; } diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 3438cbdc7..d28566910 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -400,6 +400,8 @@ export interface ReserveBankInfo { wireTypes: string[] | undefined; currency: string | undefined; + + externalConfirmation?: boolean; } /** @@ -907,8 +909,8 @@ export interface CoinRecord { /** * History item for a coin. - * - * DB-specific format, + * + * DB-specific format, */ export type DbWalletCoinHistoryItem = | { diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts index 8268828be..0649f9ce2 100644 --- a/packages/taler-wallet-core/src/transactions.ts +++ b/packages/taler-wallet-core/src/transactions.ts @@ -768,7 +768,10 @@ function buildTransactionForBankIntegratedWithdraw( confirmed: wg.wgInfo.bankInfo.timestampBankConfirmed ? true : false, exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts, reservePub: wg.reservePub, - bankConfirmationUrl: wg.wgInfo.bankInfo.confirmUrl, + bankConfirmationUrl: wg.wgInfo.bankInfo.externalConfirmation + ? undefined + : wg.wgInfo.bankInfo.confirmUrl, + externalConfirmation: wg.wgInfo.bankInfo.externalConfirmation, reserveIsReady: wg.status === WithdrawalGroupStatus.Done || wg.status === WithdrawalGroupStatus.PendingReady, diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 087db7938..083fa2a2d 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -64,6 +64,7 @@ import { TalerErrorCode, TalerErrorDetail, TalerPreciseTimestamp, + TalerUriAction, Transaction, TransactionAction, TransactionIdStr, @@ -95,6 +96,7 @@ import { getRandomBytes, j2s, makeErrorDetail, + parseTalerUri, parseWithdrawUri, } from "@gnu-taler/taler-util"; import { @@ -3064,6 +3066,17 @@ export async function prepareBankIntegratedWithdrawal( }, ); + const parsedUri = parseTalerUri(req.talerWithdrawUri); + if (parsedUri?.type !== TalerUriAction.Withdraw) { + throw TalerError.fromDetail(TalerErrorCode.WALLET_TALER_URI_MALFORMED, {}); + } + + const externalConfirmation = parsedUri.externalConfirmation; + + logger.info( + `creating withdrawal with externalConfirmation=${externalConfirmation}`, + ); + const withdrawInfo = await getBankWithdrawalInfo( wex.http, req.talerWithdrawUri, @@ -3099,6 +3112,7 @@ export async function prepareBankIntegratedWithdrawal( timestampReserveInfoPosted: undefined, wireTypes: withdrawInfo.wireTypes, currency: withdrawInfo.currency, + externalConfirmation, }, }, reserveStatus: WithdrawalGroupStatus.DialogProposed, @@ -3220,22 +3234,14 @@ export async function confirmWithdrawal( rec.denomsSel = initalDenoms; rec.rawWithdrawalAmount = initalDenoms.totalWithdrawCost; rec.effectiveWithdrawalAmount = initalDenoms.totalCoinValue; - - rec.wgInfo = { - withdrawalType: WithdrawalRecordType.BankIntegrated, - exchangeCreditAccounts: withdrawalAccountList, - bankInfo: { - exchangePaytoUri, - talerWithdrawUri, - confirmUrl: confirmUrl, - timestampBankConfirmed: undefined, - timestampReserveInfoPosted: undefined, - wireTypes: bankWireTypes, - currency: bankCurrency, - }, - }; - pending = true; + checkDbInvariant( + rec.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated, + "withdrawal type mismatch", + ); + rec.wgInfo.exchangeCreditAccounts = withdrawalAccountList; + rec.wgInfo.bankInfo.exchangePaytoUri = exchangePaytoUri; rec.status = WithdrawalGroupStatus.PendingRegisteringBank; + pending = true; return TransitionResult.transition(rec); } default: { |