/*
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;
}