Skip to content

Commit 210af16

Browse files
authored
Merge pull request #12 from PagoPlus/feat/numscript-builder
[FEAT] Numscript Builder
2 parents 2e0f726 + a2b844d commit 210af16

8 files changed

Lines changed: 699 additions & 8 deletions

File tree

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Handle line endings automatically for files detected as text
22
# and leave all files detected as binary untouched.
3-
* text=auto
3+
* text eol=lf
44

55
# Elixir files
66
*.ex diff=elixir

.github/workflows/elixir.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ jobs:
6868
name: Test
6969
runs-on: ${{ matrix.os }}
7070
steps:
71+
- name: Set Git to use LF insted of CRLF
72+
run: |
73+
git config --global core.autocrlf false
74+
git config --global core.eol lf
75+
7176
- uses: actions/checkout@v4
7277

7378
- uses: erlef/setup-beam@v1

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# NumscriptEx
2+
[![Hex Version](https://img.shields.io/hexpm/v/numscriptex.svg)](https://hex.pm/packages/numscriptex)
3+
[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/numscriptex/)
4+
25
NumscriptEx is a library that allows its users to check and run Numscripts in Elixir. If this is your first time hearing about
36
Numscripts, here is a quick explanation:
47

@@ -35,12 +38,12 @@ config :numscriptex,
3538
These above are the default values.
3639

3740
## Usage
38-
This library basically has two core functions: `Numscriptex.check/1` and `Numscriptex.run/2`.
41+
You can build Numscripts dynamically with `Numscriptex.Builder.build/1`, check if your script is valid with `Numscriptex.check/1`,
42+
and last but not least, you can run your script with `Numscriptex.run/2`.
3943

40-
Want to check if your script is valid and ready to go? Use the `check/1` function.
41-
Already checked the script and want to execute it? Use the `run/2` function.
44+
You can read more about the `Numscriptex.Builder` module and how to use it on its [guide](https://github.com/PagoPlus/numscriptex/blob/main/guides/builder.md)
4245

43-
But before introducing these two functions, you will need to know what is the `Numscriptex.Run` struct.
46+
And before introducing the other two functions, you will need to know what is the `Numscriptex.Run` struct.
4447

4548
### Numscriptex.Run
4649
A numscript needs some other data aside the script itself to run correctly, and
@@ -184,4 +187,4 @@ iex> %{
184187
## License
185188
Copyright (c) 2025 MedFlow
186189

187-
This library is MIT licensed. See the [LICENSE](https://github.com/PagoPlus/numscriptex/blob/main/README.md) for details.
190+
This library is MIT licensed. See the [LICENSE](https://github.com/PagoPlus/numscriptex/blob/main/LINCESE) for details.

guides/builder.md

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
# Builder
2+
This is a feature that makes it possible to build numscripts dynamically within your application.
3+
4+
## Usage
5+
You just need to call `Numscriptex.Builder.build/1` with the metadata necessary to build your numscript.
6+
7+
### Metadata
8+
Metadata is the sole argument you need to use the `build/1` function, wich has the following fields:
9+
10+
Required fields:
11+
- splits: a list of metadata needed to build numscripts (explained below)
12+
13+
Optional fields:
14+
- percent_asset: monetary asset (BRL, USD, EUR, and etc.) to use when build numscripts using percentage values.
15+
- remaining_to: where the rest of the money will go after the transaction is made
16+
- Only needed if you want to designate a specific destination. The default is [remaining kept](https://docs.formance.com/numscript/reference/destinations#allocation-destinations) wich makes the remainder go back to the source.
17+
18+
About the "list of metadata" mentioned on the `split` field above:
19+
20+
Required fields:
21+
- account: account whose the money will go to
22+
- amount: amount of money transferred. Be aware that this field only accepts integer values, so if you want to send $5 your amount field value should be 500.
23+
- type: the amount type, accepts two values:
24+
- fixed: treats the `amount` field as a number
25+
- percent: treats the `amount` field as a percentage value
26+
27+
Optional fields:
28+
Also accepts `aplits` and `remaining_to` fields, and a `asset` field:
29+
30+
- splits: in this case, this field is only neccessary if you want to build [nested destinations](https://docs.formance.com/numscript/reference/destinations#nested-destinations), and therefore it is only valid if the `type` field value is `:percent`
31+
- asset: also the monetary asset, but in this case is only needed on `:fixed` types
32+
33+
The `remaining_to` field continues with the same behaviour as above.
34+
35+
### Example
36+
With the metadata type above in mind, let's build a simple numscript.
37+
```elixir
38+
# metadata:
39+
%{
40+
splits: [
41+
%{
42+
type: :percent,
43+
amount: 20,
44+
account: "some:destination"
45+
}
46+
],
47+
remaining_to: "another:destination",
48+
percent_asset: "USD"
49+
}
50+
```
51+
Wich will generate the following numscript:
52+
```
53+
send [USD *] (
54+
source = @user
55+
destination = {
56+
20% to @some:destination
57+
remaining to @another:destination
58+
}
59+
)
60+
```
61+
If the `:remaining_to` field were not defined, the remaining would be kept:
62+
```
63+
send [USD *] (
64+
source = @user
65+
destination = {
66+
20% to @some:destination
67+
remaining kept
68+
}
69+
)
70+
```
71+
72+
P.S.: The percent amount types will always be executed first, so we can assure that
73+
all of the percentages are based off of the initial value. This feature is still in
74+
development as we speak, but soon we will have more flexibility in those regards.
75+
76+
## Examples
77+
### Example 1
78+
Both fixed and percent types:
79+
```elixir
80+
%{
81+
splits: [
82+
%{
83+
type: :fixed,
84+
amount: 500,
85+
asset: "USD",
86+
account: "some:destination:a"
87+
},
88+
%{
89+
type: :percent,
90+
amount: 20,
91+
account: "some:destination:b"
92+
}
93+
],
94+
remaining_to: "some:destination:c",
95+
percent_asset: "USD"
96+
}
97+
```
98+
Will generate:
99+
```
100+
send [USD *] (
101+
source = @user
102+
destination = {
103+
20% to @some:destination:b
104+
remaining kept
105+
}
106+
)
107+
108+
send [USD 500] (
109+
source = @user
110+
destination = @some:destination:a
111+
)
112+
```
113+
114+
### Example 2
115+
Nested percent-type, plus fixed
116+
```elixir
117+
%{
118+
splits: [
119+
%{
120+
type: :fixed,
121+
amount: 1250,
122+
asset: "USD",
123+
account: "some:destination:a"
124+
},
125+
%{
126+
type: :percent,
127+
amount: 30,
128+
remaining_to: "remaining:destination:b",
129+
splits: [
130+
%{
131+
type: :percent,
132+
amount: 40,
133+
account: "some:destination:c"
134+
},
135+
%{
136+
type: :percent,
137+
amount: 20,
138+
account: "some:destination:b"
139+
}
140+
]
141+
}
142+
],
143+
remaining_to: "remaining:destination:a",
144+
percent_asset: "USD"
145+
}
146+
```
147+
Will generate:
148+
```
149+
send [USD *] (
150+
source = @user
151+
destination = {
152+
30% to {
153+
40% to @some:destination:c
154+
20% to @some:destination:b
155+
remaining to remaining:destination:b
156+
}
157+
remaining to remaining:destination:a
158+
}
159+
)
160+
161+
send [USD 1250] (
162+
source = @user
163+
destination = @some:destination:a
164+
)
165+
```
166+
Note that the `asset` field inside of nested destinations will be ignored, given that
167+
they all go under the same `send` scope.
168+
169+
### Example 3
170+
Nested percent-type, plus fixed, and with fixed-type within nests
171+
```elixir
172+
%{
173+
splits: [
174+
%{
175+
type: :fixed,
176+
amount: 1050,
177+
asset: "EUR",
178+
account: "some:destination:a"
179+
},
180+
%{
181+
type: :percent,
182+
amount: 20,
183+
account: "some:destination:b"
184+
},
185+
%{
186+
type: :percent,
187+
amount: 30,
188+
remaining_to: "remaining:destination:b",
189+
splits: [
190+
%{
191+
type: :fixed,
192+
amount: 1250,
193+
asset: "USD",
194+
account: "some:destination:c"
195+
},
196+
%{
197+
type: :percent,
198+
amount: 15,
199+
account: "some:destination:e"
200+
},
201+
%{
202+
type: :percent,
203+
amount: 20,
204+
account: "some:destination:d"
205+
},
206+
%{
207+
type: :percent,
208+
amount: 50,
209+
remaining_to: "remaining:destination:c",
210+
splits: [
211+
%{
212+
type: :fixed,
213+
amount: 500,
214+
asset: "BRL",
215+
account: "some:destination:b"
216+
},
217+
%{
218+
type: :percent,
219+
amount: 20,
220+
account: "some:destination:a"
221+
},
222+
%{
223+
type: :percent,
224+
amount: 27,
225+
account: "some:destination:a"
226+
}
227+
]
228+
}
229+
]
230+
}
231+
],
232+
remaining_to: "remaining:destination:a",
233+
percent_asset: "EUR"
234+
}
235+
```
236+
Will generate:
237+
```
238+
send [EUR *] (
239+
source = @user
240+
destination = {
241+
20% to @some:destination:b
242+
30% to {
243+
15% to @some:destination:e
244+
20% to @some:destination:d
245+
50% to {
246+
20% to @some:destination:a
247+
27% to @some:destination:a
248+
remaining to @remaining:destination:c
249+
}
250+
remaining to @remaining:destination:b
251+
}
252+
remaining to @remaining:destination:a
253+
}
254+
)
255+
256+
send [USD 1250] (
257+
source = @user
258+
destination = @some:destination:c
259+
)
260+
261+
send [BRL 500] (
262+
source = @user
263+
destination = @some:destination:b
264+
)
265+
266+
send [EUR 1050] (
267+
source = @user
268+
destination = @some:destination:a
269+
)
270+
```
271+
Note that the `fixed` types in nested `splits` (i.e. `splits` fields inside of `splits` fields) will be popped out and still
272+
be built after the `percent` types.

0 commit comments

Comments
 (0)