commit 9c0ebc8928de096f0a7ed05c75414050d1688688
parent f6b6215673e829cab3bb9d62ecc334b6346d49d3
Author: Florian Dold <florian@dold.me>
Date: Tue, 22 Jul 2025 00:11:12 +0200
GLS-specific sample rules
Diffstat:
1 file changed, 133 insertions(+), 37 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx b/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx
@@ -15,6 +15,7 @@
*/
import {
AbsoluteTime,
+ AmlSpaDialect,
AmountJson,
Amounts,
assertUnreachable,
@@ -46,10 +47,9 @@ import { RulesInfo } from "../../components/RulesInfo.js";
import { ShowDecisionLimitInfo } from "../../components/ShowDecisionLimitInfo.js";
import { ShowDefaultRules } from "../../components/ShowDefaultRules.js";
import { useCurrentDecisionRequest } from "../../hooks/decision-request.js";
+import { usePreferences } from "../../hooks/preferences.js";
import { useServerMeasures } from "../../hooks/server-info.js";
-const TALER_SCREEN_ID = 103;
-
const DEFAULT_MEASURE_IF_NONE = ["VERBOTEN"];
export const DEFAULT_LIMITS_WHEN_NEW_ACCOUNT: LegitimizationRuleSet = {
custom_measures: {},
@@ -276,6 +276,10 @@ function UpdateRulesForm({
defaultRules: KycRule[];
}): VNode {
const { i18n } = useTranslationContext();
+ const [pref] = usePreferences();
+ const dialect =
+ (pref.testingDialect ? undefined : config.aml_spa_dialect) ??
+ AmlSpaDialect.TESTING;
const [request, updateRequest] = useCurrentDecisionRequest();
const [showAddRuleForm, setShowAddRuleForm] = useState(false);
const measureList = !rootMeasures ? [] : Object.keys(rootMeasures);
@@ -343,6 +347,61 @@ function UpdateRulesForm({
? undefined
: findRuleInconsistency(isWallet, currentRules);
+ const ButtonRulesBasicPlan = () => (
+ <button
+ onClick={() => {
+ setRules(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"
+ >
+ <i18n.Translate>Basic plan</i18n.Translate>
+ </button>
+ );
+
+ const ButtonRulesPremiumPlan = () => (
+ <button
+ onClick={() => {
+ setRules(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"
+ >
+ <i18n.Translate>Premium</i18n.Translate>
+ </button>
+ );
+
+ const ButtonRulesEcommerce = () => (
+ <button
+ onClick={() => {
+ setRules(E_COMMERCE(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"
+ >
+ <i18n.Translate>E-commerce</i18n.Translate>
+ </button>
+ );
+
+ const ButtonRulesPoS = () => (
+ <button
+ onClick={() => {
+ setRules(POINT_OF_SALE(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"
+ >
+ <i18n.Translate>Point-of-sale</i18n.Translate>
+ </button>
+ );
+
+ const ButtonRulesGlsMerchantRegisteredEcommerce = () => (
+ <button
+ onClick={() => {
+ setRules(GLS_MERCHANT_REGISTERED_ECOMMERCE(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"
+ >
+ <i18n.Translate>GLS Registered E-Commerce Merchant</i18n.Translate>
+ </button>
+ );
+
return (
<div>
{!ruleInconsistency ? undefined : (
@@ -394,8 +453,8 @@ function UpdateRulesForm({
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(",")}
+ The AML account is bank account, but not all account-related
+ operations are limited ({BANK_RULES.join(",")}).
</i18n.Translate>
);
}
@@ -459,38 +518,19 @@ function UpdateRulesForm({
>
<i18n.Translate>Freeze account</i18n.Translate>
</button>
- <button
- onClick={() => {
- setRules(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"
- >
- <i18n.Translate>Basic plan</i18n.Translate>
- </button>
- <button
- onClick={() => {
- setRules(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"
- >
- <i18n.Translate>Premium</i18n.Translate>
- </button>
- <button
- onClick={() => {
- setRules(E_COMMERCE(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"
- >
- <i18n.Translate>E-commerce</i18n.Translate>
- </button>
- <button
- onClick={() => {
- setRules(POINT_OF_SALE(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"
- >
- <i18n.Translate>Point-of-sale</i18n.Translate>
- </button>
+ {dialect == AmlSpaDialect.TESTING ? (
+ <>
+ <ButtonRulesBasicPlan />
+ <ButtonRulesPremiumPlan />
+ <ButtonRulesEcommerce />
+ <ButtonRulesPoS />
+ </>
+ ) : null}
+ {dialect == AmlSpaDialect.GLS ? (
+ <>
+ <ButtonRulesGlsMerchantRegisteredEcommerce />
+ </>
+ ) : null}
<h2 class="mt-4 mb-2">
<i18n.Translate>Behavior on rule expiration</i18n.Translate>
</h2>
@@ -499,7 +539,7 @@ function UpdateRulesForm({
onClick={() => {
expirationForm.model
.getHandlerForAttributeKey("measure")
- .onChange(limits.successor_measure ?? "");
+ .onChange(defaultSuccessorMeasure);
}}
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"
>
@@ -985,6 +1025,62 @@ const POINT_OF_SALE: TemplateRulesFunction = (currency, isWallet) =>
: BANK_RULES.includes(r.operation_type);
});
+const GLS_MERCHANT_REGISTERED_ECOMMERCE: TemplateRulesFunction = (
+ currency,
+ isWallet,
+) =>
+ [
+ {
+ display_priority: 1,
+ measures: ["VERBOTEN"],
+ operation_type: LimitOperationType.withdraw,
+ threshold: Amounts.stringify(`${currency}:0`),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
+ },
+ {
+ display_priority: 1,
+ measures: ["VERBOTEN"],
+ operation_type: LimitOperationType.close,
+ threshold: Amounts.stringify(`${currency}:0`),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
+ },
+ {
+ display_priority: 1,
+ measures: ["VERBOTEN"],
+ operation_type: LimitOperationType.merge,
+ threshold: Amounts.stringify(`${currency}:0`),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
+ },
+ {
+ display_priority: 1,
+ measures: ["preserve-investigate"],
+ operation_type: LimitOperationType.deposit,
+ threshold: Amounts.stringify(`${currency}:100000`),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
+ },
+ {
+ display_priority: 1,
+ measures: ["preserve-investigate"],
+ operation_type: LimitOperationType.aggregate,
+ threshold: Amounts.stringify(`${currency}:100000`),
+ timeframe: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ months: 1 }),
+ ),
+ },
+ ].filter((r) => {
+ return isWallet
+ ? WALLET_RULES.includes(r.operation_type)
+ : BANK_RULES.includes(r.operation_type);
+ });
+
const E_COMMERCE: TemplateRulesFunction = (currency, isWallet) =>
[
{