aboutsummaryrefslogtreecommitdiff
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
parentbf516a77e8d38e81ee9816d6ee0ab29bcb878e84 (diff)
wallet-core: p2p support for transactions list
-rw-r--r--packages/taler-util/src/transactionsTypes.ts88
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts44
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-push.ts13
-rw-r--r--packages/taler-wallet-core/src/db.ts30
-rw-r--r--packages/taler-wallet-core/src/operations/peer-to-peer.ts87
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts530
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts5
7 files changed, 524 insertions, 273 deletions
diff --git a/packages/taler-util/src/transactionsTypes.ts b/packages/taler-util/src/transactionsTypes.ts
index 645f0120b..c4bd3d464 100644
--- a/packages/taler-util/src/transactionsTypes.ts
+++ b/packages/taler-util/src/transactionsTypes.ts
@@ -102,7 +102,11 @@ export type Transaction =
| TransactionRefund
| TransactionTip
| TransactionRefresh
- | TransactionDeposit;
+ | TransactionDeposit
+ | TransactionPeerPullCredit
+ | TransactionPeerPullDebit
+ | TransactionPeerPushCredit
+ | TransactionPeerPushDebit;
export enum TransactionType {
Withdrawal = "withdrawal",
@@ -111,6 +115,10 @@ export enum TransactionType {
Refresh = "refresh",
Tip = "tip",
Deposit = "deposit",
+ PeerPushDebit = "peer-push-debit",
+ PeerPushCredit = "peer-push-credit",
+ PeerPullDebit = "peer-pull-debit",
+ PeerPullCredit = "peer-pull-credit",
}
export enum WithdrawalType {
@@ -179,6 +187,76 @@ export interface TransactionWithdrawal extends TransactionCommon {
withdrawalDetails: WithdrawalDetails;
}
+export interface TransactionPeerPullCredit extends TransactionCommon {
+ type: TransactionType.PeerPullCredit;
+
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+}
+
+export interface TransactionPeerPullDebit extends TransactionCommon {
+ type: TransactionType.PeerPullDebit;
+
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+
+ amountRaw: AmountString;
+
+ amountEffective: AmountString;
+}
+
+export interface TransactionPeerPushDebit extends TransactionCommon {
+ type: TransactionType.PeerPushDebit;
+
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+}
+
+export interface TransactionPeerPushCredit extends TransactionCommon {
+ type: TransactionType.PeerPushCredit;
+
+ /**
+ * Exchange used.
+ */
+ exchangeBaseUrl: string;
+
+ /**
+ * Amount that got subtracted from the reserve balance.
+ */
+ amountRaw: AmountString;
+
+ /**
+ * Amount that actually was (or will be) added to the wallet's balance.
+ */
+ amountEffective: AmountString;
+}
+
export enum PaymentStatus {
/**
* Explicitly aborted after timeout / failure
@@ -311,10 +389,10 @@ export interface OrderShortInfo {
}
export interface RefundInfoShort {
- transactionId: string,
- timestamp: TalerProtocolTimestamp,
- amountEffective: AmountString,
- amountRaw: AmountString,
+ transactionId: string;
+ timestamp: TalerProtocolTimestamp;
+ amountEffective: AmountString;
+ amountRaw: AmountString;
}
export interface TransactionRefund extends TransactionCommon {
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts
index 1be1563ce..0c149d63a 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-pull.ts
@@ -19,7 +19,7 @@
*/
import { j2s } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { GlobalTestState } from "../harness/harness.js";
+import { GlobalTestState, WalletCli } from "../harness/harness.js";
import {
createSimpleTestkudosEnvironment,
withdrawViaBank,
@@ -31,16 +31,23 @@ import {
export async function runPeerToPeerPullTest(t: GlobalTestState) {
// Set up test environment
- const { wallet, bank, exchange, merchant } =
- await createSimpleTestkudosEnvironment(t);
+ const { bank, exchange, merchant } = await createSimpleTestkudosEnvironment(
+ t,
+ );
// Withdraw digital cash into the wallet.
-
- await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
-
- await wallet.runUntilDone();
-
- const resp = await wallet.client.call(
+ const wallet1 = new WalletCli(t, "w1");
+ const wallet2 = new WalletCli(t, "w2");
+ await withdrawViaBank(t, {
+ wallet: wallet2,
+ bank,
+ exchange,
+ amount: "TESTKUDOS:20",
+ });
+
+ await wallet1.runUntilDone();
+
+ const resp = await wallet1.client.call(
WalletApiOperation.InitiatePeerPullPayment,
{
exchangeBaseUrl: exchange.baseUrl,
@@ -51,7 +58,7 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) {
},
);
- const checkResp = await wallet.client.call(
+ const checkResp = await wallet2.client.call(
WalletApiOperation.CheckPeerPullPayment,
{
talerUri: resp.talerUri,
@@ -60,18 +67,27 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) {
console.log(`checkResp: ${j2s(checkResp)}`);
- const acceptResp = await wallet.client.call(
+ const acceptResp = await wallet2.client.call(
WalletApiOperation.AcceptPeerPullPayment,
{
peerPullPaymentIncomingId: checkResp.peerPullPaymentIncomingId,
},
);
- const txs = await wallet.client.call(WalletApiOperation.GetTransactions, {});
+ await wallet1.runUntilDone();
+ await wallet2.runUntilDone();
- console.log(`transactions: ${j2s(txs)}`);
+ const txn1 = await wallet1.client.call(
+ WalletApiOperation.GetTransactions,
+ {},
+ );
+ const txn2 = await wallet2.client.call(
+ WalletApiOperation.GetTransactions,
+ {},
+ );
- await wallet.runUntilDone();
+ console.log(`txn1: ${j2s(txn1)}`);
+ console.log(`txn2: ${j2s(txn2)}`);
}
runPeerToPeerPullTest.suites = ["wallet"];
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-push.ts b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-push.ts
index bf65731d2..ebbe87ae8 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-push.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer-push.ts
@@ -17,6 +17,7 @@
/**
* Imports.
*/
+import { j2s } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { GlobalTestState, WalletCli } from "../harness/harness.js";
import {
@@ -78,6 +79,18 @@ export async function runPeerToPeerPushTest(t: GlobalTestState) {
await wallet1.runUntilDone();
await wallet2.runUntilDone();
+
+ const txn1 = await wallet1.client.call(
+ WalletApiOperation.GetTransactions,
+ {},
+ );
+ const txn2 = await wallet2.client.call(
+ WalletApiOperation.GetTransactions,
+ {},
+ );
+
+ console.log(`txn1: ${j2s(txn1)}`);
+ console.log(`txn2: ${j2s(txn2)}`);
}
runPeerToPeerPushTest.suites = ["wallet"];
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 3d59ce0a7..e6b4854db 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1219,6 +1219,13 @@ export interface DenomSelectionState {
}[];
}
+export const enum WithdrawalRecordType {
+ BankManual = "bank-manual",
+ BankIntegrated = "bank-integrated",
+ PeerPullCredit = "peer-pull-credit",
+ PeerPushCredit = "peer-push-credit",
+}
+
/**
* Group of withdrawal operations that need to be executed.
* (Either for a normal withdrawal or from a tip.)
@@ -1232,6 +1239,8 @@ export interface WithdrawalGroupRecord {
*/
withdrawalGroupId: string;
+ withdrawalType: WithdrawalRecordType;
+
/**
* Secret seed used to derive planchets.
* Stored since planchets are created lazily.
@@ -1607,8 +1616,6 @@ export interface PeerPushPaymentInitiationRecord {
contractPriv: string;
- contractPub: string;
-
purseExpiration: TalerProtocolTimestamp;
/**
@@ -1681,7 +1688,11 @@ export interface PeerPullPaymentIncomingRecord {
contractTerms: PeerContractTerms;
- timestamp: TalerProtocolTimestamp;
+ timestampCreated: TalerProtocolTimestamp;
+
+ paid: boolean;
+
+ accepted: boolean;
contractPriv: string;
}
@@ -1878,9 +1889,18 @@ export const WalletStoresV1 = {
]),
},
),
- peerPullPaymentInitiation: describeStore(
+ peerPullPaymentInitiations: describeStore(
describeContents<PeerPullPaymentInitiationRecord>(
- "peerPushPaymentInitiation",
+ "peerPullPaymentInitiations",
+ {
+ keyPath: "pursePub",
+ },
+ ),
+ {},
+ ),
+ peerPushPaymentInitiations: describeStore(
+ describeContents<PeerPushPaymentInitiationRecord>(
+ "peerPushPaymentInitiations",
{
keyPath: "pursePub",
},
diff --git a/packages/taler-wallet-core/src/operations/peer-to-peer.ts b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
index ddfaa0827..d6d71720c 100644
--- a/packages/taler-wallet-core/src/operations/peer-to-peer.ts
+++ b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
@@ -65,6 +65,7 @@ import {
MergeReserveInfo,
ReserveRecordStatus,
WalletStoresV1,
+ WithdrawalRecordType,
} from "../db.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
import { InternalWalletState } from "../internal-wallet-state.js";
@@ -208,12 +209,39 @@ export async function initiatePeerToPeerPush(
): Promise<InitiatePeerPushPaymentResponse> {
// FIXME: actually create a record for retries here!
const instructedAmount = Amounts.parseOrThrow(req.amount);
+
+ const pursePair = await ws.cryptoApi.createEddsaKeypair({});
+ const mergePair = await ws.cryptoApi.createEddsaKeypair({});
+
+ const purseExpiration: TalerProtocolTimestamp = AbsoluteTime.toTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ days: 2 }),
+ ),
+ );
+
+ const contractTerms = {
+ ...req.partialContractTerms,
+ purse_expiration: purseExpiration,
+ amount: req.amount,
+ };
+
+ const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
+
+ const econtractResp = await ws.cryptoApi.encryptContractForMerge({
+ contractTerms,
+ mergePriv: mergePair.priv,
+ pursePriv: pursePair.priv,
+ pursePub: pursePair.pub,
+ });
+
const coinSelRes: PeerCoinSelection | undefined = await ws.db
.mktx((x) => ({
exchanges: x.exchanges,
coins: x.coins,
denominations: x.denominations,
refreshGroups: x.refreshGroups,
+ peerPushPaymentInitiations: x.peerPushPaymentInitiations,
}))
.runReadWrite(async (tx) => {
const sel = await selectPeerCoins(ws, tx, instructedAmount);
@@ -232,6 +260,20 @@ export async function initiatePeerToPeerPush(
await tx.coins.put(coin);
}
+ await tx.peerPushPaymentInitiations.add({
+ amount: Amounts.stringify(instructedAmount),
+ contractPriv: econtractResp.contractPriv,
+ exchangeBaseUrl: sel.exchangeBaseUrl,
+ mergePriv: mergePair.priv,
+ mergePub: mergePair.pub,
+ // FIXME: only set this later!
+ purseCreated: true,
+ purseExpiration: purseExpiration,
+ pursePriv: pursePair.priv,
+ pursePub: pursePair.pub,
+ timestampCreated: TalerProtocolTimestamp.now(),
+ });
+
await createRefreshGroup(ws, tx, pubs, RefreshReason.Pay);
return sel;
@@ -242,24 +284,6 @@ export async function initiatePeerToPeerPush(
throw Error("insufficient balance");
}
- const pursePair = await ws.cryptoApi.createEddsaKeypair({});
- const mergePair = await ws.cryptoApi.createEddsaKeypair({});
-
- const purseExpiration: TalerProtocolTimestamp = AbsoluteTime.toTimestamp(
- AbsoluteTime.addDuration(
- AbsoluteTime.now(),
- Duration.fromSpec({ days: 2 }),
- ),
- );
-
- const contractTerms = {
- ...req.partialContractTerms,
- purse_expiration: purseExpiration,
- amount: req.amount,
- };
-
- const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
-
const purseSigResp = await ws.cryptoApi.signPurseCreation({
hContractTerms,
mergePub: mergePair.pub,
@@ -280,13 +304,6 @@ export async function initiatePeerToPeerPush(
coinSelRes.exchangeBaseUrl,
);
- const econtractResp = await ws.cryptoApi.encryptContractForMerge({
- contractTerms,
- mergePriv: mergePair.priv,
- pursePriv: pursePair.priv,
- pursePub: pursePair.pub,
- });
-
const httpResp = await ws.http.postJson(createPurseUrl.href, {
amount: Amounts.stringify(instructedAmount),
merge_pub: mergePair.pub,
@@ -517,6 +534,7 @@ export async function acceptPeerPushPayment(
await internalCreateWithdrawalGroup(ws, {
amount,
+ withdrawalType: WithdrawalRecordType.PeerPushCredit,
exchangeBaseUrl: peerInc.exchangeBaseUrl,
reserveStatus: ReserveRecordStatus.QueryingStatus,
reserveKeyPair: {
@@ -554,6 +572,7 @@ export async function acceptPeerPullPayment(
coins: x.coins,
denominations: x.denominations,
refreshGroups: x.refreshGroups,
+ peerPullPaymentIncoming: x.peerPullPaymentIncoming,
}))
.runReadWrite(async (tx) => {
const sel = await selectPeerCoins(ws, tx, instructedAmount);
@@ -574,6 +593,15 @@ export async function acceptPeerPullPayment(
await createRefreshGroup(ws, tx, pubs, RefreshReason.Pay);
+ const pi = await tx.peerPullPaymentIncoming.get(
+ req.peerPullPaymentIncomingId,
+ );
+ if (!pi) {
+ throw Error();
+ }
+ pi.accepted = true;
+ await tx.peerPullPaymentIncoming.put(pi);
+
return sel;
});
logger.info(`selected p2p coins: ${j2s(coinSelRes)}`);
@@ -656,8 +684,10 @@ export async function checkPeerPullPayment(
contractPriv: contractPriv,
exchangeBaseUrl: exchangeBaseUrl,
pursePub: pursePub,
- timestamp: TalerProtocolTimestamp.now(),
+ timestampCreated: TalerProtocolTimestamp.now(),
contractTerms: dec.contractTerms,
+ paid: false,
+ accepted: false,
});
});
@@ -672,6 +702,8 @@ export async function initiatePeerRequestForPay(
ws: InternalWalletState,
req: InitiatePeerPullPaymentRequest,
): Promise<InitiatePeerPullPaymentResponse> {
+ await updateExchangeFromUrl(ws, req.exchangeBaseUrl);
+
const mergeReserveInfo = await getMergeReserveInfo(ws, {
exchangeBaseUrl: req.exchangeBaseUrl,
});
@@ -727,7 +759,7 @@ export async function initiatePeerRequestForPay(
await ws.db
.mktx((x) => ({
- peerPullPaymentInitiation: x.peerPullPaymentInitiation,
+ peerPullPaymentInitiation: x.peerPullPaymentInitiations,
}))
.runReadWrite(async (tx) => {
await tx.peerPullPaymentInitiation.put({
@@ -772,6 +804,7 @@ export async function initiatePeerRequestForPay(
await internalCreateWithdrawalGroup(ws, {
amount: Amounts.parseOrThrow(req.amount),
+ withdrawalType: WithdrawalRecordType.PeerPullCredit,
exchangeBaseUrl: req.exchangeBaseUrl,
reserveStatus: ReserveRecordStatus.QueryingStatus,
reserveKeyPair: {
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);
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index 3c4e2d98c..4e350670d 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -74,6 +74,7 @@ import {
ReserveRecordStatus,
WalletStoresV1,
WithdrawalGroupRecord,
+ WithdrawalRecordType,
} from "../db.js";
import {
getErrorDetailFromException,
@@ -1700,6 +1701,7 @@ export async function internalCreateWithdrawalGroup(
forcedDenomSel?: ForcedDenomSel;
reserveKeyPair?: EddsaKeypair;
restrictAge?: number;
+ withdrawalType: WithdrawalRecordType;
},
): Promise<WithdrawalGroupRecord> {
const reserveKeyPair =
@@ -1745,6 +1747,7 @@ export async function internalCreateWithdrawalGroup(
restrictAge: args.restrictAge,
senderWire: undefined,
timestampFinish: undefined,
+ withdrawalType: args.withdrawalType,
};
const exchangeInfo = await updateExchangeFromUrl(ws, canonExchange);
@@ -1819,6 +1822,7 @@ export async function acceptWithdrawalFromUri(
const withdrawalGroup = await internalCreateWithdrawalGroup(ws, {
amount: withdrawInfo.amount,
exchangeBaseUrl: req.selectedExchange,
+ withdrawalType: WithdrawalRecordType.BankIntegrated,
forcedDenomSel: req.forcedDenomSel,
reserveStatus: ReserveRecordStatus.RegisteringBank,
bankInfo: {
@@ -1877,6 +1881,7 @@ export async function createManualWithdrawal(
): Promise<AcceptManualWithdrawalResult> {
const withdrawalGroup = await internalCreateWithdrawalGroup(ws, {
amount: Amounts.jsonifyAmount(req.amount),
+ withdrawalType: WithdrawalRecordType.BankManual,
exchangeBaseUrl: req.exchangeBaseUrl,
bankInfo: undefined,
forcedDenomSel: req.forcedDenomSel,