aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/transactions.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-08-24 22:17:19 +0200
committerFlorian Dold <florian@dold.me>2022-08-24 22:17:19 +0200
commita11ac57535b0375f152ce115ee541cb8aca98e84 (patch)
tree95294389598f6d15c0de389a8e5024dc17592b15 /packages/taler-wallet-core/src/operations/transactions.ts
parentbf516a77e8d38e81ee9816d6ee0ab29bcb878e84 (diff)
downloadwallet-core-a11ac57535b0375f152ce115ee541cb8aca98e84.tar.xz
wallet-core: p2p support for transactions list
Diffstat (limited to 'packages/taler-wallet-core/src/operations/transactions.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts530
1 files changed, 308 insertions, 222 deletions
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index ec499420f..62df996c3 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -38,6 +38,7 @@ import {
RefundState,
ReserveRecordStatus,
WalletRefundItem,
+ WithdrawalRecordType,
} from "../db.js";
import { processDepositGroup } from "./deposits.js";
import { getExchangeDetails } from "./exchanges.js";
@@ -101,10 +102,14 @@ const txOrder: { [t in TransactionType]: number } = {
[TransactionType.Withdrawal]: 1,
[TransactionType.Tip]: 2,
[TransactionType.Payment]: 3,
- [TransactionType.Refund]: 4,
- [TransactionType.Deposit]: 5,
- [TransactionType.Refresh]: 6,
- [TransactionType.Tip]: 7,
+ [TransactionType.PeerPullCredit]: 4,
+ [TransactionType.PeerPullDebit]: 5,
+ [TransactionType.PeerPushCredit]: 6,
+ [TransactionType.PeerPushDebit]: 7,
+ [TransactionType.Refund]: 8,
+ [TransactionType.Deposit]: 9,
+ [TransactionType.Refresh]: 10,
+ [TransactionType.Tip]: 11,
};
/**
@@ -131,267 +136,348 @@ export async function getTransactions(
recoupGroups: x.recoupGroups,
depositGroups: x.depositGroups,
tombstones: x.tombstones,
+ peerPushPaymentInitiations: x.peerPushPaymentInitiations,
+ peerPullPaymentIncoming: x.peerPullPaymentIncoming,
}))
- .runReadOnly(
- // Report withdrawals that are currently in progress.
- async (tx) => {
- tx.withdrawalGroups.iter().forEachAsync(async (wsr) => {
- if (
- shouldSkipCurrency(
- transactionsRequest,
- wsr.rawWithdrawalAmount.currency,
- )
- ) {
- return;
- }
+ .runReadOnly(async (tx) => {
+ tx.peerPushPaymentInitiations.iter().forEachAsync(async (pi) => {
+ const amount = Amounts.parseOrThrow(pi.amount);
+ if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
+ return;
+ }
+ if (shouldSkipSearch(transactionsRequest, [])) {
+ return;
+ }
+ transactions.push({
+ type: TransactionType.PeerPushDebit,
+ amountEffective: pi.amount,
+ amountRaw: pi.amount,
+ exchangeBaseUrl: pi.exchangeBaseUrl,
+ frozen: false,
+ pending: !pi.purseCreated,
+ timestamp: pi.timestampCreated,
+ transactionId: makeEventId(
+ TransactionType.PeerPushDebit,
+ pi.pursePub,
+ ),
+ });
+ });
- if (shouldSkipSearch(transactionsRequest, [])) {
- return;
- }
- let withdrawalDetails: WithdrawalDetails;
- if (wsr.bankInfo) {
- withdrawalDetails = {
- type: WithdrawalType.TalerBankIntegrationApi,
- confirmed: wsr.bankInfo.timestampBankConfirmed ? true : false,
- reservePub: wsr.reservePub,
- bankConfirmationUrl: wsr.bankInfo.confirmUrl,
- };
- } else {
- const exchangeDetails = await getExchangeDetails(
- tx,
- wsr.exchangeBaseUrl,
- );
- if (!exchangeDetails) {
- // FIXME: report somehow
- return;
- }
- withdrawalDetails = {
- type: WithdrawalType.ManualTransfer,
- reservePub: wsr.reservePub,
- exchangePaytoUris:
- exchangeDetails.wireInfo?.accounts.map((x) => `${x.payto_uri}?subject=${wsr.reservePub}`) ??
- [],
- };
- }
+ tx.peerPullPaymentIncoming.iter().forEachAsync(async (pi) => {
+ const amount = Amounts.parseOrThrow(pi.contractTerms.amount);
+ if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
+ return;
+ }
+ if (shouldSkipSearch(transactionsRequest, [])) {
+ return;
+ }
+ if (!pi.accepted) {
+ return;
+ }
+ transactions.push({
+ type: TransactionType.PeerPullDebit,
+ amountEffective: Amounts.stringify(amount),
+ amountRaw: Amounts.stringify(amount),
+ exchangeBaseUrl: pi.exchangeBaseUrl,
+ frozen: false,
+ pending: false,
+ timestamp: pi.timestampCreated,
+ transactionId: makeEventId(
+ TransactionType.PeerPullDebit,
+ pi.pursePub,
+ ),
+ });
+ });
+ tx.withdrawalGroups.iter().forEachAsync(async (wsr) => {
+ if (
+ shouldSkipCurrency(
+ transactionsRequest,
+ wsr.rawWithdrawalAmount.currency,
+ )
+ ) {
+ return;
+ }
+
+ if (shouldSkipSearch(transactionsRequest, [])) {
+ return;
+ }
+ let withdrawalDetails: WithdrawalDetails;
+ if (wsr.withdrawalType === WithdrawalRecordType.PeerPullCredit) {
transactions.push({
- type: TransactionType.Withdrawal,
+ type: TransactionType.PeerPullCredit,
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,
+ TransactionType.PeerPullCredit,
wsr.withdrawalGroupId,
),
frozen: false,
...(wsr.lastError ? { error: wsr.lastError } : {}),
});
- });
-
- tx.depositGroups.iter().forEachAsync(async (dg) => {
- const amount = Amounts.parseOrThrow(dg.contractTermsRaw.amount);
- if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
- return;
- }
-
+ return;
+ } else if (wsr.withdrawalType === WithdrawalRecordType.PeerPushCredit) {
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,
+ type: TransactionType.PeerPushCredit,
+ amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+ amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
+ exchangeBaseUrl: wsr.exchangeBaseUrl,
+ pending: !wsr.timestampFinish,
+ timestamp: wsr.timestampStart,
transactionId: makeEventId(
- TransactionType.Deposit,
- dg.depositGroupId,
+ TransactionType.PeerPushCredit,
+ wsr.withdrawalGroupId,
),
- depositGroupId: dg.depositGroupId,
- ...(dg.lastError ? { error: dg.lastError } : {}),
+ frozen: false,
+ ...(wsr.lastError ? { error: wsr.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;
+ } else if (wsr.bankInfo) {
+ withdrawalDetails = {
+ type: WithdrawalType.TalerBankIntegrationApi,
+ confirmed: wsr.bankInfo.timestampBankConfirmed ? true : false,
+ reservePub: wsr.reservePub,
+ bankConfirmationUrl: wsr.bankInfo.confirmUrl,
+ };
+ } else {
+ const exchangeDetails = await getExchangeDetails(
+ tx,
+ wsr.exchangeBaseUrl,
+ );
+ if (!exchangeDetails) {
+ // FIXME: report somehow
return;
}
- const info: OrderShortInfo = {
- merchant: contractData.merchant,
- orderId: contractData.orderId,
- products: contractData.products,
- summary: contractData.summary,
- summary_i18n: contractData.summaryI18n,
- contractTermsHash: contractData.contractTermsHash,
+ withdrawalDetails = {
+ type: WithdrawalType.ManualTransfer,
+ reservePub: wsr.reservePub,
+ exchangePaytoUris:
+ exchangeDetails.wireInfo?.accounts.map(
+ (x) => `${x.payto_uri}?subject=${wsr.reservePub}`,
+ ) ?? [],
};
- if (contractData.fulfillmentUrl !== "") {
- info.fulfillmentUrl = contractData.fulfillmentUrl;
+ }
+
+ 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,
+ ...(wsr.lastError ? { error: wsr.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 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(
+ TombstoneTag.DeleteRefund,
+ pr.proposalId,
+ groupKey,
+ );
+ const tombstone = await tx.tombstones.get(refundTombstoneId);
+ if (tombstone) {
+ continue;
}
- const paymentTransactionId = makeEventId(
- TransactionType.Payment,
+ const refundTransactionId = makeEventId(
+ TransactionType.Refund,
pr.proposalId,
+ groupKey,
);
- const refundGroupKeys = new Set<string>();
-
+ 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 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(
- TombstoneTag.DeleteRefund,
- pr.proposalId,
- groupKey,
- );
- const tombstone = await tx.tombstones.get(refundTombstoneId);
- if (tombstone) {
+ const myGroupKey = `${refund.executionTime.t_s}`;
+ if (myGroupKey !== groupKey) {
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_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");
+ r0 = refund;
}
- 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,
- });
+ 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");
}
- const err = pr.lastPayError ?? pr.lastRefundStatusError;
+ totalRefundRaw = Amounts.add(totalRefundRaw, amountRaw).amount;
+ totalRefundEffective = Amounts.add(
+ totalRefundEffective,
+ amountEffective,
+ ).amount;
transactions.push({
- type: TransactionType.Payment,
- amountRaw: Amounts.stringify(contractData.amount),
- amountEffective: Amounts.stringify(pr.totalPayCost),
- totalRefundRaw: Amounts.stringify(totalRefundRaw),
- totalRefundEffective: Amounts.stringify(totalRefundEffective),
+ 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),
- 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 } : {}),
+ pending: false,
+ frozen: false,
});
+ }
+
+ const err = pr.lastPayError ?? pr.lastRefundStatusError;
+ 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 } : {}),
});
+ });
- 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,
- // merchant: {
- // name: tipRecord.merchantBaseUrl,
- // },
- error: tipRecord.lastError,
- });
+ 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,
+ // merchant: {
+ // name: tipRecord.merchantBaseUrl,
+ // },
+ error: tipRecord.lastError,
});
- },
- );
+ });
+ });
const txPending = transactions.filter((x) => x.pending);
const txNotPending = transactions.filter((x) => !x.pending);