summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/transactions.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/operations/transactions.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts597
1 files changed, 0 insertions, 597 deletions
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
deleted file mode 100644
index dc738b77f..000000000
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ /dev/null
@@ -1,597 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Imports.
- */
-import { InternalWalletState } from "../common.js";
-import {
- WalletRefundItem,
- RefundState,
- ReserveRecordStatus,
- AbortStatus,
- ReserveRecord,
-} from "../db.js";
-import { AmountJson, Amounts, timestampCmp } from "@gnu-taler/taler-util";
-import {
- TransactionsRequest,
- TransactionsResponse,
- Transaction,
- TransactionType,
- PaymentStatus,
- WithdrawalType,
- WithdrawalDetails,
- OrderShortInfo,
-} from "@gnu-taler/taler-util";
-import { getFundingPaytoUris } from "./reserves.js";
-import { getExchangeDetails } from "./exchanges.js";
-import { processWithdrawGroup } from "./withdraw.js";
-import { processPurchasePay } from "./pay.js";
-import { processDepositGroup } from "./deposits.js";
-import { processTip } from "./tip.js";
-import { processRefreshGroup } from "./refresh.js";
-
-/**
- * Create an event ID from the type and the primary key for the event.
- */
-export function makeEventId(
- type: TransactionType | TombstoneTag,
- ...args: string[]
-): string {
- return type + ":" + args.map((x) => encodeURIComponent(x)).join(":");
-}
-
-function shouldSkipCurrency(
- transactionsRequest: TransactionsRequest | undefined,
- currency: string,
-): boolean {
- if (!transactionsRequest?.currency) {
- return false;
- }
- return transactionsRequest.currency.toLowerCase() !== currency.toLowerCase();
-}
-
-function shouldSkipSearch(
- transactionsRequest: TransactionsRequest | undefined,
- fields: string[],
-): boolean {
- if (!transactionsRequest?.search) {
- return false;
- }
- const needle = transactionsRequest.search.trim();
- for (const f of fields) {
- if (f.indexOf(needle) >= 0) {
- return false;
- }
- }
- return true;
-}
-
-/**
- * Retrieve the full event history for this wallet.
- */
-export async function getTransactions(
- ws: InternalWalletState,
- transactionsRequest?: TransactionsRequest,
-): Promise<TransactionsResponse> {
- const transactions: Transaction[] = [];
-
- await ws.db
- .mktx((x) => ({
- coins: x.coins,
- denominations: x.denominations,
- exchanges: x.exchanges,
- exchangeDetails: x.exchangeDetails,
- proposals: x.proposals,
- purchases: x.purchases,
- refreshGroups: x.refreshGroups,
- reserves: x.reserves,
- tips: x.tips,
- withdrawalGroups: x.withdrawalGroups,
- planchets: x.planchets,
- recoupGroups: x.recoupGroups,
- depositGroups: x.depositGroups,
- tombstones: x.tombstones,
- }))
- .runReadOnly(
- // Report withdrawals that are currently in progress.
- async (tx) => {
- tx.withdrawalGroups.iter().forEachAsync(async (wsr) => {
- if (
- shouldSkipCurrency(
- transactionsRequest,
- wsr.rawWithdrawalAmount.currency,
- )
- ) {
- return;
- }
-
- if (shouldSkipSearch(transactionsRequest, [])) {
- return;
- }
-
- const r = await tx.reserves.get(wsr.reservePub);
- if (!r) {
- return;
- }
- let amountRaw: AmountJson | undefined = undefined;
- if (wsr.withdrawalGroupId === r.initialWithdrawalGroupId) {
- amountRaw = r.instructedAmount;
- } else {
- amountRaw = wsr.denomsSel.totalWithdrawCost;
- }
- let withdrawalDetails: WithdrawalDetails;
- if (r.bankInfo) {
- withdrawalDetails = {
- type: WithdrawalType.TalerBankIntegrationApi,
- confirmed: true,
- bankConfirmationUrl: r.bankInfo.confirmUrl,
- };
- } else {
- const exchangeDetails = await getExchangeDetails(
- tx,
- wsr.exchangeBaseUrl,
- );
- if (!exchangeDetails) {
- // FIXME: report somehow
- return;
- }
- withdrawalDetails = {
- type: WithdrawalType.ManualTransfer,
- exchangePaytoUris:
- exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ??
- [],
- };
- }
- transactions.push({
- type: TransactionType.Withdrawal,
- amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(amountRaw),
- withdrawalDetails,
- exchangeBaseUrl: wsr.exchangeBaseUrl,
- pending: !wsr.timestampFinish,
- timestamp: wsr.timestampStart,
- transactionId: makeEventId(
- TransactionType.Withdrawal,
- wsr.withdrawalGroupId,
- ),
- frozen: false,
- ...(wsr.lastError ? { error: wsr.lastError } : {}),
- });
- });
-
- // Report pending withdrawals based on reserves that
- // were created, but where the actual withdrawal group has
- // not started yet.
- tx.reserves.iter().forEachAsync(async (r) => {
- if (shouldSkipCurrency(transactionsRequest, r.currency)) {
- return;
- }
- if (shouldSkipSearch(transactionsRequest, [])) {
- return;
- }
- if (r.initialWithdrawalStarted) {
- return;
- }
- if (r.reserveStatus === ReserveRecordStatus.BANK_ABORTED) {
- return;
- }
- let withdrawalDetails: WithdrawalDetails;
- if (r.bankInfo) {
- withdrawalDetails = {
- type: WithdrawalType.TalerBankIntegrationApi,
- confirmed: false,
- bankConfirmationUrl: r.bankInfo.confirmUrl,
- };
- } else {
- withdrawalDetails = {
- type: WithdrawalType.ManualTransfer,
- exchangePaytoUris: await getFundingPaytoUris(tx, r.reservePub),
- };
- }
- transactions.push({
- type: TransactionType.Withdrawal,
- amountRaw: Amounts.stringify(r.instructedAmount),
- amountEffective: Amounts.stringify(
- r.initialDenomSel.totalCoinValue,
- ),
- exchangeBaseUrl: r.exchangeBaseUrl,
- pending: true,
- timestamp: r.timestampCreated,
- withdrawalDetails: withdrawalDetails,
- transactionId: makeEventId(
- TransactionType.Withdrawal,
- r.initialWithdrawalGroupId,
- ),
- frozen: false,
- ...(r.lastError ? { error: r.lastError } : {}),
- });
- });
-
- tx.depositGroups.iter().forEachAsync(async (dg) => {
- const amount = Amounts.parseOrThrow(dg.contractTermsRaw.amount);
- if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
- return;
- }
-
- 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,
- ...(dg.lastError ? { error: dg.lastError } : {}),
- });
- });
-
- tx.purchases.iter().forEachAsync(async (pr) => {
- if (
- shouldSkipCurrency(
- transactionsRequest,
- pr.download.contractData.amount.currency,
- )
- ) {
- return;
- }
- const contractData = pr.download.contractData;
- if (shouldSkipSearch(transactionsRequest, [contractData.summary])) {
- return;
- }
- const proposal = await tx.proposals.get(pr.proposalId);
- 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 err = pr.lastPayError ?? pr.lastRefundStatusError;
- transactions.push({
- type: TransactionType.Payment,
- amountRaw: Amounts.stringify(contractData.amount),
- amountEffective: Amounts.stringify(pr.totalPayCost),
- status: pr.timestampFirstSuccessfulPay
- ? PaymentStatus.Paid
- : PaymentStatus.Accepted,
- pending:
- !pr.timestampFirstSuccessfulPay &&
- pr.abortStatus === AbortStatus.None,
- timestamp: pr.timestampAccept,
- transactionId: paymentTransactionId,
- proposalId: pr.proposalId,
- info: info,
- frozen: pr.payFrozen ?? false,
- ...(err ? { error: err } : {}),
- });
-
- const refundGroupKeys = new Set<string>();
-
- for (const rk of Object.keys(pr.refunds)) {
- const refund = pr.refunds[rk];
- const groupKey = `${refund.executionTime.t_ms}`;
- refundGroupKeys.add(groupKey);
- }
-
- for (const groupKey of refundGroupKeys.values()) {
- const refundTombstoneId = makeEventId(
- TombstoneTag.DeleteRefund,
- pr.proposalId,
- groupKey,
- );
- const tombstone = await tx.tombstones.get(refundTombstoneId);
- if (tombstone) {
- continue;
- }
- const refundTransactionId = makeEventId(
- TransactionType.Refund,
- pr.proposalId,
- groupKey,
- );
- 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_ms}`;
- 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;
- }
- }
- if (!r0) {
- throw Error("invariant violated");
- }
- transactions.push({
- type: TransactionType.Refund,
- info,
- refundedTransactionId: paymentTransactionId,
- transactionId: refundTransactionId,
- timestamp: r0.obtainedTime,
- amountEffective: Amounts.stringify(amountEffective),
- amountRaw: Amounts.stringify(amountRaw),
- pending: false,
- frozen: false,
- });
- }
- });
-
- tx.tips.iter().forEachAsync(async (tipRecord) => {
- if (
- shouldSkipCurrency(
- transactionsRequest,
- tipRecord.tipAmountRaw.currency,
- )
- ) {
- return;
- }
- if (!tipRecord.acceptedTimestamp) {
- return;
- }
- 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: tipRecord.lastError,
- });
- });
- },
- );
-
- const txPending = transactions.filter((x) => x.pending);
- const txNotPending = transactions.filter((x) => !x.pending);
-
- txPending.sort((h1, h2) => timestampCmp(h1.timestamp, h2.timestamp));
- txNotPending.sort((h1, h2) => timestampCmp(h1.timestamp, h2.timestamp));
-
- return { transactions: [...txNotPending, ...txPending] };
-}
-
-export enum TombstoneTag {
- DeleteWithdrawalGroup = "delete-withdrawal-group",
- DeleteReserve = "delete-reserve",
- DeletePayment = "delete-payment",
- DeleteTip = "delete-tip",
- DeleteRefreshGroup = "delete-refresh-group",
- DeleteDepositGroup = "delete-deposit-group",
- DeleteRefund = "delete-refund",
-}
-
-export async function retryTransactionNow(
- ws: InternalWalletState,
- transactionId: string,
-): Promise<void> {
- const [type, ...rest] = transactionId.split(":");
-}
-
-/**
- * Immediately retry the underlying operation
- * of a transaction.
- */
-export async function retryTransaction(
- ws: InternalWalletState,
- transactionId: string,
-): Promise<void> {
- const [type, ...rest] = transactionId.split(":");
-
- switch (type) {
- case TransactionType.Deposit:
- const depositGroupId = rest[0];
- processDepositGroup(ws, depositGroupId, true);
- break;
- case TransactionType.Withdrawal:
- const withdrawalGroupId = rest[0];
- await processWithdrawGroup(ws, withdrawalGroupId, true);
- break;
- case TransactionType.Payment:
- const proposalId = rest[0];
- await processPurchasePay(ws, proposalId, true);
- break;
- case TransactionType.Tip:
- const walletTipId = rest[0];
- await processTip(ws, walletTipId, true);
- break;
- case TransactionType.Refresh:
- const refreshGroupId = rest[0];
- await processRefreshGroup(ws, refreshGroupId, true);
- break;
- default:
- break;
- }
-}
-
-/**
- * Permanently delete a transaction based on the transaction ID.
- */
-export async function deleteTransaction(
- ws: InternalWalletState,
- transactionId: string,
-): Promise<void> {
- const [type, ...rest] = transactionId.split(":");
-
- if (type === TransactionType.Withdrawal) {
- const withdrawalGroupId = rest[0];
- await ws.db
- .mktx((x) => ({
- withdrawalGroups: x.withdrawalGroups,
- reserves: x.reserves,
- tombstones: x.tombstones,
- }))
- .runReadWrite(async (tx) => {
- const withdrawalGroupRecord = await tx.withdrawalGroups.get(
- withdrawalGroupId,
- );
- if (withdrawalGroupRecord) {
- await tx.withdrawalGroups.delete(withdrawalGroupId);
- await tx.tombstones.put({
- id: TombstoneTag.DeleteWithdrawalGroup + ":" + withdrawalGroupId,
- });
- return;
- }
- const reserveRecord:
- | ReserveRecord
- | undefined = await tx.reserves.indexes.byInitialWithdrawalGroupId.get(
- withdrawalGroupId,
- );
- if (reserveRecord && !reserveRecord.initialWithdrawalStarted) {
- const reservePub = reserveRecord.reservePub;
- await tx.reserves.delete(reservePub);
- await tx.tombstones.put({
- id: TombstoneTag.DeleteReserve + ":" + reservePub,
- });
- }
- });
- } else if (type === TransactionType.Payment) {
- const proposalId = rest[0];
- await ws.db
- .mktx((x) => ({
- proposals: x.proposals,
- purchases: x.purchases,
- tombstones: x.tombstones,
- }))
- .runReadWrite(async (tx) => {
- let found = false;
- const proposal = await tx.proposals.get(proposalId);
- if (proposal) {
- found = true;
- await tx.proposals.delete(proposalId);
- }
- const purchase = await tx.purchases.get(proposalId);
- if (purchase) {
- found = true;
- await tx.purchases.delete(proposalId);
- }
- if (found) {
- await tx.tombstones.put({
- id: TombstoneTag.DeletePayment + ":" + proposalId,
- });
- }
- });
- } else if (type === TransactionType.Refresh) {
- const refreshGroupId = rest[0];
- await ws.db
- .mktx((x) => ({
- refreshGroups: x.refreshGroups,
- tombstones: x.tombstones,
- }))
- .runReadWrite(async (tx) => {
- const rg = await tx.refreshGroups.get(refreshGroupId);
- if (rg) {
- await tx.refreshGroups.delete(refreshGroupId);
- await tx.tombstones.put({
- id: TombstoneTag.DeleteRefreshGroup + ":" + refreshGroupId,
- });
- }
- });
- } else if (type === TransactionType.Tip) {
- const tipId = rest[0];
- await ws.db
- .mktx((x) => ({
- tips: x.tips,
- tombstones: x.tombstones,
- }))
- .runReadWrite(async (tx) => {
- const tipRecord = await tx.tips.get(tipId);
- if (tipRecord) {
- await tx.tips.delete(tipId);
- await tx.tombstones.put({
- id: TombstoneTag.DeleteTip + ":" + tipId,
- });
- }
- });
- } else if (type === TransactionType.Deposit) {
- const depositGroupId = rest[0];
- await ws.db
- .mktx((x) => ({
- depositGroups: x.depositGroups,
- tombstones: x.tombstones,
- }))
- .runReadWrite(async (tx) => {
- const tipRecord = await tx.depositGroups.get(depositGroupId);
- if (tipRecord) {
- await tx.depositGroups.delete(depositGroupId);
- await tx.tombstones.put({
- id: TombstoneTag.DeleteDepositGroup + ":" + depositGroupId,
- });
- }
- });
- } else if (type === TransactionType.Refund) {
- const proposalId = rest[0];
- const executionTimeStr = rest[1];
-
- await ws.db
- .mktx((x) => ({
- proposals: x.proposals,
- purchases: x.purchases,
- tombstones: x.tombstones,
- }))
- .runReadWrite(async (tx) => {
- const purchase = await tx.purchases.get(proposalId);
- if (purchase) {
- // This should just influence the history view,
- // but won't delete any actual refund information.
- await tx.tombstones.put({
- id: makeEventId(
- TombstoneTag.DeleteRefund,
- proposalId,
- executionTimeStr,
- ),
- });
- }
- });
- } else {
- throw Error(`can't delete a '${type}' transaction`);
- }
-}