ksef_client 0.1.0.rc1
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 +7 -0
- data/CHANGELOG.md +59 -0
- data/CONTRIBUTING.md +103 -0
- data/LICENSE +27 -0
- data/README.md +150 -0
- data/SECURITY.md +69 -0
- data/docs/REFERENCE.md +403 -0
- data/docs/errors.md +128 -0
- data/lib/ksef/configuration.rb +122 -0
- data/lib/ksef/environments.rb +85 -0
- data/lib/ksef/errors.rb +92 -0
- data/lib/ksef/fa3/schema/LICENSE.upstream.txt +21 -0
- data/lib/ksef/fa3/schema/bazowe/ElementarneTypyDanych_v10-0E.xsd +1 -0
- data/lib/ksef/fa3/schema/bazowe/KodyKrajow_v10-0E.xsd +1283 -0
- data/lib/ksef/fa3/schema/bazowe/StrukturyDanych_v10-0E.xsd +1 -0
- data/lib/ksef/fa3/schema/schemat_FA(3)_v1-0E.xsd +3950 -0
- data/lib/ksef/http/connection.rb +62 -0
- data/lib/ksef/http/error_handler.rb +83 -0
- data/lib/ksef/http/system_warning.rb +29 -0
- data/lib/ksef/problem_details.rb +143 -0
- data/lib/ksef/retry_policy.rb +77 -0
- data/lib/ksef/version.rb +5 -0
- data/lib/ksef.rb +32 -0
- data/lib/ksef_client.rb +5 -0
- metadata +130 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
|
|
5
|
+
module Ksef
|
|
6
|
+
module HTTP
|
|
7
|
+
# Builds the single Faraday connection a {Ksef::Client} owns.
|
|
8
|
+
#
|
|
9
|
+
# One connection per client, shared across threads — safe with the default `net_http`
|
|
10
|
+
# adapter (DESIGN.md §5.2). The adapter is kept swappable via configuration.
|
|
11
|
+
module Connection
|
|
12
|
+
# `application/problem+json` is the current error content type, so the response
|
|
13
|
+
# parser must match it as well as plain `application/json`
|
|
14
|
+
# (docs/REFERENCE.md §5.1).
|
|
15
|
+
JSON_CONTENT_TYPE = /\bjson\b/
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
# @param config [Ksef::Configuration]
|
|
19
|
+
# @return [Faraday::Connection]
|
|
20
|
+
def build(config)
|
|
21
|
+
Faraday.new(url: config.base_url, headers: default_headers(config)) do |f|
|
|
22
|
+
f.request :json
|
|
23
|
+
|
|
24
|
+
# Registration order is load-bearing. Faraday runs `on_complete` callbacks
|
|
25
|
+
# innermost-first, so the JSON parser must be registered *after* the error
|
|
26
|
+
# handler for the handler to see a decoded body rather than a raw string.
|
|
27
|
+
f.use ErrorHandler
|
|
28
|
+
f.use SystemWarning, logger: config.logger
|
|
29
|
+
f.response :json, content_type: JSON_CONTENT_TYPE
|
|
30
|
+
|
|
31
|
+
apply_transport_options(f, config)
|
|
32
|
+
f.adapter config.adapter
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def apply_transport_options(faraday, config)
|
|
39
|
+
faraday.options.open_timeout = config.open_timeout
|
|
40
|
+
faraday.options.timeout = config.read_timeout
|
|
41
|
+
faraday.proxy = config.proxy if config.proxy
|
|
42
|
+
|
|
43
|
+
# TLS verification is never disabled, on any code path (DESIGN.md §4.5).
|
|
44
|
+
faraday.ssl.verify = true
|
|
45
|
+
faraday.ssl.min_version = :TLS1_2
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def default_headers(config)
|
|
49
|
+
{
|
|
50
|
+
"Accept" => "application/json",
|
|
51
|
+
# Opts into RFC7807 error bodies. Every one of the 83 operations documents
|
|
52
|
+
# this header, and the modern envelope is opt-in: without it the API returns
|
|
53
|
+
# the deprecated shapes, which carry no traceId, no structured `errors[]`
|
|
54
|
+
# codes on 400 and no reasonCode on 403 (docs/REFERENCE.md §5.1).
|
|
55
|
+
"X-Error-Format" => "problem-details",
|
|
56
|
+
"User-Agent" => config.user_agent
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
|
|
5
|
+
module Ksef
|
|
6
|
+
module HTTP
|
|
7
|
+
# Maps KSeF error responses and Faraday transport failures onto the {Ksef::Error}
|
|
8
|
+
# hierarchy (DESIGN.md §6.7).
|
|
9
|
+
#
|
|
10
|
+
# Status coverage follows what the contract actually declares — 400, 401, 403, 410
|
|
11
|
+
# and 429 (docs/REFERENCE.md §5.4). 5xx is handled defensively: the spec never
|
|
12
|
+
# declares it, so the body shape is unknown and degrades to a raw payload.
|
|
13
|
+
class ErrorHandler < Faraday::Middleware
|
|
14
|
+
STATUS_MAP = {
|
|
15
|
+
400 => ApiError,
|
|
16
|
+
401 => AuthenticationError,
|
|
17
|
+
403 => AuthorizationError,
|
|
18
|
+
410 => ResourceGoneError,
|
|
19
|
+
429 => RateLimitedError
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
def call(env)
|
|
23
|
+
super
|
|
24
|
+
rescue Faraday::TimeoutError => e
|
|
25
|
+
raise Ksef::TimeoutError, "KSeF request timed out: #{e.message}"
|
|
26
|
+
rescue Faraday::SSLError => e
|
|
27
|
+
raise Ksef::ConnectionError, "TLS failure talking to KSeF: #{e.message}"
|
|
28
|
+
rescue Faraday::ConnectionFailed => e
|
|
29
|
+
# The net_http adapter reports `Net::OpenTimeout` as a connection failure, which
|
|
30
|
+
# loses the distinction a caller most needs after submitting an invoice: a
|
|
31
|
+
# timeout may have been processed server-side, a refused connection was not.
|
|
32
|
+
raise Ksef::TimeoutError, "KSeF connection timed out: #{e.message}" if timeout?(e.wrapped_exception)
|
|
33
|
+
|
|
34
|
+
raise Ksef::ConnectionError, "Could not connect to KSeF: #{e.message}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def on_complete(env)
|
|
38
|
+
status = env.status
|
|
39
|
+
return if status < 400
|
|
40
|
+
|
|
41
|
+
problem = ProblemDetails.parse(status: status, body: env.body)
|
|
42
|
+
raise build_error(status, problem, env)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# `Net::OpenTimeout` and `Net::ReadTimeout` both descend from `Timeout::Error`.
|
|
48
|
+
def timeout?(wrapped)
|
|
49
|
+
defined?(Timeout::Error) && wrapped.is_a?(Timeout::Error)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build_error(status, problem, env)
|
|
53
|
+
message = "KSeF API #{status}: #{problem.summary}"
|
|
54
|
+
message = "#{message} (traceId: #{problem.trace_id})" if problem.trace_id
|
|
55
|
+
|
|
56
|
+
if status == 429
|
|
57
|
+
RateLimitedError.new(message, problem: problem, retry_after: retry_after_from(env))
|
|
58
|
+
else
|
|
59
|
+
error_class(status).new(message, problem: problem)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def error_class(status)
|
|
64
|
+
STATUS_MAP[status] || (status >= 500 ? ServerError : ApiError)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# `Retry-After` is present on every declared 429 and is expressed in seconds
|
|
68
|
+
# (docs/REFERENCE.md §5.5). The HTTP-date form is accepted defensively.
|
|
69
|
+
def retry_after_from(env)
|
|
70
|
+
raw = env.response_headers&.[]("Retry-After")
|
|
71
|
+
return if raw.nil? || raw.to_s.strip.empty?
|
|
72
|
+
|
|
73
|
+
value = raw.to_s.strip
|
|
74
|
+
return Integer(value, 10) if /\A\d+\z/.match?(value)
|
|
75
|
+
|
|
76
|
+
seconds = (Time.httpdate(value) - Time.now).ceil
|
|
77
|
+
seconds.positive? ? seconds : 0
|
|
78
|
+
rescue ArgumentError
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
|
|
5
|
+
module Ksef
|
|
6
|
+
module HTTP
|
|
7
|
+
# Surfaces the `X-System-Warning` header the API sets on every successful response
|
|
8
|
+
# (docs/REFERENCE.md §5.5).
|
|
9
|
+
#
|
|
10
|
+
# This is the Ministry's in-band channel for advisory notices — deprecations,
|
|
11
|
+
# forthcoming contract changes — and is not mentioned in DESIGN.md. Swallowing it
|
|
12
|
+
# would mean users only learn about a change when it breaks them.
|
|
13
|
+
class SystemWarning < Faraday::Middleware
|
|
14
|
+
HEADER = "X-System-Warning"
|
|
15
|
+
|
|
16
|
+
def initialize(app, logger: nil)
|
|
17
|
+
super(app)
|
|
18
|
+
@logger = logger
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def on_complete(env)
|
|
22
|
+
warning = env.response_headers&.[](HEADER)
|
|
23
|
+
return if warning.nil? || warning.to_s.strip.empty?
|
|
24
|
+
|
|
25
|
+
@logger&.warn("[ksef_client] #{HEADER}: #{warning}")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ksef
|
|
4
|
+
# A parsed KSeF error body.
|
|
5
|
+
#
|
|
6
|
+
# KSeF serves errors in two different envelopes and the response `Content-Type` picks
|
|
7
|
+
# between them (docs/REFERENCE.md §5.1): `application/problem+json` is current, while
|
|
8
|
+
# `application/json` returns legacy shapes the spec marks deprecated but which are
|
|
9
|
+
# still live in the wild. This class normalises all of them, and degrades to a raw
|
|
10
|
+
# payload for bodies that are neither (an HTML block page from the WAF in front of the
|
|
11
|
+
# API, for instance).
|
|
12
|
+
ProblemDetails = Data.define(
|
|
13
|
+
:status,
|
|
14
|
+
:title,
|
|
15
|
+
:detail,
|
|
16
|
+
:instance,
|
|
17
|
+
:timestamp,
|
|
18
|
+
:trace_id,
|
|
19
|
+
:entries,
|
|
20
|
+
:reason_code,
|
|
21
|
+
:security,
|
|
22
|
+
:raw
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Parsing and presentation for {Ksef::ProblemDetails}.
|
|
26
|
+
class ProblemDetails
|
|
27
|
+
# One entry of a 400's `errors[]`. Called `ApiError` in the OpenAPI spec, renamed
|
|
28
|
+
# here to avoid colliding with the {Ksef::ApiError} exception class.
|
|
29
|
+
Entry = Data.define(:code, :description, :details)
|
|
30
|
+
|
|
31
|
+
# Only `status` and `raw` are common to every envelope; everything else is absent in
|
|
32
|
+
# at least one of them, so the builders below name only what they actually have.
|
|
33
|
+
DEFAULTS = {
|
|
34
|
+
status: nil, title: nil, detail: nil, instance: nil, timestamp: nil,
|
|
35
|
+
trace_id: nil, entries: [].freeze, reason_code: nil, security: {}.freeze, raw: nil
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# @param status [Integer] HTTP status
|
|
40
|
+
# @param body [Hash, String, nil] decoded JSON body, or the raw string
|
|
41
|
+
# @return [Ksef::ProblemDetails]
|
|
42
|
+
def parse(status:, body:)
|
|
43
|
+
return build(status: status, raw: body) unless body.is_a?(Hash)
|
|
44
|
+
|
|
45
|
+
if body["exception"].is_a?(Hash)
|
|
46
|
+
legacy_exception(status, body)
|
|
47
|
+
elsif body["status"].is_a?(Hash)
|
|
48
|
+
legacy_status(status, body)
|
|
49
|
+
else
|
|
50
|
+
problem_json(status, body)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def build(**attrs) = new(**DEFAULTS, **attrs)
|
|
57
|
+
|
|
58
|
+
# The current `application/problem+json` shape.
|
|
59
|
+
def problem_json(status, body)
|
|
60
|
+
build(
|
|
61
|
+
status: body["status"] || status,
|
|
62
|
+
title: body["title"],
|
|
63
|
+
detail: body["detail"],
|
|
64
|
+
instance: body["instance"],
|
|
65
|
+
timestamp: body["timestamp"],
|
|
66
|
+
trace_id: body["traceId"],
|
|
67
|
+
entries: Array(body["errors"]).filter_map { |e| entry_from_api_error(e) },
|
|
68
|
+
reason_code: body["reasonCode"],
|
|
69
|
+
security: body["security"] || {},
|
|
70
|
+
raw: body
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Deprecated `ExceptionResponse`: details nest under `exception.exceptionDetailList`.
|
|
75
|
+
def legacy_exception(status, body)
|
|
76
|
+
exception = body["exception"]
|
|
77
|
+
entries = Array(exception["exceptionDetailList"]).filter_map { |e| entry_from_exception_detail(e) }
|
|
78
|
+
|
|
79
|
+
build(
|
|
80
|
+
status: status,
|
|
81
|
+
title: exception["serviceName"],
|
|
82
|
+
detail: entries.first&.description,
|
|
83
|
+
timestamp: exception["timestamp"],
|
|
84
|
+
# The legacy envelope has no traceId; referenceNumber is its closest analogue.
|
|
85
|
+
trace_id: exception["referenceNumber"],
|
|
86
|
+
entries: entries,
|
|
87
|
+
raw: body
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Deprecated `TooManyRequestsResponse`: `status` is an object, not an integer.
|
|
92
|
+
def legacy_status(status, body)
|
|
93
|
+
inner = body["status"]
|
|
94
|
+
details = Array(inner["details"])
|
|
95
|
+
entry = Entry.new(code: inner["code"], description: inner["description"], details: details)
|
|
96
|
+
|
|
97
|
+
build(
|
|
98
|
+
status: inner["code"] || status,
|
|
99
|
+
title: inner["description"],
|
|
100
|
+
detail: details.first,
|
|
101
|
+
entries: [entry],
|
|
102
|
+
raw: body
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def entry_from_api_error(entry)
|
|
107
|
+
return unless entry.is_a?(Hash)
|
|
108
|
+
|
|
109
|
+
Entry.new(
|
|
110
|
+
code: entry["code"],
|
|
111
|
+
description: entry["description"],
|
|
112
|
+
details: Array(entry["details"])
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def entry_from_exception_detail(entry)
|
|
117
|
+
return unless entry.is_a?(Hash)
|
|
118
|
+
|
|
119
|
+
Entry.new(
|
|
120
|
+
code: entry["exceptionCode"],
|
|
121
|
+
description: entry["exceptionDescription"],
|
|
122
|
+
details: Array(entry["details"])
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [Integer, nil] the first KSeF error code, when the body carried one
|
|
128
|
+
def code = entries.first&.code
|
|
129
|
+
|
|
130
|
+
# @return [Array<String>] every detail message, across all entries
|
|
131
|
+
def details = entries.flat_map(&:details)
|
|
132
|
+
|
|
133
|
+
# A single line suitable for an exception message.
|
|
134
|
+
#
|
|
135
|
+
# @return [String]
|
|
136
|
+
def summary
|
|
137
|
+
lead = detail || title || entries.first&.description
|
|
138
|
+
parts = [lead, *details.reject { |d| d == lead }].compact
|
|
139
|
+
body = parts.empty? ? "HTTP #{status}" : parts.join(" ")
|
|
140
|
+
code ? "[#{code}] #{body}" : body
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ksef
|
|
4
|
+
# When the client may transparently retry a request.
|
|
5
|
+
#
|
|
6
|
+
# The governing rule from DESIGN.md §6.7 is a business one, not a technical one:
|
|
7
|
+
# **invoice submission is never auto-retried.** A duplicate invoice in KSeF is a real
|
|
8
|
+
# tax problem that the gem must not create on a user's behalf. So retries are gated on
|
|
9
|
+
# the HTTP method being idempotent, not merely on the status looking transient — a 429
|
|
10
|
+
# on a POST is surfaced as {Ksef::RateLimitedError} with `#retry_after` for the caller
|
|
11
|
+
# to act on deliberately.
|
|
12
|
+
RetryPolicy = Data.define(
|
|
13
|
+
:max_attempts,
|
|
14
|
+
:base_interval,
|
|
15
|
+
:max_interval,
|
|
16
|
+
:max_retry_after,
|
|
17
|
+
:backoff_factor,
|
|
18
|
+
:retry_statuses,
|
|
19
|
+
:respect_retry_after
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Behaviour for {Ksef::RetryPolicy}.
|
|
23
|
+
class RetryPolicy
|
|
24
|
+
# Methods with no side effects, so replaying them is safe.
|
|
25
|
+
IDEMPOTENT_METHODS = %i[get head].freeze
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
def default
|
|
29
|
+
new(
|
|
30
|
+
max_attempts: 3,
|
|
31
|
+
base_interval: 1.0,
|
|
32
|
+
max_interval: 30.0,
|
|
33
|
+
max_retry_after: 60.0,
|
|
34
|
+
backoff_factor: 2.0,
|
|
35
|
+
retry_statuses: [429, 500, 502, 503, 504].freeze,
|
|
36
|
+
respect_retry_after: true
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Disable retries entirely.
|
|
41
|
+
def none
|
|
42
|
+
default.with(max_attempts: 1)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param method [Symbol] the HTTP verb, lowercase
|
|
47
|
+
# @param status [Integer, nil] the response status, or nil for a transport failure
|
|
48
|
+
# @param attempt [Integer] 1-based
|
|
49
|
+
# @param retry_after [Integer, Float, nil] seconds, from the `Retry-After` header
|
|
50
|
+
def retryable?(method:, status:, attempt: 1, retry_after: nil)
|
|
51
|
+
return false if attempt >= max_attempts
|
|
52
|
+
return false unless IDEMPOTENT_METHODS.include?(method.to_s.downcase.to_sym)
|
|
53
|
+
# Waiting less than the server demanded provokes a longer block, so if we are not
|
|
54
|
+
# prepared to wait the full period we must not retry at all.
|
|
55
|
+
return false if respect_retry_after && retry_after && retry_after.to_f > max_retry_after
|
|
56
|
+
return true if status.nil? # connection reset / timeout on an idempotent request
|
|
57
|
+
|
|
58
|
+
retry_statuses.include?(status)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Seconds to wait before the next attempt.
|
|
62
|
+
#
|
|
63
|
+
# `Retry-After` wins over the computed backoff, and is deliberately **not** clamped to
|
|
64
|
+
# `max_interval`: on 429 the block period is dynamic and lengthens with repeat
|
|
65
|
+
# offences, so retrying earlier than the server asked makes things strictly worse
|
|
66
|
+
# (docs/REFERENCE.md §6). `max_retry_after` bounds this instead, by declining the
|
|
67
|
+
# retry outright in {#retryable?}.
|
|
68
|
+
#
|
|
69
|
+
# @param attempt [Integer] 1-based
|
|
70
|
+
# @param retry_after [Integer, Float, nil] seconds, from the `Retry-After` header
|
|
71
|
+
def interval_for(attempt:, retry_after: nil)
|
|
72
|
+
return retry_after.to_f if respect_retry_after && retry_after
|
|
73
|
+
|
|
74
|
+
[base_interval * (backoff_factor**(attempt - 1)), max_interval].min
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/ksef/version.rb
ADDED
data/lib/ksef.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zeitwerk"
|
|
4
|
+
|
|
5
|
+
# Ruby client for KSeF 2.0, the Polish National e-Invoice System.
|
|
6
|
+
#
|
|
7
|
+
# Two decoupled subsystems live under this namespace (DESIGN.md §5):
|
|
8
|
+
# - {Ksef::Client} and friends — transport: auth, crypto, sessions, invoices.
|
|
9
|
+
# - {Ksef::FA3} — the FA(3) invoice builder, usable with no HTTP at all.
|
|
10
|
+
module Ksef
|
|
11
|
+
class << self
|
|
12
|
+
# The gem's Zeitwerk loader. Exposed for `loader.eager_load` in forking servers.
|
|
13
|
+
attr_reader :loader
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@loader = Zeitwerk::Loader.new.tap do |loader|
|
|
17
|
+
loader.push_dir(__dir__)
|
|
18
|
+
# `ksef_client.rb` is the gem entry point, not a constant definition (DESIGN.md §5.3).
|
|
19
|
+
loader.ignore("#{__dir__}/ksef_client.rb")
|
|
20
|
+
# `errors.rb` defines the whole hierarchy and no `Ksef::Errors`, so it cannot follow
|
|
21
|
+
# Zeitwerk's file-to-constant rule. It is tiny and always needed — load it eagerly.
|
|
22
|
+
loader.ignore("#{__dir__}/ksef/errors.rb")
|
|
23
|
+
loader.inflector.inflect(
|
|
24
|
+
"fa3" => "FA3",
|
|
25
|
+
"http" => "HTTP",
|
|
26
|
+
"version" => "VERSION"
|
|
27
|
+
)
|
|
28
|
+
loader.setup
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
require_relative "ksef/errors"
|
data/lib/ksef_client.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ksef_client
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0.rc1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tibor Molnár
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bigdecimal
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: nokogiri
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.16'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.16'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: zeitwerk
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '2.6'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '2.6'
|
|
68
|
+
description: |
|
|
69
|
+
A Ruby client for KSeF 2.0, Poland's mandatory national e-invoicing system.
|
|
70
|
+
Covers the REST transport layer (KSeF-token authentication, payload encryption,
|
|
71
|
+
interactive sessions, invoice submission, status polling and UPO retrieval) and
|
|
72
|
+
ships a standalone FA(3) invoice builder with XSD-backed validation. The builder
|
|
73
|
+
has no HTTP dependency and can be used on its own.
|
|
74
|
+
email:
|
|
75
|
+
- tibor@timcraft.pl
|
|
76
|
+
executables: []
|
|
77
|
+
extensions: []
|
|
78
|
+
extra_rdoc_files: []
|
|
79
|
+
files:
|
|
80
|
+
- CHANGELOG.md
|
|
81
|
+
- CONTRIBUTING.md
|
|
82
|
+
- LICENSE
|
|
83
|
+
- README.md
|
|
84
|
+
- SECURITY.md
|
|
85
|
+
- docs/REFERENCE.md
|
|
86
|
+
- docs/errors.md
|
|
87
|
+
- lib/ksef.rb
|
|
88
|
+
- lib/ksef/configuration.rb
|
|
89
|
+
- lib/ksef/environments.rb
|
|
90
|
+
- lib/ksef/errors.rb
|
|
91
|
+
- lib/ksef/fa3/schema/LICENSE.upstream.txt
|
|
92
|
+
- lib/ksef/fa3/schema/bazowe/ElementarneTypyDanych_v10-0E.xsd
|
|
93
|
+
- lib/ksef/fa3/schema/bazowe/KodyKrajow_v10-0E.xsd
|
|
94
|
+
- lib/ksef/fa3/schema/bazowe/StrukturyDanych_v10-0E.xsd
|
|
95
|
+
- lib/ksef/fa3/schema/schemat_FA(3)_v1-0E.xsd
|
|
96
|
+
- lib/ksef/http/connection.rb
|
|
97
|
+
- lib/ksef/http/error_handler.rb
|
|
98
|
+
- lib/ksef/http/system_warning.rb
|
|
99
|
+
- lib/ksef/problem_details.rb
|
|
100
|
+
- lib/ksef/retry_policy.rb
|
|
101
|
+
- lib/ksef/version.rb
|
|
102
|
+
- lib/ksef_client.rb
|
|
103
|
+
homepage: https://github.com/tibortc/ksef_client
|
|
104
|
+
licenses:
|
|
105
|
+
- MIT
|
|
106
|
+
metadata:
|
|
107
|
+
rubygems_mfa_required: 'true'
|
|
108
|
+
source_code_uri: https://github.com/tibortc/ksef_client
|
|
109
|
+
changelog_uri: https://github.com/tibortc/ksef_client/blob/main/CHANGELOG.md
|
|
110
|
+
bug_tracker_uri: https://github.com/tibortc/ksef_client/issues
|
|
111
|
+
documentation_uri: https://rubydoc.info/gems/ksef_client/0.1.0.rc1
|
|
112
|
+
rdoc_options: []
|
|
113
|
+
require_paths:
|
|
114
|
+
- lib
|
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: 3.2.0
|
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
requirements: []
|
|
126
|
+
rubygems_version: 4.0.16
|
|
127
|
+
specification_version: 4
|
|
128
|
+
summary: Ruby client for KSeF 2.0 (Polish National e-Invoice System) with an FA(3)
|
|
129
|
+
invoice builder
|
|
130
|
+
test_files: []
|