aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/aml-backoffice-ui/src/App.tsx31
-rw-r--r--packages/aml-backoffice-ui/src/hooks/account.ts32
-rw-r--r--packages/aml-backoffice-ui/src/hooks/decisions.ts17
-rw-r--r--packages/aml-backoffice-ui/src/pages/CaseDetails.tsx702
-rw-r--r--packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx36
-rw-r--r--packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx22
-rw-r--r--packages/bank-ui/src/pages/PaytoWireTransferForm.tsx5
-rw-r--r--packages/kyc-ui/src/app.tsx12
-rw-r--r--packages/taler-util/src/http-client/exchange.ts16
9 files changed, 493 insertions, 380 deletions
diff --git a/packages/aml-backoffice-ui/src/App.tsx b/packages/aml-backoffice-ui/src/App.tsx
index 0b66e0d26..d2cb2fd62 100644
--- a/packages/aml-backoffice-ui/src/App.tsx
+++ b/packages/aml-backoffice-ui/src/App.tsx
@@ -13,7 +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 { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
+import {
+ CacheEvictor,
+ TalerExchangeCacheEviction,
+ assertUnreachable,
+ canonicalizeBaseUrl,
+} from "@gnu-taler/taler-util";
import {
BrowserHashNavigationProvider,
ExchangeApiProvider,
@@ -31,6 +36,8 @@ import { strings } from "./i18n/strings.js";
import "./scss/main.css";
import { UiSettings, fetchUiSettings } from "./context/ui-settings.js";
import { UiFormsProvider, fetchUiForms } from "./context/ui-forms.js";
+import { revalidateAccountDecisions } from "./hooks/decisions.js";
+import { revalidateAccountInformation } from "./hooks/account.js";
const WITH_LOCAL_STORAGE_CACHE = false;
@@ -56,6 +63,9 @@ export function App(): VNode {
<ExchangeApiProvider
baseUrl={new URL("/", baseUrl)}
frameOnError={ExchangeAmlFrame}
+ evictors={{
+ exchange: evictExchangeSwrCache,
+ }}
>
<SWRConfig
value={{
@@ -136,3 +146,22 @@ function getInitialBackendBaseURL(
return canonicalizeBaseUrl(window.origin);
}
}
+
+const evictExchangeSwrCache: CacheEvictor<TalerExchangeCacheEviction> = {
+ async notifySuccess(op) {
+ switch (op) {
+ case TalerExchangeCacheEviction.MAKE_AML_DECISION: {
+ await revalidateAccountDecisions();
+ await revalidateAccountInformation();
+ return;
+ }
+ case TalerExchangeCacheEviction.CREATE_DESCISION:
+ case TalerExchangeCacheEviction.UPLOAD_KYC_FORM: {
+ return;
+ }
+ default: {
+ assertUnreachable(op);
+ }
+ }
+ },
+};
diff --git a/packages/aml-backoffice-ui/src/hooks/account.ts b/packages/aml-backoffice-ui/src/hooks/account.ts
index 1c5f013a7..9889c906d 100644
--- a/packages/aml-backoffice-ui/src/hooks/account.ts
+++ b/packages/aml-backoffice-ui/src/hooks/account.ts
@@ -13,25 +13,43 @@
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 { OfficerAccount, PaytoString, TalerExchangeResultByMethod, TalerHttpError } from "@gnu-taler/taler-util";
+import {
+ OfficerAccount,
+ PaytoString,
+ TalerExchangeResultByMethod,
+ TalerHttpError,
+} from "@gnu-taler/taler-util";
// FIX default import https://github.com/microsoft/TypeScript/issues/49189
-import _useSWR, { SWRHook } from "swr";
+import _useSWR, { SWRHook, mutate } from "swr";
import { useOfficer } from "./officer.js";
import { useExchangeApiContext } from "@gnu-taler/web-util/browser";
const useSWR = _useSWR as unknown as SWRHook;
+export function revalidateAccountInformation() {
+ return mutate(
+ (key) =>
+ Array.isArray(key) &&
+ key[key.length - 1] === "getAmlAttributesForAccount",
+ undefined,
+ { revalidate: true },
+ );
+}
export function useAccountInformation(paytoHash: string) {
const officer = useOfficer();
const session = officer.state === "ready" ? officer.account : undefined;
- const { lib: {exchange: api} } = useExchangeApiContext();
+ const {
+ lib: { exchange: api },
+ } = useExchangeApiContext();
async function fetcher([officer, account]: [OfficerAccount, PaytoString]) {
- return await api.getAmlAttributesForAccount(officer, account)
+ return await api.getAmlAttributesForAccount(officer, account);
}
- const { data, error } = useSWR<TalerExchangeResultByMethod<"getAmlAttributesForAccount">, TalerHttpError>(
- !session ? undefined : [session, paytoHash], fetcher, {
+ const { data, error } = useSWR<
+ TalerExchangeResultByMethod<"getAmlAttributesForAccount">,
+ TalerHttpError
+ >(!session ? undefined : [session, paytoHash], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -47,5 +65,3 @@ export function useAccountInformation(paytoHash: string) {
if (error) return error;
return undefined;
}
-
-
diff --git a/packages/aml-backoffice-ui/src/hooks/decisions.ts b/packages/aml-backoffice-ui/src/hooks/decisions.ts
index de8f1637e..eb861ae14 100644
--- a/packages/aml-backoffice-ui/src/hooks/decisions.ts
+++ b/packages/aml-backoffice-ui/src/hooks/decisions.ts
@@ -20,10 +20,10 @@ import {
OfficerAccount,
OperationOk,
TalerExchangeResultByMethod,
- TalerHttpError
+ TalerHttpError,
} from "@gnu-taler/taler-util";
import { useExchangeApiContext } from "@gnu-taler/web-util/browser";
-import _useSWR, { SWRHook } from "swr";
+import _useSWR, { SWRHook, mutate } from "swr";
import { useOfficer } from "./officer.js";
const useSWR = _useSWR as unknown as SWRHook;
@@ -49,7 +49,7 @@ export function useCurrentDecisions(filtered?: boolean) {
async function fetcher([officer, offset, investigation]: [
OfficerAccount,
string | undefined,
- boolean | undefined
+ boolean | undefined,
]) {
return await api.getAmlDecisions(officer, {
order: "dec",
@@ -64,7 +64,9 @@ export function useCurrentDecisions(filtered?: boolean) {
TalerExchangeResultByMethod<"getAmlDecisions">,
TalerHttpError
>(
- !session ? undefined : [session, offset, filtered ? true : filtered, "getAmlDecisions"],
+ !session
+ ? undefined
+ : [session, offset, filtered ? true : filtered, "getAmlDecisions"],
fetcher,
);
@@ -77,6 +79,13 @@ export function useCurrentDecisions(filtered?: boolean) {
);
}
+export function revalidateAccountDecisions() {
+ return mutate(
+ (key) => Array.isArray(key) && key[key.length - 1] === "getAmlDecisions",
+ undefined,
+ { revalidate: true },
+ );
+}
/**
* @param account
* @param args
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
index b421bd8c5..a1c09025b 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -18,6 +18,7 @@ import {
AmountJson,
Amounts,
Codec,
+ CurrencySpecification,
HttpStatusCode,
OperationFail,
OperationOk,
@@ -40,7 +41,7 @@ import {
ShowInputErrorLabel,
Time,
useExchangeApiContext,
- useTranslationContext
+ useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { format, formatDuration, intervalToDuration } from "date-fns";
import { Fragment, Ref, VNode, h } from "preact";
@@ -165,7 +166,7 @@ export function getEventsFromAmlHistory(
when: AbsoluteTime.fromProtocolTimestamp(event.collection_time),
values: !event.attributes ? {} : event.attributes,
provider: event.provider_name,
- } as AmlEvent
+ } as AmlEvent;
});
return ke.sort(selectSooner);
}
@@ -176,11 +177,10 @@ export function CaseDetails({ account }: { account: string }) {
justification: Justification;
metadata: FormMetadata;
}>();
- const { config, lib } = useExchangeApiContext()
+ const { config, lib } = useExchangeApiContext();
const officer = useOfficer();
const session = officer.state === "ready" ? officer.account : undefined;
-
const { i18n } = useTranslationContext();
const details = useAccountInformation(account);
const history = useAccountDecisions(account);
@@ -220,14 +220,12 @@ export function CaseDetails({ account }: { account: string }) {
}
}
const { details: accountDetails } = details.body;
- const activeDecision = history.body.find(d => d.is_active)
- const restDecisions = !activeDecision ? history.body : history.body.filter(d => d.rowid !== activeDecision.rowid)
+ const activeDecision = history.body.find((d) => d.is_active);
+ const restDecisions = !activeDecision
+ ? history.body
+ : history.body.filter((d) => d.rowid !== activeDecision.rowid);
- const events = getEventsFromAmlHistory(
- accountDetails,
- i18n,
- allForms,
- );
+ const events = getEventsFromAmlHistory(accountDetails, i18n, allForms);
if (showForm !== undefined) {
return (
@@ -261,27 +259,36 @@ export function CaseDetails({ account }: { account: string }) {
</h1>
</header>
- {!activeDecision || !activeDecision.to_investigate ? undefined : <Attention title={i18n.str`Under investigation`} type="warning" >
- <i18n.Translate>This account requires a manual review and is waiting for a decision to be made.</i18n.Translate>
- </Attention>}
+ {!activeDecision || !activeDecision.to_investigate ? undefined : (
+ <Attention title={i18n.str`Under investigation`} type="warning">
+ <i18n.Translate>
+ This account requires a manual review and is waiting for a decision
+ to be made.
+ </i18n.Translate>
+ </Attention>
+ )}
<div>
<button
onClick={async () => {
if (!session) return;
lib.exchange.makeAmlDesicion(session, {
- decision_time: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now()),
+ decision_time: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.now(),
+ ),
h_payto: account,
justification: "",
keep_investigating: false,
properties: {},
new_rules: {
custom_measures: {},
- expiration_time: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.never()),
+ expiration_time: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.never(),
+ ),
rules: FREEZE_RULES(config.currency),
successor_measure: "verboten",
},
- })
+ });
}}
class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
>
@@ -291,20 +298,23 @@ export function CaseDetails({ account }: { account: string }) {
onClick={async () => {
if (!session) return;
lib.exchange.makeAmlDesicion(session, {
- decision_time: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now()),
+ decision_time: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.now(),
+ ),
h_payto: account,
justification: "",
keep_investigating: false,
properties: {},
new_rules: {
custom_measures: {},
- expiration_time: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.never()),
+ expiration_time: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.never(),
+ ),
rules: THRESHOLD_100_HOUR(config.currency),
successor_measure: "verboten",
},
- })
+ });
}}
-
class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
>
<i18n.Translate>Set threshold to 100 / hour</i18n.Translate>
@@ -313,20 +323,23 @@ export function CaseDetails({ account }: { account: string }) {
onClick={async () => {
if (!session) return;
lib.exchange.makeAmlDesicion(session, {
- decision_time: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now()),
+ decision_time: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.now(),
+ ),
h_payto: account,
justification: "",
keep_investigating: false,
properties: {},
new_rules: {
custom_measures: {},
- expiration_time: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.never()),
+ expiration_time: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.never(),
+ ),
rules: THRESHOLD_2000_WEEK(config.currency),
successor_measure: "verboten",
},
- })
+ });
}}
-
class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
>
<i18n.Translate>Set threshold to 2000 / week</i18n.Translate>
@@ -335,7 +348,9 @@ export function CaseDetails({ account }: { account: string }) {
onClick={async () => {
if (!session) return;
lib.exchange.makeAmlDesicion(session, {
- decision_time: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now()),
+ decision_time: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.now(),
+ ),
h_payto: account,
justification: "",
keep_investigating: false,
@@ -343,26 +358,30 @@ export function CaseDetails({ account }: { account: string }) {
new_measure: "onlyTalers",
new_rules: {
custom_measures: {},
- expiration_time: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.never()),
+ expiration_time: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.never(),
+ ),
rules: FREEZE_RULES(config.currency),
successor_measure: "verboten",
},
- })
+ });
}}
-
class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
>
<i18n.Translate>Ask for more information</i18n.Translate>
</button>
</div>
- {!activeDecision ? <Attention title={i18n.str`No active decision found`} type="warning" /> : <Fragment>
- <h1 class="my-4 text-base font-semibold leading-6 text-black">
- <i18n.Translate>Current active rules</i18n.Translate>
- </h1>
-
- <ShowDecisionInfo decision={activeDecision} />
- </Fragment>}
+ {!activeDecision ? (
+ <Attention title={i18n.str`No active rules found`} type="warning" />
+ ) : (
+ <Fragment>
+ <h1 class="my-4 text-base font-semibold leading-6 text-black">
+ <i18n.Translate>Current active rules</i18n.Translate>
+ </h1>
+ <ShowDecisionInfo decision={activeDecision} />
+ </Fragment>
+ )}
<h1 class="my-4 text-base font-semibold leading-6 text-black">
<i18n.Translate>KYC collection events</i18n.Translate>
</h1>
@@ -386,91 +405,143 @@ export function CaseDetails({ account }: { account: string }) {
/>
{/* {selected && <ShowEventDetails event={selected} />} */}
{selected && <ShowConsolidated history={events} until={selected} />}
- {restDecisions.length > 0 ?
+ {restDecisions.length > 0 ? (
<Fragment>
<h1 class="my-4 text-base font-semibold leading-6 text-black">
<i18n.Translate>Previous AML decisions</i18n.Translate>
</h1>
- {restDecisions.map(d => {
- return <ShowDecisionInfo decision={d} />
+ {restDecisions.map((d) => {
+ return <ShowDecisionInfo decision={d} />;
})}
-
- </Fragment> : <div />}
+ </Fragment>
+ ) : (
+ <div />
+ )}
</div>
);
}
-function ShowDecisionInfo({ decision }: { decision: TalerExchangeApi.AmlDecision }): VNode {
+function ShowDecisionInfo({
+ decision,
+}: {
+ decision: TalerExchangeApi.AmlDecision;
+}): VNode {
const { i18n } = useTranslationContext();
- const { config } = useExchangeApiContext()
-
- return <Fragment>
-
- <div class="sm:col-span-5">
- <label
- for="amount"
- class="block text-sm font-medium leading-6 text-gray-900"
- >
- <b><i18n.Translate>Since</i18n.Translate></b>
- <div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
-
- <div
- class="p-4 disabled:bg-gray-200 rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900 placeholder:text-gray-400 sm:text-sm sm:leading-6"
- >
+ const { config } = useExchangeApiContext();
- <Time format="dd/MM/yyyy HH:mm:ss" timestamp={AbsoluteTime.fromProtocolTimestamp(decision.decision_time)} />
+ return (
+ <Fragment>
+ <textarea>asdasdlaksdj</textarea>
+ <div class="flex col justify-between">
+ <div class="flex mt-2 rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
+ <div class="pointer-events-none bg-gray-200 inset-y-0 flex items-center px-3">
+ <i18n.Translate>Since</i18n.Translate>
+ </div>
+ <div class="p-2 disabled:bg-gray-200 text-right rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900 placeholder:text-gray-400 sm:text-sm sm:leading-6">
+ <Time
+ format="dd/MM/yyyy HH:mm:ss"
+ timestamp={AbsoluteTime.fromProtocolTimestamp(
+ decision.decision_time,
+ )}
+ />
</div>
</div>
-
- </label>
- </div>
-
- <div class="sm:col-span-5">
- <label
- for="amount"
- class="block text-sm font-medium leading-6 text-gray-900"
- >
- {AbsoluteTime.isExpired(AbsoluteTime.fromProtocolTimestamp(decision.limits.expiration_time)) ?
- <b><i18n.Translate>Expired at</i18n.Translate></b> :
- <b><i18n.Translate>Expires at</i18n.Translate></b>
- }
- <div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
-
- <div
- class="p-4 disabled:bg-gray-200 rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900 placeholder:text-gray-400 sm:text-sm sm:leading-6"
- >
-
- <Time format="dd/MM/yyyy HH:mm:ss" timestamp={AbsoluteTime.fromProtocolTimestamp(decision.limits.expiration_time)} />
+ <div class="flex mt-2 rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
+ <div class="pointer-events-none bg-gray-200 inset-y-0 flex items-center px-3">
+ {AbsoluteTime.isExpired(
+ AbsoluteTime.fromProtocolTimestamp(
+ decision.limits.expiration_time,
+ ),
+ ) ? (
+ <i18n.Translate>Expired</i18n.Translate>
+ ) : (
+ <i18n.Translate>Expires</i18n.Translate>
+ )}
+ </div>
+ <div class="p-2 disabled:bg-gray-200 text-right rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900 placeholder:text-gray-400 sm:text-sm sm:leading-6">
+ <Time
+ format="dd/MM/yyyy HH:mm:ss"
+ timestamp={AbsoluteTime.fromProtocolTimestamp(
+ decision.decision_time,
+ )}
+ />
</div>
</div>
+ </div>
+ <table class="w-full text-center m-4">
+ <thead>
+ <tr>
+ <th>operation</th>
+ <th>timeframe</th>
+ <th>amount</th>
+ </tr>
+ </thead>
+ <tbody>
+ {decision.limits.rules.map((r) => {
+ const bySpec = Amounts.stringifyValueWithSpec(
+ Amounts.parseOrThrow(r.threshold),
+ config.currency_specification,
+ );
+ return (
+ <tr>
+ <td class="text-left">{r.operation_type}</td>
+ <td>
+ {r.timeframe.d_us === "forever"
+ ? ""
+ : formatDuration(
+ intervalToDuration({
+ start: 0,
+ end: r.timeframe.d_us / 1000,
+ }),
+ )}
+ </td>
+ <td class="text-right">
+ <RenderAmount
+ value={Amounts.parseOrThrow(r.threshold)}
+ spec={config.currency_specification}
+ />
+ </td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ </Fragment>
+ );
+}
- </label>
- </div>
- {decision.limits.rules.map(r => {
- const bySpec = Amounts.stringifyValueWithSpec(Amounts.parseOrThrow(r.threshold), config.currency_specification)
- return <div class="sm:col-span-5">
- <label
- for="amount"
- class="block text-sm font-medium leading-6 text-gray-900"
- >
- {r.operation_type}
- <InputAmount
- name="minCashout"
- left
- currency={bySpec.currency}
- value={bySpec.normal}
- onChange={undefined}
- />
- </label>
- <p class="mt-2 text-sm text-gray-500">
- over {r.timeframe.d_us === "forever" ? "" : formatDuration(intervalToDuration({ start: 0, end: r.timeframe.d_us / 1000 }))}
- </p>
- </div>
- })}
+export function RenderAmount({
+ value,
+ spec,
+ negative,
+ withColor,
+ hideSmall,
+}: {
+ spec: CurrencySpecification;
+ value: AmountJson;
+ hideSmall?: boolean;
+ negative?: boolean;
+ withColor?: boolean;
+}): VNode {
+ const neg = !!negative; // convert to true or false
- </Fragment>
+ const { currency, normal, small } = Amounts.stringifyValueWithSpec(
+ value,
+ spec,
+ );
+
+ return (
+ <span
+ data-negative={withColor ? neg : undefined}
+ class="whitespace-nowrap data-[negative=false]:text-green-600 data-[negative=true]:text-red-600"
+ >
+ {negative ? "- " : undefined}
+ {currency} {normal}{" "}
+ {!hideSmall && small && <sup class="-ml-1">{small}</sup>}
+ </span>
+ );
}
function AmlStateBadge({ state }: { state: TalerExchangeApi.AmlState }): VNode {
@@ -631,7 +702,7 @@ function InputAmount(
},
ref: Ref<HTMLInputElement>,
): VNode {
- const FRAC_SEPARATOR = ","
+ const FRAC_SEPARATOR = ",";
const { config } = useExchangeApiContext();
return (
<div class="mt-2">
@@ -658,13 +729,13 @@ function InputAmount(
if (
sep_pos !== -1 &&
l - sep_pos - 1 >
- config.currency_specification.num_fractional_input_digits
+ config.currency_specification.num_fractional_input_digits
) {
e.currentTarget.value = e.currentTarget.value.substring(
0,
sep_pos +
- config.currency_specification.num_fractional_input_digits +
- 1,
+ config.currency_specification.num_fractional_input_digits +
+ 1,
);
}
onChange(e.currentTarget.value);
@@ -702,9 +773,9 @@ function parseJustification(
listOfAllKnownForms: FormMetadata[],
):
| OperationOk<{
- justification: Justification;
- metadata: FormMetadata;
- }>
+ justification: Justification;
+ metadata: FormMetadata;
+ }>
| OperationFail<ParseJustificationFail> {
try {
const justification = JSON.parse(s);
@@ -749,242 +820,215 @@ function parseJustification(
}
}
-const THRESHOLD_2000_WEEK: (currency: string) => TalerExchangeApi.KycRule[] = (currency) => [{
- "operation_type": "WITHDRAW",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+const THRESHOLD_2000_WEEK: (currency: string) => TalerExchangeApi.KycRule[] = (
+ currency,
+) => [
+ {
+ operation_type: "WITHDRAW",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "DEPOSIT",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "DEPOSIT",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "AGGREGATE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "AGGREGATE",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "MERGE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "MERGE",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "BALANCE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "BALANCE",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "CLOSE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "CLOSE",
+ threshold: `${currency}:2000`,
+ timeframe: {
+ d_us: 7 * 24 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-}]
-
-const THRESHOLD_100_HOUR: (currency: string) => TalerExchangeApi.KycRule[] = (currency) => [{
- "operation_type": "WITHDRAW",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+];
+
+const THRESHOLD_100_HOUR: (currency: string) => TalerExchangeApi.KycRule[] = (
+ currency,
+) => [
+ {
+ operation_type: "WITHDRAW",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "DEPOSIT",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "DEPOSIT",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "AGGREGATE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "AGGREGATE",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "MERGE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "MERGE",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "BALANCE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "BALANCE",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "CLOSE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "CLOSE",
+ threshold: `${currency}:100`,
+ timeframe: {
+ d_us: 1 * 60 * 60 * 1000 * 1000,
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-}]
-
+];
-
-const FREEZE_RULES: (currency: string) => TalerExchangeApi.KycRule[] = (currency) => [{
- "operation_type": "WITHDRAW",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+const FREEZE_RULES: (currency: string) => TalerExchangeApi.KycRule[] = (
+ currency,
+) => [
+ {
+ operation_type: "WITHDRAW",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "DEPOSIT",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "DEPOSIT",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "AGGREGATE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "AGGREGATE",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "MERGE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "MERGE",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "BALANCE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "BALANCE",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-},
-{
- "operation_type": "CLOSE",
- "threshold": `${currency}:0`,
- "timeframe": {
- "d_us": "forever"
+ {
+ operation_type: "CLOSE",
+ threshold: `${currency}:0`,
+ timeframe: {
+ d_us: "forever",
+ },
+ measures: ["verboten"],
+ display_priority: 1,
+ exposed: true,
+ is_and_combinator: true,
},
- "measures": [
- "verboten"
- ],
- "display_priority": 1,
- "exposed": true,
- "is_and_combinator": true
-}]
-
+];
diff --git a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
index d1257c8fa..b79746107 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx
@@ -157,28 +157,30 @@ export function CaseUpdate({
value: validatedForm,
};
- const decision: Omit<TalerExchangeApi.AmlDecisionRequest, "officer_sig"> =
- {
- justification: JSON.stringify(justification),
- decision_time: TalerProtocolTimestamp.now(),
- h_payto: account,
- keep_investigating: false,
- new_rules: {
- custom_measures: {},
- expiration_time: {
- t_s: "never"
- },
- rules: [],
- successor_measure: undefined
+ const decision: Omit<
+ TalerExchangeApi.AmlDecisionRequest,
+ "officer_sig"
+ > = {
+ justification: JSON.stringify(justification),
+ decision_time: TalerProtocolTimestamp.now(),
+ h_payto: account,
+ keep_investigating: false,
+ new_rules: {
+ custom_measures: {},
+ expiration_time: {
+ t_s: "never",
},
- properties: {},
- new_measure: undefined,
- };
+ rules: [],
+ successor_measure: undefined,
+ },
+ properties: {},
+ new_measure: undefined,
+ };
return api.makeAmlDesicion(officer.account, decision);
},
() => {
- window.location.href = privatePages.cases.url({});
+ window.location.href = privatePages.profile.url({});
},
(fail) => {
switch (fail.case) {
diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
index cea8157b0..e53c74aa5 100644
--- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
+++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx
@@ -24,7 +24,7 @@ import {
FormConfiguration,
UIFormElementConfig,
UIHandlerId,
- useTranslationContext
+ useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { format } from "date-fns";
import { Fragment, VNode, h } from "preact";
@@ -33,21 +33,21 @@ import { AmlEvent } from "./CaseDetails.js";
/**
* the exchange doesn't hava a consistent api
* https://bugs.gnunet.org/view.php?id=9142
- *
- * @param data
- * @returns
+ *
+ * @param data
+ * @returns
*/
function fixProvidedInfo(data: object): object {
- return Object.entries(data).reduce((prev, [key,value]) => {
- prev[key] = value
+ return Object.entries(data).reduce((prev, [key, value]) => {
+ prev[key] = value;
if (typeof value === "object" && value["value"]) {
- const v = value["value"]
+ const v = value["value"];
if (typeof v === "object" && v["text"]) {
- prev[key].value = v["text"]
+ prev[key].value = v["text"];
}
}
- return prev
- }, {} as any)
+ return prev;
+ }, {} as any);
}
export function ShowConsolidated({
@@ -62,7 +62,7 @@ export function ShowConsolidated({
const cons = getConsolidated(history, until);
const fixed = fixProvidedInfo(cons.kyc);
- console.log("fixed", fixed)
+
const form: FormConfiguration = {
type: "double-column",
design: [
diff --git a/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx
index 852cfe816..743a1092b 100644
--- a/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -779,6 +779,11 @@ export function InputAmount(
);
}
+/**
+ * send to web-utils
+ * @param param0
+ * @returns
+ */
export function RenderAmount({
value,
spec,
diff --git a/packages/kyc-ui/src/app.tsx b/packages/kyc-ui/src/app.tsx
index fb63771d7..0c7cd71bf 100644
--- a/packages/kyc-ui/src/app.tsx
+++ b/packages/kyc-ui/src/app.tsx
@@ -20,14 +20,14 @@ import {
assertUnreachable,
canonicalizeBaseUrl,
getGlobalLogLevel,
- setGlobalLogLevelFromString
+ setGlobalLogLevelFromString,
} from "@gnu-taler/taler-util";
import {
BrowserHashNavigationProvider,
ExchangeApiProvider,
Loading,
TalerWalletIntegrationBrowserProvider,
- TranslationProvider
+ TranslationProvider,
} from "@gnu-taler/web-util/browser";
import { VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
@@ -63,7 +63,7 @@ export function App(): VNode {
baseUrl={new URL("/", baseUrl)}
frameOnError={Frame}
evictors={{
- exchange: evictExchangeSwrCache
+ exchange: evictExchangeSwrCache,
}}
>
<SWRConfig
@@ -154,10 +154,11 @@ function getInitialBackendBaseURL(
const evictExchangeSwrCache: CacheEvictor<TalerExchangeCacheEviction> = {
async notifySuccess(op) {
switch (op) {
- case TalerExchangeCacheEviction.CREATE_DESCISION:{
- await revalidateKycInfo()
+ case TalerExchangeCacheEviction.CREATE_DESCISION: {
+ await revalidateKycInfo();
return;
}
+ case TalerExchangeCacheEviction.MAKE_AML_DECISION:
case TalerExchangeCacheEviction.UPLOAD_KYC_FORM: {
return;
}
@@ -167,4 +168,3 @@ const evictExchangeSwrCache: CacheEvictor<TalerExchangeCacheEviction> = {
}
},
};
-
diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts
index 6ab7706b5..4bc888cc4 100644
--- a/packages/taler-util/src/http-client/exchange.ts
+++ b/packages/taler-util/src/http-client/exchange.ts
@@ -79,6 +79,7 @@ export type TalerExchangeErrorsByMethod<
export enum TalerExchangeCacheEviction {
CREATE_DESCISION,
UPLOAD_KYC_FORM,
+ MAKE_AML_DECISION,
}
declare const __pubId: unique symbol;
@@ -779,7 +780,7 @@ export class TalerExchangeHttpClient {
* https://docs.taler.net/core/api-exchange.html#get--kyc-proof-$PROVIDER_NAME?state=$H_PAYTO
*
*/
- async completeExternalKycProcess(provider: string, state: string) { }
+ async completeExternalKycProcess(provider: string, state: string) {}
//
// AML operations
@@ -948,8 +949,12 @@ export class TalerExchangeHttpClient {
});
switch (resp.status) {
- case HttpStatusCode.NoContent:
+ case HttpStatusCode.NoContent: {
+ this.cacheEvictor.notifySuccess(
+ TalerExchangeCacheEviction.MAKE_AML_DECISION,
+ );
return opEmptySuccess(resp);
+ }
case HttpStatusCode.Forbidden:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
@@ -1028,11 +1033,14 @@ function buildAMLDecisionSignature(
.put(hash(stringToBytes(decision.justification)))
.put(hash(stringToBytes(canonicalJson(decision.properties) + "\0")))
.put(hash(stringToBytes(canonicalJson(decision.new_rules) + "\0")))
- .put(decision.new_measure != null ? hash(stringToBytes(decision.new_measure)) : zero)
+ .put(
+ decision.new_measure != null
+ ? hash(stringToBytes(decision.new_measure))
+ : zero,
+ )
.put(bufferForUint64(decision.keep_investigating ? 1 : 0))
.build();
-
const officer_sig = encodeCrock(eddsaSign(sigBlob, key));
return {