aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-util
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-09-06 13:24:34 +0200
committerFlorian Dold <florian@dold.me>2023-09-06 13:24:34 +0200
commitb63937703ce1e269055497ee14ac90a28de2fc74 (patch)
treea2f9aa841b18f950028b34b17f88bf28c70dd5db /packages/taler-util
parent7450bede5b5809f6a496b7e68852a454386850e5 (diff)
downloadwallet-core-b63937703ce1e269055497ee14ac90a28de2fc74.tar.xz
move bank API client to taler-util, update typescript config
Diffstat (limited to 'packages/taler-util')
-rw-r--r--packages/taler-util/package.json6
-rw-r--r--packages/taler-util/src/bank-api-client.ts325
-rw-r--r--packages/taler-util/src/http-common.ts2
-rw-r--r--packages/taler-util/src/index.ts1
-rw-r--r--packages/taler-util/tsconfig.json4
5 files changed, 332 insertions, 6 deletions
diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json
index 6ac9a2689..0bb98767d 100644
--- a/packages/taler-util/package.json
+++ b/packages/taler-util/package.json
@@ -68,14 +68,14 @@
"esbuild": "^0.17.7",
"prettier": "^2.8.8",
"rimraf": "^3.0.2",
- "typescript": "^5.1.3"
+ "typescript": "^5.2.2"
},
"dependencies": {
"big-integer": "^1.6.51",
"fflate": "^0.7.4",
+ "hash-wasm": "^4.9.0",
"jed": "^1.1.1",
- "tslib": "^2.5.3",
- "hash-wasm": "^4.9.0"
+ "tslib": "^2.5.3"
},
"ava": {
"files": [
diff --git a/packages/taler-util/src/bank-api-client.ts b/packages/taler-util/src/bank-api-client.ts
new file mode 100644
index 000000000..cc4123500
--- /dev/null
+++ b/packages/taler-util/src/bank-api-client.ts
@@ -0,0 +1,325 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 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/>
+ */
+
+/**
+ * Client for the Taler (demo-)bank.
+ */
+
+/**
+ * Imports.
+ */
+import {
+ AmountString,
+ base64FromArrayBuffer,
+ buildCodecForObject,
+ Codec,
+ codecForAny,
+ codecForString,
+ encodeCrock,
+ generateIban,
+ getRandomBytes,
+ j2s,
+ Logger,
+ stringToBytes,
+ TalerError,
+ TalerErrorCode,
+} from "@gnu-taler/taler-util";
+import {
+ checkSuccessResponseOrThrow,
+ createPlatformHttpLib,
+ HttpRequestLibrary,
+ readSuccessResponseJsonOrThrow,
+} from "@gnu-taler/taler-util/http";
+
+const logger = new Logger("bank-api-client.ts");
+
+export enum CreditDebitIndicator {
+ Credit = "credit",
+ Debit = "debit",
+}
+
+export interface BankAccountBalanceResponse {
+ balance: {
+ amount: AmountString;
+ credit_debit_indicator: CreditDebitIndicator;
+ };
+}
+
+export interface BankUser {
+ username: string;
+ password: string;
+ accountPaytoUri: string;
+}
+
+export interface WithdrawalOperationInfo {
+ withdrawal_id: string;
+ taler_withdraw_uri: string;
+}
+
+/**
+ * Helper function to generate the "Authorization" HTTP header.
+ */
+function makeBasicAuthHeader(username: string, password: string): string {
+ const auth = `${username}:${password}`;
+ const authEncoded: string = base64FromArrayBuffer(stringToBytes(auth));
+ return `Basic ${authEncoded}`;
+}
+
+const codecForWithdrawalOperationInfo = (): Codec<WithdrawalOperationInfo> =>
+ buildCodecForObject<WithdrawalOperationInfo>()
+ .property("withdrawal_id", codecForString())
+ .property("taler_withdraw_uri", codecForString())
+ .build("WithdrawalOperationInfo");
+
+export interface BankAccessApiClientArgs {
+ auth?: { username: string; password: string };
+ httpClient?: HttpRequestLibrary;
+}
+
+export interface BankAccessApiCreateTransactionRequest {
+ amount: AmountString;
+ paytoUri: string;
+}
+
+export class WireGatewayApiClientArgs {
+ auth?: {
+ username: string;
+ password: string;
+ };
+ httpClient?: HttpRequestLibrary;
+}
+
+/**
+ * This API look like it belongs to harness
+ * but it will be nice to have in utils to be used by others
+ */
+export class WireGatewayApiClient {
+ httpLib;
+
+ constructor(
+ private baseUrl: string,
+ private args: WireGatewayApiClientArgs = {},
+ ) {
+ this.httpLib = args.httpClient ?? createPlatformHttpLib();
+ }
+
+ private makeAuthHeader(): Record<string, string> {
+ const auth = this.args.auth;
+ if (auth) {
+ return {
+ Authorization: makeBasicAuthHeader(auth.username, auth.password),
+ };
+ }
+ return {};
+ }
+
+ async adminAddIncoming(params: {
+ amount: string;
+ reservePub: string;
+ debitAccountPayto: string;
+ }): Promise<void> {
+ let url = new URL(`admin/add-incoming`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body: {
+ amount: params.amount,
+ reserve_pub: params.reservePub,
+ debit_account: params.debitAccountPayto,
+ },
+ headers: this.makeAuthHeader(),
+ });
+ logger.info(`add-incoming response status: ${resp.status}`);
+ await checkSuccessResponseOrThrow(resp);
+ }
+}
+
+/**
+ * This API look like it belongs to harness
+ * but it will be nice to have in utils to be used by others
+ */
+export class BankAccessApiClient {
+ httpLib: HttpRequestLibrary;
+
+ constructor(
+ private baseUrl: string,
+ private args: BankAccessApiClientArgs = {},
+ ) {
+ this.httpLib = args.httpClient ?? createPlatformHttpLib();
+ }
+
+ setAuth(auth: { username: string; password: string }) {
+ this.args.auth = auth;
+ }
+
+ private makeAuthHeader(): Record<string, string> {
+ if (!this.args.auth) {
+ return {};
+ }
+ const authHeaderValue = makeBasicAuthHeader(
+ this.args.auth.username,
+ this.args.auth.password,
+ );
+ return {
+ Authorization: authHeaderValue,
+ };
+ }
+
+ async getAccountBalance(
+ username: string,
+ ): Promise<BankAccountBalanceResponse> {
+ const url = new URL(`accounts/${username}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ headers: this.makeAuthHeader(),
+ });
+ return await resp.json();
+ }
+
+ async getTransactions(username: string): Promise<void> {
+ const reqUrl = new URL(`accounts/${username}/transactions`, this.baseUrl);
+ const resp = await this.httpLib.fetch(reqUrl.href, {
+ method: "GET",
+ headers: {
+ ...this.makeAuthHeader(),
+ },
+ });
+
+ const res = await readSuccessResponseJsonOrThrow(resp, codecForAny());
+ logger.info(`result: ${j2s(res)}`);
+ }
+
+ async createTransaction(
+ username: string,
+ req: BankAccessApiCreateTransactionRequest,
+ ): Promise<any> {
+ const reqUrl = new URL(`accounts/${username}/transactions`, this.baseUrl);
+
+ const resp = await this.httpLib.fetch(reqUrl.href, {
+ method: "POST",
+ body: req,
+ headers: this.makeAuthHeader(),
+ });
+
+ return await readSuccessResponseJsonOrThrow(resp, codecForAny());
+ }
+
+ async registerAccount(
+ username: string,
+ password: string,
+ options: {
+ iban?: string;
+ } = {},
+ ): Promise<BankUser> {
+ const url = new URL("testing/register", this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body: {
+ username,
+ password,
+ iban: options?.iban,
+ },
+ });
+ let paytoUri = `payto://x-taler-bank/localhost/${username}`;
+ if (resp.status !== 200 && resp.status !== 202 && resp.status !== 204) {
+ logger.error(`${j2s(await resp.json())}`);
+ throw TalerError.fromDetail(
+ TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR,
+ {
+ httpStatusCode: resp.status,
+ },
+ );
+ }
+ try {
+ // Pybank has no body, thus this might throw.
+ const respJson = await resp.json();
+ // LibEuFin demobank returns payto URI in response
+ if (respJson.paytoUri) {
+ paytoUri = respJson.paytoUri;
+ }
+ } catch (e) {
+ // Do nothing
+ }
+ return {
+ password,
+ username,
+ accountPaytoUri: paytoUri,
+ };
+ }
+
+ async createRandomBankUser(): Promise<BankUser> {
+ const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
+ const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase();
+ // FIXME: This is just a temporary workaround, because demobank is running out of short IBANs
+ const iban = generateIban("DE", 15);
+ return await this.registerAccount(username, password, {
+ iban,
+ });
+ }
+
+ async createWithdrawalOperation(
+ user: string,
+ amount: string,
+ ): Promise<WithdrawalOperationInfo> {
+ const url = new URL(`accounts/${user}/withdrawals`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body: {
+ amount,
+ },
+ headers: this.makeAuthHeader(),
+ });
+ return readSuccessResponseJsonOrThrow(
+ resp,
+ codecForWithdrawalOperationInfo(),
+ );
+ }
+
+ async confirmWithdrawalOperation(
+ username: string,
+ wopi: WithdrawalOperationInfo,
+ ): Promise<void> {
+ const url = new URL(
+ `accounts/${username}/withdrawals/${wopi.withdrawal_id}/confirm`,
+ this.baseUrl,
+ );
+ logger.info(`confirming withdrawal operation via ${url.href}`);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body: {},
+ headers: this.makeAuthHeader(),
+ });
+
+ logger.info(`response status ${resp.status}`);
+ const respJson = await readSuccessResponseJsonOrThrow(resp, codecForAny());
+
+ // FIXME: We don't check the status here!
+ }
+
+ async abortWithdrawalOperation(
+ accountName: string,
+ wopi: WithdrawalOperationInfo,
+ ): Promise<void> {
+ const url = new URL(
+ `accounts/${accountName}/withdrawals/${wopi.withdrawal_id}/abort`,
+ this.baseUrl,
+ );
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body: {},
+ headers: this.makeAuthHeader(),
+ });
+ await readSuccessResponseJsonOrThrow(resp, codecForAny());
+ }
+}
diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts
index 02ec8ce72..f25705545 100644
--- a/packages/taler-util/src/http-common.ts
+++ b/packages/taler-util/src/http-common.ts
@@ -16,7 +16,7 @@
SPDX-License-Identifier: AGPL3.0-or-later
*/
-import { CancellationToken } from "./CancellationToken.js";
+import type { CancellationToken } from "./CancellationToken.js";
import { Codec } from "./codec.js";
import { j2s } from "./helpers.js";
import {
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 0b75619ab..568e2f438 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -41,3 +41,4 @@ export * from "./iban.js";
export * from "./transaction-test-data.js";
export * from "./libeufin-api-types.js";
export * from "./MerchantApiClient.js";
+export * from "./bank-api-client.js";
diff --git a/packages/taler-util/tsconfig.json b/packages/taler-util/tsconfig.json
index 34f35d253..2e97142ce 100644
--- a/packages/taler-util/tsconfig.json
+++ b/packages/taler-util/tsconfig.json
@@ -5,8 +5,8 @@
"declaration": true,
"declarationMap": false,
"target": "ES2020",
- "module": "ES2020",
- "moduleResolution": "node16",
+ "module": "Node16",
+ "moduleResolution": "Node16",
"sourceMap": true,
"lib": ["ES2020"],
"types": ["node"],