brot 0.1.1 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -0
- data/README.md +137 -271
- data/brot.gemspec +1 -1
- data/lib/brot/account.rb +22 -0
- data/lib/brot/document.rb +105 -0
- data/lib/brot/pain_version.rb +83 -0
- data/lib/brot/schema.rb +60 -0
- data/lib/brot/serializer_base.rb +169 -0
- data/lib/brot/{pain00100112/serializer.rb → serializers/pain00100112.rb} +3 -6
- data/lib/brot/serializers/pain00100303.rb +22 -0
- data/lib/brot/transfer.rb +43 -0
- data/lib/brot/utils.rb +84 -0
- data/lib/brot/version.rb +1 -1
- data/lib/brot.rb +10 -1
- data/test/document_test.rb +137 -0
- data/xsd/pain.001.003.03.xsd +474 -0
- metadata +13 -11
- data/lib/brot/pain00100112/account.rb +0 -48
- data/lib/brot/pain00100112/document.rb +0 -162
- data/lib/brot/pain00100112/schema.rb +0 -58
- data/lib/brot/pain00100112/serializer_base.rb +0 -174
- data/lib/brot/pain00100112/transfer.rb +0 -96
- data/lib/brot/pain00100112/utils.rb +0 -92
- data/lib/brot/pain00100112.rb +0 -25
- data/test/pain00100112_document_test.rb +0 -87
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 554c5401cab39dd06ddee8d095edb4121621c9abc88cc57aacc2e53ef945a720
|
|
4
|
+
data.tar.gz: e3f4dcc0317331c41f7f6f4228b4c26dc77f89b91826c80704e45fa56dfdc39a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c37b1944939b3808a7ff3fac28827b22f1228dbd5d3c10f3ce1728718a30d44374f6595cfe9900cda74f98f577db06871d30b988f27ffd4cb3d1a22e583fdff9
|
|
7
|
+
data.tar.gz: 9ef486499ef15d58947fba7250905af25ebde97f45363d40d55b663d89beeaaa9898f5ae5bece7bc6d6bfb4db205c4722eb20820958d353caaa4c9b2fa83c263
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
|
@@ -3,71 +3,43 @@
|
|
|
3
3
|
`brot` builds ISO 20022 SEPA credit transfer XML for DATEV-style uploads with
|
|
4
4
|
`Nokogiri::XML::Builder`.
|
|
5
5
|
|
|
6
|
-
The gem
|
|
7
|
-
the provided `pain.001.001.12.xsd`.
|
|
6
|
+
The gem supports these bundled schema targets:
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
gem 'brot'
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## What The Gem Does
|
|
8
|
+
- `pain.001.003.03`
|
|
9
|
+
- `pain.001.001.12`
|
|
16
10
|
|
|
17
|
-
`
|
|
11
|
+
Use `Brot::PainVersion::PAIN_001_003_03` or
|
|
12
|
+
`Brot::PainVersion::PAIN_001_001_12` instead of raw symbols when selecting the
|
|
13
|
+
output format.
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- Stay close to the DATEV upload use case instead of exposing the full ISO
|
|
23
|
-
20022 surface area.
|
|
24
|
-
|
|
25
|
-
Current assumptions baked into the serializer:
|
|
15
|
+
The public API is intentionally narrow and DATEV-oriented. It models one
|
|
16
|
+
debtor, one payment information block, one or more transfers, and unstructured
|
|
17
|
+
remittance information.
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
- Payment method is always `TRF`.
|
|
29
|
-
- Charge bearer is always `SLEV`.
|
|
30
|
-
- Instructed amount is always emitted as `EUR`.
|
|
31
|
-
- A debtor BIC or creditor BIC is optional. If absent, the XML uses the
|
|
32
|
-
fallback financial institution identifier `NOTPROVIDED`.
|
|
19
|
+
## Official References
|
|
33
20
|
|
|
34
|
-
|
|
21
|
+
For authoritative field semantics and message context, use these primary
|
|
22
|
+
sources:
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
24
|
+
- ISO 20022 message catalogue for current `pain.001` definitions, including
|
|
25
|
+
`pain.001.001.12`:
|
|
26
|
+
https://www.iso20022.org/iso-20022-message-definitions
|
|
27
|
+
- ISO 20022 message archive for older definitions, including
|
|
28
|
+
`pain.001.001.03`:
|
|
29
|
+
https://www.iso20022.org/catalogue-messages/iso-20022-messages-archive?search=pain
|
|
30
|
+
- European Payments Council SCT rulebook and implementation-guideline entry
|
|
31
|
+
point for SEPA-specific usage constraints and terminology:
|
|
32
|
+
https://www.europeanpaymentscouncil.eu/what-we-do/epc-payment-schemes/sepa-credit-transfer/sepa-credit-transfer-rulebook-and
|
|
38
33
|
|
|
39
|
-
|
|
34
|
+
These external documents describe the full ISO 20022 or EPC meaning of many
|
|
35
|
+
fields. `brot` intentionally implements only the narrower subset documented in
|
|
36
|
+
this README.
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
- One debtor per document
|
|
43
|
-
- One payment information block (`PmtInf`) per document
|
|
44
|
-
- One or more credit transfer entries (`CdtTrfTxInf`)
|
|
45
|
-
- Debtor and creditor names
|
|
46
|
-
- Debtor and creditor IBANs
|
|
47
|
-
- Optional debtor and creditor BICs
|
|
48
|
-
- End-to-end IDs
|
|
49
|
-
- Optional instruction IDs
|
|
50
|
-
- Optional purpose codes
|
|
51
|
-
- Unstructured remittance information (`RmtInf/Ustrd`)
|
|
52
|
-
- Validation against the bundled `pain.001.001.12.xsd`
|
|
53
|
-
|
|
54
|
-
What is not modeled yet, even though the schema allows it:
|
|
55
|
-
|
|
56
|
-
- Structured remittance information
|
|
57
|
-
- Postal addresses and richer party identification data
|
|
58
|
-
- Ultimate debtor and ultimate creditor
|
|
59
|
-
- Multiple `PmtInf` sections in one document
|
|
60
|
-
- Non-SEPA service levels or non-EUR transfer setups
|
|
61
|
-
- Local instrument and category purpose variants beyond the current subset
|
|
62
|
-
- Intermediary agents and extra account layers
|
|
63
|
-
- Regulatory reporting, tax, cheque, and supplementary data sections
|
|
64
|
-
|
|
65
|
-
The practical meaning is:
|
|
38
|
+
## Installation
|
|
66
39
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
as a generic Ruby object model.
|
|
40
|
+
```ruby
|
|
41
|
+
gem 'brot'
|
|
42
|
+
```
|
|
71
43
|
|
|
72
44
|
## Quick Start
|
|
73
45
|
|
|
@@ -75,7 +47,7 @@ The practical meaning is:
|
|
|
75
47
|
require 'brot'
|
|
76
48
|
require 'date'
|
|
77
49
|
|
|
78
|
-
transfer = Brot::
|
|
50
|
+
transfer = Brot::Transfer.new(
|
|
79
51
|
amount: '1250.50',
|
|
80
52
|
creditor_name: 'Example Supplier GmbH',
|
|
81
53
|
creditor_iban: 'DE89370400440532013000',
|
|
@@ -85,7 +57,7 @@ transfer = Brot::Pain00100112::Transfer.new(
|
|
|
85
57
|
instruction_id: 'PAY-0001'
|
|
86
58
|
)
|
|
87
59
|
|
|
88
|
-
document = Brot::
|
|
60
|
+
document = Brot::Document.new(
|
|
89
61
|
message_id: 'MSG-20260313-01',
|
|
90
62
|
payment_information_id: 'PMT-20260313-01',
|
|
91
63
|
initiating_party_name: 'Example Debtor GmbH',
|
|
@@ -94,7 +66,7 @@ document = Brot::Pain00100112::Document.new(
|
|
|
94
66
|
debtor_bic: 'INGDDEFFXXX',
|
|
95
67
|
requested_execution_date: Date.new(2026, 3, 13),
|
|
96
68
|
transfers: [transfer],
|
|
97
|
-
|
|
69
|
+
version: Brot::PainVersion::PAIN_001_003_03
|
|
98
70
|
)
|
|
99
71
|
|
|
100
72
|
xml = document.to_xml
|
|
@@ -103,60 +75,90 @@ result = document.validate
|
|
|
103
75
|
raise result.errors.join("\n") unless result.valid?
|
|
104
76
|
```
|
|
105
77
|
|
|
78
|
+
If you omit `version:`, the document defaults to
|
|
79
|
+
`Brot::PainVersion::PAIN_001_003_03`.
|
|
80
|
+
Call `document.validate!` if you prefer a raised `Brot::ValidationError`.
|
|
81
|
+
|
|
82
|
+
## Supported Subset
|
|
83
|
+
|
|
84
|
+
Current assumptions baked into the serializer:
|
|
85
|
+
|
|
86
|
+
- Service level is always `SEPA`.
|
|
87
|
+
- Payment method is always `TRF`.
|
|
88
|
+
- Charge bearer is always `SLEV`.
|
|
89
|
+
- Instructed amount is always emitted as `EUR`.
|
|
90
|
+
- One debtor and one `PmtInf` block are emitted per document.
|
|
91
|
+
- Remittance information is emitted as unstructured text only.
|
|
92
|
+
- If a debtor BIC or creditor BIC is absent, the XML falls back to
|
|
93
|
+
`NOTPROVIDED`.
|
|
94
|
+
|
|
95
|
+
Supported input data:
|
|
96
|
+
|
|
97
|
+
- Debtor and creditor names
|
|
98
|
+
- Debtor and creditor IBANs
|
|
99
|
+
- Optional debtor and creditor BICs
|
|
100
|
+
- End-to-end IDs
|
|
101
|
+
- Optional instruction IDs
|
|
102
|
+
- Optional purpose codes
|
|
103
|
+
- Unstructured remittance information
|
|
104
|
+
- Validation against bundled XSDs for both supported versions
|
|
105
|
+
|
|
106
|
+
Not modeled:
|
|
107
|
+
|
|
108
|
+
- Structured remittance information
|
|
109
|
+
- Postal addresses and richer party identification
|
|
110
|
+
- Ultimate debtor and ultimate creditor
|
|
111
|
+
- Multiple `PmtInf` sections in one document
|
|
112
|
+
- Non-SEPA service levels or non-EUR transfer setups
|
|
113
|
+
- Broader ISO 20022 branches outside the current DATEV-oriented subset
|
|
114
|
+
|
|
106
115
|
## Public API
|
|
107
116
|
|
|
108
117
|
The main public classes are:
|
|
109
118
|
|
|
110
|
-
- `Brot::
|
|
111
|
-
- `Brot::
|
|
112
|
-
- `Brot::
|
|
113
|
-
- `Brot::
|
|
114
|
-
- `Brot::
|
|
119
|
+
- `Brot::Account`
|
|
120
|
+
- `Brot::Transfer`
|
|
121
|
+
- `Brot::Document`
|
|
122
|
+
- `Brot::Schema`
|
|
123
|
+
- `Brot::PainVersion`
|
|
124
|
+
- `Brot::Schema::Result`
|
|
115
125
|
|
|
116
|
-
### `Brot::
|
|
126
|
+
### `Brot::Account`
|
|
117
127
|
|
|
118
|
-
Use `Account`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
128
|
+
Use `Brot::Account` for standalone debtor or creditor banking details when you
|
|
129
|
+
need it directly. Most callers do not instantiate it manually because
|
|
130
|
+
`Brot::Document` and `Brot::Transfer` build accounts from `*_iban` and `*_bic`
|
|
131
|
+
arguments.
|
|
122
132
|
|
|
123
133
|
Example:
|
|
124
134
|
|
|
125
135
|
```ruby
|
|
126
|
-
account = Brot::
|
|
136
|
+
account = Brot::Account.new(
|
|
127
137
|
iban: 'DE89370400440532013000',
|
|
128
138
|
bic: 'COBADEFFXXX'
|
|
129
139
|
)
|
|
130
|
-
|
|
131
|
-
account.iban
|
|
132
|
-
# => "DE89370400440532013000"
|
|
133
|
-
|
|
134
|
-
account.bic
|
|
135
|
-
# => "COBADEFFXXX"
|
|
136
140
|
```
|
|
137
141
|
|
|
138
142
|
Attributes:
|
|
139
143
|
|
|
140
144
|
- `iban`
|
|
141
|
-
|
|
142
|
-
Must be a syntactically valid IBAN. Spaces are removed automatically.
|
|
145
|
+
Validated IBAN with spaces removed.
|
|
143
146
|
- `bic`
|
|
144
|
-
|
|
145
|
-
Optional. If omitted, the serializer falls back to `NOTPROVIDED` in the XML.
|
|
147
|
+
Optional validated BIC.
|
|
146
148
|
|
|
147
|
-
|
|
149
|
+
Method:
|
|
148
150
|
|
|
149
151
|
- `bic_or_placeholder`
|
|
150
|
-
Returns the
|
|
152
|
+
Returns the BIC if present, otherwise `NOTPROVIDED`.
|
|
151
153
|
|
|
152
|
-
### `Brot::
|
|
154
|
+
### `Brot::Transfer`
|
|
153
155
|
|
|
154
|
-
Use `Transfer` for each outgoing credit transfer inside the
|
|
156
|
+
Use `Brot::Transfer` for each outgoing credit transfer inside the batch.
|
|
155
157
|
|
|
156
158
|
Example:
|
|
157
159
|
|
|
158
160
|
```ruby
|
|
159
|
-
transfer = Brot::
|
|
161
|
+
transfer = Brot::Transfer.new(
|
|
160
162
|
amount: '1250.50',
|
|
161
163
|
creditor_name: 'Example Supplier GmbH',
|
|
162
164
|
creditor_iban: 'DE89370400440532013000',
|
|
@@ -171,254 +173,118 @@ transfer = Brot::Pain00100112::Transfer.new(
|
|
|
171
173
|
Initialization attributes:
|
|
172
174
|
|
|
173
175
|
- `amount`
|
|
174
|
-
|
|
175
|
-
Required. Must be positive and have at most two decimal places.
|
|
176
|
+
Required. Positive, maximum two decimal places.
|
|
176
177
|
- `creditor_name`
|
|
177
|
-
|
|
178
|
-
Required. Maximum length is 70 characters in this implementation.
|
|
178
|
+
Required. Maximum 70 characters.
|
|
179
179
|
- `creditor_iban`
|
|
180
|
-
Example: `'DE89370400440532013000'`
|
|
181
180
|
Required. Validated as an IBAN.
|
|
182
181
|
- `creditor_bic`
|
|
183
|
-
Example: `'COBADEFFXXX'`
|
|
184
182
|
Optional. Validated as a BIC if supplied.
|
|
185
183
|
- `end_to_end_id`
|
|
186
|
-
|
|
187
|
-
Required. Maximum length is 35 characters.
|
|
188
|
-
- `remittance_information`
|
|
189
|
-
Example: `'Invoice 2026-0001'`
|
|
190
|
-
Required. Maximum length is 140 characters.
|
|
191
|
-
- `instruction_id`
|
|
192
|
-
Example: `'PAY-0001'`
|
|
193
|
-
Optional. Maximum length is 35 characters.
|
|
194
|
-
- `purpose_code`
|
|
195
|
-
Example: `'SUPP'`
|
|
196
|
-
Optional. Maximum length is 4 characters.
|
|
197
|
-
|
|
198
|
-
Readable attributes after initialization:
|
|
199
|
-
|
|
200
|
-
- `amount`
|
|
201
|
-
- `creditor_name`
|
|
202
|
-
- `creditor_account`
|
|
203
|
-
- `end_to_end_id`
|
|
184
|
+
Required. Maximum 35 characters.
|
|
204
185
|
- `remittance_information`
|
|
186
|
+
Required. Maximum 140 characters.
|
|
205
187
|
- `instruction_id`
|
|
188
|
+
Optional. Maximum 35 characters.
|
|
206
189
|
- `purpose_code`
|
|
190
|
+
Optional. Maximum 4 characters.
|
|
207
191
|
|
|
208
|
-
### `Brot::
|
|
192
|
+
### `Brot::Document`
|
|
209
193
|
|
|
210
|
-
Use `Document`
|
|
211
|
-
point of the gem.
|
|
194
|
+
Use `Brot::Document` for the full payment file.
|
|
212
195
|
|
|
213
|
-
Example:
|
|
196
|
+
Example using `.12` output:
|
|
214
197
|
|
|
215
198
|
```ruby
|
|
216
|
-
document = Brot::
|
|
199
|
+
document = Brot::Document.new(
|
|
217
200
|
message_id: 'MSG-20260313-01',
|
|
218
201
|
payment_information_id: 'PMT-20260313-01',
|
|
219
202
|
initiating_party_name: 'Example Debtor GmbH',
|
|
220
203
|
debtor_name: 'Example Debtor GmbH',
|
|
221
204
|
debtor_iban: 'DE12500105170648489890',
|
|
222
|
-
debtor_bic: 'INGDDEFFXXX',
|
|
223
205
|
requested_execution_date: Date.new(2026, 3, 13),
|
|
224
206
|
transfers: [transfer],
|
|
225
|
-
|
|
226
|
-
created_at: Time.utc(2026, 3, 13, 12, 0, 0)
|
|
207
|
+
version: Brot::PainVersion::PAIN_001_001_12
|
|
227
208
|
)
|
|
228
209
|
```
|
|
229
210
|
|
|
230
211
|
Initialization attributes:
|
|
231
212
|
|
|
232
213
|
- `message_id`
|
|
233
|
-
|
|
234
|
-
Required. Group header message identifier. Maximum length is 35 characters.
|
|
214
|
+
Required. Maximum 35 characters.
|
|
235
215
|
- `payment_information_id`
|
|
236
|
-
|
|
237
|
-
Required. Payment information block identifier. Maximum length is 35
|
|
238
|
-
characters.
|
|
216
|
+
Required. Maximum 35 characters.
|
|
239
217
|
- `initiating_party_name`
|
|
240
|
-
|
|
241
|
-
Required. Maximum length is 70 characters.
|
|
218
|
+
Required. Maximum 70 characters.
|
|
242
219
|
- `debtor_name`
|
|
243
|
-
|
|
244
|
-
Required. Maximum length is 70 characters.
|
|
220
|
+
Required. Maximum 70 characters.
|
|
245
221
|
- `debtor_iban`
|
|
246
|
-
Example: `'DE12500105170648489890'`
|
|
247
222
|
Required. Validated as an IBAN.
|
|
248
223
|
- `debtor_bic`
|
|
249
|
-
Example: `'INGDDEFFXXX'`
|
|
250
224
|
Optional. Validated as a BIC if supplied.
|
|
251
225
|
- `requested_execution_date`
|
|
252
|
-
|
|
253
|
-
Required. Date only.
|
|
226
|
+
Required. `Date` or ISO 8601 date string.
|
|
254
227
|
- `transfers`
|
|
255
|
-
|
|
256
|
-
Required. Must contain at least one `Brot::Pain00100112::Transfer`.
|
|
228
|
+
Required. Must contain at least one `Brot::Transfer`.
|
|
257
229
|
- `batch_booking`
|
|
258
|
-
Example: `true`
|
|
259
230
|
Optional. Defaults to `true`.
|
|
260
231
|
- `created_at`
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
- `message_id`
|
|
267
|
-
- `payment_information_id`
|
|
268
|
-
- `initiating_party_name`
|
|
269
|
-
- `debtor_name`
|
|
270
|
-
- `debtor_account`
|
|
271
|
-
- `requested_execution_date`
|
|
272
|
-
- `transfers`
|
|
273
|
-
- `batch_booking`
|
|
274
|
-
- `created_at`
|
|
232
|
+
Optional. Defaults to current UTC time.
|
|
233
|
+
- `version`
|
|
234
|
+
Optional. Defaults to `Brot::PainVersion::PAIN_001_003_03`.
|
|
235
|
+
Supported values are `Brot::PainVersion::PAIN_001_003_03` and
|
|
236
|
+
`Brot::PainVersion::PAIN_001_001_12`.
|
|
275
237
|
|
|
276
238
|
Useful methods:
|
|
277
239
|
|
|
278
|
-
- `to_xml(pretty: true)`
|
|
279
|
-
Serializes the document to `pain.001.001.12` XML.
|
|
280
|
-
- `validate(xsd_path: Brot::Pain00100112::Schema.bundled_xsd_path)`
|
|
281
|
-
Validates the generated XML and returns a `Schema::Result`.
|
|
282
240
|
- `control_sum`
|
|
283
|
-
Returns the sum of all transfer amounts as a `BigDecimal`.
|
|
284
241
|
- `number_of_transactions`
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
Use `Schema` when you want to validate XML directly without first building a
|
|
290
|
-
`Document` object.
|
|
291
|
-
|
|
292
|
-
Example:
|
|
293
|
-
|
|
294
|
-
```ruby
|
|
295
|
-
xml = document.to_xml(pretty: false)
|
|
296
|
-
result = Brot::Pain00100112::Schema.validate(xml)
|
|
297
|
-
|
|
298
|
-
raise result.errors.join("\n") unless result.valid?
|
|
299
|
-
```
|
|
242
|
+
- `to_xml(pretty: true)`
|
|
243
|
+
- `validate(xsd_path: nil)`
|
|
244
|
+
- `validate!(xsd_path: nil)`
|
|
300
245
|
|
|
301
|
-
|
|
246
|
+
Version-specific output differences:
|
|
302
247
|
|
|
303
|
-
- `
|
|
304
|
-
|
|
305
|
-
- `
|
|
306
|
-
|
|
248
|
+
- `pain.001.003.03` uses namespace
|
|
249
|
+
`urn:iso:std:iso:20022:tech:xsd:pain.001.003.03`
|
|
250
|
+
- `pain.001.001.12` uses namespace
|
|
251
|
+
`urn:iso:std:iso:20022:tech:xsd:pain.001.001.12`
|
|
252
|
+
- `.03` writes `BIC` and plain `ReqdExctnDt`
|
|
253
|
+
- `.12` writes `BICFI` and `ReqdExctnDt/Dt`
|
|
307
254
|
|
|
308
|
-
### `Brot::
|
|
255
|
+
### `Brot::Schema`
|
|
309
256
|
|
|
310
|
-
`Schema
|
|
311
|
-
`Brot::Pain00100112::Schema::Result`.
|
|
257
|
+
Use `Brot::Schema` when you want to validate XML directly.
|
|
312
258
|
|
|
313
|
-
|
|
259
|
+
Examples:
|
|
314
260
|
|
|
315
261
|
```ruby
|
|
316
|
-
result =
|
|
317
|
-
|
|
262
|
+
result = Brot::Schema.validate(xml)
|
|
318
263
|
result.valid?
|
|
319
|
-
# => true
|
|
320
|
-
|
|
321
|
-
result.errors
|
|
322
|
-
# => []
|
|
323
264
|
```
|
|
324
265
|
|
|
325
|
-
Attributes and methods:
|
|
326
|
-
|
|
327
|
-
- `errors`
|
|
328
|
-
An array of schema validation error messages.
|
|
329
|
-
- `valid?`
|
|
330
|
-
Returns `true` when `errors` is empty.
|
|
331
|
-
|
|
332
|
-
## End-To-End Example
|
|
333
|
-
|
|
334
|
-
This example shows the normal workflow from transfers to XML generation and
|
|
335
|
-
validation:
|
|
336
|
-
|
|
337
266
|
```ruby
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
transfers = [
|
|
342
|
-
Brot::Pain00100112::Transfer.new(
|
|
343
|
-
amount: '1250.50',
|
|
344
|
-
creditor_name: 'Example Supplier GmbH',
|
|
345
|
-
creditor_iban: 'DE89370400440532013000',
|
|
346
|
-
creditor_bic: 'COBADEFFXXX',
|
|
347
|
-
end_to_end_id: 'INV-2026-0001',
|
|
348
|
-
remittance_information: 'Invoice 2026-0001'
|
|
349
|
-
),
|
|
350
|
-
Brot::Pain00100112::Transfer.new(
|
|
351
|
-
amount: '349.99',
|
|
352
|
-
creditor_name: 'Another Supplier GmbH',
|
|
353
|
-
creditor_iban: 'DE44500105175407324931',
|
|
354
|
-
end_to_end_id: 'INV-2026-0002',
|
|
355
|
-
remittance_information: 'Invoice 2026-0002'
|
|
356
|
-
)
|
|
357
|
-
]
|
|
358
|
-
|
|
359
|
-
document = Brot::Pain00100112::Document.new(
|
|
360
|
-
message_id: 'MSG-20260313-01',
|
|
361
|
-
payment_information_id: 'PMT-20260313-01',
|
|
362
|
-
initiating_party_name: 'Example Debtor GmbH',
|
|
363
|
-
debtor_name: 'Example Debtor GmbH',
|
|
364
|
-
debtor_iban: 'DE12500105170648489890',
|
|
365
|
-
debtor_bic: 'INGDDEFFXXX',
|
|
366
|
-
requested_execution_date: Date.new(2026, 3, 13),
|
|
367
|
-
transfers: transfers
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
xml = document.to_xml
|
|
371
|
-
result = document.validate
|
|
372
|
-
|
|
373
|
-
abort(result.errors.join("\n")) unless result.valid?
|
|
374
|
-
|
|
375
|
-
puts xml
|
|
267
|
+
path = Brot::Schema.bundled_xsd_path(Brot::PainVersion::PAIN_001_001_12)
|
|
268
|
+
result = Brot::Schema.validate(xml, xsd_path: path)
|
|
376
269
|
```
|
|
377
270
|
|
|
378
|
-
## Validation
|
|
379
|
-
|
|
380
|
-
The gem ships with the `pain.001.001.12.xsd` file, so validation works without
|
|
381
|
-
an external path:
|
|
382
|
-
|
|
383
271
|
```ruby
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
raise result.errors.join("\n") unless result.valid?
|
|
272
|
+
Brot::Schema.validate!(xml)
|
|
387
273
|
```
|
|
388
274
|
|
|
389
|
-
|
|
275
|
+
Methods:
|
|
390
276
|
|
|
391
|
-
|
|
277
|
+
- `bundled_xsd_path(version)`
|
|
278
|
+
- `validate(xml, version: nil, xsd_path: nil)`
|
|
279
|
+
- `validate!(xml, version: nil, xsd_path: nil)`
|
|
392
280
|
|
|
393
|
-
|
|
281
|
+
If you omit both `version:` and `xsd_path:`, the gem infers the bundled schema
|
|
282
|
+
from the XML document namespace.
|
|
394
283
|
|
|
395
|
-
|
|
284
|
+
### `Brot::PainVersion`
|
|
396
285
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
- Empty required text fields
|
|
400
|
-
- Amounts that are zero, negative, or have too many decimal places
|
|
401
|
-
- Missing transfers
|
|
286
|
+
`Brot::PainVersion` is the public value object for supported output formats.
|
|
287
|
+
Prefer these constants when constructing documents or looking up bundled XSDs:
|
|
402
288
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
```ruby
|
|
406
|
-
begin
|
|
407
|
-
Brot::Pain00100112::Transfer.new(
|
|
408
|
-
amount: '0',
|
|
409
|
-
creditor_name: 'Broken Example',
|
|
410
|
-
creditor_iban: 'INVALID',
|
|
411
|
-
end_to_end_id: 'X',
|
|
412
|
-
remittance_information: 'Broken'
|
|
413
|
-
)
|
|
414
|
-
rescue Brot::Pain00100112::ValidationError => error
|
|
415
|
-
warn error.message
|
|
416
|
-
end
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
## Development
|
|
420
|
-
|
|
421
|
-
```sh
|
|
422
|
-
bundle install
|
|
423
|
-
bundle exec rake
|
|
424
|
-
```
|
|
289
|
+
- `Brot::PainVersion::PAIN_001_003_03`
|
|
290
|
+
- `Brot::PainVersion::PAIN_001_001_12`
|
data/brot.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.summary = 'Build ISO 20022 pain.001 payment initiation XML for DATEV uploads.'
|
|
12
12
|
spec.description = <<~TEXT
|
|
13
13
|
brot builds SEPA credit transfer XML with Nokogiri::XML::Builder.
|
|
14
|
-
The output targets pain.001.001.12 for DATEV-oriented uploads.
|
|
14
|
+
The output targets pain.001.003.03 or pain.001.001.12 for DATEV-oriented uploads.
|
|
15
15
|
TEXT
|
|
16
16
|
spec.homepage = 'https://github.com/garriguv/brot'
|
|
17
17
|
spec.license = 'MIT'
|
data/lib/brot/account.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Brot
|
|
4
|
+
# Bank account details used in debtor and creditor sections.
|
|
5
|
+
class Account
|
|
6
|
+
attr_reader :bic, :iban
|
|
7
|
+
|
|
8
|
+
# @param iban [String]
|
|
9
|
+
# @param bic [String, nil]
|
|
10
|
+
def initialize(iban:, bic: nil)
|
|
11
|
+
@iban = Utils.normalize_iban!(iban)
|
|
12
|
+
@bic = bic.nil? ? nil : Utils.normalize_bic!(bic)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns the BIC if present, otherwise `NOTPROVIDED`.
|
|
16
|
+
#
|
|
17
|
+
# @return [String]
|
|
18
|
+
def bic_or_placeholder
|
|
19
|
+
bic || Utils.default_financial_institution_id
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|