case details and missing decision encryption
This commit is contained in:
parent
e90f1b4206
commit
7e37b34744
@ -1,6 +1,7 @@
|
||||
import {
|
||||
PaytoUri,
|
||||
Amounts,
|
||||
TalerSignaturePurpose,
|
||||
amountToBuffer,
|
||||
bufferForUint32,
|
||||
buildSigPS,
|
||||
createEddsaKeyPair,
|
||||
@ -11,8 +12,10 @@ import {
|
||||
encodeCrock,
|
||||
encryptWithDerivedKey,
|
||||
getRandomBytesF,
|
||||
hash,
|
||||
hashTruncate32,
|
||||
stringToBytes,
|
||||
stringifyPaytoUri,
|
||||
timestampRoundedToBuffer
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { AmlExchangeBackend } from "./types.js";
|
||||
|
||||
@ -60,12 +63,16 @@ export function buildQuerySignature(key: SigningKey): string {
|
||||
}
|
||||
export function buildDecisionSignature(
|
||||
key: SigningKey,
|
||||
payto: PaytoUri,
|
||||
state: AmlExchangeBackend.AmlState,
|
||||
decision: AmlExchangeBackend.AmlDecision,
|
||||
): string {
|
||||
|
||||
const sigBlob = buildSigPS(TalerSignaturePurpose.TALER_SIGNATURE_AML_DECISION)
|
||||
.put(decodeCrock(stringifyPaytoUri(payto)))
|
||||
.put(bufferForUint32(state))
|
||||
.put(hash(stringToBytes(decision.justification)))
|
||||
// .put(timestampRoundedToBuffer(decision.decision_time))
|
||||
.put(amountToBuffer(decision.new_threshold))
|
||||
.put(decodeCrock(decision.h_payto))
|
||||
// .put(hash(stringToBytes(decision.kyc_requirements)))
|
||||
.put(bufferForUint32(decision.new_state))
|
||||
.build();
|
||||
|
||||
return encodeCrock(eddsaSign(sigBlob, key));
|
||||
|
@ -29,7 +29,7 @@ export type FormState<T> = {
|
||||
? Partial<InputFieldState>
|
||||
: T[field] extends Array<infer P>
|
||||
? Partial<InputArrayFieldState<P>>
|
||||
: T[field] extends object
|
||||
: T[field] extends (object | undefined)
|
||||
? FormState<T[field]>
|
||||
: Partial<InputFieldState>;
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ interface useBackendType {
|
||||
path: string,
|
||||
options?: RequestOptions,
|
||||
) => Promise<HttpResponseOk<T>>;
|
||||
fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
|
||||
fetcher: <T>(args: [string, string]) => Promise<HttpResponseOk<T>>;
|
||||
paginatedFetcher: <T>(
|
||||
args: [string, number, number, string],
|
||||
) => Promise<HttpResponseOk<T>>;
|
||||
@ -35,8 +35,10 @@ export function usePublicBackend(): useBackendType {
|
||||
);
|
||||
|
||||
const fetcher = useCallback(
|
||||
function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint);
|
||||
function fetcherImpl<T>([endpoint, talerAmlOfficerSignature]: [string,string]): Promise<HttpResponseOk<T>> {
|
||||
return requestHandler<T>(baseUrl, endpoint, {
|
||||
talerAmlOfficerSignature
|
||||
});
|
||||
},
|
||||
[baseUrl],
|
||||
);
|
||||
|
162
packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts
Normal file
162
packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts
Normal file
@ -0,0 +1,162 @@
|
||||
|
||||
import {
|
||||
HttpResponse,
|
||||
HttpResponseOk,
|
||||
RequestError
|
||||
} from "@gnu-taler/web-util/browser";
|
||||
import { AmlExchangeBackend } from "../types.js";
|
||||
// FIX default import https://github.com/microsoft/TypeScript/issues/49189
|
||||
import _useSWR, { SWRHook, useSWRConfig } from "swr";
|
||||
import { AccountId } from "../account.js";
|
||||
import { usePublicBackend } from "./useBackend.js";
|
||||
const useSWR = _useSWR as unknown as SWRHook;
|
||||
|
||||
export function useCaseDetails(
|
||||
account: AccountId,
|
||||
paytoHash: string,
|
||||
signature: string | undefined,
|
||||
): HttpResponse<
|
||||
AmlExchangeBackend.AmlDecisionDetails,
|
||||
AmlExchangeBackend.AmlError
|
||||
> {
|
||||
const { fetcher } = usePublicBackend();
|
||||
|
||||
const { data, error } = useSWR<
|
||||
HttpResponseOk<AmlExchangeBackend.AmlDecisionDetails>,
|
||||
RequestError<AmlExchangeBackend.AmlError>
|
||||
>( [
|
||||
`aml/${account}/decision/${(paytoHash)}`,
|
||||
signature,
|
||||
],
|
||||
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.cause;
|
||||
return { loading: true };
|
||||
}
|
||||
|
||||
const example1: AmlExchangeBackend.AmlDecisionDetails = {
|
||||
aml_history: [
|
||||
{
|
||||
justification: "Lack of documentation",
|
||||
decider_pub: "ASDASDASD",
|
||||
decision_time: {
|
||||
t_s: Date.now() / 1000,
|
||||
},
|
||||
new_state: 2,
|
||||
new_threshold: "USD:0",
|
||||
},
|
||||
{
|
||||
justification: "Doing a transfer of high amount",
|
||||
decider_pub: "ASDASDASD",
|
||||
decision_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 6,
|
||||
},
|
||||
new_state: 1,
|
||||
new_threshold: "USD:2000",
|
||||
},
|
||||
{
|
||||
justification: "Account is known to the system",
|
||||
decider_pub: "ASDASDASD",
|
||||
decision_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 9,
|
||||
},
|
||||
new_state: 0,
|
||||
new_threshold: "USD:100",
|
||||
},
|
||||
],
|
||||
kyc_attributes: [
|
||||
{
|
||||
collection_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 8,
|
||||
},
|
||||
expiration_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 4,
|
||||
},
|
||||
provider_section: "asdasd",
|
||||
attributes: {
|
||||
name: "Sebastian",
|
||||
},
|
||||
},
|
||||
{
|
||||
collection_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 5,
|
||||
},
|
||||
expiration_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 2,
|
||||
},
|
||||
provider_section: "asdasd",
|
||||
attributes: {
|
||||
creditCard: "12312312312",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const exampleResponse: HttpResponse<AmlExchangeBackend.AmlDecisionDetails,AmlExchangeBackend.AmlError> = {
|
||||
ok: true,
|
||||
data: example1,
|
||||
}
|
||||
|
||||
|
||||
export function useAmlCasesAPI(): AmlCaseAPI {
|
||||
const { request } = usePublicBackend();
|
||||
const mutateAll = useMatchMutate();
|
||||
|
||||
const updateDecision = async (
|
||||
officer: AccountId,
|
||||
data: AmlExchangeBackend.AmlDecision,
|
||||
): Promise<HttpResponseOk<void>> => {
|
||||
const res = await request<void>(`aml/${officer}/decision`, {
|
||||
method: "POST",
|
||||
data,
|
||||
contentType: "json",
|
||||
});
|
||||
await mutateAll(/.*aml.*/);
|
||||
return res;
|
||||
};
|
||||
|
||||
return {
|
||||
updateDecision,
|
||||
};
|
||||
}
|
||||
|
||||
export interface AmlCaseAPI {
|
||||
updateDecision: (
|
||||
officer: AccountId,
|
||||
data: AmlExchangeBackend.AmlDecision,
|
||||
) => Promise<HttpResponseOk<void>>;
|
||||
}
|
||||
|
||||
|
||||
function useMatchMutate(): (
|
||||
re: RegExp,
|
||||
value?: unknown,
|
||||
) => Promise<any> {
|
||||
const { cache, mutate } = useSWRConfig();
|
||||
|
||||
if (!(cache instanceof Map)) {
|
||||
throw new Error(
|
||||
"matchMutate requires the cache provider to be a Map instance",
|
||||
);
|
||||
}
|
||||
|
||||
return function matchRegexMutate(re: RegExp, value?: unknown) {
|
||||
const allKeys = Array.from(cache.keys());
|
||||
const keys = allKeys.filter((key) => re.test(key));
|
||||
const mutations = keys.map((key) => {
|
||||
return mutate(key, value, true);
|
||||
});
|
||||
return Promise.all(mutations);
|
||||
};
|
||||
}
|
@ -92,3 +92,56 @@ export function useCases(
|
||||
}
|
||||
return { loading: true };
|
||||
}
|
||||
|
||||
const example1: AmlExchangeBackend.AmlRecords = {
|
||||
records: [
|
||||
{
|
||||
current_state: 0,
|
||||
h_payto: "QWEQWEQWEQWEWQE",
|
||||
rowid: 1,
|
||||
threshold: "USD 100",
|
||||
},
|
||||
{
|
||||
current_state: 1,
|
||||
h_payto: "ASDASDASD",
|
||||
rowid: 1,
|
||||
threshold: "USD 100",
|
||||
},
|
||||
{
|
||||
current_state: 2,
|
||||
h_payto: "ZXCZXCZXCXZC",
|
||||
rowid: 1,
|
||||
threshold: "USD 1000",
|
||||
},
|
||||
{
|
||||
current_state: 0,
|
||||
h_payto: "QWEQWEQWEQWEWQE",
|
||||
rowid: 1,
|
||||
threshold: "USD 100",
|
||||
},
|
||||
{
|
||||
current_state: 1,
|
||||
h_payto: "ASDASDASD",
|
||||
rowid: 1,
|
||||
threshold: "USD 100",
|
||||
},
|
||||
{
|
||||
current_state: 2,
|
||||
h_payto: "ZXCZXCZXCXZC",
|
||||
rowid: 1,
|
||||
threshold: "USD 1000",
|
||||
},
|
||||
].map((e, idx) => {
|
||||
e.rowid = idx;
|
||||
e.threshold = `${e.threshold}${idx}`;
|
||||
return e;
|
||||
}),
|
||||
};
|
||||
|
||||
export const exampleResponse: HttpResponsePaginated<AmlExchangeBackend.AmlRecords,AmlExchangeBackend.AmlError> = {
|
||||
ok: true,
|
||||
data: example1,
|
||||
loadMore: () => {},
|
||||
loadMorePrev: () => {},
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ const cases: PageEntry = {
|
||||
url: "#/cases",
|
||||
view: Cases,
|
||||
};
|
||||
const account: PageEntry<{ account?: string }> = {
|
||||
const account: PageEntry<{ account: string }> = {
|
||||
url: pageDefinition("#/account/:account"),
|
||||
view: CaseDetails,
|
||||
};
|
||||
|
@ -13,64 +13,13 @@ import { FlexibleForm } from "../forms/index.js";
|
||||
import { UIFormField } from "../handlers/forms.js";
|
||||
import { Pages } from "../pages.js";
|
||||
import { AmlExchangeBackend } from "../types.js";
|
||||
import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
|
||||
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||
import { useOfficer } from "../hooks/useOfficer.js";
|
||||
import { buildQuerySignature } from "../account.js";
|
||||
import { useCaseDetails } from "../hooks/useCaseDetails.js";
|
||||
import { handleNotOkResult } from "../utils/errors.js";
|
||||
|
||||
const response: AmlExchangeBackend.AmlDecisionDetails = {
|
||||
aml_history: [
|
||||
{
|
||||
justification: "Lack of documentation",
|
||||
decider_pub: "ASDASDASD",
|
||||
decision_time: {
|
||||
t_s: Date.now() / 1000,
|
||||
},
|
||||
new_state: 2,
|
||||
new_threshold: "USD:0",
|
||||
},
|
||||
{
|
||||
justification: "Doing a transfer of high amount",
|
||||
decider_pub: "ASDASDASD",
|
||||
decision_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 6,
|
||||
},
|
||||
new_state: 1,
|
||||
new_threshold: "USD:2000",
|
||||
},
|
||||
{
|
||||
justification: "Account is known to the system",
|
||||
decider_pub: "ASDASDASD",
|
||||
decision_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 9,
|
||||
},
|
||||
new_state: 0,
|
||||
new_threshold: "USD:100",
|
||||
},
|
||||
],
|
||||
kyc_attributes: [
|
||||
{
|
||||
collection_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 8,
|
||||
},
|
||||
expiration_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 4,
|
||||
},
|
||||
provider_section: "asdasd",
|
||||
attributes: {
|
||||
name: "Sebastian",
|
||||
},
|
||||
},
|
||||
{
|
||||
collection_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 5,
|
||||
},
|
||||
expiration_time: {
|
||||
t_s: Date.now() / 1000 - 60 * 60 * 24 * 30 * 2,
|
||||
},
|
||||
provider_section: "asdasd",
|
||||
attributes: {
|
||||
creditCard: "12312312312",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
type AmlEvent = AmlFormEvent | KycCollectionEvent | KycExpirationEvent;
|
||||
type AmlFormEvent = {
|
||||
type: "aml-form";
|
||||
@ -127,7 +76,7 @@ function getEventsFromAmlHistory(
|
||||
});
|
||||
prev.push({
|
||||
type: "kyc-expiration",
|
||||
title: "expired" as TranslatedString,
|
||||
title: "expiration" as TranslatedString,
|
||||
when: AbsoluteTime.fromProtocolTimestamp(k.expiration_time),
|
||||
fields: !k.attributes ? [] : Object.keys(k.attributes),
|
||||
});
|
||||
@ -136,19 +85,30 @@ function getEventsFromAmlHistory(
|
||||
return ae.concat(ke).sort(selectSooner);
|
||||
}
|
||||
|
||||
export function CaseDetails({ account }: { account?: string }) {
|
||||
const events = getEventsFromAmlHistory(
|
||||
response.aml_history,
|
||||
response.kyc_attributes,
|
||||
);
|
||||
console.log("DETAILS", events, events[events.length - 1 - 2]);
|
||||
const [selected, setSelected] = useState<AmlEvent>(
|
||||
events[events.length - 1 - 2],
|
||||
);
|
||||
export function CaseDetails({ account: paytoHash }: { account: string }) {
|
||||
const [selected, setSelected] = useState<AmlEvent | undefined>(undefined);
|
||||
|
||||
const officer = useOfficer();
|
||||
const { i18n } = useTranslationContext();
|
||||
if (officer.state !== "ready") {
|
||||
return <HandleAccountNotReady officer={officer} />;
|
||||
}
|
||||
const signature =
|
||||
officer.state === "ready"
|
||||
? buildQuerySignature(officer.account.signingKey)
|
||||
: undefined;
|
||||
const details = useCaseDetails(officer.account.accountId, paytoHash, signature)
|
||||
if (!details.ok && !details.loading) {
|
||||
return handleNotOkResult(i18n)(details);
|
||||
}
|
||||
const aml_history = details.loading ? [] : details.data.aml_history
|
||||
const kyc_attributes = details.loading ? [] : details.data.kyc_attributes
|
||||
const events = getEventsFromAmlHistory(aml_history,kyc_attributes);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<a
|
||||
href={Pages.newFormEntry.url({ account })}
|
||||
href={Pages.newFormEntry.url({ account: paytoHash })}
|
||||
class="m-4 block 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"
|
||||
>
|
||||
New AML form
|
||||
@ -271,13 +231,24 @@ function ShowConsolidated({
|
||||
history: AmlEvent[];
|
||||
until: AmlEvent;
|
||||
}): VNode {
|
||||
console.log("UNTIL", until);
|
||||
const cons = getConsolidated(history, until.when);
|
||||
|
||||
const form: FlexibleForm<Consolidated> = {
|
||||
versionId: "1",
|
||||
behavior: (form) => {
|
||||
return {};
|
||||
return {
|
||||
aml: {
|
||||
threshold: {
|
||||
hidden: !form.aml
|
||||
},
|
||||
since: {
|
||||
hidden: !form.aml
|
||||
},
|
||||
state: {
|
||||
hidden: !form.aml
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
design: [
|
||||
{
|
||||
@ -356,8 +327,8 @@ function ShowConsolidated({
|
||||
|
||||
interface Consolidated {
|
||||
aml: {
|
||||
state?: AmlExchangeBackend.AmlState;
|
||||
threshold?: AmountJson;
|
||||
state: AmlExchangeBackend.AmlState;
|
||||
threshold: AmountJson;
|
||||
since: AbsoluteTime;
|
||||
};
|
||||
kyc: {
|
||||
@ -375,7 +346,13 @@ function getConsolidated(
|
||||
): Consolidated {
|
||||
const initial: Consolidated = {
|
||||
aml: {
|
||||
since: AbsoluteTime.never(),
|
||||
state: AmlExchangeBackend.AmlState.normal,
|
||||
threshold: {
|
||||
currency: "ARS",
|
||||
value: 1000,
|
||||
fraction: 0,
|
||||
},
|
||||
since: AbsoluteTime.never()
|
||||
},
|
||||
kyc: {},
|
||||
};
|
||||
@ -391,9 +368,11 @@ function getConsolidated(
|
||||
break;
|
||||
}
|
||||
case "aml-form": {
|
||||
prev.aml.threshold = cur.threshold;
|
||||
prev.aml.state = cur.state;
|
||||
prev.aml.since = cur.when;
|
||||
prev.aml = {
|
||||
since: cur.when,
|
||||
state: cur.state,
|
||||
threshold: cur.threshold
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "kyc-collection": {
|
||||
|
@ -12,59 +12,6 @@ import { buildQuerySignature } from "../account.js";
|
||||
import { handleNotOkResult } from "../utils/errors.js";
|
||||
import { useTranslationContext } from "@gnu-taler/web-util/browser";
|
||||
|
||||
const response: AmlExchangeBackend.AmlRecords = {
|
||||
records: [
|
||||
{
|
||||
current_state: 0,
|
||||
h_payto: "QWEQWEQWEQWEWQE",
|
||||
rowid: 1,
|
||||
threshold: "USD 100",
|
||||
},
|
||||
{
|
||||
current_state: 1,
|
||||
h_payto: "ASDASDASD",
|
||||
rowid: 1,
|
||||
threshold: "USD 100",
|
||||
},
|
||||
{
|
||||
current_state: 2,
|
||||
h_payto: "ZXCZXCZXCXZC",
|
||||
rowid: 1,
|
||||
threshold: "USD 1000",
|
||||
},
|
||||
{
|
||||
current_state: 0,
|
||||
h_payto: "QWEQWEQWEQWEWQE",
|
||||
rowid: 1,
|
||||
threshold: "USD 100",
|
||||
},
|
||||
{
|
||||
current_state: 1,
|
||||
h_payto: "ASDASDASD",
|
||||
rowid: 1,
|
||||
threshold: "USD 100",
|
||||
},
|
||||
{
|
||||
current_state: 2,
|
||||
h_payto: "ZXCZXCZXCXZC",
|
||||
rowid: 1,
|
||||
threshold: "USD 1000",
|
||||
},
|
||||
].map((e, idx) => {
|
||||
e.rowid = idx;
|
||||
e.threshold = `${e.threshold}${idx}`;
|
||||
return e;
|
||||
}),
|
||||
};
|
||||
|
||||
function doFilter(
|
||||
list: typeof response.records,
|
||||
filter: AmlExchangeBackend.AmlState | undefined,
|
||||
): typeof response.records {
|
||||
if (filter === undefined) return list;
|
||||
return list.filter((r) => r.current_state === filter);
|
||||
}
|
||||
|
||||
export function Cases() {
|
||||
const officer = useOfficer();
|
||||
const { i18n } = useTranslationContext();
|
||||
|
@ -2,8 +2,12 @@ import { VNode, h } from "preact";
|
||||
import { allForms } from "./AntiMoneyLaunderingForm.js";
|
||||
import { Pages } from "../pages.js";
|
||||
import { NiceForm } from "../NiceForm.js";
|
||||
import { AbsoluteTime, Amounts } from "@gnu-taler/taler-util";
|
||||
import { AbsoluteTime, Amounts, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
|
||||
import { AmlExchangeBackend } from "../types.js";
|
||||
import { useAmlCasesAPI } from "../hooks/useCaseDetails.js";
|
||||
import { useOfficer } from "../hooks/useOfficer.js";
|
||||
import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
|
||||
import { buildDecisionSignature, buildQuerySignature } from "../account.js";
|
||||
|
||||
export function NewFormEntry({
|
||||
account,
|
||||
@ -12,30 +16,58 @@ export function NewFormEntry({
|
||||
account?: string;
|
||||
type?: string;
|
||||
}): VNode {
|
||||
const officer = useOfficer();
|
||||
|
||||
if (!account) {
|
||||
return <div>no account</div>;
|
||||
}
|
||||
if (!type) {
|
||||
return <SelectForm account={account} />;
|
||||
}
|
||||
if (officer.state !== "ready") {
|
||||
return <HandleAccountNotReady officer={officer} />;
|
||||
}
|
||||
|
||||
const selectedForm = Number.parseInt(type ?? "0", 10);
|
||||
if (Number.isNaN(selectedForm)) {
|
||||
return <div>WHAT! {type}</div>;
|
||||
}
|
||||
const showingFrom = allForms[selectedForm].impl;
|
||||
const formName = allForms[selectedForm].name
|
||||
const initial = {
|
||||
fullName: "loggedIn_user_fullname",
|
||||
when: AbsoluteTime.now(),
|
||||
state: AmlExchangeBackend.AmlState.pending,
|
||||
threshold: Amounts.parseOrThrow("USD:10"),
|
||||
threshold: Amounts.parseOrThrow("ARS:1000"),
|
||||
};
|
||||
const api = useAmlCasesAPI()
|
||||
|
||||
return (
|
||||
<NiceForm
|
||||
initial={initial}
|
||||
form={showingFrom(initial)}
|
||||
onSubmit={(v) => {
|
||||
alert(JSON.stringify(v));
|
||||
onSubmit={(formValue) => {
|
||||
if (formValue.state === undefined || formValue.threshold === undefined) return;
|
||||
|
||||
const justification = {
|
||||
index: selectedForm,
|
||||
name: formName,
|
||||
value: formValue
|
||||
}
|
||||
const decision: AmlExchangeBackend.AmlDecision = {
|
||||
justification: JSON.stringify(justification),
|
||||
decision_time: TalerProtocolTimestamp.now(),
|
||||
h_payto: account,
|
||||
new_state: formValue.state,
|
||||
new_threshold: Amounts.stringify(formValue.threshold),
|
||||
officer_sig: "",
|
||||
kyc_requirements: undefined
|
||||
}
|
||||
const signature = buildDecisionSignature(officer.account.signingKey, decision);
|
||||
decision.officer_sig = signature
|
||||
api.updateDecision(officer.account.accountId, decision);
|
||||
|
||||
// alert(JSON.stringify(formValue));
|
||||
}}
|
||||
>
|
||||
<div class="mt-6 flex items-center justify-end gap-x-6">
|
||||
|
@ -59,6 +59,9 @@ export namespace AmlExchangeBackend {
|
||||
type PaytoHash = string;
|
||||
type Integer = number;
|
||||
type Amount = string;
|
||||
// EdDSA signatures are transmitted as 64-bytes base32
|
||||
// binary-encoded objects with just the R and S values (base32_ binary-only).
|
||||
type EddsaSignature = string;
|
||||
|
||||
export interface AmlRecords {
|
||||
// Array of AML records matching the query.
|
||||
@ -85,4 +88,37 @@ export namespace AmlExchangeBackend {
|
||||
pending = 1,
|
||||
frozen = 2,
|
||||
}
|
||||
|
||||
|
||||
export interface AmlDecision {
|
||||
|
||||
// Human-readable justification for the decision.
|
||||
justification: string;
|
||||
|
||||
// At what monthly transaction volume should the
|
||||
// decision be automatically reviewed?
|
||||
new_threshold: Amount;
|
||||
|
||||
// Which payto-address is the decision about?
|
||||
// Identifies a GNU Taler wallet or an affected bank account.
|
||||
h_payto: PaytoHash;
|
||||
|
||||
// What is the new AML state (e.g. frozen, unfrozen, etc.)
|
||||
// Numerical values are defined in AmlDecisionState.
|
||||
new_state: Integer;
|
||||
|
||||
// Signature by the AML officer over a
|
||||
// TALER_MasterAmlOfficerStatusPS.
|
||||
// Must have purpose TALER_SIGNATURE_MASTER_AML_KEY.
|
||||
officer_sig: EddsaSignature;
|
||||
|
||||
// When was the decision made?
|
||||
decision_time: Timestamp;
|
||||
|
||||
// Optional argument to impose new KYC requirements
|
||||
// that the customer has to satisfy to unblock transactions.
|
||||
kyc_requirements?: string[];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user