aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-02-20 03:22:43 +0100
committerFlorian Dold <florian@dold.me>2023-02-20 03:22:43 +0100
commitd4fda1eea86ef901d125078f1f4fe0fe4a141afb (patch)
tree58c5ee16db7199eed263e0416ad4fa6ffa8a5765
parentc8b93a37ba78ffe24d9cc0548a1f8a0b3f1fb7de (diff)
wallet-core: raw/effective amount for push transactions, fix transactions list for push/pull credit
-rw-r--r--packages/taler-util/src/wallet-types.ts7
-rw-r--r--packages/taler-wallet-core/src/db.ts29
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer.ts24
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts280
-rw-r--r--packages/taler-wallet-core/src/util/retries.ts4
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts4
6 files changed, 265 insertions, 83 deletions
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index e89d143c8..aff83da14 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -2084,9 +2084,14 @@ export interface PreparePeerPullDebitRequest {
talerUri: string;
}
-export interface CheckPeerPushPaymentResponse {
+export interface PreparePeerPushCreditResponse {
contractTerms: PeerContractTerms;
+ /**
+ * @deprecated
+ */
amount: AmountString;
+ amountRaw: AmountString;
+ amountEffective: AmountString;
peerPushPaymentIncomingId: string;
}
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 5f7a6a4c4..7e1906351 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1850,6 +1850,8 @@ export interface PeerPushPaymentIncomingRecord {
timestamp: TalerProtocolTimestamp;
+ estimatedAmountEffective: AmountString;
+
/**
* Hash of the contract terms. Also
* used to look up the contract terms in the DB.
@@ -1865,6 +1867,14 @@ export interface PeerPushPaymentIncomingRecord {
* Associated withdrawal group.
*/
withdrawalGroupId: string | undefined;
+
+ /**
+ * Currency of the peer push payment credit transaction.
+ *
+ * Mandatory in current schema version, optional for compatibility
+ * with older (ver_minor<4) DB versions.
+ */
+ currency: string | undefined;
}
export enum PeerPullPaymentIncomingStatus {
@@ -2567,6 +2577,25 @@ export const walletDbFixups: FixupDescription[] = [
});
},
},
+ {
+ name: "PeerPushPaymentIncomingRecord_totalCostEstimated_add",
+ async fn(tx): Promise<void> {
+ await tx.peerPushPaymentIncoming.iter().forEachAsync(async (pi) => {
+ if (pi.estimatedAmountEffective) {
+ return;
+ }
+ const contractTerms = await tx.contractTerms.get(pi.contractTermsHash);
+ if (!contractTerms) {
+ // Not sure what we can do here!
+ } else {
+ // Not really the cost, but a good substitute for older transactions
+ // that don't sture the effective cost of the transaction.
+ pi.estimatedAmountEffective = contractTerms.contractTermsRaw.amount;
+ await tx.peerPushPaymentIncoming.put(pi);
+ }
+ });
+ },
+ },
];
const logger = new Logger("db.ts");
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts
index ef2c19c33..6e5f1b89b 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer.ts
@@ -31,7 +31,7 @@ import {
PreparePeerPullDebitRequest,
PreparePeerPullDebitResponse,
PreparePeerPushCredit,
- CheckPeerPushPaymentResponse,
+ PreparePeerPushCreditResponse,
Codec,
codecForAmountString,
codecForAny,
@@ -100,7 +100,10 @@ import {
import { getPeerPaymentBalanceDetailsInTx } from "./balance.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import { getTotalRefreshCost } from "./refresh.js";
-import { internalCreateWithdrawalGroup } from "./withdraw.js";
+import {
+ getExchangeWithdrawalInfo,
+ internalCreateWithdrawalGroup,
+} from "./withdraw.js";
const logger = new Logger("operations/peer-to-peer.ts");
@@ -623,7 +626,7 @@ export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> =>
export async function preparePeerPushCredit(
ws: InternalWalletState,
req: PreparePeerPushCredit,
-): Promise<CheckPeerPushPaymentResponse> {
+): Promise<PreparePeerPushCreditResponse> {
const uri = parsePayPushUri(req.talerUri);
if (!uri) {
@@ -658,6 +661,8 @@ export async function preparePeerPushCredit(
if (existing) {
return {
amount: existing.existingContractTerms.amount,
+ amountEffective: existing.existingPushInc.estimatedAmountEffective,
+ amountRaw: existing.existingContractTerms.amount,
contractTerms: existing.existingContractTerms,
peerPushPaymentIncomingId:
existing.existingPushInc.peerPushPaymentIncomingId,
@@ -705,6 +710,13 @@ export async function preparePeerPushCredit(
const withdrawalGroupId = encodeCrock(getRandomBytes(32));
+ const wi = await getExchangeWithdrawalInfo(
+ ws,
+ exchangeBaseUrl,
+ Amounts.parseOrThrow(purseStatus.balance),
+ undefined,
+ );
+
await ws.db
.mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming])
.runReadWrite(async (tx) => {
@@ -718,6 +730,10 @@ export async function preparePeerPushCredit(
contractTermsHash,
status: PeerPushPaymentIncomingStatus.Proposed,
withdrawalGroupId,
+ currency: Amounts.currencyOf(purseStatus.balance),
+ estimatedAmountEffective: Amounts.stringify(
+ wi.withdrawalAmountEffective,
+ ),
});
await tx.contractTerms.put({
@@ -728,6 +744,8 @@ export async function preparePeerPushCredit(
return {
amount: purseStatus.balance,
+ amountEffective: wi.withdrawalAmountEffective,
+ amountRaw: purseStatus.balance,
contractTerms: dec.contractTerms,
peerPushPaymentIncomingId,
};
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index 1864a0b50..c988e1e84 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -58,6 +58,9 @@ import {
WithdrawalGroupStatus,
RefreshGroupRecord,
RefreshOperationStatus,
+ PeerPushPaymentIncomingRecord,
+ PeerPushPaymentIncomingStatus,
+ PeerPullPaymentInitiationRecord,
} from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { checkDbInvariant } from "../util/invariants.js";
@@ -135,8 +138,7 @@ export async function getTransactionById(
const { type, args: rest } = parseId("txn", req.transactionId);
if (
type === TransactionType.Withdrawal ||
- type === TransactionType.PeerPullCredit ||
- type === TransactionType.PeerPushCredit
+ type === TransactionType.PeerPullCredit
) {
const withdrawalGroupId = rest[0];
return await ws.db
@@ -165,24 +167,6 @@ export async function getTransactionById(
ort,
);
}
- if (
- withdrawalGroupRecord.wgInfo.withdrawalType ===
- WithdrawalRecordType.PeerPullCredit
- ) {
- return buildTransactionForPullPaymentCredit(
- withdrawalGroupRecord,
- ort,
- );
- }
- if (
- withdrawalGroupRecord.wgInfo.withdrawalType ===
- WithdrawalRecordType.PeerPushCredit
- ) {
- return buildTransactionForPushPaymentCredit(
- withdrawalGroupRecord,
- ort,
- );
- }
const exchangeDetails = await getExchangeDetails(
tx,
withdrawalGroupRecord.exchangeBaseUrl,
@@ -356,9 +340,15 @@ export async function getTransactionById(
checkDbInvariant(!!ct);
return buildTransactionForPushPaymentDebit(debit, ct.contractTermsRaw);
});
+ } else if (type === TransactionType.PeerPushCredit) {
+ // FIXME: Implement!
+ throw Error("getTransaction not yet implemented for PeerPushCredit");
+ } else if (type === TransactionType.PeerPushCredit) {
+ // FIXME: Implement!
+ throw Error("getTransaction not yet implemented for PeerPullCredit");
} else {
const unknownTxType: never = type;
- throw Error(`can't delete a '${unknownTxType}' transaction`);
+ throw Error(`can't retrieve a '${unknownTxType}' transaction`);
}
}
@@ -422,82 +412,144 @@ function buildTransactionForPullPaymentDebit(
};
}
-function buildTransactionForPullPaymentCredit(
- wsr: WithdrawalGroupRecord,
- ort?: OperationRetryRecord,
+function buildTransactionForPeerPullCredit(
+ pullCredit: PeerPullPaymentInitiationRecord,
+ pullCreditOrt: OperationRetryRecord | undefined,
+ peerContractTerms: PeerContractTerms,
+ wsr: WithdrawalGroupRecord | undefined,
+ wsrOrt: OperationRetryRecord | undefined,
): Transaction {
- if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPullCredit) {
- throw Error(`Unexpected withdrawalType: ${wsr.wgInfo.withdrawalType}`);
+ 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
+ );
+ });
+ return {
+ type: TransactionType.PeerPullCredit,
+ amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+ amountRaw: Amounts.stringify(wsr.instructedAmount),
+ exchangeBaseUrl: wsr.exchangeBaseUrl,
+ extendedStatus: wsr.timestampFinish
+ ? ExtendedStatus.Done
+ : ExtendedStatus.Pending,
+ pending: !wsr.timestampFinish,
+ timestamp: pullCredit.mergeTimestamp,
+ info: {
+ expiration: wsr.wgInfo.contractTerms.purse_expiration,
+ summary: wsr.wgInfo.contractTerms.summary,
+ },
+ talerUri: constructPayPullUri({
+ exchangeBaseUrl: wsr.exchangeBaseUrl,
+ contractPriv: wsr.wgInfo.contractPriv,
+ }),
+ transactionId: makeTransactionId(
+ TransactionType.PeerPullCredit,
+ pullCredit.pursePub,
+ ),
+ frozen: false,
+ ...(wsrOrt?.lastError
+ ? {
+ error: silentWithdrawalErrorForInvoice
+ ? undefined
+ : wsrOrt.lastError,
+ }
+ : {}),
+ };
}
- /**
- * 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 =
- ort?.lastError &&
- ort.lastError.code === TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE &&
- Object.values(ort.lastError.errorsPerCoin ?? {}).every((e) => {
- return (
- e.code === TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR &&
- e.httpStatusCode === 409
- );
- });
+
return {
type: TransactionType.PeerPullCredit,
- amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(wsr.instructedAmount),
- exchangeBaseUrl: wsr.exchangeBaseUrl,
- extendedStatus: wsr.timestampFinish
- ? ExtendedStatus.Done
- : ExtendedStatus.Pending,
- pending: !wsr.timestampFinish,
- timestamp: wsr.timestampStart,
+ amountEffective: Amounts.stringify(peerContractTerms.amount),
+ amountRaw: Amounts.stringify(peerContractTerms.amount),
+ exchangeBaseUrl: pullCredit.exchangeBaseUrl,
+ extendedStatus: ExtendedStatus.Pending,
+ pending: true,
+ timestamp: pullCredit.mergeTimestamp,
info: {
- expiration: wsr.wgInfo.contractTerms.purse_expiration,
- summary: wsr.wgInfo.contractTerms.summary,
+ expiration: peerContractTerms.purse_expiration,
+ summary: peerContractTerms.summary,
},
talerUri: constructPayPullUri({
- exchangeBaseUrl: wsr.exchangeBaseUrl,
- contractPriv: wsr.wgInfo.contractPriv,
+ exchangeBaseUrl: pullCredit.exchangeBaseUrl,
+ contractPriv: pullCredit.contractPriv,
}),
transactionId: makeTransactionId(
TransactionType.PeerPullCredit,
- wsr.withdrawalGroupId,
+ pullCredit.pursePub,
),
frozen: false,
- ...(ort?.lastError
- ? { error: silentWithdrawalErrorForInvoice ? undefined : ort.lastError }
- : {}),
+ ...(pullCreditOrt?.lastError ? { error: pullCreditOrt.lastError } : {}),
};
}
-function buildTransactionForPushPaymentCredit(
- wsr: WithdrawalGroupRecord,
- ort?: OperationRetryRecord,
+function buildTransactionForPeerPushCredit(
+ pushInc: PeerPushPaymentIncomingRecord,
+ pushOrt: OperationRetryRecord | undefined,
+ peerContractTerms: PeerContractTerms,
+ wsr: WithdrawalGroupRecord | undefined,
+ wsrOrt: OperationRetryRecord | undefined,
): Transaction {
- if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit)
- throw Error("");
+ if (wsr) {
+ if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) {
+ throw Error("invalid withdrawal group type for push payment credit");
+ }
+
+ return {
+ type: TransactionType.PeerPushCredit,
+ amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
+ amountRaw: Amounts.stringify(wsr.instructedAmount),
+ exchangeBaseUrl: wsr.exchangeBaseUrl,
+ info: {
+ expiration: wsr.wgInfo.contractTerms.purse_expiration,
+ summary: wsr.wgInfo.contractTerms.summary,
+ },
+ extendedStatus: wsr.timestampFinish
+ ? ExtendedStatus.Done
+ : ExtendedStatus.Pending,
+ pending: !wsr.timestampFinish,
+ timestamp: wsr.timestampStart,
+ transactionId: makeTransactionId(
+ TransactionType.PeerPushCredit,
+ pushInc.peerPushPaymentIncomingId,
+ ),
+ frozen: false,
+ ...(wsrOrt?.lastError ? { error: wsrOrt.lastError } : {}),
+ };
+ }
+
return {
type: TransactionType.PeerPushCredit,
- amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(wsr.instructedAmount),
- exchangeBaseUrl: wsr.exchangeBaseUrl,
+ // FIXME: This is wrong, needs to consider fees!
+ amountEffective: Amounts.stringify(peerContractTerms.amount),
+ amountRaw: Amounts.stringify(peerContractTerms.amount),
+ exchangeBaseUrl: pushInc.exchangeBaseUrl,
info: {
- expiration: wsr.wgInfo.contractTerms.purse_expiration,
- summary: wsr.wgInfo.contractTerms.summary,
+ expiration: peerContractTerms.purse_expiration,
+ summary: peerContractTerms.summary,
},
- extendedStatus: wsr.timestampFinish
- ? ExtendedStatus.Done
- : ExtendedStatus.Pending,
- pending: !wsr.timestampFinish,
- timestamp: wsr.timestampStart,
+ extendedStatus: ExtendedStatus.Pending,
+ pending: true,
+ timestamp: pushInc.timestamp,
transactionId: makeTransactionId(
TransactionType.PeerPushCredit,
- wsr.withdrawalGroupId,
+ pushInc.peerPushPaymentIncomingId,
),
frozen: false,
- ...(ort?.lastError ? { error: ort.lastError } : {}),
+ ...(pushOrt?.lastError ? { error: pushOrt.lastError } : {}),
};
}
@@ -926,6 +978,8 @@ export async function getTransactions(
x.operationRetries,
x.peerPullPaymentIncoming,
x.peerPushPaymentInitiations,
+ x.peerPushPaymentIncoming,
+ x.peerPullPaymentInitiations,
x.planchets,
x.purchases,
x.contractTerms,
@@ -970,6 +1024,80 @@ export async function getTransactions(
transactions.push(buildTransactionForPullPaymentDebit(pi));
});
+ tx.peerPushPaymentIncoming.iter().forEachAsync(async (pi) => {
+ if (!pi.currency) {
+ // Legacy transaction
+ return;
+ }
+ if (shouldSkipCurrency(transactionsRequest, pi.currency)) {
+ return;
+ }
+ if (shouldSkipSearch(transactionsRequest, [])) {
+ return;
+ }
+ if (pi.status === PeerPushPaymentIncomingStatus.Proposed) {
+ // We don't report proposed push credit transactions, user needs
+ // 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 = RetryTags.forWithdrawal(wg);
+ wgOrt = await tx.operationRetries.get(withdrawalOpId);
+ }
+ }
+ const pushIncOpId = RetryTags.forPeerPushCredit(pi);
+ let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
+
+ checkDbInvariant(!!ct);
+ transactions.push(
+ buildTransactionForPeerPushCredit(
+ pi,
+ pushIncOrt,
+ ct.contractTermsRaw,
+ wg,
+ wgOrt,
+ ),
+ );
+ });
+
+ tx.peerPullPaymentInitiations.iter().forEachAsync(async (pi) => {
+ const currency = Amounts.currencyOf(pi.amount);
+ if (shouldSkipCurrency(transactionsRequest, currency)) {
+ return;
+ }
+ 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 = RetryTags.forWithdrawal(wg);
+ wgOrt = await tx.operationRetries.get(withdrawalOpId);
+ }
+ }
+ const pushIncOpId = RetryTags.forPeerPullPaymentInitiation(pi);
+ let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
+
+ checkDbInvariant(!!ct);
+ transactions.push(
+ buildTransactionForPeerPullCredit(
+ pi,
+ pushIncOrt,
+ ct.contractTermsRaw,
+ wg,
+ wgOrt,
+ ),
+ );
+ });
+
tx.refreshGroups.iter().forEachAsync(async (rg) => {
if (shouldSkipCurrency(transactionsRequest, rg.currency)) {
return;
@@ -1009,10 +1137,12 @@ export async function getTransactions(
switch (wsr.wgInfo.withdrawalType) {
case WithdrawalRecordType.PeerPullCredit:
- transactions.push(buildTransactionForPullPaymentCredit(wsr, ort));
+ // Will be reported by the corresponding p2p transaction.
+ // FIXME: If this is an orphan withdrawal, still report it as a withdrawal!
return;
case WithdrawalRecordType.PeerPushCredit:
- transactions.push(buildTransactionForPushPaymentCredit(wsr, ort));
+ // Will be reported by the corresponding p2p transaction.
+ // FIXME: If this is an orphan withdrawal, still report it as a withdrawal!
return;
case WithdrawalRecordType.BankIntegrated:
transactions.push(
diff --git a/packages/taler-wallet-core/src/util/retries.ts b/packages/taler-wallet-core/src/util/retries.ts
index ffa4d5b9e..5744bf8fe 100644
--- a/packages/taler-wallet-core/src/util/retries.ts
+++ b/packages/taler-wallet-core/src/util/retries.ts
@@ -220,12 +220,12 @@ export namespace RetryTags {
export function forPeerPullPaymentDebit(
ppi: PeerPullPaymentIncomingRecord,
): string {
- return `${PendingTaskType.PeerPullDebit}:${ppi.pursePub}`;
+ return `${PendingTaskType.PeerPullDebit}:${ppi.peerPullPaymentIncomingId}`;
}
export function forPeerPushCredit(
ppi: PeerPushPaymentIncomingRecord,
): string {
- return `${PendingTaskType.PeerPushCredit}:${ppi.pursePub}`;
+ return `${PendingTaskType.PeerPushCredit}:${ppi.peerPushPaymentIncomingId}`;
}
export function byPaymentProposalId(proposalId: string): string {
return `${PendingTaskType.Purchase}:${proposalId}`;
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index 39d3f3d1f..904462c36 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -45,7 +45,7 @@ import {
PreparePeerPullDebitRequest,
PreparePeerPullDebitResponse,
PreparePeerPushCredit,
- CheckPeerPushPaymentResponse,
+ PreparePeerPushCreditResponse,
CoinDumpJson,
ConfirmPayRequest,
ConfirmPayResult,
@@ -615,7 +615,7 @@ export type InitiatePeerPushDebitOp = {
export type PreparePeerPushCreditOp = {
op: WalletApiOperation.PreparePeerPushCredit;
request: PreparePeerPushCredit;
- response: CheckPeerPushPaymentResponse;
+ response: PreparePeerPushCreditResponse;
};
/**