@pax2pay/model-banking 0.1.383 → 0.1.385
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Card/Creatable.ts +2 -1
- package/Card/index.ts +2 -1
- package/Operation/Creatable.ts +1 -1
- package/Organization/index.ts +2 -1
- package/Rule/{Rule/Base.ts → Base.ts} +7 -0
- package/Rule/Charge.ts +108 -0
- package/Rule/Other.ts +48 -0
- package/Rule/Reserve.ts +111 -0
- package/Rule/Score.ts +45 -0
- package/Rule/State/Transaction.ts +17 -4
- package/Rule/State/index.ts +3 -2
- package/Rule/control.ts +8 -0
- package/Rule/index.ts +76 -97
- package/Rule/type.ts +13 -0
- package/Settlement/Batch.ts +1 -2
- package/Transaction/index.ts +3 -3
- package/dist/Authorization/Status.d.ts +1 -1
- package/dist/Card/Creatable.d.ts +1 -1
- package/dist/Card/Creatable.js +1 -1
- package/dist/Card/Creatable.js.map +1 -1
- package/dist/Card/index.d.ts +1 -1
- package/dist/Card/index.js +1 -1
- package/dist/Card/index.js.map +1 -1
- package/dist/Operation/Creatable.js +1 -1
- package/dist/Operation/Creatable.js.map +1 -1
- package/dist/Organization/index.d.ts +3 -3
- package/dist/Organization/index.js +1 -1
- package/dist/Organization/index.js.map +1 -1
- package/dist/Rail/index.d.ts +1 -1
- package/dist/Rule/{Rule/Base.d.ts → Base.d.ts} +1 -0
- package/dist/Rule/{Rule/Base.js → Base.js} +6 -0
- package/dist/Rule/Base.js.map +1 -0
- package/dist/Rule/Charge.d.ts +40 -0
- package/dist/Rule/Charge.js +68 -0
- package/dist/Rule/Charge.js.map +1 -0
- package/dist/Rule/{Rule/Other.d.ts → Other.d.ts} +8 -0
- package/dist/Rule/Other.js +38 -0
- package/dist/Rule/Other.js.map +1 -0
- package/dist/Rule/Reserve.d.ts +38 -0
- package/dist/Rule/Reserve.js +76 -0
- package/dist/Rule/Reserve.js.map +1 -0
- package/dist/Rule/{Rule/Score.d.ts → Score.d.ts} +6 -0
- package/dist/Rule/Score.js +30 -0
- package/dist/Rule/Score.js.map +1 -0
- package/dist/Rule/State/Transaction.d.ts +9 -4
- package/dist/Rule/State/Transaction.js +7 -2
- package/dist/Rule/State/Transaction.js.map +1 -1
- package/dist/Rule/State/index.d.ts +3 -3
- package/dist/Rule/State/index.js +2 -2
- package/dist/Rule/State/index.js.map +1 -1
- package/dist/Rule/control.d.ts +4 -0
- package/dist/Rule/control.js +6 -0
- package/dist/Rule/control.js.map +1 -0
- package/dist/Rule/index.d.ts +29 -13
- package/dist/Rule/index.js +68 -70
- package/dist/Rule/index.js.map +1 -1
- package/dist/Rule/type.d.ts +3 -0
- package/dist/Rule/type.js +7 -0
- package/dist/Rule/type.js.map +1 -0
- package/dist/Settlement/Batch.d.ts +0 -1
- package/dist/Settlement/Batch.js +1 -2
- package/dist/Settlement/Batch.js.map +1 -1
- package/dist/Transaction/Status.d.ts +1 -1
- package/dist/Transaction/index.js +3 -3
- package/dist/Transaction/index.js.map +1 -1
- package/package.json +1 -1
- package/Rule/Rule/Charge.ts +0 -48
- package/Rule/Rule/Other.ts +0 -14
- package/Rule/Rule/Score.ts +0 -21
- package/Rule/Rule/index.ts +0 -37
- package/dist/Rule/Rule/Base.js.map +0 -1
- package/dist/Rule/Rule/Charge.d.ts +0 -29
- package/dist/Rule/Rule/Charge.js +0 -34
- package/dist/Rule/Rule/Charge.js.map +0 -1
- package/dist/Rule/Rule/Other.js +0 -12
- package/dist/Rule/Rule/Other.js.map +0 -1
- package/dist/Rule/Rule/Score.js +0 -16
- package/dist/Rule/Rule/Score.js.map +0 -1
- package/dist/Rule/Rule/index.d.ts +0 -24
- package/dist/Rule/Rule/index.js +0 -27
- package/dist/Rule/Rule/index.js.map +0 -1
package/Card/Creatable.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isoly } from "isoly"
|
|
2
2
|
import { isly } from "isly"
|
|
3
3
|
import { Amount } from "../Amount"
|
|
4
|
-
import { Rule
|
|
4
|
+
import type { Rule } from "../Rule"
|
|
5
|
+
import { type as ruleType } from "../Rule/type"
|
|
5
6
|
import { Expiry } from "./Expiry"
|
|
6
7
|
import { Meta } from "./Meta"
|
|
7
8
|
import { Preset } from "./Preset"
|
package/Card/index.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { isly } from "isly"
|
|
|
3
3
|
import { Amount } from "../Amount"
|
|
4
4
|
import { Realm } from "../Realm"
|
|
5
5
|
import { Report } from "../Report"
|
|
6
|
-
import { Rule
|
|
6
|
+
import type { Rule } from "../Rule"
|
|
7
|
+
import { type as ruleType } from "../Rule/type"
|
|
7
8
|
import { Changeable as CardChangeable } from "./Changeable"
|
|
8
9
|
import { Creatable as CardCreatable } from "./Creatable"
|
|
9
10
|
import { Expiry as CardExpiry } from "./Expiry"
|
package/Operation/Creatable.ts
CHANGED
package/Organization/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isly } from "isly"
|
|
2
2
|
import { Realm } from "../Realm"
|
|
3
|
-
import { Rule
|
|
3
|
+
import { Rule } from "../Rule"
|
|
4
|
+
import { type as ruleType } from "../Rule/type"
|
|
4
5
|
import { Changeable as OrganizationChangeable } from "./Changeable"
|
|
5
6
|
import { Contact as OrganizationContact } from "./Contact"
|
|
6
7
|
|
|
@@ -15,6 +15,13 @@ export namespace Base {
|
|
|
15
15
|
export namespace Kind {
|
|
16
16
|
export const values = ["authorization", "outbound", "inbound", "capture", "refund"] as const
|
|
17
17
|
export const type = isly.string<Kind>(values)
|
|
18
|
+
export function is(kind: Kind, rule: Base, groups: string[] | undefined): boolean {
|
|
19
|
+
return (
|
|
20
|
+
kind == rule.type &&
|
|
21
|
+
(!rule.groups ||
|
|
22
|
+
rule.groups.some(ruleGroup => groups?.some(organizationGroup => organizationGroup == ruleGroup)))
|
|
23
|
+
)
|
|
24
|
+
}
|
|
18
25
|
}
|
|
19
26
|
export type Category = typeof Category.values[number]
|
|
20
27
|
export namespace Category {
|
package/Rule/Charge.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { isoly } from "isoly"
|
|
2
|
+
import { selectively } from "selectively"
|
|
3
|
+
import { isly } from "isly"
|
|
4
|
+
import { Amount } from "../Amount"
|
|
5
|
+
import { Exchange } from "../Exchange"
|
|
6
|
+
import { Realm } from "../Realm"
|
|
7
|
+
import { Base } from "./Base"
|
|
8
|
+
import { control } from "./control"
|
|
9
|
+
import type { State } from "./State"
|
|
10
|
+
|
|
11
|
+
export interface Charge extends Charge.Api {
|
|
12
|
+
charge: {
|
|
13
|
+
percentage?: number
|
|
14
|
+
fixed?: Amount
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export namespace Charge {
|
|
18
|
+
export type Action = typeof Action.value
|
|
19
|
+
export namespace Action {
|
|
20
|
+
export const value = "charge"
|
|
21
|
+
}
|
|
22
|
+
export interface Api extends Base {
|
|
23
|
+
action: Charge.Action
|
|
24
|
+
charge: {
|
|
25
|
+
percentage?: number
|
|
26
|
+
fixed?: number | Amount
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export namespace Api {
|
|
30
|
+
export const type = Base.type.extend<Api>({
|
|
31
|
+
action: isly.string(Action.value),
|
|
32
|
+
charge: isly.object({
|
|
33
|
+
percentage: isly.number().optional(),
|
|
34
|
+
fixed: isly.union<number | Amount>(Amount.type, isly.number()).optional(),
|
|
35
|
+
}),
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
export function fromApi(rule: Api, realm: Realm): Charge
|
|
39
|
+
export function fromApi(rule: Api, currency: isoly.Currency): Charge
|
|
40
|
+
export function fromApi(rule: Api, currency: Realm | isoly.Currency): Charge {
|
|
41
|
+
return {
|
|
42
|
+
...rule,
|
|
43
|
+
charge: {
|
|
44
|
+
...rule.charge,
|
|
45
|
+
fixed:
|
|
46
|
+
typeof rule.charge.fixed == "number"
|
|
47
|
+
? [Realm.is(currency) ? Realm.currency[currency] : currency, rule.charge.fixed]
|
|
48
|
+
: rule.charge.fixed,
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function evaluate(
|
|
53
|
+
rules: Charge[],
|
|
54
|
+
state: State,
|
|
55
|
+
macros?: Record<string, selectively.Definition>,
|
|
56
|
+
table: Exchange.Rates = {}
|
|
57
|
+
): { outcomes: Charge[]; charge: Required<State["transaction"]["original"]>["charge"] } {
|
|
58
|
+
const result: ReturnType<typeof evaluate> = {
|
|
59
|
+
outcomes: [],
|
|
60
|
+
charge: { current: 0, total: state.transaction.original.charge?.total ?? 0 },
|
|
61
|
+
}
|
|
62
|
+
if (state.transaction.stage == "finalize" && ["card", "external"].some(type => type == state.transaction.type))
|
|
63
|
+
for (const rule of rules) {
|
|
64
|
+
if (control(rule, state, macros)) {
|
|
65
|
+
if (rule.charge.percentage)
|
|
66
|
+
result.charge.current = isoly.Currency.add(
|
|
67
|
+
state.transaction.original.currency,
|
|
68
|
+
result.charge.current,
|
|
69
|
+
isoly.Currency.multiply(
|
|
70
|
+
state.transaction.original.currency,
|
|
71
|
+
state.transaction.original.amount,
|
|
72
|
+
rule.charge.percentage / 100
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
if (rule.charge.fixed) {
|
|
76
|
+
const charge =
|
|
77
|
+
state.transaction.original.currency === rule.charge.fixed[0]
|
|
78
|
+
? rule.charge.fixed[1]
|
|
79
|
+
: Exchange.convert(
|
|
80
|
+
rule.charge.fixed[1],
|
|
81
|
+
rule.charge.fixed[0],
|
|
82
|
+
state.transaction.original.currency,
|
|
83
|
+
table
|
|
84
|
+
) ?? 0
|
|
85
|
+
result.charge.current = isoly.Currency.add(
|
|
86
|
+
state.transaction.original.currency,
|
|
87
|
+
result.charge.current,
|
|
88
|
+
charge
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
result.outcomes.push(rule)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
result.charge.total = isoly.Currency.add(
|
|
95
|
+
state.transaction.original.currency,
|
|
96
|
+
result.charge.current,
|
|
97
|
+
result.charge.total
|
|
98
|
+
)
|
|
99
|
+
return result
|
|
100
|
+
}
|
|
101
|
+
export function apply(charge: { current: number; total: number }, state: State): number {
|
|
102
|
+
return state.transaction.kind == "authorization" ||
|
|
103
|
+
state.transaction.kind == "outbound" ||
|
|
104
|
+
state.transaction.kind == "capture"
|
|
105
|
+
? isoly.Currency.add(state.transaction.original.currency, state.transaction.original.total, charge.current)
|
|
106
|
+
: isoly.Currency.subtract(state.transaction.original.currency, state.transaction.original.total, charge.current)
|
|
107
|
+
}
|
|
108
|
+
}
|
package/Rule/Other.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isoly } from "isoly"
|
|
2
|
+
import { selectively } from "selectively"
|
|
3
|
+
import { isly } from "isly"
|
|
4
|
+
import type { Note } from "../Transaction/Note"
|
|
5
|
+
import { Base } from "./Base"
|
|
6
|
+
import { control } from "./control"
|
|
7
|
+
import type { State } from "./State"
|
|
8
|
+
|
|
9
|
+
export interface Other extends Base {
|
|
10
|
+
action: Other.Action
|
|
11
|
+
}
|
|
12
|
+
export namespace Other {
|
|
13
|
+
export type Action = typeof Action.values[number]
|
|
14
|
+
export namespace Action {
|
|
15
|
+
export const values = ["review", "reject", "flag"] as const
|
|
16
|
+
export const type = isly.string<Action>(values)
|
|
17
|
+
}
|
|
18
|
+
export const type = Base.type.extend<Other>({ action: Action.type })
|
|
19
|
+
export function evaluate(
|
|
20
|
+
rules: Other[],
|
|
21
|
+
state: State,
|
|
22
|
+
macros?: Record<string, selectively.Definition>
|
|
23
|
+
): { outcomes: Record<Other.Action, Other[]>; notes: Note[]; flags: Set<string> } {
|
|
24
|
+
const now = isoly.DateTime.now()
|
|
25
|
+
const result: ReturnType<typeof evaluate> = {
|
|
26
|
+
outcomes: {
|
|
27
|
+
review: [],
|
|
28
|
+
reject: [],
|
|
29
|
+
flag: [],
|
|
30
|
+
},
|
|
31
|
+
notes: [],
|
|
32
|
+
flags: new Set<string>(),
|
|
33
|
+
}
|
|
34
|
+
if (
|
|
35
|
+
state.transaction.stage == "initiate" &&
|
|
36
|
+
["card", "external", "internal"].some(type => type == state.transaction.type)
|
|
37
|
+
)
|
|
38
|
+
for (const rule of rules) {
|
|
39
|
+
if (control(rule, state, macros)) {
|
|
40
|
+
result.outcomes[rule.action].push(rule)
|
|
41
|
+
result.notes.push({ author: "automatic", created: now, text: rule.name, rule })
|
|
42
|
+
rule.flags.forEach(f => result.flags.add(f))
|
|
43
|
+
rule.action == "review" && result.flags.add("review")
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
48
|
+
}
|
package/Rule/Reserve.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { isoly } from "isoly"
|
|
2
|
+
import { selectively } from "selectively"
|
|
3
|
+
import { isly } from "isly"
|
|
4
|
+
import { Amount } from "../Amount"
|
|
5
|
+
import { Exchange } from "../Exchange"
|
|
6
|
+
import { Realm } from "../Realm"
|
|
7
|
+
import { Base } from "./Base"
|
|
8
|
+
import { control } from "./control"
|
|
9
|
+
import type { State } from "./State"
|
|
10
|
+
|
|
11
|
+
export interface Reserve extends Reserve.Api {
|
|
12
|
+
reserve: {
|
|
13
|
+
percentage?: number
|
|
14
|
+
fixed?: Amount
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export namespace Reserve {
|
|
18
|
+
export type Action = typeof Action.value
|
|
19
|
+
export namespace Action {
|
|
20
|
+
export const value = "reserve"
|
|
21
|
+
}
|
|
22
|
+
export const type = Base.type.extend<Reserve>({
|
|
23
|
+
action: isly.string(Action.value),
|
|
24
|
+
reserve: isly.object({
|
|
25
|
+
percentage: isly.number(),
|
|
26
|
+
fixed: Amount.type.optional(),
|
|
27
|
+
}),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export interface Api extends Base {
|
|
31
|
+
action: Reserve.Action
|
|
32
|
+
reserve: {
|
|
33
|
+
percentage?: number
|
|
34
|
+
fixed?: number | Amount
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export namespace Api {
|
|
38
|
+
export const type = Base.type.extend<Api>({
|
|
39
|
+
action: isly.string(Action.value),
|
|
40
|
+
reserve: isly.object({
|
|
41
|
+
percentage: isly.number().optional(),
|
|
42
|
+
fixed: isly.union<number | Amount>(Amount.type, isly.number()).optional(),
|
|
43
|
+
}),
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
export function fromApi(rule: Api, realm: Realm): Reserve
|
|
47
|
+
export function fromApi(rule: Api, currency: isoly.Currency): Reserve
|
|
48
|
+
export function fromApi(rule: Api, currency: Realm | isoly.Currency): Reserve {
|
|
49
|
+
return {
|
|
50
|
+
...rule,
|
|
51
|
+
reserve: {
|
|
52
|
+
...rule.reserve,
|
|
53
|
+
fixed:
|
|
54
|
+
typeof rule.reserve.fixed == "number"
|
|
55
|
+
? [Realm.is(currency) ? Realm.currency[currency] : currency, rule.reserve.fixed]
|
|
56
|
+
: rule.reserve.fixed,
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function evaluate(
|
|
61
|
+
rules: Reserve[],
|
|
62
|
+
state: State,
|
|
63
|
+
macros?: Record<string, selectively.Definition>,
|
|
64
|
+
table: Exchange.Rates = {}
|
|
65
|
+
): { outcomes: Reserve[]; reserve: number } {
|
|
66
|
+
const result: ReturnType<typeof evaluate> = {
|
|
67
|
+
outcomes: [],
|
|
68
|
+
reserve: 0,
|
|
69
|
+
}
|
|
70
|
+
if (
|
|
71
|
+
state.transaction.stage == "initiate" &&
|
|
72
|
+
["authorization", "outbound"].some(kind => kind == state.transaction.kind) &&
|
|
73
|
+
["card", "external"].some(type => type == state.transaction.type)
|
|
74
|
+
)
|
|
75
|
+
for (const rule of rules) {
|
|
76
|
+
if (control(rule, state, macros)) {
|
|
77
|
+
if (rule.reserve.percentage)
|
|
78
|
+
result.reserve = isoly.Currency.add(
|
|
79
|
+
state.transaction.original.currency,
|
|
80
|
+
result.reserve,
|
|
81
|
+
isoly.Currency.multiply(
|
|
82
|
+
state.transaction.original.currency,
|
|
83
|
+
state.transaction.original.amount,
|
|
84
|
+
rule.reserve.percentage / 100
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
if (rule.reserve.fixed) {
|
|
88
|
+
const reserve =
|
|
89
|
+
state.transaction.original.currency === rule.reserve.fixed[0]
|
|
90
|
+
? rule.reserve.fixed[1]
|
|
91
|
+
: Exchange.convert(
|
|
92
|
+
rule.reserve.fixed[1],
|
|
93
|
+
rule.reserve.fixed[0],
|
|
94
|
+
state.transaction.original.currency,
|
|
95
|
+
table
|
|
96
|
+
) ?? 0
|
|
97
|
+
result.reserve = isoly.Currency.add(state.transaction.original.currency, result.reserve, reserve)
|
|
98
|
+
}
|
|
99
|
+
result.outcomes.push(rule)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return result
|
|
103
|
+
}
|
|
104
|
+
export function apply(reserve: number, state: State): number {
|
|
105
|
+
return state.transaction.kind == "authorization" ||
|
|
106
|
+
state.transaction.kind == "outbound" ||
|
|
107
|
+
state.transaction.kind == "capture"
|
|
108
|
+
? isoly.Currency.add(state.transaction.original.currency, state.transaction.original.total, reserve)
|
|
109
|
+
: isoly.Currency.subtract(state.transaction.original.currency, state.transaction.original.total, reserve)
|
|
110
|
+
}
|
|
111
|
+
}
|
package/Rule/Score.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { selectively } from "selectively"
|
|
2
|
+
import { isly } from "isly"
|
|
3
|
+
import { Base } from "./Base"
|
|
4
|
+
import { control } from "./control"
|
|
5
|
+
import type { State } from "./State"
|
|
6
|
+
|
|
7
|
+
export interface Score extends Base {
|
|
8
|
+
action: Score.Action
|
|
9
|
+
category: "fincrime"
|
|
10
|
+
risk: Score.Risk
|
|
11
|
+
}
|
|
12
|
+
export namespace Score {
|
|
13
|
+
export type Action = typeof Action.value
|
|
14
|
+
export namespace Action {
|
|
15
|
+
export const value = "score"
|
|
16
|
+
}
|
|
17
|
+
export type Risk = number
|
|
18
|
+
export const Risk = isly.number<Risk>(["positive", "integer"])
|
|
19
|
+
export const type = Base.type.extend<Score>({
|
|
20
|
+
action: isly.string(Action.value),
|
|
21
|
+
category: isly.string("fincrime"),
|
|
22
|
+
risk: Risk,
|
|
23
|
+
})
|
|
24
|
+
export function evaluate(
|
|
25
|
+
rules: Score[],
|
|
26
|
+
state: State,
|
|
27
|
+
macros?: Record<string, selectively.Definition>
|
|
28
|
+
): {
|
|
29
|
+
outcomes: Score[]
|
|
30
|
+
risk?: number | undefined
|
|
31
|
+
} {
|
|
32
|
+
const result: ReturnType<typeof evaluate> = { outcomes: [] }
|
|
33
|
+
if (
|
|
34
|
+
state.transaction.stage == "initiate" &&
|
|
35
|
+
["card", "external", "internal"].some(type => type == state.transaction.type)
|
|
36
|
+
)
|
|
37
|
+
for (const rule of rules) {
|
|
38
|
+
if (control(rule, state, macros)) {
|
|
39
|
+
result.risk = (result.risk ?? 100) * (rule.risk / 100)
|
|
40
|
+
result.outcomes.push(rule)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,27 +1,40 @@
|
|
|
1
1
|
import { isoly } from "isoly"
|
|
2
2
|
import type { Address } from "../../Rail/Address"
|
|
3
3
|
import { Transaction as ModelTransaction } from "../../Transaction"
|
|
4
|
-
import { Rule } from "../
|
|
4
|
+
import type { Rule } from "../index"
|
|
5
5
|
|
|
6
6
|
export interface Transaction extends ModelTransaction.Creatable {
|
|
7
7
|
kind: Rule.Base.Kind
|
|
8
|
+
stage: "finalize" | "initiate"
|
|
8
9
|
amount: number
|
|
9
10
|
type: ModelTransaction.Types
|
|
10
11
|
risk?: number
|
|
11
|
-
original: {
|
|
12
|
+
original: {
|
|
13
|
+
currency: isoly.Currency
|
|
14
|
+
total: number
|
|
15
|
+
amount: number
|
|
16
|
+
charge?: { current: number; total: number }
|
|
17
|
+
reserve?: number
|
|
18
|
+
}
|
|
12
19
|
}
|
|
13
20
|
export namespace Transaction {
|
|
14
21
|
export function from(
|
|
15
22
|
accountName: string,
|
|
16
23
|
transaction: ModelTransaction.Creatable & { counterpart: Address },
|
|
17
|
-
kind: Rule.Base.Kind
|
|
24
|
+
kind: Rule.Base.Kind,
|
|
25
|
+
stage: "finalize" | "initiate"
|
|
18
26
|
): Transaction {
|
|
19
27
|
return {
|
|
20
28
|
...transaction,
|
|
29
|
+
stage,
|
|
21
30
|
kind,
|
|
22
31
|
amount: Math.abs(transaction.amount),
|
|
23
32
|
type: ModelTransaction.getType(transaction.counterpart, accountName),
|
|
24
|
-
original: {
|
|
33
|
+
original: {
|
|
34
|
+
currency: transaction.currency,
|
|
35
|
+
amount: Math.abs(transaction.amount),
|
|
36
|
+
total: Math.abs(transaction.amount),
|
|
37
|
+
},
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
40
|
}
|
package/Rule/State/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { isly } from "isly"
|
|
|
2
2
|
import { Account as ModelAccount } from "../../Account"
|
|
3
3
|
import type { Address } from "../../Rail/Address"
|
|
4
4
|
import type { Transaction as ModelTransaction } from "../../Transaction"
|
|
5
|
-
import { Rule } from "../
|
|
5
|
+
import type { Rule } from "../index"
|
|
6
6
|
import { Account as StateAccount } from "./Account"
|
|
7
7
|
import { Authorization as StateAuthorization } from "./Authorization"
|
|
8
8
|
import { Card as StateCard } from "./Card"
|
|
@@ -45,6 +45,7 @@ export namespace State {
|
|
|
45
45
|
days: Account.Days,
|
|
46
46
|
transaction: ModelTransaction.Creatable & { counterpart: Address },
|
|
47
47
|
kind: Rule.Base.Kind,
|
|
48
|
+
stage: "finalize" | "initiate",
|
|
48
49
|
authorization?: Authorization,
|
|
49
50
|
card?: Card,
|
|
50
51
|
organization?: Organization
|
|
@@ -52,7 +53,7 @@ export namespace State {
|
|
|
52
53
|
return {
|
|
53
54
|
data,
|
|
54
55
|
account: Account.from(account, transactions, days),
|
|
55
|
-
transaction: Transaction.from(account.name, transaction, kind),
|
|
56
|
+
transaction: Transaction.from(account.name, transaction, kind, stage),
|
|
56
57
|
authorization,
|
|
57
58
|
card,
|
|
58
59
|
organization,
|
package/Rule/control.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { selectively } from "selectively"
|
|
2
|
+
import type { Rule } from "."
|
|
3
|
+
import { definitions } from "./definitions"
|
|
4
|
+
import type { State } from "./State"
|
|
5
|
+
|
|
6
|
+
export function control(rule: Rule, state: State, macros?: Record<string, selectively.Definition>): boolean {
|
|
7
|
+
return selectively.resolve({ ...macros, ...definitions }, selectively.parse(rule.condition)).is(state)
|
|
8
|
+
}
|
package/Rule/index.ts
CHANGED
|
@@ -1,129 +1,108 @@
|
|
|
1
|
-
import { isoly } from "isoly"
|
|
2
1
|
import { selectively } from "selectively"
|
|
2
|
+
import { isly } from "isly"
|
|
3
3
|
import { Exchange } from "../Exchange"
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { Realm } from "../Realm"
|
|
5
|
+
import { Base as RuleBase } from "./Base"
|
|
6
|
+
import { Charge as RuleCharge } from "./Charge"
|
|
7
|
+
import { control as ruleControl } from "./control"
|
|
8
|
+
import { Other as RuleOther } from "./Other"
|
|
9
|
+
import { Reserve as RuleReserve } from "./Reserve"
|
|
10
|
+
import { Score as RuleScore } from "./Score"
|
|
7
11
|
import { State as RuleState } from "./State"
|
|
12
|
+
import { type as ruleType } from "./type"
|
|
8
13
|
|
|
9
|
-
export type Rule =
|
|
10
|
-
|
|
14
|
+
export type Rule = Rule.Other | Rule.Score | Rule.Charge | Rule.Reserve
|
|
11
15
|
export namespace Rule {
|
|
12
|
-
export import
|
|
13
|
-
export import
|
|
14
|
-
export import
|
|
15
|
-
export import Kind = ModelRule.Base.Kind
|
|
16
|
-
export import Other = ModelRule.Other
|
|
17
|
-
export import Score = ModelRule.Score
|
|
18
|
-
export import Charge = ModelRule.Charge
|
|
16
|
+
export import Other = RuleOther
|
|
17
|
+
export import Score = RuleScore
|
|
18
|
+
export import Charge = RuleCharge
|
|
19
19
|
export import State = RuleState
|
|
20
|
-
export
|
|
21
|
-
export
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
export import Reserve = RuleReserve
|
|
21
|
+
export import Base = RuleBase
|
|
22
|
+
export import Kind = Base.Kind
|
|
23
|
+
export import Category = Base.Category
|
|
24
|
+
export const control = ruleControl
|
|
25
|
+
export type Api = Rule.Other | Rule.Score | Rule.Charge.Api | Rule.Reserve.Api
|
|
26
|
+
export namespace Api {
|
|
27
|
+
export const type = isly.union<Api, Rule.Other, Rule.Score, Rule.Charge.Api, Rule.Reserve.Api>(
|
|
28
|
+
Rule.Other.type,
|
|
29
|
+
Rule.Score.type,
|
|
30
|
+
Rule.Charge.Api.type,
|
|
31
|
+
Rule.Reserve.Api.type
|
|
27
32
|
)
|
|
28
33
|
}
|
|
29
|
-
function
|
|
30
|
-
return
|
|
34
|
+
export function fromApi(rule: Rule.Api, realm: Realm): Rule {
|
|
35
|
+
return rule.action == "charge"
|
|
36
|
+
? Charge.fromApi(rule, realm)
|
|
37
|
+
: rule.action == "reserve"
|
|
38
|
+
? Reserve.fromApi(rule, realm)
|
|
39
|
+
: rule
|
|
31
40
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
function charge(
|
|
43
|
-
rules: ModelRule.Charge[],
|
|
44
|
-
state: State,
|
|
45
|
-
macros?: Record<string, selectively.Definition>,
|
|
46
|
-
table: Exchange.Rates = {}
|
|
47
|
-
): { outcomes: Rule[]; charge: number } {
|
|
48
|
-
const result: { outcomes: Rule[]; charge: number } = { outcomes: [], charge: 0 }
|
|
49
|
-
for (const rule of rules) {
|
|
50
|
-
if (control(rule, state, macros)) {
|
|
51
|
-
if (rule.charge.percentage)
|
|
52
|
-
result.charge = isoly.Currency.add(
|
|
53
|
-
state.transaction.currency,
|
|
54
|
-
result.charge,
|
|
55
|
-
isoly.Currency.multiply(state.transaction.currency, state.transaction.amount, rule.charge.percentage / 100)
|
|
56
|
-
)
|
|
57
|
-
if (rule.charge.fixed) {
|
|
58
|
-
const charge =
|
|
59
|
-
state.transaction.currency === rule.charge.fixed[0]
|
|
60
|
-
? rule.charge.fixed[1]
|
|
61
|
-
: Exchange.convert(rule.charge.fixed[1], rule.charge.fixed[0], state.transaction.currency, table) ?? 0
|
|
62
|
-
result.charge = isoly.Currency.add(state.transaction.currency, result.charge, charge)
|
|
63
|
-
}
|
|
64
|
-
result.outcomes.push(rule)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return result
|
|
41
|
+
export type Action = typeof Action.values[number]
|
|
42
|
+
export namespace Action {
|
|
43
|
+
export const values = [
|
|
44
|
+
...Other.Action.values,
|
|
45
|
+
Score.Action.value,
|
|
46
|
+
Charge.Action.value,
|
|
47
|
+
Reserve.Action.value,
|
|
48
|
+
] as const
|
|
49
|
+
export const type = isly.string<Action>(values)
|
|
68
50
|
}
|
|
51
|
+
export const type = ruleType
|
|
69
52
|
export function evaluate(
|
|
70
53
|
rules: Rule[],
|
|
71
|
-
state:
|
|
54
|
+
state: RuleState,
|
|
72
55
|
macros?: Record<string, selectively.Definition>,
|
|
73
56
|
table: Exchange.Rates = {}
|
|
74
|
-
):
|
|
75
|
-
const outcomes: Record<
|
|
57
|
+
): RuleState.Evaluated {
|
|
58
|
+
const outcomes: Record<Rule.Action, Rule[]> = {
|
|
76
59
|
review: [],
|
|
77
60
|
reject: [],
|
|
78
61
|
flag: [],
|
|
79
62
|
charge: [],
|
|
63
|
+
score: [],
|
|
64
|
+
reserve: [],
|
|
80
65
|
}
|
|
81
|
-
const { other, chargers, scorers } = rules
|
|
82
|
-
|
|
83
|
-
|
|
66
|
+
const { other, chargers, scorers, reservers } = sort(rules, state)
|
|
67
|
+
const scored = Score.evaluate(scorers, state, macros)
|
|
68
|
+
outcomes.score.push(...scored.outcomes)
|
|
69
|
+
state.transaction.risk = scored.risk
|
|
70
|
+
const evaluated = Other.evaluate(other, state, macros)
|
|
71
|
+
outcomes.flag.push(...evaluated.outcomes.flag)
|
|
72
|
+
outcomes.review.push(...evaluated.outcomes.review)
|
|
73
|
+
outcomes.reject.push(...evaluated.outcomes.reject)
|
|
74
|
+
const reserved = Reserve.evaluate(reservers, state, macros, table)
|
|
75
|
+
outcomes.reserve.push(...reserved.outcomes)
|
|
76
|
+
state.transaction.original.reserve = reserved.reserve
|
|
77
|
+
state.transaction.original.total = Reserve.apply(reserved.reserve, state)
|
|
78
|
+
const charged = Charge.evaluate(chargers, state, macros, table)
|
|
79
|
+
outcomes.charge.push(...charged.outcomes)
|
|
80
|
+
state.transaction.original.charge = charged.charge
|
|
81
|
+
state.transaction.original.total = Charge.apply(charged.charge, state)
|
|
82
|
+
const outcome = outcomes.reject.length > 0 ? "reject" : outcomes.review.length > 0 ? "review" : "approve"
|
|
83
|
+
return { ...state, flags: [...evaluated.flags], notes: evaluated.notes, outcomes, outcome }
|
|
84
|
+
}
|
|
85
|
+
function sort(
|
|
86
|
+
rules: Rule[],
|
|
87
|
+
state: State
|
|
88
|
+
): { other: Rule.Other[]; chargers: Rule.Charge[]; scorers: Rule.Score[]; reservers: Rule.Reserve[] } {
|
|
89
|
+
return rules.reduce(
|
|
90
|
+
(r: ReturnType<typeof sort>, rule) => {
|
|
91
|
+
if (Base.Kind.is(state.transaction.kind, rule, state.organization?.groups))
|
|
84
92
|
rule.action == "score"
|
|
85
93
|
? r.scorers.push(rule)
|
|
86
94
|
: rule.action == "charge"
|
|
87
95
|
? r.chargers.push(rule)
|
|
96
|
+
: rule.action == "reserve"
|
|
97
|
+
? r.reservers.push(rule)
|
|
88
98
|
: r.other.push(rule)
|
|
89
99
|
return r
|
|
90
100
|
},
|
|
91
|
-
{ other: [], chargers: [], scorers: [] }
|
|
101
|
+
{ other: [], chargers: [], scorers: [], reservers: [] }
|
|
92
102
|
)
|
|
93
|
-
state.transaction.risk = score(scorers, state, macros)
|
|
94
|
-
const notes: Note[] = []
|
|
95
|
-
const flags: Set<string> = new Set()
|
|
96
|
-
const now = isoly.DateTime.now()
|
|
97
|
-
for (const rule of other) {
|
|
98
|
-
if (control(rule, state, macros)) {
|
|
99
|
-
outcomes[rule.action].push(rule)
|
|
100
|
-
notes.push({ author: "automatic", created: now, text: rule.name, rule })
|
|
101
|
-
rule.flags.forEach(f => flags.add(f))
|
|
102
|
-
rule.action == "review" && flags.add("review")
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const charged = charge(chargers, state, macros, table)
|
|
106
|
-
state.transaction.original.charge = isoly.Currency.add(
|
|
107
|
-
state.transaction.original.currency,
|
|
108
|
-
state.transaction.original.charge ?? 0,
|
|
109
|
-
charged.charge
|
|
110
|
-
)
|
|
111
|
-
state.transaction.original.total =
|
|
112
|
-
state.transaction.kind == "authorization" ||
|
|
113
|
-
state.transaction.kind == "outbound" ||
|
|
114
|
-
state.transaction.kind == "capture"
|
|
115
|
-
? isoly.Currency.add(state.transaction.original.currency, state.transaction.original.amount, charged.charge)
|
|
116
|
-
: isoly.Currency.subtract(
|
|
117
|
-
state.transaction.original.currency,
|
|
118
|
-
state.transaction.original.amount,
|
|
119
|
-
charged.charge
|
|
120
|
-
)
|
|
121
|
-
outcomes.charge.push(...charged.outcomes)
|
|
122
|
-
const outcome = outcomes.reject.length > 0 ? "reject" : outcomes.review.length > 0 ? "review" : "approve"
|
|
123
|
-
return { ...state, flags: [...flags], notes, outcomes, outcome }
|
|
124
103
|
}
|
|
125
104
|
export function isLegacy(rule: Rule): boolean {
|
|
126
|
-
return !
|
|
105
|
+
return !Rule.Base.Category.type.is(rule.category)
|
|
127
106
|
}
|
|
128
107
|
export function fromLegacy(rule: Rule): Rule {
|
|
129
108
|
return isLegacy(rule) ? { ...rule, category: "fincrime" } : rule
|
package/Rule/type.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isly } from "isly"
|
|
2
|
+
import { Charge } from "./Charge"
|
|
3
|
+
import type { Rule } from "./index"
|
|
4
|
+
import { Other } from "./Other"
|
|
5
|
+
import { Reserve } from "./Reserve"
|
|
6
|
+
import { Score } from "./Score"
|
|
7
|
+
|
|
8
|
+
export const type = isly.union<Rule, Other, Score, Charge.Api, Reserve>(
|
|
9
|
+
Other.type,
|
|
10
|
+
Score.type,
|
|
11
|
+
Charge.Api.type,
|
|
12
|
+
Reserve.type
|
|
13
|
+
)
|