super_pdp 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +88 -0
- data/lib/super_pdp/client.rb +201 -0
- data/lib/super_pdp/error.rb +19 -0
- data/lib/super_pdp/version.rb +5 -0
- data/lib/super_pdp.rb +9 -0
- metadata +121 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 80df88af44b2d803aa180b0d205329440167089782755f4ac1635b94a5066d54
|
|
4
|
+
data.tar.gz: 5083f9d514fb7163910a0d8ed9b3129762c1f561e11ceb50ec3f0f214148f56f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 252df23df32876233d65684b4afb80b2756e788a7b51b0d6dc3fc6cc3b504dd83a15064ab69ff49a09432fd97b2dcccf7d5fb32180d04fbf21f3b19577cb5baf
|
|
7
|
+
data.tar.gz: 2ce4577ad5003b603dba86a1bdd1946bc45735b3631c686e82e883ff236e547152478b5992bc18375241fc4a697248a63d4631caa6651c0e54dd11e81e27d895
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Demoncy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# super_pdp
|
|
2
|
+
|
|
3
|
+
Thin, dependency-free Ruby client for the [SUPER PDP API](https://api.superpdp.tech) —
|
|
4
|
+
send and receive invoices under France's e-invoicing reform (Peppol network).
|
|
5
|
+
|
|
6
|
+
Stdlib only (`net/http` + `json`). No Faraday, no generated model classes.
|
|
7
|
+
|
|
8
|
+
> **Unofficial.** This is an independent, community-maintained client. It is
|
|
9
|
+
> **not** affiliated with, endorsed by, or supported by SUPER PDP. "SUPER PDP"
|
|
10
|
+
> and any related marks belong to their respective owners; they are used here
|
|
11
|
+
> only to describe what this gem talks to (nominative use). The gem calls the
|
|
12
|
+
> publicly documented HTTP API — no proprietary code is included — and is
|
|
13
|
+
> provided "as is" under the MIT license, with no warranty. The maintainer may
|
|
14
|
+
> update or **yank this gem from RubyGems at any time**; pin a version if you
|
|
15
|
+
> depend on it.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem "super_pdp"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Auth
|
|
24
|
+
|
|
25
|
+
Two modes, both OAuth2:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
# Client credentials — acts on your own data (api-key-like). Token is fetched
|
|
29
|
+
# and refreshed automatically.
|
|
30
|
+
pdp = SuperPDP.new(client_id: "...", client_secret: "...")
|
|
31
|
+
|
|
32
|
+
# Bearer token — acts on a user's behalf. You obtain the token yourself via the
|
|
33
|
+
# authorization_code flow (/oauth2/authorize) and pass it in.
|
|
34
|
+
pdp = SuperPDP.new(access_token: "user-token")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Use
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
pdp.companies_me
|
|
41
|
+
pdp.create_invoice({ en_invoice: { ... } }) # body is a positional Hash
|
|
42
|
+
pdp.create_invoice({ en_invoice: { ... } }, expand: %w[events]) # + query params
|
|
43
|
+
pdp.invoice(42, expand: %w[en_invoice events])
|
|
44
|
+
pdp.download_invoice(42) # raw bytes (PDF/XML), not JSON
|
|
45
|
+
pdp.create_invoice_event(invoice_id: 42, status: "fr:204")
|
|
46
|
+
|
|
47
|
+
# Cursor pagination handled for you:
|
|
48
|
+
pdp.each_item("/invoices", direction: "outgoing").each do |inv|
|
|
49
|
+
puts inv["id"]
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Named helpers exist for every endpoint (companies, invoices, invoice_events,
|
|
54
|
+
ereportings, b2c_transactions, b2c_payments, directory_entries,
|
|
55
|
+
french_directory, validation_reports, oauth2_sessions). For anything not
|
|
56
|
+
wrapped, drop to the raw verbs:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
pdp.get("/invoices", limit: 10)
|
|
60
|
+
pdp.post("/some/new/route", { key: "value" })
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Responses are parsed JSON (`Hash`/`Array`). Non-2xx raises `SuperPDP::APIError`
|
|
64
|
+
(`#status`, `#body`).
|
|
65
|
+
|
|
66
|
+
## Test
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
bundle exec rake test # or: bundle exec rake (runs tests + rubocop)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Contributing
|
|
73
|
+
|
|
74
|
+
Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). In short:
|
|
75
|
+
open an issue to discuss anything non-trivial, then send a PR with `bundle exec
|
|
76
|
+
rake` (tests + rubocop) passing.
|
|
77
|
+
|
|
78
|
+
## Releasing (maintainers)
|
|
79
|
+
|
|
80
|
+
Bump `SuperPDP::VERSION`, update `CHANGELOG.md`, then:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
bundle exec rake release # tags the version and pushes the .gem to RubyGems
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT — see [LICENSE.txt](LICENSE.txt).
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module SuperPDP
|
|
8
|
+
# Thin HTTP client for the SUPER PDP API.
|
|
9
|
+
#
|
|
10
|
+
# Auth: pass an +access_token+ directly (e.g. one you obtained via the
|
|
11
|
+
# authorization_code flow to act on a user's behalf), or pass
|
|
12
|
+
# +client_id+/+client_secret+ to have the client fetch and refresh a
|
|
13
|
+
# client_credentials token automatically (acts on your own data, api-key-like).
|
|
14
|
+
class Client
|
|
15
|
+
DEFAULT_BASE_URL = "https://api.superpdp.tech"
|
|
16
|
+
API_PREFIX = "/v1.beta"
|
|
17
|
+
|
|
18
|
+
attr_reader :base_url
|
|
19
|
+
|
|
20
|
+
def initialize(access_token: nil, client_id: nil, client_secret: nil,
|
|
21
|
+
base_url: DEFAULT_BASE_URL, open_timeout: 10, read_timeout: 60)
|
|
22
|
+
@access_token = access_token
|
|
23
|
+
@client_id = client_id
|
|
24
|
+
@client_secret = client_secret
|
|
25
|
+
@base_url = base_url
|
|
26
|
+
@open_timeout = open_timeout
|
|
27
|
+
@read_timeout = read_timeout
|
|
28
|
+
@token_expires_at = nil
|
|
29
|
+
@token_mutex = Mutex.new
|
|
30
|
+
|
|
31
|
+
return if access_token || (client_id && client_secret)
|
|
32
|
+
|
|
33
|
+
raise ArgumentError, "provide access_token, or client_id and client_secret"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# --- Resource helpers (thin wrappers over the raw verbs) --------------
|
|
37
|
+
|
|
38
|
+
def companies_me(**params) = get("/companies/me", params)
|
|
39
|
+
def enroll_company(body) = post("/companies", body)
|
|
40
|
+
def update_company_vat_regime(body) = patch("/companies", body)
|
|
41
|
+
|
|
42
|
+
def invoices(**params) = get("/invoices", params)
|
|
43
|
+
def invoice(id, **params) = get("/invoices/#{id}", params)
|
|
44
|
+
def create_invoice(body, **params) = post("/invoices", body, params)
|
|
45
|
+
def convert_invoice(body, **params) = post("/invoices/convert", body, params)
|
|
46
|
+
def validation_report(body) = post("/validation_reports", body)
|
|
47
|
+
def generate_test_invoice(**params) = get("/invoices/generate_test_invoice", params)
|
|
48
|
+
|
|
49
|
+
# Raw invoice file bytes (not JSON) — returns the String body.
|
|
50
|
+
def download_invoice(id, **params) = request(:get, "/invoices/#{id}/download", query: params, raw: true)
|
|
51
|
+
|
|
52
|
+
def invoice_events(**params) = get("/invoice_events", params)
|
|
53
|
+
def create_invoice_event(body) = post("/invoice_events", body)
|
|
54
|
+
|
|
55
|
+
def ereportings(**params) = get("/ereportings", params)
|
|
56
|
+
def ereporting(id, **params) = get("/ereportings/#{id}", params)
|
|
57
|
+
def preview_ereporting(**params) = get("/ereportings/preview", params)
|
|
58
|
+
|
|
59
|
+
def b2c_transactions(**params) = get("/b2c_transactions", params)
|
|
60
|
+
def create_b2c_transactions(body) = post("/b2c_transactions", body)
|
|
61
|
+
def b2c_payments(**params) = get("/b2c_payments", params)
|
|
62
|
+
def create_b2c_payment(body) = post("/b2c_payments", body)
|
|
63
|
+
|
|
64
|
+
def directory_entries(**params) = get("/directory_entries", params)
|
|
65
|
+
def directory_entry(id, **params) = get("/directory_entries/#{id}", params)
|
|
66
|
+
def create_directory_entry(body) = post("/directory_entries", body)
|
|
67
|
+
def delete_directory_entry(id) = delete("/directory_entries/#{id}")
|
|
68
|
+
|
|
69
|
+
def french_directory_companies(**params) = get("/french_directory/companies", params)
|
|
70
|
+
def french_directory_entries(**params) = get("/french_directory/entries", params)
|
|
71
|
+
|
|
72
|
+
def oauth2_session_me(**params) = get("/oauth2_sessions/me", params)
|
|
73
|
+
|
|
74
|
+
# Iterate a list endpoint across all pages, yielding each item.
|
|
75
|
+
# Uses the API's cursor pagination (starting_after_id + has_after).
|
|
76
|
+
# Returns an Enumerator when no block is given.
|
|
77
|
+
def each_item(path, **params, &block)
|
|
78
|
+
return enum_for(:each_item, path, **params) unless block_given?
|
|
79
|
+
|
|
80
|
+
cursor = params[:starting_after_id]
|
|
81
|
+
loop do
|
|
82
|
+
page = get(path, params.merge(starting_after_id: cursor).compact)
|
|
83
|
+
data = page["data"] || []
|
|
84
|
+
data.each(&block)
|
|
85
|
+
break unless page["has_after"] && !data.empty?
|
|
86
|
+
|
|
87
|
+
cursor = data.last["id"]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# --- Raw verbs --------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
def get(path, query = {}) = request(:get, path, query: query)
|
|
94
|
+
def post(path, body = {}, query = {}) = request(:post, path, body: body, query: query)
|
|
95
|
+
def patch(path, body = {}, query = {}) = request(:patch, path, body: body, query: query)
|
|
96
|
+
def delete(path, query = {}) = request(:delete, path, query: query)
|
|
97
|
+
|
|
98
|
+
def request(method, path, query: {}, body: nil, raw: false)
|
|
99
|
+
uri = build_uri(path, query)
|
|
100
|
+
req = build_request(method, uri, body)
|
|
101
|
+
req["Authorization"] = "Bearer #{token}"
|
|
102
|
+
|
|
103
|
+
res = http(uri).request(req)
|
|
104
|
+
handle_response(res, raw: raw)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def build_uri(path, query)
|
|
110
|
+
full = path.start_with?("/v1") || path.start_with?("http") ? path : "#{API_PREFIX}#{path}"
|
|
111
|
+
uri = URI.join(@base_url, full)
|
|
112
|
+
q = flatten_query(query || {})
|
|
113
|
+
uri.query = URI.encode_www_form(q) unless q.empty?
|
|
114
|
+
uri
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Encode array params as repeated `key[]=v` pairs (matches expand[] etc).
|
|
118
|
+
def flatten_query(query)
|
|
119
|
+
query.compact.flat_map do |k, v|
|
|
120
|
+
v.is_a?(Array) ? v.map { |item| ["#{k}[]", item] } : [[k.to_s, v]]
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def build_request(method, uri, body)
|
|
125
|
+
klass = {
|
|
126
|
+
get: Net::HTTP::Get, post: Net::HTTP::Post,
|
|
127
|
+
patch: Net::HTTP::Patch, delete: Net::HTTP::Delete
|
|
128
|
+
}.fetch(method)
|
|
129
|
+
req = klass.new(uri)
|
|
130
|
+
req["Accept"] = "application/json"
|
|
131
|
+
if body
|
|
132
|
+
req["Content-Type"] = "application/json"
|
|
133
|
+
req.body = body.is_a?(String) ? body : JSON.generate(body)
|
|
134
|
+
end
|
|
135
|
+
req
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def http(uri)
|
|
139
|
+
Net::HTTP.new(uri.host, uri.port).tap do |h|
|
|
140
|
+
h.use_ssl = uri.scheme == "https"
|
|
141
|
+
h.open_timeout = @open_timeout
|
|
142
|
+
h.read_timeout = @read_timeout
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def handle_response(res, raw:)
|
|
147
|
+
status = res.code.to_i
|
|
148
|
+
body = raw ? res.body : parse_json(res.body)
|
|
149
|
+
return body if status.between?(200, 299)
|
|
150
|
+
|
|
151
|
+
raise APIError.new(status, body)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def parse_json(str)
|
|
155
|
+
return nil if str.nil? || str.empty?
|
|
156
|
+
|
|
157
|
+
JSON.parse(str)
|
|
158
|
+
rescue JSON::ParserError
|
|
159
|
+
str
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# --- OAuth2 -----------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
def token
|
|
165
|
+
# Lock so a shared client in a threaded server (Puma/Sidekiq) doesn't
|
|
166
|
+
# race two concurrent refreshes. Double-checked inside the lock.
|
|
167
|
+
@token_mutex.synchronize do
|
|
168
|
+
return @access_token if @access_token && !expired?
|
|
169
|
+
|
|
170
|
+
fetch_client_credentials_token
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def expired?
|
|
175
|
+
@token_expires_at && Time.now >= @token_expires_at
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def fetch_client_credentials_token
|
|
179
|
+
unless @client_id && @client_secret
|
|
180
|
+
raise AuthError, "access_token missing/expired and no client credentials to refresh it"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
uri = URI.join(@base_url, "/oauth2/token")
|
|
184
|
+
req = Net::HTTP::Post.new(uri)
|
|
185
|
+
req["Accept"] = "application/json"
|
|
186
|
+
req.set_form_data(
|
|
187
|
+
grant_type: "client_credentials",
|
|
188
|
+
client_id: @client_id,
|
|
189
|
+
client_secret: @client_secret
|
|
190
|
+
)
|
|
191
|
+
res = http(uri).request(req)
|
|
192
|
+
raise AuthError, "token request failed (#{res.code}): #{res.body}" unless res.code.to_i.between?(200, 299)
|
|
193
|
+
|
|
194
|
+
data = JSON.parse(res.body)
|
|
195
|
+
@access_token = data.fetch("access_token")
|
|
196
|
+
# Refresh 60s early. Default to 1h if the server omits expires_in.
|
|
197
|
+
@token_expires_at = Time.now + (data["expires_in"] || 3600).to_i - 60
|
|
198
|
+
@access_token
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SuperPDP
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
# Raised for any non-2xx HTTP response. Carries the status and parsed body.
|
|
7
|
+
class APIError < Error
|
|
8
|
+
attr_reader :status, :body
|
|
9
|
+
|
|
10
|
+
def initialize(status, body)
|
|
11
|
+
@status = status
|
|
12
|
+
@body = body
|
|
13
|
+
message = body.is_a?(Hash) ? (body["message"] || body["error"] || body.to_s) : body.to_s
|
|
14
|
+
super("SuperPDP API error #{status}: #{message}")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class AuthError < Error; end
|
|
19
|
+
end
|
data/lib/super_pdp.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: super_pdp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Thomas Demoncy
|
|
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: minitest
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '5.0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '5.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rubocop
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.60'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.60'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rubocop-minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.35'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.35'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rubocop-rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0.6'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0.6'
|
|
82
|
+
description: Thin, dependency-free Ruby client for the SUPER PDP API (France electronic
|
|
83
|
+
invoicing reform / Peppol).
|
|
84
|
+
email:
|
|
85
|
+
- thomas.demoncy@gmail.com
|
|
86
|
+
executables: []
|
|
87
|
+
extensions: []
|
|
88
|
+
extra_rdoc_files: []
|
|
89
|
+
files:
|
|
90
|
+
- LICENSE.txt
|
|
91
|
+
- README.md
|
|
92
|
+
- lib/super_pdp.rb
|
|
93
|
+
- lib/super_pdp/client.rb
|
|
94
|
+
- lib/super_pdp/error.rb
|
|
95
|
+
- lib/super_pdp/version.rb
|
|
96
|
+
homepage: https://api.superpdp.tech
|
|
97
|
+
licenses:
|
|
98
|
+
- MIT
|
|
99
|
+
metadata:
|
|
100
|
+
homepage_uri: https://api.superpdp.tech
|
|
101
|
+
source_code_uri: https://github.com/ThomasDmnc/supepdp-gem
|
|
102
|
+
changelog_uri: https://github.com/ThomasDmnc/supepdp-gem/blob/main/CHANGELOG.md
|
|
103
|
+
rubygems_mfa_required: 'true'
|
|
104
|
+
rdoc_options: []
|
|
105
|
+
require_paths:
|
|
106
|
+
- lib
|
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
- - ">="
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '3.0'
|
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0'
|
|
117
|
+
requirements: []
|
|
118
|
+
rubygems_version: 3.6.9
|
|
119
|
+
specification_version: 4
|
|
120
|
+
summary: Ruby client for the SUPER PDP e-invoicing API
|
|
121
|
+
test_files: []
|