ksef-client-rails 0.1.0 → 1.0.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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +161 -0
  3. data/NOTICE +40 -0
  4. data/README.md +524 -6
  5. data/examples/fa3_full.xml +143 -0
  6. data/examples/fa3_minimal.xml +65 -0
  7. data/examples/fa3_typical.xml +76 -0
  8. data/lib/generators/ksef/install/install_generator.rb +20 -0
  9. data/lib/generators/ksef/install/templates/ksef.rb +25 -0
  10. data/lib/ksef/auth/coordinator.rb +164 -0
  11. data/lib/ksef/auth/ksef_token_encryptor.rb +36 -0
  12. data/lib/ksef/batch/package_builder.rb +125 -0
  13. data/lib/ksef/batch/splitter.rb +99 -0
  14. data/lib/ksef/batch/uploader.rb +114 -0
  15. data/lib/ksef/client.rb +135 -0
  16. data/lib/ksef/configuration.rb +206 -0
  17. data/lib/ksef/crypto/cryptography_service.rb +136 -0
  18. data/lib/ksef/crypto/csr.rb +106 -0
  19. data/lib/ksef/crypto/public_keys.rb +125 -0
  20. data/lib/ksef/environments.rb +61 -0
  21. data/lib/ksef/errors.rb +187 -0
  22. data/lib/ksef/http/connection.rb +432 -0
  23. data/lib/ksef/http/error_parser.rb +194 -0
  24. data/lib/ksef/http/pagination.rb +80 -0
  25. data/lib/ksef/invoices/builder/checks.rb +127 -0
  26. data/lib/ksef/invoices/builder/number.rb +67 -0
  27. data/lib/ksef/invoices/builder/xml.rb +256 -0
  28. data/lib/ksef/invoices/builder.rb +251 -0
  29. data/lib/ksef/invoices/export.rb +241 -0
  30. data/lib/ksef/invoices/sample.rb +78 -0
  31. data/lib/ksef/invoices/schema_outline.rb +120 -0
  32. data/lib/ksef/invoices/validator.rb +200 -0
  33. data/lib/ksef/models/form_code.rb +49 -0
  34. data/lib/ksef/models/ksef_number.rb +74 -0
  35. data/lib/ksef/models/nip.rb +62 -0
  36. data/lib/ksef/polish_time.rb +49 -0
  37. data/lib/ksef/polling.rb +65 -0
  38. data/lib/ksef/qr/code.rb +104 -0
  39. data/lib/ksef/qr/verification_link.rb +215 -0
  40. data/lib/ksef/railtie.rb +20 -0
  41. data/lib/ksef/resources/auth.rb +116 -0
  42. data/lib/ksef/resources/base.rb +120 -0
  43. data/lib/ksef/resources/batch_sessions.rb +43 -0
  44. data/lib/ksef/resources/certificates.rb +82 -0
  45. data/lib/ksef/resources/collective_identifiers.rb +70 -0
  46. data/lib/ksef/resources/exports.rb +47 -0
  47. data/lib/ksef/resources/invoices.rb +67 -0
  48. data/lib/ksef/resources/limits.rb +33 -0
  49. data/lib/ksef/resources/online_sessions.rb +62 -0
  50. data/lib/ksef/resources/peppol.rb +21 -0
  51. data/lib/ksef/resources/permissions.rb +180 -0
  52. data/lib/ksef/resources/security.rb +18 -0
  53. data/lib/ksef/resources/sessions.rb +125 -0
  54. data/lib/ksef/resources/test_data.rb +178 -0
  55. data/lib/ksef/resources/tokens.rb +62 -0
  56. data/lib/ksef/sessions/online.rb +168 -0
  57. data/lib/ksef/sync/incremental.rb +169 -0
  58. data/lib/ksef/testing.rb +153 -0
  59. data/lib/ksef/token_manager.rb +130 -0
  60. data/lib/ksef/version.rb +11 -0
  61. data/lib/ksef/xades/auth_token_request.rb +135 -0
  62. data/lib/ksef/xades/external_signer.rb +58 -0
  63. data/lib/ksef/xades/signer.rb +312 -0
  64. data/lib/ksef-client-rails.rb +3 -2
  65. data/lib/ksef.rb +116 -0
  66. data/schemas/FA/schemat_FA(2)_v1-0E.xsd +3661 -0
  67. data/schemas/FA/schemat_FA(3)_v1-0E.xsd +3950 -0
  68. data/schemas/README.md +53 -0
  69. data/schemas/RR/schemat_FA_RR(1)_v1-1E.xsd +2188 -0
  70. data/schemas/bazowe/ElementarneTypyDanych_v10-0E.xsd +1 -0
  71. data/schemas/bazowe/KodyKrajow_v10-0E.xsd +1283 -0
  72. data/schemas/bazowe/StrukturyDanych_v10-0E.xsd +1 -0
  73. metadata +291 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b68dc2061bf9afb952fe14fb5bb13fccec2dbe7ca8dcf83b92939f10d3a8728
4
- data.tar.gz: fbc9de34b15553de53f81bc5c81f82e6620a5b9be1da733403dbe36fbd0e62b8
3
+ metadata.gz: 4ad5bc199c88428b14246234faed79b641060f5a87b4daa6157cfbc33a82c67d
4
+ data.tar.gz: 2f4efe72191d1a0cfa1093856a37c9cda4d4b821ddb94bdbb36562197a352b86
5
5
  SHA512:
6
- metadata.gz: 801e309165999653b17843ea56344fa55caa604c6b97058d299b0613101f5443d84e5790b37672bb85833ca807e1e9ff53cc8dc7b67b6b2fff6ce59fb3bdadd8
7
- data.tar.gz: ec52d382dd0be5713a3dd9ecbe8d617f542dd3d7d01905f56ebe243db34682d30c3dc0548d5ccee28715b57d1c47e4a0096a540dee0779f5ec86caf19ad0c3e9
6
+ metadata.gz: 274542ba6918c449c35a3d758c660fbd1c178a2c1a93e73bc244ccbc719495f6430d432ba6f704ef27cb76af447ace44aa9b00ea069419ff1f320a03d5aa52cd
7
+ data.tar.gz: eb7a19017a260b466230d8bcec04cfb4280824e9a67558e8f7cbdead5ee2781df369ba94b701f02eafa76d017b87802cbf78686e0e719537ae12f2e8db3f6892
data/CHANGELOG.md ADDED
@@ -0,0 +1,161 @@
1
+ # Changelog
2
+
3
+ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4
+ The gem tracks the KSeF API contract version (currently **2.8.1**, `Ksef::API_VERSION`); every
5
+ request is checked against that version's OpenAPI document in the test suite.
6
+
7
+ ## [1.0.0] — 2026-10-14
8
+
9
+ First functional release: a pure-Ruby client for KSeF API 2.0, covering every operation of the
10
+ 2.8.1 contract plus high-level flows. Rails integration is optional.
11
+
12
+ ### Added
13
+ - **Client and configuration:** `Ksef::Client` facade with 14 domain resources mapped 1:1 to the
14
+ API; global or per-client `Configuration` (`#apply` assigns any option and rejects unknown keys,
15
+ `#with` copies it with some options changed — e.g. another environment);
16
+ TEST, DEMO and PROD environments; option values are type-checked when set, and a client copies the
17
+ configuration it is built with. The access token may be a callable read for every request attempt,
18
+ `with_access_token` derives a per-context client that shares the connection and the key cache, and
19
+ `close` closes the keep-alive connections. One CA store per process.
20
+ - **HTTP layer:** retries — a `429` for any call, honouring `Retry-After` up to
21
+ `config.max_retry_after` (60 s by default, `nil` for no limit; a longer wait is raised as
22
+ `Ksef::RateLimited`), and `500`/`502`/`503`/`504` or connection failures for idempotent requests
23
+ (GET, presigned transfers) with exponential backoff — also a GET whose reused TLS connection was
24
+ dropped without close_notify, and any request that never left the process (a connect or a
25
+ connection-pool timeout); transport failures — including the ones Faraday leaves unwrapped, and a
26
+ response cut short of its Content-Length — surface as `Ksef::NetworkError` /
27
+ `Ksef::RequestTimeout`. Keep-alive by
28
+ default (`faraday-net_http_persistent`; `config.adapter` picks another Faraday adapter), array query
29
+ parameters as repeated keys, presigned transfers for batch and export (normalized `https://host` URLs
30
+ only), both pagination schemes as lazy enumerators guarded against a server that never ends them (a
31
+ repeated token, a run of empty pages), polling with backoff, typed errors parsed from both API error
32
+ formats (including the legacy 429 body, the `reasonCode` of a 403 and each problem-details error's
33
+ description). A 2xx body that is not what the endpoint returns — not JSON, not a JSON object, without
34
+ a field the gem needs, or for an invoice or UPO download not an XML document or not matching its
35
+ `x-ms-meta-hash` — raises `Ksef::ApiError` instead of reading as an empty result; a JSON-typed 2xx
36
+ body is never quoted in it (it may carry a token), a non-JSON error body is kept as a short UTF-8
37
+ excerpt. A token with a control character is refused without being echoed. Every HTTP attempt is
38
+ logged at `debug` and published as `request.ksef` through ActiveSupport::Notifications (method, path
39
+ without the query, status).
40
+ - **Authentication:** KSeF token (RSA-OAEP) and XAdES-BASELINE-B signature (RSA of at least 2048 bits,
41
+ or EC of at least 256) with a replaceable signer (`#sign(xml)`/`#call(xml)` — HSM, qualified signature
42
+ services; checked, with the context, subject type and allowed IPs, before the challenge is
43
+ requested; its result must be the signed XML), token redemption and refresh, authentication
44
+ sessions, thread-safe `TokenManager` (refreshing at `validUntil`, or at the token's JWT `exp` claim
45
+ when that is not given; `on_refresh`, run outside the lock and again after a failure, to persist each
46
+ new token; `#access_token_valid_until`; a skew longer than the token's lifetime does not refresh on
47
+ every request). A NIP context is sent as its 10 digits, and an invalid one is refused before any
48
+ request. The signer refuses malformed XML, a DOCTYPE, a key without its private part and a
49
+ certificate it does not match; `X509IssuerName` decodes every string type and multi-valued RDNs.
50
+ - **Cryptography:** AES-256-CBC session encryption, RSA-OAEP with SHA-256/MGF1-SHA-256, Ministry
51
+ public-key cache (RSA certificates only) with a refetch when the cached list has no valid
52
+ certificate, and a retry on key
53
+ rotation (`21470`) in every flow that wraps a key — online and batch sessions, export (the same
54
+ key re-wrapped under the new certificate) and token authentication (a fresh challenge); PKCS#10
55
+ CSR generation (RSA/EC).
56
+ - **Invoices:** interactive session (open → encrypt → send → close → UPO; `valid_until`, a BOM or
57
+ non-UTF-8 invoice refused, a technical correction sent in offline mode, a rejection raised as
58
+ `Ksef::InvoiceRejected`); batch session (ZIP or
59
+ tar.gz — `compression: :zip | :tar_gz` — declared as `batchFile.compressionType`, split into parts
60
+ encrypted one at a time, the contract limits of 50 parts, 5 GB and 100 MB per part checked before
61
+ anything is sent — and so are an empty package, colliding or unsafe names and a tar entry name over
62
+ 100 bytes —, UTF-8 ZIP entry names flagged as such, `Ksef::BatchUploadError` naming the session
63
+ when an upload or the close fails); invoice download; metadata queries (250 records per page by
64
+ default, `Ksef::ResultTruncated` at the 10,000-record limit, with the `dateRange` bound to move for
65
+ the sort order); package export (ZIP or tar.gz, checked against `package.compressionType`, streamed
66
+ with `each_entry` — names never absolute or climbing, each file capped at 100 MB unpacked —,
67
+ `_metadata.json` parsed by `metadata`, every part checked against its required `encryptedPartHash`);
68
+ incremental HWM sync (invoices paired with their metadata, a stop once the HWM stops moving, closed
69
+ 90-day windows for a start more than 90 days back, nothing yielded while the HWM has not reached the
70
+ start, a `Date` start at Polish midnight, `#hwm` to persist between runs — after an error too).
71
+ Invoices and UPOs are returned as UTF-8 strings.
72
+ - **Offline validation:** `Ksef::Invoices::Validator` against the packaged FA(2), FA(3) and
73
+ FA_RR(1) schemas plus KSeF's XML rules (UTF-8 without a BOM, XML 1.0, no processing instructions,
74
+ none of the characters KSeF refuses), without a network call; the messages are libxml2's. The
75
+ XML is parsed strictly (a malformed or namespace-malformed document is reported, never repaired),
76
+ and each schema is parsed once per process. `#errors` gives each violation's element path and line,
77
+ `#outline` a form's field tree with the occurrence of enclosing groups and extension bases (and each
78
+ field's description from the schema on request); `FormCode#namespace` is the invoice namespace.
79
+ - **Invoice builder:** `Ksef::Invoices::Builder` writes FA(3) XML — parties (a buyer by NIP, EU VAT
80
+ number, another tax ID or none), lines with the ten rates and their per-rate summary (rounded half
81
+ up), payment, annotations (reverse charge set by an `oo` or `np II` line), corrections (lines as
82
+ differences), footer and registers. Amounts are parsed strictly and written exactly (prices and
83
+ discounts to 2 decimal places, quantities to 6); missing values, unknown codes, a date that is not a
84
+ Date or "YYYY-MM-DD", text KSeF refuses, a foreign-currency invoice with VAT and a `zw` line without
85
+ its legal basis are refused, and the output is checked against the schema.
86
+ `Ksef::Invoices::Sample.minimal`, `.typical` and `.full` are three invoices accepted on KSeF TEST,
87
+ dated with the Polish calendar day by default (`Ksef::PolishTime.today`) and packaged as
88
+ `examples/fa3_{minimal,typical,full}.xml`.
89
+ - **QR codes:** CODE I and CODE II links (RSASSA-PSS / ECDSA P-256 signatures with an RSA key of at
90
+ least 2048 bits, every path segment validated — the context type and value against the contract,
91
+ `NipVatUe` values with `+`/`*` included, a 16-hex-digit certificate serial (or `certificate.serial`),
92
+ a seller NIP normalized and checked, the CODE I date read from the invoice's `P_1`, a calendar date
93
+ and a `Time` refused — and KSeF's Base64 `invoiceHash` decoded and checked against the XML) and
94
+ rendering as scalable SVG with the required quiet zone and the label KSeF prescribes (the colour
95
+ validated), PNG or an ANSI terminal preview (optional `rqrcode` gem).
96
+ - **Other resources:** collective identifiers (API 2.7+), KSeF tokens, KSeF certificates,
97
+ permissions, limits, security, Peppol, TEST data (refused locally on PROD; includes
98
+ `update_certificate`). `sessions.list` requires the `session_type` the API requires and takes its
99
+ optional filters.
100
+ - **Value objects:** `Nip` and `KsefNumber` checked against the contract patterns plus the mod-11
101
+ checksum and CRC-8 (a NIP's separators — no-break spaces and dashes too — and `PL` prefix stripped;
102
+ `Nip.normalize!`; equal only to their own kind), `FormCode`; `Ksef::PolishTime` for the Polish
103
+ calendar day without tzinfo.
104
+ - **Rails:** `Ksef::Railtie` (`config.ksef.*` for every option, applied before the app's
105
+ initializers) and the `ksef:install` generator, whose
106
+ initializer sets only the environment — from `KSEF_ENV` when set and not empty, never from
107
+ `Rails.env` — and
108
+ leaves the rest commented out, so it does not override `config.ksef.*`.
109
+ - **Errors:** one hierarchy under `Ksef::Error`, including `Ksef::SessionUnavailable` for code
110
+ `21184` (a session under maintenance), `Ksef::ReauthenticationRequired` for `21301`/`21304` and a
111
+ 401 from the token refresh (authenticate again), `Ksef::InvoiceRejected` and `Ksef::ResultTruncated`.
112
+ - **Testing:** `require "ksef/testing"` — WebMock stubs of the public keys (a certificate valid
113
+ 2000–2099, so time travel in tests is safe), sign-in (token and XAdES), refresh and an interactive
114
+ session, with responses checked against the contract.
115
+ - **Secret hygiene:** `Ksef::Client`, `Ksef::TokenManager`, the authentication result, session
116
+ encryption data and a generated CSR redact tokens and keys in `#inspect` and in `pp` (the Rails
117
+ console), and an export result or a batch package shows only its size; `Configuration` and the
118
+ connection redact proxy credentials in every form Faraday accepts.
119
+ - **Verification tooling:** the contract suite against the 2.8.1 OpenAPI document
120
+ (`spec/fixtures/openapi/`), `rake contract:drift` comparing it with the live servers, and
121
+ `rake e2e:test` running every flow on live KSeF TEST without credentials.
122
+ - **Dependencies:** Ruby 3.3 or later; `faraday ~> 2.5`, `faraday-net_http_persistent ~> 2.3`
123
+ (`>= 2.3.1`) with `connection_pool >= 2.4` (the after-fork reset of the keep-alive pool),
124
+ `nokogiri >= 1.19.1, < 2` (the libxml2 security fixes), `rubyzip ~> 3.4` (older releases carry
125
+ CVE-2026-85396), `bigdecimal`, `base64`, `logger`; `rqrcode` and `minitar` are optional.
126
+
127
+ ### Verified
128
+ - Offline RSpec suite on Ruby 3.3, 3.4 and 4.0 and at the lowest dependency versions the gemspec
129
+ allows, including the contract check of every request against the KSeF API 2.8.1 OpenAPI document
130
+ (all 83 operations, every parameter and every list's pagination); the built gem loads with its
131
+ runtime dependencies only; cryptography, the KSeF-token plaintext, CSRs and XAdES output (with
132
+ `SigningCertificate`) checked by an independent Python implementation.
133
+ - PROD publishes the 2.8.1 contract (`rake contract:drift`, 2026-10-14); TEST and DEMO publish 2.9.0.
134
+ - On the live KSeF TEST environment (2026-10-14, `rake e2e:test`, every flow passed; TEST already runs
135
+ API 2.9.0): XAdES authentication with RSA and EC seals, KSeF-token authentication, token refresh,
136
+ authentication sessions, limits, KSeF tokens (with the status filter), interactive session with UPO
137
+ (the three sample invoices included), batch sessions (ZIP in several parts and tar.gz) with UPO and
138
+ every invoice accepted, invoice download checked against its hash, metadata query, export (ZIP and
139
+ tar.gz), `Sync::Incremental` (also from 91 days back: a closed 90-day window, then an open one),
140
+ collective identifiers, Offline certificate enrollment with a CODE II link, permissions, Peppol,
141
+ TEST data, revocations.
142
+ - On the live KSeF DEMO environment through the demo application (2026-10-12): authentication (token
143
+ and XAdES), interactive and batch sessions with UPO, export, `Sync::Incremental`, permissions,
144
+ certificate enrollment and offline QR; on the live KSeF PROD environment (2026-10-12):
145
+ authentication with a KSeF token. A GET was retried live after a dropped keep-alive connection, and
146
+ a 429 slept through and retried.
147
+ - The Railtie and the `ksef:install` generator smoke-tested in a Rails 8.1 application.
148
+
149
+ ### Known limitations
150
+ - Not exercised live: the 5xx retries, `SessionUnavailable` and a token refresh refused with 401.
151
+ The Railtie and the generator are not exercised by the demo application.
152
+ - No HTML/PDF invoice rendering, no PEF (UBL) schema validation. The builder writes VAT and KOR
153
+ invoices only (no advance, settlement or simplified invoices, no `Podmiot3`, no attachments), in PLN
154
+ when a line carries VAT, and a correction as difference lines (no `StanPrzed`).
155
+ - Batch and export packages are held in memory (one copy of the package).
156
+ - With the default keep-alive adapter, a proxy password containing percent-encoded characters fails
157
+ (`URI::InvalidComponentError`, faraday-net_http_persistent 2.3); use `adapter: :net_http`.
158
+
159
+ ## [0.1.0] — 2026-09-24
160
+
161
+ Name reservation only; no functionality.
data/NOTICE ADDED
@@ -0,0 +1,40 @@
1
+ Third-party material included in ksef-client-rails
2
+ ==================================================
3
+
4
+ The XSD schemas in schemas/ (FA(2), FA(3), FA_RR(1) and the base types in schemas/bazowe/)
5
+ are copied from the official KSeF documentation repository published by the Polish Ministry of
6
+ Finance (https://github.com/CIRFMF/ksef-api); the forms are published on https://crd.gov.pl/. The
7
+ only change is the schemaLocation of the base-types import (see schemas/README.md). The OpenAPI document in
8
+ spec/fixtures/openapi/ (used by the test suite only, not packaged into the gem) is the KSeF API 2.8.1
9
+ document as the KSeF TEST server publishes it, unchanged; it has the same operations and schemas as
10
+ the one in that repository. Parts of the gem follow
11
+ the behaviour of the official KSeF client libraries (https://github.com/CIRFMF/ksef-client-csharp,
12
+ https://github.com/CIRFMF/ksef-client-java), which are published under the same licence.
13
+
14
+ This project is independent and is not affiliated with or endorsed by the Ministry of Finance.
15
+
16
+ That material is distributed under the following licence:
17
+
18
+ --------------------------------------------------------------------------------
19
+ MIT License
20
+
21
+ Copyright (c) 2025 Ministerstwo Finansów
22
+
23
+ Permission is hereby granted, free of charge, to any person obtaining a copy
24
+ of this software and associated documentation files (the "Software"), to deal
25
+ in the Software without restriction, including without limitation the rights
26
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27
+ copies of the Software, and to permit persons to whom the Software is
28
+ furnished to do so, subject to the following conditions:
29
+
30
+ The above copyright notice and this permission notice shall be included in all
31
+ copies or substantial portions of the Software.
32
+
33
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39
+ SOFTWARE.
40
+ --------------------------------------------------------------------------------