taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit a458ec3b5558908d3176cce4178b6c6bf7926938
parent f530efac981a2c62d0607cbedc54cf1624a943ff
Author: Sebastian <sebasjm@gmail.com>
Date:   Tue, 22 Apr 2025 16:59:06 -0300

add warning based on account type, also hide buttons if not needed and hint in the title the type of account

Diffstat:
Mpackages/aml-backoffice-ui/src/pages/RulesInfo.tsx | 44--------------------------------------------
Mpackages/aml-backoffice-ui/src/pages/decision/AmlDecisionRequestWizard.tsx | 108++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mpackages/aml-backoffice-ui/src/pages/decision/Rules.tsx | 605+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
3 files changed, 486 insertions(+), 271 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/pages/RulesInfo.tsx b/packages/aml-backoffice-ui/src/pages/RulesInfo.tsx @@ -58,34 +58,11 @@ export function RulesInfo({ ); } - const OPERATION_TYPE_MISSING = { - [LimitOperationType.balance]: true, - [LimitOperationType.transaction]: true, - [LimitOperationType.withdraw]: true, - [LimitOperationType.deposit]: true, - [LimitOperationType.aggregate]: true, - [LimitOperationType.close]: true, - [LimitOperationType.refund]: true, - [LimitOperationType.merge]: true, - }; - const theRules = rules.map((r, idx): KycRuleWithIdx => ({ ...r, idx })); const sorted = theRules.sort((a, b) => { - // to prevent iterate again we are using this sort function - // to save present operation type - OPERATION_TYPE_MISSING[a.operation_type] = false; - OPERATION_TYPE_MISSING[b.operation_type] = false; return sortKycRules(a, b); }); - if (rules.length === 1) { - // if there is only one element, sort function is not called - OPERATION_TYPE_MISSING[rules[0].operation_type] = false; - } - - const missing = Object.entries(OPERATION_TYPE_MISSING) - .filter(([key, value]) => !!value) - .map(([key]) => key) as LimitOperationType[]; const hasActions = !!onEdit || !!onRemove; @@ -270,27 +247,6 @@ export function RulesInfo({ })} </tbody> </table> - {!missing.length ? undefined : missing.length === 1 ? ( - <Attention - type="warning" - title={i18n.str`There is an operation without limit`} - > - <i18n.Translate> - This mean that this operation can be used without limit:{" "} - {missing.join(", ")} - </i18n.Translate> - </Attention> - ) : ( - <Attention - type="warning" - title={i18n.str`There are operations without limit`} - > - <i18n.Translate> - This mean that these operations can be used without limit:{" "} - {missing.join(", ")} - </i18n.Translate> - </Attention> - )} </div> </Fragment> ); diff --git a/packages/aml-backoffice-ui/src/pages/decision/AmlDecisionRequestWizard.tsx b/packages/aml-backoffice-ui/src/pages/decision/AmlDecisionRequestWizard.tsx @@ -16,13 +16,13 @@ import { AbsoluteTime, assertUnreachable, + parsePaytoUri, PaytoString, - TranslatedString + PaytoUri, + TalerError, + TranslatedString, } from "@gnu-taler/taler-util"; -import { - CopyButton, - useTranslationContext -} from "@gnu-taler/web-util/browser"; +import { CopyButton, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { DecisionRequest, @@ -35,6 +35,7 @@ import { Measures } from "./Measures.js"; import { Properties } from "./Properties.js"; import { Rules } from "./Rules.js"; import { Summary } from "./Summary.js"; +import { useAccountActiveDecision } from "../../hooks/decisions.js"; export type WizardSteps = | "attributes" // submit more information @@ -75,7 +76,9 @@ export function isRulesCompleted(request: DecisionRequest): boolean { return request.rules !== undefined && request.deadline !== undefined; } export function isAttributesCompleted(request: DecisionRequest): boolean { - return request.attributes === undefined || request.attributes.errors === undefined; + return ( + request.attributes === undefined || request.attributes.errors === undefined + ); } export function isPropertiesCompleted(request: DecisionRequest): boolean { return request.properties !== undefined; @@ -89,8 +92,14 @@ export function isMeasuresCompleted(request: DecisionRequest): boolean { export function isJustificationCompleted(request: DecisionRequest): boolean { return request.keep_investigating !== undefined && !!request.justification; } -export function isJustificationCompletedForNewACcount(request: DecisionRequest): boolean { - return request.keep_investigating !== undefined && !!request.justification && !!request.accountName; +export function isJustificationCompletedForNewACcount( + request: DecisionRequest, +): boolean { + return ( + request.keep_investigating !== undefined && + !!request.justification && + !!request.accountName + ); } export function AmlDecisionRequestWizard({ @@ -101,7 +110,7 @@ export function AmlDecisionRequestWizard({ onMove, }: { account: string; - newPayto?: PaytoString, + newPayto?: PaytoString; formId: string | undefined; step?: WizardSteps; onMove: (n: WizardSteps | undefined) => void; @@ -121,25 +130,28 @@ export function AmlDecisionRequestWizard({ case "justification": return <Justification account={account} newPayto={newPayto} />; case "attributes": - return <Attributes formId={formId}/>; + return <Attributes formId={formId} />; case "summary": - return <Summary account={account} onMove={onMove} newPayto={newPayto}/>; + return ( + <Summary account={account} onMove={onMove} newPayto={newPayto} /> + ); } assertUnreachable(stepOrDefault); })(); return ( <div class="min-w-60"> - <header class="flex items-center justify-between border-b border-white/5 px-4 py-4 sm:px-6 sm:py-6 lg:px-8"> - <h1 class="text-base font-semibold leading-7 text-black"> - <i18n.Translate>Decision for account: </i18n.Translate> - </h1> + <Header account={account} newPayto={newPayto} /> <div>{account}</div> <CopyButton class="" getContent={() => account} /> </header> - <WizardSteps step={stepOrDefault} onMove={onMove} newAccount={!!newPayto} /> + <WizardSteps + step={stepOrDefault} + onMove={onMove} + newAccount={!!newPayto} + /> <button disabled={!STEPS_ORDER_MAP[stepOrDefault].prev} onClick={() => { @@ -203,7 +215,9 @@ function WizardSteps({ justification: { label: i18n.str`Justification`, description: i18n.str`Describe the decision.`, - isCompleted: newAccount ? isJustificationCompletedForNewACcount : isJustificationCompleted, + isCompleted: newAccount + ? isJustificationCompletedForNewACcount + : isJustificationCompleted, }, properties: { label: i18n.str`Properties`, @@ -333,3 +347,63 @@ function WizardSteps({ </div> ); } + +function Header({ + newPayto, + account, +}: { + account: string; + newPayto: PaytoString | undefined; +}): VNode { + const { i18n } = useTranslationContext(); + const isNewAccount = !!newPayto; + + let newPaytoParsed: PaytoUri | undefined; + const isNewAccountAWallet = + newPayto === undefined + ? undefined + : (newPaytoParsed = parsePaytoUri(newPayto)) === undefined + ? undefined + : newPaytoParsed.isKnown && newPaytoParsed.targetType === "taler"; + + const activeDecision = useAccountActiveDecision( + isNewAccount ? undefined : account, + ); + + const info = + !activeDecision || + activeDecision instanceof TalerError || + activeDecision.type === "fail" + ? undefined + : activeDecision.body; + + if (!info && !isNewAccount) { + <h1 class="text-base font-semibold leading-7 text-black"> + <i18n.Translate>loading... </i18n.Translate> + </h1>; + } + // info may be undefined if this is a new account + // for which we use the payto:// parameter + const isWallet = info?.is_wallet ?? isNewAccountAWallet; + + if (isWallet === undefined) { + return ( + <h1 class="text-base font-semibold leading-7 text-black"> + <i18n.Translate>Decision for account: </i18n.Translate> + </h1> + ); + } + if (isWallet) { + return ( + <h1 class="text-base font-semibold leading-7 text-black"> + <i18n.Translate>Decision for wallet: </i18n.Translate> + </h1> + ); + } else { + return ( + <h1 class="text-base font-semibold leading-7 text-black"> + <i18n.Translate>Decision for bank account: </i18n.Translate> + </h1> + ); + } +} diff --git a/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx b/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx @@ -2,6 +2,7 @@ import { AbsoluteTime, AmountJson, Amounts, + assertUnreachable, AvailableMeasureSummary, Duration, ExchangeVersionResponse, @@ -9,11 +10,14 @@ import { LegitimizationRuleSet, LimitOperationType, MeasureInformation, + parsePaytoUri, PaytoString, + PaytoUri, TalerError, TranslatedString, } from "@gnu-taler/taler-util"; import { + Attention, FormDesign, FormUI, InternationalizationAPI, @@ -38,6 +42,78 @@ export const DEFAULT_LIMITS_WHEN_NEW_ACCOUNT: LegitimizationRuleSet = { rules: [], }; +const SHARED_RULES = [ + LimitOperationType.refund, + LimitOperationType.transaction, +]; +const ONLY_WALLET_RULES = [ + LimitOperationType.balance, + LimitOperationType.merge, +]; +const ONLY_BANK_RULES = [ + LimitOperationType.deposit, + LimitOperationType.aggregate, + LimitOperationType.withdraw, + LimitOperationType.close, +]; +const WALLET_RULES = [...ONLY_WALLET_RULES, ...SHARED_RULES]; +const BANK_RULES = [...ONLY_BANK_RULES, ...SHARED_RULES]; + +export type RuleInconsistency = + | "missing-wallet-rules" + | "missing-bank-rules" + | "shold-not-have-wallet-rules" + | "shold-not-have-bank-rules"; + +export function findRuleInconsistency( + isWallet: boolean, + rules: KycRule[], +): undefined | RuleInconsistency { + const OPERATION_TYPE_PRESENT: Record<LimitOperationType, boolean> = { + [LimitOperationType.balance]: false, + [LimitOperationType.transaction]: false, + [LimitOperationType.withdraw]: false, + [LimitOperationType.deposit]: false, + [LimitOperationType.aggregate]: false, + [LimitOperationType.close]: false, + [LimitOperationType.refund]: false, + [LimitOperationType.merge]: false, + }; + + rules.forEach((r) => { + OPERATION_TYPE_PRESENT[r.operation_type] = true; + }); + + if (isWallet) { + const hasBankRule = ONLY_BANK_RULES.some( + (ot) => OPERATION_TYPE_PRESENT[ot], + ); + if (hasBankRule) { + return "shold-not-have-bank-rules"; + } + const hasAllWalletRules = WALLET_RULES.every( + (ot) => OPERATION_TYPE_PRESENT[ot], + ); + if (!hasAllWalletRules) { + return "missing-wallet-rules"; + } + } else { + const hasWalletrule = ONLY_WALLET_RULES.some( + (ot) => OPERATION_TYPE_PRESENT[ot], + ); + if (hasWalletrule) { + return "shold-not-have-wallet-rules"; + } + const hasAllBankRules = BANK_RULES.every( + (ot) => OPERATION_TYPE_PRESENT[ot], + ); + if (!hasAllBankRules) { + return "missing-bank-rules"; + } + } + return undefined; +} + /** * Defined new limits for the account * @param param0 @@ -55,6 +131,14 @@ export function Rules({ const isNewAccount = !!newPayto; + let newPaytoParsed: PaytoUri | undefined; + const isNewAccountAWallet = + newPayto === undefined + ? undefined + : (newPaytoParsed = parsePaytoUri(newPayto)) === undefined + ? undefined + : newPaytoParsed.isKnown && newPaytoParsed.targetType === "taler"; + // const [request, updateRequestField, updateRequest] = // useCurrentDecisionRequest(); const measures = useServerMeasures(); @@ -78,11 +162,15 @@ export function Rules({ return <Loading />; } + // info may be undefined if this is a new account + // for which we use the payto:// parameter + const isWallet = info?.is_wallet ?? isNewAccountAWallet; return ( <div> <UpdateRulesForm rootMeasures={rootMeasures} config={config.config} + isWallet={isWallet ?? false} limits={info?.limits ?? DEFAULT_LIMITS_WHEN_NEW_ACCOUNT} /> @@ -118,17 +206,20 @@ function AddNewRuleForm({ onClose, config, measureList, + isWallet, }: { onAdd: (nr: RuleFormType) => void; config: ExchangeVersionResponse; measureList: MeasureListWithId; onClose: () => void; + isWallet: boolean; }): VNode { const { i18n } = useTranslationContext(); const ruleFormDesign = ruleFormDesignTemplate( i18n, config.currency, measureList, + isWallet, ); const ruleForm = useForm<RuleFormType>(ruleFormDesign, {}); @@ -167,9 +258,11 @@ function UpdateRulesForm({ config, limits, rootMeasures, + isWallet, }: { config: ExchangeVersionResponse; limits: LegitimizationRuleSet; + isWallet: boolean | undefined; rootMeasures: AvailableMeasureSummary["roots"] | undefined; }): VNode { const { i18n } = useTranslationContext(); @@ -226,12 +319,80 @@ function UpdateRulesForm({ updateRequestField("rules", result); } + const ruleInconsistency = + isWallet === undefined + ? undefined + : findRuleInconsistency(isWallet, currentRules); + return ( <div> + {!ruleInconsistency ? undefined : ( + <Attention + title={i18n.str`Check rules`} + type={(function (state: RuleInconsistency) { + switch (state) { + case "shold-not-have-bank-rules": + case "shold-not-have-wallet-rules": { + return "warning"; + } + case "missing-wallet-rules": + case "missing-bank-rules": + return "info"; + default: { + assertUnreachable(state); + } + } + })(ruleInconsistency)} + > + {(function (state: RuleInconsistency) { + switch (state) { + case "shold-not-have-bank-rules": { + return ( + <i18n.Translate> + The account is a wallet and there are bank account + operation's limits which may trigger actions that are not + required: {ONLY_BANK_RULES.join(",")} + </i18n.Translate> + ); + } + case "shold-not-have-wallet-rules": { + return ( + <i18n.Translate> + The account isn't a wallet and there are wallet operation's + limits which may trigger actions that are not required:{" "} + {ONLY_WALLET_RULES.join(",")} + </i18n.Translate> + ); + } + case "missing-wallet-rules": { + return ( + <i18n.Translate> + The account is a wallet and there are wallet operation's + limits which are not limited: {WALLET_RULES.join(",")} + </i18n.Translate> + ); + } + case "missing-bank-rules": { + return ( + <i18n.Translate> + The account is bank account and there are bank operation's + limits which are not limited: {BANK_RULES.join(",")} + </i18n.Translate> + ); + } + default: { + assertUnreachable(state); + } + } + })(ruleInconsistency)} + </Attention> + )} + {!showAddRuleForm ? undefined : ( <AddNewRuleForm measureList={measureList} config={config} + isWallet={isWallet ?? false} onAdd={addNewRule} onClose={() => { setShowAddRuleForm(false); @@ -265,7 +426,7 @@ function UpdateRulesForm({ </button> <button onClick={() => { - updateRequestField("rules", FREEZE_PLAN(config.currency)); + updateRequestField("rules", FREEZE_PLAN(config.currency, isWallet)); }} 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 disabled:bg-gray-600" > @@ -273,7 +434,7 @@ function UpdateRulesForm({ </button> <button onClick={() => { - updateRequestField("rules", BASIC_PLAN(config.currency)); + updateRequestField("rules", BASIC_PLAN(config.currency, isWallet)); }} 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 disabled:bg-gray-600" > @@ -281,7 +442,7 @@ function UpdateRulesForm({ </button> <button onClick={() => { - updateRequestField("rules", PREMIUM_PLAN(config.currency)); + updateRequestField("rules", PREMIUM_PLAN(config.currency, isWallet)); }} 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 disabled:bg-gray-600" > @@ -358,6 +519,7 @@ const ruleFormDesignTemplate = ( i18n: InternationalizationAPI, currency: string, mi: (MeasureInformation & { id: string })[], + isWallet: boolean, ): FormDesign<KycRule> => ({ type: "single-column", fields: [ @@ -366,7 +528,7 @@ const ruleFormDesignTemplate = ( type: "choiceHorizontal", label: i18n.str`Operation type`, required: true, - choices: Object.values(LimitOperationType).map((op) => { + choices: (isWallet ? WALLET_RULES : BANK_RULES).map((op) => { return { value: op, label: labelForOperationType(op, i18n), @@ -477,214 +639,237 @@ const expirationFormDesignTemplate = ( ], }); -const BASIC_PLAN: (currency: string) => KycRule[] = (currency) => [ - { - display_priority: 1, - measures: ["VERBOTEN"], - operation_type: LimitOperationType.balance, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 10000, - }), - timeframe: Duration.toTalerProtocolDuration(Duration.getForever()), - }, - { - display_priority: 1, - measures: ["VERBOTEN"], - operation_type: LimitOperationType.transaction, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 1000, - }), - timeframe: Duration.toTalerProtocolDuration(Duration.getForever()), - }, - { - display_priority: 1, - measures: ["VERBOTEN"], - operation_type: LimitOperationType.withdraw, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ months: 1 }), - ), - }, - { - display_priority: 1, - measures: ["VERBOTEN"], - operation_type: LimitOperationType.merge, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ months: 1 }), - ), - }, - { - display_priority: 1, - measures: ["preserve-investigate"], - operation_type: LimitOperationType.deposit, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 5 * 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ months: 1 }), - ), - }, - { - display_priority: 1, - measures: ["preserve-investigate"], - operation_type: LimitOperationType.deposit, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 50 * 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ years: 1 }), - ), - }, - { - display_priority: 1, - measures: ["preserve-investigate"], - operation_type: LimitOperationType.aggregate, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 5 * 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ months: 1 }), - ), - }, - { - display_priority: 1, - measures: ["preserve-investigate"], - operation_type: LimitOperationType.aggregate, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 50 * 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ years: 1 }), - ), - }, -]; +const BASIC_PLAN: TemplateRulesFunction = (currency, isWallet) => + [ + { + display_priority: 1, + measures: ["VERBOTEN"], + operation_type: LimitOperationType.balance, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 10000, + }), + timeframe: Duration.toTalerProtocolDuration(Duration.getForever()), + }, + { + display_priority: 1, + measures: ["VERBOTEN"], + operation_type: LimitOperationType.transaction, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 1000, + }), + timeframe: Duration.toTalerProtocolDuration(Duration.getForever()), + }, + { + display_priority: 1, + measures: ["VERBOTEN"], + operation_type: LimitOperationType.withdraw, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ months: 1 }), + ), + }, + { + display_priority: 1, + measures: ["VERBOTEN"], + operation_type: LimitOperationType.merge, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ months: 1 }), + ), + }, + { + display_priority: 1, + measures: ["preserve-investigate"], + operation_type: LimitOperationType.deposit, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 5 * 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ months: 1 }), + ), + }, + { + display_priority: 1, + measures: ["preserve-investigate"], + operation_type: LimitOperationType.deposit, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 50 * 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ years: 1 }), + ), + }, + { + display_priority: 1, + measures: ["preserve-investigate"], + operation_type: LimitOperationType.aggregate, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 5 * 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ months: 1 }), + ), + }, + { + display_priority: 1, + measures: ["preserve-investigate"], + operation_type: LimitOperationType.aggregate, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 50 * 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ years: 1 }), + ), + }, + ].filter((r) => { + return isWallet + ? WALLET_RULES.includes(r.operation_type) + : BANK_RULES.includes(r.operation_type); + }); -const PREMIUM_PLAN: (currency: string) => KycRule[] = (currency) => [ - { - display_priority: 1, - measures: ["VERBOTEN"], - operation_type: LimitOperationType.balance, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 10 * 1000, - }), - timeframe: Duration.toTalerProtocolDuration(Duration.getForever()), - }, - { - display_priority: 1, - measures: ["VERBOTEN"], - operation_type: LimitOperationType.transaction, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 1000, - }), - timeframe: Duration.toTalerProtocolDuration(Duration.getForever()), - }, - { - display_priority: 1, - measures: ["VERBOTEN"], - operation_type: LimitOperationType.withdraw, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ months: 1 }), - ), - }, - { - display_priority: 1, - measures: ["VERBOTEN"], - operation_type: LimitOperationType.merge, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ months: 1 }), - ), - }, - { - display_priority: 1, - measures: ["preserve-investigate"], - operation_type: LimitOperationType.deposit, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 15 * 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ months: 1 }), - ), - }, - { - display_priority: 1, - measures: ["preserve-investigate"], - operation_type: LimitOperationType.deposit, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 150 * 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ years: 1 }), - ), - }, - { - display_priority: 1, - measures: ["preserve-investigate"], - operation_type: LimitOperationType.aggregate, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 15 * 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ months: 1 }), - ), - }, - { - display_priority: 1, - measures: ["preserve-investigate"], - operation_type: LimitOperationType.aggregate, - threshold: Amounts.stringify({ - currency, - fraction: 0, - value: 150 * 1000, - }), - timeframe: Duration.toTalerProtocolDuration( - Duration.fromSpec({ years: 1 }), - ), - }, -]; +type TemplateRulesFunction = ( + currency: string, + isWallet: boolean | undefined, +) => KycRule[]; + +const PREMIUM_PLAN: TemplateRulesFunction = (currency, isWallet) => + [ + { + display_priority: 1, + measures: ["VERBOTEN"], + operation_type: LimitOperationType.balance, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 10 * 1000, + }), + timeframe: Duration.toTalerProtocolDuration(Duration.getForever()), + }, + { + display_priority: 1, + measures: ["VERBOTEN"], + operation_type: LimitOperationType.transaction, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 1000, + }), + timeframe: Duration.toTalerProtocolDuration(Duration.getForever()), + }, + { + display_priority: 1, + measures: ["VERBOTEN"], + operation_type: LimitOperationType.withdraw, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ months: 1 }), + ), + }, + { + display_priority: 1, + measures: ["VERBOTEN"], + operation_type: LimitOperationType.merge, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ months: 1 }), + ), + }, + { + display_priority: 1, + measures: ["preserve-investigate"], + operation_type: LimitOperationType.deposit, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 15 * 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ months: 1 }), + ), + }, + { + display_priority: 1, + measures: ["preserve-investigate"], + operation_type: LimitOperationType.deposit, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 150 * 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ years: 1 }), + ), + }, + { + display_priority: 1, + measures: ["preserve-investigate"], + operation_type: LimitOperationType.aggregate, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 15 * 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ months: 1 }), + ), + }, + { + display_priority: 1, + measures: ["preserve-investigate"], + operation_type: LimitOperationType.aggregate, + threshold: Amounts.stringify({ + currency, + fraction: 0, + value: 150 * 1000, + }), + timeframe: Duration.toTalerProtocolDuration( + Duration.fromSpec({ years: 1 }), + ), + }, + ].filter((r) => { + return isWallet + ? WALLET_RULES.includes(r.operation_type) + : BANK_RULES.includes(r.operation_type); + }); -const FREEZE_PLAN: (currency: string) => KycRule[] = (currency) => - Object.values(LimitOperationType).map((operation_type) => ({ +const FREEZE_PLAN: TemplateRulesFunction = ( + currency, + isWallet: boolean | undefined, +) => + (isWallet === undefined + ? Object.values(LimitOperationType) + : isWallet + ? WALLET_RULES + : BANK_RULES + ).map((operation_type) => ({ display_priority: 1, measures: ["VERBOTEN"], operation_type,