taler-typescript-core

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

commit 94b1297b966b2b45b8f7a5f15d42f8ef046f8813
parent 50c7c31d3ec59a73c43f86277ba0d5c6a7fff984
Author: Florian Dold <dold@taler.net>
Date:   Thu, 20 Aug 2026 19:06:46 +0200

wallet-core: maintain refund totals

Diffstat:
Mpackages/taler-wallet-core/src/pay-merchant.test.ts | 35++++++++++++++++++++++++++++++++++-
Mpackages/taler-wallet-core/src/pay-merchant.ts | 51++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 80 insertions(+), 6 deletions(-)

diff --git a/packages/taler-wallet-core/src/pay-merchant.test.ts b/packages/taler-wallet-core/src/pay-merchant.test.ts @@ -22,11 +22,17 @@ import { } from "@gnu-taler/taler-util"; import assert from "node:assert"; import { test } from "node:test"; -import { WalletToken } from "./db-common.js"; +import { + RefundGroupStatus, + WalletRefundGroup, + WalletToken, +} from "./db-common.js"; import { WalletDbTransaction } from "./dbtx.js"; import { getCoinsToSpendForMerchantRepair, + getRefundTotals, releasePaymentTokensInTx, + setRefundGroupEffectiveAmount, splitPaymentOutputTokenSignatures, } from "./pay-merchant.js"; @@ -139,3 +145,30 @@ test("cached output signatures recover an older partial finalization", () => { }, ); }); + +function refundGroup( + id: string, + raw: AmountString, + effective: AmountString, +): WalletRefundGroup { + return { + refundGroupId: id, + proposalId: "proposal", + status: RefundGroupStatus.Done, + amountRaw: raw, + amountEffective: effective, + } as WalletRefundGroup; +} + +test("refund totals and effective amounts reflect completed items", () => { + const first = refundGroup("first", "TESTKUDOS:3", "TESTKUDOS:0"); + setRefundGroupEffectiveAmount(first, [ + Amounts.parseOrThrow("TESTKUDOS:2.5"), + ]); + const second = refundGroup("second", "TESTKUDOS:1", "TESTKUDOS:0.5"); + + assert.deepStrictEqual(getRefundTotals([first, second], "TESTKUDOS"), { + raw: "TESTKUDOS:4", + effective: "TESTKUDOS:3", + }); +}); diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -329,6 +329,7 @@ export class PayMerchantTransactionContext implements TransactionContext { } let zero = Amounts.zeroOfAmount(amountRaw); + const refundTotals = getRefundTotals(refundsInfo, zero.currency); const info: OrderShortInfo = { merchant: { @@ -387,8 +388,8 @@ export class PayMerchantTransactionContext implements TransactionContext { txActions: computePayMerchantTransactionActions(purchaseRec), amountRaw, amountEffective, - totalRefundRaw: Amounts.stringify(zero), // FIXME! - totalRefundEffective: Amounts.stringify(zero), // FIXME! + totalRefundRaw: refundTotals.raw, + totalRefundEffective: refundTotals.effective, refundPending: purchaseRec.refundAmountAwaiting === undefined ? undefined @@ -4830,6 +4831,38 @@ function getItemStatus(rf: MerchantCoinRefundStatus): RefundItemStatus { } } +export function getRefundTotals( + refundGroups: WalletRefundGroup[], + currency: string, +): { raw: AmountString; effective: AmountString } { + return { + raw: Amounts.stringify( + Amounts.sumOrZero( + currency, + refundGroups.map((x) => x.amountRaw), + ).amount, + ), + effective: Amounts.stringify( + Amounts.sumOrZero( + currency, + refundGroups.map((x) => x.amountEffective), + ).amount, + ), + }; +} + +export function setRefundGroupEffectiveAmount( + refundGroup: WalletRefundGroup, + outputAmounts: AmountJson[], +): void { + refundGroup.amountEffective = Amounts.stringify( + Amounts.sumOrZero( + Amounts.currencyOf(refundGroup.amountRaw), + outputAmounts, + ).amount, + ); +} + /** * Store refunds, possibly creating a new refund group. */ @@ -4949,9 +4982,7 @@ async function storeRefunds( currency, refreshCoins, ); - newGroup.amountEffective = Amounts.stringify( - Amounts.sumOrZero(currency, outInfo.outputPerCoin).amount, - ); + setRefundGroupEffectiveAmount(newGroup, outInfo.outputPerCoin); newGroup.amountRaw = Amounts.stringify( Amounts.sumOrZero(currency, amountsRaw).amount, ); @@ -4992,6 +5023,14 @@ async function storeRefunds( assertUnreachable(refundGroup.status); } const items = await tx.getRefundItemsByGroup(refundGroup.refundGroupId); + const refreshCoins = await computeRefreshRequest(wex, tx, items); + const outInfo = await calculateRefreshOutput( + wex, + tx, + currency, + refreshCoins, + ); + setRefundGroupEffectiveAmount(refundGroup, outInfo.outputPerCoin); let numPending = 0; let numFailed = 0; for (const item of items) { @@ -5039,6 +5078,8 @@ async function storeRefunds( proposalId: myPurchase.proposalId, }), ); + } else { + await tx.upsertRefundGroup(refundGroup); } } const shouldCheckAutoRefund =