/* This file is part of GNU Taler (C) 2022 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ import { useState } from "preact/hooks"; import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js"; import { useBackendState } from "./backend.js"; import { AccessToken, AmountJson, Amounts, OperationOk, TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } from "@gnu-taler/taler-util"; import _useSWR, { SWRHook } from "swr"; import { useBankCoreApiContext } from "../context/config.js"; import { assertUnreachable } from "../pages/HomePage.js"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 const useSWR = _useSWR as unknown as SWRHook; export type TransferCalculation = { debit: AmountJson; credit: AmountJson; beforeFee: AmountJson; }; type EstimatorFunction = ( amount: AmountJson, sellFee: AmountJson, sellRate: number, ) => Promise; type CashoutEstimators = { estimateByCredit: EstimatorFunction; estimateByDebit: EstimatorFunction; }; export function useEstimator(): CashoutEstimators { const { state } = useBackendState(); const { api } = useBankCoreApiContext(); return { estimateByCredit: async (amount, fee, rate) => { const resp = await api.getCashoutRate({ credit: amount }); if (resp.type === "fail") { // can't happen // not-supported: it should not be able to call this function // wrong-calculation: we are using just one parameter throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint) } const credit = amount; const _credit = { ...credit, currency: fee.currency }; const beforeFee = Amounts.sub(_credit, fee).amount; const debit = Amounts.parseOrThrow(resp.body.amount_debit); return { debit, beforeFee, credit, }; }, estimateByDebit: async (amount, fee, rate) => { const zeroBalance = Amounts.zeroOfCurrency(fee.currency); const zeroFiat = Amounts.zeroOfCurrency(fee.currency); const zeroCalc = { debit: zeroBalance, credit: zeroFiat, beforeFee: zeroBalance, }; const resp = await api.getCashoutRate({ debit: amount }); if (resp.type === "fail") { // can't happen // not-supported: it should not be able to call this function // wrong-calculation: we are using just one parameter throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint) } const credit = Amounts.parseOrThrow(resp.body.amount_credit); const _credit = { ...credit, currency: fee.currency }; const debit = amount; const beforeFee = Amounts.sub(_credit, fee).amount; return { debit, beforeFee, credit, }; }, }; } export function useRatiosAndFeeConfig() { const { api, config } = useBankCoreApiContext(); function fetcher() { return api.getConversionRates() } const { data, error } = useSWR, TalerHttpError>( !config.have_cashout || !config.fiat_currency ? false : [, "getConversionRates"], fetcher, { refreshInterval: 60 * 1000, refreshWhenHidden: false, revalidateOnFocus: false, revalidateIfStale: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }); if (data) return data; if (error) return error; return undefined; } interface PaginationFilter { account?: string; page?: number; } export function useBusinessAccounts() { const { state: credentials } = useBackendState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token const { api } = useBankCoreApiContext(); const [offset, setOffset] = useState(); function fetcher([token, offset]: [AccessToken, string]) { return api.getAccounts(token, { limit: MAX_RESULT_SIZE, offset, order: "asc" }) } const { data, error } = useSWR, TalerHttpError>( [token, offset, "getAccounts"], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }, ); const isLastPage = data && data.type === "ok" && data.body.accounts.length < PAGE_SIZE; const isFirstPage = false; const pagination = { isLastPage, isFirstPage, loadMore: () => { if (isLastPage || data?.type !== "ok") return; const list = data.body.accounts if (list.length < MAX_RESULT_SIZE) { //FIXME: define pagination // setOffset(list[list.length - 1].row_id); } }, loadMorePrev: () => { null; }, }; if (data) return { ok: true, data, ...pagination }; if (error) return error; return undefined; } type CashoutWithId = TalerCorebankApi.CashoutStatusResponse & { id: string } function notUndefined(c: CashoutWithId | undefined): c is CashoutWithId { return c !== undefined } export function useCashouts(account: string) { const { state: credentials } = useBackendState(); const { api } = useBankCoreApiContext(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token async function fetcher([username, token]: [string, AccessToken]) { const list = await api.getAccountCashouts({ username, token }) if (list.type !== "ok") { assertUnreachable(list.type) } const all: Array = await Promise.all(list.body.cashouts.map(c => { return api.getCashoutById({ username, token }, c.cashout_id).then(r => { if (r.type === "fail") return undefined return { ...r.body, id: c.cashout_id } }) })) const cashouts = all.filter(notUndefined) return { type: "ok" as const, body: { cashouts } } } const { data, error } = useSWR, TalerHttpError>( [account, token, "getAccountCashouts"], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }); if (data) return data; if (error) return error; return undefined; } export function useCashoutDetails(cashoutId: string) { const { state: credentials } = useBackendState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials const { api } = useBankCoreApiContext(); async function fetcher([username, token, id]: [string, AccessToken, string]) { return api.getCashoutById({ username, token }, id) } const { data, error } = useSWR, TalerHttpError>( [creds?.username, creds?.token, cashoutId, "getCashoutById"], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, errorRetryCount: 0, errorRetryInterval: 1, shouldRetryOnError: false, keepPreviousData: true, }); if (data) return data; if (error) return error; return undefined; }