brot 0.1.1 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf6f5e4155bf59cb2cc724e9ea723bb066d5c95e745e687b66e36ad4d058d81f
4
- data.tar.gz: 931cfa4d40375498939bdec52b97b9f63987e16e9efa825aec351987f7b97d6e
3
+ metadata.gz: 11ae2c0176f01320f17c5755596448f27c35b258c911ade2f6e4f4fba2096273
4
+ data.tar.gz: b985c0fdb460926d4c30c26c9c47f1815a061545ae1fac068f8d6584bd0c1fdd
5
5
  SHA512:
6
- metadata.gz: 46155574730e67a42bba7c1a815f1be3dcec94c2fda775a66b646b6237b6f51ee83f1d15907a4d4ed503ef9d0195316a42891d64acaa7326b83b1cca542651e5
7
- data.tar.gz: 9e5c22fdb7710f461e28639032e8534c8aafed98c77943e8ba4c27460cda071db18a0bdb10b0f1f11036cd44e117d1acea521762fac85e3c7971a06fd78beaf7
6
+ metadata.gz: ab2e95235f9d24a157076fee29ceb68499c845a43d4517c0f3a6ef2dbc0f233d7c7f4990ba443487c6d5b8e1c4a0cc00ad12415455d11c60fe6d197306ca0f53
7
+ data.tar.gz: 3664f7b067f59f1c62a7013e117b8521560e169cda40b3713eaba6178bd3e951dd178f3e6216621b38f4e984fab7ceddbc99b49a49f31b20fcff83cd98abe441
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
 
13
13
  steps:
14
- - uses: actions/checkout@v4
14
+ - uses: actions/checkout@v6
15
15
 
16
16
  - uses: ruby/setup-ruby@v1
17
17
  with:
@@ -10,7 +10,7 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
 
12
12
  steps:
13
- - uses: actions/checkout@v4
13
+ - uses: actions/checkout@v6
14
14
 
15
15
  - uses: ruby/setup-ruby@v1
16
16
  with:
@@ -33,7 +33,7 @@ jobs:
33
33
  contents: write
34
34
 
35
35
  steps:
36
- - uses: actions/checkout@v4
36
+ - uses: actions/checkout@v6
37
37
 
38
38
  - uses: ruby/setup-ruby@v1
39
39
  with:
data/.rubocop.yml CHANGED
@@ -7,3 +7,8 @@ AllCops:
7
7
  SuggestExtensions: false
8
8
  TargetRubyVersion: 4.0
9
9
 
10
+ Metrics/ClassLength:
11
+ Max: 120
12
+
13
+ Metrics/ModuleLength:
14
+ Max: 150
data/README.md CHANGED
@@ -1,73 +1,63 @@
1
1
  # brot
2
2
 
3
- `brot` builds ISO 20022 SEPA credit transfer XML for DATEV-style uploads with
3
+ `brot` builds ISO 20022 credit transfer XML for DATEV-style uploads with
4
4
  `Nokogiri::XML::Builder`.
5
5
 
6
- The gem emits `pain.001.001.12` and is designed to validate directly against
7
- the provided `pain.001.001.12.xsd`.
6
+ The gem supports these bundled schema targets:
8
7
 
9
- ## Installation
8
+ - `pain.001.001.03`
9
+ - `pain.001.001.12`
10
10
 
11
- ```ruby
12
- gem 'brot'
13
- ```
11
+ Use `Brot::PainVersion::PAIN_001_001_03` or
12
+ `Brot::PainVersion::PAIN_001_001_12` instead of raw symbols when selecting the
13
+ output format.
14
14
 
15
- ## What The Gem Does
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.
16
18
 
17
- `brot` is intentionally focused on one narrow job:
19
+ ## DATEV Compatibility
18
20
 
19
- - Generate `pain.001.001.12` customer credit transfer files.
20
- - Build XML with `Nokogiri::XML::Builder`.
21
- - Validate generated XML against the bundled `pain.001.001.12.xsd`.
22
- - Stay close to the DATEV upload use case instead of exposing the full ISO
23
- 20022 surface area.
21
+ Current DATEV compatibility and announced migration path are narrower than the
22
+ gem's bundled schema support:
24
23
 
25
- Current assumptions baked into the serializer:
24
+ - As of March 13, 2026, use `pain.001.001.03` for DATEV uploads.
25
+ - DATEV states that SEPA version 3.7 becomes mandatory in November 2026.
26
+ - Bundesbank maps SEPA version 3.6 to `pain.001.001.03` and SEPA version 3.7
27
+ to `pain.001.001.09`.
28
+ - `pain.001.001.09` is not implemented in this gem yet.
29
+ - `pain.001.001.12` remains available as a generic ISO 20022 output format,
30
+ but it should not be treated as a current DATEV upload target.
26
31
 
27
- - Service level is always `SEPA`.
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`.
32
+ ## Official References
33
33
 
34
- ## Scope And Gaps
35
-
36
- `brot` is intentionally a DATEV-oriented subset of `pain.001.001.12`, not a
37
- full abstraction over every branch allowed by the ISO 20022 schema.
38
-
39
- What is currently supported:
40
-
41
- - SEPA credit transfers
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`
34
+ For authoritative field semantics and message context, use these primary
35
+ sources:
53
36
 
54
- What is not modeled yet, even though the schema allows it:
37
+ - ISO 20022 message catalogue for current `pain.001` definitions, including
38
+ `pain.001.001.12`:
39
+ https://www.iso20022.org/iso-20022-message-definitions
40
+ - ISO 20022 message archive for older definitions, including
41
+ `pain.001.001.03`:
42
+ https://www.iso20022.org/catalogue-messages/iso-20022-messages-archive?search=pain
43
+ - DATEV note on SEPA version 3.7 becoming mandatory in November 2026:
44
+ https://www.datev.de/web/de/mydatev/datev-magazin/fuer-selbstbucher/neu-sepa-version-3-7/
45
+ - Deutsche Bundesbank format overview mapping SEPA versions to
46
+ `pain.001.001.03` and `pain.001.001.09`:
47
+ https://www.bundesbank.de/de/aufgaben/unbarer-zahlungsverkehr/serviceangebot/xml-formate-im-zv-613692
48
+ - European Payments Council SCT rulebook and implementation-guideline entry
49
+ point for SEPA-specific usage constraints and terminology:
50
+ https://www.europeanpaymentscouncil.eu/what-we-do/epc-payment-schemes/sepa-credit-transfer/sepa-credit-transfer-rulebook-and
55
51
 
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
52
+ These external documents describe the full ISO 20022 or EPC meaning of many
53
+ fields. `brot` intentionally implements only the narrower subset documented in
54
+ this README.
64
55
 
65
- The practical meaning is:
56
+ ## Installation
66
57
 
67
- - The gem aims to produce a clean DATEV-style payment initiation file for the
68
- supported SEPA use case.
69
- - The gem does not yet try to represent the entire `pain.001.001.12` standard
70
- as a generic Ruby object model.
58
+ ```ruby
59
+ gem 'brot'
60
+ ```
71
61
 
72
62
  ## Quick Start
73
63
 
@@ -75,8 +65,9 @@ The practical meaning is:
75
65
  require 'brot'
76
66
  require 'date'
77
67
 
78
- transfer = Brot::Pain00100112::Transfer.new(
68
+ transfer = Brot::Transfer.new(
79
69
  amount: '1250.50',
70
+ currency: 'EUR',
80
71
  creditor_name: 'Example Supplier GmbH',
81
72
  creditor_iban: 'DE89370400440532013000',
82
73
  creditor_bic: 'COBADEFFXXX',
@@ -85,7 +76,7 @@ transfer = Brot::Pain00100112::Transfer.new(
85
76
  instruction_id: 'PAY-0001'
86
77
  )
87
78
 
88
- document = Brot::Pain00100112::Document.new(
79
+ document = Brot::Document.new(
89
80
  message_id: 'MSG-20260313-01',
90
81
  payment_information_id: 'PMT-20260313-01',
91
82
  initiating_party_name: 'Example Debtor GmbH',
@@ -94,7 +85,7 @@ document = Brot::Pain00100112::Document.new(
94
85
  debtor_bic: 'INGDDEFFXXX',
95
86
  requested_execution_date: Date.new(2026, 3, 13),
96
87
  transfers: [transfer],
97
- batch_booking: true
88
+ version: Brot::PainVersion::PAIN_001_001_03
98
89
  )
99
90
 
100
91
  xml = document.to_xml
@@ -103,61 +94,103 @@ result = document.validate
103
94
  raise result.errors.join("\n") unless result.valid?
104
95
  ```
105
96
 
97
+ If you omit `version:`, the document defaults to
98
+ `Brot::PainVersion::PAIN_001_001_03`.
99
+ Call `document.validate!` if you prefer a raised `Brot::ValidationError`.
100
+
101
+ For current DATEV uploads, stick to
102
+ `Brot::PainVersion::PAIN_001_001_03`.
103
+
104
+ ## Supported Subset
105
+
106
+ Current assumptions baked into the serializer:
107
+
108
+ - Service level is always `SEPA`.
109
+ - Payment method is always `TRF`.
110
+ - Charge bearer is always `SLEV`.
111
+ - One debtor and one `PmtInf` block are emitted per document.
112
+ - Remittance information is emitted as unstructured text only.
113
+ - If a debtor BIC or creditor BIC is absent, the XML falls back to
114
+ `NOTPROVIDED`.
115
+
116
+ Currency support:
117
+
118
+ - `Brot::Transfer` accepts `currency:` and defaults it to `EUR`.
119
+ - The bundled `pain.001.001.03` and `pain.001.001.12` schemas both emit the
120
+ supplied transfer currency and support mixed-currency documents.
121
+ - DATEV-specific acceptance is governed by DATEV's supported schema versions,
122
+ not by the gem alone.
123
+
124
+ Supported input data:
125
+
126
+ - Debtor and creditor names
127
+ - Debtor and creditor IBANs
128
+ - Optional debtor and creditor BICs
129
+ - End-to-end IDs
130
+ - Optional instruction IDs
131
+ - Optional purpose codes
132
+ - Optional transfer currencies, defaulting to `EUR`
133
+ - Unstructured remittance information
134
+ - Validation against bundled XSDs for both supported versions
135
+
136
+ Not modeled:
137
+
138
+ - Structured remittance information
139
+ - Postal addresses and richer party identification
140
+ - Ultimate debtor and ultimate creditor
141
+ - Multiple `PmtInf` sections in one document
142
+ - Non-SEPA service levels
143
+ - Broader ISO 20022 branches outside the current DATEV-oriented subset
144
+
106
145
  ## Public API
107
146
 
108
147
  The main public classes are:
109
148
 
110
- - `Brot::Pain00100112::Account`
111
- - `Brot::Pain00100112::Transfer`
112
- - `Brot::Pain00100112::Document`
113
- - `Brot::Pain00100112::Schema`
114
- - `Brot::Pain00100112::Schema::Result`
149
+ - `Brot::Account`
150
+ - `Brot::Transfer`
151
+ - `Brot::Document`
152
+ - `Brot::Schema`
153
+ - `Brot::PainVersion`
154
+ - `Brot::Schema::Result`
115
155
 
116
- ### `Brot::Pain00100112::Account`
156
+ ### `Brot::Account`
117
157
 
118
- Use `Account` when you want a standalone object representing debtor or creditor
119
- banking details. You do not need to instantiate it manually for normal usage,
120
- because `Document` and `Transfer` build accounts from `*_iban` and `*_bic`
121
- attributes internally.
158
+ Use `Brot::Account` for standalone debtor or creditor banking details when you
159
+ need it directly. Most callers do not instantiate it manually because
160
+ `Brot::Document` and `Brot::Transfer` build accounts from `*_iban` and `*_bic`
161
+ arguments.
122
162
 
123
163
  Example:
124
164
 
125
165
  ```ruby
126
- account = Brot::Pain00100112::Account.new(
166
+ account = Brot::Account.new(
127
167
  iban: 'DE89370400440532013000',
128
168
  bic: 'COBADEFFXXX'
129
169
  )
130
-
131
- account.iban
132
- # => "DE89370400440532013000"
133
-
134
- account.bic
135
- # => "COBADEFFXXX"
136
170
  ```
137
171
 
138
172
  Attributes:
139
173
 
140
174
  - `iban`
141
- Example: `DE89370400440532013000`
142
- Must be a syntactically valid IBAN. Spaces are removed automatically.
175
+ Validated IBAN with spaces removed.
143
176
  - `bic`
144
- Example: `COBADEFFXXX`
145
- Optional. If omitted, the serializer falls back to `NOTPROVIDED` in the XML.
177
+ Optional validated BIC.
146
178
 
147
- Useful methods:
179
+ Method:
148
180
 
149
181
  - `bic_or_placeholder`
150
- Returns the actual BIC if present, otherwise `NOTPROVIDED`.
182
+ Returns the BIC if present, otherwise `NOTPROVIDED`.
151
183
 
152
- ### `Brot::Pain00100112::Transfer`
184
+ ### `Brot::Transfer`
153
185
 
154
- Use `Transfer` for each outgoing credit transfer inside the payment batch.
186
+ Use `Brot::Transfer` for each outgoing credit transfer inside the batch.
155
187
 
156
188
  Example:
157
189
 
158
190
  ```ruby
159
- transfer = Brot::Pain00100112::Transfer.new(
191
+ transfer = Brot::Transfer.new(
160
192
  amount: '1250.50',
193
+ currency: 'USD',
161
194
  creditor_name: 'Example Supplier GmbH',
162
195
  creditor_iban: 'DE89370400440532013000',
163
196
  creditor_bic: 'COBADEFFXXX',
@@ -171,254 +204,121 @@ transfer = Brot::Pain00100112::Transfer.new(
171
204
  Initialization attributes:
172
205
 
173
206
  - `amount`
174
- Example: `'1250.50'`
175
- Required. Must be positive and have at most two decimal places.
207
+ Required. Positive, maximum two decimal places.
176
208
  - `creditor_name`
177
- Example: `'Example Supplier GmbH'`
178
- Required. Maximum length is 70 characters in this implementation.
209
+ Required. Maximum 70 characters.
179
210
  - `creditor_iban`
180
- Example: `'DE89370400440532013000'`
181
211
  Required. Validated as an IBAN.
182
212
  - `creditor_bic`
183
- Example: `'COBADEFFXXX'`
184
213
  Optional. Validated as a BIC if supplied.
214
+ - `currency`
215
+ Optional. Three-letter ISO-style currency code. Defaults to `EUR`.
185
216
  - `end_to_end_id`
186
- Example: `'INV-2026-0001'`
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`
217
+ Required. Maximum 35 characters.
204
218
  - `remittance_information`
219
+ Required. Maximum 140 characters.
205
220
  - `instruction_id`
221
+ Optional. Maximum 35 characters.
206
222
  - `purpose_code`
223
+ Optional. Maximum 4 characters.
207
224
 
208
- ### `Brot::Pain00100112::Document`
225
+ ### `Brot::Document`
209
226
 
210
- Use `Document` to represent the full payment file. This is the main entry
211
- point of the gem.
227
+ Use `Brot::Document` for the full payment file.
212
228
 
213
- Example:
229
+ Example using generic `.12` output:
214
230
 
215
231
  ```ruby
216
- document = Brot::Pain00100112::Document.new(
232
+ document = Brot::Document.new(
217
233
  message_id: 'MSG-20260313-01',
218
234
  payment_information_id: 'PMT-20260313-01',
219
235
  initiating_party_name: 'Example Debtor GmbH',
220
236
  debtor_name: 'Example Debtor GmbH',
221
237
  debtor_iban: 'DE12500105170648489890',
222
- debtor_bic: 'INGDDEFFXXX',
223
238
  requested_execution_date: Date.new(2026, 3, 13),
224
239
  transfers: [transfer],
225
- batch_booking: true,
226
- created_at: Time.utc(2026, 3, 13, 12, 0, 0)
240
+ version: Brot::PainVersion::PAIN_001_001_12
227
241
  )
228
242
  ```
229
243
 
230
244
  Initialization attributes:
231
245
 
232
246
  - `message_id`
233
- Example: `'MSG-20260313-01'`
234
- Required. Group header message identifier. Maximum length is 35 characters.
247
+ Required. Maximum 35 characters.
235
248
  - `payment_information_id`
236
- Example: `'PMT-20260313-01'`
237
- Required. Payment information block identifier. Maximum length is 35
238
- characters.
249
+ Required. Maximum 35 characters.
239
250
  - `initiating_party_name`
240
- Example: `'Example Debtor GmbH'`
241
- Required. Maximum length is 70 characters.
251
+ Required. Maximum 70 characters.
242
252
  - `debtor_name`
243
- Example: `'Example Debtor GmbH'`
244
- Required. Maximum length is 70 characters.
253
+ Required. Maximum 70 characters.
245
254
  - `debtor_iban`
246
- Example: `'DE12500105170648489890'`
247
255
  Required. Validated as an IBAN.
248
256
  - `debtor_bic`
249
- Example: `'INGDDEFFXXX'`
250
257
  Optional. Validated as a BIC if supplied.
251
258
  - `requested_execution_date`
252
- Example: `Date.new(2026, 3, 13)` or `'2026-03-13'`
253
- Required. Date only.
259
+ Required. `Date` or ISO 8601 date string.
254
260
  - `transfers`
255
- Example: `[transfer_1, transfer_2]`
256
- Required. Must contain at least one `Brot::Pain00100112::Transfer`.
261
+ Required. Must contain at least one `Brot::Transfer`.
257
262
  - `batch_booking`
258
- Example: `true`
259
263
  Optional. Defaults to `true`.
260
264
  - `created_at`
261
- Example: `Time.utc(2026, 3, 13, 12, 0, 0)`
262
- Optional. Defaults to the current UTC time.
263
-
264
- Readable attributes after initialization:
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`
265
+ Optional. Defaults to current UTC time.
266
+ - `version`
267
+ Optional. Defaults to `Brot::PainVersion::PAIN_001_001_03`.
268
+ Supported values are `Brot::PainVersion::PAIN_001_001_03` and
269
+ `Brot::PainVersion::PAIN_001_001_12`.
275
270
 
276
271
  Useful methods:
277
272
 
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
273
  - `control_sum`
283
- Returns the sum of all transfer amounts as a `BigDecimal`.
284
274
  - `number_of_transactions`
285
- Returns the number of transfers.
286
-
287
- ### `Brot::Pain00100112::Schema`
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)
275
+ - `to_xml(pretty: true)`
276
+ - `validate(xsd_path: nil)`
277
+ - `validate!(xsd_path: nil)`
297
278
 
298
- raise result.errors.join("\n") unless result.valid?
299
- ```
279
+ Version-specific output differences:
300
280
 
301
- Available methods:
281
+ - `pain.001.001.03` uses namespace
282
+ `urn:iso:std:iso:20022:tech:xsd:pain.001.001.03`
283
+ - `pain.001.001.12` uses namespace
284
+ `urn:iso:std:iso:20022:tech:xsd:pain.001.001.12`
285
+ - `.03` writes `BIC` and plain `ReqdExctnDt`
286
+ - `.12` writes `BICFI` and `ReqdExctnDt/Dt`
287
+ - For DATEV compatibility today, prefer `.03`
302
288
 
303
- - `bundled_xsd_path`
304
- Returns the absolute path of the XSD bundled inside the gem.
305
- - `validate(xml, xsd_path: bundled_xsd_path)`
306
- Validates an XML string against the bundled schema by default.
289
+ ### `Brot::Schema`
307
290
 
308
- ### `Brot::Pain00100112::Schema::Result`
291
+ Use `Brot::Schema` when you want to validate XML directly.
309
292
 
310
- `Schema.validate` and `Document#validate` both return a
311
- `Brot::Pain00100112::Schema::Result`.
312
-
313
- Example:
293
+ Examples:
314
294
 
315
295
  ```ruby
316
- result = document.validate
317
-
296
+ result = Brot::Schema.validate(xml)
318
297
  result.valid?
319
- # => true
320
-
321
- result.errors
322
- # => []
323
298
  ```
324
299
 
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
300
  ```ruby
338
- require 'brot'
339
- require 'date'
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
301
+ path = Brot::Schema.bundled_xsd_path(Brot::PainVersion::PAIN_001_001_12)
302
+ result = Brot::Schema.validate(xml, xsd_path: path)
376
303
  ```
377
304
 
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
305
  ```ruby
384
- result = document.validate
385
-
386
- raise result.errors.join("\n") unless result.valid?
306
+ Brot::Schema.validate!(xml)
387
307
  ```
388
308
 
389
- If you want to override the schema file, pass `xsd_path:` explicitly.
309
+ Methods:
390
310
 
391
- ## Error Handling
311
+ - `bundled_xsd_path(version)`
312
+ - `validate(xml, version: nil, xsd_path: nil)`
313
+ - `validate!(xml, version: nil, xsd_path: nil)`
392
314
 
393
- Invalid input values raise `Brot::Pain00100112::ValidationError`.
315
+ If you omit both `version:` and `xsd_path:`, the gem infers the bundled schema
316
+ from the XML document namespace.
394
317
 
395
- Typical examples:
318
+ ### `Brot::PainVersion`
396
319
 
397
- - Invalid IBAN
398
- - Invalid BIC
399
- - Empty required text fields
400
- - Amounts that are zero, negative, or have too many decimal places
401
- - Missing transfers
320
+ `Brot::PainVersion` is the public value object for supported output formats.
321
+ Prefer these constants when constructing documents or looking up bundled XSDs:
402
322
 
403
- Example:
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
- ```
323
+ - `Brot::PainVersion::PAIN_001_001_03`
324
+ - `Brot::PainVersion::PAIN_001_001_12`
data/brot.gemspec CHANGED
@@ -10,8 +10,8 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = 'Build ISO 20022 pain.001 payment initiation XML for DATEV uploads.'
12
12
  spec.description = <<~TEXT
13
- brot builds SEPA credit transfer XML with Nokogiri::XML::Builder.
14
- The output targets pain.001.001.12 for DATEV-oriented uploads.
13
+ brot builds ISO 20022 credit transfer XML with Nokogiri::XML::Builder.
14
+ The output targets pain.001.001.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'
@@ -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