diff options
author | Florian Dold <florian.dold@gmail.com> | 2020-08-03 13:00:48 +0530 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2020-08-03 13:01:05 +0530 |
commit | ffd2a62c3f7df94365980302fef3bc3376b48182 (patch) | |
tree | 270af6f16b4cc7f5da2afdba55c8bc9dbea5eca5 /packages/taler-wallet-core/src/operations/transactions.ts | |
parent | aa481e42675fb7c4dcbbeec0ba1c61e1953b9596 (diff) | |
download | wallet-core-ffd2a62c3f7df94365980302fef3bc3376b48182.tar.xz |
modularize repo, use pnpm, improve typechecking
Diffstat (limited to 'packages/taler-wallet-core/src/operations/transactions.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/transactions.ts | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts new file mode 100644 index 000000000..2d66b5e9d --- /dev/null +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -0,0 +1,288 @@ +/* + 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 "./state"; +import { Stores, WithdrawalSourceType } from "../types/dbTypes"; +import { Amounts, AmountJson } from "../util/amounts"; +import { timestampCmp } from "../util/time"; +import { + TransactionsRequest, + TransactionsResponse, + Transaction, + TransactionType, + PaymentStatus, + WithdrawalType, + WithdrawalDetails, +} from "../types/transactions"; +import { getFundingPaytoUris } from "./reserves"; + +/** + * Create an event ID from the type and the primary key for the event. + */ +function makeEventId(type: TransactionType, ...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; +} + +/** + * Retrive the full event history for this wallet. + */ +export async function getTransactions( + ws: InternalWalletState, + transactionsRequest?: TransactionsRequest, +): Promise<TransactionsResponse> { + const transactions: Transaction[] = []; + + await ws.db.runWithReadTransaction( + [ + Stores.currencies, + Stores.coins, + Stores.denominations, + Stores.exchanges, + Stores.proposals, + Stores.purchases, + Stores.refreshGroups, + Stores.reserves, + Stores.reserveHistory, + Stores.tips, + Stores.withdrawalGroups, + Stores.payEvents, + Stores.planchets, + Stores.refundEvents, + Stores.reserveUpdatedEvents, + Stores.recoupGroups, + ], + // Report withdrawals that are currently in progress. + async (tx) => { + tx.iter(Stores.withdrawalGroups).forEachAsync(async (wsr) => { + if ( + shouldSkipCurrency( + transactionsRequest, + wsr.rawWithdrawalAmount.currency, + ) + ) { + return; + } + + if (shouldSkipSearch(transactionsRequest, [])) { + return; + } + + switch (wsr.source.type) { + case WithdrawalSourceType.Reserve: + { + const r = await tx.get(Stores.reserves, wsr.source.reservePub); + if (!r) { + break; + } + 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 exchange = await tx.get( + Stores.exchanges, + r.exchangeBaseUrl, + ); + if (!exchange) { + // FIXME: report somehow + break; + } + withdrawalDetails = { + type: WithdrawalType.ManualTransfer, + exchangePaytoUris: + exchange.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, + ), + }); + } + break; + default: + // Tips are reported via their own event + break; + } + }); + + // Report pending withdrawals based on reserves that + // were created, but where the actual withdrawal group has + // not started yet. + tx.iter(Stores.reserves).forEachAsync(async (r) => { + if (shouldSkipCurrency(transactionsRequest, r.currency)) { + return; + } + if (shouldSkipSearch(transactionsRequest, [])) { + return; + } + if (r.initialWithdrawalStarted) { + 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, + ), + }); + }); + + tx.iter(Stores.purchases).forEachAsync(async (pr) => { + if ( + shouldSkipCurrency( + transactionsRequest, + pr.contractData.amount.currency, + ) + ) { + return; + } + if (shouldSkipSearch(transactionsRequest, [pr.contractData.summary])) { + return; + } + const proposal = await tx.get(Stores.proposals, pr.proposalId); + if (!proposal) { + return; + } + transactions.push({ + type: TransactionType.Payment, + amountRaw: Amounts.stringify(pr.contractData.amount), + amountEffective: Amounts.stringify(pr.payCostInfo.totalCost), + status: pr.timestampFirstSuccessfulPay + ? PaymentStatus.Paid + : PaymentStatus.Accepted, + pending: !pr.timestampFirstSuccessfulPay, + timestamp: pr.timestampAccept, + transactionId: makeEventId(TransactionType.Payment, pr.proposalId), + info: { + fulfillmentUrl: pr.contractData.fulfillmentUrl, + merchant: pr.contractData.merchant, + orderId: pr.contractData.orderId, + products: pr.contractData.products, + summary: pr.contractData.summary, + summary_i18n: pr.contractData.summaryI18n, + }, + }); + + // for (const rg of pr.refundGroups) { + // const pending = Object.keys(pr.refundsPending).length > 0; + // const stats = getRefundStats(pr, rg.refundGroupId); + + // transactions.push({ + // type: TransactionType.Refund, + // pending, + // info: { + // fulfillmentUrl: pr.contractData.fulfillmentUrl, + // merchant: pr.contractData.merchant, + // orderId: pr.contractData.orderId, + // products: pr.contractData.products, + // summary: pr.contractData.summary, + // summary_i18n: pr.contractData.summaryI18n, + // }, + // timestamp: rg.timestampQueried, + // transactionId: makeEventId( + // TransactionType.Refund, + // pr.proposalId, + // `${rg.timestampQueried.t_ms}`, + // ), + // refundedTransactionId: makeEventId( + // TransactionType.Payment, + // pr.proposalId, + // ), + // amountEffective: Amounts.stringify(stats.amountEffective), + // amountInvalid: Amounts.stringify(stats.amountInvalid), + // amountRaw: Amounts.stringify(stats.amountRaw), + // }); + // } + }); + }, + ); + + 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: [...txPending, ...txNotPending] }; +} |