Class: Ksef::Sessions::Online
- Inherits:
-
Object
- Object
- Ksef::Sessions::Online
- Defined in:
- lib/ksef/sessions/online.rb
Overview
The three HTTP calls of an interactive session: open, send, close (docs/REFERENCE.md §11).
Deliberately thin and stateless, exactly like Auth::Client: it maps requests and responses and holds no session of its own. That shape is not a guess — both official clients do the same, threading the session reference through as a parameter and offering no session object at all. Deciding when to open a session, and composing open-send-close into one call, belongs a layer up in Client.
The encryptor travels with the session, not with each send
Recorded as a decision at docs/REFERENCE.md §11.2a, alongside the rest of the session-layer choices upstream does not make for us.
Session carries the Crypto::Encryptor that opened it. That is the single most important design decision here. The symmetric key is agreed once, at open, and every invoice in the session is encrypted under it — so sending an invoice encrypted with a different key produces a payload KSeF cannot decrypt, and the only symptom is per-invoice status 435, "błąd odszyfrowania pliku" (§12.1), arriving asynchronously, long after the call returned 202.
Binding the encryptor to the session at open time makes that mistake unrepresentable rather than merely documented — the same reasoning that makes Crypto::Encryptor#seal produce both digests together.
Defined Under Namespace
Classes: Session, Submission
Instance Method Summary collapse
-
#close(session) ⇒ nil
Closes the session, which starts asynchronous generation of the collective UPO (§11).
-
#initialize(connection, credential) ⇒ Online
constructor
A new instance of Online.
-
#open(encryptor:, certificate:, form_code: DEFAULT_FORM_CODE, upo_version: UPO_VERSION) ⇒ Session
Opens a session.
-
#send_invoice(session, invoice, offline_mode: false, corrected_invoice_hash: nil) ⇒ Submission
Encrypts an invoice and submits it.
Constructor Details
#initialize(connection, credential) ⇒ Online
Returns a new instance of Online.
51 52 53 54 |
# File 'lib/ksef/sessions/online.rb', line 51 def initialize(connection, credential) @connection = connection @credential = credential end |
Instance Method Details
#close(session) ⇒ nil
Closes the session, which starts asynchronous generation of the collective UPO
(§11). The UPO is not available when this returns; poll the session status for
upo.pages[].
131 132 133 134 |
# File 'lib/ksef/sessions/online.rb', line 131 def close(session) post(path(session, "close")) nil end |
#open(encryptor:, certificate:, form_code: DEFAULT_FORM_CODE, upo_version: UPO_VERSION) ⇒ Session
Opens a session. "Lightweight and synchronous" per §11 — but not free: a session opened and never used is cancelled with status 440, "nie przesłano faktur" (§12.1).
67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/ksef/sessions/online.rb', line 67 def open(encryptor:, certificate:, form_code: DEFAULT_FORM_CODE, upo_version: UPO_VERSION) body = { formCode: Sessions.form_code(form_code), encryption: encryptor.encryption_info(certificate) } headers = upo_version.nil? ? {} : { FEATURE_HEADER => upo_version } payload = post("sessions/online", body: body, headers: headers).body Session.new( reference_number: payload["referenceNumber"], valid_until: Ksef::Auth.time(payload["validUntil"]), encryptor: encryptor ) end |
#send_invoice(session, invoice, offline_mode: false, corrected_invoice_hash: nil) ⇒ Submission
Encrypts an invoice and submits it. Returns as soon as KSeF accepts the upload — a 202, not an acceptance of the invoice. Whether the invoice itself is accepted arrives later, per invoice, via the status endpoints (§12.1).
The four integrity values of §11.1 all come from one Crypto::Encryptor#seal call, so the plaintext hash cannot be computed over different bytes than the ciphertext hash.
The two offline parameters (docs/REFERENCE.md §16)
offline_mode declares the taxpayer's offline invoicing mode — one boolean covering
all three legal regimes, offline24, offline and awaryjny. Declaring false does
not guarantee the invoice is treated as online: KSeF compares P_1 against the
moment it accepts the document and marks it offline if P_1's calendar day is
earlier, whatever was declared (§16.1). An invoice dated yesterday and sent today is
therefore offline by the service's reckoning, one second past midnight included.
corrected_invoice_hash is the base64 SHA-256 of the original, rejected offline
invoice, and it makes this submission a technical correction (§16.2): a resend of an
invoice KSeF refused for a technical reason — schema mismatch, size, duplicate — with
different bytes and therefore a different hash. It is not a way to correct
content; that is a KOR, which is an ordinary invoice of a different type. Upstream
sends offlineMode: true alongside it, and only in an interactive session, though the
invoice being corrected may have been rejected in a batch one. This method does not
enforce that pairing: the contract makes both fields independently optional, and
imposing a rule it states only in prose would be inventing one.
115 116 117 118 119 120 121 122 123 124 |
# File 'lib/ksef/sessions/online.rb', line 115 def send_invoice(session, invoice, offline_mode: false, corrected_invoice_hash: nil) # Serialised once. What gets hashed has to be exactly what gets encrypted, so the # bytes are settled here and never re-derived. xml = invoice.respond_to?(:to_xml) ? invoice.to_xml : invoice.to_s body = integrity(session.encryptor.seal(xml)) body[:offlineMode] = true if offline_mode body[:hashOfCorrectedInvoice] = corrected_invoice_hash if corrected_invoice_hash Submission.from(post(path(session, "invoices"), body: body).body) end |