commit ba68d7f49503aa48f7f8e33cf22634de1a79097f
parent f80bfee758403381bc0b8549406fe32c105f8101
Author: Florian Dold <florian@dold.me>
Date: Wed, 17 Jul 2024 15:45:34 +0200
wallet-core: refactor generation of transaction details
Diffstat:
11 files changed, 767 insertions(+), 896 deletions(-)
diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts
@@ -38,6 +38,7 @@ import {
TalerPreciseTimestamp,
TalerProtocolTimestamp,
TombstoneIdStr,
+ Transaction,
TransactionIdStr,
WalletNotification,
assertUnreachable,
@@ -61,6 +62,7 @@ import {
PurchaseRecord,
RecoupGroupRecord,
RefreshGroupRecord,
+ WalletDbAllStoresReadOnlyTransaction,
WalletDbReadWriteTransaction,
WithdrawalGroupRecord,
timestampPreciseToDb,
@@ -795,6 +797,9 @@ export interface TransactionContext {
resumeTransaction(): Promise<void>;
failTransaction(): Promise<void>;
deleteTransaction(): Promise<void>;
+ lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined>;
}
declare const __taskIdStr: unique symbol;
diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts
@@ -31,6 +31,7 @@ import {
CreateDepositGroupRequest,
CreateDepositGroupResponse,
DepositGroupFees,
+ DepositTransactionTrackingState,
Duration,
ExchangeBatchDepositRequest,
ExchangeHandle,
@@ -48,6 +49,7 @@ import {
TalerPreciseTimestamp,
TalerProtocolTimestamp,
TrackTransaction,
+ Transaction,
TransactionAction,
TransactionIdStr,
TransactionMajorState,
@@ -91,7 +93,10 @@ import {
DepositTrackingInfo,
KycPendingInfo,
RefreshOperationStatus,
+ WalletDbAllStoresReadOnlyTransaction,
+ timestampPreciseFromDb,
timestampPreciseToDb,
+ timestampProtocolFromDb,
timestampProtocolToDb,
} from "./db.js";
import { getExchangeWireDetailsInTx } from "./exchanges.js";
@@ -107,6 +112,7 @@ import {
} from "./refresh.js";
import {
constructTransactionIdentifier,
+ isUnsuccessfulTransaction,
notifyTransition,
parseTransactionIdentifier,
} from "./transactions.js";
@@ -135,6 +141,78 @@ export class DepositTransactionContext implements TransactionContext {
});
}
+ /**
+ * Get the full transaction details for the transaction.
+ *
+ * Returns undefined if the transaction is in a state where we do not have a
+ * transaction item (e.g. if it was deleted).
+ */
+ async lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ const dg = await tx.depositGroups.get(this.depositGroupId);
+ if (!dg) {
+ return undefined;
+ }
+ const ort = await tx.operationRetries.get(this.taskId);
+
+ let deposited = true;
+ if (dg.statusPerCoin) {
+ for (const d of dg.statusPerCoin) {
+ if (d == DepositElementStatus.DepositPending) {
+ deposited = false;
+ }
+ }
+ } else {
+ deposited = false;
+ }
+
+ const trackingState: DepositTransactionTrackingState[] = [];
+
+ for (const ts of Object.values(dg.trackingState ?? {})) {
+ trackingState.push({
+ amountRaw: ts.amountRaw,
+ timestampExecuted: timestampProtocolFromDb(ts.timestampExecuted),
+ wireFee: ts.wireFee,
+ wireTransferId: ts.wireTransferId,
+ });
+ }
+
+ let wireTransferProgress = 0;
+ if (dg.statusPerCoin) {
+ wireTransferProgress =
+ (100 *
+ dg.statusPerCoin.reduce(
+ (prev, cur) => prev + (cur === DepositElementStatus.Wired ? 1 : 0),
+ 0,
+ )) /
+ dg.statusPerCoin.length;
+ }
+
+ const txState = computeDepositTransactionStatus(dg);
+ return {
+ type: TransactionType.Deposit,
+ txState,
+ txActions: computeDepositTransactionActions(dg),
+ amountRaw: Amounts.stringify(dg.counterpartyEffectiveDepositAmount),
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(Amounts.zeroOfAmount(dg.totalPayCost))
+ : Amounts.stringify(dg.totalPayCost),
+ timestamp: timestampPreciseFromDb(dg.timestampCreated),
+ targetPaytoUri: dg.wire.payto_uri,
+ wireTransferDeadline: timestampProtocolFromDb(dg.wireTransferDeadline),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Deposit,
+ depositGroupId: dg.depositGroupId,
+ }),
+ wireTransferProgress,
+ depositGroupId: dg.depositGroupId,
+ trackingState,
+ deposited,
+ ...(ort?.lastError ? { error: ort.lastError } : {}),
+ };
+ }
+
async deleteTransaction(): Promise<void> {
const depositGroupId = this.depositGroupId;
const ws = this.wex;
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
@@ -69,6 +69,8 @@ import {
TalerPreciseTimestamp,
TalerProtocolDuration,
TalerProtocolTimestamp,
+ Transaction,
+ TransactionAction,
TransactionIdStr,
TransactionMajorState,
TransactionState,
@@ -125,6 +127,7 @@ import {
ExchangeEntryDbRecordStatus,
ExchangeEntryDbUpdateStatus,
ExchangeEntryRecord,
+ WalletDbAllStoresReadOnlyTransaction,
WalletDbHelpers,
WalletDbReadOnlyTransaction,
WalletDbReadWriteTransaction,
@@ -2062,23 +2065,38 @@ export function computeDenomLossTransactionStatus(
}
export class DenomLossTransactionContext implements TransactionContext {
+ transactionId: TransactionIdStr;
+
+ constructor(
+ private wex: WalletExecutionContext,
+ public denomLossEventId: string,
+ ) {
+ this.transactionId = constructTransactionIdentifier({
+ tag: TransactionType.DenomLoss,
+ denomLossEventId,
+ });
+ }
+
get taskId(): TaskIdStr | undefined {
return undefined;
}
- transactionId: TransactionIdStr;
abortTransaction(): Promise<void> {
throw new Error("Method not implemented.");
}
+
suspendTransaction(): Promise<void> {
throw new Error("Method not implemented.");
}
+
resumeTransaction(): Promise<void> {
throw new Error("Method not implemented.");
}
+
failTransaction(): Promise<void> {
throw new Error("Method not implemented.");
}
+
async deleteTransaction(): Promise<void> {
const transitionInfo = await this.wex.db.runReadWriteTx(
{ storeNames: ["denomLossEvents"] },
@@ -2100,14 +2118,28 @@ export class DenomLossTransactionContext implements TransactionContext {
notifyTransition(this.wex, this.transactionId, transitionInfo);
}
- constructor(
- private wex: WalletExecutionContext,
- public denomLossEventId: string,
- ) {
- this.transactionId = constructTransactionIdentifier({
- tag: TransactionType.DenomLoss,
- denomLossEventId,
- });
+ async lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ const rec = await tx.denomLossEvents.get(this.denomLossEventId);
+ if (!rec) {
+ return undefined;
+ }
+ const txState = computeDenomLossTransactionStatus(rec);
+ return {
+ type: TransactionType.DenomLoss,
+ txState,
+ txActions: [TransactionAction.Delete],
+ amountRaw: Amounts.stringify(rec.amount),
+ amountEffective: Amounts.stringify(rec.amount),
+ timestamp: timestampPreciseFromDb(rec.timestampCreated),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.DenomLoss,
+ denomLossEventId: rec.denomLossEventId,
+ }),
+ lossEventType: rec.eventType,
+ exchangeBaseUrl: rec.exchangeBaseUrl,
+ };
}
}
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -63,6 +63,7 @@ import {
MerchantPayResponse,
MerchantUsingTemplateDetails,
NotificationType,
+ OrderShortInfo,
parsePayTemplateUri,
parsePayUri,
parseTalerUri,
@@ -71,6 +72,8 @@ import {
PreparePayTemplateRequest,
randomBytes,
RefreshReason,
+ RefundInfoShort,
+ RefundPaymentInfo,
SelectedProspectiveCoin,
SharePaymentResult,
StartRefundQueryForUriResponse,
@@ -84,6 +87,7 @@ import {
TalerPreciseTimestamp,
TalerProtocolViolationError,
TalerUriAction,
+ Transaction,
TransactionAction,
TransactionIdStr,
TransactionMajorState,
@@ -111,6 +115,7 @@ import {
constructTaskIdentifier,
PendingTaskType,
spendCoins,
+ TaskIdentifiers,
TaskIdStr,
TaskRunResult,
TaskRunResultType,
@@ -130,9 +135,11 @@ import {
RefundItemRecord,
RefundItemStatus,
RefundReason,
+ timestampPreciseFromDb,
timestampPreciseToDb,
timestampProtocolFromDb,
timestampProtocolToDb,
+ WalletDbAllStoresReadOnlyTransaction,
WalletDbReadOnlyTransaction,
WalletDbReadWriteTransaction,
WalletStoresV1,
@@ -145,6 +152,7 @@ import {
} from "./refresh.js";
import {
constructTransactionIdentifier,
+ isUnsuccessfulTransaction,
notifyTransition,
parseTransactionIdentifier,
} from "./transactions.js";
@@ -177,6 +185,97 @@ export class PayMerchantTransactionContext implements TransactionContext {
});
}
+ async lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ const proposalId = this.proposalId;
+ const purchaseRec = await tx.purchases.get(proposalId);
+ if (!purchaseRec) throw Error("not found");
+ const download = await expectProposalDownloadInTx(
+ this.wex,
+ tx,
+ purchaseRec,
+ );
+ const contractData = download.contractData;
+ const payOpId = TaskIdentifiers.forPay(purchaseRec);
+ const payRetryRec = await tx.operationRetries.get(payOpId);
+
+ const refundsInfo = await tx.refundGroups.indexes.byProposalId.getAll(
+ purchaseRec.proposalId,
+ );
+
+ const zero = Amounts.zeroOfAmount(contractData.amount);
+
+ const info: OrderShortInfo = {
+ merchant: {
+ name: contractData.merchant.name,
+ address: contractData.merchant.address,
+ email: contractData.merchant.email,
+ jurisdiction: contractData.merchant.jurisdiction,
+ website: contractData.merchant.website,
+ },
+ orderId: contractData.orderId,
+ summary: contractData.summary,
+ summary_i18n: contractData.summaryI18n,
+ contractTermsHash: contractData.contractTermsHash,
+ };
+
+ if (contractData.fulfillmentUrl !== "") {
+ info.fulfillmentUrl = contractData.fulfillmentUrl;
+ }
+
+ const refunds: RefundInfoShort[] = refundsInfo.map((r) => ({
+ amountEffective: r.amountEffective,
+ amountRaw: r.amountRaw,
+ timestamp: TalerPreciseTimestamp.round(
+ timestampPreciseFromDb(r.timestampCreated),
+ ),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Refund,
+ refundGroupId: r.refundGroupId,
+ }),
+ }));
+
+ const timestamp = purchaseRec.timestampAccept;
+ checkDbInvariant(
+ !!timestamp,
+ `purchase ${purchaseRec.orderId} without accepted time`,
+ );
+ checkDbInvariant(
+ !!purchaseRec.payInfo,
+ `purchase ${purchaseRec.orderId} without payinfo`,
+ );
+
+ const txState = computePayMerchantTransactionState(purchaseRec);
+ return {
+ type: TransactionType.Payment,
+ txState,
+ txActions: computePayMerchantTransactionActions(purchaseRec),
+ amountRaw: Amounts.stringify(contractData.amount),
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(zero)
+ : Amounts.stringify(purchaseRec.payInfo.totalPayCost),
+ totalRefundRaw: Amounts.stringify(zero), // FIXME!
+ totalRefundEffective: Amounts.stringify(zero), // FIXME!
+ refundPending:
+ purchaseRec.refundAmountAwaiting === undefined
+ ? undefined
+ : Amounts.stringify(purchaseRec.refundAmountAwaiting),
+ refunds,
+ posConfirmation: purchaseRec.posConfirmation,
+ timestamp: timestampPreciseFromDb(timestamp),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Payment,
+ proposalId: purchaseRec.proposalId,
+ }),
+ proposalId: purchaseRec.proposalId,
+ info,
+ refundQueryActive:
+ purchaseRec.purchaseStatus === PurchaseStatus.PendingQueryingRefund,
+ ...(payRetryRec?.lastError ? { error: payRetryRec.lastError } : {}),
+ };
+ }
+
/**
* Transition a payment transition.
*/
@@ -420,6 +519,7 @@ export class PayMerchantTransactionContext implements TransactionContext {
export class RefundTransactionContext implements TransactionContext {
public transactionId: TransactionIdStr;
public taskId: TaskIdStr | undefined = undefined;
+
constructor(
public wex: WalletExecutionContext,
public refundGroupId: string,
@@ -430,6 +530,50 @@ export class RefundTransactionContext implements TransactionContext {
});
}
+ async lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ const refundRecord = await tx.refundGroups.get(this.refundGroupId);
+ if (!refundRecord) {
+ throw Error("not found");
+ }
+ const maybeContractData = await lookupMaybeContractData(
+ tx,
+ refundRecord?.proposalId,
+ );
+
+ let paymentInfo: RefundPaymentInfo | undefined = undefined;
+
+ if (maybeContractData) {
+ paymentInfo = {
+ merchant: maybeContractData.merchant,
+ summary: maybeContractData.summary,
+ summary_i18n: maybeContractData.summaryI18n,
+ };
+ }
+
+ const txState = computeRefundTransactionState(refundRecord);
+ return {
+ type: TransactionType.Refund,
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(Amounts.zeroOfAmount(refundRecord.amountEffective))
+ : refundRecord.amountEffective,
+ amountRaw: refundRecord.amountRaw,
+ refundedTransactionId: constructTransactionIdentifier({
+ tag: TransactionType.Payment,
+ proposalId: refundRecord.proposalId,
+ }),
+ timestamp: timestampPreciseFromDb(refundRecord.timestampCreated),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Refund,
+ refundGroupId: refundRecord.refundGroupId,
+ }),
+ txState,
+ txActions: [],
+ paymentInfo,
+ };
+ }
+
async deleteTransaction(): Promise<void> {
const { wex, refundGroupId, transactionId } = this;
await wex.db.runReadWriteTx(
@@ -463,6 +607,30 @@ export class RefundTransactionContext implements TransactionContext {
}
}
+async function lookupMaybeContractData(
+ tx: WalletDbReadOnlyTransaction<["purchases", "contractTerms"]>,
+ proposalId: string,
+): Promise<WalletContractData | undefined> {
+ let contractData: WalletContractData | undefined = undefined;
+ const purchaseTx = await tx.purchases.get(proposalId);
+ if (purchaseTx && purchaseTx.download) {
+ const download = purchaseTx.download;
+ const contractTermsRecord = await tx.contractTerms.get(
+ download.contractTermsHash,
+ );
+ if (!contractTermsRecord) {
+ return;
+ }
+ contractData = extractContractData(
+ contractTermsRecord?.contractTermsRaw,
+ download.contractTermsHash,
+ download.contractTermsMerchantSig,
+ );
+ }
+
+ return contractData;
+}
+
/**
* Compute the total cost of a payment to the customer.
*
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
@@ -31,6 +31,7 @@ import {
TalerPreciseTimestamp,
TalerProtocolTimestamp,
TalerUriAction,
+ Transaction,
TransactionAction,
TransactionIdStr,
TransactionMajorState,
@@ -47,6 +48,7 @@ import {
getRandomBytes,
j2s,
makeErrorDetail,
+ stringifyPayPullUri,
stringifyTalerUri,
talerPaytoFromExchangeReserve,
} from "@gnu-taler/taler-util";
@@ -54,6 +56,7 @@ import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import {
PendingTaskType,
TaskIdStr,
+ TaskIdentifiers,
TaskRunResult,
TaskRunResultType,
TombstoneTag,
@@ -65,8 +68,11 @@ import {
import {
KycPendingInfo,
KycUserType,
+ OperationRetryRecord,
PeerPullCreditRecord,
PeerPullPaymentCreditStatus,
+ WalletDbAllStoresReadOnlyTransaction,
+ WithdrawalGroupRecord,
WithdrawalGroupStatus,
WithdrawalRecordType,
timestampOptionalPreciseFromDb,
@@ -80,6 +86,7 @@ import {
} from "./pay-peer-common.js";
import {
constructTransactionIdentifier,
+ isUnsuccessfulTransaction,
notifyTransition,
} from "./transactions.js";
import { WalletExecutionContext } from "./wallet.js";
@@ -109,6 +116,121 @@ export class PeerPullCreditTransactionContext implements TransactionContext {
});
}
+ /**
+ * Get the full transaction details for the transaction.
+ *
+ * Returns undefined if the transaction is in a state where we do not have a
+ * transaction item (e.g. if it was deleted).
+ */
+ async lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ const pullCredit = await tx.peerPullCredit.get(this.pursePub);
+ if (!pullCredit) {
+ return undefined;
+ }
+ const ct = await tx.contractTerms.get(pullCredit.contractTermsHash);
+ checkDbInvariant(!!ct, `no contract terms for p2p push ${this.pursePub}`);
+
+ const peerContractTerms = ct.contractTermsRaw;
+
+ let wsr: WithdrawalGroupRecord | undefined = undefined;
+ let wsrOrt: OperationRetryRecord | undefined = undefined;
+ if (pullCredit.withdrawalGroupId) {
+ wsr = await tx.withdrawalGroups.get(pullCredit.withdrawalGroupId);
+ if (wsr) {
+ const withdrawalOpId = TaskIdentifiers.forWithdrawal(wsr);
+ wsrOrt = await tx.operationRetries.get(withdrawalOpId);
+ }
+ }
+ const pullCreditOpId =
+ TaskIdentifiers.forPeerPullPaymentInitiation(pullCredit);
+ let pullCreditOrt = await tx.operationRetries.get(pullCreditOpId);
+
+ if (wsr) {
+ if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPullCredit) {
+ throw Error(`Unexpected withdrawalType: ${wsr.wgInfo.withdrawalType}`);
+ }
+ /**
+ * FIXME: this should be handled in the withdrawal process.
+ * PeerPull withdrawal fails until reserve have funds but it is not
+ * an error from the user perspective.
+ */
+ const silentWithdrawalErrorForInvoice =
+ wsrOrt?.lastError &&
+ wsrOrt.lastError.code ===
+ TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE &&
+ Object.values(wsrOrt.lastError.errorsPerCoin ?? {}).every((e) => {
+ return (
+ e.code === TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR &&
+ e.httpStatusCode === 409
+ );
+ });
+ const txState = computePeerPullCreditTransactionState(pullCredit);
+ checkDbInvariant(wsr.instructedAmount !== undefined, "wg uninitialized");
+ checkDbInvariant(wsr.denomsSel !== undefined, "wg uninitialized");
+ checkDbInvariant(wsr.exchangeBaseUrl !== undefined, "wg uninitialized");
+ return {
+ type: TransactionType.PeerPullCredit,
+ txState,
+ txActions: computePeerPullCreditTransactionActions(pullCredit),
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(Amounts.zeroOfAmount(wsr.instructedAmount))
+ : Amounts.stringify(wsr.denomsSel.totalCoinValue),
+ amountRaw: Amounts.stringify(wsr.instructedAmount),
+ exchangeBaseUrl: wsr.exchangeBaseUrl,
+ timestamp: timestampPreciseFromDb(pullCredit.mergeTimestamp),
+ info: {
+ expiration: peerContractTerms.purse_expiration,
+ summary: peerContractTerms.summary,
+ },
+ talerUri: stringifyPayPullUri({
+ exchangeBaseUrl: wsr.exchangeBaseUrl,
+ contractPriv: wsr.wgInfo.contractPriv,
+ }),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.PeerPullCredit,
+ pursePub: pullCredit.pursePub,
+ }),
+ kycUrl: pullCredit.kycUrl,
+ ...(wsrOrt?.lastError
+ ? {
+ error: silentWithdrawalErrorForInvoice
+ ? undefined
+ : wsrOrt.lastError,
+ }
+ : {}),
+ };
+ }
+
+ const txState = computePeerPullCreditTransactionState(pullCredit);
+ return {
+ type: TransactionType.PeerPullCredit,
+ txState,
+ txActions: computePeerPullCreditTransactionActions(pullCredit),
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(Amounts.zeroOfAmount(peerContractTerms.amount))
+ : Amounts.stringify(pullCredit.estimatedAmountEffective),
+ amountRaw: Amounts.stringify(peerContractTerms.amount),
+ exchangeBaseUrl: pullCredit.exchangeBaseUrl,
+ timestamp: timestampPreciseFromDb(pullCredit.mergeTimestamp),
+ info: {
+ expiration: peerContractTerms.purse_expiration,
+ summary: peerContractTerms.summary,
+ },
+ talerUri: stringifyPayPullUri({
+ exchangeBaseUrl: pullCredit.exchangeBaseUrl,
+ contractPriv: pullCredit.contractPriv,
+ }),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.PeerPullCredit,
+ pursePub: pullCredit.pursePub,
+ }),
+ kycUrl: pullCredit.kycUrl,
+ ...(pullCreditOrt?.lastError ? { error: pullCreditOrt.lastError } : {}),
+ };
+ }
+
async deleteTransaction(): Promise<void> {
const { wex: ws, pursePub } = this;
await ws.db.runReadWriteTx(
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
@@ -43,6 +43,7 @@ import {
TalerErrorCode,
TalerPreciseTimestamp,
TalerProtocolViolationError,
+ Transaction,
TransactionAction,
TransactionIdStr,
TransactionMajorState,
@@ -50,6 +51,7 @@ import {
TransactionState,
TransactionType,
assertUnreachable,
+ checkDbInvariant,
checkLogicInvariant,
codecForAny,
codecForExchangeGetContractResponse,
@@ -81,7 +83,9 @@ import {
PeerPullDebitRecordStatus,
PeerPullPaymentIncomingRecord,
RefreshOperationStatus,
+ WalletDbAllStoresReadOnlyTransaction,
WalletStoresV1,
+ timestampPreciseFromDb,
timestampPreciseToDb,
} from "./db.js";
import {
@@ -93,6 +97,7 @@ import { DbReadWriteTransaction, StoreNames } from "./query.js";
import { createRefreshGroup } from "./refresh.js";
import {
constructTransactionIdentifier,
+ isUnsuccessfulTransaction,
notifyTransition,
parseTransactionIdentifier,
} from "./transactions.js";
@@ -122,6 +127,48 @@ export class PeerPullDebitTransactionContext implements TransactionContext {
this.peerPullDebitId = peerPullDebitId;
}
+ /**
+ * Get the full transaction details for the transaction.
+ *
+ * Returns undefined if the transaction is in a state where we do not have a
+ * transaction item (e.g. if it was deleted).
+ */
+ async lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ const pi = await tx.peerPullDebit.get(this.peerPullDebitId);
+ if (!pi) {
+ return undefined;
+ }
+ const ort = await tx.operationRetries.get(this.taskId);
+ const txState = computePeerPullDebitTransactionState(pi);
+ const ctRec = await tx.contractTerms.get(pi.contractTermsHash);
+ checkDbInvariant(!!ctRec, `no contract terms for ${this.transactionId}`);
+ const contractTerms = ctRec.contractTermsRaw;
+ return {
+ type: TransactionType.PeerPullDebit,
+ txState,
+ txActions: computePeerPullDebitTransactionActions(pi),
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(Amounts.zeroOfAmount(pi.amount))
+ : pi.coinSel?.totalCost
+ ? pi.coinSel?.totalCost
+ : Amounts.stringify(pi.amount),
+ amountRaw: Amounts.stringify(pi.amount),
+ exchangeBaseUrl: pi.exchangeBaseUrl,
+ info: {
+ expiration: contractTerms.purse_expiration,
+ summary: contractTerms.summary,
+ },
+ timestamp: timestampPreciseFromDb(pi.timestampCreated),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.PeerPullDebit,
+ peerPullDebitId: pi.peerPullDebitId,
+ }),
+ ...(ort?.lastError ? { error: ort.lastError } : {}),
+ };
+ }
+
async deleteTransaction(): Promise<void> {
const transactionId = this.transactionId;
const ws = this.wex;
diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
@@ -29,6 +29,7 @@ import {
PreparePeerPushCreditResponse,
TalerErrorCode,
TalerPreciseTimestamp,
+ Transaction,
TransactionAction,
TransactionIdStr,
TransactionMajorState,
@@ -56,6 +57,7 @@ import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import {
PendingTaskType,
TaskIdStr,
+ TaskIdentifiers,
TaskRunResult,
TaskRunResultType,
TombstoneTag,
@@ -66,8 +68,11 @@ import {
import {
KycPendingInfo,
KycUserType,
+ OperationRetryRecord,
PeerPushCreditStatus,
PeerPushPaymentIncomingRecord,
+ WalletDbAllStoresReadOnlyTransaction,
+ WithdrawalGroupRecord,
WithdrawalGroupStatus,
WithdrawalRecordType,
timestampPreciseFromDb,
@@ -81,6 +86,7 @@ import {
import {
TransitionInfo,
constructTransactionIdentifier,
+ isUnsuccessfulTransaction,
notifyTransition,
parseTransactionIdentifier,
} from "./transactions.js";
@@ -113,6 +119,99 @@ export class PeerPushCreditTransactionContext implements TransactionContext {
});
}
+ /**
+ * Get the full transaction details for the transaction.
+ *
+ * Returns undefined if the transaction is in a state where we do not have a
+ * transaction item (e.g. if it was deleted).
+ */
+ async lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ const pushInc = await tx.peerPushCredit.get(this.peerPushCreditId);
+ if (!pushInc) {
+ return undefined;
+ }
+
+ let wg: WithdrawalGroupRecord | undefined = undefined;
+ let wgRetryRecord: OperationRetryRecord | undefined = undefined;
+ if (pushInc.withdrawalGroupId) {
+ wg = await tx.withdrawalGroups.get(pushInc.withdrawalGroupId);
+ if (wg) {
+ const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
+ wgRetryRecord = await tx.operationRetries.get(withdrawalOpId);
+ }
+ }
+ const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pushInc);
+ const pushRetryRecord = await tx.operationRetries.get(pushIncOpId);
+
+ const ct = await tx.contractTerms.get(pushInc.contractTermsHash);
+
+ if (!ct) {
+ throw Error("contract terms for P2P payment not found");
+ }
+
+ const peerContractTerms = ct.contractTermsRaw;
+
+ if (wg) {
+ if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) {
+ throw Error("invalid withdrawal group type for push payment credit");
+ }
+ checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
+ checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
+ checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized");
+
+ const txState = computePeerPushCreditTransactionState(pushInc);
+ return {
+ type: TransactionType.PeerPushCredit,
+ txState,
+ txActions: computePeerPushCreditTransactionActions(pushInc),
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount))
+ : Amounts.stringify(wg.denomsSel.totalCoinValue),
+ amountRaw: Amounts.stringify(wg.instructedAmount),
+ exchangeBaseUrl: wg.exchangeBaseUrl,
+ info: {
+ expiration: peerContractTerms.purse_expiration,
+ summary: peerContractTerms.summary,
+ },
+ timestamp: timestampPreciseFromDb(wg.timestampStart),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.PeerPushCredit,
+ peerPushCreditId: pushInc.peerPushCreditId,
+ }),
+ kycUrl: pushInc.kycUrl,
+ ...(wgRetryRecord?.lastError ? { error: wgRetryRecord.lastError } : {}),
+ };
+ }
+
+ const txState = computePeerPushCreditTransactionState(pushInc);
+ return {
+ type: TransactionType.PeerPushCredit,
+ txState,
+ txActions: computePeerPushCreditTransactionActions(pushInc),
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(Amounts.zeroOfAmount(peerContractTerms.amount))
+ : // FIXME: This is wrong, needs to consider fees!
+ Amounts.stringify(peerContractTerms.amount),
+ amountRaw: Amounts.stringify(peerContractTerms.amount),
+ exchangeBaseUrl: pushInc.exchangeBaseUrl,
+ info: {
+ expiration: peerContractTerms.purse_expiration,
+ summary: peerContractTerms.summary,
+ },
+ kycUrl: pushInc.kycUrl,
+ timestamp: timestampPreciseFromDb(pushInc.timestamp),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.PeerPushCredit,
+ peerPushCreditId: pushInc.peerPushCreditId,
+ }),
+ ...(pushRetryRecord?.lastError
+ ? { error: pushRetryRecord.lastError }
+ : {}),
+ };
+ }
+
async deleteTransaction(): Promise<void> {
const { wex, peerPushCreditId } = this;
await wex.db.runReadWriteTx(
diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts
@@ -33,6 +33,7 @@ import {
TalerPreciseTimestamp,
TalerProtocolTimestamp,
TalerProtocolViolationError,
+ Transaction,
TransactionAction,
TransactionIdStr,
TransactionMajorState,
@@ -45,6 +46,7 @@ import {
encodeCrock,
getRandomBytes,
j2s,
+ stringifyPayPushUri,
} from "@gnu-taler/taler-util";
import {
HttpResponse,
@@ -71,6 +73,8 @@ import {
PeerPushDebitRecord,
PeerPushDebitStatus,
RefreshOperationStatus,
+ WalletDbAllStoresReadOnlyTransaction,
+ timestampPreciseFromDb,
timestampPreciseToDb,
timestampProtocolFromDb,
timestampProtocolToDb,
@@ -84,6 +88,7 @@ import {
import { createRefreshGroup, waitRefreshFinal } from "./refresh.js";
import {
constructTransactionIdentifier,
+ isUnsuccessfulTransaction,
notifyTransition,
} from "./transactions.js";
import { WalletExecutionContext } from "./wallet.js";
@@ -108,6 +113,59 @@ export class PeerPushDebitTransactionContext implements TransactionContext {
});
}
+ /**
+ * Get the full transaction details for the transaction.
+ *
+ * Returns undefined if the transaction is in a state where we do not have a
+ * transaction item (e.g. if it was deleted).
+ */
+ async lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ const pushDebitRec = await tx.peerPushDebit.get(this.pursePub);
+ if (!pushDebitRec) {
+ return undefined;
+ }
+ const retryRec = await tx.operationRetries.get(this.taskId);
+
+ const ctRec = await tx.contractTerms.get(pushDebitRec.contractTermsHash);
+ checkDbInvariant(!!ctRec, `no contract terms for p2p push ${this.pursePub}`);
+
+ const contractTerms = ctRec.contractTermsRaw;
+
+ let talerUri: string | undefined = undefined;
+ switch (pushDebitRec.status) {
+ case PeerPushDebitStatus.PendingReady:
+ case PeerPushDebitStatus.SuspendedReady:
+ talerUri = stringifyPayPushUri({
+ exchangeBaseUrl: pushDebitRec.exchangeBaseUrl,
+ contractPriv: pushDebitRec.contractPriv,
+ });
+ }
+ const txState = computePeerPushDebitTransactionState(pushDebitRec);
+ return {
+ type: TransactionType.PeerPushDebit,
+ txState,
+ txActions: computePeerPushDebitTransactionActions(pushDebitRec),
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(Amounts.zeroOfAmount(pushDebitRec.totalCost))
+ : pushDebitRec.totalCost,
+ amountRaw: pushDebitRec.amount,
+ exchangeBaseUrl: pushDebitRec.exchangeBaseUrl,
+ info: {
+ expiration: contractTerms.purse_expiration,
+ summary: contractTerms.summary,
+ },
+ timestamp: timestampPreciseFromDb(pushDebitRec.timestampCreated),
+ talerUri,
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub: pushDebitRec.pursePub,
+ }),
+ ...(retryRec?.lastError ? { error: retryRec.lastError } : {}),
+ };
+ }
+
async deleteTransaction(): Promise<void> {
const { wex, pursePub, transactionId } = this;
await wex.db.runReadWriteTx(
diff --git a/packages/taler-wallet-core/src/recoup.ts b/packages/taler-wallet-core/src/recoup.ts
@@ -30,6 +30,7 @@ import {
Logger,
RefreshReason,
TalerPreciseTimestamp,
+ Transaction,
TransactionIdStr,
TransactionType,
URL,
@@ -54,6 +55,7 @@ import {
RecoupGroupRecord,
RecoupOperationStatus,
RefreshCoinSource,
+ WalletDbAllStoresReadOnlyTransaction,
WalletDbReadWriteTransaction,
WithdrawCoinSource,
WithdrawalGroupStatus,
@@ -432,36 +434,47 @@ export async function processRecoupGroup(
}
export class RecoupTransactionContext implements TransactionContext {
+ public transactionId: TransactionIdStr;
+ public taskId: TaskIdStr;
+
+ constructor(
+ public wex: WalletExecutionContext,
+ private recoupGroupId: string,
+ ) {
+ this.transactionId = constructTransactionIdentifier({
+ tag: TransactionType.Recoup,
+ recoupGroupId,
+ });
+ this.taskId = constructTaskIdentifier({
+ tag: PendingTaskType.Recoup,
+ recoupGroupId,
+ });
+ }
+
abortTransaction(): Promise<void> {
throw new Error("Method not implemented.");
}
+
suspendTransaction(): Promise<void> {
throw new Error("Method not implemented.");
}
+
resumeTransaction(): Promise<void> {
throw new Error("Method not implemented.");
}
+
failTransaction(): Promise<void> {
throw new Error("Method not implemented.");
}
+
deleteTransaction(): Promise<void> {
throw new Error("Method not implemented.");
}
- public transactionId: TransactionIdStr;
- public taskId: TaskIdStr;
- constructor(
- public wex: WalletExecutionContext,
- private recoupGroupId: string,
- ) {
- this.transactionId = constructTransactionIdentifier({
- tag: TransactionType.Recoup,
- recoupGroupId,
- });
- this.taskId = constructTaskIdentifier({
- tag: PendingTaskType.Recoup,
- recoupGroupId,
- });
+ lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ throw new Error("Method not implemented.");
}
}
diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts
@@ -57,6 +57,7 @@ import {
TalerErrorCode,
TalerErrorDetail,
TalerPreciseTimestamp,
+ Transaction,
TransactionAction,
TransactionIdStr,
TransactionMajorState,
@@ -101,7 +102,9 @@ import {
RefreshGroupRecord,
RefreshOperationStatus,
RefreshSessionRecord,
+ timestampPreciseFromDb,
timestampPreciseToDb,
+ WalletDbAllStoresReadOnlyTransaction,
WalletDbReadOnlyTransaction,
WalletDbReadWriteTransaction,
WalletDbStoresArr,
@@ -109,6 +112,7 @@ import {
import { selectWithdrawalDenominations } from "./denomSelection.js";
import {
constructTransactionIdentifier,
+ isUnsuccessfulTransaction,
notifyTransition,
TransitionInfo,
} from "./transactions.js";
@@ -157,6 +161,52 @@ export class RefreshTransactionContext implements TransactionContext {
}
/**
+ * Get the full transaction details for the transaction.
+ *
+ * Returns undefined if the transaction is in a state where we do not have a
+ * transaction item (e.g. if it was deleted).
+ */
+ async lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ const refreshGroupRecord = await tx.refreshGroups.get(this.refreshGroupId);
+ if (!refreshGroupRecord) {
+ return undefined;
+ }
+ const ort = await tx.operationRetries.get(this.taskId);
+ const inputAmount = Amounts.sumOrZero(
+ refreshGroupRecord.currency,
+ refreshGroupRecord.inputPerCoin,
+ ).amount;
+ const outputAmount = Amounts.sumOrZero(
+ refreshGroupRecord.currency,
+ refreshGroupRecord.expectedOutputPerCoin,
+ ).amount;
+ const txState = computeRefreshTransactionState(refreshGroupRecord);
+ return {
+ type: TransactionType.Refresh,
+ txState,
+ txActions: computeRefreshTransactionActions(refreshGroupRecord),
+ refreshReason: refreshGroupRecord.reason,
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(Amounts.zeroOfAmount(inputAmount))
+ : Amounts.stringify(Amounts.sub(outputAmount, inputAmount).amount),
+ amountRaw: Amounts.stringify(
+ Amounts.zeroOfCurrency(refreshGroupRecord.currency),
+ ),
+ refreshInputAmount: Amounts.stringify(inputAmount),
+ refreshOutputAmount: Amounts.stringify(outputAmount),
+ originatingTransactionId: refreshGroupRecord.originatingTransactionId,
+ timestamp: timestampPreciseFromDb(refreshGroupRecord.timestampCreated),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Refresh,
+ refreshGroupId: refreshGroupRecord.refreshGroupId,
+ }),
+ ...(ort?.lastError ? { error: ort.lastError } : {}),
+ };
+ }
+
+ /**
* Transition a withdrawal transaction.
* Extra object stores may be accessed during the transition.
*/
diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts
@@ -22,22 +22,11 @@ import {
AbsoluteTime,
Amounts,
assertUnreachable,
- checkDbInvariant,
- DepositTransactionTrackingState,
j2s,
Logger,
NotificationType,
- OrderShortInfo,
- PeerContractTerms,
- RefundInfoShort,
- RefundPaymentInfo,
ScopeType,
- stringifyPayPullUri,
- stringifyPayPushUri,
- TalerErrorCode,
- TalerPreciseTimestamp,
Transaction,
- TransactionAction,
TransactionByIdRequest,
TransactionIdStr,
TransactionMajorState,
@@ -47,9 +36,7 @@ import {
TransactionState,
TransactionType,
TransactionWithdrawal,
- WalletContractData,
WithdrawalTransactionByURIRequest,
- WithdrawalType,
} from "@gnu-taler/taler-util";
import {
constructTaskIdentifier,
@@ -60,81 +47,36 @@ import {
} from "./common.js";
import {
DenomLossEventRecord,
- DepositElementStatus,
DepositGroupRecord,
OPERATION_STATUS_NONFINAL_FIRST,
OPERATION_STATUS_NONFINAL_LAST,
- OperationRetryRecord,
PeerPullCreditRecord,
PeerPullDebitRecordStatus,
PeerPullPaymentIncomingRecord,
PeerPushCreditStatus,
PeerPushDebitRecord,
- PeerPushDebitStatus,
PeerPushPaymentIncomingRecord,
PurchaseRecord,
- PurchaseStatus,
RefreshGroupRecord,
RefreshOperationStatus,
RefundGroupRecord,
- timestampPreciseFromDb,
- timestampProtocolFromDb,
WalletDbReadOnlyTransaction,
WithdrawalGroupRecord,
- WithdrawalGroupStatus,
WithdrawalRecordType,
} from "./db.js";
+import { DepositTransactionContext } from "./deposits.js";
+import { DenomLossTransactionContext } from "./exchanges.js";
import {
- computeDepositTransactionActions,
- computeDepositTransactionStatus,
- DepositTransactionContext,
-} from "./deposits.js";
-import {
- computeDenomLossTransactionStatus,
- DenomLossTransactionContext,
- ExchangeWireDetails,
-} from "./exchanges.js";
-import {
- computePayMerchantTransactionActions,
- computePayMerchantTransactionState,
- computeRefundTransactionState,
- expectProposalDownloadInTx,
- extractContractData,
PayMerchantTransactionContext,
RefundTransactionContext,
} from "./pay-merchant.js";
-import {
- computePeerPullCreditTransactionActions,
- computePeerPullCreditTransactionState,
- PeerPullCreditTransactionContext,
-} from "./pay-peer-pull-credit.js";
-import {
- computePeerPullDebitTransactionActions,
- computePeerPullDebitTransactionState,
- PeerPullDebitTransactionContext,
-} from "./pay-peer-pull-debit.js";
-import {
- computePeerPushCreditTransactionActions,
- computePeerPushCreditTransactionState,
- PeerPushCreditTransactionContext,
-} from "./pay-peer-push-credit.js";
-import {
- computePeerPushDebitTransactionActions,
- computePeerPushDebitTransactionState,
- PeerPushDebitTransactionContext,
-} from "./pay-peer-push-debit.js";
-import {
- computeRefreshTransactionActions,
- computeRefreshTransactionState,
- RefreshTransactionContext,
-} from "./refresh.js";
+import { PeerPullCreditTransactionContext } from "./pay-peer-pull-credit.js";
+import { PeerPullDebitTransactionContext } from "./pay-peer-pull-debit.js";
+import { PeerPushCreditTransactionContext } from "./pay-peer-push-credit.js";
+import { PeerPushDebitTransactionContext } from "./pay-peer-push-debit.js";
+import { RefreshTransactionContext } from "./refresh.js";
import type { WalletExecutionContext } from "./wallet.js";
-import {
- augmentPaytoUrisForWithdrawal,
- computeWithdrawalTransactionActions,
- computeWithdrawalTransactionStatus,
- WithdrawTransactionContext,
-} from "./withdraw.js";
+import { WithdrawTransactionContext } from "./withdraw.js";
const logger = new Logger("taler-wallet-core:transactions.ts");
@@ -222,474 +164,27 @@ export async function getTransactionById(
switch (parsedTx.tag) {
case TransactionType.InternalWithdrawal:
- case TransactionType.Withdrawal: {
- const ctx = new WithdrawTransactionContext(
- wex,
- parsedTx.withdrawalGroupId,
- );
- const res = await wex.db.runAllStoresReadOnlyTx({}, async (tx) => {
- return await ctx.lookupFullTransaction(tx);
- });
- if (!res) {
- throw Error("not found");
- }
- return res;
- }
-
- case TransactionType.DenomLoss: {
- const rec = await wex.db.runReadOnlyTx(
- { storeNames: ["denomLossEvents"] },
- async (tx) => {
- return tx.denomLossEvents.get(parsedTx.denomLossEventId);
- },
- );
- if (!rec) {
- throw Error("denom loss record not found");
- }
- return buildTransactionForDenomLoss(rec);
- }
-
+ case TransactionType.Withdrawal:
+ case TransactionType.DenomLoss:
case TransactionType.Recoup:
- throw new Error("not yet supported");
-
- case TransactionType.Payment: {
- const proposalId = parsedTx.proposalId;
- return await wex.db.runReadWriteTx(
- {
- storeNames: [
- "purchases",
- "tombstones",
- "operationRetries",
- "contractTerms",
- "refundGroups",
- ],
- },
- async (tx) => {
- const purchase = await tx.purchases.get(proposalId);
- if (!purchase) throw Error("not found");
- const download = await expectProposalDownloadInTx(wex, tx, purchase);
- const contractData = download.contractData;
- const payOpId = TaskIdentifiers.forPay(purchase);
- const payRetryRecord = await tx.operationRetries.get(payOpId);
-
- const refunds = await tx.refundGroups.indexes.byProposalId.getAll(
- purchase.proposalId,
- );
-
- return buildTransactionForPurchase(
- purchase,
- contractData,
- refunds,
- payRetryRecord,
- );
- },
- );
- }
-
- case TransactionType.Refresh: {
- // FIXME: We should return info about the refresh here!;
- const refreshGroupId = parsedTx.refreshGroupId;
- return await wex.db.runReadOnlyTx(
- { storeNames: ["refreshGroups", "operationRetries"] },
- async (tx) => {
- const refreshGroupRec = await tx.refreshGroups.get(refreshGroupId);
- if (!refreshGroupRec) {
- throw Error("not found");
- }
- const retries = await tx.operationRetries.get(
- TaskIdentifiers.forRefresh(refreshGroupRec),
- );
- return buildTransactionForRefresh(refreshGroupRec, retries);
- },
- );
- }
-
- case TransactionType.Deposit: {
- const depositGroupId = parsedTx.depositGroupId;
- return await wex.db.runReadWriteTx(
- { storeNames: ["depositGroups", "operationRetries"] },
- async (tx) => {
- const depositRecord = await tx.depositGroups.get(depositGroupId);
- if (!depositRecord) throw Error("not found");
-
- const retries = await tx.operationRetries.get(
- TaskIdentifiers.forDeposit(depositRecord),
- );
- return buildTransactionForDeposit(depositRecord, retries);
- },
- );
- }
-
+ case TransactionType.PeerPushDebit:
+ case TransactionType.PeerPushCredit:
+ case TransactionType.Refresh:
+ case TransactionType.PeerPullCredit:
+ case TransactionType.Payment:
+ case TransactionType.Deposit:
+ case TransactionType.PeerPullDebit:
case TransactionType.Refund: {
- return await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "refundGroups",
- "purchases",
- "operationRetries",
- "contractTerms",
- ],
- },
- async (tx) => {
- const refundRecord = await tx.refundGroups.get(
- parsedTx.refundGroupId,
- );
- if (!refundRecord) {
- throw Error("not found");
- }
- const contractData = await lookupMaybeContractData(
- tx,
- refundRecord?.proposalId,
- );
- return buildTransactionForRefund(refundRecord, contractData);
- },
+ const ctx = await getContextForTransaction(wex, req.transactionId);
+ const txDetails = await wex.db.runAllStoresReadOnlyTx({}, async (tx) =>
+ ctx.lookupFullTransaction(tx),
);
+ if (!txDetails) {
+ throw Error("transaction not found");
+ }
+ return txDetails;
}
- case TransactionType.PeerPullDebit: {
- return await wex.db.runReadWriteTx(
- { storeNames: ["peerPullDebit", "contractTerms"] },
- async (tx) => {
- const debit = await tx.peerPullDebit.get(parsedTx.peerPullDebitId);
- if (!debit) throw Error("not found");
- const contractTermsRec = await tx.contractTerms.get(
- debit.contractTermsHash,
- );
- if (!contractTermsRec)
- throw Error("contract terms for peer-pull-debit not found");
- return buildTransactionForPullPaymentDebit(
- debit,
- contractTermsRec.contractTermsRaw,
- );
- },
- );
- }
-
- case TransactionType.PeerPushDebit: {
- return await wex.db.runReadWriteTx(
- { storeNames: ["peerPushDebit", "contractTerms"] },
- async (tx) => {
- const debit = await tx.peerPushDebit.get(parsedTx.pursePub);
- if (!debit) throw Error("not found");
- const ct = await tx.contractTerms.get(debit.contractTermsHash);
- checkDbInvariant(
- !!ct,
- `no contract terms for p2p push ${parsedTx.pursePub}`,
- );
- return buildTransactionForPushPaymentDebit(
- debit,
- ct.contractTermsRaw,
- );
- },
- );
- }
-
- case TransactionType.PeerPushCredit: {
- const peerPushCreditId = parsedTx.peerPushCreditId;
- return await wex.db.runReadWriteTx(
- {
- storeNames: [
- "peerPushCredit",
- "contractTerms",
- "withdrawalGroups",
- "operationRetries",
- ],
- },
- async (tx) => {
- const pushInc = await tx.peerPushCredit.get(peerPushCreditId);
- if (!pushInc) throw Error("not found");
- const ct = await tx.contractTerms.get(pushInc.contractTermsHash);
- checkDbInvariant(
- !!ct,
- `no contract terms for p2p push ${peerPushCreditId}`,
- );
-
- let wg: WithdrawalGroupRecord | undefined = undefined;
- let wgOrt: OperationRetryRecord | undefined = undefined;
- if (pushInc.withdrawalGroupId) {
- wg = await tx.withdrawalGroups.get(pushInc.withdrawalGroupId);
- if (wg) {
- const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
- wgOrt = await tx.operationRetries.get(withdrawalOpId);
- }
- }
- const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pushInc);
- const pushIncOrt = await tx.operationRetries.get(pushIncOpId);
-
- return buildTransactionForPeerPushCredit(
- pushInc,
- pushIncOrt,
- ct.contractTermsRaw,
- wg,
- wgOrt,
- );
- },
- );
- }
-
- case TransactionType.PeerPullCredit: {
- const pursePub = parsedTx.pursePub;
- return await wex.db.runReadWriteTx(
- {
- storeNames: [
- "peerPullCredit",
- "contractTerms",
- "withdrawalGroups",
- "operationRetries",
- ],
- },
- async (tx) => {
- const pushInc = await tx.peerPullCredit.get(pursePub);
- if (!pushInc) throw Error("not found");
- const ct = await tx.contractTerms.get(pushInc.contractTermsHash);
- checkDbInvariant(!!ct, `no contract terms for p2p push ${pursePub}`);
-
- let wg: WithdrawalGroupRecord | undefined = undefined;
- let wgOrt: OperationRetryRecord | undefined = undefined;
- if (pushInc.withdrawalGroupId) {
- wg = await tx.withdrawalGroups.get(pushInc.withdrawalGroupId);
- if (wg) {
- const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
- wgOrt = await tx.operationRetries.get(withdrawalOpId);
- }
- }
- const pushIncOpId =
- TaskIdentifiers.forPeerPullPaymentInitiation(pushInc);
- let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
-
- return buildTransactionForPeerPullCredit(
- pushInc,
- pushIncOrt,
- ct.contractTermsRaw,
- wg,
- wgOrt,
- );
- },
- );
- }
- }
-}
-
-function buildTransactionForPushPaymentDebit(
- pi: PeerPushDebitRecord,
- contractTerms: PeerContractTerms,
- ort?: OperationRetryRecord,
-): Transaction {
- let talerUri: string | undefined = undefined;
- switch (pi.status) {
- case PeerPushDebitStatus.PendingReady:
- case PeerPushDebitStatus.SuspendedReady:
- talerUri = stringifyPayPushUri({
- exchangeBaseUrl: pi.exchangeBaseUrl,
- contractPriv: pi.contractPriv,
- });
- }
- const txState = computePeerPushDebitTransactionState(pi);
- return {
- type: TransactionType.PeerPushDebit,
- txState,
- txActions: computePeerPushDebitTransactionActions(pi),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(pi.totalCost))
- : pi.totalCost,
- amountRaw: pi.amount,
- exchangeBaseUrl: pi.exchangeBaseUrl,
- info: {
- expiration: contractTerms.purse_expiration,
- summary: contractTerms.summary,
- },
- timestamp: timestampPreciseFromDb(pi.timestampCreated),
- talerUri,
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.PeerPushDebit,
- pursePub: pi.pursePub,
- }),
- ...(ort?.lastError ? { error: ort.lastError } : {}),
- };
-}
-
-function buildTransactionForPullPaymentDebit(
- pi: PeerPullPaymentIncomingRecord,
- contractTerms: PeerContractTerms,
- ort?: OperationRetryRecord,
-): Transaction {
- const txState = computePeerPullDebitTransactionState(pi);
- return {
- type: TransactionType.PeerPullDebit,
- txState,
- txActions: computePeerPullDebitTransactionActions(pi),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(pi.amount))
- : pi.coinSel?.totalCost
- ? pi.coinSel?.totalCost
- : Amounts.stringify(pi.amount),
- amountRaw: Amounts.stringify(pi.amount),
- exchangeBaseUrl: pi.exchangeBaseUrl,
- info: {
- expiration: contractTerms.purse_expiration,
- summary: contractTerms.summary,
- },
- timestamp: timestampPreciseFromDb(pi.timestampCreated),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.PeerPullDebit,
- peerPullDebitId: pi.peerPullDebitId,
- }),
- ...(ort?.lastError ? { error: ort.lastError } : {}),
- };
-}
-
-function buildTransactionForPeerPullCredit(
- pullCredit: PeerPullCreditRecord,
- pullCreditOrt: OperationRetryRecord | undefined,
- peerContractTerms: PeerContractTerms,
- wsr: WithdrawalGroupRecord | undefined,
- wsrOrt: OperationRetryRecord | undefined,
-): Transaction {
- if (wsr) {
- if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPullCredit) {
- throw Error(`Unexpected withdrawalType: ${wsr.wgInfo.withdrawalType}`);
- }
- /**
- * FIXME: this should be handled in the withdrawal process.
- * PeerPull withdrawal fails until reserve have funds but it is not
- * an error from the user perspective.
- */
- const silentWithdrawalErrorForInvoice =
- wsrOrt?.lastError &&
- wsrOrt.lastError.code ===
- TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE &&
- Object.values(wsrOrt.lastError.errorsPerCoin ?? {}).every((e) => {
- return (
- e.code === TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR &&
- e.httpStatusCode === 409
- );
- });
- const txState = computePeerPullCreditTransactionState(pullCredit);
- checkDbInvariant(wsr.instructedAmount !== undefined, "wg uninitialized");
- checkDbInvariant(wsr.denomsSel !== undefined, "wg uninitialized");
- checkDbInvariant(wsr.exchangeBaseUrl !== undefined, "wg uninitialized");
- return {
- type: TransactionType.PeerPullCredit,
- txState,
- txActions: computePeerPullCreditTransactionActions(pullCredit),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(wsr.instructedAmount))
- : Amounts.stringify(wsr.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(wsr.instructedAmount),
- exchangeBaseUrl: wsr.exchangeBaseUrl,
- timestamp: timestampPreciseFromDb(pullCredit.mergeTimestamp),
- info: {
- expiration: peerContractTerms.purse_expiration,
- summary: peerContractTerms.summary,
- },
- talerUri: stringifyPayPullUri({
- exchangeBaseUrl: wsr.exchangeBaseUrl,
- contractPriv: wsr.wgInfo.contractPriv,
- }),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.PeerPullCredit,
- pursePub: pullCredit.pursePub,
- }),
- kycUrl: pullCredit.kycUrl,
- ...(wsrOrt?.lastError
- ? {
- error: silentWithdrawalErrorForInvoice
- ? undefined
- : wsrOrt.lastError,
- }
- : {}),
- };
- }
-
- const txState = computePeerPullCreditTransactionState(pullCredit);
- return {
- type: TransactionType.PeerPullCredit,
- txState,
- txActions: computePeerPullCreditTransactionActions(pullCredit),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(peerContractTerms.amount))
- : Amounts.stringify(pullCredit.estimatedAmountEffective),
- amountRaw: Amounts.stringify(peerContractTerms.amount),
- exchangeBaseUrl: pullCredit.exchangeBaseUrl,
- timestamp: timestampPreciseFromDb(pullCredit.mergeTimestamp),
- info: {
- expiration: peerContractTerms.purse_expiration,
- summary: peerContractTerms.summary,
- },
- talerUri: stringifyPayPullUri({
- exchangeBaseUrl: pullCredit.exchangeBaseUrl,
- contractPriv: pullCredit.contractPriv,
- }),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.PeerPullCredit,
- pursePub: pullCredit.pursePub,
- }),
- kycUrl: pullCredit.kycUrl,
- ...(pullCreditOrt?.lastError ? { error: pullCreditOrt.lastError } : {}),
- };
-}
-
-function buildTransactionForPeerPushCredit(
- pushInc: PeerPushPaymentIncomingRecord,
- pushOrt: OperationRetryRecord | undefined,
- peerContractTerms: PeerContractTerms,
- wg: WithdrawalGroupRecord | undefined,
- wsrOrt: OperationRetryRecord | undefined,
-): Transaction {
- if (wg) {
- if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) {
- throw Error("invalid withdrawal group type for push payment credit");
- }
- checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
- checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
- checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized");
-
- const txState = computePeerPushCreditTransactionState(pushInc);
- return {
- type: TransactionType.PeerPushCredit,
- txState,
- txActions: computePeerPushCreditTransactionActions(pushInc),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount))
- : Amounts.stringify(wg.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(wg.instructedAmount),
- exchangeBaseUrl: wg.exchangeBaseUrl,
- info: {
- expiration: peerContractTerms.purse_expiration,
- summary: peerContractTerms.summary,
- },
- timestamp: timestampPreciseFromDb(wg.timestampStart),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.PeerPushCredit,
- peerPushCreditId: pushInc.peerPushCreditId,
- }),
- kycUrl: pushInc.kycUrl,
- ...(wsrOrt?.lastError ? { error: wsrOrt.lastError } : {}),
- };
}
-
- const txState = computePeerPushCreditTransactionState(pushInc);
- return {
- type: TransactionType.PeerPushCredit,
- txState,
- txActions: computePeerPushCreditTransactionActions(pushInc),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(peerContractTerms.amount))
- : // FIXME: This is wrong, needs to consider fees!
- Amounts.stringify(peerContractTerms.amount),
- amountRaw: Amounts.stringify(peerContractTerms.amount),
- exchangeBaseUrl: pushInc.exchangeBaseUrl,
- info: {
- expiration: peerContractTerms.purse_expiration,
- summary: peerContractTerms.summary,
- },
- kycUrl: pushInc.kycUrl,
- timestamp: timestampPreciseFromDb(pushInc.timestamp),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.PeerPushCredit,
- peerPushCreditId: pushInc.peerPushCreditId,
- }),
- ...(pushOrt?.lastError ? { error: pushOrt.lastError } : {}),
- };
}
export function isUnsuccessfulTransaction(state: TransactionState): boolean {
@@ -702,259 +197,6 @@ export function isUnsuccessfulTransaction(state: TransactionState): boolean {
);
}
-function buildTransactionForRefund(
- refundRecord: RefundGroupRecord,
- maybeContractData: WalletContractData | undefined,
-): Transaction {
- let paymentInfo: RefundPaymentInfo | undefined = undefined;
-
- if (maybeContractData) {
- paymentInfo = {
- merchant: maybeContractData.merchant,
- summary: maybeContractData.summary,
- summary_i18n: maybeContractData.summaryI18n,
- };
- }
-
- const txState = computeRefundTransactionState(refundRecord);
- return {
- type: TransactionType.Refund,
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(refundRecord.amountEffective))
- : refundRecord.amountEffective,
- amountRaw: refundRecord.amountRaw,
- refundedTransactionId: constructTransactionIdentifier({
- tag: TransactionType.Payment,
- proposalId: refundRecord.proposalId,
- }),
- timestamp: timestampPreciseFromDb(refundRecord.timestampCreated),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.Refund,
- refundGroupId: refundRecord.refundGroupId,
- }),
- txState,
- txActions: [],
- paymentInfo,
- };
-}
-
-function buildTransactionForRefresh(
- refreshGroupRecord: RefreshGroupRecord,
- ort?: OperationRetryRecord,
-): Transaction {
- const inputAmount = Amounts.sumOrZero(
- refreshGroupRecord.currency,
- refreshGroupRecord.inputPerCoin,
- ).amount;
- const outputAmount = Amounts.sumOrZero(
- refreshGroupRecord.currency,
- refreshGroupRecord.expectedOutputPerCoin,
- ).amount;
- const txState = computeRefreshTransactionState(refreshGroupRecord);
- return {
- type: TransactionType.Refresh,
- txState,
- txActions: computeRefreshTransactionActions(refreshGroupRecord),
- refreshReason: refreshGroupRecord.reason,
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(inputAmount))
- : Amounts.stringify(Amounts.sub(outputAmount, inputAmount).amount),
- amountRaw: Amounts.stringify(
- Amounts.zeroOfCurrency(refreshGroupRecord.currency),
- ),
- refreshInputAmount: Amounts.stringify(inputAmount),
- refreshOutputAmount: Amounts.stringify(outputAmount),
- originatingTransactionId: refreshGroupRecord.originatingTransactionId,
- timestamp: timestampPreciseFromDb(refreshGroupRecord.timestampCreated),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.Refresh,
- refreshGroupId: refreshGroupRecord.refreshGroupId,
- }),
- ...(ort?.lastError ? { error: ort.lastError } : {}),
- };
-}
-
-function buildTransactionForDenomLoss(rec: DenomLossEventRecord): Transaction {
- const txState = computeDenomLossTransactionStatus(rec);
- return {
- type: TransactionType.DenomLoss,
- txState,
- txActions: [TransactionAction.Delete],
- amountRaw: Amounts.stringify(rec.amount),
- amountEffective: Amounts.stringify(rec.amount),
- timestamp: timestampPreciseFromDb(rec.timestampCreated),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.DenomLoss,
- denomLossEventId: rec.denomLossEventId,
- }),
- lossEventType: rec.eventType,
- exchangeBaseUrl: rec.exchangeBaseUrl,
- };
-}
-
-function buildTransactionForDeposit(
- dg: DepositGroupRecord,
- ort?: OperationRetryRecord,
-): Transaction {
- let deposited = true;
- if (dg.statusPerCoin) {
- for (const d of dg.statusPerCoin) {
- if (d == DepositElementStatus.DepositPending) {
- deposited = false;
- }
- }
- } else {
- deposited = false;
- }
-
- const trackingState: DepositTransactionTrackingState[] = [];
-
- for (const ts of Object.values(dg.trackingState ?? {})) {
- trackingState.push({
- amountRaw: ts.amountRaw,
- timestampExecuted: timestampProtocolFromDb(ts.timestampExecuted),
- wireFee: ts.wireFee,
- wireTransferId: ts.wireTransferId,
- });
- }
-
- let wireTransferProgress = 0;
- if (dg.statusPerCoin) {
- wireTransferProgress =
- (100 *
- dg.statusPerCoin.reduce(
- (prev, cur) => prev + (cur === DepositElementStatus.Wired ? 1 : 0),
- 0,
- )) /
- dg.statusPerCoin.length;
- }
-
- const txState = computeDepositTransactionStatus(dg);
- return {
- type: TransactionType.Deposit,
- txState,
- txActions: computeDepositTransactionActions(dg),
- amountRaw: Amounts.stringify(dg.counterpartyEffectiveDepositAmount),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(dg.totalPayCost))
- : Amounts.stringify(dg.totalPayCost),
- timestamp: timestampPreciseFromDb(dg.timestampCreated),
- targetPaytoUri: dg.wire.payto_uri,
- wireTransferDeadline: timestampProtocolFromDb(dg.wireTransferDeadline),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.Deposit,
- depositGroupId: dg.depositGroupId,
- }),
- wireTransferProgress,
- depositGroupId: dg.depositGroupId,
- trackingState,
- deposited,
- ...(ort?.lastError ? { error: ort.lastError } : {}),
- };
-}
-
-async function lookupMaybeContractData(
- tx: WalletDbReadOnlyTransaction<["purchases", "contractTerms"]>,
- proposalId: string,
-): Promise<WalletContractData | undefined> {
- let contractData: WalletContractData | undefined = undefined;
- const purchaseTx = await tx.purchases.get(proposalId);
- if (purchaseTx && purchaseTx.download) {
- const download = purchaseTx.download;
- const contractTermsRecord = await tx.contractTerms.get(
- download.contractTermsHash,
- );
- if (!contractTermsRecord) {
- return;
- }
- contractData = extractContractData(
- contractTermsRecord?.contractTermsRaw,
- download.contractTermsHash,
- download.contractTermsMerchantSig,
- );
- }
-
- return contractData;
-}
-
-function buildTransactionForPurchase(
- purchaseRecord: PurchaseRecord,
- contractData: WalletContractData,
- refundsInfo: RefundGroupRecord[],
- ort?: OperationRetryRecord,
-): Transaction {
- const zero = Amounts.zeroOfAmount(contractData.amount);
-
- const info: OrderShortInfo = {
- merchant: {
- name: contractData.merchant.name,
- address: contractData.merchant.address,
- email: contractData.merchant.email,
- jurisdiction: contractData.merchant.jurisdiction,
- website: contractData.merchant.website,
- },
- orderId: contractData.orderId,
- summary: contractData.summary,
- summary_i18n: contractData.summaryI18n,
- contractTermsHash: contractData.contractTermsHash,
- };
-
- if (contractData.fulfillmentUrl !== "") {
- info.fulfillmentUrl = contractData.fulfillmentUrl;
- }
-
- const refunds: RefundInfoShort[] = refundsInfo.map((r) => ({
- amountEffective: r.amountEffective,
- amountRaw: r.amountRaw,
- timestamp: TalerPreciseTimestamp.round(
- timestampPreciseFromDb(r.timestampCreated),
- ),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.Refund,
- refundGroupId: r.refundGroupId,
- }),
- }));
-
- const timestamp = purchaseRecord.timestampAccept;
- checkDbInvariant(
- !!timestamp,
- `purchase ${purchaseRecord.orderId} without accepted time`,
- );
- checkDbInvariant(
- !!purchaseRecord.payInfo,
- `purchase ${purchaseRecord.orderId} without payinfo`,
- );
-
- const txState = computePayMerchantTransactionState(purchaseRecord);
- return {
- type: TransactionType.Payment,
- txState,
- txActions: computePayMerchantTransactionActions(purchaseRecord),
- amountRaw: Amounts.stringify(contractData.amount),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(zero)
- : Amounts.stringify(purchaseRecord.payInfo.totalPayCost),
- totalRefundRaw: Amounts.stringify(zero), // FIXME!
- totalRefundEffective: Amounts.stringify(zero), // FIXME!
- refundPending:
- purchaseRecord.refundAmountAwaiting === undefined
- ? undefined
- : Amounts.stringify(purchaseRecord.refundAmountAwaiting),
- refunds,
- posConfirmation: purchaseRecord.posConfirmation,
- timestamp: timestampPreciseFromDb(timestamp),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.Payment,
- proposalId: purchaseRecord.proposalId,
- }),
- proposalId: purchaseRecord.proposalId,
- info,
- refundQueryActive:
- purchaseRecord.purchaseStatus === PurchaseStatus.PendingQueryingRefund,
- ...(ort?.lastError ? { error: ort.lastError } : {}),
- };
-}
-
export async function getWithdrawalTransactionByUri(
wex: WalletExecutionContext,
request: WithdrawalTransactionByURIRequest,
@@ -1005,11 +247,11 @@ export async function getTransactions(
if (shouldSkipSearch(transactionsRequest, [])) {
return;
}
- const ct = await tx.contractTerms.get(pi.contractTermsHash);
- checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
- transactions.push(
- buildTransactionForPushPaymentDebit(pi, ct.contractTermsRaw),
- );
+ const ctx = new PeerPushDebitTransactionContext(wex, pi.pursePub);
+ const txDetails = await ctx.lookupFullTransaction(tx);
+ if (txDetails) {
+ transactions.push(txDetails);
+ }
});
await iterRecordsForPeerPullDebit(tx, filter, async (pi) => {
@@ -1031,17 +273,11 @@ export async function getTransactions(
return;
}
- const contractTermsRec = await tx.contractTerms.get(pi.contractTermsHash);
- if (!contractTermsRec) {
- return;
+ const ctx = new PeerPullDebitTransactionContext(wex, pi.peerPullDebitId);
+ const txDetails = await ctx.lookupFullTransaction(tx);
+ if (txDetails) {
+ transactions.push(txDetails);
}
-
- transactions.push(
- buildTransactionForPullPaymentDebit(
- pi,
- contractTermsRec.contractTermsRaw,
- ),
- );
});
await iterRecordsForPeerPushCredit(tx, filter, async (pi) => {
@@ -1061,29 +297,15 @@ export async function getTransactions(
// to scan URI again and confirm to see it.
return;
}
- const ct = await tx.contractTerms.get(pi.contractTermsHash);
- let wg: WithdrawalGroupRecord | undefined = undefined;
- let wgOrt: OperationRetryRecord | undefined = undefined;
- if (pi.withdrawalGroupId) {
- wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId);
- if (wg) {
- const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
- wgOrt = await tx.operationRetries.get(withdrawalOpId);
- }
- }
- const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pi);
- const pushIncOrt = await tx.operationRetries.get(pushIncOpId);
-
- checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
- transactions.push(
- buildTransactionForPeerPushCredit(
- pi,
- pushIncOrt,
- ct.contractTermsRaw,
- wg,
- wgOrt,
- ),
+
+ const ctx = new PeerPushCreditTransactionContext(
+ wex,
+ pi.peerPushCreditId,
);
+ const txDetails = await ctx.lookupFullTransaction(tx);
+ if (txDetails) {
+ transactions.push(txDetails);
+ }
});
await iterRecordsForPeerPullCredit(tx, filter, async (pi) => {
@@ -1095,29 +317,12 @@ export async function getTransactions(
if (shouldSkipSearch(transactionsRequest, [])) {
return;
}
- const ct = await tx.contractTerms.get(pi.contractTermsHash);
- let wg: WithdrawalGroupRecord | undefined = undefined;
- let wgOrt: OperationRetryRecord | undefined = undefined;
- if (pi.withdrawalGroupId) {
- wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId);
- if (wg) {
- const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
- wgOrt = await tx.operationRetries.get(withdrawalOpId);
- }
+
+ const ctx = new PeerPullCreditTransactionContext(wex, pi.pursePub);
+ const txDetails = await ctx.lookupFullTransaction(tx);
+ if (txDetails) {
+ transactions.push(txDetails);
}
- const pushIncOpId = TaskIdentifiers.forPeerPullPaymentInitiation(pi);
- const pushIncOrt = await tx.operationRetries.get(pushIncOpId);
-
- checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
- transactions.push(
- buildTransactionForPeerPullCredit(
- pi,
- pushIncOrt,
- ct.contractTermsRaw,
- wg,
- wgOrt,
- ),
- );
});
await iterRecordsForRefund(tx, filter, async (refundGroup) => {
@@ -1141,11 +346,12 @@ export async function getTransactions(
if (shouldSkipCurrency(transactionsRequest, currency, exchangesInTx)) {
return;
}
- const contractData = await lookupMaybeContractData(
- tx,
- refundGroup.proposalId,
- );
- transactions.push(buildTransactionForRefund(refundGroup, contractData));
+
+ const ctx = new RefundTransactionContext(wex, refundGroup.refundGroupId);
+ const txDetails = await ctx.lookupFullTransaction(tx);
+ if (txDetails) {
+ transactions.push(txDetails);
+ }
});
await iterRecordsForRefresh(tx, filter, async (rg) => {
@@ -1166,8 +372,11 @@ export async function getTransactions(
}
}
if (required) {
- const ort = await tx.operationRetries.get(opId);
- transactions.push(buildTransactionForRefresh(rg, ort));
+ const ctx = new RefreshTransactionContext(wex, rg.refreshGroupId);
+ const txDetails = await ctx.lookupFullTransaction(tx);
+ if (txDetails) {
+ transactions.push(txDetails);
+ }
}
});
@@ -1232,7 +441,11 @@ export async function getTransactions(
) {
return;
}
- transactions.push(buildTransactionForDenomLoss(rec));
+ const ctx = new DenomLossTransactionContext(wex, rec.denomLossEventId);
+ const txDetails = await ctx.lookupFullTransaction(tx);
+ if (txDetails) {
+ transactions.push(txDetails);
+ }
});
await iterRecordsForDeposit(tx, filter, async (dg) => {
@@ -1245,10 +458,12 @@ export async function getTransactions(
) {
return;
}
- const opId = TaskIdentifiers.forDeposit(dg);
- const retryRecord = await tx.operationRetries.get(opId);
- transactions.push(buildTransactionForDeposit(dg, retryRecord));
+ const ctx = new DepositTransactionContext(wex, dg.depositGroupId);
+ const txDetails = await ctx.lookupFullTransaction(tx);
+ if (txDetails) {
+ transactions.push(txDetails);
+ }
});
await iterRecordsForPurchase(tx, filter, async (purchase) => {
@@ -1291,27 +506,11 @@ export async function getTransactions(
return;
}
- const contractData = extractContractData(
- contractTermsRecord?.contractTermsRaw,
- download.contractTermsHash,
- download.contractTermsMerchantSig,
- );
-
- const payOpId = TaskIdentifiers.forPay(purchase);
- const payRetryRecord = await tx.operationRetries.get(payOpId);
-
- const refunds = await tx.refundGroups.indexes.byProposalId.getAll(
- purchase.proposalId,
- );
-
- transactions.push(
- buildTransactionForPurchase(
- purchase,
- contractData,
- refunds,
- payRetryRecord,
- ),
- );
+ const ctx = new PayMerchantTransactionContext(wex, purchase.proposalId);
+ const txDetails = await ctx.lookupFullTransaction(tx);
+ if (txDetails) {
+ transactions.push(txDetails);
+ }
});
});