From b5312973a6f25ac73c8e29b659bf7ea23d77ac5e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 18 Mar 2024 12:12:57 -0300 Subject: wip --- packages/bank-ui/package.json | 8 +- .../merchant-backoffice-ui/src/hooks/backend.ts | 2 +- .../merchant-backoffice-ui/src/hooks/merchant.ts | 211 +++++++ packages/taler-util/.eslintrc.cjs | 28 + packages/taler-util/package.json | 4 +- packages/taler-util/src/http-client/README.md | 24 + packages/taler-util/src/http-client/merchant.ts | 679 +++++++++++++++++++++ packages/taler-util/src/http-client/types.ts | 106 ++-- 8 files changed, 1018 insertions(+), 44 deletions(-) create mode 100644 packages/merchant-backoffice-ui/src/hooks/merchant.ts create mode 100644 packages/taler-util/.eslintrc.cjs create mode 100644 packages/taler-util/src/http-client/README.md create mode 100644 packages/taler-util/src/http-client/merchant.ts (limited to 'packages') diff --git a/packages/bank-ui/package.json b/packages/bank-ui/package.json index dd5589fe2..c25decf8d 100644 --- a/packages/bank-ui/package.json +++ b/packages/bank-ui/package.json @@ -27,6 +27,10 @@ }, "devDependencies": { "eslint": "^8.56.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.33.2", "@gnu-taler/pogen": "^0.0.5", "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.9", @@ -34,13 +38,9 @@ "@types/history": "^4.7.8", "@types/mocha": "^10.0.1", "@types/node": "^18.11.17", - "@typescript-eslint/eslint-plugin": "^6.19.0", - "@typescript-eslint/parser": "^6.19.0", "autoprefixer": "^10.4.14", "chai": "^4.3.6", "esbuild": "^0.19.9", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-react": "^7.33.2", "mocha": "9.2.0", "po2json": "^0.4.5", "tailwindcss": "^3.3.2", diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts b/packages/merchant-backoffice-ui/src/hooks/backend.ts index a3bb43545..292261bc8 100644 --- a/packages/merchant-backoffice-ui/src/hooks/backend.ts +++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts @@ -278,7 +278,7 @@ export function useBackendBaseRequest(): useBackendBaseRequestType { export function useBackendInstanceRequest(): useBackendInstanceRequestType { const { url: rootBackendUrl, token: rootToken } = useBackendContext(); - const { token: instanceToken, id, admin } = useInstanceContext(); + const { token: instanceToken, admin } = useInstanceContext(); const { request: requestHandler } = useApiContext(); const { baseUrl, token: loginToken } = !admin diff --git a/packages/merchant-backoffice-ui/src/hooks/merchant.ts b/packages/merchant-backoffice-ui/src/hooks/merchant.ts new file mode 100644 index 000000000..5d5785442 --- /dev/null +++ b/packages/merchant-backoffice-ui/src/hooks/merchant.ts @@ -0,0 +1,211 @@ +/* + This file is part of GNU Taler + (C) 2021-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 + */ +import { + HttpResponse, + HttpResponseOk, + HttpResponsePaginated, + RequestError, +} from "@gnu-taler/web-util/browser"; +import { useEffect, useState } from "preact/hooks"; +import { MerchantBackend } from "../declaration.js"; +import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js"; +import { useBackendInstanceRequest, useMatchMutate } from "./backend.js"; + +// FIX default import https://github.com/microsoft/TypeScript/issues/49189 +import _useSWR, { SWRHook, mutate } from "swr"; +const useSWR = _useSWR as unknown as SWRHook; + +// const MOCKED_ACCOUNTS: Record = { +// "hwire1": { +// h_wire: "hwire1", +// payto_uri: "payto://fake/iban/123", +// salt: "qwe", +// }, +// "hwire2": { +// h_wire: "hwire2", +// payto_uri: "payto://fake/iban/123", +// salt: "qwe2", +// }, +// } + +export function useBankAccountAPI(): BankAccountAPI { + const mutateAll = useMatchMutate(); + const { request } = useBackendInstanceRequest(); + + const createBankAccount = async ( + data: MerchantBackend.BankAccounts.AccountAddDetails, + ): Promise> => { + // MOCKED_ACCOUNTS[data.h_wire] = data + // return Promise.resolve({ ok: true, data: undefined }); + const res = await request(`/private/accounts`, { + method: "POST", + data, + }); + await mutateAll(/.*private\/accounts.*/); + return res; + }; + + const updateBankAccount = async ( + h_wire: string, + data: MerchantBackend.BankAccounts.AccountPatchDetails, + ): Promise> => { + // MOCKED_ACCOUNTS[h_wire].credit_facade_credentials = data.credit_facade_credentials + // MOCKED_ACCOUNTS[h_wire].credit_facade_url = data.credit_facade_url + // return Promise.resolve({ ok: true, data: undefined }); + const res = await request(`/private/accounts/${h_wire}`, { + method: "PATCH", + data, + }); + await mutateAll(/.*private\/accounts.*/); + return res; + }; + + const deleteBankAccount = async ( + h_wire: string, + ): Promise> => { + // delete MOCKED_ACCOUNTS[h_wire] + // return Promise.resolve({ ok: true, data: undefined }); + const res = await request(`/private/accounts/${h_wire}`, { + method: "DELETE", + }); + await mutateAll(/.*private\/accounts.*/); + return res; + }; + + return { + createBankAccount, + updateBankAccount, + deleteBankAccount, + }; +} + +export interface BankAccountAPI { + createBankAccount: ( + data: MerchantBackend.BankAccounts.AccountAddDetails, + ) => Promise>; + updateBankAccount: ( + id: string, + data: MerchantBackend.BankAccounts.AccountPatchDetails, + ) => Promise>; + deleteBankAccount: (id: string) => Promise>; +} + +export interface InstanceBankAccountFilter { +} + +export function revalidateInstanceBankAccounts() { + // mutate(key => key instanceof) + return mutate((key) => Array.isArray(key) && key[key.length - 1] === "/private/accounts", undefined, { revalidate: true }); +} +export function useInstanceBankAccounts( + args?: InstanceBankAccountFilter, + updatePosition?: (id: string) => void, +): HttpResponsePaginated< + MerchantBackend.BankAccounts.AccountsSummaryResponse, + MerchantBackend.ErrorDetail +> { + + const { fetcher } = useBackendInstanceRequest(); + + const [pageAfter, setPageAfter] = useState(1); + + const totalAfter = pageAfter * PAGE_SIZE; + const { + data: afterData, + error: afterError, + isValidating: loadingAfter, + } = useSWR< + HttpResponseOk, + RequestError + >([`/private/accounts`], fetcher); + + const [lastAfter, setLastAfter] = useState< + HttpResponse< + MerchantBackend.BankAccounts.AccountsSummaryResponse, + MerchantBackend.ErrorDetail + > + >({ loading: true }); + useEffect(() => { + if (afterData) setLastAfter(afterData); + }, [afterData /*, beforeData*/]); + + if (afterError) return afterError.cause; + + // if the query returns less that we ask, then we have reach the end or beginning + const isReachingEnd = + afterData && afterData.data.accounts.length < totalAfter; + const isReachingStart = false; + + const pagination = { + isReachingEnd, + isReachingStart, + loadMore: () => { + if (!afterData || isReachingEnd) return; + if (afterData.data.accounts.length < MAX_RESULT_SIZE) { + setPageAfter(pageAfter + 1); + } else { + const from = `${afterData.data.accounts[afterData.data.accounts.length - 1] + .h_wire + }`; + if (from && updatePosition) updatePosition(from); + } + }, + loadMorePrev: () => { + }, + }; + + const accounts = !afterData ? [] : (afterData || lastAfter).data.accounts; + if (loadingAfter /* || loadingBefore */) + return { loading: true, data: { accounts } }; + if (/*beforeData &&*/ afterData) { + return { ok: true, data: { accounts }, ...pagination }; + } + return { loading: true }; +} + +export function useBankAccountDetails( + h_wire: string, +): HttpResponse< + MerchantBackend.BankAccounts.BankAccountEntry, + MerchantBackend.ErrorDetail +> { + // return { + // ok: true, + // data: { + // ...MOCKED_ACCOUNTS[h_wire], + // active: true, + // } + // } + const { fetcher } = useBackendInstanceRequest(); + + const { data, error, isValidating } = useSWR< + HttpResponseOk, + RequestError + >([`/private/accounts/${h_wire}`], fetcher, { + refreshInterval: 0, + refreshWhenHidden: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + refreshWhenOffline: false, + }); + + if (isValidating) return { loading: true, data: data?.data }; + if (data) { + return data; + } + if (error) return error.cause; + return { loading: true }; +} diff --git a/packages/taler-util/.eslintrc.cjs b/packages/taler-util/.eslintrc.cjs new file mode 100644 index 000000000..05618b499 --- /dev/null +++ b/packages/taler-util/.eslintrc.cjs @@ -0,0 +1,28 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'header'], + root: true, + rules: { + "react/no-unknown-property": 0, + "react/no-unescaped-entities": 0, + "@typescript-eslint/no-namespace": 0, + "@typescript-eslint/no-unused-vars": [2,{argsIgnorePattern:"^_"}], + "header/header": [2,"copyleft-header.js"] + }, + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + jsx: true, + }, + settings: { + react: { + version: "18", + pragma: "h", + } + }, +}; diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json index 1caf1af9e..c1ff9873f 100644 --- a/packages/taler-util/package.json +++ b/packages/taler-util/package.json @@ -64,11 +64,13 @@ "pretty": "prettier --write src" }, "devDependencies": { + "eslint": "^8.56.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", "@types/follow-redirects": "^1.14.4", "@types/node": "^18.11.17", "ava": "^6.0.1", "esbuild": "^0.19.9", - "prettier": "^3.1.1", "typescript": "^5.3.3" }, "dependencies": { diff --git a/packages/taler-util/src/http-client/README.md b/packages/taler-util/src/http-client/README.md new file mode 100644 index 000000000..5af5d48a7 --- /dev/null +++ b/packages/taler-util/src/http-client/README.md @@ -0,0 +1,24 @@ +HTTP Cclients +------------- + +This folder contain class or function specifically designed to facilitate HTTP client +interactions with a the core systems. + +These API defines: + +1. **API Communication**: Handle communication with the component API, +abstracting away the details of HTTP requests and responses. +This includes making GET, POST, PUT, and DELETE requests to the servers. + +2. **Data Formatting**: Responsible for formatting requests to the API in a +way that's expected by the servers (JSON) and parsing the responses back +into formats usable by the client. + +3. **Authentication and Security**: Handling authentication with the server API, +which could involve sending API keys, client credentials, or managing tokens. +It might also implement security features to ensure data integrity and confidentiality during transit. + +4. **Error Handling**: Providing robust error handling and retry mechanisms +for failed HTTP requests, including logging and potentially user notifications for critical failures. + +5. **Data Validation**: Before sending requests, it could validate the data to ensure it meets the API's expected format, types, and value ranges, reducing the likelihood of errors and improving system reliability. diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts new file mode 100644 index 000000000..32384f8b4 --- /dev/null +++ b/packages/taler-util/src/http-client/merchant.ts @@ -0,0 +1,679 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 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 + */ + +import { + HttpStatusCode, + LibtoolVersion, + MerchantApiClient, + TalerMerchantApi, + codecForMerchantConfig +} from "@gnu-taler/taler-util"; +import { + HttpRequestLibrary, + createPlatformHttpLib, +} from "@gnu-taler/taler-util/http"; +import { + FailCasesByMethod, + ResultByMethod, + opSuccessFromHttp, + opUnknownFailure, +} from "../operation.js"; +import { CacheEvictor, nullEvictor } from "./utils.js"; + +export enum TalerMerchantCacheEviction { + CREATE_ORDER, +} +/** + * Protocol version spoken with the core bank. + * + * Endpoint must be ordered in the same way that in the docs + * Response code (http and taler) must have the same order that in the docs + * That way is easier to see changes + * + * Uses libtool's current:revision:age versioning. + */ +class TalerMerchantInstanceHttpClient { + + readonly httpLib: HttpRequestLibrary; + readonly cacheEvictor: CacheEvictor; + + constructor( + readonly baseUrl: string, + httpClient?: HttpRequestLibrary, + cacheEvictor?: CacheEvictor, + ) { + this.httpLib = httpClient ?? createPlatformHttpLib(); + this.cacheEvictor = cacheEvictor ?? nullEvictor; + } + + // + // Wallet API + // + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-orders-$ORDER_ID-claim + */ + async claimOrder(orderId: string, body: TalerMerchantApi.ClaimRequest) { + const url = new URL(`orders/${orderId}/claim`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + // switch (resp.status) { + // case HttpStatusCode.Ok: + // return opSuccessFromHttp(resp, codecForClaimResponse()); + // case HttpStatusCode.Conflict: + // return opKnownHttpFailure(resp.status, resp) + // case HttpStatusCode.NotFound: + // return opKnownHttpFailure(resp.status, resp) + // default: + // return opUnknownFailure(resp, await resp.text()); + // } + } + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-orders-$ORDER_ID-pay + */ + async makePayment(orderId: string, body: TalerMerchantApi.PayRequest) { + const url = new URL(`orders/${orderId}/pay`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + + /// + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-orders-$ORDER_ID + */ + + async getPaymentStatus(orderId: string, params: TalerMerchantApi.PaymentStatusRequestParams = {}) { + const url = new URL(`orders/${orderId}`, this.baseUrl); + + if (params.allowRefundedForRepurchase !== undefined) { + url.searchParams.set("allow_refunded_for_repurchase", params.allowRefundedForRepurchase ? "YES" : "NO") + } + if (params.awaitRefundObtained !== undefined) { + url.searchParams.set("await_refund_obtained", params.allowRefundedForRepurchase ? "YES" : "NO") + } + if (params.claimToken !== undefined) { + url.searchParams.set("token", params.claimToken) + } + if (params.contractTermHash !== undefined) { + url.searchParams.set("h_contract", params.contractTermHash) + } + if (params.refund !== undefined) { + url.searchParams.set("refund", params.refund) + } + if (params.sessionId !== undefined) { + url.searchParams.set("session_id", params.sessionId) + } + if (params.timeout !== undefined) { + url.searchParams.set("timeout_ms", String(params.timeout)) + } + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + // body, + }); + + /// + } + + /** + * https://docs.taler.net/core/api-merchant.html#demonstrating-payment + */ + async demostratePayment(orderId: string, body: TalerMerchantApi.PaidRequest) { + const url = new URL(`orders/${orderId}/paid`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + } + + /** + * https://docs.taler.net/core/api-merchant.html#aborting-incomplete-payments + */ + async abortIncompletePayment(orderId: string, body: TalerMerchantApi.AbortRequest) { + const url = new URL(`orders/${orderId}/abort`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + } + + /** + * https://docs.taler.net/core/api-merchant.html#obtaining-refunds + */ + async obtainRefund(orderId: string, body: TalerMerchantApi.WalletRefundRequest) { + const url = new URL(`orders/${orderId}/refund`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + } + + // + // Management + // + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-auth + */ + async updateCurrentInstanceAuthentication(body: TalerMerchantApi.InstanceAuthConfigurationMessage) { + const url = new URL(`private/auth`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + + // + } + + /** + * Get the auth api agaisnt the current instance + * + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-token + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-token + */ + getAuthenticationAPI(): URL { + return new URL(`/`, this.baseUrl); + } + + /** + * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private + */ + async updateCurrentInstance(body: TalerMerchantApi.InstanceReconfigurationMessage) { + const url = new URL(`private`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "PATCH", + body, + }); + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private + * + */ + async getCurrentInstance() { + const url = new URL(`private`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + }); + } + + /** + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private + */ + async deleteCurrentInstance(params: { purge?: boolean }) { + const url = new URL(`private`, this.baseUrl); + + if (params.purge) { + url.searchParams.set("purge", "YES") + } + + const resp = await this.httpLib.fetch(url.href, { + method: "DELETE", + }); + } + + /** + * https://docs.taler.net/core/api-merchant.html#get--instances-$INSTANCE-private-kyc + */ + async getCurrentIntanceKycStatus(params: { wireHash?: string, exchangeURL?: string, timeout: number }) { + const url = new URL(`private/kyc`, this.baseUrl); + + if (params.wireHash) { + url.searchParams.set("h_wire", params.wireHash) + } + if (params.exchangeURL) { + url.searchParams.set("exchange_url", params.exchangeURL) + } + if (params.timeout) { + url.searchParams.set("timeout_ms", String(params.timeout)) + } + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + }); + + } + + // + // Bank Accounts + // + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-accounts + */ + async addAccount(body: TalerMerchantApi.AccountAddDetails) { + const url = new URL(`private/accounts`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + } + + /** + * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-accounts-$H_WIRE + */ + async updateAccount(wireAccount: string, body: TalerMerchantApi.AccountPatchDetails) { + const url = new URL(`private/accounts/${wireAccount}`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "PATCH", + body, + }); + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-accounts + */ + async listAccounts() { + const url = new URL(`private/accounts`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + }); + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-accounts-$H_WIRE + */ + async getAccount(wireAccount: string) { + const url = new URL(`private/accounts/${wireAccount}`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + }); + } + + /** + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-accounts-$H_WIRE + */ + async deleteAccount(wireAccount: string) { + const url = new URL(`private/accounts/${wireAccount}`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "DELETE", + }); + } + + // + // Inventory Management + // + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-products + */ + async addProduct() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-products-$PRODUCT_ID + */ + async updateProduct() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-products + */ + async listProducts() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-products-$PRODUCT_ID + */ + async getProduct() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#reserving-inventory + */ + async lockProduct() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#removing-products-from-inventory + */ + async removeProduct() { + } + + // + // Payment processing + // + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-orders + */ + async createOrder() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#inspecting-orders + */ + async listOrders() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-orders-$ORDER_ID + */ + async getOrder() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#private-order-data-cleanup + */ + async forgetOrder() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-orders-$ORDER_ID + */ + async deleteOrder() { + } + + // + // Refunds + // + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-orders-$ORDER_ID-refund + */ + async addRefund() { + } + + // + // Wire Transfer + // + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-transfers + */ + async informWireTransfer() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-transfers + */ + async listWireTransfers() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-transfers-$TID + */ + async deleteWireTransfer() { + } + + // + // OTP Devices + // + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-otp-devices + */ + async addOtpDevice() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-otp-devices-$DEVICE_ID + */ + async updateOtpDevice() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-otp-devices + */ + async listOtpDevices() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-otp-devices-$DEVICE_ID + */ + async getOtpDevice() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-otp-devices-$DEVICE_ID + */ + async deleteOtpDevice() { + } + + // + // Templates + // + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-templates + */ + async addTemplate() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCE]-private-templates-$TEMPLATE_ID + */ + async updateTemplate() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#inspecting-template + */ + async listTemplates() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-templates-$TEMPLATE_ID + */ + async getTemplate() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-templates-$TEMPLATE_ID + */ + async deleteTemplate() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCES]-templates-$TEMPLATE_ID + */ + async useTemplate() { + } + + // + // Webhooks + // + + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCES]-private-webhooks + */ + async addWebhook() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCES]-private-webhooks-$WEBHOOK_ID + */ + async updateWebhook() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-webhooks + */ + async listWebhooks() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-webhooks-$WEBHOOK_ID + */ + async getWebhook() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCES]-private-webhooks-$WEBHOOK_ID + */ + async removeWebhook() { + } + + // + // token families + // + + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCES]-private-tokenfamilies + */ + async createTokenFamily() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#patch-[-instances-$INSTANCES]-private-tokenfamilies-$TOKEN_FAMILY_SLUG + */ + async updateTokenFamily() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-tokenfamilies + */ + async listTokenFamilies() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCES]-private-tokenfamilies-$TOKEN_FAMILY_SLUG + */ + async getTokenFamily() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCES]-private-tokenfamilies-$TOKEN_FAMILY_SLUG + */ + async deleteTokenFamily() { + } + +} + +export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttpClient { + public readonly PROTOCOL_VERSION = "10:0:6"; + + httpLib: HttpRequestLibrary; + cacheEvictor: CacheEvictor; + constructor( + readonly baseUrl: string, + httpClient?: HttpRequestLibrary, + cacheEvictor?: CacheEvictor, + ) { + super(baseUrl, httpClient, cacheEvictor) + } + + isCompatible(version: string): boolean { + const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version); + return compare?.compatible ?? false; + } + + /** + * https://docs.taler.net/core/api-merchant.html#get--config + * + */ + async getConfig() { + const url = new URL(`config`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForMerchantConfig()); + default: + return opUnknownFailure(resp, await resp.text()); + } + } + + + // + // Instance Management + // + + /** + * https://docs.taler.net/core/api-merchant.html#post--management-instances + */ + async createInstance(body: TalerMerchantApi.InstanceConfigurationMessage) { + const url = new URL(`management/instances`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + + // + } + + /** + * https://docs.taler.net/core/api-merchant.html#post--management-instances-$INSTANCE-auth + */ + async updateInstanceAuthentication(body: TalerMerchantApi.InstanceAuthConfigurationMessage) { + const url = new URL(`management/instances`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + + // + } + + + /** + * https://docs.taler.net/core/api-merchant.html#patch--management-instances-$INSTANCE + */ + async updateInstance() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get--management-instances + */ + async listInstances() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get--management-instances-$INSTANCE + * + */ + async getInstance() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#delete--management-instances-$INSTANCE + */ + async deleteInstance() { + } + + /** + * https://docs.taler.net/core/api-merchant.html#get--management-instances-$INSTANCE-kyc + */ + async getIntanceKycStatus() { + } + + + + +} diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index 132ca867d..08b29106e 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -2403,6 +2403,10 @@ export namespace TalerMerchantApi { // Name of the protocol. name: "taler-merchant"; + // URN of the implementation (needed to interpret 'revision' in version). + // @since **v8**, may become mandatory in the future. + implementation?: string; + // Default (!) currency supported by this backend. // This is the currency that the backend should // suggest by default to the user when entering @@ -2417,8 +2421,27 @@ export namespace TalerMerchantApi { // All currencies in this map are supported by // the backend. currencies: { [currency: string]: CurrencySpecification }; + + // Array of exchanges trusted by the merchant. + // Since protocol **v6**. + exchanges: ExchangeConfigInfo[]; } + export interface ExchangeConfigInfo { + // Base URL of the exchange REST API. + base_url: string; + + // Currency for which the merchant is configured + // to trust the exchange. + // May not be the one the exchange actually uses, + // but is the only one we would trust this exchange for. + currency: string; + + // Offline master public key of the exchange. The + // /keys data must be signed with this public + // key for us to trust it. + master_pub: EddsaPublicKey; + } export interface ClaimRequest { // Nonce to identify the wallet that claimed the order. nonce: string; @@ -2447,7 +2470,43 @@ export namespace TalerMerchantApi { pos_confirmation?: string; } - interface PayRequest { + export interface PaymentStatusRequestParams { + // Hash of the order’s contract terms (this is used to + // authenticate the wallet/customer in case + // $ORDER_ID is guessable). + // Required once an order was claimed. + contractTermHash?: string; + // Authorizes the request via the claim token that + // was returned in the PostOrderResponse. Used with + // unclaimed orders only. Whether token authorization is + // required is determined by the merchant when the + // frontend creates the order. + claimToken?: string; + // Session ID that the payment must be bound to. + // If not specified, the payment is not session-bound. + sessionId?: string; + // If specified, the merchant backend will wait up to + // timeout_ms milliseconds for completion of the payment + // before sending the HTTP response. A client must never + // rely on this behavior, as the merchant backend may return + // a response immediately. + timeout?: number; + // If set to “yes”, poll for the order’s pending refunds + // to be picked up. timeout_ms specifies how long we + // will wait for the refund. + awaitRefundObtained?: boolean; + // Indicates that we are polling for a refund above the + // given AMOUNT. timeout_ms will specify how long we + // will wait for the refund. + refund?: AmountString; + // Since protocol v9 refunded orders are only returned + // under “already_paid_order_id” if this flag is set + // explicitly to “YES”. + allowRefundedForRepurchase?: boolean; + + } + + export interface PayRequest { // The coins used to make the payment. coins: CoinPaySig[]; @@ -2520,7 +2579,7 @@ export namespace TalerMerchantApi { // refunds. False if it was simply paid. refunded: boolean; } - interface PaidRequest { + export interface PaidRequest { // Signature on TALER_PaymentResponsePS with the public // key of the merchant instance. sig: EddsaSignature; @@ -2537,7 +2596,7 @@ export namespace TalerMerchantApi { session_id: string; } - interface AbortRequest { + export interface AbortRequest { // Hash of the order's contract terms (this is used to authenticate the // wallet/customer in case $ORDER_ID is guessable). h_contract: HashCode; @@ -2603,7 +2662,7 @@ export namespace TalerMerchantApi { exchange_pub: EddsaPublicKey; } - interface WalletRefundRequest { + export interface WalletRefundRequest { // Hash of the order's contract terms (this is used to authenticate the // wallet/customer). h_contract: HashCode; @@ -2723,7 +2782,7 @@ export namespace TalerMerchantApi { blind_sig: BlindedRsaSignature; } - interface InstanceConfigurationMessage { + export interface InstanceConfigurationMessage { // Name of the merchant instance to create (will become $INSTANCE). // Must match the regex ^[A-Za-z0-9][A-Za-z0-9_.@-]+$. id: string; @@ -2771,7 +2830,7 @@ export namespace TalerMerchantApi { default_pay_delay: RelativeTime; } - interface InstanceAuthConfigurationMessage { + export interface InstanceAuthConfigurationMessage { // Type of authentication. // "external": The mechant backend does not do // any authentication checks. Instead an API @@ -2788,37 +2847,8 @@ export namespace TalerMerchantApi { token?: string; } - interface LoginTokenRequest { - // Scope of the token (which kinds of operations it will allow) - scope: "readonly" | "write"; - - // Server may impose its own upper bound - // on the token validity duration - duration?: RelativeTime; - - // Can this token be refreshed? - // Defaults to false. - refreshable?: boolean; - } - interface LoginTokenSuccessResponse { - // The login token that can be used to access resources - // that are in scope for some time. Must be prefixed - // with "Bearer " when used in the "Authorization" HTTP header. - // Will already begin with the RFC 8959 prefix. - token: string; - - // Scope of the token (which kinds of operations it will allow) - scope: "readonly" | "write"; - - // Server may impose its own upper bound - // on the token validity duration - expiration: Timestamp; - - // Can this token be refreshed? - refreshable: boolean; - } - interface InstanceReconfigurationMessage { + export interface InstanceReconfigurationMessage { // Merchant name corresponding to this instance. name: string; @@ -2980,7 +3010,7 @@ export namespace TalerMerchantApi { exchange_http_status: number; } - interface AccountAddDetails { + export interface AccountAddDetails { // payto:// URI of the account. payto_uri: PaytoString; @@ -3017,7 +3047,7 @@ export namespace TalerMerchantApi { salt: HashCode; } - interface AccountPatchDetails { + export interface AccountPatchDetails { // URL from where the merchant can download information // about incoming wire transfers to this account. credit_facade_url?: string; -- cgit v1.2.3