smartbill-sdk 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 (85) hide show
  1. checksums.yaml +7 -0
  2. data/AGENTS.md +211 -0
  3. data/CHANGELOG.md +18 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +265 -0
  6. data/Rakefile +22 -0
  7. data/Steepfile +6 -0
  8. data/docs/openapi.json +9741 -0
  9. data/examples/create_estimate_sync.rb +53 -0
  10. data/examples/create_invoice_eur_product_in_ron.rb +100 -0
  11. data/examples/create_invoice_sync.rb +57 -0
  12. data/examples/create_payment_sync.rb +36 -0
  13. data/examples/fiscal_receipt_sync.rb +50 -0
  14. data/examples/invoice_lifecycle_sync.rb +43 -0
  15. data/examples/list_series_sync.rb +27 -0
  16. data/examples/send_email_sync.rb +40 -0
  17. data/examples/taxes_and_stocks_sync.rb +48 -0
  18. data/lib/smartbill/sdk/api_error.rb +27 -0
  19. data/lib/smartbill/sdk/auth_error.rb +8 -0
  20. data/lib/smartbill/sdk/client.rb +68 -0
  21. data/lib/smartbill/sdk/contracts/base.rb +33 -0
  22. data/lib/smartbill/sdk/contracts/email_contract.rb +29 -0
  23. data/lib/smartbill/sdk/contracts/estimate_contract.rb +18 -0
  24. data/lib/smartbill/sdk/contracts/invoice_contract.rb +29 -0
  25. data/lib/smartbill/sdk/contracts/invoice_payment_contract.rb +18 -0
  26. data/lib/smartbill/sdk/contracts/invoice_ref_contract.rb +19 -0
  27. data/lib/smartbill/sdk/contracts/payment_contract.rb +37 -0
  28. data/lib/smartbill/sdk/contracts/storno_contract.rb +17 -0
  29. data/lib/smartbill/sdk/contracts.rb +19 -0
  30. data/lib/smartbill/sdk/error.rb +8 -0
  31. data/lib/smartbill/sdk/models/base_response.rb +16 -0
  32. data/lib/smartbill/sdk/models/client.rb +26 -0
  33. data/lib/smartbill/sdk/models/discount_type.rb +13 -0
  34. data/lib/smartbill/sdk/models/document_type.rb +13 -0
  35. data/lib/smartbill/sdk/models/email_document.rb +23 -0
  36. data/lib/smartbill/sdk/models/email_response.rb +12 -0
  37. data/lib/smartbill/sdk/models/email_status.rb +13 -0
  38. data/lib/smartbill/sdk/models/estimate.rb +32 -0
  39. data/lib/smartbill/sdk/models/fiscal_receipt_response.rb +16 -0
  40. data/lib/smartbill/sdk/models/invoice.rb +44 -0
  41. data/lib/smartbill/sdk/models/invoice_create_response.rb +10 -0
  42. data/lib/smartbill/sdk/models/invoice_payment.rb +15 -0
  43. data/lib/smartbill/sdk/models/invoice_ref.rb +24 -0
  44. data/lib/smartbill/sdk/models/payment.rb +49 -0
  45. data/lib/smartbill/sdk/models/payment_status_response.rb +19 -0
  46. data/lib/smartbill/sdk/models/payment_type.rb +22 -0
  47. data/lib/smartbill/sdk/models/product.rb +38 -0
  48. data/lib/smartbill/sdk/models/proforma_invoices_response.rb +13 -0
  49. data/lib/smartbill/sdk/models/series.rb +14 -0
  50. data/lib/smartbill/sdk/models/series_list_response.rb +14 -0
  51. data/lib/smartbill/sdk/models/stock_list.rb +13 -0
  52. data/lib/smartbill/sdk/models/stock_product.rb +15 -0
  53. data/lib/smartbill/sdk/models/stock_warehouse.rb +13 -0
  54. data/lib/smartbill/sdk/models/stocks_response.rb +14 -0
  55. data/lib/smartbill/sdk/models/storno_request.rb +17 -0
  56. data/lib/smartbill/sdk/models/storno_response.rb +14 -0
  57. data/lib/smartbill/sdk/models/struct.rb +102 -0
  58. data/lib/smartbill/sdk/models/tax.rb +14 -0
  59. data/lib/smartbill/sdk/models/taxes_response.rb +14 -0
  60. data/lib/smartbill/sdk/models.rb +17 -0
  61. data/lib/smartbill/sdk/net_http_adapter.rb +62 -0
  62. data/lib/smartbill/sdk/rate_limit_error.rb +9 -0
  63. data/lib/smartbill/sdk/response.rb +12 -0
  64. data/lib/smartbill/sdk/services/base_service.rb +39 -0
  65. data/lib/smartbill/sdk/services/configuration_service.rb +29 -0
  66. data/lib/smartbill/sdk/services/email_service.rb +18 -0
  67. data/lib/smartbill/sdk/services/estimates_service.rb +60 -0
  68. data/lib/smartbill/sdk/services/invoices_service.rb +69 -0
  69. data/lib/smartbill/sdk/services/payments_service.rb +50 -0
  70. data/lib/smartbill/sdk/services/stocks_service.rb +21 -0
  71. data/lib/smartbill/sdk/services.rb +30 -0
  72. data/lib/smartbill/sdk/transport/rate_limiter.rb +49 -0
  73. data/lib/smartbill/sdk/transport/request.rb +17 -0
  74. data/lib/smartbill/sdk/transport.rb +162 -0
  75. data/lib/smartbill/sdk/transport_error.rb +8 -0
  76. data/lib/smartbill/sdk/types.rb +22 -0
  77. data/lib/smartbill/sdk/validation_error.rb +8 -0
  78. data/lib/smartbill/sdk/version.rb +7 -0
  79. data/lib/smartbill/sdk.rb +31 -0
  80. data/sig/smartbill/sdk.rbs +661 -0
  81. data/skills/README.md +35 -0
  82. data/skills/smartbill-email/SKILL.md +120 -0
  83. data/skills/smartbill-invoices/SKILL.md +178 -0
  84. data/skills/smartbill-payments/SKILL.md +194 -0
  85. metadata +214 -0
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+
6
+ module Smartbill
7
+ module Sdk
8
+ # Shared transport logic for the SmartBill clients.
9
+ #
10
+ # The SmartBill API uses HTTP Basic Auth with +username:token+ and
11
+ # returns JSON envelopes whose root key depends on the endpoint
12
+ # (+sbcResponse+, +Response+, +sbcInvoicePaymentStatusResponse+,
13
+ # +sbcSeries+, +sbcTaxes+, +stocks+, +Fault+). This module centralises
14
+ # request building, auth, envelope unwrapping and error mapping so the
15
+ # clients stay DRY.
16
+ module Transport
17
+ DEFAULT_BASE_URL = "https://ws.smartbill.ro/SBORO/api/"
18
+ DEFAULT_TIMEOUT = 30.0
19
+
20
+ # Envelope root keys used by SmartBill responses.
21
+ ENVELOPE_KEYS = %w[
22
+ sbcResponse
23
+ Response
24
+ sbcInvoicePaymentStatusResponse
25
+ sbcSeries
26
+ sbcTaxes
27
+ stocks
28
+ Fault
29
+ ].freeze
30
+
31
+ # Fields that may carry an error message inside an envelope.
32
+ ERROR_FIELDS = %w[errorText errorTextError].freeze
33
+
34
+ # Build the +Authorization: Basic ...+ header value for +username:token+.
35
+ def self.build_auth_header(username, token)
36
+ "Basic #{Base64.strict_encode64("#{username}:#{token}")}"
37
+ end
38
+
39
+ # Alias kept for parity with the Python SDK.
40
+ def self.build_auth(username, token)
41
+ build_auth_header(username, token)
42
+ end
43
+
44
+ # Construct a {Request} with SmartBill default headers.
45
+ def self.build_request(method:, base_url:, path:, params: nil, json_body: nil,
46
+ accept: "application/json", content_type: "application/json",
47
+ auth_header: nil)
48
+ url = path.start_with?("http") ? path : "#{base_url.chomp("/")}/#{path.delete_prefix("/")}"
49
+ headers = { "Accept" => accept, "Content-Type" => content_type }
50
+ headers["Authorization"] = auth_header if auth_header
51
+ body = json_body ? JSON.generate(json_body) : nil
52
+ Request.new(http_method: method.upcase, url: url, headers: headers, query: params, body: body)
53
+ end
54
+
55
+ # Unwrap the SmartBill response envelope and return its inner object.
56
+ #
57
+ # If the payload is a Hash whose only key is one of the known envelope
58
+ # keys, the value under that key is returned. Otherwise the payload is
59
+ # returned unchanged.
60
+ def self.parse_envelope(payload)
61
+ return payload unless payload.is_a?(Hash)
62
+
63
+ if payload.size == 1
64
+ key, value = payload.first
65
+ return value if ENVELOPE_KEYS.include?(key)
66
+ end
67
+ # Some envelopes nest under a known key with sibling fields.
68
+ ENVELOPE_KEYS.each do |key|
69
+ next unless payload.key?(key)
70
+
71
+ inner = payload[key]
72
+ return inner if inner.is_a?(Hash)
73
+ end
74
+ payload
75
+ end
76
+
77
+ # Extract +(error_text, message)+ from an envelope.
78
+ def self.extract_error(envelope)
79
+ return ["", nil] unless envelope.is_a?(Hash)
80
+
81
+ error_text = ""
82
+ ERROR_FIELDS.each do |field|
83
+ value = envelope[field]
84
+ next unless value.is_a?(String) && !value.strip.empty?
85
+
86
+ error_text = value
87
+ break
88
+ end
89
+ message = envelope["message"]
90
+ message = nil unless message.is_a?(String)
91
+ [error_text, message]
92
+ end
93
+
94
+ # Validate a {Response} and return its parsed payload.
95
+ #
96
+ # Raises the appropriate {Error} subclass for auth / rate-limit / API
97
+ # errors. When +binary+ is true, the raw body String is returned
98
+ # (used for PDF endpoints).
99
+ def self.handle_response(response, binary: false)
100
+ status = response.status
101
+ raise auth_error(response) if status == 401
102
+ raise rate_limit_error if status == 403
103
+
104
+ return response.body if binary && (200..299).cover?(status)
105
+ return handle_success(response, status, binary: binary) if (200..299).cover?(status)
106
+
107
+ raise api_error_from(response, status)
108
+ end
109
+
110
+ def self.auth_error(response)
111
+ AuthError.new(
112
+ "Authentication failed (401). Check username/token and company CIF. " \
113
+ "Body: #{(response.body || "")[0, 200]}"
114
+ )
115
+ end
116
+
117
+ def self.rate_limit_error
118
+ RateLimitError.new(
119
+ "Access blocked (403): rate limit exceeded. " \
120
+ "SmartBill blocks access for 10 minutes after >30 calls/10s."
121
+ )
122
+ end
123
+
124
+ def self.handle_success(response, status, binary:)
125
+ content_type = response.content_type.to_s
126
+ if content_type.include?("application/octet-stream") || response.body.nil? || response.body.empty?
127
+ return binary ? response.body : nil
128
+ end
129
+
130
+ payload = begin
131
+ JSON.parse(response.body)
132
+ rescue JSON::ParserError => e
133
+ raise TransportError, "Failed to decode JSON response: #{e.message}"
134
+ end
135
+ envelope = parse_envelope(payload)
136
+ error_text, message = extract_error(envelope)
137
+ raise APIError.new(error_text: error_text, message: message, status_code: status) unless error_text.empty?
138
+
139
+ envelope
140
+ end
141
+
142
+ def self.api_error_from(response, status)
143
+ error_text = ""
144
+ message = nil
145
+ payload = begin
146
+ JSON.parse(response.body || "")
147
+ rescue JSON::ParserError
148
+ nil
149
+ end
150
+ if payload
151
+ envelope = parse_envelope(payload)
152
+ error_text, message = extract_error(envelope)
153
+ else
154
+ error_text = (response.body || "")[0, 300]
155
+ end
156
+ APIError.new(error_text: error_text, message: message, status_code: status)
157
+ end
158
+
159
+ private_class_method :auth_error, :rate_limit_error, :handle_success, :api_error_from
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartbill
4
+ module Sdk
5
+ # Raised on a network / transport-level failure.
6
+ class TransportError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-types"
4
+
5
+ module Smartbill
6
+ module Sdk
7
+ # Shared {Dry::Types} module used by the model structs and the
8
+ # validation contracts.
9
+ #
10
+ # `Strict::` types raise on wrong types (no silent coercion); `Coercible::`
11
+ # types coerce strings/numbers (e.g. `"10"` → `10.0`). Optional fields use
12
+ # `.optional.default(nil)`; collections use `Types::Array.of(...)`.
13
+ module Types
14
+ include Dry::Types()
15
+
16
+ # A value that may be either a strict String or a strict Integer — the
17
+ # SmartBill API returns some fields (e.g. +id+, +nextNumber+, +code+)
18
+ # as either, depending on the endpoint.
19
+ StrOrInt = Strict::String | Strict::Integer
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartbill
4
+ module Sdk
5
+ # Raised when a model fails validation (missing required fields).
6
+ class ValidationError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartbill
4
+ module Sdk
5
+ VERSION = "1.0.0"
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # smartbill-sdk — Ruby SDK for the SmartBill Cloud REST API.
4
+ #
5
+ # Provides a synchronous {Client} with typed request/response models for
6
+ # every endpoint in the SmartBill OpenAPI specification.
7
+ #
8
+ # Constants under `Smartbill::Sdk` are autoloaded by Zeitwerk from
9
+ # `lib/smartbill/sdk/` (one file per constant). `require "smartbill/sdk"`
10
+ # sets up the loader; constants are then resolved on first reference.
11
+
12
+ require "zeitwerk"
13
+
14
+ module Smartbill
15
+ # Namespace for the SmartBill SDK.
16
+ module Sdk
17
+ end
18
+ end
19
+
20
+ loader = Zeitwerk::Loader.new
21
+ loader.push_dir(File.expand_path("sdk", __dir__), namespace: Smartbill::Sdk)
22
+ # `version.rb` defines the `VERSION` constant (not `Version`), and
23
+ # `api_error.rb` defines `APIError` (not `ApiError`).
24
+ loader.inflector.inflect(
25
+ "version" => "VERSION",
26
+ "api_error" => "APIError"
27
+ )
28
+ loader.setup
29
+
30
+ # Public alias for the default SmartBill Cloud REST API base URL.
31
+ Smartbill::Sdk::DEFAULT_BASE_URL = Smartbill::Sdk::Transport::DEFAULT_BASE_URL