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:
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,