aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-util/src/time.ts10
-rw-r--r--packages/taler-util/src/transactionsTypes.ts9
-rw-r--r--packages/taler-util/src/walletTypes.ts26
-rw-r--r--packages/taler-wallet-core/src/operations/deposits.ts7
-rw-r--r--packages/taler-wallet-core/src/operations/pay.ts5
-rw-r--r--packages/taler-wallet-core/src/operations/peer-to-peer.ts51
-rw-r--r--packages/taler-wallet-core/src/operations/refund.ts3
-rw-r--r--packages/taler-wallet-core/src/operations/tip.ts11
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts804
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts41
-rw-r--r--packages/taler-wallet-core/src/wallet.ts9
-rw-r--r--packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx26
-rw-r--r--packages/taler-wallet-webextension/src/cta/Payment/stories.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DeveloperPage.stories.tsx1
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Transaction.tsx9
-rw-r--r--packages/taler-wallet-webextension/src/wxApi.ts16
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