diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-peer.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/pay-peer.ts | 72 |
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; |