aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-01-06 11:08:45 +0100
committerFlorian Dold <florian@dold.me>2023-01-06 11:08:45 +0100
commitc2c35925bb953bf07e32c005dbe312d220b45749 (patch)
tree2fcb5eb7ad737f6cba796a3f1bbc66f763e970a7
parent80639429a23c34bdfbb5f3853caf721b49fd6beb (diff)
downloadwallet-core-c2c35925bb953bf07e32c005dbe312d220b45749.tar.xz
wallet-core: allow failure result in peer payment coin selection
-rw-r--r--packages/taler-util/src/taler-error-codes.ts8
-rw-r--r--packages/taler-util/src/wallet-types.ts65
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer.ts63
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.
*/