aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts15
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-flex.ts70
-rw-r--r--packages/taler-harness/src/integrationtests/testrunner.ts4
-rw-r--r--packages/taler-util/src/bank-api-client.ts2
-rw-r--r--packages/taler-util/src/http-client/types.ts4
-rw-r--r--packages/taler-util/src/taler-types.ts4
-rw-r--r--packages/taler-util/src/wallet-types.ts31
-rw-r--r--packages/taler-wallet-core/src/db.ts1
-rw-r--r--packages/taler-wallet-core/src/shepherd.ts8
-rw-r--r--packages/taler-wallet-core/src/wallet.ts5
-rw-r--r--packages/taler-wallet-core/src/withdraw.ts58
11 files changed, 166 insertions, 36 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
index 1c65de7d9..8a2268231 100644
--- a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
@@ -90,7 +90,10 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) {
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
accountPassword: exchangeBankPassword,
- wireGatewayApiBaseUrl: new URL("accounts/exchange/taler-wire-gateway/", bank.baseUrl).href,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
accountPaytoUri: exchangePaytoUri,
});
@@ -133,10 +136,7 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) {
const user = await bankClient.createRandomBankUser();
bankClient.setAuth(user);
- const wop = await bankClient.createWithdrawalOperation(
- user.username,
- amount,
- );
+ const wop = await bankClient.createWithdrawalOperation(user.username, amount);
// Hand it to the wallet
@@ -149,10 +149,13 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) {
console.log(j2s(details));
+ const myAmount = details.amount;
+ t.assertTrue(!!myAmount);
+
const amountDetails = await wallet.client.call(
WalletApiOperation.GetWithdrawalDetailsForAmount,
{
- amount: details.amount,
+ amount: myAmount,
exchangeBaseUrl: details.possibleExchanges[0].exchangeBaseUrl,
},
);
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-flex.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-flex.ts
new file mode 100644
index 000000000..ffc7249b8
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-flex.ts
@@ -0,0 +1,70 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import { j2s } from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState } from "../harness/harness.js";
+import { createSimpleTestkudosEnvironmentV3 } from "../harness/helpers.js";
+
+/**
+ * Run test for bank-integrated withdrawal with flexible amount,
+ * i.e. the amount is chosen by the wallet.
+ */
+export async function runWithdrawalFlexTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange } =
+ await createSimpleTestkudosEnvironmentV3(t);
+
+ // Create a withdrawal operation
+ const user = await bankClient.createRandomBankUser();
+ bankClient.setAuth(user);
+ const wop = await bankClient.createWithdrawalOperation(
+ user.username,
+ undefined,
+ );
+
+ const r1 = await walletClient.call(
+ WalletApiOperation.GetWithdrawalDetailsForUri,
+ {
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ },
+ );
+
+ console.log(j2s(r1));
+
+ // Withdraw
+
+ const r2 = await walletClient.call(
+ WalletApiOperation.AcceptBankIntegratedWithdrawal,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ amount: "TESTKUDOS:10",
+ },
+ );
+
+ await bankClient.confirmWithdrawalOperation(user.username, {
+ withdrawalOperationId: wop.withdrawal_id,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+}
+
+runWithdrawalFlexTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
index eb2ae7fa6..4588310b1 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -113,14 +113,15 @@ import { runWalletRefreshTest } from "./test-wallet-refresh.js";
import { runWalletWirefeesTest } from "./test-wallet-wirefees.js";
import { runWallettestingTest } from "./test-wallettesting.js";
import { runWithdrawalAbortBankTest } from "./test-withdrawal-abort-bank.js";
+import { runWithdrawalAmountTest } from "./test-withdrawal-amount.js";
import { runWithdrawalBankIntegratedTest } from "./test-withdrawal-bank-integrated.js";
import { runWithdrawalConversionTest } from "./test-withdrawal-conversion.js";
import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js";
import { runWithdrawalFeesTest } from "./test-withdrawal-fees.js";
+import { runWithdrawalFlexTest } from "./test-withdrawal-flex.js";
import { runWithdrawalHandoverTest } from "./test-withdrawal-handover.js";
import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js";
import { runWithdrawalManualTest } from "./test-withdrawal-manual.js";
-import { runWithdrawalAmountTest } from "./test-withdrawal-amount.js";
/**
* Test runner.
@@ -232,6 +233,7 @@ const allTests: TestMainFunction[] = [
runPeerPushLargeTest,
runWithdrawalHandoverTest,
runWithdrawalAmountTest,
+ runWithdrawalFlexTest,
];
export interface TestRunSpec {
diff --git a/packages/taler-util/src/bank-api-client.ts b/packages/taler-util/src/bank-api-client.ts
index e9f442af6..e1409087f 100644
--- a/packages/taler-util/src/bank-api-client.ts
+++ b/packages/taler-util/src/bank-api-client.ts
@@ -385,7 +385,7 @@ export class TalerCorebankApiClient {
async createWithdrawalOperation(
user: string,
- amount: string,
+ amount: string | undefined,
): Promise<WithdrawalOperationInfo> {
const url = new URL(`accounts/${user}/withdrawals`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts
index edddf7d94..9268f6387 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -1311,7 +1311,7 @@ export const codecForBankWithdrawalOperationStatus =
codecForConstString("confirmed"),
),
)
- .property("amount", codecForAmountString())
+ .property("amount", codecOptional(codecForAmountString()))
.property("sender_wire", codecOptional(codecForPaytoString()))
.property("suggested_exchange", codecOptional(codecForString()))
.property("confirm_transfer_url", codecOptional(codecForURL()))
@@ -2030,7 +2030,7 @@ export namespace TalerBankIntegrationApi {
// Amount that will be withdrawn with this operation
// (raw amount without fee considerations).
- amount: AmountString;
+ amount: AmountString | undefined;
// Bank account of the customer that is withdrawing, as a
// payto URI.
diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts
index e2536b74a..66f98ea9a 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -978,7 +978,7 @@ export class WithdrawOperationStatusResponse {
aborted: boolean;
- amount: string;
+ amount: string | undefined;
sender_wire?: string;
@@ -1557,7 +1557,7 @@ export const codecForWithdrawOperationStatusResponse =
.property("selection_done", codecForBoolean())
.property("transfer_done", codecForBoolean())
.property("aborted", codecForBoolean())
- .property("amount", codecForString())
+ .property("amount", codecOptional(codecForString()))
.property("sender_wire", codecOptional(codecForString()))
.property("suggested_exchange", codecOptional(codecForString()))
.property("confirm_transfer_url", codecOptional(codecForString()))
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index d472af187..9301a9723 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -663,11 +663,11 @@ export interface CoinDumpJson {
withdrawal_reserve_pub: string | undefined;
coin_status: CoinStatus;
spend_allocation:
- | {
- id: string;
- amount: AmountString;
- }
- | undefined;
+ | {
+ id: string;
+ amount: AmountString;
+ }
+ | undefined;
/**
* Information about the age restriction
*/
@@ -801,7 +801,7 @@ export const codecForPreparePayResultPaymentPossible =
)
.build("PreparePayResultPaymentPossible");
-export interface BalanceDetails { }
+export interface BalanceDetails {}
/**
* Detailed reason for why the wallet's balance is insufficient.
@@ -984,7 +984,8 @@ export interface PreparePayResultAlreadyConfirmed {
export interface BankWithdrawDetails {
status: WithdrawalOperationStatus;
- amount: AmountJson;
+ currency: string;
+ amount: AmountJson | undefined;
senderWire?: string;
suggestedExchange?: string;
confirmTransferUrl?: string;
@@ -1883,6 +1884,13 @@ export interface AcceptBankIntegratedWithdrawalRequest {
talerWithdrawUri: string;
exchangeBaseUrl: string;
forcedDenomSel?: ForcedDenomSel;
+ /**
+ * Amount to withdraw.
+ * If the bank's withdrawal operation uses a fixed amount,
+ * this field must either be left undefined or its value must match
+ * the amount from the withdrawal operation.
+ */
+ amount?: AmountString;
restrictAge?: number;
}
@@ -1892,6 +1900,7 @@ export const codecForAcceptBankIntegratedWithdrawalRequest =
.property("exchangeBaseUrl", codecForCanonBaseUrl())
.property("talerWithdrawUri", codecForString())
.property("forcedDenomSel", codecForAny())
+ .property("amount", codecOptional(codecForAmountString()))
.property("restrictAge", codecOptional(codecForNumber()))
.build("AcceptBankIntegratedWithdrawalRequest");
@@ -2047,7 +2056,7 @@ export interface CheckPayTemplateRequest {
export type CheckPayTemplateReponse = {
templateDetails: TalerMerchantApi.WalletTemplateDetails;
supportedCurrencies: string[];
-}
+};
export const codecForCheckPayTemplateRequest =
(): Codec<CheckPayTemplateRequest> =>
@@ -2352,7 +2361,8 @@ export interface WithdrawUriInfoResponse {
operationId: string;
status: WithdrawalOperationStatus;
confirmTransferUrl?: string;
- amount: AmountString;
+ currency: string;
+ amount: AmountString | undefined;
defaultExchangeBaseUrl?: string;
possibleExchanges: ExchangeListItem[];
}
@@ -2371,7 +2381,8 @@ export const codecForWithdrawUriInfoResponse =
codecForConstString("confirmed"),
),
)
- .property("amount", codecForAmountString())
+ .property("amount", codecOptional(codecForAmountString()))
+ .property("currency", codecForString())
.property("defaultExchangeBaseUrl", codecOptional(codecForCanonBaseUrl()))
.property("possibleExchanges", codecForList(codecForExchangeListItem()))
.build("WithdrawUriInfoResponse");
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 44c241aed..640d94753 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1439,6 +1439,7 @@ export interface WgInfoBankIntegrated {
* a Taler-integrated bank.
*/
bankInfo: ReserveBankInfo;
+
/**
* Info about withdrawal accounts, possibly including currency conversion.
*/
diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts
index dbdd7aac5..d662bd7ae 100644
--- a/packages/taler-wallet-core/src/shepherd.ts
+++ b/packages/taler-wallet-core/src/shepherd.ts
@@ -382,7 +382,13 @@ export class TaskSchedulerImpl implements TaskScheduler {
});
switch (res.type) {
case TaskRunResultType.Error: {
- logger.trace(`Shepherd for ${taskId} got error result.`);
+ if (logger.shouldLogTrace()) {
+ logger.trace(
+ `Shepherd for ${taskId} got error result: ${j2s(
+ res.errorDetail,
+ )}`,
+ );
+ }
const retryRecord = await storePendingTaskError(
this.ws,
taskId,
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index c17a2b467..3455d451b 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -724,7 +724,9 @@ async function dispatchRequestInternal(
const req = codecForInitRequest().decode(payload);
if (logger.shouldLogTrace()) {
- const initType = wex.ws.initCalled ? "repeat initialization" : "first initialization";
+ const initType = wex.ws.initCalled
+ ? "repeat initialization"
+ : "first initialization";
logger.trace(`init request (${initType}): ${j2s(req)}`);
}
@@ -997,6 +999,7 @@ async function dispatchRequestInternal(
talerWithdrawUri: req.talerWithdrawUri,
forcedDenomSel: req.forcedDenomSel,
restrictAge: req.restrictAge,
+ amount: req.amount,
});
}
case WalletApiOperation.ConfirmWithdrawal: {
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
index 4a7c7873c..16289b1ef 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -857,10 +857,16 @@ export async function getBankWithdrawalInfo(
}
const { body: status } = resp;
+ let amount: AmountJson | undefined;
+ if (status.amount) {
+ amount = Amounts.parseOrThrow(status.amount);
+ }
+
return {
operationId: uriResult.withdrawalOperationId,
apiBaseUrl: uriResult.bankIntegrationApiBaseUrl,
- amount: Amounts.parseOrThrow(status.amount),
+ currency: config.currency,
+ amount,
confirmTransferUrl: status.confirm_transfer_url,
senderWire: status.sender_wire,
suggestedExchange: status.suggested_exchange,
@@ -2262,7 +2268,7 @@ export async function getWithdrawalDetailsForUri(
}
}
- const currency = Amounts.currencyOf(info.amount);
+ const currency = info.currency;
const listExchangesResp = await listExchanges(wex);
const possibleExchanges = listExchangesResp.exchanges.filter((x) => {
@@ -2277,7 +2283,8 @@ export async function getWithdrawalDetailsForUri(
operationId: info.operationId,
confirmTransferUrl: info.confirmTransferUrl,
status: info.status,
- amount: Amounts.stringify(info.amount),
+ currency,
+ amount: info.amount ? Amounts.stringify(info.amount) : undefined,
defaultExchangeBaseUrl: info.suggestedExchange,
possibleExchanges,
};
@@ -2379,6 +2386,7 @@ export function getBankAbortUrl(talerWithdrawUri: string): string {
async function registerReserveWithBank(
wex: WalletExecutionContext,
withdrawalGroupId: string,
+ isFlexibleAmount: boolean,
): Promise<void> {
const withdrawalGroup = await wex.db.runReadOnlyTx(
{ storeNames: ["withdrawalGroups"] },
@@ -2407,7 +2415,11 @@ async function registerReserveWithBank(
const reqBody = {
reserve_pub: withdrawalGroup.reservePub,
selected_exchange: bankInfo.exchangePaytoUri,
- };
+ } as any;
+ if (isFlexibleAmount) {
+ reqBody.amount = withdrawalGroup.instructedAmount;
+ }
+ logger.trace(`isFlexibleAmount: ${isFlexibleAmount}`);
logger.info(`registering reserve with bank: ${j2s(reqBody)}`);
const httpResp = await wex.http.fetch(bankStatusUrl, {
method: "POST",
@@ -2516,7 +2528,9 @@ async function processBankRegisterReserve(
// FIXME: Put confirm transfer URL in the DB!
- await registerReserveWithBank(wex, withdrawalGroupId);
+ const isFlexibleAmount = status.amount == null;
+
+ await registerReserveWithBank(wex, withdrawalGroupId, isFlexibleAmount);
return TaskRunResult.progress();
}
@@ -2985,7 +2999,7 @@ export async function confirmWithdrawal(
const confirmUrl = withdrawalGroup.wgInfo.bankInfo.confirmUrl;
/**
- * The only reasong this to be undefined is because it is an old wallet
+ * The only reason this could be undefined is because it is an old wallet
* database before adding the wireType field was added
*/
let wtypes: string[];
@@ -3025,7 +3039,7 @@ export async function confirmWithdrawal(
req.forcedDenomSel,
);
- ctx.transition({}, async (rec) => {
+ await ctx.transition({}, async (rec) => {
if (!rec) {
return TransitionResult.stay();
}
@@ -3060,7 +3074,6 @@ export async function confirmWithdrawal(
});
await wex.taskScheduler.resetTaskRetries(ctx.taskId);
- wex.taskScheduler.startShepherdTask(ctx.taskId);
}
/**
@@ -3080,6 +3093,7 @@ export async function acceptWithdrawalFromUri(
selectedExchange: string;
forcedDenomSel?: ForcedDenomSel;
restrictAge?: number;
+ amount?: AmountLike;
},
): Promise<AcceptWithdrawalResponse> {
const selectedExchange = req.selectedExchange;
@@ -3124,17 +3138,37 @@ export async function acceptWithdrawalFromUri(
withdrawInfo.wireTypes,
);
+ let amount: AmountJson;
+ if (withdrawInfo.amount == null) {
+ if (req.amount == null) {
+ throw Error(
+ "amount required, as withdrawal operation has flexible amount",
+ );
+ }
+ amount = Amounts.parseOrThrow(req.amount);
+ } else {
+ if (
+ req.amount != null &&
+ Amounts.cmp(req.amount, withdrawInfo.amount) != 0
+ ) {
+ throw Error(
+ "mismatched amount, amount is fixed by bank but client provided different amount",
+ );
+ }
+ amount = withdrawInfo.amount;
+ }
+
const withdrawalAccountList = await fetchWithdrawalAccountInfo(
wex,
{
exchange,
- instructedAmount: withdrawInfo.amount,
+ instructedAmount: amount,
},
CancellationToken.CONTINUE,
);
const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
- amount: withdrawInfo.amount,
+ amount,
exchangeBaseUrl: req.selectedExchange,
wgInfo: {
withdrawalType: WithdrawalRecordType.BankIntegrated,
@@ -3162,10 +3196,10 @@ export async function acceptWithdrawalFromUri(
hintTransactionId: ctx.transactionId,
});
- await waitWithdrawalRegistered(wex, ctx);
-
wex.taskScheduler.startShepherdTask(ctx.taskId);
+ await waitWithdrawalRegistered(wex, ctx);
+
return {
reservePub: withdrawalGroup.reservePub,
confirmTransferUrl: withdrawInfo.confirmTransferUrl,