factur-x-builder 0.1.0 → 0.1.2
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/.gitignore +1 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +1 -1
- data/README.md +67 -0
- data/lib/factur_x/codes.rb +20 -1
- data/lib/factur_x/document.rb +1 -0
- data/lib/factur_x/line.rb +5 -4
- data/lib/factur_x/party.rb +9 -1
- data/lib/factur_x/schemas/Factur-X_1.09_EN16931_ReusableAggregateBusinessInformationEntity_100.xsd +2 -2
- data/lib/factur_x/schemas/Factur-X_EN16931.xsd +3 -3
- data/lib/factur_x/validation/rules.rb +45 -1
- data/lib/factur_x/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2f380d59a7b981bd4999314d3321a99dc762ac9c67709f6e543cc5632eade2c4
|
|
4
|
+
data.tar.gz: 553e1dda58b31b9804ede1acc9c75ff4ee515b1bcf152a508fb56bca7ebf2357
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 54363eb24098df15598c699e48956247464bc739435af190736762e0707b77513f98e12b836db743e832b98672ef989fc225833c08ab06402b391bd10474f83d
|
|
7
|
+
data.tar.gz: 71355fc896353819a7d66da2b3d1fb078f251184858add396e3d25bc6475535c6eb7593ffeb6ec23b48fb4900e9fe8a76440b8576e10dc20173c41e556461223
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
|
+
## [0.1.2] - 2026-08-15
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- `Codes::PaymentMeansType::ALL` lists the 84 UNTDID 4461 codes EN 16931 permits for BT-81, and a
|
|
6
|
+
payment means outside that list is now rejected instead of producing XML the schematron refuses.
|
|
7
|
+
More codes are named (`PAYMENT_TO_BANK_ACCOUNT`, `CREDIT_CARD`, `ONLINE_PAYMENT_SERVICE`,
|
|
8
|
+
`MUTUALLY_DEFINED`, …); the constants are a convenience, not the permitted set.
|
|
9
|
+
- BT-87 is checked against BR-51, which caps a card number at 10 characters — the first 6 and last
|
|
10
|
+
4 digits PCI DSS allows to be displayed. Masking the middle with asterisks overflows that limit
|
|
11
|
+
and was previously emitted, producing XML the schematron refuses.
|
|
12
|
+
- Unit prices are checked against BR-27 and BR-28: a negative BT-146 or BT-148 is rejected with a
|
|
13
|
+
message pointing at credit notes, which use type code 381 with positive amounts.
|
|
14
|
+
|
|
15
|
+
## [0.1.1] - 2026-08-10
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- The vendored XSD files were renamed without updating the `schemaLocation` of the files that
|
|
20
|
+
import them, which made every `FacturX.build` raise `Nokogiri::XML::SyntaxError`. A test now
|
|
21
|
+
checks that the schema compiles and that every `schemaLocation` resolves to an existing file.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- `Party#legal_information` (BT-33, seller additional legal information), emitted as
|
|
26
|
+
`SellerTradeParty/Description`: legal form, share capital, RCS registration, or a registered
|
|
27
|
+
office that differs from the invoicing establishment. EN 16931 allows a single postal address
|
|
28
|
+
per party, so this is the only structured place for a second one.
|
|
29
|
+
|
|
1
30
|
## [0.1.0] - 2026-08-05
|
|
2
31
|
|
|
3
32
|
- Initial release
|
|
33
|
+
|
|
4
34
|
### Added
|
|
5
35
|
|
|
6
36
|
- `FacturX.build` generates an EN 16931 `factur-x.xml` document from invoicing domain objects:
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -112,6 +112,73 @@ FacturX.violations(invoice) # => ["...", "..."]
|
|
|
112
112
|
FacturX.valid?(invoice) # => false
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
### Payment states
|
|
116
|
+
|
|
117
|
+
An issued invoice is immutable. Payments received **after** issuance are not written back into it —
|
|
118
|
+
they are reported to the platform as lifecycle statuses (`Encaissée`), a separate message flow that
|
|
119
|
+
this gem does not produce and is not meant to.
|
|
120
|
+
|
|
121
|
+
What the invoice records is the state **at issuance**, through BT-113 and BT-115:
|
|
122
|
+
|
|
123
|
+
| Situation | `prepaid` (BT-113) | `due_payable` (BT-115) | `type_code` |
|
|
124
|
+
|---|---|---|---|
|
|
125
|
+
| Nothing paid | omitted | grand total | `380` |
|
|
126
|
+
| Paid before issuance | grand total | `0.00` | `380` |
|
|
127
|
+
| Deposit collected before issuance | the deposit | the balance | `380` |
|
|
128
|
+
| Cancellation | omitted | amount to refund | `381` + `preceding_invoices` |
|
|
129
|
+
|
|
130
|
+
`BR-CO-16` ties them together — `BT-115 = BT-112 − BT-113 + BT-114` — and is checked before
|
|
131
|
+
anything is generated.
|
|
132
|
+
|
|
133
|
+
A credit note carries **positive** amounts; the type code conveys the direction. Negative unit
|
|
134
|
+
prices are rejected (`BR-27`, `BR-28`).
|
|
135
|
+
|
|
136
|
+
### Payment instructions are optional
|
|
137
|
+
|
|
138
|
+
`payment_means` maps to BG-16 *Payment instructions*, which both schematrons accept as absent. It
|
|
139
|
+
states how the seller **expects** to be paid — it is not a record of the buyer's choice, since the
|
|
140
|
+
invoice precedes the payment. Several means may be declared at once.
|
|
141
|
+
|
|
142
|
+
Once a means is declared, the rules apply: a credit transfer (`30`, `58`) requires an account
|
|
143
|
+
identifier (BR-61, BR-CO-27), and the code must belong to UNTDID 4461 —
|
|
144
|
+
`Codes::PaymentMeansType::ALL` holds the 84 permitted values, checked against the code database the
|
|
145
|
+
official schematron uses. The named constants (`CREDIT_TRANSFER`, `BANK_CARD`, …) cover the common
|
|
146
|
+
cases; any other code can be passed as a plain string.
|
|
147
|
+
|
|
148
|
+
A platform may narrow the list further. Chorus Pro accepts only `30`, `42`, `48`, `49`, `58`, `59`
|
|
149
|
+
and `97` for the public sector — a restriction no schematron encodes, so the gem cannot catch it.
|
|
150
|
+
|
|
151
|
+
Nothing downstream contradicts it. The `Encaissée` lifecycle status reports only the invoice number,
|
|
152
|
+
the payment date and the amount collected per VAT rate — the means actually used is not among the
|
|
153
|
+
data transmitted, so a buyer paying by cheque against an invoice quoting a transfer raises no
|
|
154
|
+
compliance issue.
|
|
155
|
+
|
|
156
|
+
### One address per party
|
|
157
|
+
|
|
158
|
+
EN 16931 gives each party exactly one `PostalTradeAddress`, and it must be the address of the
|
|
159
|
+
establishment identified by the SIRET carried in `GlobalID`. There is no second slot: the element
|
|
160
|
+
that would hold a registered office, `SpecifiedLegalOrganization/PostalTradeAddress`, exists in the
|
|
161
|
+
`extended` XSD but the French CTC schematron forbids it (`CII-SR-225`), as it forbids
|
|
162
|
+
`ShipFromTradeParty` (`CII-SR-166`).
|
|
163
|
+
|
|
164
|
+
When the registered office differs from the invoicing establishment, it belongs in BT-33:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
FacturX::Party.new(
|
|
168
|
+
name: "OFFICE DE TOURISME DES MONTS FICTIFS - Établissement de Chambéry",
|
|
169
|
+
legal_information: "SARL au capital de 50 000 € - RCS Clermont-Ferrand 398 765 438 - " \
|
|
170
|
+
"Siège social : 4 boulevard des Sources, 63240 LE MONT-FICTIF",
|
|
171
|
+
siret: "39876543800005",
|
|
172
|
+
siren: "398765438",
|
|
173
|
+
vat_number: "FR79398765438",
|
|
174
|
+
address: FacturX::Address.new(line_one: "8 rue des Entrepôts", postcode: "73000",
|
|
175
|
+
city: "Chambéry", country_code: "FR")
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
BT-33 is free text — the counterpart of `cbc:CompanyLegalForm` in UBL. Receivers display it, they do
|
|
180
|
+
not parse it.
|
|
181
|
+
|
|
115
182
|
### Profiles
|
|
116
183
|
|
|
117
184
|
By default the French e-invoicing rules apply on top of EN 16931: an invoicing mode (BT-23) and the
|
data/lib/factur_x/codes.rb
CHANGED
|
@@ -37,15 +37,34 @@ module FacturX
|
|
|
37
37
|
REQUIRING_EXEMPTION_REASON = [EXEMPT, REVERSE_CHARGE, INTRA_COMMUNITY, EXPORT].freeze
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# NOTE: Chorus Pro only accepts 30, 42, 48, 49, 58, 59 and 97
|
|
40
41
|
module PaymentMeansType
|
|
42
|
+
NOT_DEFINED = "1"
|
|
41
43
|
CASH = "10"
|
|
42
44
|
CHEQUE = "20"
|
|
43
45
|
CREDIT_TRANSFER = "30"
|
|
46
|
+
PAYMENT_TO_BANK_ACCOUNT = "42"
|
|
47
|
+
# The instrument, whatever the channel: a card read on a physical
|
|
48
|
+
# terminal and a card entered on a web page are both 48.
|
|
44
49
|
BANK_CARD = "48"
|
|
45
50
|
DIRECT_DEBIT = "49"
|
|
51
|
+
CREDIT_CARD = "54"
|
|
52
|
+
DEBIT_CARD = "55"
|
|
53
|
+
STANDING_AGREEMENT = "57"
|
|
46
54
|
SEPA_CREDIT_TRANSFER = "58"
|
|
47
55
|
SEPA_DIRECT_DEBIT = "59"
|
|
48
|
-
|
|
56
|
+
# The service is itself the instrument - a wallet or a provider that
|
|
57
|
+
# collects in its own name. A card payment through a payment page is
|
|
58
|
+
# still BANK_CARD, not this.
|
|
59
|
+
ONLINE_PAYMENT_SERVICE = "68"
|
|
60
|
+
CLEARING_BETWEEN_PARTNERS = "97"
|
|
61
|
+
MUTUALLY_DEFINED = "ZZZ"
|
|
62
|
+
|
|
63
|
+
# The complete UNTDID 4461 subset of the EN 16931 code list, as published
|
|
64
|
+
# in the Factur-X code database used by the official schematron.
|
|
65
|
+
ALL = (
|
|
66
|
+
(1..70).to_a + (74..78).to_a + (91..98).to_a
|
|
67
|
+
).map(&:to_s).push("ZZZ").freeze
|
|
49
68
|
|
|
50
69
|
REQUIRING_CREDITOR_ACCOUNT = [CREDIT_TRANSFER, SEPA_CREDIT_TRANSFER].freeze
|
|
51
70
|
REQUIRING_MANDATE = [DIRECT_DEBIT, SEPA_DIRECT_DEBIT].freeze
|
data/lib/factur_x/document.rb
CHANGED
|
@@ -306,6 +306,7 @@ module FacturX
|
|
|
306
306
|
xml["ram"].send(element_name) do
|
|
307
307
|
text(xml, :GlobalID, party.global_id, scheme_attribute(party.global_id_scheme))
|
|
308
308
|
text(xml, :Name, party.name)
|
|
309
|
+
text(xml, :Description, party.legal_information)
|
|
309
310
|
emit_legal_organization(xml, party)
|
|
310
311
|
emit_contact(xml, party)
|
|
311
312
|
emit_address(xml, party.address)
|
data/lib/factur_x/line.rb
CHANGED
|
@@ -7,7 +7,7 @@ module FacturX
|
|
|
7
7
|
class Line
|
|
8
8
|
attr_reader :number, :name, :description, :seller_item_id, :buyer_item_id,
|
|
9
9
|
:global_id, :global_id_scheme, :unit_price, :gross_unit_price,
|
|
10
|
-
:unit_discount, :quantity, :unit_code, :vat_category, :vat_rate,
|
|
10
|
+
:unit_discount, :quantity, :unit_code, :vat_category, :vat_rate, :vat_amount,
|
|
11
11
|
:net_amount, :note, :origin_country, :period_start, :period_end,
|
|
12
12
|
:order_line_id, :accounting_reference
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@ module FacturX
|
|
|
15
15
|
number: nil, description: nil, seller_item_id: nil, buyer_item_id: nil,
|
|
16
16
|
global_id: nil, global_id_scheme: nil, gross_unit_price: nil,
|
|
17
17
|
unit_discount: nil, unit_code: Codes::PIECE_UNIT,
|
|
18
|
-
vat_category: Codes::VatCategory::STANDARD, vat_rate: nil,
|
|
18
|
+
vat_category: Codes::VatCategory::STANDARD, vat_rate: nil, vat_amount: nil,
|
|
19
19
|
note: nil, origin_country: nil, period_start: nil, period_end: nil,
|
|
20
20
|
order_line_id: nil, accounting_reference: nil)
|
|
21
21
|
@number = number
|
|
@@ -32,6 +32,7 @@ module FacturX
|
|
|
32
32
|
@unit_code = unit_code
|
|
33
33
|
@vat_category = vat_category
|
|
34
34
|
@vat_rate = vat_rate
|
|
35
|
+
@vat_amount = vat_amount
|
|
35
36
|
@net_amount = net_amount
|
|
36
37
|
@note = note
|
|
37
38
|
@origin_country = origin_country
|
|
@@ -72,8 +73,8 @@ module FacturX
|
|
|
72
73
|
global_id: global_id, global_id_scheme: global_id_scheme,
|
|
73
74
|
unit_price: unit_price, gross_unit_price: gross_unit_price,
|
|
74
75
|
unit_discount: unit_discount, quantity: quantity, unit_code: unit_code,
|
|
75
|
-
vat_category: vat_category, vat_rate: vat_rate,
|
|
76
|
-
note: note, origin_country: origin_country,
|
|
76
|
+
vat_category: vat_category, vat_rate: vat_rate, vat_amount: vat_amount,
|
|
77
|
+
net_amount: net_amount, note: note, origin_country: origin_country,
|
|
77
78
|
period_start: period_start, period_end: period_end,
|
|
78
79
|
order_line_id: order_line_id, accounting_reference: accounting_reference
|
|
79
80
|
}
|
data/lib/factur_x/party.rb
CHANGED
|
@@ -9,18 +9,26 @@ module FacturX
|
|
|
9
9
|
# (scheme 0009), `siren` the legal organization ID (scheme 0002), and
|
|
10
10
|
# `routing_id` the electronic address (scheme 0225) used to route the
|
|
11
11
|
# invoice through the PPF, defaulting to the SIRET.
|
|
12
|
+
#
|
|
13
|
+
# `legal_information` is BT-33, the seller's additional legal information:
|
|
14
|
+
# legal form, share capital, RCS registration, and any other statement the
|
|
15
|
+
# printed invoice must carry. A party holds a single postal address, so this
|
|
16
|
+
# is also where a registered office that differs from the invoicing
|
|
17
|
+
# establishment belongs.
|
|
12
18
|
class Party
|
|
13
|
-
attr_reader :name, :trading_name, :siret, :siren, :vat_number, :tax_id,
|
|
19
|
+
attr_reader :name, :trading_name, :legal_information, :siret, :siren, :vat_number, :tax_id,
|
|
14
20
|
:address, :contact, :global_id, :global_id_scheme,
|
|
15
21
|
:legal_id, :legal_id_scheme, :routing_id, :routing_id_scheme
|
|
16
22
|
|
|
17
23
|
def initialize(name:, address: nil, contact: nil, trading_name: nil,
|
|
24
|
+
legal_information: nil,
|
|
18
25
|
siret: nil, siren: nil, vat_number: nil, tax_id: nil,
|
|
19
26
|
global_id: nil, global_id_scheme: nil,
|
|
20
27
|
legal_id: nil, legal_id_scheme: nil,
|
|
21
28
|
routing_id: :derive_from_siret, routing_id_scheme: nil)
|
|
22
29
|
@name = name
|
|
23
30
|
@trading_name = trading_name
|
|
31
|
+
@legal_information = legal_information
|
|
24
32
|
@address = address
|
|
25
33
|
@contact = contact
|
|
26
34
|
@siret = siret
|
data/lib/factur_x/schemas/Factur-X_1.09_EN16931_ReusableAggregateBusinessInformationEntity_100.xsd
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
|
|
6
6
|
targetNamespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
|
|
7
7
|
elementFormDefault="qualified">
|
|
8
|
-
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_1.
|
|
9
|
-
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_1.
|
|
8
|
+
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_1.09_EN16931_QualifiedDataType_100.xsd"/>
|
|
9
|
+
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_1.09_EN16931_UnqualifiedDataType_100.xsd"/>
|
|
10
10
|
<xs:complexType name="CreditorFinancialAccountType">
|
|
11
11
|
<xs:sequence>
|
|
12
12
|
<xs:element name="IBANID" type="udt:IDType" minOccurs="0"/>
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
|
|
7
7
|
targetNamespace="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
|
8
8
|
elementFormDefault="qualified">
|
|
9
|
-
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_1.
|
|
10
|
-
<xs:import namespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" schemaLocation="Factur-X_1.
|
|
11
|
-
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_1.
|
|
9
|
+
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_1.09_EN16931_QualifiedDataType_100.xsd"/>
|
|
10
|
+
<xs:import namespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" schemaLocation="Factur-X_1.09_EN16931_ReusableAggregateBusinessInformationEntity_100.xsd"/>
|
|
11
|
+
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_1.09_EN16931_UnqualifiedDataType_100.xsd"/>
|
|
12
12
|
<xs:element name="CrossIndustryInvoice" type="rsm:CrossIndustryInvoiceType"/>
|
|
13
13
|
<xs:complexType name="CrossIndustryInvoiceType">
|
|
14
14
|
<xs:sequence>
|
|
@@ -22,6 +22,8 @@ module FacturX
|
|
|
22
22
|
|
|
23
23
|
ROUNDING_TOLERANCE = BigDecimal("0.01")
|
|
24
24
|
|
|
25
|
+
CARD_NUMBER_MAX_LENGTH = 10
|
|
26
|
+
|
|
25
27
|
PROFILES = %i[en16931 fr_ctc].freeze
|
|
26
28
|
|
|
27
29
|
# BR-FR-05: every invoice must carry these three statements.
|
|
@@ -174,7 +176,25 @@ module FacturX
|
|
|
174
176
|
add("#{label}: name is required (BT-153)") if blank?(line.name)
|
|
175
177
|
add("#{label}: unit code is required (BT-130)") if blank?(line.unit_code)
|
|
176
178
|
check_vat_category(line.vat_category, line.vat_rate, label)
|
|
179
|
+
check_unit_prices(line, label)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# A credit note carries positive amounts: the document type code (BT-3)
|
|
184
|
+
# conveys the direction, not the sign of the prices.
|
|
185
|
+
def check_unit_prices(line, label)
|
|
186
|
+
if negative?(line.unit_price)
|
|
187
|
+
add("#{label}: unit price must not be negative (BT-146, BR-27) - a credit note uses " \
|
|
188
|
+
"type code #{Codes::CREDIT_NOTE} with positive amounts")
|
|
177
189
|
end
|
|
190
|
+
|
|
191
|
+
return unless negative?(line.gross_unit_price)
|
|
192
|
+
|
|
193
|
+
add("#{label}: gross unit price must not be negative (BT-148, BR-28)")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def negative?(value)
|
|
197
|
+
!value.nil? && Formatting.decimal(value).negative?
|
|
178
198
|
end
|
|
179
199
|
|
|
180
200
|
def check_vat_category(category, rate, label)
|
|
@@ -274,6 +294,9 @@ module FacturX
|
|
|
274
294
|
[item.vat_category, Formatting.decimal(item.vat_rate || 0)]
|
|
275
295
|
end
|
|
276
296
|
|
|
297
|
+
# Payment instructions (BG-16) are optional: an invoice with no payment
|
|
298
|
+
# means at all satisfies both schematrons. What is checked here applies
|
|
299
|
+
# only once a payment means has been declared.
|
|
277
300
|
def check_payment
|
|
278
301
|
invoice.payment_means.each do |means|
|
|
279
302
|
if blank?(means.type_code)
|
|
@@ -281,18 +304,39 @@ module FacturX
|
|
|
281
304
|
next
|
|
282
305
|
end
|
|
283
306
|
|
|
307
|
+
unless Codes::PaymentMeansType::ALL.include?(means.type_code.to_s)
|
|
308
|
+
add("payment means #{means.type_code.inspect} is not a UNTDID 4461 code (BT-81)")
|
|
309
|
+
next
|
|
310
|
+
end
|
|
311
|
+
|
|
284
312
|
if Codes::PaymentMeansType::REQUIRING_CREDITOR_ACCOUNT.include?(means.type_code) &&
|
|
285
313
|
!means.creditor_account?
|
|
286
314
|
add("payment means #{means.type_code} (credit transfer) requires a creditor " \
|
|
287
|
-
"account identifier (BR-61)")
|
|
315
|
+
"account identifier - an IBAN or a proprietary account id (BR-61, BR-CO-27)")
|
|
288
316
|
end
|
|
289
317
|
|
|
290
318
|
if means.iban && !means.iban.to_s.delete(" ").match?(IBAN_PATTERN)
|
|
291
319
|
add("payment IBAN #{means.iban.inspect} is not a well-formed IBAN")
|
|
292
320
|
end
|
|
321
|
+
|
|
322
|
+
check_card_number(means)
|
|
293
323
|
end
|
|
294
324
|
end
|
|
295
325
|
|
|
326
|
+
# BR-51 caps BT-87 at 10 characters, which is what PCI DSS allows to be
|
|
327
|
+
# displayed: the first 6 and last 4 digits. Masking the middle with
|
|
328
|
+
# asterisks overflows that limit and is rejected.
|
|
329
|
+
def check_card_number(means)
|
|
330
|
+
return if means.card_id.nil?
|
|
331
|
+
|
|
332
|
+
length = means.card_id.to_s.strip.length
|
|
333
|
+
return if length <= CARD_NUMBER_MAX_LENGTH
|
|
334
|
+
|
|
335
|
+
add("payment card number is #{length} characters; an invoice must never carry a full " \
|
|
336
|
+
"card number (BT-87, BR-51). Keep at most #{CARD_NUMBER_MAX_LENGTH} characters - " \
|
|
337
|
+
"the first 6 and last 4 digits, without masking characters")
|
|
338
|
+
end
|
|
339
|
+
|
|
296
340
|
def check_totals
|
|
297
341
|
totals = invoice.totals
|
|
298
342
|
if totals.nil?
|
data/lib/factur_x/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: factur-x-builder
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hotentic
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|