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:
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 =