diff options
21 files changed, 146 insertions, 2000 deletions
diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx index d752d612d..497f49c0e 100644 --- a/packages/merchant-backoffice-ui/src/Application.tsx +++ b/packages/merchant-backoffice-ui/src/Application.tsx @@ -19,12 +19,14 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { canonicalizeBaseUrl } from "@gnu-taler/taler-util"; +import { TalerMerchantApi, assertUnreachable, canonicalizeBaseUrl } from "@gnu-taler/taler-util"; import { BrowserHashNavigationProvider, + ConfigResultFail, MerchantApiProvider, TalerWalletIntegrationBrowserProvider, - TranslationProvider + TranslationProvider, + useTranslationContext } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; @@ -34,8 +36,10 @@ import { Loading } from "./components/exception/loading.js"; import { SettingsProvider } from "./context/settings.js"; import { strings } from "./i18n/strings.js"; import { MerchantUiSettings, buildDefaultBackendBaseURL, fetchSettings } from "./settings.js"; +import { NotificationCard } from "./components/menu/index.js"; const WITH_LOCAL_STORAGE_CACHE = false; + export function Application(): VNode { const [settings, setSettings] = useState<MerchantUiSettings>(); useEffect(() => { @@ -53,7 +57,7 @@ export function Application(): VNode { de: strings["de"].completeness, }} > - <MerchantApiProvider baseUrl={new URL("/", baseUrl)} frameOnError={({ children }) => <div>{children}</div>}> + <MerchantApiProvider baseUrl={new URL("/", baseUrl)} frameOnError={OnConfigError}> <SWRConfig value={{ provider: WITH_LOCAL_STORAGE_CACHE @@ -136,3 +140,32 @@ function localStorageProvider(): Map<unknown, unknown> { }); return map; } + +function OnConfigError({ state }: { state: ConfigResultFail<TalerMerchantApi.VersionResponse> | undefined }): VNode { + const { i18n } = useTranslationContext(); + if (!state) { + return <i18n.Translate>checking compatibility with server...</i18n.Translate> + } + switch (state.type) { + case "error": { + return <NotificationCard + notification={{ + message: i18n.str`Contacting the server failed`, + description: state.error.message, + details: JSON.stringify(state.error.errorDetail, undefined, 2), + type: "ERROR", + }} + /> + } + case "incompatible": { + return <NotificationCard + notification={{ + message: i18n.str`The server version is not supported`, + description: i18n.str`Supported version "${state.supported}", server version "${state.result.version}".`, + type: "WARN", + }} + /> + } + default: assertUnreachable(state) + } +} diff --git a/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx b/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx index e47259732..8104d1f9f 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx @@ -18,7 +18,8 @@ * * @author Sebastian Javier Marchano (sebasjm) */ -import { Location, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { TalerMerchantApi, TalerProtocolTimestamp } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h } from "preact"; import { useLayoutEffect, useState } from "preact/hooks"; import { FormErrors, FormProvider } from "./FormProvider.js"; @@ -27,7 +28,6 @@ import { InputGroup } from "./InputGroup.js"; import { InputLocation } from "./InputLocation.js"; import { InputNumber } from "./InputNumber.js"; import { InputProps, useField } from "./useField.js"; -import { TalerMerchantApi } from "@gnu-taler/taler-util"; export interface Props<T> extends InputProps<T> { alreadyExist?: boolean; @@ -40,7 +40,7 @@ export interface Stock { lost: number; sold: number; address?: TalerMerchantApi.Location; - nextRestock?: Timestamp; + nextRestock?: TalerProtocolTimestamp; } interface StockDelta { @@ -133,8 +133,7 @@ export function InputStock<T>({ const stockAddedErrors: FormErrors<typeof addedStock> = { lost: currentStock + addedStock.incoming < addedStock.lost - ? i18n.str`lost cannot be greater than current and incoming (max ${ - currentStock + addedStock.incoming + ? i18n.str`lost cannot be greater than current and incoming (max ${currentStock + addedStock.incoming })` : undefined, }; diff --git a/packages/merchant-backoffice-ui/src/context/instance.ts b/packages/merchant-backoffice-ui/src/context/instance.ts deleted file mode 100644 index 9b67f7170..000000000 --- a/packages/merchant-backoffice-ui/src/context/instance.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-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 <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { createContext } from "preact"; -import { useContext } from "preact/hooks"; - -interface Type { - id: string; - token?: LoginToken; - admin?: boolean; - changeToken: (t?: LoginToken) => void; -} - -const Context = createContext<Type>({} as any); - -const InstanceContextProvider = Context.Provider; -const useInstanceContext = (): Type => useContext(Context); diff --git a/packages/merchant-backoffice-ui/src/context/session.ts b/packages/merchant-backoffice-ui/src/context/session.ts index 83f3f113a..9d63d8e33 100644 --- a/packages/merchant-backoffice-ui/src/context/session.ts +++ b/packages/merchant-backoffice-ui/src/context/session.ts @@ -215,7 +215,8 @@ export function useSessionContext(): SessionStateHandler { .href, isAdmin: info.instance === DEFAULT_ADMIN_USERNAME, instance: info.instance, - token: info.token, + // FIXME: bank and merchant should have consistent behavior + token: info.token?.substring("secret-token:".length) as AccessToken, impersonate: { originalBackendUrl: state.backendUrl, originalToken: state.token, @@ -239,7 +240,9 @@ export function useSessionContext(): SessionStateHandler { impersonate: undefined, ...state, status: "loggedIn", - token: info.token, + // FIXME: bank and merchant should have consistent behavior + token: info.token?.substring("secret-token:".length) as AccessToken, + // token: info.token, }; update(nextState); cleanAllCache(); diff --git a/packages/merchant-backoffice-ui/src/declaration.d.ts b/packages/merchant-backoffice-ui/src/declaration.d.ts index 93fecd9c4..1baf80ba6 100644 --- a/packages/merchant-backoffice-ui/src/declaration.d.ts +++ b/packages/merchant-backoffice-ui/src/declaration.d.ts @@ -19,1653 +19,6 @@ * @author Sebastian Javier Marchano (sebasjm) */ -type HashCode = string; -type EddsaPublicKey = string; -type EddsaSignature = string; -type WireTransferIdentifierRawP = string; -type RelativeTime = TalerProtocolDuration; -type ImageDataUrl = string; -type MerchantUserType = "business" | "individual"; - - -interface WithId { - id: string; -} - -interface Timestamp { - // Milliseconds since epoch, or the special - // value "forever" to represent an event that will - // never happen. - t_s: number | "never"; -} -interface TalerProtocolDuration { - d_us: number | "forever"; -} -interface Duration { - d_ms: number | "forever"; -} - interface WithId { id: string; } - -type Amount = string; -type UUID = string; -type Integer = number; - -interface WireAccount { - // payto:// URI identifying the account and wire method - payto_uri: string; - - // URI to convert amounts from or to the currency used by - // this wire account of the exchange. Missing if no - // conversion is applicable. - conversion_url?: string; - - // Restrictions that apply to bank accounts that would send - // funds to the exchange (crediting this exchange bank account). - // Optional, empty array for unrestricted. - credit_restrictions: AccountRestriction[]; - - // Restrictions that apply to bank accounts that would receive - // funds from the exchange (debiting this exchange bank account). - // Optional, empty array for unrestricted. - debit_restrictions: AccountRestriction[]; - - // Signature using the exchange's offline key over - // a TALER_MasterWireDetailsPS - // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS. - master_sig: EddsaSignature; -} - -type AccountRestriction = RegexAccountRestriction | DenyAllAccountRestriction; - -// Account restriction that disables this type of -// account for the indicated operation categorically. -interface DenyAllAccountRestriction { - type: "deny"; -} - -// Accounts interacting with this type of account -// restriction must have a payto://-URI matching -// the given regex. -interface RegexAccountRestriction { - type: "regex"; - - // Regular expression that the payto://-URI of the - // partner account must follow. The regular expression - // should follow posix-egrep, but without support for character - // classes, GNU extensions, back-references or intervals. See - // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html - // for a description of the posix-egrep syntax. Applications - // may support regexes with additional features, but exchanges - // must not use such regexes. - payto_regex: string; - - // Hint for a human to understand the restriction - // (that is hopefully easier to comprehend than the regex itself). - human_hint: string; - - // Map from IETF BCP 47 language tags to localized - // human hints. - human_hint_i18n?: { [lang_tag: string]: string }; -} -interface LoginToken { - token: string, - expiration: Timestamp, -} -// token used to get loginToken -// must forget after used -declare const __ac_token: unique symbol; - -namespace dead_ExchangeBackend2 { - interface WireResponse { - // Master public key of the exchange, must match the key returned in /keys. - master_public_key: EddsaPublicKey; - - // Array of wire accounts operated by the exchange for - // incoming wire transfers. - accounts: WireAccount[]; - - // Object mapping names of wire methods (i.e. "sepa" or "x-taler-bank") - // to wire fees. - fees: { method: AggregateTransferFee }; - } - interface AggregateTransferFee { - // Per transfer wire transfer fee. - wire_fee: Amount; - - // Per transfer closing fee. - closing_fee: Amount; - - // What date (inclusive) does this fee go into effect? - // The different fees must cover the full time period in which - // any of the denomination keys are valid without overlap. - start_date: Timestamp; - - // What date (exclusive) does this fee stop going into effect? - // The different fees must cover the full time period in which - // any of the denomination keys are valid without overlap. - end_date: Timestamp; - - // Signature of TALER_MasterWireFeePS with - // purpose TALER_SIGNATURE_MASTER_WIRE_FEES. - sig: EddsaSignature; - } -} -namespace dead_MerchantBackend2 { - interface ErrorDetail { - // Numeric error code unique to the condition. - // The other arguments are specific to the error value reported here. - code: number; - - // Human-readable description of the error, i.e. "missing parameter", "commitment violation", ... - // Should give a human-readable hint about the error's nature. Optional, may change without notice! - hint?: string; - - // Optional detail about the specific input value that failed. May change without notice! - detail?: string; - - // Name of the parameter that was bogus (if applicable). - parameter?: string; - - // Path to the argument that was bogus (if applicable). - path?: string; - - // Offset of the argument that was bogus (if applicable). - offset?: string; - - // Index of the argument that was bogus (if applicable). - index?: string; - - // Name of the object that was bogus (if applicable). - object?: string; - - // Name of the currency than was problematic (if applicable). - currency?: string; - - // Expected type (if applicable). - type_expected?: string; - - // Type that was provided instead (if applicable). - type_actual?: string; - } - - // Delivery location, loosely modeled as a subset of - // ISO20022's PostalAddress25. - interface Tax { - // the name of the tax - name: string; - - // amount paid in tax - tax: Amount; - } - - interface Auditor { - // official name - name: string; - - // Auditor's public key - auditor_pub: EddsaPublicKey; - - // Base URL of the auditor - url: string; - } - interface Exchange { - // the exchange's base URL - url: string; - - // master public key of the exchange - master_pub: EddsaPublicKey; - } - - interface Product { - // merchant-internal identifier for the product. - product_id?: string; - - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n?: { [lang_tag: string]: string }; - - // The number of units of the product to deliver to the customer. - quantity: Integer; - - // The unit in which the product is measured (liters, kilograms, packages, etc.) - unit: string; - - // The price of the product; this is the total price for quantity times unit of this product. - price?: Amount; - - // An optional base64-encoded product image - image: ImageDataUrl; - - // a list of taxes paid by the merchant for this product. Can be empty. - taxes: Tax[]; - - // time indicating when this product should be delivered - delivery_date?: TalerProtocolTimestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; - } - interface Merchant { - // label for a location with the business address of the merchant - address: Location; - - // the merchant's legal name of business - name: string; - - // label for a location that denotes the jurisdiction for disputes. - // Some of the typical fields for a location (such as a street address) may be absent. - jurisdiction: Location; - } - - interface VersionResponse { - // libtool-style representation of the Merchant protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // Name of the protocol. - name: "taler-merchant"; - - // Default (!) currency supported by this backend. - // This is the currency that the backend should - // suggest by default to the user when entering - // amounts. See currencies for a list of - // supported currencies and how to render them. - currency: string; - - // How services should render currencies supported - // by this backend. Maps - // currency codes (e.g. "EUR" or "KUDOS") to - // the respective currency specification. - // All currencies in this map are supported by - // the backend. Note that the actual currency - // specifications are a *hint* for applications - // that would like *advice* on how to render amounts. - // Applications *may* ignore the currency specification - // if they know how to render currencies that they are - // used with. - currencies: { currency: CurrencySpecification }; - - // Array of exchanges trusted by the merchant. - // Since protocol v6. - exchanges: ExchangeConfigInfo[]; - } - - 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; - } - interface Location { - // Nation with its own government. - country?: string; - - // Identifies a subdivision of a country such as state, region, county. - country_subdivision?: string; - - // Identifies a subdivision within a country sub-division. - district?: string; - - // Name of a built-up area, with defined boundaries, and a local government. - town?: string; - - // Specific location name within the town. - town_location?: string; - - // Identifier consisting of a group of letters and/or numbers that - // is added to a postal address to assist the sorting of mail. - post_code?: string; - - // Name of a street or thoroughfare. - street?: string; - - // Name of the building or house. - building_name?: string; - - // Number that identifies the position of a building on a street. - building_number?: string; - - // Free-form address lines, should not exceed 7 elements. - address_lines?: string[]; - } - namespace dead_Instances2 { - //POST /private/instances/$INSTANCE/auth - interface InstanceAuthConfigurationMessage { - // Type of authentication. - // "external": The mechant backend does not do - // any authentication checks. Instead an API - // gateway must do the authentication. - // "token": The merchant checks an auth token. - // See "token" for details. - method: "external" | "token"; - - // For method "external", this field is mandatory. - // The token MUST begin with the string "secret-token:". - // After the auth token has been set (with method "token"), - // the value must be provided in a "Authorization: Bearer $token" - // header. - token?: string; - } - //POST /private/instances - interface InstanceConfigurationMessage { - // Name of the merchant instance to create (will become $INSTANCE). - id: string; - - // Merchant name corresponding to this instance. - name: string; - - // Type of the user (business or individual). - // Defaults to 'business'. Should become mandatory field - // in the future, left as optional for API compatibility for now. - user_type?: MerchantUserType; - - // Merchant email for customer contact. - email?: string; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // "Authentication" header required to authorize management access the instance. - // Optional, if not given authentication will be disabled for - // this instance (hopefully authentication checks are still - // done by some reverse proxy). - auth: InstanceAuthConfigurationMessage; - - // The merchant's physical address (to be put into contracts). - address: Location; - - // The jurisdiction under which the merchant conducts its business - // (to be put into contracts). - jurisdiction: Location; - - // Use STEFAN curves to determine default fees? - // If false, no fees are allowed by default. - // Can always be overridden by the frontend on a per-order basis. - use_stefan: boolean; - - // If the frontend does NOT specify an execution date, how long should - // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; - - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; - } - - // PATCH /private/instances/$INSTANCE - interface InstanceReconfigurationMessage { - - // Merchant name corresponding to this instance. - name: string; - - // Type of the user (business or individual). - // Defaults to 'business'. Should become mandatory field - // in the future, left as optional for API compatibility for now. - user_type?: MerchantUserType; - - // Merchant email for customer contact. - email?: string; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // The merchant's physical address (to be put into contracts). - address: Location; - - // The jurisdiction under which the merchant conducts its business - // (to be put into contracts). - jurisdiction: Location; - - // Use STEFAN curves to determine default fees? - // If false, no fees are allowed by default. - // Can always be overridden by the frontend on a per-order basis. - use_stefan: boolean; - - // If the frontend does NOT specify an execution date, how long should - // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; - - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; - } - - // GET /private/instances - interface InstancesResponse { - // List of instances that are present in the backend (see Instance) - instances: Instance[]; - } - - interface Instance { - // Merchant name corresponding to this instance. - name: string; - - // Type of the user ("business" or "individual"). - user_type: MerchantUserType; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // Merchant instance this response is about ($INSTANCE) - id: string; - - // Public key of the merchant/instance, in Crockford Base32 encoding. - merchant_pub: EddsaPublicKey; - - // List of the payment targets supported by this instance. Clients can - // specify the desired payment target in /order requests. Note that - // front-ends do not have to support wallets selecting payment targets. - payment_targets: string[]; - - // Has this instance been deleted (but not purged)? - deleted: boolean; - } - - //GET /private/instances/$INSTANCE - interface QueryInstancesResponse { - - // Merchant name corresponding to this instance. - name: string; - // Type of the user ("business" or "individual"). - user_type: MerchantUserType; - - // Merchant email for customer contact. - email?: string; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // Public key of the merchant/instance, in Crockford Base32 encoding. - merchant_pub: EddsaPublicKey; - - // The merchant's physical address (to be put into contracts). - address: Location; - - // The jurisdiction under which the merchant conducts its business - // (to be put into contracts). - jurisdiction: Location; - - // Use STEFAN curves to determine default fees? - // If false, no fees are allowed by default. - // Can always be overridden by the frontend on a per-order basis. - use_stefan: boolean; - - // If the frontend does NOT specify an execution date, how long should - // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; - - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; - - // Authentication configuration. - // Does not contain the token when token auth is configured. - auth: { - method: "external" | "token"; - }; - } - // DELETE /private/instances/$INSTANCE - 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; - } - } - - namespace dead_KYC { - //GET /private/instances/$INSTANCE/kyc - interface AccountKycRedirects { - // Array of pending KYCs. - pending_kycs: MerchantAccountKycRedirect[]; - - // Array of exchanges with no reply. - timeout_kycs: ExchangeKycTimeout[]; - } - interface MerchantAccountKycRedirect { - // URL that the user should open in a browser to - // proceed with the KYC process (as returned - // by the exchange's /kyc-check/ endpoint). - // Optional, missing if the account is blocked - // due to AML and not due to KYC. - kyc_url?: string; - - // Base URL of the exchange this is about. - exchange_url: string; - - // AML status of the account. - aml_status: number; - - // Our bank wire account this is about. - payto_uri: string; - } - interface ExchangeKycTimeout { - // Base URL of the exchange this is about. - exchange_url: string; - - // Numeric error code indicating errors the exchange - // returned, or TALER_EC_INVALID for none. - exchange_code: number; - - // HTTP status code returned by the exchange when we asked for - // information about the KYC status. - // 0 if there was no response at all. - exchange_http_status: number; - } - - } - - namespace dead_BankAccounts { - - 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; - - } - - type FacadeCredentials = - | NoFacadeCredentials - | BasicAuthFacadeCredentials; - - interface NoFacadeCredentials { - type: "none"; - } - - interface BasicAuthFacadeCredentials { - type: "basic"; - - // Username to use to authenticate - username: string; - - // Password to use to authenticate - password: string; - } - - interface AccountAddResponse { - // Hash over the wire details (including over the salt). - h_wire: HashCode; - - // Salt used to compute h_wire. - salt: HashCode; - } - - interface AccountPatchDetails { - - // 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; - } - - - interface AccountsSummaryResponse { - - // List of accounts that are known for the instance. - accounts: BankAccountEntry[]; - } - - interface BankAccountEntry { - // payto:// URI of the account. - payto_uri: string; - - // Hash over the wire details (including over the salt) - h_wire: HashCode; - - // salt used to compute h_wire - salt: HashCode; - - // 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. - credit_facade_credentials?: FacadeCredentials; - - // true if this account is active, - // false if it is historic. - active: boolean; - } - - } - - namespace dead_Products { - // POST /private/products - interface ProductAddDetail { - // product ID to use. - product_id: string; - - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n: { [lang_tag: string]: string }; - - // unit in which the product is measured (liters, kilograms, packages, etc.) - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: Amount; - - // An optional base64-encoded product image - image: ImageDataUrl; - - // a list of taxes paid by the merchant for one unit of this product - taxes: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Identifies where the product is in stock. - address: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; - } - // PATCH /private/products/$PRODUCT_ID - interface ProductPatchDetail { - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n: { [lang_tag: string]: string }; - - // unit in which the product is measured (liters, kilograms, packages, etc.) - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: Amount; - - // An optional base64-encoded product image - image: ImageDataUrl; - - // a list of taxes paid by the merchant for one unit of this product - taxes: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Number of units of the product that were lost (spoiled, stolen, etc.) - total_lost: Integer; - - // Identifies where the product is in stock. - address: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; - } - - // GET /private/products - interface InventorySummaryResponse { - // List of products that are present in the inventory - products: InventoryEntry[]; - } - interface InventoryEntry { - // Product identifier, as found in the product. - product_id: string; - } - - // GET /private/products/$PRODUCT_ID - interface ProductDetail { - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n: { [lang_tag: string]: string }; - - // unit in which the product is measured (liters, kilograms, packages, etc.) - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: Amount; - - // An optional base64-encoded product image - image: ImageDataUrl; - - // a list of taxes paid by the merchant for one unit of this product - taxes: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Number of units of the product that have already been sold. - total_sold: Integer; - - // Number of units of the product that were lost (spoiled, stolen, etc.) - total_lost: Integer; - - // Identifies where the product is in stock. - address: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; - } - - // POST /private/products/$PRODUCT_ID/lock - interface LockRequest { - // UUID that identifies the frontend performing the lock - // It is suggested that clients use a timeflake for this, - // see https://github.com/anthonynsimon/timeflake - lock_uuid: UUID; - - // How long does the frontend intend to hold the lock - duration: RelativeTime; - - // How many units should be locked? - quantity: Integer; - } - - // DELETE /private/products/$PRODUCT_ID - } - - namespace dead_Orders { - type MerchantOrderStatusResponse = - | CheckPaymentPaidResponse - | CheckPaymentClaimedResponse - | CheckPaymentUnpaidResponse; - interface CheckPaymentPaidResponse { - // The customer paid for this contract. - order_status: "paid"; - - // Was the payment refunded (even partially)? - refunded: boolean; - - // True if there are any approved refunds that the wallet has - // not yet obtained. - refund_pending: boolean; - - // Did the exchange wire us the funds? - wired: boolean; - - // Total amount the exchange deposited into our bank account - // for this contract, excluding fees. - deposit_total: Amount; - - // Numeric error code indicating errors the exchange - // encountered tracking the wire transfer for this purchase (before - // we even got to specific coin issues). - // 0 if there were no issues. - exchange_ec: number; - - // HTTP status code returned by the exchange when we asked for - // information to track the wire transfer for this purchase. - // 0 if there were no issues. - exchange_hc: number; - - // Total amount that was refunded, 0 if refunded is false. - refund_amount: Amount; - - // Contract terms. - contract_terms: ContractTerms; - - // The wire transfer status from the exchange for this order if - // available, otherwise empty array. - wire_details: TransactionWireTransfer[]; - - // The refund details for this order. One entry per - // refunded coin; empty array if there are no refunds. - refund_details: RefundDetails[]; - - // Status URL, can be used as a redirect target for the browser - // to show the order QR code / trigger the wallet. - order_status_url: string; - } - interface CheckPaymentClaimedResponse { - // A wallet claimed the order, but did not yet pay for the contract. - order_status: "claimed"; - - // Contract terms. - contract_terms: ContractTerms; - } - interface CheckPaymentUnpaidResponse { - // The order was neither claimed nor paid. - order_status: "unpaid"; - - // when was the order created - creation_time: Timestamp; - - // Order summary text. - summary: string; - - // Total amount of the order (to be paid by the customer). - total_amount: Amount; - - // URI that the wallet must process to complete the payment. - taler_pay_uri: string; - - // Alternative order ID which was paid for already in the same session. - // Only given if the same product was purchased before in the same session. - already_paid_order_id?: string; - - // Fulfillment URL of an already paid order. Only given if under this - // session an already paid order with a fulfillment URL exists. - already_paid_fulfillment_url?: string; - - // Status URL, can be used as a redirect target for the browser - // to show the order QR code / trigger the wallet. - order_status_url: string; - - // We do we NOT return the contract terms here because they may not - // exist in case the wallet did not yet claim them. - } - interface RefundDetails { - // Reason given for the refund. - reason: string; - - // When was the refund approved. - timestamp: Timestamp; - - // Set to true if a refund is still available for the wallet for this payment. - pending: boolean; - - // Total amount that was refunded (minus a refund fee). - amount: Amount; - } - interface TransactionWireTransfer { - // Responsible exchange. - exchange_url: string; - - // 32-byte wire transfer identifier. - wtid: Base32; - - // Execution time of the wire transfer. - execution_time: Timestamp; - - // Total amount that has been wire transferred - // to the merchant. - amount: Amount; - - // Was this transfer confirmed by the merchant via the - // POST /transfers API, or is it merely claimed by the exchange? - confirmed: boolean; - } - interface TransactionWireReport { - // Numerical error code. - code: number; - - // Human-readable error description. - hint: string; - - // Numerical error code from the exchange. - exchange_ec: number; - - // HTTP status code received from the exchange. - exchange_hc: number; - - // Public key of the coin for which we got the exchange error. - coin_pub: CoinPublicKey; - } - - interface OrderHistory { - // timestamp-sorted array of all orders matching the query. - // The order of the sorting depends on the sign of delta. - orders: OrderHistoryEntry[]; - } - interface OrderHistoryEntry { - // order ID of the transaction related to this entry. - order_id: string; - - // row ID of the order in the database - row_id: number; - - // when the order was created - timestamp: Timestamp; - - // the amount of money the order is for - amount: Amount; - - // the summary of the order - summary: string; - - // whether some part of the order is refundable, - // that is the refund deadline has not yet expired - // and the total amount refunded so far is below - // the value of the original transaction. - refundable: boolean; - - // whether the order has been paid or not - paid: boolean; - } - - interface PostOrderRequest { - // The order must at least contain the minimal - // order detail, but can override all - order: Order; - - // if set, the backend will then set the refund deadline to the current - // time plus the specified delay. If it's not set, refunds will not be - // possible. - refund_delay?: RelativeTime; - - // specifies the payment target preferred by the client. Can be used - // to select among the various (active) wire methods supported by the instance. - payment_target?: string; - - // specifies that some products are to be included in the - // order from the inventory. For these inventory management - // is performed (so the products must be in stock) and - // details are completed from the product data of the backend. - inventory_products?: MinimalInventoryProduct[]; - - // Specifies a lock identifier that was used to - // lock a product in the inventory. Only useful if - // manage_inventory is set. Used in case a frontend - // reserved quantities of the individual products while - // the shopping card was being built. Multiple UUIDs can - // be used in case different UUIDs were used for different - // products (i.e. in case the user started with multiple - // shopping sessions that were combined during checkout). - lock_uuids?: UUID[]; - - // Should a token for claiming the order be generated? - // False can make sense if the ORDER_ID is sufficiently - // high entropy to prevent adversarial claims (like it is - // if the backend auto-generates one). Default is 'true'. - create_token?: boolean; - - // OTP device ID to associate with the order. - // This parameter is optional. - otp_id?: string; - } - type Order = MinimalOrderDetail | ContractTerms; - - interface MinimalOrderDetail { - // Amount to be paid by the customer - amount: Amount; - - // Short summary of the order - summary: string; - - // URL that will show that the order was successful after - // it has been paid for. Optional. When POSTing to the - // merchant, the placeholder "${ORDER_ID}" will be - // replaced with the actual order ID (useful if the - // order ID is generated server-side and needs to be - // in the URL). - fulfillment_url?: string; - } - - interface MinimalInventoryProduct { - // Which product is requested (here mandatory!) - product_id: string; - - // How many units of the product are requested - quantity: Integer; - } - interface PostOrderResponse { - // Order ID of the response that was just created - order_id: string; - - // Token that authorizes the wallet to claim the order. - // Provided only if "create_token" was set to 'true' - // in the request. - token?: ClaimToken; - } - interface OutOfStockResponse { - // Product ID of an out-of-stock item - product_id: string; - - // Requested quantity - requested_quantity: Integer; - - // Available quantity (must be below requested_quanitity) - available_quantity: Integer; - - // When do we expect the product to be again in stock? - // Optional, not given if unknown. - restock_expected?: Timestamp; - } - - interface ForgetRequest { - // Array of valid JSON paths to forgettable fields in the order's - // contract terms. - fields: string[]; - } - interface RefundRequest { - // Amount to be refunded - refund: Amount; - - // Human-readable refund justification - reason: string; - } - interface MerchantRefundResponse { - // URL (handled by the backend) that the wallet should access to - // trigger refund processing. - // taler://refund/... - taler_refund_uri: string; - - // Contract hash that a client may need to authenticate an - // HTTP request to obtain the above URI in a wallet-friendly way. - h_contract: HashCode; - } - } - - namespace dead_Rewards { - // GET /private/reserves - interface RewardReserveStatus { - // Array of all known reserves (possibly empty!) - reserves: ReserveStatusEntry[]; - } - interface ReserveStatusEntry { - // Public key of the reserve - reserve_pub: EddsaPublicKey; - - // Timestamp when it was established - creation_time: Timestamp; - - // Timestamp when it expires - expiration_time: Timestamp; - - // Initial amount as per reserve creation call - merchant_initial_amount: Amount; - - // Initial amount as per exchange, 0 if exchange did - // not confirm reserve creation yet. - exchange_initial_amount: Amount; - - // Amount picked up so far. - pickup_amount: Amount; - - // Amount approved for rewards that exceeds the pickup_amount. - committed_amount: Amount; - - // Is this reserve active (false if it was deleted but not purged) - active: boolean; - } - - interface ReserveCreateRequest { - // Amount that the merchant promises to put into the reserve - initial_balance: Amount; - - // Exchange the merchant intends to use for reward - exchange_url: string; - - // Desired wire method, for example "iban" or "x-taler-bank" - wire_method: string; - } - interface ReserveCreateConfirmation { - // Public key identifying the reserve - reserve_pub: EddsaPublicKey; - - // Wire accounts of the exchange where to transfer the funds. - accounts: WireAccount[]; - } - interface RewardCreateRequest { - // Amount that the customer should be reward - amount: Amount; - - // Justification for giving the reward - justification: string; - - // URL that the user should be directed to after rewarding, - // will be included in the reward_token. - next_url: string; - } - interface RewardCreateConfirmation { - // Unique reward identifier for the reward that was created. - reward_id: HashCode; - - // taler://reward URI for the reward - taler_reward_uri: string; - - // URL that will directly trigger processing - // the reward when the browser is redirected to it - reward_status_url: string; - - // when does the reward expire - reward_expiration: Timestamp; - } - - interface ReserveDetail { - // Timestamp when it was established. - creation_time: Timestamp; - - // Timestamp when it expires. - expiration_time: Timestamp; - - // Initial amount as per reserve creation call. - merchant_initial_amount: Amount; - - // Initial amount as per exchange, 0 if exchange did - // not confirm reserve creation yet. - exchange_initial_amount: Amount; - - // Amount picked up so far. - pickup_amount: Amount; - - // Amount approved for rewards that exceeds the pickup_amount. - committed_amount: Amount; - - // Array of all rewards created by this reserves (possibly empty!). - // Only present if asked for explicitly. - rewards?: RewardStatusEntry[]; - - // Is this reserve active (false if it was deleted but not purged)? - active: boolean; - - // Array of wire accounts of the exchange that could - // be used to fill the reserve, can be NULL - // if the reserve is inactive or was already filled - accounts?: WireAccount[]; - - // URL of the exchange hosting the reserve, - // NULL if the reserve is inactive - exchange_url: string; - } - - interface RewardStatusEntry { - // Unique identifier for the reward. - reward_id: HashCode; - - // Total amount of the reward that can be withdrawn. - total_amount: Amount; - - // Human-readable reason for why the reward was granted. - reason: string; - } - - interface RewardDetails { - // Amount that we authorized for this reward. - total_authorized: Amount; - - // Amount that was picked up by the user already. - total_picked_up: Amount; - - // Human-readable reason given when authorizing the reward. - reason: string; - - // Timestamp indicating when the reward is set to expire (may be in the past). - expiration: Timestamp; - - // Reserve public key from which the reward is funded. - reserve_pub: EddsaPublicKey; - - // Array showing the pickup operations of the wallet (possibly empty!). - // Only present if asked for explicitly. - pickups?: PickupDetail[]; - } - interface PickupDetail { - // Unique identifier for the pickup operation. - pickup_id: HashCode; - - // Number of planchets involved. - num_planchets: Integer; - - // Total amount requested for this pickup_id. - requested_amount: Amount; - } - } - - namespace dead_Transfers { - interface TransferList { - // list of all the transfers that fit the filter that we know - transfers: TransferDetails[]; - } - interface TransferDetails { - // how much was wired to the merchant (minus fees) - credit_amount: Amount; - - // raw wire transfer identifier identifying the wire transfer (a base32-encoded value) - wtid: string; - - // target account that received the wire transfer - payto_uri: string; - - // base URL of the exchange that made the wire transfer - exchange_url: string; - - // Serial number identifying the transfer in the merchant backend. - // Used for filgering via offset. - transfer_serial_id: number; - - // Time of the execution of the wire transfer by the exchange, according to the exchange - // Only provided if we did get an answer from the exchange. - execution_time?: Timestamp; - - // True if we checked the exchange's answer and are happy with it. - // False if we have an answer and are unhappy, missing if we - // do not have an answer from the exchange. - verified?: boolean; - - // True if the merchant uses the POST /transfers API to confirm - // that this wire transfer took place (and it is thus not - // something merely claimed by the exchange). - confirmed?: boolean; - } - - interface TransferInformation { - // how much was wired to the merchant (minus fees) - credit_amount: Amount; - - // raw wire transfer identifier identifying the wire transfer (a base32-encoded value) - wtid: WireTransferIdentifierRawP; - - // target account that received the wire transfer - payto_uri: string; - - // base URL of the exchange that made the wire transfer - exchange_url: string; - } - } - - namespace dead_OTP { - interface OtpDeviceAddDetails { - // Device ID to use. - otp_device_id: string; - - // Human-readable description for the device. - otp_device_description: string; - - // A base64-encoded key - otp_key: string; - - // Algorithm for computing the POS confirmation. - otp_algorithm: Integer; - - // Counter for counter-based OTP devices. - otp_ctr?: Integer; - } - - interface OtpDevicePatchDetails { - // Human-readable description for the device. - otp_device_description: string; - - // A base64-encoded key - otp_key: string | undefined; - - // Algorithm for computing the POS confirmation. - otp_algorithm: Integer; - - // Counter for counter-based OTP devices. - otp_ctr?: Integer; - } - - interface OtpDeviceSummaryResponse { - // Array of devices that are present in our backend. - otp_devices: OtpDeviceEntry[]; - } - interface OtpDeviceEntry { - // Device identifier. - otp_device_id: string; - - // Human-readable description for the device. - device_description: string; - } - - interface OtpDeviceDetails { - // Human-readable description for the device. - device_description: string; - - // Algorithm for computing the POS confirmation. - otp_algorithm: Integer; - - // Counter for counter-based OTP devices. - otp_ctr?: Integer; - } - - - } - namespace dead_Template { - interface TemplateAddDetails { - // Template ID to use. - template_id: string; - - // Human-readable description for the template. - template_description: string; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; - - // Additional information in a separate template. - template_contract: TemplateContractDetails; - } - interface TemplateContractDetails { - // Human-readable summary for the template. - summary?: string; - - // The price is imposed by the merchant and cannot be changed by the customer. - // This parameter is optional. - amount?: Amount; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age: Integer; - - // The time the customer need to pay before his order will be deleted. - // It is deleted if the customer did not pay and if the duration is over. - pay_duration: RelativeTime; - } - interface TemplatePatchDetails { - // Human-readable description for the template. - template_description: string; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; - - // Additional information in a separate template. - template_contract: TemplateContractDetails; - } - - interface TemplateSummaryResponse { - // List of templates that are present in our backend. - templates: TemplateEntry[]; - } - - interface TemplateEntry { - // Template identifier, as found in the template. - template_id: string; - - // Human-readable description for the template. - template_description: string; - } - - interface TemplateDetails { - // Human-readable description for the template. - template_description: string; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; - - // Additional information in a separate template. - template_contract: TemplateContractDetails; - } - - interface UsingTemplateDetails { - // Subject of the template - summary?: string; - - // The amount entered by the customer. - amount?: Amount; - } - - interface UsingTemplateResponse { - // After enter the request. The user will be pay with a taler URL. - order_id: string; - token: string; - } - } - - namespace dead_Webhooks { - type MerchantWebhookType = "pay" | "refund"; - interface WebhookAddDetails { - // Webhook ID to use. - webhook_id: string; - - // The event of the webhook: why the webhook is used. - event_type: MerchantWebhookType; - - // URL of the webhook where the customer will be redirected. - url: string; - - // Method used by the webhook - http_method: string; - - // Header template of the webhook - header_template?: string; - - // Body template by the webhook - body_template?: string; - } - interface WebhookPatchDetails { - // The event of the webhook: why the webhook is used. - event_type: string; - - // URL of the webhook where the customer will be redirected. - url: string; - - // Method used by the webhook - http_method: string; - - // Header template of the webhook - header_template?: string; - - // Body template by the webhook - body_template?: string; - } - interface WebhookSummaryResponse { - // List of webhooks that are present in our backend. - webhooks: WebhookEntry[]; - } - interface WebhookEntry { - // Webhook identifier, as found in the webhook. - webhook_id: string; - - // The event of the webhook: why the webhook is used. - event_type: string; - } - interface WebhookDetails { - // The event of the webhook: why the webhook is used. - event_type: string; - - // URL of the webhook where the customer will be redirected. - url: string; - - // Method used by the webhook - http_method: string; - - // Header template of the webhook - header_template?: string; - - // Body template by the webhook - body_template?: string; - } - } - - interface ContractTerms { - // Human-readable description of the whole purchase - summary: string; - - // Map from IETF BCP 47 language tags to localized summaries - summary_i18n?: { [lang_tag: string]: string }; - - // Unique, free-form identifier for the proposal. - // Must be unique within a merchant instance. - // For merchants that do not store proposals in their DB - // before the customer paid for them, the order_id can be used - // by the frontend to restore a proposal from the information - // encoded in it (such as a short product identifier and timestamp). - order_id: string; - - // Total price for the transaction. - // The exchange will subtract deposit fees from that amount - // before transferring it to the merchant. - amount: Amount; - - // The URL for this purchase. Every time is is visited, the merchant - // will send back to the customer the same proposal. Clearly, this URL - // can be bookmarked and shared by users. - fulfillment_url?: string; - - // Maximum total deposit fee accepted by the merchant for this contract - max_fee: Amount; - - // List of products that are part of the purchase (see Product). - products: Product[]; - - // Time when this contract was generated - timestamp: TalerProtocolTimestamp; - - // After this deadline has passed, no refunds will be accepted. - refund_deadline: TalerProtocolTimestamp; - - // After this deadline, the merchant won't accept payments for the contact - pay_deadline: TalerProtocolTimestamp; - - // Transfer deadline for the exchange. Must be in the - // deposit permissions of coins used to pay for this order. - wire_transfer_deadline: TalerProtocolTimestamp; - - // Merchant's public key used to sign this proposal; this information - // is typically added by the backend Note that this can be an ephemeral key. - merchant_pub: EddsaPublicKey; - - // Base URL of the (public!) merchant backend API. - // Must be an absolute URL that ends with a slash. - merchant_base_url: string; - - // More info about the merchant, see below - merchant: Merchant; - - // The hash of the merchant instance's wire details. - h_wire: HashCode; - - // Wire transfer method identifier for the wire method associated with h_wire. - // The wallet may only select exchanges via a matching auditor if the - // exchange also supports this wire method. - // The wire transfer fees must be added based on this wire transfer method. - wire_method: string; - - // Any exchanges audited by these auditors are accepted by the merchant. - auditors: Auditor[]; - - // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. - exchanges: Exchange[]; - - // Delivery location for (all!) products. - delivery_location?: Location; - - // Time indicating when the order should be delivered. - // May be overwritten by individual products. - delivery_date?: TalerProtocolTimestamp; - - // Nonce generated by the wallet and echoed by the merchant - // in this field when the proposal is generated. - nonce: string; - - // Specifies for how long the wallet should try to get an - // automatic refund for the purchase. If this field is - // present, the wallet should wait for a few seconds after - // the purchase and then automatically attempt to obtain - // a refund. The wallet should probe until "delay" - // after the payment was successful (i.e. via long polling - // or via explicit requests with exponential back-off). - // - // In particular, if the wallet is offline - // at that time, it MUST repeat the request until it gets - // one response from the merchant after the delay has expired. - // If the refund is granted, the wallet MUST automatically - // recover the payment. This is used in case a merchant - // knows that it might be unable to satisfy the contract and - // desires for the wallet to attempt to get the refund without any - // customer interaction. Note that it is NOT an error if the - // merchant does not grant a refund. - auto_refund?: RelativeTime; - - // Extra data that is only interpreted by the merchant frontend. - // Useful when the merchant needs to store extra information on a - // contract without storing it separately in their database. - extra?: any; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; - } -} diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts b/packages/merchant-backoffice-ui/src/hooks/backend.ts index 8eb9b4cf2..e4e50c8ad 100644 --- a/packages/merchant-backoffice-ui/src/hooks/backend.ts +++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts @@ -177,16 +177,6 @@ interface useBackendBaseRequestType { } type YesOrNo = "yes" | "no"; -type LoginResult = - | { - valid: true; - token: string; - expiration: Timestamp; - } - | { - valid: false; - cause: HttpError<EmptyObject>; - }; /** * diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.test.ts b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts index 35147d988..3b02d7758 100644 --- a/packages/merchant-backoffice-ui/src/hooks/instance.test.ts +++ b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts @@ -19,15 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { AccessToken, TalerMerchantApi } from "@gnu-taler/taler-util"; +import { TalerMerchantApi } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { expect } from "chai"; import { - useAdminAPI, useBackendInstances, - useInstanceAPI, useInstanceDetails, - useManagementAPI, } from "./instance.js"; import { ApiMockEnvironment } from "./testing.js"; import { @@ -39,6 +36,7 @@ import { API_UPDATE_CURRENT_INSTANCE_AUTH, API_UPDATE_INSTANCE_BY_ID, } from "./urls.js"; +import { useMerchantApiContext } from "@gnu-taler/web-util/browser"; describe("instance api interaction with details", () => { it("should evict cache when updating an instance", async () => { @@ -52,7 +50,8 @@ describe("instance api interaction with details", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { - const api = useInstanceAPI(); + // const api = useInstanceAPI(); + const { lib: api } = useMerchantApiContext() const query = useInstanceDetails(); return { query, api }; }, @@ -82,7 +81,7 @@ describe("instance api interaction with details", () => { name: "other_name", } as TalerMerchantApi.QueryInstancesResponse, }); - api.updateInstance({ + api.management.updateCurrentInstance(undefined, { name: "other_name", } as TalerMerchantApi.InstanceReconfigurationMessage); }, @@ -120,7 +119,7 @@ describe("instance api interaction with details", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { - const api = useInstanceAPI(); + const { lib: api } = useMerchantApiContext() const query = useInstanceDetails(); return { query, api }; }, @@ -206,7 +205,7 @@ describe("instance api interaction with details", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { - const api = useInstanceAPI(); + const { lib: api } = useMerchantApiContext() const query = useInstanceDetails(); return { query, api }; }, @@ -243,7 +242,9 @@ describe("instance api interaction with details", () => { } as TalerMerchantApi.QueryInstancesResponse, }); - api.clearAccessToken(undefined); + api.management.updateCurrentInstanceAuthentication(undefined, { + method: "external" + }); }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -337,7 +338,7 @@ describe("instance admin api interaction with listing", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { - const api = useAdminAPI(); + const { lib: api } = useMerchantApiContext() const query = useBackendInstances(); return { query, api }; }, @@ -379,9 +380,9 @@ describe("instance admin api interaction with listing", () => { }, }); - api.createInstance({ + api.management.createInstance(undefined, { name: "other_name", - } as TalerMerchantApi.InstanceConfigurationMessage); + } as TalerMerchantApi.InstanceConfigurationMessage) }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -428,7 +429,7 @@ describe("instance admin api interaction with listing", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { - const api = useAdminAPI(); + const { lib: api } = useMerchantApiContext() const query = useBackendInstances(); return { query, api }; }, @@ -469,7 +470,7 @@ describe("instance admin api interaction with listing", () => { }, }); - api.deleteInstance("the_id"); + api.management.deleteInstance(undefined, "the_id"); }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -582,7 +583,7 @@ describe("instance admin api interaction with listing", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { - const api = useAdminAPI(); + const { lib: api } = useMerchantApiContext() const query = useBackendInstances(); return { query, api }; }, @@ -627,7 +628,7 @@ describe("instance admin api interaction with listing", () => { }, }); - api.purgeInstance("the_id"); + api.management.deleteInstance(undefined, "the_id", { purge: true }) }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -670,7 +671,7 @@ describe("instance management api interaction with listing", () => { const hookBehavior = await tests.hookBehaveLikeThis( () => { - const api = useManagementAPI("managed"); + const { lib: api } = useMerchantApiContext() const query = useBackendInstances(); return { query, api }; }, @@ -711,7 +712,7 @@ describe("instance management api interaction with listing", () => { }, }); - api.updateInstance({ + api.management.updateCurrentInstance(undefined, { name: "other_name", } as TalerMerchantApi.InstanceConfigurationMessage); }, diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.ts b/packages/merchant-backoffice-ui/src/hooks/instance.ts index 5f17dbf79..0ba68250a 100644 --- a/packages/merchant-backoffice-ui/src/hooks/instance.ts +++ b/packages/merchant-backoffice-ui/src/hooks/instance.ts @@ -20,219 +20,14 @@ import { } from "@gnu-taler/web-util/browser"; import { useBackendBaseRequest, - useBackendInstanceRequest, - useMatchMutate, + useBackendInstanceRequest } from "./backend.js"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 -import { AccessToken, TalerErrorDetail, TalerMerchantApi } from "@gnu-taler/taler-util"; -import _useSWR, { SWRHook, useSWRConfig } from "swr"; -import { useSessionContext } from "../context/session.js"; +import { TalerErrorDetail, TalerMerchantApi } from "@gnu-taler/taler-util"; +import _useSWR, { SWRHook } from "swr"; const useSWR = _useSWR as unknown as SWRHook; -interface InstanceAPI { - updateInstance: ( - data: TalerMerchantApi.InstanceReconfigurationMessage, - ) => Promise<void>; - deleteInstance: () => Promise<void>; - clearAccessToken: (currentToken: AccessToken | undefined) => Promise<void>; - // setNewAccessToken: ( - // currentToken: AccessToken | undefined, - // token: AccessToken, - // ) => Promise<void>; -} - -export function useAdminAPI(): AdminAPI { - const { request } = useBackendBaseRequest(); - const mutateAll = useMatchMutate(); - - const createInstance = async ( - instance: TalerMerchantApi.InstanceConfigurationMessage, - ): Promise<void> => { - await request(`/management/instances`, { - method: "POST", - data: instance, - }); - - mutateAll(/\/management\/instances/); - }; - - const deleteInstance = async (id: string): Promise<void> => { - await request(`/management/instances/${id}`, { - method: "DELETE", - }); - - mutateAll(/\/management\/instances/); - }; - - const purgeInstance = async (id: string): Promise<void> => { - await request(`/management/instances/${id}`, { - method: "DELETE", - params: { - purge: "YES", - }, - }); - - mutateAll(/\/management\/instances/); - }; - - return { createInstance, deleteInstance, purgeInstance }; -} - -export interface AdminAPI { - createInstance: ( - data: TalerMerchantApi.InstanceConfigurationMessage, - ) => Promise<void>; - deleteInstance: (id: string) => Promise<void>; - purgeInstance: (id: string) => Promise<void>; -} - -export function useManagementAPI(instanceId: string): InstanceAPI { - const mutateAll = useMatchMutate(); - const { - state: { backendUrl }, - logIn, - logOut, - } = useSessionContext(); - const { request } = useBackendBaseRequest(); - - const updateInstance = async ( - instance: TalerMerchantApi.InstanceReconfigurationMessage, - ): Promise<void> => { - await request(`/management/instances/${instanceId}`, { - method: "PATCH", - data: instance, - }); - - mutateAll(/\/management\/instances/); - }; - - const deleteInstance = async (): Promise<void> => { - await request(`/management/instances/${instanceId}`, { - method: "DELETE", - }); - - mutateAll(/\/management\/instances/); - }; - - const clearAccessToken = async ( - currentToken: AccessToken | undefined, - ): Promise<void> => { - await request(`/management/instances/${instanceId}/auth`, { - method: "POST", - token: currentToken, - data: { method: "external" }, - }); - - mutateAll(/\/management\/instances/); - }; - - // const setNewAccessToken = async ( - // currentToken: AccessToken | undefined, - // newToken: AccessToken, - // ): Promise<void> => { - - // await request(`/management/instances/${instanceId}/auth`, { - // method: "POST", - // token: currentToken, - // data: { method: "token", token: newToken }, - // }); - - // const resp = await requestNewLoginToken(backendUrl, newToken); - // if (resp.valid) { - // logIn({ token: resp.token as AccessToken }); - // } else { - // logOut(); - // } - - // mutateAll(/\/management\/instances/); - // }; - - return { - updateInstance, - deleteInstance, - // setNewAccessToken, - clearAccessToken, - }; -} - -export function useInstanceAPI(): InstanceAPI { - const { mutate } = useSWRConfig(); - const { - state: { backendUrl }, - } = useSessionContext(); - - const { request } = useBackendInstanceRequest(); - const { state, logIn, logOut } = useSessionContext(); - - const adminToken = - state.status === "loggedIn" && state.isAdmin ? state.token : undefined; - - const updateInstance = async ( - instance: TalerMerchantApi.InstanceReconfigurationMessage, - ): Promise<void> => { - await request(`/private/`, { - method: "PATCH", - data: instance, - }); - - if (adminToken) { - mutate(["/private/instances", adminToken, backendUrl], null); - } - mutate([`/private/`], null); - }; - - const deleteInstance = async (): Promise<void> => { - await request(`/private/`, { - method: "DELETE", - // token: adminToken, - }); - - if (adminToken) { - mutate(["/private/instances", adminToken, backendUrl], null); - } - mutate([`/private/`], null); - }; - - const clearAccessToken = async ( - currentToken: AccessToken | undefined, - ): Promise<void> => { - await request(`/private/auth`, { - method: "POST", - token: currentToken, - data: { method: "external" }, - }); - - mutate([`/private/`], null); - }; - - // const setNewAccessToken = async ( - // currentToken: AccessToken | undefined, - // newToken: AccessToken, - // ): Promise<void> => { - // await request(`/private/auth`, { - // method: "POST", - // token: currentToken, - // data: { method: "token", token: newToken }, - // }); - - // const resp = await requestNewLoginToken(backendUrl, newToken); - // if (resp.valid) { - // logIn({ token: resp.token as AccessToken }); - // } else { - // logOut(); - // } - - // mutate([`/private/`], null); - // }; - - return { - updateInstance, - deleteInstance, - // setNewAccessToken, - clearAccessToken, - }; -} export function useInstanceDetails(): HttpResponse< TalerMerchantApi.QueryInstancesResponse, diff --git a/packages/merchant-backoffice-ui/src/hooks/templates.ts b/packages/merchant-backoffice-ui/src/hooks/templates.ts index 2e39a0c46..2da02e3c7 100644 --- a/packages/merchant-backoffice-ui/src/hooks/templates.ts +++ b/packages/merchant-backoffice-ui/src/hooks/templates.ts @@ -190,20 +190,20 @@ export function useInstanceTemplates( // if the query returns less that we ask, then we have reach the end or beginning const isReachingEnd = - afterData && afterData.data.templates_list.length < totalAfter; + afterData && afterData.data.templates.length < totalAfter; const isReachingStart = args?.position === undefined || - (beforeData && beforeData.data.templates_list.length < totalBefore); + (beforeData && beforeData.data.templates.length < totalBefore); const pagination = { isReachingEnd, isReachingStart, loadMore: () => { if (!afterData || isReachingEnd) return; - if (afterData.data.templates_list.length < MAX_RESULT_SIZE) { + if (afterData.data.templates.length < MAX_RESULT_SIZE) { setPageAfter(pageAfter + 1); } else { - const from = `${afterData.data.templates_list[afterData.data.templates_list.length - 1] + const from = `${afterData.data.templates[afterData.data.templates.length - 1] .template_id }`; if (from && updatePosition) updatePosition(from); @@ -211,10 +211,10 @@ export function useInstanceTemplates( }, loadMorePrev: () => { if (!beforeData || isReachingStart) return; - if (beforeData.data.templates_list.length < MAX_RESULT_SIZE) { + if (beforeData.data.templates.length < MAX_RESULT_SIZE) { setPageBefore(pageBefore + 1); } else if (beforeData) { - const from = `${beforeData.data.templates_list[beforeData.data.templates_list.length - 1] + const from = `${beforeData.data.templates[beforeData.data.templates.length - 1] .template_id }`; if (from && updatePosition) updatePosition(from); @@ -223,17 +223,17 @@ export function useInstanceTemplates( }; // const templates = !afterData ? [] : (afterData || lastAfter).data.templates; - const templates_list = + const templates = !beforeData || !afterData ? [] - : (beforeData || lastBefore).data.templates_list + : (beforeData || lastBefore).data.templates .slice() .reverse() - .concat((afterData || lastAfter).data.templates_list); + .concat((afterData || lastAfter).data.templates); if (loadingAfter || loadingBefore) - return { loading: true, data: { templates_list } }; + return { loading: true, data: { templates } }; if (beforeData && afterData) { - return { ok: true, data: { templates_list }, ...pagination }; + return { ok: true, data: { templates }, ...pagination }; } return { loading: true }; } diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx index 731ea8939..d53d93e8b 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx @@ -123,7 +123,7 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { newValue.auth_token = undefined; newValue.auth = newToken === null || newToken === undefined ? { method: "external" } - : { method: "token", token: `secret-token:${newToken}` }; + : { method: "token", token: newToken }; if (!newValue.address) newValue.address = {}; if (!newValue.jurisdiction) newValue.jurisdiction = {}; // remove above use conversion diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx index 0e8ea1f5b..431015d6f 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx @@ -26,7 +26,6 @@ import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { NotificationCard } from "../../../components/menu/index.js"; import { useSessionContext } from "../../../context/session.js"; -import { useAdminAPI } from "../../../hooks/instance.js"; import { Notification } from "../../../utils/types.js"; import { CreatePage } from "./CreatePage.js"; @@ -38,11 +37,10 @@ interface Props { export type Entity = TalerMerchantApi.InstanceConfigurationMessage; export default function Create({ onBack, onConfirm, forceId }: Props): VNode { - const { createInstance } = useAdminAPI(); const [notif, setNotif] = useState<Notification | undefined>(undefined); const { i18n } = useTranslationContext(); const { lib } = useMerchantApiContext(); - const { logIn } = useSessionContext(); + const { state, logIn } = useSessionContext(); return ( <Fragment> @@ -54,9 +52,11 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode { onCreate={async ( d: TalerMerchantApi.InstanceConfigurationMessage, ) => { + if (state.status !== "loggedIn") return; try { - await createInstance(d); + await lib.management.createInstance(state.token, d); if (d.auth.token) { + //if auth has been updated, request a new access token const result = await lib.authenticate.createAccessTokenBearer( d.auth.token, { diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx index d3fa78b65..5b8cf2a5c 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx @@ -23,6 +23,7 @@ import { HttpStatusCode, TalerErrorDetail, TalerMerchantApi } from "@gnu-taler/t import { ErrorType, HttpError, + useMerchantApiContext, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; @@ -30,9 +31,10 @@ import { useState } from "preact/hooks"; import { Loading } from "../../../components/exception/loading.js"; import { NotificationCard } from "../../../components/menu/index.js"; import { DeleteModal, PurgeModal } from "../../../components/modal/index.js"; -import { useAdminAPI, useBackendInstances } from "../../../hooks/instance.js"; +import { useBackendInstances } from "../../../hooks/instance.js"; import { Notification } from "../../../utils/types.js"; import { View } from "./View.js"; +import { useSessionContext } from "../../../context/session.js"; interface Props { onCreate: () => void; @@ -55,9 +57,10 @@ export default function Instances({ useState<TalerMerchantApi.Instance | null>(null); const [purging, setPurging] = useState<TalerMerchantApi.Instance | null>(null); - const { deleteInstance, purgeInstance } = useAdminAPI(); const [notif, setNotif] = useState<Notification | undefined>(undefined); const { i18n } = useTranslationContext(); + const { lib } = useMerchantApiContext(); + const { state } = useSessionContext(); if (result.loading) return <Loading />; if (!result.ok) { @@ -90,9 +93,12 @@ export default function Instances({ element={deleting} onCancel={() => setDeleting(null)} onConfirm={async (): Promise<void> => { + if (state.status !== "loggedIn") { + return; + } try { - await deleteInstance(deleting.id); - // pushNotification({ message: 'delete_success', type: 'SUCCESS' }) + await lib.management.deleteInstance(state.token, deleting.id); + // pushNotification({message: 'delete_success', type: 'SUCCESS' }) setNotif({ message: i18n.str`Instance "${deleting.name}" (ID: ${deleting.id}) has been deleted`, type: "SUCCESS", @@ -103,7 +109,7 @@ export default function Instances({ type: "ERROR", description: error instanceof Error ? error.message : undefined, }); - // pushNotification({ message: 'delete_error', type: 'ERROR' }) + // pushNotification({message: 'delete_error', type: 'ERROR' }) } setDeleting(null); }} @@ -114,8 +120,11 @@ export default function Instances({ element={purging} onCancel={() => setPurging(null)} onConfirm={async (): Promise<void> => { + if (state.status !== "loggedIn") { + return; + } try { - await purgeInstance(purging.id); + await lib.management.deleteInstance(state.token, purging.id, { purge: true }); setNotif({ message: i18n.str`Instance '${purging.name}' (ID: ${purging.id}) has been disabled`, type: "SUCCESS", diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx index b76abee30..2714c8e02 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx @@ -13,12 +13,12 @@ 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 { ErrorType, HttpError } from "@gnu-taler/web-util/browser"; +import { ErrorType, HttpError, useMerchantApiContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../components/exception/loading.js"; import { DeleteModal } from "../../../components/modal/index.js"; -import { useInstanceAPI, useInstanceDetails } from "../../../hooks/instance.js"; +import { useInstanceDetails } from "../../../hooks/instance.js"; import { DetailPage } from "./DetailPage.js"; import { HttpStatusCode, TalerErrorDetail } from "@gnu-taler/taler-util"; import { useSessionContext } from "../../../context/session.js"; @@ -42,7 +42,8 @@ export default function Detail({ const result = useInstanceDetails(); const [deleting, setDeleting] = useState<boolean>(false); - const { deleteInstance } = useInstanceAPI(); + // const { deleteInstance } = useInstanceAPI(); + const { lib } = useMerchantApiContext(); if (result.loading) return <Loading />; if (!result.ok) { @@ -71,8 +72,11 @@ export default function Detail({ element={{ name: result.data.name, id: state.instance }} onCancel={() => setDeleting(false)} onConfirm={async (): Promise<void> => { + if (state.status !== "loggedIn") { + return + } try { - await deleteInstance(); + await lib.management.deleteCurrentInstance(state.token); onDelete(); } catch (error) { //FIXME: show message error diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx index 2138d24a4..40ca6ac98 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx @@ -92,7 +92,7 @@ export default function ListTemplates({ /> <ListPage - templates={result.data.templates_list} + templates={result.data.templates} onLoadMoreBefore={ result.isReachingStart ? result.loadMorePrev : undefined } diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx index f2b1db29b..c833b908c 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx @@ -77,9 +77,9 @@ export function DetailPage({ async function submitForm() { if (hasErrors) return; const oldToken = hasToken - ? (`secret-token:${form.old_token}` as AccessToken) + ? (form.old_token as AccessToken) : undefined; - const newToken = `secret-token:${form.new_token}` as AccessToken; + const newToken = form.new_token as AccessToken; onNewToken(oldToken, newToken); } @@ -133,8 +133,7 @@ export function DetailPage({ class="button" onClick={() => { if (hasToken) { - const oldToken = - `secret-token:${form.old_token}` as AccessToken; + const oldToken = form.old_token as AccessToken; onClearToken(oldToken); } else { onClearToken(undefined); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx index 13b5c45f1..f3c9a52ea 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx @@ -13,16 +13,16 @@ 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 { AccessToken, HttpStatusCode, TalerErrorDetail } from "@gnu-taler/taler-util"; +import { HttpStatusCode, TalerErrorDetail } from "@gnu-taler/taler-util"; import { ErrorType, HttpError, useMerchantApiContext, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../components/exception/loading.js"; import { NotificationCard } from "../../../components/menu/index.js"; -import { useInstanceAPI, useInstanceDetails } from "../../../hooks/instance.js"; +import { useSessionContext } from "../../../context/session.js"; +import { useInstanceDetails } from "../../../hooks/instance.js"; import { Notification } from "../../../utils/types.js"; import { DetailPage } from "./DetailPage.js"; -import { useSessionContext } from "../../../context/session.js"; interface Props { onUnauthorized: () => VNode; @@ -43,7 +43,7 @@ export default function Token({ const { lib } = useMerchantApiContext(); const { logIn } = useSessionContext(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const { clearAccessToken } = useInstanceAPI(); + // const { clearAccessToken } = useInstanceAPI(); const result = useInstanceDetails() if (result.loading) return <Loading />; @@ -71,7 +71,9 @@ export default function Token({ hasToken={hasToken} onClearToken={async (currentToken): Promise<void> => { try { - await clearAccessToken(currentToken); + await lib.management.updateCurrentInstanceAuthentication(currentToken, { + method: "external", + }) onChange(); } catch (error) { if (error instanceof Error) { diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx index d28ca0555..32e4e149c 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx @@ -13,11 +13,12 @@ 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 { HttpStatusCode, TalerErrorDetail, TalerMerchantApi } from "@gnu-taler/taler-util"; +import { HttpStatusCode, TalerErrorDetail, TalerMerchantApi, TalerMerchantInstanceHttpClient } from "@gnu-taler/taler-util"; import { ErrorType, HttpError, HttpResponse, + useMerchantApiContext, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; @@ -25,13 +26,12 @@ import { useState } from "preact/hooks"; import { Loading } from "../../../components/exception/loading.js"; import { NotificationCard } from "../../../components/menu/index.js"; import { - useInstanceAPI, useInstanceDetails, useManagedInstanceDetails, - useManagementAPI, } from "../../../hooks/instance.js"; import { Notification } from "../../../utils/types.js"; import { UpdatePage } from "./UpdatePage.js"; +import { useSessionContext } from "../../../context/session.js"; export interface Props { onBack: () => void; @@ -44,19 +44,21 @@ export interface Props { } export default function Update(props: Props): VNode { - const { updateInstance } = useInstanceAPI(); + const { lib } = useMerchantApiContext(); + const updateInstance = lib.management.updateCurrentInstance.bind(lib.management) const result = useInstanceDetails(); - return CommonUpdate(props, result, updateInstance, ); + return CommonUpdate(props, result, updateInstance,); } export function AdminUpdate(props: Props & { instanceId: string }): VNode { - const { updateInstance } = useManagementAPI( - props.instanceId, - ); + const { lib } = useMerchantApiContext(); + const t = lib.instance(props.instanceId) + const updateInstance = lib.instance(props.instanceId).updateCurrentInstance.bind(t) const result = useManagedInstanceDetails(props.instanceId); - return CommonUpdate(props, result, updateInstance, ); + return CommonUpdate(props, result, updateInstance,); } + function CommonUpdate( { onBack, @@ -69,10 +71,11 @@ function CommonUpdate( TalerMerchantApi.QueryInstancesResponse, TalerErrorDetail >, - updateInstance: any, + updateInstance: typeof TalerMerchantInstanceHttpClient.prototype.updateCurrentInstance, ): VNode { const [notif, setNotif] = useState<Notification | undefined>(undefined); const { i18n } = useTranslationContext(); + const { state } = useSessionContext(); if (result.loading) return <Loading />; if (!result.ok) { @@ -99,11 +102,14 @@ function CommonUpdate( onUpdate={( d: TalerMerchantApi.InstanceReconfigurationMessage, ): Promise<void> => { - return updateInstance(d) + if (state.status !== "loggedIn") { + return Promise.resolve(); + } + return updateInstance(state.token, d) .then(onConfirm) .catch((error: Error) => setNotif({ - message: i18n.str`Failed to create instance`, + message: i18n.str`Failed to update instance`, type: "ERROR", description: error.message, }), diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts index 394625e38..fec1e7143 100644 --- a/packages/taler-util/src/http-client/merchant.ts +++ b/packages/taler-util/src/http-client/merchant.ts @@ -385,7 +385,7 @@ export class TalerMerchantInstanceHttpClient { headers, }); switch (resp.status) { - case HttpStatusCode.Ok: + case HttpStatusCode.NoContent: return opEmptySuccess(resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); @@ -421,7 +421,7 @@ export class TalerMerchantInstanceHttpClient { /** * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private */ - async deleteCurrentInstance(token: AccessToken | undefined, params: { purge?: boolean }) { + async deleteCurrentInstance(token: AccessToken | undefined, params: { purge?: boolean } = {}) { const url = new URL(`private`, this.baseUrl); if (params.purge) { diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index 8578eecd4..682b08984 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -801,7 +801,7 @@ export const codecForOtpDeviceDetails = export const codecForTemplateSummaryResponse = (): Codec<TalerMerchantApi.TemplateSummaryResponse> => buildCodecForObject<TalerMerchantApi.TemplateSummaryResponse>() - .property("templates_list", codecForList(codecForTemplateEntry())) + .property("templates", codecForList(codecForTemplateEntry())) .build("TalerMerchantApi.TemplateSummaryResponse"); export const codecForTemplateEntry = @@ -4606,7 +4606,7 @@ export namespace TalerMerchantApi { export interface TemplateSummaryResponse { // List of templates that are present in our backend. - templates_list: TemplateEntry[]; + templates: TemplateEntry[]; } export interface TemplateEntry { diff --git a/packages/web-util/src/context/merchant-api.ts b/packages/web-util/src/context/merchant-api.ts index a531a5958..23ea05cd2 100644 --- a/packages/web-util/src/context/merchant-api.ts +++ b/packages/web-util/src/context/merchant-api.ts @@ -79,6 +79,9 @@ type Evictors = { type ConfigResult<T> = | undefined | { type: "ok"; config: T; hints: VersionHint[] } + | ConfigResultFail<T>; + +export type ConfigResultFail<T> = | { type: "incompatible"; result: T; supported: string } | { type: "error"; error: TalerError }; @@ -91,7 +94,7 @@ export const MerchantApiProvider = ({ baseUrl: URL; evictors?: Evictors; children: ComponentChildren; - frameOnError: FunctionComponent<{ children: ComponentChildren }>; + frameOnError: FunctionComponent<{ state: ConfigResultFail<TalerMerchantApi.VersionResponse> | undefined }>; }): VNode => { const [checked, setChecked] = useState<ConfigResult<TalerMerchantApi.VersionResponse>>(); @@ -120,24 +123,8 @@ export const MerchantApiProvider = ({ }); }, []); - if (checked === undefined) { - return h(frameOnError, { - children: h("div", {}, "checking compatibility with server..."), - }); - } - if (checked.type === "error") { - return h(frameOnError, { - children: h(ErrorLoading, { error: checked.error, showDetail: true }), - }); - } - if (checked.type === "incompatible") { - return h(frameOnError, { - children: h( - "div", - {}, - i18n.str`The server version is not supported. Supported version "${checked.supported}", server version "${checked.result.version}"`, - ), - }); + if (!checked || checked.type !== "ok") { + return h(frameOnError, { state: checked }, []); } const value: MerchantContextType = { diff --git a/packages/web-util/src/utils/request.ts b/packages/web-util/src/utils/request.ts index f8a892d99..70f943540 100644 --- a/packages/web-util/src/utils/request.ts +++ b/packages/web-util/src/utils/request.ts @@ -41,7 +41,7 @@ export async function defaultRequestHandler<T>( ): Promise<HttpResponseOk<T>> { const requestHeaders: Record<string, string> = {}; if (options.token) { - requestHeaders.Authorization = `Bearer ${options.token}`; + requestHeaders.Authorization = `Bearer secret-token:${options.token}`; } else if (options.basicAuth) { requestHeaders.Authorization = `Basic ${base64encode( `${options.basicAuth.username}:${options.basicAuth.password}`, |