aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-util
diff options
context:
space:
mode:
authorÖzgür Kesim <oec-taler@kesim.org>2023-09-09 07:34:11 +0200
committerÖzgür Kesim <oec-taler@kesim.org>2023-09-09 07:34:11 +0200
commit5495551071a3fdc36c38deb4c1cf6f4aa5b98bd4 (patch)
treeadf7730b190618a0499e50a2d43cf1b850cddd16 /packages/taler-util
parent94cfcc875065f988815c31aaf8ebf36f75ac5983 (diff)
parent6c3cfa9be7a332c2cc8490f25ebd6c73c8244842 (diff)
Merge branch 'master' into age-withdraw
Diffstat (limited to 'packages/taler-util')
-rw-r--r--packages/taler-util/package.json6
-rw-r--r--packages/taler-util/src/MerchantApiClient.ts331
-rw-r--r--packages/taler-util/src/backup-types.ts19
-rw-r--r--packages/taler-util/src/bank-api-client.ts325
-rw-r--r--packages/taler-util/src/bitcoin.ts8
-rw-r--r--packages/taler-util/src/http-common.ts7
-rw-r--r--packages/taler-util/src/http-impl.node.ts6
-rw-r--r--packages/taler-util/src/http-impl.qtart.ts6
-rw-r--r--packages/taler-util/src/index.ts3
-rw-r--r--packages/taler-util/src/libeufin-api-types.ts31
-rw-r--r--packages/taler-util/src/merchant-api-types.ts17
-rw-r--r--packages/taler-util/src/payto.ts6
-rw-r--r--packages/taler-util/src/time.ts14
-rw-r--r--packages/taler-util/src/transactions-types.ts17
-rw-r--r--packages/taler-util/src/wallet-types.ts62
-rw-r--r--packages/taler-util/tsconfig.json4
16 files changed, 792 insertions, 70 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/MerchantApiClient.ts b/packages/taler-util/src/MerchantApiClient.ts
new file mode 100644
index 000000000..cbdcb9fdf
--- /dev/null
+++ b/packages/taler-util/src/MerchantApiClient.ts
@@ -0,0 +1,331 @@
+/*
+ This file is part of GNU Taler
+ (C) 2023 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/>
+ */
+
+import {
+ createPlatformHttpLib,
+ expectSuccessResponseOrThrow,
+ readSuccessResponseJsonOrThrow,
+} from "./http.js";
+import { FacadeCredentials } from "./libeufin-api-types.js";
+import { Logger } from "./logging.js";
+import {
+ MerchantReserveCreateConfirmation,
+ codecForMerchantReserveCreateConfirmation,
+ TippingReserveStatus,
+ MerchantInstancesResponse,
+ MerchantPostOrderRequest,
+ MerchantPostOrderResponse,
+ codecForMerchantPostOrderResponse,
+ MerchantOrderPrivateStatusResponse,
+ codecForMerchantOrderPrivateStatusResponse,
+ RewardCreateRequest,
+ RewardCreateConfirmation,
+ MerchantTemplateAddDetails,
+} from "./merchant-api-types.js";
+import { AmountString } from "./taler-types.js";
+import { TalerProtocolDuration } from "./time.js";
+
+const logger = new Logger("MerchantApiClient.ts");
+
+export interface MerchantAuthConfiguration {
+ method: "external" | "token";
+ token?: string;
+}
+
+// FIXME: Why do we need this? Describe / fix!
+export interface PartialMerchantInstanceConfig {
+ auth?: MerchantAuthConfiguration;
+ id: string;
+ name: string;
+ paytoUris: string[];
+ address?: unknown;
+ jurisdiction?: unknown;
+ defaultWireTransferDelay?: TalerProtocolDuration;
+ defaultPayDelay?: TalerProtocolDuration;
+}
+
+export interface CreateMerchantTippingReserveRequest {
+ // Amount that the merchant promises to put into the reserve
+ initial_balance: AmountString;
+
+ // Exchange the merchant intends to use for tipping
+ exchange_url: string;
+
+ // Desired wire method, for example "iban" or "x-taler-bank"
+ wire_method: string;
+}
+
+export interface DeleteTippingReserveArgs {
+ reservePub: string;
+ purge?: boolean;
+}
+
+export interface MerchantInstanceConfig {
+ accounts: MerchantBankAccount[];
+ auth: MerchantAuthConfiguration;
+ id: string;
+ name: string;
+ address: unknown;
+ jurisdiction: unknown;
+ use_stefan: boolean;
+ default_wire_transfer_delay: TalerProtocolDuration;
+ default_pay_delay: TalerProtocolDuration;
+}
+
+interface MerchantBankAccount {
+ // The payto:// URI where the wallet will send coins.
+ payto_uri: string;
+
+ // Optional base URL for a facade where the
+ // merchant backend can see incoming wire
+ // transfers to reconcile its accounting
+ // with that of the exchange. Used by
+ // taler-merchant-wirewatch.
+ credit_facade_url?: string;
+
+ // Credentials for accessing the credit facade.
+ credit_facade_credentials?: FacadeCredentials;
+}
+
+export interface MerchantInstanceConfig {
+ accounts: MerchantBankAccount[];
+ auth: MerchantAuthConfiguration;
+ id: string;
+ name: string;
+ address: unknown;
+ jurisdiction: unknown;
+ use_stefan: boolean;
+ default_wire_transfer_delay: TalerProtocolDuration;
+ default_pay_delay: TalerProtocolDuration;
+}
+
+export interface PrivateOrderStatusQuery {
+ instance?: string;
+ orderId: string;
+ sessionId?: string;
+}
+
+/**
+ * Client for the GNU Taler merchant backend.
+ */
+export class MerchantApiClient {
+ /**
+ * Base URL for the particular instance that this merchant API client
+ * is for.
+ */
+ private baseUrl: string;
+
+ readonly auth: MerchantAuthConfiguration;
+
+ constructor(baseUrl: string, auth?: MerchantAuthConfiguration) {
+ this.baseUrl = baseUrl;
+
+ this.auth = auth ?? {
+ method: "external",
+ };
+ }
+
+ httpClient = createPlatformHttpLib();
+
+ async changeAuth(auth: MerchantAuthConfiguration): Promise<void> {
+ const url = new URL("private/auth", this.baseUrl);
+ const res = await this.httpClient.fetch(url.href, {
+ method: "POST",
+ body: auth,
+ headers: this.makeAuthHeader(),
+ });
+ await expectSuccessResponseOrThrow(res);
+ }
+
+ async deleteTippingReserve(req: DeleteTippingReserveArgs): Promise<void> {
+ const url = new URL(`private/reserves/${req.reservePub}`, this.baseUrl);
+ if (req.purge) {
+ url.searchParams.set("purge", "YES");
+ }
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "DELETE",
+ headers: this.makeAuthHeader(),
+ });
+ logger.info(`delete status: ${resp.status}`);
+ return;
+ }
+
+ async createTippingReserve(
+ req: CreateMerchantTippingReserveRequest,
+ ): Promise<MerchantReserveCreateConfirmation> {
+ const url = new URL("private/reserves", this.baseUrl);
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "POST",
+ body: req,
+ headers: this.makeAuthHeader(),
+ });
+ const respData = readSuccessResponseJsonOrThrow(
+ resp,
+ codecForMerchantReserveCreateConfirmation(),
+ );
+ return respData;
+ }
+
+ async getPrivateInstanceInfo(): Promise<any> {
+ const url = new URL("private", this.baseUrl);
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "GET",
+ headers: this.makeAuthHeader(),
+ });
+ return await resp.json();
+ }
+
+ async getPrivateTipReserves(): Promise<TippingReserveStatus> {
+ const url = new URL("private/reserves", this.baseUrl);
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "GET",
+ headers: this.makeAuthHeader(),
+ });
+ // FIXME: Validate!
+ return await resp.json();
+ }
+
+ async deleteInstance(instanceId: string) {
+ const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "DELETE",
+ headers: this.makeAuthHeader(),
+ });
+ await expectSuccessResponseOrThrow(resp);
+ }
+
+ async createInstance(req: MerchantInstanceConfig): Promise<void> {
+ const url = new URL("management/instances", this.baseUrl);
+ await this.httpClient.fetch(url.href, {
+ method: "POST",
+ body: req,
+ headers: this.makeAuthHeader(),
+ });
+ }
+
+ async getInstances(): Promise<MerchantInstancesResponse> {
+ const url = new URL("management/instances", this.baseUrl);
+ const resp = await this.httpClient.fetch(url.href, {
+ headers: this.makeAuthHeader(),
+ });
+ return resp.json();
+ }
+
+ async getInstanceFullDetails(instanceId: string): Promise<any> {
+ const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
+ try {
+ const resp = await this.httpClient.fetch(url.href, {
+ headers: this.makeAuthHeader(),
+ });
+ return resp.json();
+ } catch (e) {
+ throw e;
+ }
+ }
+
+ async createOrder(
+ req: MerchantPostOrderRequest,
+ ): Promise<MerchantPostOrderResponse> {
+ let url = new URL("private/orders", this.baseUrl);
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "POST",
+ body: req,
+ headers: this.makeAuthHeader(),
+ });
+ return readSuccessResponseJsonOrThrow(
+ resp,
+ codecForMerchantPostOrderResponse(),
+ );
+ }
+
+ async queryPrivateOrderStatus(
+ query: PrivateOrderStatusQuery,
+ ): Promise<MerchantOrderPrivateStatusResponse> {
+ const reqUrl = new URL(`private/orders/${query.orderId}`, this.baseUrl);
+ if (query.sessionId) {
+ reqUrl.searchParams.set("session_id", query.sessionId);
+ }
+ const resp = await this.httpClient.fetch(reqUrl.href, {
+ headers: this.makeAuthHeader(),
+ });
+ return readSuccessResponseJsonOrThrow(
+ resp,
+ codecForMerchantOrderPrivateStatusResponse(),
+ );
+ }
+
+ async giveTip(req: RewardCreateRequest): Promise<RewardCreateConfirmation> {
+ const reqUrl = new URL(`private/tips`, this.baseUrl);
+ const resp = await this.httpClient.fetch(reqUrl.href, {
+ method: "POST",
+ body: req,
+ });
+ // FIXME: validate
+ return resp.json();
+ }
+
+ async queryTippingReserves(): Promise<TippingReserveStatus> {
+ const reqUrl = new URL(`private/reserves`, this.baseUrl);
+ const resp = await this.httpClient.fetch(reqUrl.href, {
+ headers: this.makeAuthHeader(),
+ });
+ // FIXME: validate
+ return resp.json();
+ }
+
+ async giveRefund(r: {
+ instance: string;
+ orderId: string;
+ amount: string;
+ justification: string;
+ }): Promise<{ talerRefundUri: string }> {
+ const reqUrl = new URL(`private/orders/${r.orderId}/refund`, this.baseUrl);
+ const resp = await this.httpClient.fetch(reqUrl.href, {
+ method: "POST",
+ body: {
+ refund: r.amount,
+ reason: r.justification,
+ },
+ });
+ const respBody = await resp.json();
+ return {
+ talerRefundUri: respBody.taler_refund_uri,
+ };
+ }
+
+ async createTemplate(req: MerchantTemplateAddDetails) {
+ let url = new URL("private/templates", this.baseUrl);
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "POST",
+ body: req,
+ headers: this.makeAuthHeader(),
+ });
+ if (resp.status !== 204) {
+ throw Error("failed to create template");
+ }
+ }
+
+ private makeAuthHeader(): Record<string, string> {
+ switch (this.auth.method) {
+ case "external":
+ return {};
+ case "token":
+ return {
+ Authorization: `Bearer ${this.auth.token}`,
+ };
+ }
+ }
+}
diff --git a/packages/taler-util/src/backup-types.ts b/packages/taler-util/src/backup-types.ts
index 2eba1e4ca..8c38b70a6 100644
--- a/packages/taler-util/src/backup-types.ts
+++ b/packages/taler-util/src/backup-types.ts
@@ -14,6 +14,8 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { AmountString } from "./taler-types.js";
+
export interface BackupRecovery {
walletRootPriv: string;
providers: {
@@ -21,3 +23,20 @@ export interface BackupRecovery {
url: string;
}[];
}
+
+export class BackupBackupProviderTerms {
+ /**
+ * Last known supported protocol version.
+ */
+ supported_protocol_version: string;
+
+ /**
+ * Last known annual fee.
+ */
+ annual_fee: AmountString;
+
+ /**
+ * Last known storage limit.
+ */
+ storage_limit_in_megabytes: number;
+}
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/bitcoin.ts b/packages/taler-util/src/bitcoin.ts
index 8c22ba522..37b7ae6b9 100644
--- a/packages/taler-util/src/bitcoin.ts
+++ b/packages/taler-util/src/bitcoin.ts
@@ -69,10 +69,10 @@ export function generateFakeSegwitAddress(
addr[0] === "t" && addr[1] == "b"
? "tb"
: addr[0] === "b" && addr[1] == "c" && addr[2] === "r" && addr[3] == "t"
- ? "bcrt"
- : addr[0] === "b" && addr[1] == "c"
- ? "bc"
- : undefined;
+ ? "bcrt"
+ : addr[0] === "b" && addr[1] == "c"
+ ? "bc"
+ : undefined;
if (prefix === undefined) throw new Error("unknown bitcoin net");
const addr1 = segwit.default.encode(prefix, 0, first_part);
diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts
index 93cf9bba0..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 {
@@ -436,7 +436,10 @@ export function getExpiry(
export interface HttpLibArgs {
enableThrottling?: boolean;
- allowHttp?: boolean;
+ /**
+ * Only allow HTTPS connections, not plain http.
+ */
+ requireTls?: boolean;
}
export function encodeBody(body: any): ArrayBuffer {
diff --git a/packages/taler-util/src/http-impl.node.ts b/packages/taler-util/src/http-impl.node.ts
index 07648a28d..528d303be 100644
--- a/packages/taler-util/src/http-impl.node.ts
+++ b/packages/taler-util/src/http-impl.node.ts
@@ -63,11 +63,11 @@ const textDecoder = new TextDecoder();
export class HttpLibImpl implements HttpRequestLibrary {
private throttle = new RequestThrottler();
private throttlingEnabled = true;
- private allowHttp = false;
+ private requireTls = false;
constructor(args?: HttpLibArgs) {
this.throttlingEnabled = args?.enableThrottling ?? false;
- this.allowHttp = args?.allowHttp ?? false;
+ this.requireTls = args?.requireTls ?? false;
}
/**
@@ -94,7 +94,7 @@ export class HttpLibImpl implements HttpRequestLibrary {
`request to origin ${parsedUrl.origin} was throttled`,
);
}
- if (!this.allowHttp && parsedUrl.protocol !== "https:") {
+ if (this.requireTls && parsedUrl.protocol !== "https:") {
throw TalerError.fromDetail(
TalerErrorCode.WALLET_NETWORK_ERROR,
{
diff --git a/packages/taler-util/src/http-impl.qtart.ts b/packages/taler-util/src/http-impl.qtart.ts
index 3e076e96d..fb642ac89 100644
--- a/packages/taler-util/src/http-impl.qtart.ts
+++ b/packages/taler-util/src/http-impl.qtart.ts
@@ -41,11 +41,11 @@ const textDecoder = new TextDecoder();
export class HttpLibImpl implements HttpRequestLibrary {
private throttle = new RequestThrottler();
private throttlingEnabled = true;
- private allowHttp = false;
+ private requireTls = false;
constructor(args?: HttpLibArgs) {
this.throttlingEnabled = args?.enableThrottling ?? false;
- this.allowHttp = args?.allowHttp ?? false;
+ this.requireTls = args?.requireTls ?? false;
}
/**
@@ -72,7 +72,7 @@ export class HttpLibImpl implements HttpRequestLibrary {
`request to origin ${parsedUrl.origin} was throttled`,
);
}
- if (!this.allowHttp && parsedUrl.protocol !== "https:") {
+ if (this.requireTls && parsedUrl.protocol !== "https:") {
throw TalerError.fromDetail(
TalerErrorCode.WALLET_NETWORK_ERROR,
{
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index cfd0f7c47..568e2f438 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -39,3 +39,6 @@ export * from "./merchant-api-types.js";
export * from "./errors.js";
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/src/libeufin-api-types.ts b/packages/taler-util/src/libeufin-api-types.ts
new file mode 100644
index 000000000..aa3d0cb7a
--- /dev/null
+++ b/packages/taler-util/src/libeufin-api-types.ts
@@ -0,0 +1,31 @@
+/*
+ This file is part of GNU Taler
+ (C) 2023 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/>
+ */
+
+export type FacadeCredentials =
+ | NoFacadeCredentials
+ | BasicAuthFacadeCredentials;
+export interface NoFacadeCredentials {
+ type: "none";
+}
+export interface BasicAuthFacadeCredentials {
+ type: "basic";
+
+ // Username to use to authenticate
+ username: string;
+
+ // Password to use to authenticate
+ password: string;
+}
diff --git a/packages/taler-util/src/merchant-api-types.ts b/packages/taler-util/src/merchant-api-types.ts
index 9f00173f2..9933b93dc 100644
--- a/packages/taler-util/src/merchant-api-types.ts
+++ b/packages/taler-util/src/merchant-api-types.ts
@@ -47,6 +47,7 @@ import {
WireAccount,
codecForWireAccount,
codecForList,
+ FacadeCredentials,
} from "@gnu-taler/taler-util";
export interface MerchantPostOrderRequest {
@@ -384,3 +385,19 @@ export const codecForMerchantReserveCreateConfirmation =
.property("accounts", codecForList(codecForWireAccount()))
.property("reserve_pub", codecForString())
.build("MerchantReserveCreateConfirmation");
+
+export interface AccountAddDetails {
+ // payto:// URI of the account.
+ payto_uri: string;
+
+ // URL from where the merchant can download information
+ // about incoming wire transfers to this account.
+ credit_facade_url?: string;
+
+ // Credentials to use when accessing the credit facade.
+ // Never returned on a GET (as this may be somewhat
+ // sensitive data). Can be set in POST
+ // or PATCH requests to update (or delete) credentials.
+ // To really delete credentials, set them to the type: "none".
+ credit_facade_credentials?: FacadeCredentials;
+}
diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts
index 2b0af4cc2..60c4ba838 100644
--- a/packages/taler-util/src/payto.ts
+++ b/packages/taler-util/src/payto.ts
@@ -24,7 +24,7 @@ export type PaytoUri =
| PaytoUriBitcoin;
export interface PaytoUriGeneric {
- targetType: string;
+ targetType: PaytoType | string;
targetPath: string;
params: { [name: string]: string };
}
@@ -55,6 +55,8 @@ export interface PaytoUriBitcoin extends PaytoUriGeneric {
const paytoPfx = "payto://";
+export type PaytoType = "iban" | "bitcoin" | "x-taler-bank"
+
export function buildPayto(
type: "iban",
iban: string,
@@ -71,7 +73,7 @@ export function buildPayto(
account: string,
): PaytoUriTalerBank;
export function buildPayto(
- type: "iban" | "bitcoin" | "x-taler-bank",
+ type: PaytoType,
first: string,
second?: string,
): PaytoUriGeneric {
diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts
index 55cda08a5..46ed37637 100644
--- a/packages/taler-util/src/time.ts
+++ b/packages/taler-util/src/time.ts
@@ -154,13 +154,27 @@ export interface TalerProtocolDuration {
readonly d_us: number | "forever";
}
+/**
+ * Timeshift in milliseconds.
+ */
let timeshift = 0;
+/**
+ * Set timetravel offset in milliseconds.
+ *
+ * Use carefully and only for testing.
+ */
export function setDangerousTimetravel(dt: number): void {
timeshift = dt;
}
export namespace Duration {
+ export function toMilliseconds(d: Duration): number {
+ if (d.d_ms === "forever") {
+ return Number.MAX_VALUE;
+ }
+ return d.d_ms;
+ }
export function getRemaining(
deadline: AbsoluteTime,
now = AbsoluteTime.now(),
diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts
index 6331bc731..304183ceb 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -528,22 +528,6 @@ export interface OrderShortInfo {
summary_i18n?: InternationalizedString;
/**
- * List of products that are part of the order
- */
- products: Product[] | undefined;
-
- /**
- * Time indicating when the order should be delivered.
- * May be overwritten by individual products.
- */
- delivery_date?: TalerProtocolTimestamp;
-
- /**
- * Delivery location for (all!) products.
- */
- delivery_location?: Location;
-
- /**
* URL of the fulfillment, given by the merchant
*/
fulfillmentUrl?: string;
@@ -724,7 +708,6 @@ export const codecForOrderShortInfo = (): Codec<OrderShortInfo> =>
.property("fulfillmentUrl", codecOptional(codecForString()))
.property("merchant", codecForMerchantInfo())
.property("orderId", codecForString())
- .property("products", codecOptional(codecForList(codecForProduct())))
.property("summary", codecForString())
.property("summary_i18n", codecOptional(codecForInternationalizedString()))
.build("OrderShortInfo");
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index accab746f..c6f19c73f 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -1076,13 +1076,6 @@ export interface KnownBankAccounts {
accounts: KnownBankAccountsInfo[];
}
-export interface ExchangeTosStatusDetails {
- acceptedVersion?: string;
- currentVersion?: string;
- contentType?: string;
- content?: string;
-}
-
/**
* Wire fee for one wire method
*/
@@ -1253,7 +1246,6 @@ export interface ExchangeFullDetails {
exchangeBaseUrl: string;
currency: string;
paytoUris: string[];
- tos: ExchangeTosStatusDetails;
auditors: ExchangeAuditor[];
wireInfo: WireInfo;
denomFees: DenomOperationMap<FeeDescription[]>;
@@ -1317,14 +1309,6 @@ const codecForExchangeAuditor = (): Codec<ExchangeAuditor> =>
.property("denomination_keys", codecForList(codecForAuditorDenomSig()))
.build("codecForExchangeAuditor");
-const codecForExchangeTos = (): Codec<ExchangeTosStatusDetails> =>
- buildCodecForObject<ExchangeTosStatusDetails>()
- .property("acceptedVersion", codecOptional(codecForString()))
- .property("currentVersion", codecOptional(codecForString()))
- .property("contentType", codecOptional(codecForString()))
- .property("content", codecOptional(codecForString()))
- .build("ExchangeTos");
-
export const codecForFeeDescriptionPair = (): Codec<FeeDescriptionPair> =>
buildCodecForObject<FeeDescriptionPair>()
.property("group", codecForString())
@@ -1357,7 +1341,6 @@ export const codecForExchangeFullDetails = (): Codec<ExchangeFullDetails> =>
.property("currency", codecForString())
.property("exchangeBaseUrl", codecForString())
.property("paytoUris", codecForList(codecForString()))
- .property("tos", codecForExchangeTos())
.property("auditors", codecForList(codecForExchangeAuditor()))
.property("wireInfo", codecForWireInfo())
.property("denomFees", codecForFeesByOperations())
@@ -1465,22 +1448,11 @@ export interface ExchangeWithdrawalDetails {
selectedDenoms: DenomSelectionState;
/**
- * Does the wallet know about an auditor for
- * the exchange that the reserve.
- */
- isAudited: boolean;
-
- /**
* Did the user already accept the current terms of service for the exchange?
*/
termsOfServiceAccepted: boolean;
/**
- * The exchange is trusted directly.
- */
- isTrusted: boolean;
-
- /**
* The earliest deposit expiration of the selected coins.
*/
earliestDepositExpiration: TalerProtocolTimestamp;
@@ -2466,7 +2438,7 @@ export interface PreparePeerPushCreditResponse {
amount: AmountString;
amountRaw: AmountString;
amountEffective: AmountString;
- peerPushPaymentIncomingId: string;
+ peerPushCreditId: string;
transactionId: string;
}
@@ -2481,7 +2453,7 @@ export interface PreparePeerPullDebitResponse {
amountRaw: AmountString;
amountEffective: AmountString;
- peerPullPaymentIncomingId: string;
+ peerPullDebitId: string;
transactionId: string;
}
@@ -2504,7 +2476,7 @@ export interface ConfirmPeerPushCreditRequest {
*
* @deprecated specify transactionId instead!
*/
- peerPushPaymentIncomingId?: string;
+ peerPushCreditId?: string;
transactionId?: string;
}
@@ -2519,7 +2491,7 @@ export interface AcceptPeerPullPaymentResponse {
export const codecForConfirmPeerPushPaymentRequest =
(): Codec<ConfirmPeerPushCreditRequest> =>
buildCodecForObject<ConfirmPeerPushCreditRequest>()
- .property("peerPushPaymentIncomingId", codecOptional(codecForString()))
+ .property("peerPushCreditId", codecOptional(codecForString()))
.property("transactionId", codecOptional(codecForString()))
.build("ConfirmPeerPushCreditRequest");
@@ -2529,7 +2501,7 @@ export interface ConfirmPeerPullDebitRequest {
*
* @deprecated use transactionId instead
*/
- peerPullPaymentIncomingId?: string;
+ peerPullDebitId?: string;
transactionId?: string;
}
@@ -2547,7 +2519,7 @@ export const codecForApplyDevExperiment =
export const codecForAcceptPeerPullPaymentRequest =
(): Codec<ConfirmPeerPullDebitRequest> =>
buildCodecForObject<ConfirmPeerPullDebitRequest>()
- .property("peerPullPaymentIncomingId", codecOptional(codecForString()))
+ .property("peerPullDebitId", codecOptional(codecForString()))
.property("transactionId", codecOptional(codecForString()))
.build("ConfirmPeerPullDebitRequest");
@@ -2673,3 +2645,25 @@ export interface RecoverStoredBackupRequest {
export interface DeleteStoredBackupRequest {
name: string;
}
+
+export const codecForDeleteStoredBackupRequest =
+ (): Codec<DeleteStoredBackupRequest> =>
+ buildCodecForObject<DeleteStoredBackupRequest>()
+ .property("name", codecForString())
+ .build("DeleteStoredBackupRequest");
+
+export const codecForRecoverStoredBackupRequest =
+ (): Codec<RecoverStoredBackupRequest> =>
+ buildCodecForObject<RecoverStoredBackupRequest>()
+ .property("name", codecForString())
+ .build("RecoverStoredBackupRequest");
+
+export interface TestingSetTimetravelRequest {
+ offsetMs: number;
+}
+
+export const codecForTestingSetTimetravelRequest =
+ (): Codec<TestingSetTimetravelRequest> =>
+ buildCodecForObject<TestingSetTimetravelRequest>()
+ .property("offsetMs", codecForNumber())
+ .build("TestingSetTimetravelRequest");
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"],