aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-06-28 11:48:54 +0200
committerFlorian Dold <florian@dold.me>2024-06-28 11:48:54 +0200
commitad98210b79f350de631e593ef520f5a57a44812e (patch)
tree48bafb0531c709b45dde86213c635fbf14426473
parent64cb9fcf0da36d5ced33bedc382faa9a64e84719 (diff)
downloadwallet-core-ad98210b79f350de631e593ef520f5a57a44812e.tar.xz
wallet-core: support externally confirmed bank-integrated withdrawals
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-external.ts101
-rw-r--r--packages/taler-harness/src/integrationtests/testrunner.ts4
-rw-r--r--packages/taler-util/src/taleruri.test.ts12
-rw-r--r--packages/taler-util/src/taleruri.ts9
-rw-r--r--packages/taler-util/src/transactions-types.ts5
-rw-r--r--packages/taler-wallet-core/src/db.ts6
-rw-r--r--packages/taler-wallet-core/src/transactions.ts5
-rw-r--r--packages/taler-wallet-core/src/withdraw.ts36
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: {