aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/pay-peer.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-01-06 13:55:08 +0100
committerFlorian Dold <florian@dold.me>2023-01-06 13:55:08 +0100
commit417c07f3f4866918e1aaa6d42b7d5ec0ca59dd51 (patch)
tree9966f647bb0779cf2de248b805f0ea13a24ddba6 /packages/taler-wallet-core/src/operations/pay-peer.ts
parentc2c35925bb953bf07e32c005dbe312d220b45749 (diff)
downloadwallet-core-417c07f3f4866918e1aaa6d42b7d5ec0ca59dd51.tar.xz
wallet-core: insufficient balance details for p2p payments
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-peer.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer.ts72
1 files changed, 65 insertions, 7 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts
index 3d03c46db..3ee1795b0 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer.ts
@@ -62,6 +62,7 @@ import {
PreparePeerPushPaymentResponse,
RefreshReason,
strcmp,
+ TalerErrorCode,
TalerProtocolTimestamp,
TransactionType,
UnblindedSignature,
@@ -77,11 +78,13 @@ import {
WithdrawalGroupStatus,
WithdrawalRecordType,
} from "../db.js";
+import { TalerError } from "../errors.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { makeTransactionId, spendCoins } from "../operations/common.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
import { checkDbInvariant } from "../util/invariants.js";
import { GetReadOnlyAccess } from "../util/query.js";
+import { getPeerPaymentBalanceDetailsInTx } from "./balance.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import { internalCreateWithdrawalGroup } from "./withdraw.js";
@@ -135,6 +138,7 @@ export type SelectPeerCoinsResult =
| { type: "success"; result: PeerCoinSelection }
| {
type: "failure";
+ insufficientBalanceDetails: PayPeerInsufficientBalanceDetails;
};
export async function selectPeerCoins(
@@ -143,12 +147,16 @@ export async function selectPeerCoins(
exchanges: typeof WalletStoresV1.exchanges;
denominations: typeof WalletStoresV1.denominations;
coins: typeof WalletStoresV1.coins;
+ coinAvailability: typeof WalletStoresV1.coinAvailability;
+ refreshGroups: typeof WalletStoresV1.refreshGroups;
}>,
instructedAmount: AmountJson,
): Promise<SelectPeerCoinsResult> {
const exchanges = await tx.exchanges.iter().toArray();
+ const exchangeFeeGap: { [url: string]: AmountJson } = {};
+ const currency = Amounts.currencyOf(instructedAmount);
for (const exch of exchanges) {
- if (exch.detailsPointer?.currency !== instructedAmount.currency) {
+ if (exch.detailsPointer?.currency !== currency) {
continue;
}
const coins = (
@@ -184,8 +192,8 @@ export async function selectPeerCoins(
-Amounts.cmp(o1.value, o2.value) ||
strcmp(o1.denomPubHash, o2.denomPubHash),
);
- let amountAcc = Amounts.zeroOfCurrency(instructedAmount.currency);
- let depositFeesAcc = Amounts.zeroOfCurrency(instructedAmount.currency);
+ let amountAcc = Amounts.zeroOfCurrency(currency);
+ let depositFeesAcc = Amounts.zeroOfCurrency(currency);
const resCoins: {
coinPub: string;
coinPriv: string;
@@ -194,6 +202,7 @@ export async function selectPeerCoins(
denomSig: UnblindedSignature;
ageCommitmentProof: AgeCommitmentProof | undefined;
}[] = [];
+ let lastDepositFee = Amounts.zeroOfCurrency(currency);
for (const coin of coinInfos) {
if (Amounts.cmp(amountAcc, instructedAmount) >= 0) {
break;
@@ -216,6 +225,7 @@ export async function selectPeerCoins(
denomSig: coin.denomSig,
ageCommitmentProof: coin.ageCommitmentProof,
});
+ lastDepositFee = coin.feeDeposit;
}
if (Amounts.cmp(amountAcc, instructedAmount) >= 0) {
const res: PeerCoinSelection = {
@@ -225,9 +235,48 @@ export async function selectPeerCoins(
};
return { type: "success", result: res };
}
+ const diff = Amounts.sub(instructedAmount, amountAcc).amount;
+ exchangeFeeGap[exch.baseUrl] = Amounts.add(lastDepositFee, diff).amount;
+
continue;
}
- return { type: "failure" };
+ // We were unable to select coins.
+ // Now we need to produce error details.
+
+ const infoGeneral = await getPeerPaymentBalanceDetailsInTx(ws, tx, {
+ currency,
+ });
+
+ const perExchange: PayPeerInsufficientBalanceDetails["perExchange"] = {};
+
+ for (const exch of exchanges) {
+ if (exch.detailsPointer?.currency !== currency) {
+ continue;
+ }
+ const infoExchange = await getPeerPaymentBalanceDetailsInTx(ws, tx, {
+ currency,
+ restrictExchangeTo: exch.baseUrl,
+ });
+ let gap = exchangeFeeGap[exch.baseUrl] ?? Amounts.zeroOfCurrency(currency);
+ if (Amounts.cmp(infoExchange.balanceMaterial, instructedAmount) < 0) {
+ // Show fee gap only if we should've been able to pay with the material amount
+ gap = Amounts.zeroOfAmount(currency);
+ }
+ perExchange[exch.baseUrl] = {
+ balanceAvailable: Amounts.stringify(infoExchange.balanceAvailable),
+ balanceMaterial: Amounts.stringify(infoExchange.balanceMaterial),
+ feeGapEstimate: Amounts.stringify(gap),
+ };
+ }
+
+ const errDetails: PayPeerInsufficientBalanceDetails = {
+ amountRequested: Amounts.stringify(instructedAmount),
+ balanceAvailable: Amounts.stringify(infoGeneral.balanceAvailable),
+ balanceMaterial: Amounts.stringify(infoGeneral.balanceMaterial),
+ perExchange,
+ };
+
+ return { type: "failure", insufficientBalanceDetails: errDetails };
}
export async function preparePeerPushPayment(
@@ -316,8 +365,12 @@ export async function initiatePeerToPeerPush(
logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
if (coinSelRes.type !== "success") {
- // FIXME: use error code with details here
- throw Error("insufficient balance");
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
+ {
+ insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
+ },
+ );
}
const purseSigResp = await ws.cryptoApi.signPurseCreation({
@@ -675,7 +728,12 @@ export async function acceptPeerPullPayment(
logger.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
if (coinSelRes.type !== "success") {
- throw Error("insufficient balance");
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
+ {
+ insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
+ },
+ );
}
const pursePub = peerPullInc.pursePub;