diff options
Diffstat (limited to 'packages')
10 files changed, 168 insertions, 29 deletions
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index 5733e776b..840149e7c 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -2097,7 +2097,7 @@ export interface WalletClientArgs { export class WalletClient { remoteWallet: RemoteWallet | undefined = undefined; - waiter: WalletNotificationWaiter = makeNotificationWaiter(); + private waiter: WalletNotificationWaiter = makeNotificationWaiter(); constructor(private args: WalletClientArgs) {} diff --git a/packages/taler-harness/src/harness/helpers.ts b/packages/taler-harness/src/harness/helpers.ts index d203cc608..516312ed8 100644 --- a/packages/taler-harness/src/harness/helpers.ts +++ b/packages/taler-harness/src/harness/helpers.ts @@ -99,6 +99,8 @@ export interface EnvOptions { /** * Run a test case with a simple TESTKUDOS Taler environment, consisting * of one exchange, one bank and one merchant. + * + * @deprecated use {@link createSimpleTestkudosEnvironmentV2} instead */ export async function createSimpleTestkudosEnvironment( t: GlobalTestState, @@ -505,6 +507,11 @@ export interface WithdrawViaBankResult { withdrawalFinishedCond: Promise<WithdrawalGroupFinishedNotification>; } +/** + * Withdraw via a bank with the testing API enabled. + * Uses the new notification-based mechanism to wait for the + * operation to finish. + */ export async function withdrawViaBankV2( t: GlobalTestState, p: { @@ -550,6 +557,8 @@ export async function withdrawViaBankV2( /** * Withdraw balance. + * + * @deprecated use {@link withdrawViaBankV2 instead} */ export async function withdrawViaBank( t: GlobalTestState, diff --git a/packages/taler-harness/src/integrationtests/test-deposit.ts b/packages/taler-harness/src/integrationtests/test-deposit.ts index 24dcbafc0..1b46daf5f 100644 --- a/packages/taler-harness/src/integrationtests/test-deposit.ts +++ b/packages/taler-harness/src/integrationtests/test-deposit.ts @@ -17,11 +17,12 @@ /** * Imports. */ +import { NotificationType, TransactionState } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { GlobalTestState, getPayto } from "../harness/harness.js"; import { - createSimpleTestkudosEnvironment, - withdrawViaBank, + createSimpleTestkudosEnvironmentV2, + withdrawViaBankV2, } from "../harness/helpers.js"; /** @@ -30,16 +31,27 @@ import { export async function runDepositTest(t: GlobalTestState) { // Set up test environment - const { wallet, bank, exchange, merchant } = - await createSimpleTestkudosEnvironment(t); + const { walletClient, bank, exchange } = + await createSimpleTestkudosEnvironmentV2(t); // Withdraw digital cash into the wallet. - await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); + const withdrawalResult = await withdrawViaBankV2(t, { + walletClient, + bank, + exchange, + amount: "TESTKUDOS:20", + }); - await wallet.runUntilDone(); + await withdrawalResult.withdrawalFinishedCond; - const { depositGroupId } = await wallet.client.call( + const depositDone = await walletClient.waitForNotificationCond( + (n) => + n.type == NotificationType.TransactionStateTransition && + n.newTxState == TransactionState.Done, + ); + + const depositGroupResult = await walletClient.client.call( WalletApiOperation.CreateDepositGroup, { amount: "TESTKUDOS:10", @@ -47,9 +59,7 @@ export async function runDepositTest(t: GlobalTestState) { }, ); - await wallet.runUntilDone(); - - const transactions = await wallet.client.call( + const transactions = await walletClient.client.call( WalletApiOperation.GetTransactions, {}, ); diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts index 0d85c85e9..ff1017cd1 100644 --- a/packages/taler-util/src/notifications.ts +++ b/packages/taler-util/src/notifications.ts @@ -22,6 +22,7 @@ /** * Imports. */ +import { TransactionState, TransactionSubstate } from "./transactions-types.js"; import { TalerErrorDetail } from "./wallet-types.js"; export enum NotificationType { @@ -67,6 +68,16 @@ export enum NotificationType { WithdrawalGroupReserveReady = "withdrawal-group-reserve-ready", PeerPullCreditReady = "peer-pull-credit-ready", DepositOperationError = "deposit-operation-error", + TransactionStateTransition = "transaction-state-transition", +} + +export interface TransactionStateTransitionNotification { + type: NotificationType.TransactionStateTransition; + transactionId: string; + oldTxState: TransactionState; + oldTxSubstate: TransactionSubstate; + newTxState: TransactionState; + newTxSubstate: TransactionSubstate; } export interface ProposalAcceptedNotification { @@ -327,4 +338,5 @@ export type WalletNotification = | KycRequestedNotification | WithdrawalGroupBankConfirmed | WithdrawalGroupReserveReadyNotification - | PeerPullCreditReadyNotification; + | PeerPullCreditReadyNotification + | TransactionStateTransitionNotification; diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index dea6bd361..c75ca7fdd 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -1722,6 +1722,7 @@ export const codecForPrepareDepositRequest = (): Codec<PrepareDepositRequest> => export interface PrepareDepositResponse { totalDepositCost: AmountString; effectiveDepositAmount: AmountString; + fees: DepositGroupFees; } export const codecForCreateDepositGroupRequest = diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 660fb8816..9abec89bf 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -57,12 +57,13 @@ import { WireFee, } from "@gnu-taler/taler-util"; import { + DenominationRecord, DepositGroupRecord, OperationStatus, TransactionStatus, } from "../db.js"; import { TalerError } from "@gnu-taler/taler-util"; -import { KycPendingInfo, KycUserType } from "../index.js"; +import { getTotalRefreshCost, KycPendingInfo, KycUserType } from "../index.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { OperationAttemptResult } from "../util/retries.js"; @@ -556,9 +557,17 @@ export async function prepareDepositGroup( payCoinSel.coinSel, ); + const fees = await getTotalFeesForDepositAmount( + ws, + p.targetType, + amount, + payCoinSel.coinSel, + ); + return { totalDepositCost: Amounts.stringify(totalDepositCost), effectiveDepositAmount: Amounts.stringify(effectiveDepositAmount), + fees, }; } @@ -774,3 +783,83 @@ export async function getCounterpartyEffectiveDepositAmount( }); return Amounts.sub(Amounts.sum(amt).amount, Amounts.sum(fees).amount).amount; } + +/** + * Get the fee amount that will be charged when trying to deposit the + * specified amount using the selected coins and the wire method. + */ +export async function getTotalFeesForDepositAmount( + ws: InternalWalletState, + wireType: string, + total: AmountJson, + pcs: PayCoinSelection, +): Promise<DepositGroupFees> { + const wireFee: AmountJson[] = []; + const coinFee: AmountJson[] = []; + const refreshFee: AmountJson[] = []; + const exchangeSet: Set<string> = new Set(); + + await ws.db + .mktx((x) => [x.coins, x.denominations, x.exchanges, x.exchangeDetails]) + .runReadOnly(async (tx) => { + for (let i = 0; i < pcs.coinPubs.length; i++) { + const coin = await tx.coins.get(pcs.coinPubs[i]); + if (!coin) { + throw Error("can't calculate deposit amount, coin not found"); + } + const denom = await ws.getDenomInfo( + ws, + tx, + coin.exchangeBaseUrl, + coin.denomPubHash, + ); + if (!denom) { + throw Error("can't find denomination to calculate deposit amount"); + } + coinFee.push(Amounts.parseOrThrow(denom.feeDeposit)); + exchangeSet.add(coin.exchangeBaseUrl); + + const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl + .iter(coin.exchangeBaseUrl) + .filter((x) => + Amounts.isSameCurrency( + DenominationRecord.getValue(x), + pcs.coinContributions[i], + ), + ); + const amountLeft = Amounts.sub( + denom.value, + pcs.coinContributions[i], + ).amount; + const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft); + refreshFee.push(refreshCost); + } + + for (const exchangeUrl of exchangeSet.values()) { + const exchangeDetails = await getExchangeDetails(tx, exchangeUrl); + if (!exchangeDetails) { + continue; + } + const fee = exchangeDetails.wireInfo.feesForType[wireType]?.find( + (x) => { + return AbsoluteTime.isBetween( + AbsoluteTime.now(), + AbsoluteTime.fromTimestamp(x.startStamp), + AbsoluteTime.fromTimestamp(x.endStamp), + ); + }, + )?.wireFee; + if (fee) { + wireFee.push(Amounts.parseOrThrow(fee)); + } + } + }); + + return { + coin: Amounts.stringify(Amounts.sumOrZero(total.currency, coinFee).amount), + wire: Amounts.stringify(Amounts.sumOrZero(total.currency, wireFee).amount), + refresh: Amounts.stringify( + Amounts.sumOrZero(total.currency, refreshFee).amount, + ), + }; +} diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts index b9fbc3638..a7d24eeb8 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts @@ -74,6 +74,11 @@ describe("Deposit CTA states", () => { { effectiveDepositAmount: "EUR:1", totalDepositCost: "EUR:1.2", + fees: { + coin: "EUR:0", + refresh: "EUR:0.2", + wire: "EUR:0", + }, }, ); diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts index b744b80e5..42a3ba847 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts @@ -151,7 +151,7 @@ export function useComponentState({ // eslint-disable-next-line react-hooks/rules-of-hooks const hook = useAsyncAsHook(async () => { - const fee = await api.wallet.call(WalletApiOperation.GetFeeForDeposit, { + const fee = await api.wallet.call(WalletApiOperation.PrepareDeposit, { amount: amountStr, depositPaytoUri, }); @@ -181,7 +181,7 @@ export function useComponentState({ const totalFee = fee !== undefined - ? Amounts.sum([fee.wire, fee.coin, fee.refresh]).amount + ? Amounts.sum([fee.fees.wire, fee.fees.coin, fee.fees.refresh]).amount : Amounts.zeroOfCurrency(currency); const totalToDeposit = diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts index 1489e2bb9..a06b1ae75 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts @@ -23,6 +23,7 @@ import { Amounts, DepositGroupFees, parsePaytoUri, + PrepareDepositResponse, ScopeType, stringifyPaytoUri, } from "@gnu-taler/taler-util"; @@ -36,16 +37,24 @@ import { useComponentState } from "./state.js"; const currency = "EUR"; const amount = `${currency}:0`; -const withoutFee = (): DepositGroupFees => ({ - coin: Amounts.stringify(`${currency}:0`), - wire: Amounts.stringify(`${currency}:0`), - refresh: Amounts.stringify(`${currency}:0`), +const withoutFee = (): PrepareDepositResponse => ({ + effectiveDepositAmount: `${currency}:5`, + totalDepositCost: `${currency}:5`, + fees: { + coin: Amounts.stringify(`${currency}:0`), + wire: Amounts.stringify(`${currency}:0`), + refresh: Amounts.stringify(`${currency}:0`), + }, }); -const withSomeFee = (): DepositGroupFees => ({ - coin: Amounts.stringify(`${currency}:1`), - wire: Amounts.stringify(`${currency}:1`), - refresh: Amounts.stringify(`${currency}:1`), +const withSomeFee = (): PrepareDepositResponse => ({ + effectiveDepositAmount: `${currency}:5`, + totalDepositCost: `${currency}:5`, + fees: { + coin: Amounts.stringify(`${currency}:1`), + wire: Amounts.stringify(`${currency}:1`), + refresh: Amounts.stringify(`${currency}:1`), + }, }); describe("DepositPage states", () => { @@ -182,7 +191,7 @@ describe("DepositPage states", () => { }, ); handler.addWalletCallResponse( - WalletApiOperation.GetFeeForDeposit, + WalletApiOperation.PrepareDeposit, undefined, withoutFee(), ); @@ -241,13 +250,13 @@ describe("DepositPage states", () => { }, ); handler.addWalletCallResponse( - WalletApiOperation.GetFeeForDeposit, + WalletApiOperation.PrepareDeposit, undefined, withoutFee(), ); handler.addWalletCallResponse( - WalletApiOperation.GetFeeForDeposit, + WalletApiOperation.PrepareDeposit, undefined, withoutFee(), ); @@ -330,17 +339,17 @@ describe("DepositPage states", () => { }, ); handler.addWalletCallResponse( - WalletApiOperation.GetFeeForDeposit, + WalletApiOperation.PrepareDeposit, undefined, withoutFee(), ); handler.addWalletCallResponse( - WalletApiOperation.GetFeeForDeposit, + WalletApiOperation.PrepareDeposit, undefined, withSomeFee(), ); handler.addWalletCallResponse( - WalletApiOperation.GetFeeForDeposit, + WalletApiOperation.PrepareDeposit, undefined, withSomeFee(), ); diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx index d338b77f5..bf59573ec 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx @@ -34,6 +34,8 @@ import { TransactionPeerPushDebit, TransactionRefresh, TransactionRefund, + TransactionState, + TransactionSubstate, TransactionTip, TransactionType, TransactionWithdrawal, @@ -68,6 +70,8 @@ const commonTransaction = { transactionId: "txn:deposit:12", frozen: undefined as any as boolean, //deprecated type: TransactionType.Deposit, + txState: TransactionState.Unknown, + txSubstate: TransactionSubstate.None, } as TransactionCommon; import merchantIcon from "../../static-dev/merchant-icon.jpeg"; |