aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-08-20 14:34:56 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-08-20 14:34:56 +0530
commita8fb16021d6f71e6d0c7fae6b440e5c3197b8867 (patch)
treeff893a899e34d7f94dad420b0a6907e95f2942cb
parent786976e5a8e10f6a3eab50cacbefe98d8b2364f5 (diff)
handle withdrawals aborted by the bank, add test
-rw-r--r--packages/taler-integrationtests/src/harness.ts19
-rw-r--r--packages/taler-integrationtests/src/test-withdrawal-abort-bank.ts67
-rw-r--r--packages/taler-wallet-core/src/TalerErrorCode.ts49
-rw-r--r--packages/taler-wallet-core/src/operations/reserves.ts32
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts4
-rw-r--r--packages/taler-wallet-core/src/types/dbTypes.ts5
-rw-r--r--packages/taler-wallet-core/src/types/talerTypes.ts3
7 files changed, 178 insertions, 1 deletions
diff --git a/packages/taler-integrationtests/src/harness.ts b/packages/taler-integrationtests/src/harness.ts
index 6897b4b5c..545ea3097 100644
--- a/packages/taler-integrationtests/src/harness.ts
+++ b/packages/taler-integrationtests/src/harness.ts
@@ -618,6 +618,25 @@ export namespace BankApi {
},
);
}
+
+ export async function abortWithdrawalOperation(
+ bank: BankServiceInterface,
+ bankUser: BankUser,
+ wopi: WithdrawalOperationInfo,
+ ): Promise<void> {
+ const url = new URL(
+ `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/abort`,
+ bank.baseUrl,
+ );
+ await axios.post(
+ url.href,
+ {},
+ {
+ auth: bankUser,
+ },
+ );
+ }
+
}
export class BankService implements BankServiceInterface {
diff --git a/packages/taler-integrationtests/src/test-withdrawal-abort-bank.ts b/packages/taler-integrationtests/src/test-withdrawal-abort-bank.ts
new file mode 100644
index 000000000..3c1e62924
--- /dev/null
+++ b/packages/taler-integrationtests/src/test-withdrawal-abort-bank.ts
@@ -0,0 +1,67 @@
+/*
+ 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 { runTest, GlobalTestState, BankApi, BankAccessApi } from "./harness";
+import { createSimpleTestkudosEnvironment } from "./helpers";
+import { codecForBalancesResponse, TalerErrorCode } from "taler-wallet-core";
+
+/**
+ * Run test for basic, bank-integrated withdrawal.
+ */
+runTest(async (t: GlobalTestState) => {
+ // Set up test environment
+
+ const { wallet, bank, exchange } = await createSimpleTestkudosEnvironment(t);
+
+ // Create a withdrawal operation
+
+ const user = await BankApi.createRandomBankUser(bank);
+ const wop = await BankAccessApi.createWithdrawalOperation(
+ bank,
+ user,
+ "TESTKUDOS:10",
+ );
+
+ // Hand it to the wallet
+
+ const r1 = await wallet.apiRequest("getWithdrawalDetailsForUri", {
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ });
+ t.assertTrue(r1.type === "response");
+
+ await wallet.runPending();
+
+ // Confirm it
+
+ await BankApi.abortWithdrawalOperation(bank, user, wop);
+
+ // Withdraw
+
+ const r2 = await wallet.apiRequest("acceptBankIntegratedWithdrawal", {
+ exchangeBaseUrl: exchange.baseUrl,
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ });
+ t.assertTrue(r2.type === "error");
+ t.assertTrue(
+ r2.error.talerErrorCode ===
+ TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
+ );
+
+ await t.shutdown();
+});
diff --git a/packages/taler-wallet-core/src/TalerErrorCode.ts b/packages/taler-wallet-core/src/TalerErrorCode.ts
index fd3ca1fc8..412f3ef8a 100644
--- a/packages/taler-wallet-core/src/TalerErrorCode.ts
+++ b/packages/taler-wallet-core/src/TalerErrorCode.ts
@@ -1768,6 +1768,13 @@ export enum TalerErrorCode {
POST_TRANSFERS_DB_LOOKUP_ERROR = 2413,
/**
+ * The merchant backend cannot create an instance with the given default max deposit fee or default max wire fee because the fee currencies are incompatible with the merchant's currency in the config.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ POST_INSTANCES_BAD_CURRENCY = 2449,
+
+ /**
* The merchant backend cannot create an instance under the given identifier as one already exists. Use PATCH to modify the existing entry.
* Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
* (A value of 0 indicates that the error is generated client-side).
@@ -2734,6 +2741,41 @@ export enum TalerErrorCode {
MERCHANT_GET_ORDER_INVALID_TOKEN = 2923,
/**
+ * The merchant backup failed to lookup the order status in the database.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ MERCHANT_PRIVATE_GET_ORDERS_STATUS_DB_LOOKUP_ERROR = 2924,
+
+ /**
+ * The merchant backup failed to lookup the contract terms in the database.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ MERCHANT_PRIVATE_GET_ORDERS_CONTRACT_DB_LOOKUP_ERROR = 2925,
+
+ /**
+ * The merchant backup failed to parse the order contract terms.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ MERCHANT_PRIVATE_GET_ORDERS_PARSE_CONTRACT_ERROR = 2926,
+
+ /**
+ * The merchant backup failed to lookup the refunds in the database.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ MERCHANT_PRIVATE_GET_ORDERS_REFUND_DB_LOOKUP_ERROR = 2927,
+
+ /**
+ * The merchant backup failed to lookup filtered orders in the database.
+ * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ MERCHANT_PRIVATE_GET_ORDERS_BY_FILTER_DB_LOOKUP_ERROR = 2928,
+
+ /**
* The signature from the exchange on the deposit confirmation is invalid. Returned with a "400 Bad Request" status code.
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
* (A value of 0 indicates that the error is generated client-side).
@@ -3154,6 +3196,13 @@ export enum TalerErrorCode {
WALLET_CORE_NOT_AVAILABLE = 7011,
/**
+ * The bank has aborted a withdrawal operation, and thus a withdrawal can't complete.
+ * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK = 7012,
+
+ /**
* End of error code range.
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
* (A value of 0 indicates that the error is generated client-side).
diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts
index fb525da45..8adaeea81 100644
--- a/packages/taler-wallet-core/src/operations/reserves.ts
+++ b/packages/taler-wallet-core/src/operations/reserves.ts
@@ -60,6 +60,7 @@ import {
guardOperationException,
OperationFailedAndReportedError,
makeErrorDetails,
+ OperationFailedError,
} from "./errors";
import { NotificationType } from "../types/notifications";
import { codecForReserveStatus } from "../types/ReserveStatus";
@@ -358,7 +359,7 @@ async function registerReserveWithBank(
return processReserveBankStatus(ws, reservePub);
}
-export async function processReserveBankStatus(
+async function processReserveBankStatus(
ws: InternalWalletState,
reservePub: string,
): Promise<void> {
@@ -393,6 +394,25 @@ async function processReserveBankStatusImpl(
codecForWithdrawOperationStatusResponse(),
);
+ if (status.aborted) {
+ logger.trace("bank aborted the withdrawal");
+ await ws.db.mutate(Stores.reserves, reservePub, (r) => {
+ switch (r.reserveStatus) {
+ case ReserveRecordStatus.REGISTERING_BANK:
+ case ReserveRecordStatus.WAIT_CONFIRM_BANK:
+ break;
+ default:
+ return;
+ }
+ const now = getTimestampNow();
+ r.timestampBankConfirmed = now;
+ r.reserveStatus = ReserveRecordStatus.BANK_ABORTED;
+ r.retryInfo = initRetryInfo();
+ return r;
+ });
+ return;
+ }
+
if (status.selection_done) {
if (reserve.reserveStatus === ReserveRecordStatus.REGISTERING_BANK) {
await registerReserveWithBank(ws, reservePub);
@@ -612,6 +632,8 @@ async function processReserveImpl(
case ReserveRecordStatus.WAIT_CONFIRM_BANK:
await processReserveBankStatus(ws, reservePub);
break;
+ case ReserveRecordStatus.BANK_ABORTED:
+ break;
default:
console.warn("unknown reserve record status:", reserve.reserveStatus);
assertUnreachable(reserve.reserveStatus);
@@ -802,6 +824,14 @@ export async function createTalerWithdrawReserve(
// We do this here, as the reserve should be registered before we return,
// so that we can redirect the user to the bank's status page.
await processReserveBankStatus(ws, reserve.reservePub);
+ const processedReserve = await ws.db.get(Stores.reserves, reserve.reservePub);
+ if (processedReserve?.reserveStatus === ReserveRecordStatus.BANK_ABORTED) {
+ throw OperationFailedError.fromCode(
+ TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
+ "withdrawal aborted by bank",
+ {},
+ );
+ }
return {
reservePub: reserve.reservePub,
confirmTransferUrl: withdrawInfo.confirmTransferUrl,
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index 8d0558dbd..b79ac3b27 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -23,6 +23,7 @@ import {
WithdrawalSourceType,
WalletRefundItem,
RefundState,
+ ReserveRecordStatus,
} from "../types/dbTypes";
import { Amounts, AmountJson } from "../util/amounts";
import { timestampCmp, Timestamp } from "../util/time";
@@ -186,6 +187,9 @@ export async function getTransactions(
if (r.initialWithdrawalStarted) {
return;
}
+ if (r.reserveStatus === ReserveRecordStatus.BANK_ABORTED) {
+ return;
+ }
let withdrawalDetails: WithdrawalDetails;
if (r.bankInfo) {
withdrawalDetails = {
diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts b/packages/taler-wallet-core/src/types/dbTypes.ts
index 42192dd9a..82260963b 100644
--- a/packages/taler-wallet-core/src/types/dbTypes.ts
+++ b/packages/taler-wallet-core/src/types/dbTypes.ts
@@ -76,6 +76,11 @@ export enum ReserveRecordStatus {
* by the user.
*/
DORMANT = "dormant",
+
+ /**
+ * The bank aborted the withdrawal.
+ */
+ BANK_ABORTED = "bank-aborted",
}
export interface RetryInfo {
diff --git a/packages/taler-wallet-core/src/types/talerTypes.ts b/packages/taler-wallet-core/src/types/talerTypes.ts
index f251b47d1..f14e2a2ab 100644
--- a/packages/taler-wallet-core/src/types/talerTypes.ts
+++ b/packages/taler-wallet-core/src/types/talerTypes.ts
@@ -707,6 +707,8 @@ export class WithdrawOperationStatusResponse {
transfer_done: boolean;
+ aborted: boolean;
+
amount: string;
sender_wire?: string;
@@ -1178,6 +1180,7 @@ export const codecForWithdrawOperationStatusResponse = (): Codec<
buildCodecForObject<WithdrawOperationStatusResponse>()
.property("selection_done", codecForBoolean)
.property("transfer_done", codecForBoolean)
+ .property("aborted", codecForBoolean)
.property("amount", codecForString())
.property("sender_wire", codecOptional(codecForString()))
.property("suggested_exchange", codecOptional(codecForString()))