taler-typescript-core

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

commit 28b50a4707a78991901361fdfd7f905034e25a4d
parent 8b22ae29a035bbabab132ba3e0f7078e943525e4
Author: Iván Ávalos <avalos@disroot.org>
Date:   Mon, 31 Mar 2025 21:18:43 +0200

WIP: token re-selection on 403 from merchant

Diffstat:
Mpackages/taler-wallet-core/src/pay-merchant.ts | 141+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
1 file changed, 92 insertions(+), 49 deletions(-)

diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -1653,6 +1653,7 @@ async function handleInsufficientFunds( } const prevPayCoins: PreviousPayCoins = []; + const prevTokensPubs: string[] = []; const payInfo = proposal.payInfo; if (!payInfo) { @@ -1660,9 +1661,7 @@ async function handleInsufficientFunds( } const payCoinSelection = payInfo.payCoinSelection; - if (!payCoinSelection) { - return; - } + const payTokenSelection = payInfo.payTokenSelection; // FIXME: Above code should go into the transaction. // TODO: also do token re-selection. @@ -1704,60 +1703,104 @@ async function handleInsufficientFunds( assertUnreachable(contractData); } - for (let i = 0; i < payCoinSelection.coinPubs.length; i++) { - const coinPub = payCoinSelection.coinPubs[i]; - const contrib = payCoinSelection.coinContributions[i]; - prevPayCoins.push({ - coinPub, - contribution: Amounts.parseOrThrow(contrib), + if (payCoinSelection) { + for (let i = 0; i < payCoinSelection.coinPubs.length; i++) { + const coinPub = payCoinSelection.coinPubs[i]; + const contrib = payCoinSelection.coinContributions[i]; + prevPayCoins.push({ + coinPub, + contribution: Amounts.parseOrThrow(contrib), + }); + } + + const res = await selectPayCoinsInTx(wex, tx, { + restrictExchanges: { + auditors: [], + exchanges: contractData.exchanges.map(ex => ({ + exchangeBaseUrl: ex.url, + exchangePub: ex.master_pub, + })), + }, + restrictWireMethod: contractData.wire_method, + contractTermsAmount: Amounts.parseOrThrow(amount), + depositFeeLimit: Amounts.parseOrThrow(maxFee), + prevPayCoins, + requiredMinimumAge: contractData.minimum_age, }); + + switch (res.type) { + case "failure": + logger.trace("insufficient funds for coin re-selection"); + return; + case "prospective": + return; + case "success": + break; + default: + assertUnreachable(res); + } + + // Convert to DB format + payInfo.payCoinSelection = { + coinContributions: res.coinSel.coins.map((x) => x.contribution), + coinPubs: res.coinSel.coins.map((x) => x.coinPub), + }; + payInfo.payCoinSelectionUid = encodeCrock(getRandomBytes(32)); + p.exchanges = [...new Set(res.coinSel.coins.map((x) => x.exchangeBaseUrl))]; + p.exchanges.sort(); } - const res = await selectPayCoinsInTx(wex, tx, { - restrictExchanges: { - auditors: [], - exchanges: contractData.exchanges.map(ex => ({ - exchangeBaseUrl: ex.url, - exchangePub: ex.master_pub, - })), - }, - restrictWireMethod: contractData.wire_method, - contractTermsAmount: Amounts.parseOrThrow(amount), - depositFeeLimit: Amounts.parseOrThrow(maxFee), - prevPayCoins, - requiredMinimumAge: contractData.minimum_age, - }); + if (payTokenSelection) { + prevTokensPubs.push(...payTokenSelection.tokenPubs); - switch (res.type) { - case "failure": - logger.trace("insufficient funds for coin re-selection"); - return; - case "prospective": - return; - case "success": - break; - default: - assertUnreachable(res); + if (p.choiceIndex === undefined) + throw Error("assertion failed"); + + if (contractData.version !== MerchantContractVersion.V1) + throw Error("assertion failed"); + + const res = await selectPayTokensInTx(tx, { + proposalId: p.proposalId, + choiceIndex: p.choiceIndex, + contractTerms: contractData, + }); + + switch (res.type) { + case "failure": + logger.trace("insufficient tokens for token re-selection"); + return; + break; + case "success": + break; + default: + assertUnreachable(res); + } + + payInfo.payTokenSelection = { + tokenPubs: res.tokens.map(t => t.tokenUsePub), + }; } - // Convert to DB format - payInfo.payCoinSelection = { - coinContributions: res.coinSel.coins.map((x) => x.contribution), - coinPubs: res.coinSel.coins.map((x) => x.coinPub), - }; - payInfo.payCoinSelectionUid = encodeCrock(getRandomBytes(32)); - p.exchanges = [...new Set(res.coinSel.coins.map((x) => x.exchangeBaseUrl))]; - p.exchanges.sort(); await tx.purchases.put(p); await ctx.updateTransactionMeta(tx); - await spendCoins(wex, tx, { - transactionId: ctx.transactionId, - coinPubs: payInfo.payCoinSelection.coinPubs, - contributions: payInfo.payCoinSelection.coinContributions.map((x) => - Amounts.parseOrThrow(x), - ), - refreshReason: RefreshReason.PayMerchant, - }); + + if (payInfo.payCoinSelection) { + await spendCoins(wex, tx, { + transactionId: ctx.transactionId, + coinPubs: payInfo.payCoinSelection.coinPubs, + contributions: payInfo.payCoinSelection.coinContributions.map((x) => + Amounts.parseOrThrow(x), + ), + refreshReason: RefreshReason.PayMerchant, + }); + } + + if (payInfo.payTokenSelection) { + await spendTokens(tx, { + transactionId: ctx.transactionId, + tokenPubs: payInfo.payTokenSelection.tokenPubs, + }); + } }); wex.ws.notify({