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:
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({