gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: working on #7357


From: gnunet
Subject: [taler-wallet-core] branch master updated: working on #7357
Date: Fri, 16 Sep 2022 16:13:18 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 5d0837913 working on #7357
5d0837913 is described below

commit 5d0837913901a2947c66209d64855b324824757d
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Sep 16 11:06:55 2022 -0300

    working on #7357
    
    getTransactionById is introduced:
     with that we move all transaction information building into a function
    transactionId was added in every response that creates a tx
---
 packages/taler-util/src/time.ts                    |  10 +
 packages/taler-util/src/transactionsTypes.ts       |   9 +
 packages/taler-util/src/walletTypes.ts             |  26 +-
 .../taler-wallet-core/src/operations/deposits.ts   |   7 +-
 packages/taler-wallet-core/src/operations/pay.ts   |   5 +-
 .../src/operations/peer-to-peer.ts                 |  51 +-
 .../taler-wallet-core/src/operations/refund.ts     |   3 +
 packages/taler-wallet-core/src/operations/tip.ts   |  11 +-
 .../src/operations/transactions.ts                 | 804 ++++++++++++++-------
 .../taler-wallet-core/src/operations/withdraw.ts   |  41 +-
 packages/taler-wallet-core/src/wallet.ts           |   9 +-
 .../src/components/BankDetailsByPaytoType.tsx      |  26 +-
 .../src/cta/Payment/stories.tsx                    |   2 +
 .../src/wallet/DeveloperPage.stories.tsx           |   1 +
 .../src/wallet/Transaction.tsx                     |   9 +-
 packages/taler-wallet-webextension/src/wxApi.ts    |  16 +-
 16 files changed, 703 insertions(+), 327 deletions(-)

diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts
index 1e79b943b..c3f1b30bd 100644
--- a/packages/taler-util/src/time.ts
+++ b/packages/taler-util/src/time.ts
@@ -56,6 +56,16 @@ export namespace TalerProtocolTimestamp {
       t_s: s,
     };
   }
+  export function min(t1: TalerProtocolTimestamp, t2: TalerProtocolTimestamp): 
TalerProtocolTimestamp {
+    if (t1.t_s === "never") {
+      return { t_s: t2.t_s };
+    }
+    if (t2.t_s === "never") {
+      return { t_s: t2.t_s };
+    }
+    return { t_s: Math.min(t1.t_s, t2.t_s) };
+  }
+
 }
 
 export interface Duration {
diff --git a/packages/taler-util/src/transactionsTypes.ts 
b/packages/taler-util/src/transactionsTypes.ts
index a46f304d1..e5b0695f8 100644
--- a/packages/taler-util/src/transactionsTypes.ts
+++ b/packages/taler-util/src/transactionsTypes.ts
@@ -505,6 +505,15 @@ export interface TransactionDeposit extends 
TransactionCommon {
   amountEffective: AmountString;
 }
 
+export interface TransactionByIdRequest {
+  transactionId: string;
+}
+
+export const codecForTransactionByIdRequest = (): 
Codec<TransactionByIdRequest> =>
+  buildCodecForObject<TransactionByIdRequest>()
+    .property("transactionId", codecForString())
+    .build("TransactionByIdRequest");
+
 export const codecForTransactionsRequest = (): Codec<TransactionsRequest> =>
   buildCodecForObject<TransactionsRequest>()
     .property("currency", codecOptional(codecForString()))
diff --git a/packages/taler-util/src/walletTypes.ts 
b/packages/taler-util/src/walletTypes.ts
index 701049c26..7fcb752b1 100644
--- a/packages/taler-util/src/walletTypes.ts
+++ b/packages/taler-util/src/walletTypes.ts
@@ -138,11 +138,12 @@ export enum ConfirmPayResultType {
 export interface ConfirmPayResultDone {
   type: ConfirmPayResultType.Done;
   contractTerms: ContractTerms;
+  transactionId: string;
 }
 
 export interface ConfirmPayResultPending {
   type: ConfirmPayResultType.Pending;
-
+  transactionId: string;
   lastError: TalerErrorDetail | undefined;
 }
 
@@ -152,12 +153,14 @@ export const codecForConfirmPayResultPending =
   (): Codec<ConfirmPayResultPending> =>
     buildCodecForObject<ConfirmPayResultPending>()
       .property("lastError", codecForAny())
+      .property("transactionId", codecForString())
       .property("type", codecForConstString(ConfirmPayResultType.Pending))
       .build("ConfirmPayResultPending");
 
 export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> =>
   buildCodecForObject<ConfirmPayResultDone>()
     .property("type", codecForConstString(ConfirmPayResultType.Done))
+    .property("transactionId", codecForString())
     .property("contractTerms", codecForContractTerms())
     .build("ConfirmPayResultDone");
 
@@ -334,6 +337,10 @@ export interface PrepareTipResult {
   expirationTimestamp: TalerProtocolTimestamp;
 }
 
+export interface AcceptTipResponse {
+  transactionId: string;
+}
+
 export const codecForPrepareTipResult = (): Codec<PrepareTipResult> =>
   buildCodecForObject<PrepareTipResult>()
     .property("accepted", codecForBoolean())
@@ -462,6 +469,7 @@ export interface BankWithdrawDetails {
 export interface AcceptWithdrawalResponse {
   reservePub: string;
   confirmTransferUrl?: string;
+  transactionId: string;
 }
 
 /**
@@ -864,6 +872,8 @@ export interface AcceptManualWithdrawalResult {
    * Public key of the newly created reserve.
    */
   reservePub: string;
+
+  transactionId: string;
 }
 
 export interface ManualWithdrawalDetails {
@@ -1252,6 +1262,8 @@ export const codecForWithdrawTestBalance =
 export interface ApplyRefundResponse {
   contractTermsHash: string;
 
+  transactionId: string;
+
   proposalId: string;
 
   amountEffectivePaid: AmountString;
@@ -1273,6 +1285,7 @@ export const codecForApplyRefundResponse = (): 
Codec<ApplyRefundResponse> =>
     .property("contractTermsHash", codecForString())
     .property("pendingAtExchange", codecForBoolean())
     .property("proposalId", codecForString())
+    .property("transactionId", codecForString())
     .property("info", codecForOrderShortInfo())
     .build("ApplyRefundResponse");
 
@@ -1374,6 +1387,7 @@ export const codecForCreateDepositGroupRequest =
 
 export interface CreateDepositGroupResponse {
   depositGroupId: string;
+  transactionId: string;
 }
 
 export interface TrackDepositGroupRequest {
@@ -1539,6 +1553,7 @@ export interface InitiatePeerPushPaymentResponse {
   mergePriv: string;
   contractPriv: string;
   talerUri: string;
+  transactionId: string;
 }
 
 export const codecForInitiatePeerPushPaymentRequest =
@@ -1586,6 +1601,13 @@ export interface AcceptPeerPushPaymentRequest {
    */
   peerPushPaymentIncomingId: string;
 }
+export interface AcceptPeerPushPaymentResponse {
+  transactionId: string;
+}
+
+export interface AcceptPeerPullPaymentResponse {
+  transactionId: string;
+}
 
 export const codecForAcceptPeerPushPaymentRequest =
   (): Codec<AcceptPeerPushPaymentRequest> =>
@@ -1629,4 +1651,6 @@ export interface InitiatePeerPullPaymentResponse {
    * that was requested.
    */
   talerUri: string;
+
+  transactionId: string;
 }
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index 612de8240..e6f1591ee 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -41,6 +41,7 @@ import {
   TalerProtocolTimestamp,
   TrackDepositGroupRequest,
   TrackDepositGroupResponse,
+  TransactionType,
   URL,
 } from "@gnu-taler/taler-util";
 import {
@@ -62,6 +63,7 @@ import {
   getTotalPaymentCost,
 } from "./pay.js";
 import { getTotalRefreshCost } from "./refresh.js";
+import { makeEventId } from "./transactions.js";
 
 /**
  * Logger.
@@ -531,7 +533,10 @@ export async function createDepositGroup(
       await tx.depositGroups.put(depositGroup);
     });
 
-  return { depositGroupId };
+  return {
+    depositGroupId: depositGroupId,
+    transactionId: makeEventId(TransactionType.Deposit, depositGroupId)
+  };
 }
 
 /**
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index 5a0d3cee3..5e3c3dd15 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -103,6 +103,7 @@ import { RetryInfo, RetryTags, scheduleRetry } from 
"../util/retries.js";
 import { spendCoins } from "../wallet.js";
 import { getExchangeDetails } from "./exchanges.js";
 import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js";
+import { makeEventId } from "./transactions.js";
 
 /**
  * Logger.
@@ -511,7 +512,7 @@ export function extractContractData(
 export async function processDownloadProposal(
   ws: InternalWalletState,
   proposalId: string,
-  options: {} = {},
+  options: object = {},
 ): Promise<OperationAttemptResult> {
   const proposal = await ws.db
     .mktx((x) => [x.proposals])
@@ -1312,6 +1313,7 @@ export async function runPayForConfirmPay(
       return {
         type: ConfirmPayResultType.Done,
         contractTerms: purchase.download.contractTermsRaw,
+        transactionId: makeEventId(TransactionType.Payment, proposalId)
       };
     }
     case OperationAttemptResultType.Error:
@@ -1320,6 +1322,7 @@ export async function runPayForConfirmPay(
     case OperationAttemptResultType.Pending:
       return {
         type: ConfirmPayResultType.Pending,
+        transactionId: makeEventId(TransactionType.Payment, proposalId),
         lastError: undefined,
       };
     case OperationAttemptResultType.Longpoll:
diff --git a/packages/taler-wallet-core/src/operations/peer-to-peer.ts 
b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
index 449a91c68..e71e8a709 100644
--- a/packages/taler-wallet-core/src/operations/peer-to-peer.ts
+++ b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
@@ -20,11 +20,11 @@
 import {
   AbsoluteTime,
   AcceptPeerPullPaymentRequest,
+  AcceptPeerPullPaymentResponse,
   AcceptPeerPushPaymentRequest,
+  AcceptPeerPushPaymentResponse,
   AgeCommitmentProof,
-  AmountJson,
-  AmountLike,
-  Amounts,
+  AmountJson, Amounts,
   AmountString,
   buildCodecForObject,
   CheckPeerPullPaymentRequest,
@@ -34,9 +34,7 @@ import {
   Codec,
   codecForAmountString,
   codecForAny,
-  codecForExchangeGetContractResponse,
-  CoinPublicKey,
-  constructPayPullUri,
+  codecForExchangeGetContractResponse, constructPayPullUri,
   constructPayPushUri,
   ContractTermsUtil,
   decodeCrock,
@@ -58,25 +56,25 @@ import {
   RefreshReason,
   strcmp,
   TalerProtocolTimestamp,
+  TransactionType,
   UnblindedSignature,
-  WalletAccountMergeFlags,
+  WalletAccountMergeFlags
 } from "@gnu-taler/taler-util";
 import {
   CoinStatus,
   MergeReserveInfo,
   ReserveRecordStatus,
   WalletStoresV1,
-  WithdrawalRecordType,
+  WithdrawalRecordType
 } from "../db.js";
-import { readSuccessResponseJsonOrThrow } from "../util/http.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
+import { readSuccessResponseJsonOrThrow } from "../util/http.js";
 import { checkDbInvariant } from "../util/invariants.js";
-import { internalCreateWithdrawalGroup } from "./withdraw.js";
 import { GetReadOnlyAccess } from "../util/query.js";
-import { createRefreshGroup } from "./refresh.js";
-import { updateExchangeFromUrl } from "./exchanges.js";
 import { spendCoins } from "../wallet.js";
-import { RetryTags } from "../util/retries.js";
+import { updateExchangeFromUrl } from "./exchanges.js";
+import { makeEventId } from "./transactions.js";
+import { internalCreateWithdrawalGroup } from "./withdraw.js";
 
 const logger = new Logger("operations/peer-to-peer.ts");
 
@@ -338,6 +336,7 @@ export async function initiatePeerToPeerPush(
       exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
       contractPriv: econtractResp.contractPriv,
     }),
+    transactionId: makeEventId(TransactionType.PeerPushDebit, pursePair.pub)
   };
 }
 
@@ -472,7 +471,7 @@ async function getMergeReserveInfo(
 export async function acceptPeerPushPayment(
   ws: InternalWalletState,
   req: AcceptPeerPushPaymentRequest,
-): Promise<void> {
+): Promise<AcceptPeerPushPaymentResponse> {
   const peerInc = await ws.db
     .mktx((x) => [x.peerPushPaymentIncoming])
     .runReadOnly(async (tx) => {
@@ -533,7 +532,7 @@ export async function acceptPeerPushPayment(
   const res = await readSuccessResponseJsonOrThrow(mergeHttpReq, 
codecForAny());
   logger.info(`merge response: ${j2s(res)}`);
 
-  await internalCreateWithdrawalGroup(ws, {
+  const wg = await internalCreateWithdrawalGroup(ws, {
     amount,
     wgInfo: {
       withdrawalType: WithdrawalRecordType.PeerPushCredit,
@@ -546,6 +545,13 @@ export async function acceptPeerPushPayment(
       pub: mergeReserveInfo.reservePub,
     },
   });
+
+  return {
+    transactionId: makeEventId(
+      TransactionType.PeerPushCredit,
+      wg.withdrawalGroupId
+    )
+  }
 }
 
 /**
@@ -554,7 +560,7 @@ export async function acceptPeerPushPayment(
 export async function acceptPeerPullPayment(
   ws: InternalWalletState,
   req: AcceptPeerPullPaymentRequest,
-): Promise<void> {
+): Promise<AcceptPeerPullPaymentResponse> {
   const peerPullInc = await ws.db
     .mktx((x) => [x.peerPullPaymentIncoming])
     .runReadOnly(async (tx) => {
@@ -630,6 +636,13 @@ export async function acceptPeerPullPayment(
   const httpResp = await ws.http.postJson(purseDepositUrl.href, 
depositPayload);
   const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
   logger.trace(`purse deposit response: ${j2s(resp)}`);
+
+  return {
+    transactionId: makeEventId(
+      TransactionType.PeerPullDebit,
+      req.peerPullPaymentIncomingId,
+    )
+  }
 }
 
 export async function checkPeerPullPayment(
@@ -801,7 +814,7 @@ export async function initiatePeerRequestForPay(
 
   logger.info(`reserve merge response: ${j2s(resp)}`);
 
-  await internalCreateWithdrawalGroup(ws, {
+  const wg = await internalCreateWithdrawalGroup(ws, {
     amount: Amounts.parseOrThrow(req.amount),
     wgInfo: {
       withdrawalType: WithdrawalRecordType.PeerPullCredit,
@@ -821,5 +834,9 @@ export async function initiatePeerRequestForPay(
       exchangeBaseUrl: req.exchangeBaseUrl,
       contractPriv: econtractResp.contractPriv,
     }),
+    transactionId: makeEventId(
+      TransactionType.PeerPullCredit,
+      wg.withdrawalGroupId
+    )
   };
 }
diff --git a/packages/taler-wallet-core/src/operations/refund.ts 
b/packages/taler-wallet-core/src/operations/refund.ts
index f028dfbf1..644b07ef1 100644
--- a/packages/taler-wallet-core/src/operations/refund.ts
+++ b/packages/taler-wallet-core/src/operations/refund.ts
@@ -46,6 +46,7 @@ import {
   TalerErrorCode,
   TalerErrorDetail,
   TalerProtocolTimestamp,
+  TransactionType,
   URL,
 } from "@gnu-taler/taler-util";
 import {
@@ -63,6 +64,7 @@ import { readSuccessResponseJsonOrThrow } from 
"../util/http.js";
 import { checkDbInvariant } from "../util/invariants.js";
 import { GetReadWriteAccess } from "../util/query.js";
 import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js";
+import { makeEventId } from "./transactions.js";
 
 const logger = new Logger("refund.ts");
 
@@ -573,6 +575,7 @@ export async function applyRefundFromPurchaseId(
   return {
     contractTermsHash: purchase.download.contractData.contractTermsHash,
     proposalId: purchase.proposalId,
+    transactionId: makeEventId(TransactionType.Payment, proposalId), //FIXME: 
can we have the tx id of the refund
     amountEffectivePaid: Amounts.stringify(summary.amountEffectivePaid),
     amountRefundGone: Amounts.stringify(summary.amountRefundGone),
     amountRefundGranted: Amounts.stringify(summary.amountRefundGranted),
diff --git a/packages/taler-wallet-core/src/operations/tip.ts 
b/packages/taler-wallet-core/src/operations/tip.ts
index c8f327a56..eef151cf2 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -18,6 +18,7 @@
  * Imports.
  */
 import {
+  AcceptTipResponse,
   Amounts,
   BlindedDenominationSignature,
   codecForMerchantTipResponseV2,
@@ -32,6 +33,7 @@ import {
   TalerErrorCode,
   TalerProtocolTimestamp,
   TipPlanchetDetail,
+  TransactionType,
   URL,
 } from "@gnu-taler/taler-util";
 import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
@@ -53,6 +55,7 @@ import {
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
 import { makeCoinAvailable } from "../wallet.js";
 import { updateExchangeFromUrl } from "./exchanges.js";
+import { makeEventId } from "./transactions.js";
 import {
   getCandidateWithdrawalDenoms,
   getExchangeWithdrawalInfo,
@@ -341,7 +344,7 @@ export async function processTip(
 export async function acceptTip(
   ws: InternalWalletState,
   tipId: string,
-): Promise<void> {
+): Promise<AcceptTipResponse> {
   const found = await ws.db
     .mktx((x) => [x.tips])
     .runReadWrite(async (tx) => {
@@ -357,4 +360,10 @@ export async function acceptTip(
   if (found) {
     await processTip(ws, tipId);
   }
+  return {
+    transactionId: makeEventId(
+      TransactionType.Tip,
+      tipId
+    )
+  }
 }
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index 95be50183..4c0ea7663 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -19,13 +19,16 @@
  */
 import {
   AbsoluteTime,
-  addPaytoQueryParams, Amounts,
+  addPaytoQueryParams, AmountJson, Amounts,
   constructPayPullUri,
   constructPayPushUri,
   Logger,
   OrderShortInfo, PaymentStatus,
   RefundInfoShort,
+  TalerProtocolTimestamp,
   Transaction,
+  TransactionByIdRequest,
+  TransactionRefund,
   TransactionsRequest,
   TransactionsResponse,
   TransactionType,
@@ -34,8 +37,16 @@ import {
 } from "@gnu-taler/taler-util";
 import {
   AbortStatus,
+  DepositGroupRecord,
+  ExchangeDetailsRecord,
+  OperationRetryRecord,
+  PeerPullPaymentIncomingRecord,
+  PeerPushPaymentInitiationRecord,
+  PurchaseRecord,
   RefundState,
+  TipRecord,
   WalletRefundItem,
+  WithdrawalGroupRecord,
   WithdrawalRecordType
 } from "../db.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
@@ -44,6 +55,7 @@ import { processDepositGroup } from "./deposits.js";
 import { getExchangeDetails } from "./exchanges.js";
 import { processPurchasePay } from "./pay.js";
 import { processRefreshGroup } from "./refresh.js";
+import { applyRefundFromPurchaseId } from "./refund.js";
 import { processTip } from "./tip.js";
 import { processWithdrawalGroup } from "./withdraw.js";
 
@@ -114,6 +126,500 @@ const txOrder: { [t in TransactionType]: number } = {
   [TransactionType.Tip]: 11,
 };
 
+export async function getTransactionById(
+  ws: InternalWalletState,
+  req: TransactionByIdRequest,
+): Promise<Transaction> {
+  const [typeStr, ...rest] = req.transactionId.split(":");
+  const type = typeStr as TransactionType;
+
+  if (
+    type === TransactionType.Withdrawal ||
+    type === TransactionType.PeerPullCredit ||
+    type === TransactionType.PeerPushCredit
+  ) {
+    const withdrawalGroupId = rest[0];
+    return await ws.db
+      .mktx((x) => [x.withdrawalGroups, x.exchangeDetails, x.exchanges, 
x.operationRetries])
+      .runReadWrite(async (tx) => {
+        const withdrawalGroupRecord = await tx.withdrawalGroups.get(
+          withdrawalGroupId,
+        );
+
+        if (!withdrawalGroupRecord) throw Error("not found")
+
+        const opId = RetryTags.forWithdrawal(withdrawalGroupRecord);
+        const ort = await tx.operationRetries.get(opId);
+
+        if (withdrawalGroupRecord.wgInfo.withdrawalType === 
WithdrawalRecordType.BankIntegrated) {
+          return 
buildTransactionForBankIntegratedWithdraw(withdrawalGroupRecord, ort);
+        }
+        if (withdrawalGroupRecord.wgInfo.withdrawalType === 
WithdrawalRecordType.PeerPullCredit) {
+          return buildTransactionForPullPaymentCredit(withdrawalGroupRecord, 
ort);
+        }
+        if (withdrawalGroupRecord.wgInfo.withdrawalType === 
WithdrawalRecordType.PeerPushCredit) {
+          return buildTransactionForPushPaymentCredit(withdrawalGroupRecord, 
ort);
+        }
+        const exchangeDetails = await getExchangeDetails(tx, 
withdrawalGroupRecord.exchangeBaseUrl,);
+        if (!exchangeDetails) throw Error('not exchange details')
+
+        return buildTransactionForManualWithdraw(withdrawalGroupRecord, 
exchangeDetails, ort);
+
+      });
+
+  } else if (type === TransactionType.Payment) {
+    const proposalId = rest[0];
+    return await ws.db
+      .mktx((x) => [x.purchases, x.tombstones, x.operationRetries])
+      .runReadWrite(async (tx) => {
+        const purchase = await tx.purchases.get(proposalId);
+        if (!purchase) throw Error("not found")
+
+        const filteredRefunds = await 
Promise.all(Object.values(purchase.refunds).map(async r => {
+          const t = await tx.tombstones.get(makeEventId(
+            TombstoneTag.DeleteRefund,
+            purchase.proposalId,
+            `${r.executionTime.t_s}`,
+          ))
+          if (!t) return r
+          return undefined
+        }));
+
+        const cleanRefunds = filteredRefunds.filter((x): x is WalletRefundItem 
=> !!x);
+
+        const contractData = purchase.download.contractData;
+        const refunds = mergeRefundByExecutionTime(cleanRefunds, 
Amounts.getZero(contractData.amount.currency));
+
+        const payOpId = RetryTags.forPay(purchase);
+        const refundQueryOpId = RetryTags.forRefundQuery(purchase);
+        const payRetryRecord = await tx.operationRetries.get(payOpId);
+        const refundQueryRetryRecord = await tx.operationRetries.get(
+          refundQueryOpId,
+        );
+
+        const err = payRetryRecord !== undefined ? payRetryRecord : 
refundQueryRetryRecord
+
+        return buildTransactionForPurchase(purchase, refunds, err);
+      });
+  } else if (type === TransactionType.Refresh) {
+    const refreshGroupId = rest[0];
+    throw Error(`no tx for refresh`);
+
+  } else if (type === TransactionType.Tip) {
+    const tipId = rest[0];
+    return await ws.db
+      .mktx((x) => [x.tips, x.operationRetries])
+      .runReadWrite(async (tx) => {
+        const tipRecord = await tx.tips.get(tipId);
+        if (!tipRecord) throw Error("not found")
+
+        const retries = await 
tx.operationRetries.get(RetryTags.forTipPickup(tipRecord));
+        return buildTransactionForTip(tipRecord, retries)
+      });
+  } else if (type === TransactionType.Deposit) {
+    const depositGroupId = rest[0];
+    return await ws.db
+      .mktx((x) => [x.depositGroups, x.operationRetries])
+      .runReadWrite(async (tx) => {
+        const depositRecord = await tx.depositGroups.get(depositGroupId);
+        if (!depositRecord) throw Error("not found")
+
+        const retries = await 
tx.operationRetries.get(RetryTags.forDeposit(depositRecord));
+        return buildTransactionForDeposit(depositRecord, retries)
+      });
+  } else if (type === TransactionType.Refund) {
+    const proposalId = rest[0];
+    const executionTimeStr = rest[1];
+
+    return await ws.db
+      .mktx((x) => [x.operationRetries, x.purchases, x.tombstones])
+      .runReadWrite(async (tx) => {
+        const purchase = await tx.purchases.get(proposalId);
+        if (!purchase) throw Error("not found")
+
+        const theRefund = Object.values(purchase.refunds).find(r => 
`${r.executionTime.t_s}` === executionTimeStr)
+        if (!theRefund) throw Error("not found")
+
+        const t = await tx.tombstones.get(makeEventId(
+          TombstoneTag.DeleteRefund,
+          purchase.proposalId,
+          executionTimeStr,
+        ))
+        if (t) throw Error("deleted")
+
+        const contractData = purchase.download.contractData;
+        const refunds = mergeRefundByExecutionTime([theRefund], 
Amounts.getZero(contractData.amount.currency))
+
+        const refundQueryOpId = RetryTags.forRefundQuery(purchase);
+        const refundQueryRetryRecord = await tx.operationRetries.get(
+          refundQueryOpId,
+        );
+
+        return buildTransactionForRefund(purchase, refunds[0], 
refundQueryRetryRecord);
+      });
+  } else if (type === TransactionType.PeerPullDebit) {
+    const peerPullPaymentIncomingId = rest[0];
+    return await ws.db
+      .mktx((x) => [x.peerPullPaymentIncoming])
+      .runReadWrite(async (tx) => {
+        const debit = await tx.peerPullPaymentIncoming.get(
+          peerPullPaymentIncomingId,
+        );
+        if (!debit) throw Error("not found");
+        return buildTransactionForPullPaymentDebit(debit)
+      });
+  } else if (type === TransactionType.PeerPushDebit) {
+    const pursePub = rest[0];
+    return await ws.db
+      .mktx((x) => [x.peerPushPaymentInitiations])
+      .runReadWrite(async (tx) => {
+        const debit = await tx.peerPushPaymentInitiations.get(pursePub);
+        if (!debit) throw Error("not found");
+        return buildTransactionForPushPaymentDebit(debit)
+      });
+  } else {
+    const unknownTxType: never = type;
+    throw Error(`can't delete a '${unknownTxType}' transaction`);
+  }
+}
+
+
+function buildTransactionForPushPaymentDebit(pi: 
PeerPushPaymentInitiationRecord, ort?: OperationRetryRecord): Transaction {
+  return {
+    type: TransactionType.PeerPushDebit,
+    amountEffective: pi.amount,
+    amountRaw: pi.amount,
+    exchangeBaseUrl: pi.exchangeBaseUrl,
+    info: {
+      expiration: pi.contractTerms.purse_expiration,
+      summary: pi.contractTerms.summary,
+    },
+    frozen: false,
+    pending: !pi.purseCreated,
+    timestamp: pi.timestampCreated,
+    talerUri: constructPayPushUri({
+      exchangeBaseUrl: pi.exchangeBaseUrl,
+      contractPriv: pi.contractPriv,
+    }),
+    transactionId: makeEventId(
+      TransactionType.PeerPushDebit,
+      pi.pursePub,
+    ),
+    ...(ort?.lastError ? { error: ort.lastError } : {}),
+  };
+}
+
+function buildTransactionForPullPaymentDebit(pi: 
PeerPullPaymentIncomingRecord, ort?: OperationRetryRecord): Transaction {
+  return {
+    type: TransactionType.PeerPullDebit,
+    amountEffective: Amounts.stringify(pi.contractTerms.amount),
+    amountRaw: Amounts.stringify(pi.contractTerms.amount),
+    exchangeBaseUrl: pi.exchangeBaseUrl,
+    frozen: false,
+    pending: false,
+    info: {
+      expiration: pi.contractTerms.purse_expiration,
+      summary: pi.contractTerms.summary,
+    },
+    timestamp: pi.timestampCreated,
+    transactionId: makeEventId(
+      TransactionType.PeerPullDebit,
+      pi.peerPullPaymentIncomingId,
+    ),
+    ...(ort?.lastError ? { error: ort.lastError } : {}),
+  }
+}
+
+function buildTransactionForPullPaymentCredit(wsr: WithdrawalGroupRecord, 
ort?: OperationRetryRecord): Transaction {
+  if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPullCredit) throw 
Error("")
+  return {
+    type: TransactionType.PeerPullCredit,
+    amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+    amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
+    exchangeBaseUrl: wsr.exchangeBaseUrl,
+    pending: !wsr.timestampFinish,
+    timestamp: wsr.timestampStart,
+    info: {
+      expiration: wsr.wgInfo.contractTerms.purse_expiration,
+      summary: wsr.wgInfo.contractTerms.summary,
+    },
+    talerUri: constructPayPullUri({
+      exchangeBaseUrl: wsr.exchangeBaseUrl,
+      contractPriv: wsr.wgInfo.contractPriv,
+    }),
+    transactionId: makeEventId(
+      TransactionType.PeerPullCredit,
+      wsr.withdrawalGroupId,
+    ),
+    frozen: false,
+    ...(ort?.lastError ? { error: ort.lastError } : {}),
+  }
+}
+
+function buildTransactionForPushPaymentCredit(wsr: WithdrawalGroupRecord, 
ort?: OperationRetryRecord): Transaction {
+  if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) throw 
Error("")
+  return {
+    type: TransactionType.PeerPushCredit,
+    amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+    amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
+    exchangeBaseUrl: wsr.exchangeBaseUrl,
+    info: {
+      expiration: wsr.wgInfo.contractTerms.purse_expiration,
+      summary: wsr.wgInfo.contractTerms.summary,
+    },
+    pending: !wsr.timestampFinish,
+    timestamp: wsr.timestampStart,
+    transactionId: makeEventId(
+      TransactionType.PeerPushCredit,
+      wsr.withdrawalGroupId,
+    ),
+    frozen: false,
+    ...(ort?.lastError ? { error: ort.lastError } : {}),
+  }
+}
+
+function buildTransactionForBankIntegratedWithdraw(wsr: WithdrawalGroupRecord, 
ort?: OperationRetryRecord): Transaction {
+  if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) throw 
Error("")
+
+  return {
+    type: TransactionType.Withdrawal,
+    amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+    amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
+    withdrawalDetails: {
+      type: WithdrawalType.TalerBankIntegrationApi,
+      confirmed: wsr.wgInfo.bankInfo.timestampBankConfirmed
+        ? true
+        : false,
+      reservePub: wsr.reservePub,
+      bankConfirmationUrl: wsr.wgInfo.bankInfo.confirmUrl,
+    },
+    exchangeBaseUrl: wsr.exchangeBaseUrl,
+    pending: !wsr.timestampFinish,
+    timestamp: wsr.timestampStart,
+    transactionId: makeEventId(
+      TransactionType.Withdrawal,
+      wsr.withdrawalGroupId,
+    ),
+    frozen: false,
+    ...(ort?.lastError ? { error: ort.lastError } : {}),
+  }
+}
+
+function buildTransactionForManualWithdraw(wsr: WithdrawalGroupRecord, 
exchangeDetails: ExchangeDetailsRecord, ort?: OperationRetryRecord): 
Transaction {
+  if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual) throw 
Error("")
+
+  return {
+    type: TransactionType.Withdrawal,
+    amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+    amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
+    withdrawalDetails: {
+      type: WithdrawalType.ManualTransfer,
+      reservePub: wsr.reservePub,
+      exchangePaytoUris:
+        exchangeDetails.wireInfo?.accounts.map(
+          (x) => addPaytoQueryParams(x.payto_uri, { subject: wsr.reservePub }),
+        ) ?? [],
+    },
+    exchangeBaseUrl: wsr.exchangeBaseUrl,
+    pending: !wsr.timestampFinish,
+    timestamp: wsr.timestampStart,
+    transactionId: makeEventId(
+      TransactionType.Withdrawal,
+      wsr.withdrawalGroupId,
+    ),
+    frozen: false,
+    ...(ort?.lastError ? { error: ort.lastError } : {}),
+  }
+}
+
+function buildTransactionForDeposit(dg: DepositGroupRecord, ort?: 
OperationRetryRecord): Transaction {
+  return {
+    type: TransactionType.Deposit,
+    amountRaw: Amounts.stringify(dg.effectiveDepositAmount),
+    amountEffective: Amounts.stringify(dg.totalPayCost),
+    pending: !dg.timestampFinished,
+    frozen: false,
+    timestamp: dg.timestampCreated,
+    targetPaytoUri: dg.wire.payto_uri,
+    transactionId: makeEventId(
+      TransactionType.Deposit,
+      dg.depositGroupId,
+    ),
+    depositGroupId: dg.depositGroupId,
+    ...(ort?.lastError ? { error: ort.lastError } : {}),
+  }
+}
+
+function buildTransactionForTip(tipRecord: TipRecord, ort?: 
OperationRetryRecord): Transaction {
+  if (!tipRecord.acceptedTimestamp) throw Error("")
+
+  return {
+    type: TransactionType.Tip,
+    amountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
+    amountRaw: Amounts.stringify(tipRecord.tipAmountRaw),
+    pending: !tipRecord.pickedUpTimestamp,
+    frozen: false,
+    timestamp: tipRecord.acceptedTimestamp,
+    transactionId: makeEventId(
+      TransactionType.Tip,
+      tipRecord.walletTipId,
+    ),
+    merchantBaseUrl: tipRecord.merchantBaseUrl,
+    ...(ort?.lastError ? { error: ort.lastError } : {}),
+  }
+}
+
+/**
+ * For a set of refund with the same executionTime.
+ * 
+ */
+interface MergedRefundInfo {
+  executionTime: TalerProtocolTimestamp;
+  amountAppliedRaw: AmountJson;
+  amountAppliedEffective: AmountJson;
+  firstTimestamp: TalerProtocolTimestamp;
+}
+
+function mergeRefundByExecutionTime(rs: WalletRefundItem[], zero: AmountJson): 
MergedRefundInfo[] {
+  const refundByExecTime = rs.reduce((prev, refund) => {
+    const key = `${refund.executionTime.t_s}`;
+
+    //refunds counts if applied
+    const effective = refund.type === RefundState.Applied ? Amounts.sub(
+      refund.refundAmount,
+      refund.refundFee,
+      refund.totalRefreshCostBound,
+    ).amount : zero
+    const raw = refund.type === RefundState.Applied ? refund.refundAmount : 
zero
+
+    const v = prev.get(key)
+    if (!v) {
+      prev.set(key, {
+        executionTime: refund.executionTime,
+        amountAppliedEffective: effective,
+        amountAppliedRaw: raw,
+        firstTimestamp: refund.obtainedTime
+      })
+    } else {
+      //v.executionTime is the same
+      v.amountAppliedEffective = Amounts.add(v.amountAppliedEffective, 
effective).amount;
+      v.amountAppliedRaw = Amounts.add(v.amountAppliedRaw).amount
+      v.firstTimestamp = TalerProtocolTimestamp.min(v.firstTimestamp, 
refund.obtainedTime);
+    }
+    return prev
+  }, {} as Map<string, MergedRefundInfo>);
+
+  return Array.from(refundByExecTime.values());
+}
+
+function buildTransactionForRefund(purchaseRecord: PurchaseRecord, refundInfo: 
MergedRefundInfo, ort?: OperationRetryRecord): Transaction {
+
+  const contractData = purchaseRecord.download.contractData;
+
+  const info: OrderShortInfo = {
+    merchant: contractData.merchant,
+    orderId: contractData.orderId,
+    products: contractData.products,
+    summary: contractData.summary,
+    summary_i18n: contractData.summaryI18n,
+    contractTermsHash: contractData.contractTermsHash,
+  };
+  if (contractData.fulfillmentUrl !== "") {
+    info.fulfillmentUrl = contractData.fulfillmentUrl;
+  }
+
+  return {
+    type: TransactionType.Refund,
+    info,
+    refundedTransactionId: makeEventId(
+      TransactionType.Payment,
+      purchaseRecord.proposalId,
+    ),
+    transactionId: makeEventId(
+      TransactionType.Refund,
+      purchaseRecord.proposalId,
+      `${refundInfo.executionTime.t_s}`,
+    ),
+    timestamp: refundInfo.firstTimestamp,
+    amountEffective: Amounts.stringify(refundInfo.amountAppliedEffective),
+    amountRaw: Amounts.stringify(refundInfo.amountAppliedRaw),
+    refundPending:
+      purchaseRecord.refundAwaiting === undefined
+        ? undefined
+        : Amounts.stringify(purchaseRecord.refundAwaiting),
+    pending: false,
+    frozen: false,
+    ...(ort?.lastError ? { error: ort.lastError } : {}),
+  }
+}
+
+function buildTransactionForPurchase(purchaseRecord: PurchaseRecord, 
refundsInfo: MergedRefundInfo[], ort?: OperationRetryRecord): Transaction {
+
+  const contractData = purchaseRecord.download.contractData;
+  const zero = Amounts.getZero(contractData.amount.currency)
+
+  const info: OrderShortInfo = {
+    merchant: contractData.merchant,
+    orderId: contractData.orderId,
+    products: contractData.products,
+    summary: contractData.summary,
+    summary_i18n: contractData.summaryI18n,
+    contractTermsHash: contractData.contractTermsHash,
+  };
+
+  if (contractData.fulfillmentUrl !== "") {
+    info.fulfillmentUrl = contractData.fulfillmentUrl;
+  }
+
+  const totalRefund = refundsInfo.reduce((prev, cur) => {
+    return {
+      raw: Amounts.add(prev.raw, cur.amountAppliedRaw).amount,
+      effective: Amounts.add(prev.effective, 
cur.amountAppliedEffective).amount,
+    }
+  }, {
+    raw: zero, effective: zero
+  } as { raw: AmountJson, effective: AmountJson })
+
+  const refunds: RefundInfoShort[] = refundsInfo.map(r => ({
+    amountEffective: Amounts.stringify(r.amountAppliedEffective),
+    amountRaw: Amounts.stringify(r.amountAppliedRaw),
+    timestamp: r.executionTime,
+    transactionId: makeEventId(
+      TransactionType.Refund,
+      purchaseRecord.proposalId,
+      `${r.executionTime.t_s}`
+    ),
+  }))
+
+  return {
+    type: TransactionType.Payment,
+    amountRaw: Amounts.stringify(contractData.amount),
+    amountEffective: Amounts.stringify(purchaseRecord.totalPayCost),
+    totalRefundRaw: Amounts.stringify(totalRefund.raw),
+    totalRefundEffective: Amounts.stringify(totalRefund.effective),
+    refundPending:
+      purchaseRecord.refundAwaiting === undefined
+        ? undefined
+        : Amounts.stringify(purchaseRecord.refundAwaiting),
+    status: purchaseRecord.timestampFirstSuccessfulPay
+      ? PaymentStatus.Paid
+      : PaymentStatus.Accepted,
+    pending:
+      !purchaseRecord.timestampFirstSuccessfulPay &&
+      purchaseRecord.abortStatus === AbortStatus.None,
+    refunds,
+    timestamp: purchaseRecord.timestampAccept,
+    transactionId: makeEventId(
+      TransactionType.Payment,
+      purchaseRecord.proposalId,
+    ),
+    proposalId: purchaseRecord.proposalId,
+    info,
+    frozen: purchaseRecord.payFrozen ?? false,
+    ...(ort?.lastError ? { error: ort.lastError } : {}),
+  }
+}
+
 /**
  * Retrieve the full event history for this wallet.
  */
@@ -137,7 +643,6 @@ export async function getTransactions(
       x.proposals,
       x.purchases,
       x.recoupGroups,
-      x.recoupGroups,
       x.tips,
       x.tombstones,
       x.withdrawalGroups,
@@ -152,27 +657,7 @@ export async function getTransactions(
         if (shouldSkipSearch(transactionsRequest, [])) {
           return;
         }
-        transactions.push({
-          type: TransactionType.PeerPushDebit,
-          amountEffective: pi.amount,
-          amountRaw: pi.amount,
-          exchangeBaseUrl: pi.exchangeBaseUrl,
-          info: {
-            expiration: pi.contractTerms.purse_expiration,
-            summary: pi.contractTerms.summary,
-          },
-          frozen: false,
-          pending: !pi.purseCreated,
-          timestamp: pi.timestampCreated,
-          talerUri: constructPayPushUri({
-            exchangeBaseUrl: pi.exchangeBaseUrl,
-            contractPriv: pi.contractPriv,
-          }),
-          transactionId: makeEventId(
-            TransactionType.PeerPushDebit,
-            pi.pursePub,
-          ),
-        });
+        transactions.push(buildTransactionForPushPaymentDebit(pi));
       });
 
       tx.peerPullPaymentIncoming.iter().forEachAsync(async (pi) => {
@@ -187,23 +672,7 @@ export async function getTransactions(
           return;
         }
 
-        transactions.push({
-          type: TransactionType.PeerPullDebit,
-          amountEffective: Amounts.stringify(amount),
-          amountRaw: Amounts.stringify(amount),
-          exchangeBaseUrl: pi.exchangeBaseUrl,
-          frozen: false,
-          pending: false,
-          info: {
-            expiration: pi.contractTerms.purse_expiration,
-            summary: pi.contractTerms.summary,
-          },
-          timestamp: pi.timestampCreated,
-          transactionId: makeEventId(
-            TransactionType.PeerPullDebit,
-            pi.peerPullPaymentIncomingId,
-          ),
-        });
+        transactions.push(buildTransactionForPullPaymentDebit(pi));
       });
 
       tx.withdrawalGroups.iter().forEachAsync(async (wsr) => {
@@ -223,64 +692,18 @@ export async function getTransactions(
         const opId = RetryTags.forWithdrawal(wsr);
         const ort = await tx.operationRetries.get(opId);
 
-        let withdrawalDetails: WithdrawalDetails;
         if (wsr.wgInfo.withdrawalType === WithdrawalRecordType.PeerPullCredit) 
{
-          transactions.push({
-            type: TransactionType.PeerPullCredit,
-            amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
-            amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
-            exchangeBaseUrl: wsr.exchangeBaseUrl,
-            pending: !wsr.timestampFinish,
-            timestamp: wsr.timestampStart,
-            info: {
-              expiration: wsr.wgInfo.contractTerms.purse_expiration,
-              summary: wsr.wgInfo.contractTerms.summary,
-            },
-            talerUri: constructPayPullUri({
-              exchangeBaseUrl: wsr.exchangeBaseUrl,
-              contractPriv: wsr.wgInfo.contractPriv,
-            }),
-            transactionId: makeEventId(
-              TransactionType.PeerPullCredit,
-              wsr.withdrawalGroupId,
-            ),
-            frozen: false,
-            ...(ort?.lastError ? { error: ort.lastError } : {}),
-          });
+          transactions.push(buildTransactionForPullPaymentCredit(wsr, ort));
           return;
         } else if (
           wsr.wgInfo.withdrawalType === WithdrawalRecordType.PeerPushCredit
         ) {
-          transactions.push({
-            type: TransactionType.PeerPushCredit,
-            amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
-            amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
-            exchangeBaseUrl: wsr.exchangeBaseUrl,
-            info: {
-              expiration: wsr.wgInfo.contractTerms.purse_expiration,
-              summary: wsr.wgInfo.contractTerms.summary,
-            },
-            pending: !wsr.timestampFinish,
-            timestamp: wsr.timestampStart,
-            transactionId: makeEventId(
-              TransactionType.PeerPushCredit,
-              wsr.withdrawalGroupId,
-            ),
-            frozen: false,
-            ...(ort?.lastError ? { error: ort.lastError } : {}),
-          });
+          transactions.push(buildTransactionForPushPaymentCredit(wsr, ort));
           return;
         } else if (
           wsr.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated
         ) {
-          withdrawalDetails = {
-            type: WithdrawalType.TalerBankIntegrationApi,
-            confirmed: wsr.wgInfo.bankInfo.timestampBankConfirmed
-              ? true
-              : false,
-            reservePub: wsr.reservePub,
-            bankConfirmationUrl: wsr.wgInfo.bankInfo.confirmUrl,
-          };
+          transactions.push(buildTransactionForBankIntegratedWithdraw(wsr, 
ort));
         } else {
           const exchangeDetails = await getExchangeDetails(
             tx,
@@ -290,31 +713,9 @@ export async function getTransactions(
             // FIXME: report somehow
             return;
           }
-          withdrawalDetails = {
-            type: WithdrawalType.ManualTransfer,
-            reservePub: wsr.reservePub,
-            exchangePaytoUris:
-              exchangeDetails.wireInfo?.accounts.map(
-                (x) => addPaytoQueryParams(x.payto_uri, { subject: 
wsr.reservePub }),
-              ) ?? [],
-          };
-        }
 
-        transactions.push({
-          type: TransactionType.Withdrawal,
-          amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
-          amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
-          withdrawalDetails,
-          exchangeBaseUrl: wsr.exchangeBaseUrl,
-          pending: !wsr.timestampFinish,
-          timestamp: wsr.timestampStart,
-          transactionId: makeEventId(
-            TransactionType.Withdrawal,
-            wsr.withdrawalGroupId,
-          ),
-          frozen: false,
-          ...(ort?.lastError ? { error: ort.lastError } : {}),
-        });
+          transactions.push(buildTransactionForManualWithdraw(wsr, 
exchangeDetails, ort));
+        }
       });
 
       tx.depositGroups.iter().forEachAsync(async (dg) => {
@@ -324,21 +725,8 @@ export async function getTransactions(
         }
         const opId = RetryTags.forDeposit(dg);
         const retryRecord = await tx.operationRetries.get(opId);
-        transactions.push({
-          type: TransactionType.Deposit,
-          amountRaw: Amounts.stringify(dg.effectiveDepositAmount),
-          amountEffective: Amounts.stringify(dg.totalPayCost),
-          pending: !dg.timestampFinished,
-          frozen: false,
-          timestamp: dg.timestampCreated,
-          targetPaytoUri: dg.wire.payto_uri,
-          transactionId: makeEventId(
-            TransactionType.Deposit,
-            dg.depositGroupId,
-          ),
-          depositGroupId: dg.depositGroupId,
-          ...(retryRecord?.lastError ? { error: retryRecord.lastError } : {}),
-        });
+
+        transactions.push(buildTransactionForDeposit(dg, retryRecord));
       });
 
       tx.purchases.iter().forEachAsync(async (pr) => {
@@ -358,107 +746,31 @@ export async function getTransactions(
         if (!proposal) {
           return;
         }
-        const info: OrderShortInfo = {
-          merchant: contractData.merchant,
-          orderId: contractData.orderId,
-          products: contractData.products,
-          summary: contractData.summary,
-          summary_i18n: contractData.summaryI18n,
-          contractTermsHash: contractData.contractTermsHash,
-        };
-        if (contractData.fulfillmentUrl !== "") {
-          info.fulfillmentUrl = contractData.fulfillmentUrl;
-        }
-        const paymentTransactionId = makeEventId(
-          TransactionType.Payment,
-          pr.proposalId,
-        );
-        const refundGroupKeys = new Set<string>();
-
-        for (const rk of Object.keys(pr.refunds)) {
-          const refund = pr.refunds[rk];
-          const groupKey = `${refund.executionTime.t_s}`;
-          refundGroupKeys.add(groupKey);
-        }
-
-        let totalRefundRaw = Amounts.getZero(contractData.amount.currency);
-        let totalRefundEffective = Amounts.getZero(
-          contractData.amount.currency,
-        );
-        const refunds: RefundInfoShort[] = [];
 
-        for (const groupKey of refundGroupKeys.values()) {
-          const refundTombstoneId = makeEventId(
+        const filteredRefunds = await 
Promise.all(Object.values(pr.refunds).map(async r => {
+          const t = await tx.tombstones.get(makeEventId(
             TombstoneTag.DeleteRefund,
             pr.proposalId,
-            groupKey,
-          );
-          const tombstone = await tx.tombstones.get(refundTombstoneId);
-          if (tombstone) {
-            continue;
-          }
-          const refundTransactionId = makeEventId(
-            TransactionType.Refund,
-            pr.proposalId,
-            groupKey,
+            `${r.executionTime.t_s}`,
+          ))
+          if (!t) return r
+          return undefined
+        }));
+
+        const cleanRefunds = filteredRefunds.filter((x): x is WalletRefundItem 
=> !!x);
+
+        const refunds = mergeRefundByExecutionTime(cleanRefunds, 
Amounts.getZero(contractData.amount.currency));
+
+        refunds.forEach(async (refundInfo) => {
+          const refundQueryOpId = RetryTags.forRefundQuery(pr);
+          const refundQueryRetryRecord = await tx.operationRetries.get(
+            refundQueryOpId,
           );
-          let r0: WalletRefundItem | undefined;
-          let amountRaw = Amounts.getZero(contractData.amount.currency);
-          let amountEffective = Amounts.getZero(contractData.amount.currency);
-          for (const rk of Object.keys(pr.refunds)) {
-            const refund = pr.refunds[rk];
-            const myGroupKey = `${refund.executionTime.t_s}`;
-            if (myGroupKey !== groupKey) {
-              continue;
-            }
-            if (!r0) {
-              r0 = refund;
-            }
-
-            if (refund.type === RefundState.Applied) {
-              amountRaw = Amounts.add(amountRaw, refund.refundAmount).amount;
-              amountEffective = Amounts.add(
-                amountEffective,
-                Amounts.sub(
-                  refund.refundAmount,
-                  refund.refundFee,
-                  refund.totalRefreshCostBound,
-                ).amount,
-              ).amount;
-
-              refunds.push({
-                transactionId: refundTransactionId,
-                timestamp: r0.obtainedTime,
-                amountEffective: Amounts.stringify(amountEffective),
-                amountRaw: Amounts.stringify(amountRaw),
-              });
-            }
-          }
-          if (!r0) {
-            throw Error("invariant violated");
-          }
 
-          totalRefundRaw = Amounts.add(totalRefundRaw, amountRaw).amount;
-          totalRefundEffective = Amounts.add(
-            totalRefundEffective,
-            amountEffective,
-          ).amount;
-          transactions.push({
-            type: TransactionType.Refund,
-            info,
-            refundedTransactionId: paymentTransactionId,
-            transactionId: refundTransactionId,
-            timestamp: r0.obtainedTime,
-            amountEffective: Amounts.stringify(amountEffective),
-            amountRaw: Amounts.stringify(amountRaw),
-            refundPending:
-              pr.refundAwaiting === undefined
-                ? undefined
-                : Amounts.stringify(pr.refundAwaiting),
-            pending: false,
-            frozen: false,
-          });
-        }
+          transactions.push(
+            buildTransactionForRefund(pr, refundInfo, refundQueryRetryRecord)
+          )
+        })
 
         const payOpId = RetryTags.forPay(pr);
         const refundQueryOpId = RetryTags.forRefundQuery(pr);
@@ -467,32 +779,9 @@ export async function getTransactions(
           refundQueryOpId,
         );
 
-        const err =
-          refundQueryRetryRecord?.lastError ?? payRetryRecord?.lastError;
-        transactions.push({
-          type: TransactionType.Payment,
-          amountRaw: Amounts.stringify(contractData.amount),
-          amountEffective: Amounts.stringify(pr.totalPayCost),
-          totalRefundRaw: Amounts.stringify(totalRefundRaw),
-          totalRefundEffective: Amounts.stringify(totalRefundEffective),
-          refundPending:
-            pr.refundAwaiting === undefined
-              ? undefined
-              : Amounts.stringify(pr.refundAwaiting),
-          status: pr.timestampFirstSuccessfulPay
-            ? PaymentStatus.Paid
-            : PaymentStatus.Accepted,
-          pending:
-            !pr.timestampFirstSuccessfulPay &&
-            pr.abortStatus === AbortStatus.None,
-          refunds,
-          timestamp: pr.timestampAccept,
-          transactionId: paymentTransactionId,
-          proposalId: pr.proposalId,
-          info,
-          frozen: pr.payFrozen ?? false,
-          ...(err ? { error: err } : {}),
-        });
+        const err = payRetryRecord !== undefined ? payRetryRecord : 
refundQueryRetryRecord
+
+        transactions.push(buildTransactionForPurchase(pr, refunds, err));
       });
 
       tx.tips.iter().forEachAsync(async (tipRecord) => {
@@ -509,20 +798,7 @@ export async function getTransactions(
         }
         const opId = RetryTags.forTipPickup(tipRecord);
         const retryRecord = await tx.operationRetries.get(opId);
-        transactions.push({
-          type: TransactionType.Tip,
-          amountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
-          amountRaw: Amounts.stringify(tipRecord.tipAmountRaw),
-          pending: !tipRecord.pickedUpTimestamp,
-          frozen: false,
-          timestamp: tipRecord.acceptedTimestamp,
-          transactionId: makeEventId(
-            TransactionType.Tip,
-            tipRecord.walletTipId,
-          ),
-          merchantBaseUrl: tipRecord.merchantBaseUrl,
-          error: retryRecord?.lastError,
-        });
+        transactions.push(buildTransactionForTip(tipRecord, retryRecord));
       });
     });
 
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 47252a7e4..f2152ccbc 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -50,6 +50,7 @@ import {
   TalerErrorCode,
   TalerErrorDetail,
   TalerProtocolTimestamp,
+  TransactionType,
   UnblindedSignature,
   URL,
   VersionMatchResult,
@@ -104,6 +105,7 @@ import {
   getExchangeTrust,
   updateExchangeFromUrl,
 } from "./exchanges.js";
+import { makeEventId } from "./transactions.js";
 
 /**
  * Logger for this file.
@@ -256,7 +258,7 @@ export function selectWithdrawalDenominations(
       DenominationRecord.getValue(d),
       d.fees.feeWithdraw,
     ).amount;
-    for (;;) {
+    for (; ;) {
       if (Amounts.cmp(remaining, cost) < 0) {
         break;
       }
@@ -890,8 +892,7 @@ export async function updateWithdrawalDenoms(
         denom.verificationStatus === DenominationVerificationStatus.Unverified
       ) {
         logger.trace(
-          `Validating denomination (${current + 1}/${
-            denominations.length
+          `Validating denomination (${current + 1}/${denominations.length
           }) signature of ${denom.denomPubHash}`,
         );
         let valid = false;
@@ -974,7 +975,7 @@ async function queryReserve(
     if (
       resp.status === 404 &&
       result.talerErrorResponse.code ===
-        TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
+      TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
     ) {
       ws.notify({
         type: NotificationType.ReserveNotYetFound,
@@ -1003,10 +1004,16 @@ async function queryReserve(
   return { ready: true };
 }
 
+enum BankStatusResultCode {
+  Done = "done",
+  Waiting = "waiting",
+  Aborted = "aborted",
+}
+
 export async function processWithdrawalGroup(
   ws: InternalWalletState,
   withdrawalGroupId: string,
-  options: {} = {},
+  options: object = {},
 ): Promise<OperationAttemptResult> {
   logger.trace("processing withdrawal group", withdrawalGroupId);
   const withdrawalGroup = await ws.db
@@ -1053,13 +1060,15 @@ export async function processWithdrawalGroup(
           };
         }
       }
+      break;
     }
-    case ReserveRecordStatus.BankAborted:
+    case ReserveRecordStatus.BankAborted: {
       // FIXME
       return {
         type: OperationAttemptResultType.Pending,
         result: undefined,
       };
+    }
     case ReserveRecordStatus.Dormant:
       // We can try to withdraw, nothing needs to be done with the reserve.
       break;
@@ -1288,7 +1297,7 @@ export async function getExchangeWithdrawalInfo(
     ) {
       logger.warn(
         `wallet's support for exchange protocol version 
${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` +
-          `(exchange has ${exchangeDetails.protocolVersion}), checking for 
updates`,
+        `(exchange has ${exchangeDetails.protocolVersion}), checking for 
updates`,
       );
     }
   }
@@ -1540,12 +1549,6 @@ async function registerReserveWithBank(
   ws.notify({ type: NotificationType.ReserveRegisteredWithBank });
 }
 
-enum BankStatusResultCode {
-  Done = "done",
-  Waiting = "waiting",
-  Aborted = "aborted",
-}
-
 interface BankStatusResult {
   status: BankStatusResultCode;
 }
@@ -1790,6 +1793,10 @@ export async function acceptWithdrawalFromUri(
     return {
       reservePub: existingWithdrawalGroup.reservePub,
       confirmTransferUrl: url,
+      transactionId: makeEventId(
+        TransactionType.Withdrawal,
+        existingWithdrawalGroup.withdrawalGroupId,
+      )
     };
   }
 
@@ -1847,6 +1854,10 @@ export async function acceptWithdrawalFromUri(
   return {
     reservePub: withdrawalGroup.reservePub,
     confirmTransferUrl: withdrawInfo.confirmTransferUrl,
+    transactionId: makeEventId(
+      TransactionType.Withdrawal,
+      withdrawalGroupId,
+    )
   };
 }
 
@@ -1901,5 +1912,9 @@ export async function createManualWithdrawal(
   return {
     reservePub: withdrawalGroup.reservePub,
     exchangePaytoUris: exchangePaytoUris,
+    transactionId: makeEventId(
+      TransactionType.Withdrawal,
+      withdrawalGroupId,
+    )
   };
 }
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 02ed8a61b..49c7f77cf 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -91,6 +91,7 @@ import {
   OperationMap,
   FeeDescription,
   TalerErrorDetail,
+  codecForTransactionByIdRequest,
 } from "@gnu-taler/taler-util";
 import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
 import {
@@ -198,6 +199,7 @@ import {
 import { acceptTip, prepareTip, processTip } from "./operations/tip.js";
 import {
   deleteTransaction,
+  getTransactionById,
   getTransactions,
   retryTransaction,
 } from "./operations/transactions.js";
@@ -1080,6 +1082,10 @@ async function dispatchRequestInternal(
       const req = codecForTransactionsRequest().decode(payload);
       return await getTransactions(ws, req);
     }
+    case "getTransactionById": {
+      const req = codecForTransactionByIdRequest().decode(payload);
+      return await getTransactionById(ws, req)
+    }
     case "addExchange": {
       const req = codecForAddExchangeRequest().decode(payload);
       await updateExchangeFromUrl(ws, req.exchangeBaseUrl, {
@@ -1227,8 +1233,7 @@ async function dispatchRequestInternal(
     }
     case "acceptTip": {
       const req = codecForAcceptTipRequest().decode(payload);
-      await acceptTip(ws, req.walletTipId);
-      return {};
+      return await acceptTip(ws, req.walletTipId);
     }
     case "exportBackupPlain": {
       return exportBackup(ws);
diff --git 
a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx 
b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
index 3a15cf1fb..9ab5212f2 100644
--- 
a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
+++ 
b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
@@ -73,10 +73,17 @@ export function BankDetailsByPaytoType({
         </p>
         <table>
           <tr>
-            <td>{payto.targetPath}</td>
             <td>
-              <Amount value={amount} hideCurrency /> BTC
+              <div>
+                {payto.targetPath} <Amount value={amount} hideCurrency /> BTC
+              </div>
+              {payto.segwitAddrs.map((addr, i) => (
+                <div key={i}>
+                  {addr} <Amount value={min} hideCurrency /> BTC
+                </div>
+              ))}
             </td>
+            <td></td>
             <td>
               <CopyButton
                 getContent={() =>
@@ -85,21 +92,6 @@ export function BankDetailsByPaytoType({
               />
             </td>
           </tr>
-          {payto.segwitAddrs.map((addr, i) => (
-            <tr key={i}>
-              <td>{addr}</td>
-              <td>
-                <Amount value={min} hideCurrency /> BTC
-              </td>
-              <td>
-                <CopyButton
-                  getContent={() =>
-                    `${addr} ${Amounts.stringifyValue(min)} BTC`
-                  }
-                />
-              </td>
-            </tr>
-          ))}
         </table>
         <p>
           <i18n.Translate>
diff --git a/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx 
b/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx
index 877c1996a..559e5c5d4 100644
--- a/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Payment/stories.tsx
@@ -348,6 +348,7 @@ export const AlreadyPaidWithoutFulfillment = 
createExample(BaseView, {
   payResult: {
     type: ConfirmPayResultType.Done,
     contractTerms: {} as any,
+    transactionId: "",
   },
   payStatus: {
     status: PreparePayResultType.AlreadyConfirmed,
@@ -386,6 +387,7 @@ export const AlreadyPaidWithFulfillment = 
createExample(BaseView, {
       fulfillment_message: "thanks for buying!",
       fulfillment_url: "https://demo.taler.net";,
     } as Partial<ContractTerms> as any,
+    transactionId: "",
   },
   payStatus: {
     status: PreparePayResultType.AlreadyConfirmed,
diff --git 
a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.stories.tsx
index 1a5d72337..ae1581009 100644
--- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.stories.tsx
@@ -35,6 +35,7 @@ export const AllOff = createExample(TestedComponent, {
   onDownloadDatabase: async () => "this is the content of the database",
   operations: [
     {
+      id: "",
       type: PendingTaskType.ExchangeUpdate,
       exchangeBaseUrl: "http://exchange.url.";,
       givesLifeness: false,
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 3d2a16e8f..21cee8789 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -70,13 +70,8 @@ interface Props {
 }
 
 async function getTransaction(tid: string): Promise<Transaction> {
-  const res = await wxApi.getTransactions();
-  const ts = res.transactions.filter((t) => t.transactionId === tid);
-  if (ts.length > 1) throw Error("more than one transaction with this id");
-  if (ts.length === 1) {
-    return ts[0];
-  }
-  throw Error("no transaction found");
+  const res = await wxApi.getTransactionById(tid);
+  return res;
 }
 
 export function TransactionPage({ tid, goToWalletHistory }: Props): VNode {
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts 
b/packages/taler-wallet-webextension/src/wxApi.ts
index b797179c3..e9d26853d 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -68,6 +68,10 @@ import {
   WalletCoreVersion,
   WithdrawUriInfoResponse,
   ExchangeFullDetails,
+  Transaction,
+  AcceptTipResponse,
+  AcceptPeerPullPaymentResponse,
+  AcceptPeerPushPaymentResponse,
 } from "@gnu-taler/taler-util";
 import {
   AddBackupProviderRequest,
@@ -476,7 +480,7 @@ export function prepareTip(req: PrepareTipRequest): 
Promise<PrepareTipResult> {
   return callBackend("prepareTip", req);
 }
 
-export function acceptTip(req: AcceptTipRequest): Promise<void> {
+export function acceptTip(req: AcceptTipRequest): Promise<AcceptTipResponse> {
   return callBackend("acceptTip", req);
 }
 
@@ -513,7 +517,7 @@ export function checkPeerPushPayment(
 }
 export function acceptPeerPushPayment(
   req: AcceptPeerPushPaymentRequest,
-): Promise<void> {
+): Promise<AcceptPeerPushPaymentResponse> {
   return callBackend("acceptPeerPushPayment", req);
 }
 export function initiatePeerPullPayment(
@@ -528,6 +532,12 @@ export function checkPeerPullPayment(
 }
 export function acceptPeerPullPayment(
   req: AcceptPeerPullPaymentRequest,
-): Promise<void> {
+): Promise<AcceptPeerPullPaymentResponse> {
   return callBackend("acceptPeerPullPayment", req);
 }
+
+export function getTransactionById(tid: string): Promise<Transaction> {
+  return callBackend("getTransactionById", {
+    transactionId: tid
+  })
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]