diff options
author | Florian Dold <florian@dold.me> | 2023-01-06 11:08:45 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2023-01-06 11:08:45 +0100 |
commit | c2c35925bb953bf07e32c005dbe312d220b45749 (patch) | |
tree | 2fcb5eb7ad737f6cba796a3f1bbc66f763e970a7 | |
parent | 80639429a23c34bdfbb5f3853caf721b49fd6beb (diff) |
wallet-core: allow failure result in peer payment coin selection
-rw-r--r-- | packages/taler-util/src/taler-error-codes.ts | 8 | ||||
-rw-r--r-- | packages/taler-util/src/wallet-types.ts | 65 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/pay-peer.ts | 63 |
3 files changed, 97 insertions, 39 deletions
diff --git a/packages/taler-util/src/taler-error-codes.ts b/packages/taler-util/src/taler-error-codes.ts index 8bac5bffb..5e3c8fdfb 100644 --- a/packages/taler-util/src/taler-error-codes.ts +++ b/packages/taler-util/src/taler-error-codes.ts @@ -3241,6 +3241,14 @@ export enum TalerErrorCode { /** + * The wallet does not have sufficient balance to create a peer push payment. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE = 7027, + + + /** * We encountered a timeout with our payment backend. * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504). * (A value of 0 indicates that the error is generated client-side). diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index b63e043f5..3a1176021 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -419,7 +419,10 @@ export const codecForPreparePayResultInsufficientBalance = "status", codecForConstString(PreparePayResultType.InsufficientBalance), ) - .property("balanceDetails", codecForPayMerchantInsufficientBalanceDetails()) + .property( + "balanceDetails", + codecForPayMerchantInsufficientBalanceDetails(), + ) .build("PreparePayResultInsufficientBalance"); export const codecForPreparePayResultAlreadyConfirmed = @@ -2084,7 +2087,6 @@ export interface InitiatePeerPullPaymentResponse { transactionId: string; } - /** * Detailed reason for why the wallet's balance is insufficient. */ @@ -2124,23 +2126,58 @@ export interface PayMerchantInsufficientBalanceDetails { * (i.e. balanceMechantWireable >= amountRequested), * this field contains an estimate of the amount that would additionally * be required to cover the fees. - * + * * It is not possible to give an exact value here, since it depends * on the coin selection for the amount that would be additionally withdrawn. */ feeGapEstimate: AmountString; } -const codecForPayMerchantInsufficientBalanceDetails = -(): Codec<PayMerchantInsufficientBalanceDetails> => - buildCodecForObject<PayMerchantInsufficientBalanceDetails>() - .property("amountRequested", codecForAmountString()) - .property("balanceAgeAcceptable", codecForAmountString()) - .property("balanceAvailable", codecForAmountString()) - .property("balanceMaterial", codecForAmountString()) - .property("balanceMerchantAcceptable", codecForAmountString()) - .property("balanceMerchantDepositable", codecForAmountString()) - .property("feeGapEstimate", codecForAmountString()) - .build("PayMerchantInsufficientBalanceDetails"); +export const codecForPayMerchantInsufficientBalanceDetails = + (): Codec<PayMerchantInsufficientBalanceDetails> => + buildCodecForObject<PayMerchantInsufficientBalanceDetails>() + .property("amountRequested", codecForAmountString()) + .property("balanceAgeAcceptable", codecForAmountString()) + .property("balanceAvailable", codecForAmountString()) + .property("balanceMaterial", codecForAmountString()) + .property("balanceMerchantAcceptable", codecForAmountString()) + .property("balanceMerchantDepositable", codecForAmountString()) + .property("feeGapEstimate", codecForAmountString()) + .build("PayMerchantInsufficientBalanceDetails"); + +/** + * Detailed reason for why the wallet's balance is insufficient. + */ +export interface PayPeerInsufficientBalanceDetails { + /** + * Amount requested by the merchant. + */ + amountRequested: AmountString; + + /** + * Balance of type "available" (see balance.ts for definition). + */ + balanceAvailable: AmountString; + + /** + * Balance of type "material" (see balance.ts for definition). + */ + balanceMaterial: AmountString; + /** + * Acceptable balance based on restrictions on which + * exchange can be used. + */ + balanceExchangeAcceptable: AmountString + /** + * If the payment would succeed without fees + * (i.e. balanceExchangeAcceptable >= amountRequested), + * this field contains an estimate of the amount that would additionally + * be required to cover the fees. + * + * It is not possible to give an exact value here, since it depends + * on the coin selection for the amount that would be additionally withdrawn. + */ + feeGapEstimate: AmountString; +} diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts index cc859f243..3d03c46db 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer.ts @@ -18,7 +18,6 @@ * Imports. */ import { - AbsoluteTime, AcceptPeerPullPaymentRequest, AcceptPeerPullPaymentResponse, AcceptPeerPushPaymentRequest, @@ -41,7 +40,6 @@ import { constructPayPushUri, ContractTermsUtil, decodeCrock, - Duration, eddsaGetPublic, encodeCrock, ExchangePurseDeposits, @@ -56,6 +54,7 @@ import { Logger, parsePayPullUri, parsePayPushUri, + PayPeerInsufficientBalanceDetails, PeerContractTerms, PreparePeerPullPaymentRequest, PreparePeerPullPaymentResponse, @@ -132,6 +131,12 @@ interface CoinInfo { ageCommitmentProof?: AgeCommitmentProof; } +export type SelectPeerCoinsResult = + | { type: "success"; result: PeerCoinSelection } + | { + type: "failure"; + }; + export async function selectPeerCoins( ws: InternalWalletState, tx: GetReadOnlyAccess<{ @@ -140,7 +145,7 @@ export async function selectPeerCoins( coins: typeof WalletStoresV1.coins; }>, instructedAmount: AmountJson, -): Promise<PeerCoinSelection | undefined> { +): Promise<SelectPeerCoinsResult> { const exchanges = await tx.exchanges.iter().toArray(); for (const exch of exchanges) { if (exch.detailsPointer?.currency !== instructedAmount.currency) { @@ -218,11 +223,11 @@ export async function selectPeerCoins( coins: resCoins, depositFees: depositFeesAcc, }; - return res; + return { type: "success", result: res }; } continue; } - return undefined; + return { type: "failure" }; } export async function preparePeerPushPayment( @@ -258,7 +263,7 @@ export async function initiatePeerToPeerPush( pursePub: pursePair.pub, }); - const coinSelRes: PeerCoinSelection | undefined = await ws.db + const coinSelRes: SelectPeerCoinsResult = await ws.db .mktx((x) => [ x.exchanges, x.contractTerms, @@ -270,11 +275,13 @@ export async function initiatePeerToPeerPush( x.peerPushPaymentInitiations, ]) .runReadWrite(async (tx) => { - const sel = await selectPeerCoins(ws, tx, instructedAmount); - if (!sel) { - return undefined; + const selRes = await selectPeerCoins(ws, tx, instructedAmount); + if (selRes.type === "failure") { + return selRes; } + const sel = selRes.result; + await spendCoins(ws, tx, { allocationId: `txn:peer-push-debit:${pursePair.pub}`, coinPubs: sel.coins.map((x) => x.coinPub), @@ -304,11 +311,12 @@ export async function initiatePeerToPeerPush( contractTermsRaw: contractTerms, }); - return sel; + return selRes; }); logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`); - if (!coinSelRes) { + if (coinSelRes.type !== "success") { + // FIXME: use error code with details here throw Error("insufficient balance"); } @@ -322,14 +330,14 @@ export async function initiatePeerToPeerPush( }); const depositSigsResp = await ws.cryptoApi.signPurseDeposits({ - exchangeBaseUrl: coinSelRes.exchangeBaseUrl, + exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl, pursePub: pursePair.pub, - coins: coinSelRes.coins, + coins: coinSelRes.result.coins, }); const createPurseUrl = new URL( `purses/${pursePair.pub}/create`, - coinSelRes.exchangeBaseUrl, + coinSelRes.result.exchangeBaseUrl, ); const httpResp = await ws.http.postJson(createPurseUrl.href, { @@ -355,9 +363,9 @@ export async function initiatePeerToPeerPush( contractPriv: econtractResp.contractPriv, mergePriv: mergePair.priv, pursePub: pursePair.pub, - exchangeBaseUrl: coinSelRes.exchangeBaseUrl, + exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl, talerUri: constructPayPushUri({ - exchangeBaseUrl: coinSelRes.exchangeBaseUrl, + exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl, contractPriv: econtractResp.contractPriv, }), transactionId: makeTransactionId( @@ -627,7 +635,7 @@ export async function acceptPeerPullPayment( const instructedAmount = Amounts.parseOrThrow( peerPullInc.contractTerms.amount, ); - const coinSelRes: PeerCoinSelection | undefined = await ws.db + const coinSelRes: SelectPeerCoinsResult = await ws.db .mktx((x) => [ x.exchanges, x.coins, @@ -637,11 +645,13 @@ export async function acceptPeerPullPayment( x.coinAvailability, ]) .runReadWrite(async (tx) => { - const sel = await selectPeerCoins(ws, tx, instructedAmount); - if (!sel) { - return undefined; + const selRes = await selectPeerCoins(ws, tx, instructedAmount); + if (selRes.type !== "success") { + return selRes; } + const sel = selRes.result; + await spendCoins(ws, tx, { allocationId: `txn:peer-pull-debit:${req.peerPullPaymentIncomingId}`, coinPubs: sel.coins.map((x) => x.coinPub), @@ -660,25 +670,27 @@ export async function acceptPeerPullPayment( pi.status = PeerPullPaymentIncomingStatus.Accepted; await tx.peerPullPaymentIncoming.put(pi); - return sel; + return selRes; }); logger.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`); - if (!coinSelRes) { + if (coinSelRes.type !== "success") { throw Error("insufficient balance"); } const pursePub = peerPullInc.pursePub; + const coinSel = coinSelRes.result; + const depositSigsResp = await ws.cryptoApi.signPurseDeposits({ - exchangeBaseUrl: coinSelRes.exchangeBaseUrl, + exchangeBaseUrl: coinSel.exchangeBaseUrl, pursePub, - coins: coinSelRes.coins, + coins: coinSel.coins, }); const purseDepositUrl = new URL( `purses/${pursePub}/deposit`, - coinSelRes.exchangeBaseUrl, + coinSel.exchangeBaseUrl, ); const depositPayload: ExchangePurseDeposits = { @@ -770,6 +782,7 @@ export async function preparePeerPullPayment( amountRaw: req.amount, }; } + /** * Initiate a peer pull payment. */ |