commit 762e764bc58f789642af156179c2c6be8bfcd9a4
parent 702b552cb63f09053c8353d71bde69436437096d
Author: Florian Dold <florian@dold.me>
Date: Thu, 12 Mar 2026 14:02:59 +0100
wallet-core: improve handling of refresh in pay aborts
Diffstat:
2 files changed, 46 insertions(+), 24 deletions(-)
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
@@ -1555,6 +1555,12 @@ export interface PurchaseRecord {
purchaseStatus: PurchaseStatus;
+ /**
+ * Refresh group ID of the refresh transaction that
+ * has been created to abort the payment.
+ */
+ abortRefreshGroupId?: string;
+
abortReason?: TalerErrorDetail;
failReason?: TalerErrorDetail;
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -476,25 +476,6 @@ export class PayMerchantTransactionContext implements TransactionContext {
case PurchaseStatus.SuspendedPaying: {
purchase.abortReason = reason;
purchase.purchaseStatus = PurchaseStatus.AbortingWithRefund;
- if (purchase.payInfo && purchase.payInfo.payCoinSelection) {
- const coinSel = purchase.payInfo.payCoinSelection;
- const currency = Amounts.currencyOf(purchase.payInfo.totalPayCost);
- const refreshCoins: CoinRefreshRequest[] = [];
- for (let i = 0; i < coinSel.coinPubs.length; i++) {
- refreshCoins.push({
- amount: coinSel.coinContributions[i],
- coinPub: coinSel.coinPubs[i],
- });
- }
- await createRefreshGroup(
- wex,
- tx,
- currency,
- refreshCoins,
- RefreshReason.AbortPay,
- this.transactionId,
- );
- }
break;
}
case PurchaseStatus.PendingQueryingAutoRefund:
@@ -4422,9 +4403,9 @@ async function processPurchaseAbortingRefund(
purchase: PurchaseRecord,
): Promise<TaskRunResult> {
const proposalId = purchase.proposalId;
+ const ctx = new PayMerchantTransactionContext(wex, proposalId);
const download = await expectProposalDownload(wex, purchase);
logger.trace(`processing aborting-refund for proposal ${proposalId}`);
-
const requestUrl = new URL(
`orders/${download.contractTerms.order_id}/abort`,
download.contractTerms.merchant_base_url,
@@ -4434,10 +4415,47 @@ async function processPurchaseAbortingRefund(
const payCoinSelection = purchase.payInfo?.payCoinSelection;
if (!payCoinSelection) {
- throw Error("can't abort, no coins selected");
+ await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+ const [rec, h] = await ctx.getRecordHandle(tx);
+ if (rec?.purchaseStatus !== PurchaseStatus.AbortingWithRefund) {
+ return;
+ }
+ if (purchase.payInfo?.payCoinSelection != null) {
+ return;
+ }
+ rec.purchaseStatus = PurchaseStatus.AbortedOrderDeleted;
+ await h.update(rec);
+ });
+ return TaskRunResult.finished();
}
- await wex.db.runReadOnlyTx({ storeNames: ["coins"] }, async (tx) => {
+ await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+ const [rec, h] = await ctx.getRecordHandle(tx);
+ if (!rec) {
+ return;
+ }
+ if (rec.payInfo?.payCoinSelection && rec.abortRefreshGroupId == null) {
+ const coinSel = rec.payInfo.payCoinSelection;
+ const currency = Amounts.currencyOf(rec.payInfo.totalPayCost);
+ const refreshCoins: CoinRefreshRequest[] = [];
+ for (let i = 0; i < coinSel.coinPubs.length; i++) {
+ refreshCoins.push({
+ amount: coinSel.coinContributions[i],
+ coinPub: coinSel.coinPubs[i],
+ });
+ }
+ const res = await createRefreshGroup(
+ wex,
+ tx,
+ currency,
+ refreshCoins,
+ RefreshReason.AbortPay,
+ ctx.transactionId,
+ );
+ rec.abortRefreshGroupId = res.refreshGroupId;
+ await h.update(rec);
+ }
+
for (let i = 0; i < payCoinSelection.coinPubs.length; i++) {
const coinPub = payCoinSelection.coinPubs[i];
const coin = await tx.coins.get(coinPub);
@@ -4469,7 +4487,6 @@ async function processPurchaseAbortingRefund(
err.code ===
TalerErrorCode.MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND
) {
- const ctx = new PayMerchantTransactionContext(wex, proposalId);
await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
const [rec, h] = await ctx.getRecordHandle(tx);
if (rec?.purchaseStatus !== PurchaseStatus.AbortingWithRefund) {
@@ -4779,7 +4796,6 @@ async function storeRefunds(
rf.coin_pub,
rf.rtransaction_id,
]);
- let oldTxState: TransactionState | undefined = undefined;
if (oldItem) {
logger.info("already have refund in database");
if (oldItem.status === RefundItemStatus.Done) {