taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 59c9ff665fe605baa8d7ca7ead09940e43c9bf2c
parent 7983507bfe19dc1a64050a6d24fe85c1f47fa5bc
Author: Florian Dold <florian@dold.me>
Date:   Wed, 19 Feb 2025 21:30:16 +0100

wallet-core: store bank account based on reserve response

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-withdrawal-manual.ts | 23++++++++++++++++++++++-
Mpackages/taler-util/src/ReserveStatus.ts | 17++++++++++++++++-
Mpackages/taler-wallet-core/src/withdraw.ts | 68+++++++++++++++++++++++++++++++++++++++++++-------------------------
3 files changed, 81 insertions(+), 27 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts @@ -40,6 +40,16 @@ export async function runWithdrawalManualTest(t: GlobalTestState) { const { walletClient, bankClient, exchange, exchangeBankAccount } = await createSimpleTestkudosEnvironmentV3(t); + { + const accounts = await walletClient.call( + WalletApiOperation.ListBankAccounts, + {}, + ); + + // Wallet doesn't have known bank accounts initially + t.assertTrue(accounts.accounts.length == 0); + } + // Create a withdrawal operation const user = await bankClient.createRandomBankUser(); @@ -116,7 +126,18 @@ export async function runWithdrawalManualTest(t: GlobalTestState) { const balResp = await walletClient.call(WalletApiOperation.GetBalances, {}); t.assertAmountEquals("TESTKUDOS:9.72", balResp.balances[0].available); - await t.shutdown(); + { + const accounts = await walletClient.call( + WalletApiOperation.ListBankAccounts, + {}, + ); + + console.log(j2s(accounts)); + + // Account from withdrawal is now known to the wallet, + // as the exchange returns it in the reserve status response + t.assertTrue(accounts.accounts.length == 1); + } } runWithdrawalManualTest.suites = ["wallet"]; diff --git a/packages/taler-util/src/ReserveStatus.ts b/packages/taler-util/src/ReserveStatus.ts @@ -22,7 +22,12 @@ * Imports. */ import { codecForAmountString } from "./amounts.js"; -import { Codec, buildCodecForObject } from "./codec.js"; +import { + Codec, + buildCodecForObject, + codecForString, + codecOptional, +} from "./codec.js"; import { AmountString } from "./types-taler-common.js"; /** @@ -35,9 +40,19 @@ export interface ReserveStatus { * Balance left in the reserve. */ balance: AmountString; + + /** + * Full payto URI of the bank account that + * (most recently) funded this reserve. + * Useful as a hint for deposit operations for wallets. + * Missing if this reserve was only filled via P2P merges. + * @since protocol **v23**. + */ + last_origin?: string; } export const codecForReserveStatus = (): Codec<ReserveStatus> => buildCodecForObject<ReserveStatus>() .property("balance", codecForAmountString()) + .property("last_origin", codecOptional(codecForString())) .build("ReserveStatus"); diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -1962,7 +1962,7 @@ async function processQueryReserve( const transitionResult = await ctx.transition( { - extraStores: ["denominations"], + extraStores: ["denominations", "bankAccountsV2"], }, async (wg, tx) => { if (!wg) { @@ -1972,6 +1972,10 @@ async function processQueryReserve( if (wg.status !== WithdrawalGroupStatus.PendingQueryingStatus) { return TransitionResult.stay(); } + const lastOrigin = result.response.last_origin; + if (lastOrigin != null) { + await storeKnownBankAccount(tx, currency, lastOrigin); + } if (amountChanged) { const candidates = await getCandidateWithdrawalDenomsTx( wex, @@ -3496,6 +3500,43 @@ export async function prepareBankIntegratedWithdrawal( }; } +/** + * Store account as known bank account. + * + * Return ID of the new bank account or undefined + * if the account already exists. + */ +async function storeKnownBankAccount( + tx: WalletDbReadWriteTransaction<["bankAccountsV2"]>, + instructedCurrency: string, + senderWire: string, +): Promise<string | undefined> { + const existingAccount = + await tx.bankAccountsV2.indexes.byPaytoUri.get(senderWire); + if (existingAccount) { + // Add currency for existing known bank account if necessary + if (existingAccount.currencies?.includes(instructedCurrency)) { + existingAccount.currencies = [ + instructedCurrency, + ...(existingAccount.currencies ?? []), + ]; + existingAccount.currencies.sort(); + await tx.bankAccountsV2.put(existingAccount); + } + return undefined; + } + + const myId = `acct:${encodeCrock(getRandomBytes(32))}`; + await tx.bankAccountsV2.put({ + currencies: [instructedCurrency], + kycCompleted: false, + paytoUri: senderWire, + bankAccountId: myId, + label: undefined, + }); + return myId; +} + export async function confirmWithdrawal( wex: WalletExecutionContext, req: ConfirmWithdrawalRequest, @@ -3628,30 +3669,7 @@ export async function confirmWithdrawal( storeNames: ["bankAccountsV2"], }, async (tx) => { - const existingAccount = - await tx.bankAccountsV2.indexes.byPaytoUri.get(senderWire); - if (existingAccount) { - // Add currency for existing known bank account if necessary - if (existingAccount.currencies?.includes(instructedCurrency)) { - existingAccount.currencies = [ - instructedCurrency, - ...(existingAccount.currencies ?? []), - ]; - existingAccount.currencies.sort(); - await tx.bankAccountsV2.put(existingAccount); - } - return; - } - - const myId = `acct:${encodeCrock(getRandomBytes(32))}`; - await tx.bankAccountsV2.put({ - currencies: [instructedCurrency], - kycCompleted: false, - paytoUri: senderWire, - bankAccountId: myId, - label: undefined, - }); - return myId; + return await storeKnownBankAccount(tx, instructedCurrency, senderWire); }, );