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 +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/.github/workflows/release.yml +2 -2
- data/.rubocop.yml +5 -0
- data/README.md +169 -269
- data/brot.gemspec +2 -2
- 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/serializers/pain00100103.rb +22 -0
- data/lib/brot/{pain00100112/serializer.rb → serializers/pain00100112.rb} +3 -6
- data/lib/brot/transfer.rb +45 -0
- data/lib/brot/utils.rb +91 -0
- data/lib/brot/version.rb +1 -1
- data/lib/brot.rb +10 -1
- data/test/currency_support_test.rb +102 -0
- data/test/document_test.rb +155 -0
- data/test/transfer_test.rb +33 -0
- data/xsd/pain.001.001.03.xsd +921 -0
- metadata +16 -12
- 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: 11ae2c0176f01320f17c5755596448f27c35b258c911ade2f6e4f4fba2096273
|
|
4
|
+
data.tar.gz: b985c0fdb460926d4c30c26c9c47f1815a061545ae1fac068f8d6584bd0c1fdd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab2e95235f9d24a157076fee29ceb68499c845a43d4517c0f3a6ef2dbc0f233d7c7f4990ba443487c6d5b8e1c4a0cc00ad12415455d11c60fe6d197306ca0f53
|
|
7
|
+
data.tar.gz: 3664f7b067f59f1c62a7013e117b8521560e169cda40b3713eaba6178bd3e951dd178f3e6216621b38f4e984fab7ceddbc99b49a49f31b20fcff83cd98abe441
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -10,7 +10,7 @@ jobs:
|
|
|
10
10
|
runs-on: ubuntu-latest
|
|
11
11
|
|
|
12
12
|
steps:
|
|
13
|
-
- uses: actions/checkout@
|
|
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@
|
|
36
|
+
- uses: actions/checkout@v6
|
|
37
37
|
|
|
38
38
|
- uses: ruby/setup-ruby@v1
|
|
39
39
|
with:
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
|
@@ -1,73 +1,63 @@
|
|
|
1
1
|
# brot
|
|
2
2
|
|
|
3
|
-
`brot` builds ISO 20022
|
|
3
|
+
`brot` builds ISO 20022 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
|
-
|
|
8
|
+
- `pain.001.001.03`
|
|
9
|
+
- `pain.001.001.12`
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
+
## DATEV Compatibility
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
56
|
+
## Installation
|
|
66
57
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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::
|
|
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::
|
|
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
|
-
|
|
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::
|
|
111
|
-
- `Brot::
|
|
112
|
-
- `Brot::
|
|
113
|
-
- `Brot::
|
|
114
|
-
- `Brot::
|
|
149
|
+
- `Brot::Account`
|
|
150
|
+
- `Brot::Transfer`
|
|
151
|
+
- `Brot::Document`
|
|
152
|
+
- `Brot::Schema`
|
|
153
|
+
- `Brot::PainVersion`
|
|
154
|
+
- `Brot::Schema::Result`
|
|
115
155
|
|
|
116
|
-
### `Brot::
|
|
156
|
+
### `Brot::Account`
|
|
117
157
|
|
|
118
|
-
Use `Account`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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::
|
|
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
|
-
|
|
142
|
-
Must be a syntactically valid IBAN. Spaces are removed automatically.
|
|
175
|
+
Validated IBAN with spaces removed.
|
|
143
176
|
- `bic`
|
|
144
|
-
|
|
145
|
-
Optional. If omitted, the serializer falls back to `NOTPROVIDED` in the XML.
|
|
177
|
+
Optional validated BIC.
|
|
146
178
|
|
|
147
|
-
|
|
179
|
+
Method:
|
|
148
180
|
|
|
149
181
|
- `bic_or_placeholder`
|
|
150
|
-
Returns the
|
|
182
|
+
Returns the BIC if present, otherwise `NOTPROVIDED`.
|
|
151
183
|
|
|
152
|
-
### `Brot::
|
|
184
|
+
### `Brot::Transfer`
|
|
153
185
|
|
|
154
|
-
Use `Transfer` for each outgoing credit transfer inside the
|
|
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::
|
|
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
|
-
|
|
175
|
-
Required. Must be positive and have at most two decimal places.
|
|
207
|
+
Required. Positive, maximum two decimal places.
|
|
176
208
|
- `creditor_name`
|
|
177
|
-
|
|
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
|
-
|
|
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::
|
|
225
|
+
### `Brot::Document`
|
|
209
226
|
|
|
210
|
-
Use `Document`
|
|
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::
|
|
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
|
-
|
|
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
|
-
|
|
234
|
-
Required. Group header message identifier. Maximum length is 35 characters.
|
|
247
|
+
Required. Maximum 35 characters.
|
|
235
248
|
- `payment_information_id`
|
|
236
|
-
|
|
237
|
-
Required. Payment information block identifier. Maximum length is 35
|
|
238
|
-
characters.
|
|
249
|
+
Required. Maximum 35 characters.
|
|
239
250
|
- `initiating_party_name`
|
|
240
|
-
|
|
241
|
-
Required. Maximum length is 70 characters.
|
|
251
|
+
Required. Maximum 70 characters.
|
|
242
252
|
- `debtor_name`
|
|
243
|
-
|
|
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
|
-
|
|
253
|
-
Required. Date only.
|
|
259
|
+
Required. `Date` or ISO 8601 date string.
|
|
254
260
|
- `transfers`
|
|
255
|
-
|
|
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
|
-
|
|
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`
|
|
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
|
-
|
|
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)
|
|
275
|
+
- `to_xml(pretty: true)`
|
|
276
|
+
- `validate(xsd_path: nil)`
|
|
277
|
+
- `validate!(xsd_path: nil)`
|
|
297
278
|
|
|
298
|
-
|
|
299
|
-
```
|
|
279
|
+
Version-specific output differences:
|
|
300
280
|
|
|
301
|
-
|
|
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
|
-
|
|
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
|
-
|
|
291
|
+
Use `Brot::Schema` when you want to validate XML directly.
|
|
309
292
|
|
|
310
|
-
|
|
311
|
-
`Brot::Pain00100112::Schema::Result`.
|
|
312
|
-
|
|
313
|
-
Example:
|
|
293
|
+
Examples:
|
|
314
294
|
|
|
315
295
|
```ruby
|
|
316
|
-
result =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
raise result.errors.join("\n") unless result.valid?
|
|
306
|
+
Brot::Schema.validate!(xml)
|
|
387
307
|
```
|
|
388
308
|
|
|
389
|
-
|
|
309
|
+
Methods:
|
|
390
310
|
|
|
391
|
-
|
|
311
|
+
- `bundled_xsd_path(version)`
|
|
312
|
+
- `validate(xml, version: nil, xsd_path: nil)`
|
|
313
|
+
- `validate!(xml, version: nil, xsd_path: nil)`
|
|
392
314
|
|
|
393
|
-
|
|
315
|
+
If you omit both `version:` and `xsd_path:`, the gem infers the bundled schema
|
|
316
|
+
from the XML document namespace.
|
|
394
317
|
|
|
395
|
-
|
|
318
|
+
### `Brot::PainVersion`
|
|
396
319
|
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
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
|
|
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'
|
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
|