trevol 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.
Files changed (231) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +15 -0
  3. data/GENERADO.md +14 -0
  4. data/LICENSE +21 -0
  5. data/README.md +173 -0
  6. data/lib/trevol/client.rb +94 -0
  7. data/lib/trevol/errors.rb +152 -0
  8. data/lib/trevol/llave.rb +21 -0
  9. data/lib/trevol/paginacion.rb +41 -0
  10. data/lib/trevol/transporte.rb +173 -0
  11. data/lib/trevol/webhook.rb +100 -0
  12. data/lib/trevol.rb +18 -0
  13. data/lib/trevol_generado/api/balance_api.rb +220 -0
  14. data/lib/trevol_generado/api/credit_notes_api.rb +671 -0
  15. data/lib/trevol_generado/api/debit_notes_api.rb +671 -0
  16. data/lib/trevol_generado/api/equivalent_document_adjustments_api.rb +691 -0
  17. data/lib/trevol_generado/api/equivalent_documents_api.rb +698 -0
  18. data/lib/trevol_generado/api/events_api.rb +231 -0
  19. data/lib/trevol_generado/api/general_api.rb +201 -0
  20. data/lib/trevol_generado/api/invoices_api.rb +736 -0
  21. data/lib/trevol_generado/api/payroll_adjustments_api.rb +637 -0
  22. data/lib/trevol_generado/api/payrolls_api.rb +637 -0
  23. data/lib/trevol_generado/api/radian_events_api.rb +431 -0
  24. data/lib/trevol_generado/api/requests_api.rb +97 -0
  25. data/lib/trevol_generado/api/support_document_adjustments_api.rb +565 -0
  26. data/lib/trevol_generado/api/support_documents_api.rb +565 -0
  27. data/lib/trevol_generado/api/webhook_endpoints_api.rb +556 -0
  28. data/lib/trevol_generado/api_client.rb +441 -0
  29. data/lib/trevol_generado/api_error.rb +58 -0
  30. data/lib/trevol_generado/api_model_base.rb +88 -0
  31. data/lib/trevol_generado/configuration.rb +392 -0
  32. data/lib/trevol_generado/models/acquirer.rb +209 -0
  33. data/lib/trevol_generado/models/air_collection.rb +205 -0
  34. data/lib/trevol_generado/models/air_extension.rb +216 -0
  35. data/lib/trevol_generado/models/allowance_charge.rb +250 -0
  36. data/lib/trevol_generado/models/alternative_currency.rb +180 -0
  37. data/lib/trevol_generado/models/balance.rb +244 -0
  38. data/lib/trevol_generado/models/balance_event_data.rb +172 -0
  39. data/lib/trevol_generado/models/balance_transaction.rb +265 -0
  40. data/lib/trevol_generado/models/balance_transaction_company.rb +172 -0
  41. data/lib/trevol_generado/models/balance_transaction_document.rb +173 -0
  42. data/lib/trevol_generado/models/balance_transaction_list.rb +191 -0
  43. data/lib/trevol_generado/models/carrier.rb +219 -0
  44. data/lib/trevol_generado/models/certificate_event_data.rb +180 -0
  45. data/lib/trevol_generado/models/cinema_extension.rb +214 -0
  46. data/lib/trevol_generado/models/cinema_screening.rb +256 -0
  47. data/lib/trevol_generado/models/company.rb +267 -0
  48. data/lib/trevol_generado/models/company_key.rb +182 -0
  49. data/lib/trevol_generado/models/company_numbering.rb +201 -0
  50. data/lib/trevol_generado/models/consumption.rb +246 -0
  51. data/lib/trevol_generado/models/consumption_adjustment.rb +204 -0
  52. data/lib/trevol_generado/models/contingency.rb +160 -0
  53. data/lib/trevol_generado/models/credit_note_list.rb +191 -0
  54. data/lib/trevol_generado/models/currency.rb +241 -0
  55. data/lib/trevol_generado/models/customer.rb +360 -0
  56. data/lib/trevol_generado/models/debit_note_list.rb +191 -0
  57. data/lib/trevol_generado/models/deleted_object.rb +178 -0
  58. data/lib/trevol_generado/models/delivery.rb +200 -0
  59. data/lib/trevol_generado/models/delivery_email.rb +181 -0
  60. data/lib/trevol_generado/models/delivery_terms.rb +169 -0
  61. data/lib/trevol_generado/models/dian_result.rb +182 -0
  62. data/lib/trevol_generado/models/dian_rule.rb +216 -0
  63. data/lib/trevol_generado/models/document.rb +432 -0
  64. data/lib/trevol_generado/models/document_event_data.rb +315 -0
  65. data/lib/trevol_generado/models/document_reference.rb +169 -0
  66. data/lib/trevol_generado/models/document_totals.rb +299 -0
  67. data/lib/trevol_generado/models/document_totals_currency.rb +197 -0
  68. data/lib/trevol_generado/models/document_totals_item.rb +196 -0
  69. data/lib/trevol_generado/models/document_totals_tax.rb +197 -0
  70. data/lib/trevol_generado/models/document_totals_withholding.rb +206 -0
  71. data/lib/trevol_generado/models/equivalent_adjustment_item.rb +374 -0
  72. data/lib/trevol_generado/models/equivalent_document.rb +470 -0
  73. data/lib/trevol_generado/models/equivalent_document_adjustment.rb +440 -0
  74. data/lib/trevol_generado/models/equivalent_document_adjustment_input.rb +388 -0
  75. data/lib/trevol_generado/models/equivalent_document_adjustment_list.rb +191 -0
  76. data/lib/trevol_generado/models/equivalent_document_contingency.rb +181 -0
  77. data/lib/trevol_generado/models/equivalent_document_contingency_backup.rb +171 -0
  78. data/lib/trevol_generado/models/equivalent_document_event_data.rb +313 -0
  79. data/lib/trevol_generado/models/equivalent_document_input.rb +365 -0
  80. data/lib/trevol_generado/models/equivalent_document_list.rb +191 -0
  81. data/lib/trevol_generado/models/equivalent_document_reference.rb +241 -0
  82. data/lib/trevol_generado/models/equivalent_item.rb +387 -0
  83. data/lib/trevol_generado/models/event.rb +223 -0
  84. data/lib/trevol_generado/models/event_claim.rb +170 -0
  85. data/lib/trevol_generado/models/event_delivery.rb +178 -0
  86. data/lib/trevol_generado/models/event_delivery_email.rb +161 -0
  87. data/lib/trevol_generado/models/event_dian_result.rb +173 -0
  88. data/lib/trevol_generado/models/event_dian_rule.rb +170 -0
  89. data/lib/trevol_generado/models/event_document_error.rb +170 -0
  90. data/lib/trevol_generado/models/event_equivalent_document_ref.rb +161 -0
  91. data/lib/trevol_generado/models/event_error.rb +170 -0
  92. data/lib/trevol_generado/models/event_invoice_ref.rb +161 -0
  93. data/lib/trevol_generado/models/event_list.rb +191 -0
  94. data/lib/trevol_generado/models/event_payroll_ref.rb +160 -0
  95. data/lib/trevol_generado/models/event_radian_invoice_ref.rb +181 -0
  96. data/lib/trevol_generado/models/event_resend_input.rb +177 -0
  97. data/lib/trevol_generado/models/event_support_document_ref.rb +161 -0
  98. data/lib/trevol_generado/models/extension.rb +215 -0
  99. data/lib/trevol_generado/models/foreign_address.rb +218 -0
  100. data/lib/trevol_generado/models/free_of_charge.rb +170 -0
  101. data/lib/trevol_generado/models/game.rb +203 -0
  102. data/lib/trevol_generado/models/game_credit.rb +204 -0
  103. data/lib/trevol_generado/models/games_extension.rb +216 -0
  104. data/lib/trevol_generado/models/games_venue.rb +204 -0
  105. data/lib/trevol_generado/models/interoperability.rb +205 -0
  106. data/lib/trevol_generado/models/interoperability_collection.rb +171 -0
  107. data/lib/trevol_generado/models/interoperability_data.rb +186 -0
  108. data/lib/trevol_generado/models/invoice_input.rb +364 -0
  109. data/lib/trevol_generado/models/invoice_list.rb +191 -0
  110. data/lib/trevol_generado/models/invoice_reference.rb +198 -0
  111. data/lib/trevol_generado/models/item.rb +439 -0
  112. data/lib/trevol_generado/models/item_allowance.rb +190 -0
  113. data/lib/trevol_generado/models/item_consignment.rb +286 -0
  114. data/lib/trevol_generado/models/item_mandator.rb +231 -0
  115. data/lib/trevol_generado/models/item_notary.rb +204 -0
  116. data/lib/trevol_generado/models/item_property.rb +223 -0
  117. data/lib/trevol_generado/models/item_tax.rb +246 -0
  118. data/lib/trevol_generado/models/item_withholding.rb +214 -0
  119. data/lib/trevol_generado/models/meter.rb +233 -0
  120. data/lib/trevol_generado/models/note_input.rb +329 -0
  121. data/lib/trevol_generado/models/numbering.rb +178 -0
  122. data/lib/trevol_generado/models/numbering_expiring_event_data.rb +209 -0
  123. data/lib/trevol_generado/models/numbering_low_event_data.rb +209 -0
  124. data/lib/trevol_generado/models/origin_note.rb +230 -0
  125. data/lib/trevol_generado/models/payment.rb +232 -0
  126. data/lib/trevol_generado/models/payment_method.rb +187 -0
  127. data/lib/trevol_generado/models/payroll.rb +455 -0
  128. data/lib/trevol_generado/models/payroll_adjustment.rb +467 -0
  129. data/lib/trevol_generado/models/payroll_adjustment_input.rb +362 -0
  130. data/lib/trevol_generado/models/payroll_adjustment_list.rb +191 -0
  131. data/lib/trevol_generado/models/payroll_basic.rb +204 -0
  132. data/lib/trevol_generado/models/payroll_compensated_vacation.rb +204 -0
  133. data/lib/trevol_generado/models/payroll_compensation.rb +205 -0
  134. data/lib/trevol_generado/models/payroll_deductions.rb +370 -0
  135. data/lib/trevol_generado/models/payroll_disability.rb +249 -0
  136. data/lib/trevol_generado/models/payroll_earnings.rb +406 -0
  137. data/lib/trevol_generado/models/payroll_employee.rb +437 -0
  138. data/lib/trevol_generado/models/payroll_employer.rb +238 -0
  139. data/lib/trevol_generado/models/payroll_epctv_bonus.rb +191 -0
  140. data/lib/trevol_generado/models/payroll_event_data.rb +303 -0
  141. data/lib/trevol_generado/models/payroll_files.rb +179 -0
  142. data/lib/trevol_generado/models/payroll_input.rb +420 -0
  143. data/lib/trevol_generado/models/payroll_leaves.rb +184 -0
  144. data/lib/trevol_generado/models/payroll_list.rb +191 -0
  145. data/lib/trevol_generado/models/payroll_loan.rb +204 -0
  146. data/lib/trevol_generado/models/payroll_novelty.rb +177 -0
  147. data/lib/trevol_generado/models/payroll_other_concept.rb +197 -0
  148. data/lib/trevol_generado/models/payroll_overtime.rb +262 -0
  149. data/lib/trevol_generado/models/payroll_paid_period.rb +222 -0
  150. data/lib/trevol_generado/models/payroll_payment.rb +215 -0
  151. data/lib/trevol_generado/models/payroll_percent_amount.rb +205 -0
  152. data/lib/trevol_generado/models/payroll_period.rb +247 -0
  153. data/lib/trevol_generado/models/payroll_place.rb +179 -0
  154. data/lib/trevol_generado/models/payroll_reference.rb +188 -0
  155. data/lib/trevol_generado/models/payroll_salary_amounts.rb +171 -0
  156. data/lib/trevol_generado/models/payroll_sanction.rb +205 -0
  157. data/lib/trevol_generado/models/payroll_service_bonus.rb +214 -0
  158. data/lib/trevol_generado/models/payroll_severance.rb +232 -0
  159. data/lib/trevol_generado/models/payroll_solidarity_fund.rb +191 -0
  160. data/lib/trevol_generado/models/payroll_totals.rb +188 -0
  161. data/lib/trevol_generado/models/payroll_transport.rb +181 -0
  162. data/lib/trevol_generado/models/payroll_unpaid_period.rb +195 -0
  163. data/lib/trevol_generado/models/payroll_vacation.rb +173 -0
  164. data/lib/trevol_generado/models/payroll_workplace.rb +188 -0
  165. data/lib/trevol_generado/models/period.rb +169 -0
  166. data/lib/trevol_generado/models/physical_document.rb +179 -0
  167. data/lib/trevol_generado/models/pos.rb +186 -0
  168. data/lib/trevol_generado/models/pos_buyer.rb +181 -0
  169. data/lib/trevol_generado/models/pos_register.rb +269 -0
  170. data/lib/trevol_generado/models/prepayment.rb +248 -0
  171. data/lib/trevol_generado/models/preview.rb +195 -0
  172. data/lib/trevol_generado/models/problem.rb +329 -0
  173. data/lib/trevol_generado/models/problem_error.rb +178 -0
  174. data/lib/trevol_generado/models/public_services_extension.rb +385 -0
  175. data/lib/trevol_generado/models/radian_event.rb +359 -0
  176. data/lib/trevol_generado/models/radian_event_data.rb +289 -0
  177. data/lib/trevol_generado/models/radian_event_input.rb +284 -0
  178. data/lib/trevol_generado/models/radian_event_invoice.rb +208 -0
  179. data/lib/trevol_generado/models/radian_event_list.rb +191 -0
  180. data/lib/trevol_generado/models/received_invoice.rb +200 -0
  181. data/lib/trevol_generado/models/received_invoice_issuer.rb +190 -0
  182. data/lib/trevol_generado/models/references.rb +182 -0
  183. data/lib/trevol_generado/models/request_list.rb +191 -0
  184. data/lib/trevol_generado/models/request_log.rb +275 -0
  185. data/lib/trevol_generado/models/seller.rb +385 -0
  186. data/lib/trevol_generado/models/shipment.rb +178 -0
  187. data/lib/trevol_generado/models/show_event.rb +276 -0
  188. data/lib/trevol_generado/models/show_extension.rb +203 -0
  189. data/lib/trevol_generado/models/show_producer.rb +212 -0
  190. data/lib/trevol_generado/models/standard_code.rb +169 -0
  191. data/lib/trevol_generado/models/stock_exchange_extension.rb +320 -0
  192. data/lib/trevol_generado/models/stock_exchange_prices.rb +169 -0
  193. data/lib/trevol_generado/models/support_adjustment_item.rb +295 -0
  194. data/lib/trevol_generado/models/support_adjustment_reference.rb +188 -0
  195. data/lib/trevol_generado/models/support_currency.rb +231 -0
  196. data/lib/trevol_generado/models/support_document.rb +401 -0
  197. data/lib/trevol_generado/models/support_document_adjustment.rb +409 -0
  198. data/lib/trevol_generado/models/support_document_adjustment_input.rb +331 -0
  199. data/lib/trevol_generado/models/support_document_adjustment_list.rb +191 -0
  200. data/lib/trevol_generado/models/support_document_delivery.rb +199 -0
  201. data/lib/trevol_generado/models/support_document_event_data.rb +305 -0
  202. data/lib/trevol_generado/models/support_document_input.rb +331 -0
  203. data/lib/trevol_generado/models/support_document_list.rb +191 -0
  204. data/lib/trevol_generado/models/support_document_reference.rb +198 -0
  205. data/lib/trevol_generado/models/support_item.rb +317 -0
  206. data/lib/trevol_generado/models/support_item_charge.rb +180 -0
  207. data/lib/trevol_generado/models/support_item_tax.rb +197 -0
  208. data/lib/trevol_generado/models/support_item_withholding.rb +205 -0
  209. data/lib/trevol_generado/models/tacit_acceptance.rb +224 -0
  210. data/lib/trevol_generado/models/tacit_acceptance_rule.rb +226 -0
  211. data/lib/trevol_generado/models/test_event_data.rb +171 -0
  212. data/lib/trevol_generado/models/third_party_charge.rb +277 -0
  213. data/lib/trevol_generado/models/third_party_company.rb +203 -0
  214. data/lib/trevol_generado/models/top_up.rb +199 -0
  215. data/lib/trevol_generado/models/top_up_buyer.rb +352 -0
  216. data/lib/trevol_generado/models/top_up_input.rb +187 -0
  217. data/lib/trevol_generado/models/top_up_wompi.rb +209 -0
  218. data/lib/trevol_generado/models/transport_extension.rb +281 -0
  219. data/lib/trevol_generado/models/webhook_delivery.rb +267 -0
  220. data/lib/trevol_generado/models/webhook_delivery_attempt.rb +192 -0
  221. data/lib/trevol_generado/models/webhook_delivery_list.rb +191 -0
  222. data/lib/trevol_generado/models/webhook_endpoint.rb +317 -0
  223. data/lib/trevol_generado/models/webhook_endpoint_input.rb +229 -0
  224. data/lib/trevol_generado/models/webhook_endpoint_list.rb +191 -0
  225. data/lib/trevol_generado/models/webhook_endpoint_rotate_input.rb +160 -0
  226. data/lib/trevol_generado/models/webhook_endpoint_update.rb +227 -0
  227. data/lib/trevol_generado/models/withholding.rb +224 -0
  228. data/lib/trevol_generado/tablas.rb +243 -0
  229. data/lib/trevol_generado/version.rb +15 -0
  230. data/lib/trevol_generado.rb +251 -0
  231. metadata +386 -0
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'securerandom'
5
+ require_relative 'errors'
6
+
7
+ module Trevol
8
+ # El transporte: lo que hace que las llamadas se comporten igual en los 7 SDK (docs/api/43 §2.6.2 a §2.6.5).
9
+ #
10
+ # Es un middleware de Faraday (el punto de extensión del código generado): pone las cabeceras y la Idempotency-Key, reintenta y convierte
11
+ # toda respuesta que no es 2xx en un error tipado que atraviesa el código generado sin envolverse (`ApiClient#call_api` solo atrapa
12
+ # `Faraday::TimeoutError` y `Faraday::ConnectionFailed`, y convierte en `ApiError` lo que le llega como respuesta).
13
+ class Transporte < Faraday::Middleware
14
+ ESTADOS_REINTENTABLES = [429, 500, 502, 503, 504].freeze
15
+ CODES_EN_CURSO = %w[request_in_progress previous_event_pending].freeze
16
+ PROBLEMA = 'application/problem+json'
17
+ ACCEPT = {
18
+ 'pdf' => "application/pdf, #{PROBLEMA}", 'xml' => "application/xml, #{PROBLEMA}", 'zip' => "application/zip, #{PROBLEMA}"
19
+ }.tap { |h| h.default = "application/json, #{PROBLEMA}" }.freeze
20
+ CLAVE_VALIDA = /\A[!-~]{1,255}\z/
21
+
22
+ # Qué pasó en un intento: `tipo` :respuesta (con `estado`) o :red (sin respuesta). `retry_after` en segundos, ya resuelto.
23
+ Resultado = Struct.new(:tipo, :estado, :code, :retryable, :retry_after, keyword_init: true)
24
+ Decision = Struct.new(:reintentar, :espera, :motivo)
25
+
26
+ # Lo que necesita el middleware. La llave nunca sale en un inspect.
27
+ Opciones = Struct.new(:llave, :api_version, :user_agent, :max_reintentos, :max_retry_after, :ruta_base, :dormir, :azar, :uuid, :ahora, keyword_init: true) do
28
+ def inspect = "#<Trevol::Transporte::Opciones api_version=#{api_version.inspect} max_reintentos=#{max_reintentos.inspect}>"
29
+ alias_method :to_s, :inspect
30
+ end
31
+
32
+ class << self
33
+ # La espera propia antes del intento siguiente: 0,5 · 2^(intento−1), con tope de 8 s, por un factor al azar en [0,75; 1].
34
+ def espera_propia(intento, azar)
35
+ [0.5 * (2**(intento - 1)), 8.0].min * (0.75 + (0.25 * azar))
36
+ end
37
+
38
+ # Función PURA: ¿se reintenta y cuánto se espera? Las mismas reglas y los mismos casos (`pruebas/datos/transporte.json`) en los 7 SDK.
39
+ def decidir(intento:, reintento:, max_reintentos:, max_retry_after:, azar:, resultado:)
40
+ r = resultado
41
+ return Decision.new(false, nil, 'exito') if r.tipo == :respuesta && r.estado && r.estado >= 200 && r.estado < 300
42
+
43
+ reintentable =
44
+ if r.tipo == :red
45
+ reintento == 'seguro'
46
+ elsif reintento == 'solo_429'
47
+ r.estado == 429
48
+ elsif r.estado == 402
49
+ false
50
+ else
51
+ ESTADOS_REINTENTABLES.include?(r.estado) || CODES_EN_CURSO.include?(r.code) || r.retryable == true
52
+ end
53
+ return Decision.new(false, nil, nil) unless reintentable
54
+ return Decision.new(false, nil, 'agotado') if intento > max_reintentos
55
+
56
+ propia = espera_propia(intento, azar)
57
+ pedida = r.tipo == :respuesta && r.retry_after && r.retry_after.positive? ? r.retry_after : 0.0
58
+ return Decision.new(false, nil, 'retry_after_excede') if pedida > max_retry_after
59
+
60
+ Decision.new(true, [pedida, propia].max, nil)
61
+ end
62
+
63
+ def validar_clave(clave)
64
+ return if clave.is_a?(String) && CLAVE_VALIDA.match?(clave)
65
+
66
+ raise ConfigurationError, 'La idempotency_key debe tener de 1 a 255 caracteres ASCII visibles (sin espacios).'
67
+ end
68
+
69
+ # La operación (tablas) de un método y una ruta SIN el prefijo /v1. Las rutas fijas ganan a las plantillas
70
+ # (GET /radian_events/tacit_acceptance frente a GET /radian_events/{id}).
71
+ def operacion(metodo, ruta)
72
+ indices[:exactas]["#{metodo} #{ruta}"] ||
73
+ indices[:plantillas].find { |o, re| o[:metodo] == metodo && re.match?(ruta) }&.first
74
+ end
75
+
76
+ private
77
+
78
+ def indices
79
+ @indices ||= begin
80
+ exactas = {}
81
+ plantillas = []
82
+ TrevolGenerado::Tablas::OPERACIONES.each do |o|
83
+ if o[:ruta].include?('{')
84
+ plantillas << [o, Regexp.new("\\A#{Regexp.escape(o[:ruta]).gsub(/\\\{[a-z_]+\\\}/, '[^/]+')}\\z")]
85
+ else
86
+ exactas["#{o[:metodo]} #{o[:ruta]}"] = o
87
+ end
88
+ end
89
+ { exactas: exactas, plantillas: plantillas }
90
+ end
91
+ end
92
+ end
93
+
94
+ def initialize(app, opciones)
95
+ super(app)
96
+ @o = opciones
97
+ end
98
+
99
+ def call(env)
100
+ metodo = env.method.to_s.upcase
101
+ ruta = env.url.path
102
+ ruta = ruta.delete_prefix(@o.ruta_base) unless @o.ruta_base.to_s.empty?
103
+ op = self.class.operacion(metodo, ruta)
104
+ reintento = op ? op[:reintento] : (metodo == 'GET' ? 'seguro' : 'solo_429')
105
+
106
+ # La Idempotency-Key: una por llamada, la misma en todos los reintentos.
107
+ clave = nil
108
+ if op && op[:idempotente]
109
+ dada = env.request_headers['Idempotency-Key']
110
+ if dada.nil?
111
+ clave = @o.uuid.call
112
+ else
113
+ self.class.validar_clave(dada)
114
+ clave = dada
115
+ end
116
+ env.request_headers['Idempotency-Key'] = clave
117
+ else
118
+ env.request_headers.delete('Idempotency-Key')
119
+ end
120
+ h = env.request_headers
121
+ h['Authorization'] = "Bearer #{@o.llave}"
122
+ h['Trevol-Version'] = @o.api_version
123
+ h['User-Agent'] = @o.user_agent
124
+ h['Accept'] = ACCEPT[op && op[:binario]]
125
+
126
+ cuerpo_pedido = env.body
127
+ destino = env.request.on_data # descarga en flujo: se junta por intento y solo la 2xx llega a quien la pidió
128
+ intento = 0
129
+ loop do
130
+ intento += 1
131
+ env.body = cuerpo_pedido
132
+ trozos = []
133
+ env.request.on_data = proc { |trozo, *| trozos << trozo.dup } if destino
134
+ respuesta = nil
135
+ causa = nil
136
+ begin
137
+ respuesta = @app.call(env)
138
+ rescue Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::SSLError => e
139
+ causa = e
140
+ end
141
+
142
+ if respuesta
143
+ estado = respuesta.status
144
+ if estado >= 200 && estado < 300
145
+ if destino
146
+ total = 0
147
+ trozos.each { |t| total += t.bytesize; destino.call(t, total) }
148
+ env.request.on_data = destino
149
+ end
150
+ return respuesta
151
+ end
152
+ texto = respuesta.body.to_s
153
+ texto = trozos.join if texto.empty? && !trozos.empty?
154
+ error = Errores.de_respuesta(estado, texto.dup.force_encoding('UTF-8').scrub, respuesta.headers, clave, @o.ahora.call)
155
+ decision = self.class.decidir(
156
+ intento: intento, reintento: reintento, max_reintentos: @o.max_reintentos, max_retry_after: @o.max_retry_after, azar: @o.azar.call,
157
+ resultado: Resultado.new(tipo: :respuesta, estado: estado, code: error.code, retryable: error.retryable, retry_after: error.retry_after)
158
+ )
159
+ raise error unless decision.reintentar
160
+ else
161
+ decision = self.class.decidir(
162
+ intento: intento, reintento: reintento, max_reintentos: @o.max_reintentos, max_retry_after: @o.max_retry_after, azar: @o.azar.call,
163
+ resultado: Resultado.new(tipo: :red)
164
+ )
165
+ unless decision.reintentar
166
+ raise APIConnectionError.new("No pude conectar con la API de Trevol (#{causa.message.to_s[0, 200]}).", idempotency_key: clave), cause: causa
167
+ end
168
+ end
169
+ @o.dormir.call(decision.espera || 0.0)
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'json'
5
+ require 'standardwebhooks'
6
+ require_relative 'errors'
7
+
8
+ module Trevol
9
+ # Verificación de webhooks (docs/api/43 §2.6.9): Standard Webhooks sobre el CUERPO CRUDO (`request.raw_post`, `request.body.read`).
10
+ #
11
+ # La firma esperada la calcula la biblioteca abierta `standardwebhooks` (`StandardWebhooks::Webhook#sign`); la tolerancia de 5 minutos
12
+ # (con reloj inyectable en las pruebas), las cabeceras (en cualquier caja) y la comparación en tiempo constante son de este SDK: la
13
+ # biblioteca no deja inyectar el reloj, solo acepta cabeceras en minúsculas y entrega un Hash en vez del evento generado.
14
+ class Webhook
15
+ TOLERANCIA = 300 # segundos, hacia atrás y hacia adelante
16
+ NOMBRES = %w[webhook-id webhook-timestamp webhook-signature].freeze
17
+
18
+ # @param secret [String] el secreto del endpoint (`whsec_…`), que se muestra una sola vez al crearlo o rotarlo
19
+ # @param ahora [#call, nil] solo para pruebas: el reloj, en segundos
20
+ def initialize(secret, ahora: nil)
21
+ secret = secret.to_s.strip
22
+ raise ConfigurationError, 'Falta el secreto del webhook (whsec_…). Se muestra una sola vez al crear o rotar el endpoint.' if secret.empty? || secret == 'whsec_'
23
+
24
+ sin_prefijo = secret.delete_prefix('whsec_')
25
+ crudo = begin
26
+ Base64.strict_decode64(sin_prefijo)
27
+ rescue ArgumentError
28
+ nil
29
+ end
30
+ raise ConfigurationError, 'El secreto del webhook no tiene el formato esperado (whsec_ + base64).' if crudo.nil? || crudo.empty?
31
+
32
+ @firmador = StandardWebhooks::Webhook.new(secret)
33
+ @ahora = ahora || -> { Time.now.to_i }
34
+ end
35
+
36
+ # Las tres cabeceras en minúsculas, venga lo que venga (un Hash, `request.headers`, una lista de pares, valores en arreglo).
37
+ # La primera repetida gana.
38
+ def self.normalizar_cabeceras(cabeceras)
39
+ pares = cabeceras.respond_to?(:each_pair) ? cabeceras.each_pair.to_a : Array(cabeceras)
40
+ salida = {}
41
+ pares.each do |k, v|
42
+ nombre = k.to_s.downcase.tr('_', '-').sub(/\Ahttp-/, '')
43
+ v = v.first if v.is_a?(Array)
44
+ salida[nombre] ||= v.to_s if NOMBRES.include?(nombre) && !v.nil?
45
+ end
46
+ salida
47
+ end
48
+
49
+ # Verifica la firma y la fecha de una entrega y devuelve el evento (`TrevolGenerado::Event`).
50
+ # Lanza `Trevol::WebhookVerificationError` si algo no cuadra.
51
+ #
52
+ # Ojo: la verificación NO evita repeticiones dentro de los 5 minutos ni los reintentos de entrega: deduplica por `webhook-id`.
53
+ def verify(body, headers)
54
+ unless body.is_a?(String)
55
+ raise WebhookVerificationError.new(
56
+ 'El cuerpo del webhook debe ser el CUERPO CRUDO (String), no un objeto ya parseado: la firma se calcula sobre los bytes exactos. ' \
57
+ 'En Rails usa request.raw_post; en Sinatra, request.body.read.', :cuerpo
58
+ )
59
+ end
60
+ texto = body.dup.force_encoding('UTF-8')
61
+ raise WebhookVerificationError.new('El cuerpo del webhook no es UTF-8.', :cuerpo) unless texto.valid_encoding?
62
+
63
+ h = self.class.normalizar_cabeceras(headers)
64
+ NOMBRES.each do |n|
65
+ raise WebhookVerificationError.new("Falta la cabecera #{n}.", :cabeceras) if h[n].to_s.empty?
66
+ end
67
+ raise WebhookVerificationError.new('El webhook-timestamp no es un número de segundos.', :fecha) unless h['webhook-timestamp'].match?(/\A[0-9]{1,12}\z/)
68
+
69
+ marca = h['webhook-timestamp'].to_i
70
+ if (@ahora.call - marca).abs > TOLERANCIA
71
+ raise WebhookVerificationError.new('La fecha del webhook está fuera de la tolerancia de 5 minutos.', :fecha)
72
+ end
73
+
74
+ esperada = @firmador.sign(h['webhook-id'], marca.to_s, texto).split(',', 2)[1].to_s
75
+ coincide = false
76
+ h['webhook-signature'].split(' ').each do |firma|
77
+ version, valor = firma.split(',', 2)
78
+ coincide = true if version == 'v1' && valor && StandardWebhooks.secure_compare(esperada, valor) # basta una válida (rotación del secreto)
79
+ end
80
+ unless coincide
81
+ raise WebhookVerificationError.new('La firma del webhook no coincide: revisa el secreto y que el cuerpo sea el crudo, sin reescribir.', :firma)
82
+ end
83
+
84
+ begin
85
+ TrevolGenerado::Event.build_from_hash(JSON.parse(texto, symbolize_names: true))
86
+ rescue StandardError
87
+ raise WebhookVerificationError.new('El cuerpo firmado no es un evento válido.', :cuerpo)
88
+ end
89
+ end
90
+
91
+ # El `data` del evento como el modelo de su `type` (p. ej. `TrevolGenerado::DocumentEventData` para `invoice.accepted`). nil si el tipo
92
+ # es nuevo y este SDK todavía no lo conoce: el evento no se pierde, `evento.data` sigue ahí como Hash.
93
+ def event_data(evento)
94
+ nombre = TrevolGenerado::Tablas::EVENTO_MODELOS[evento.type.to_s]
95
+ return nil if nombre.nil? || evento.data.nil?
96
+
97
+ TrevolGenerado.const_get(nombre).build_from_hash(evento.data)
98
+ end
99
+ end
100
+ end
data/lib/trevol.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SDK de Ruby para la API de Trevol. Documentación: https://platform.trevol.si/docs
4
+ require 'trevol_generado'
5
+ require 'trevol_generado/tablas'
6
+ require_relative 'trevol/errors'
7
+ require_relative 'trevol/llave'
8
+ require_relative 'trevol/paginacion'
9
+ require_relative 'trevol/transporte'
10
+ require_relative 'trevol/webhook'
11
+
12
+ module Trevol
13
+ # La versión de este SDK y la de la API con que se generó (el valor de Trevol-Version por defecto).
14
+ VERSION = TrevolGenerado::Tablas::VERSION
15
+ API_VERSION = TrevolGenerado::Tablas::API_VERSION
16
+ end
17
+
18
+ require_relative 'trevol/client'
@@ -0,0 +1,220 @@
1
+ =begin
2
+ #API de Trevol
3
+
4
+ #Trevol API. Documentación: https://platform.trevol.si/docs
5
+
6
+ The version of the OpenAPI document: 2026-10-22
7
+
8
+ Generated by: https://openapi-generator.tech
9
+ Generator version: 7.26.0
10
+
11
+ =end
12
+
13
+ require 'cgi'
14
+
15
+ module TrevolGenerado
16
+ class BalanceApi
17
+ attr_accessor :api_client
18
+
19
+ def initialize(api_client = ApiClient.default)
20
+ @api_client = api_client
21
+ end
22
+ # Preparar una recarga del saldo (para el widget de Wompi)
23
+ # Prepara una recarga del saldo de la cuenta y devuelve lo que necesita el widget de Wompi (`WidgetCheckout`: `publicKey`, `currency`, `amountInCents`, `reference`, `signature: {integrity}` y `expirationTime`). El pago se hace en el widget; cuando Wompi lo aprueba, Trevol abona el saldo (y la factura de la recarga sale a nombre de `buyer`). Preparar no mueve saldo y la firma vence en una hora: si el widget venció, prepara otra. Solo llaves de escritura; exige `Idempotency-Key` (repetir con la misma clave devuelve la MISMA recarga). Sin `buyer`, la factura va a nombre de la empresa de la llave con sus datos (NIT, DV, razón social, correo, teléfono, dirección y municipio); si no alcanzan, 422 en `buyer.*`. El monto va entre la recarga mínima y la máxima (422 `top_up_below_minimum` / `top_up_above_maximum` con `minimum_cop` / `maximum_cop`). Es dinero real con cualquier llave (también `test`): el saldo es uno solo. Hasta 20 por llave cada 10 minutos (429).
24
+ # @param top_up_input [TopUpInput]
25
+ # @param [Hash] opts the optional parameters
26
+ # @option opts [String] :idempotency_key Obligatoria: 1 a 255 caracteres ASCII visibles, única por pedido (7 días).
27
+ # @return [TopUp]
28
+ def create_balance_top_up(top_up_input, opts = {})
29
+ data, _status_code, _headers = create_balance_top_up_with_http_info(top_up_input, opts)
30
+ data
31
+ end
32
+
33
+ # Preparar una recarga del saldo (para el widget de Wompi)
34
+ # Prepara una recarga del saldo de la cuenta y devuelve lo que necesita el widget de Wompi (&#x60;WidgetCheckout&#x60;: &#x60;publicKey&#x60;, &#x60;currency&#x60;, &#x60;amountInCents&#x60;, &#x60;reference&#x60;, &#x60;signature: {integrity}&#x60; y &#x60;expirationTime&#x60;). El pago se hace en el widget; cuando Wompi lo aprueba, Trevol abona el saldo (y la factura de la recarga sale a nombre de &#x60;buyer&#x60;). Preparar no mueve saldo y la firma vence en una hora: si el widget venció, prepara otra. Solo llaves de escritura; exige &#x60;Idempotency-Key&#x60; (repetir con la misma clave devuelve la MISMA recarga). Sin &#x60;buyer&#x60;, la factura va a nombre de la empresa de la llave con sus datos (NIT, DV, razón social, correo, teléfono, dirección y municipio); si no alcanzan, 422 en &#x60;buyer.*&#x60;. El monto va entre la recarga mínima y la máxima (422 &#x60;top_up_below_minimum&#x60; / &#x60;top_up_above_maximum&#x60; con &#x60;minimum_cop&#x60; / &#x60;maximum_cop&#x60;). Es dinero real con cualquier llave (también &#x60;test&#x60;): el saldo es uno solo. Hasta 20 por llave cada 10 minutos (429).
35
+ # @param top_up_input [TopUpInput]
36
+ # @param [Hash] opts the optional parameters
37
+ # @option opts [String] :idempotency_key Obligatoria: 1 a 255 caracteres ASCII visibles, única por pedido (7 días).
38
+ # @return [Array<(TopUp, Integer, Hash)>] TopUp data, response status code and response headers
39
+ def create_balance_top_up_with_http_info(top_up_input, opts = {})
40
+ if @api_client.config.debugging
41
+ @api_client.config.logger.debug 'Calling API: BalanceApi.create_balance_top_up ...'
42
+ end
43
+ # verify the required parameter 'top_up_input' is set
44
+ if @api_client.config.client_side_validation && top_up_input.nil?
45
+ fail ArgumentError, "Missing the required parameter 'top_up_input' when calling BalanceApi.create_balance_top_up"
46
+ end
47
+ # resource path
48
+ local_var_path = '/balance/top_ups'
49
+
50
+ # query parameters
51
+ query_params = opts[:query_params] || {}
52
+
53
+ # header parameters
54
+ header_params = opts[:header_params] || {}
55
+ # HTTP header 'Accept' (if needed)
56
+ header_params['Accept'] = @api_client.select_header_accept(['application/json', 'application/problem+json']) unless header_params['Accept']
57
+ # HTTP header 'Content-Type'
58
+ content_type = @api_client.select_header_content_type(['application/json'])
59
+ if !content_type.nil?
60
+ header_params['Content-Type'] = content_type
61
+ end
62
+ header_params[:'Idempotency-Key'] = opts[:'idempotency_key'] if !opts[:'idempotency_key'].nil?
63
+
64
+ # form parameters
65
+ form_params = opts[:form_params] || {}
66
+
67
+ # http body (model)
68
+ post_body = opts[:debug_body] || @api_client.object_to_http_body(top_up_input)
69
+
70
+ # return_type
71
+ return_type = opts[:debug_return_type] || 'TopUp'
72
+
73
+ # auth_names
74
+ auth_names = opts[:debug_auth_names] || ['llave']
75
+
76
+ new_options = opts.merge(
77
+ :operation => :"BalanceApi.create_balance_top_up",
78
+ :header_params => header_params,
79
+ :query_params => query_params,
80
+ :form_params => form_params,
81
+ :body => post_body,
82
+ :auth_names => auth_names,
83
+ :return_type => return_type
84
+ )
85
+
86
+ data, status_code, headers = @api_client.call_api(:POST, local_var_path, new_options)
87
+ if @api_client.config.debugging
88
+ @api_client.config.logger.debug "API called: BalanceApi#create_balance_top_up\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
89
+ end
90
+ return data, status_code, headers
91
+ end
92
+
93
+ # Los movimientos del saldo de la cuenta
94
+ # Los movimientos del saldo de la cuenta (de todas sus empresas), del más nuevo al más viejo: lo mismo que «Saldo y recargas» en la app. Un documento es UNA fila (`type: document`) con su `status`: `pending` (apartado mientras la DIAN lo valida), `charged` (validado y cobrado) o `released` (la DIAN lo rechazó: no se cobró y `amount` es 0); `company` dice qué empresa lo emitió. Lo demás es una fila por movimiento: `top_up` (recarga, con su `reference`), `top_up_reversal`, `bonus`, `adjustment`, `license_fee`, `expiration` u `other`. `amount` es lo que el movimiento hizo con el disponible, con signo; `available_after`, el disponible justo después. Filtro `type`: `all` (por defecto), `documents`, `funding` (todo lo que no es un documento) o `top_ups`. Paginación por cursor: `limit` (1 a 100, 25 por defecto) y `starting_after` (el `id` de la última fila que tienes).
95
+ # @param [Hash] opts the optional parameters
96
+ # @option opts [Integer] :limit (default to 25)
97
+ # @option opts [String] :starting_after
98
+ # @option opts [String] :type (default to 'all')
99
+ # @return [BalanceTransactionList]
100
+ def list_balance_transactions(opts = {})
101
+ data, _status_code, _headers = list_balance_transactions_with_http_info(opts)
102
+ data
103
+ end
104
+
105
+ # Los movimientos del saldo de la cuenta
106
+ # Los movimientos del saldo de la cuenta (de todas sus empresas), del más nuevo al más viejo: lo mismo que «Saldo y recargas» en la app. Un documento es UNA fila (&#x60;type: document&#x60;) con su &#x60;status&#x60;: &#x60;pending&#x60; (apartado mientras la DIAN lo valida), &#x60;charged&#x60; (validado y cobrado) o &#x60;released&#x60; (la DIAN lo rechazó: no se cobró y &#x60;amount&#x60; es 0); &#x60;company&#x60; dice qué empresa lo emitió. Lo demás es una fila por movimiento: &#x60;top_up&#x60; (recarga, con su &#x60;reference&#x60;), &#x60;top_up_reversal&#x60;, &#x60;bonus&#x60;, &#x60;adjustment&#x60;, &#x60;license_fee&#x60;, &#x60;expiration&#x60; u &#x60;other&#x60;. &#x60;amount&#x60; es lo que el movimiento hizo con el disponible, con signo; &#x60;available_after&#x60;, el disponible justo después. Filtro &#x60;type&#x60;: &#x60;all&#x60; (por defecto), &#x60;documents&#x60;, &#x60;funding&#x60; (todo lo que no es un documento) o &#x60;top_ups&#x60;. Paginación por cursor: &#x60;limit&#x60; (1 a 100, 25 por defecto) y &#x60;starting_after&#x60; (el &#x60;id&#x60; de la última fila que tienes).
107
+ # @param [Hash] opts the optional parameters
108
+ # @option opts [Integer] :limit (default to 25)
109
+ # @option opts [String] :starting_after
110
+ # @option opts [String] :type (default to 'all')
111
+ # @return [Array<(BalanceTransactionList, Integer, Hash)>] BalanceTransactionList data, response status code and response headers
112
+ def list_balance_transactions_with_http_info(opts = {})
113
+ if @api_client.config.debugging
114
+ @api_client.config.logger.debug 'Calling API: BalanceApi.list_balance_transactions ...'
115
+ end
116
+ allowable_values = ["all", "documents", "funding", "top_ups", "unknown_default_open_api"]
117
+ if @api_client.config.client_side_validation && opts[:'type'] && !allowable_values.include?(opts[:'type'])
118
+ fail ArgumentError, "invalid value for \"type\", must be one of #{allowable_values}"
119
+ end
120
+ # resource path
121
+ local_var_path = '/balance/transactions'
122
+
123
+ # query parameters
124
+ query_params = opts[:query_params] || {}
125
+ query_params[:'limit'] = opts[:'limit'] if !opts[:'limit'].nil?
126
+ query_params[:'starting_after'] = opts[:'starting_after'] if !opts[:'starting_after'].nil?
127
+ query_params[:'type'] = opts[:'type'] if !opts[:'type'].nil?
128
+
129
+ # header parameters
130
+ header_params = opts[:header_params] || {}
131
+ # HTTP header 'Accept' (if needed)
132
+ header_params['Accept'] = @api_client.select_header_accept(['application/json', 'application/problem+json']) unless header_params['Accept']
133
+
134
+ # form parameters
135
+ form_params = opts[:form_params] || {}
136
+
137
+ # http body (model)
138
+ post_body = opts[:debug_body]
139
+
140
+ # return_type
141
+ return_type = opts[:debug_return_type] || 'BalanceTransactionList'
142
+
143
+ # auth_names
144
+ auth_names = opts[:debug_auth_names] || ['llave']
145
+
146
+ new_options = opts.merge(
147
+ :operation => :"BalanceApi.list_balance_transactions",
148
+ :header_params => header_params,
149
+ :query_params => query_params,
150
+ :form_params => form_params,
151
+ :body => post_body,
152
+ :auth_names => auth_names,
153
+ :return_type => return_type
154
+ )
155
+
156
+ data, status_code, headers = @api_client.call_api(:GET, local_var_path, new_options)
157
+ if @api_client.config.debugging
158
+ @api_client.config.logger.debug "API called: BalanceApi#list_balance_transactions\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
159
+ end
160
+ return data, status_code, headers
161
+ end
162
+
163
+ # El saldo de la cuenta (lo comparten sus empresas)
164
+ # El saldo prepago de Trevol. Es de la **cuenta**, no de la empresa: todas las empresas de la cuenta lo comparten (`shared: true`) y cualquier llave de cualquiera de ellas, de lectura o de escritura, `test` o `live`, ve el mismo. Solo lo que se emite en producción (llaves `live`) lo consume; la habilitación (`test`) no cobra. `available` es lo que se puede gastar (en pesos); `held`, lo apartado por documentos que la DIAN todavía valida; `overdraft`, cuánto puede bajar `available` de cero al apartar un documento; `low_threshold`, el umbral del aviso de saldo bajo (el 20 % de la última recarga; `null` sin recargas). `level`: `empty` con `available` ≤ 0, `low` por debajo de `low_threshold`, si no `ok`. Con `available` por debajo del precio de un documento, emitir en producción responde 402 `insufficient_balance`. El evento `balance.low` avisa por webhook.
165
+ # @param [Hash] opts the optional parameters
166
+ # @return [Balance]
167
+ def retrieve_balance(opts = {})
168
+ data, _status_code, _headers = retrieve_balance_with_http_info(opts)
169
+ data
170
+ end
171
+
172
+ # El saldo de la cuenta (lo comparten sus empresas)
173
+ # El saldo prepago de Trevol. Es de la **cuenta**, no de la empresa: todas las empresas de la cuenta lo comparten (&#x60;shared: true&#x60;) y cualquier llave de cualquiera de ellas, de lectura o de escritura, &#x60;test&#x60; o &#x60;live&#x60;, ve el mismo. Solo lo que se emite en producción (llaves &#x60;live&#x60;) lo consume; la habilitación (&#x60;test&#x60;) no cobra. &#x60;available&#x60; es lo que se puede gastar (en pesos); &#x60;held&#x60;, lo apartado por documentos que la DIAN todavía valida; &#x60;overdraft&#x60;, cuánto puede bajar &#x60;available&#x60; de cero al apartar un documento; &#x60;low_threshold&#x60;, el umbral del aviso de saldo bajo (el 20 % de la última recarga; &#x60;null&#x60; sin recargas). &#x60;level&#x60;: &#x60;empty&#x60; con &#x60;available&#x60; ≤ 0, &#x60;low&#x60; por debajo de &#x60;low_threshold&#x60;, si no &#x60;ok&#x60;. Con &#x60;available&#x60; por debajo del precio de un documento, emitir en producción responde 402 &#x60;insufficient_balance&#x60;. El evento &#x60;balance.low&#x60; avisa por webhook.
174
+ # @param [Hash] opts the optional parameters
175
+ # @return [Array<(Balance, Integer, Hash)>] Balance data, response status code and response headers
176
+ def retrieve_balance_with_http_info(opts = {})
177
+ if @api_client.config.debugging
178
+ @api_client.config.logger.debug 'Calling API: BalanceApi.retrieve_balance ...'
179
+ end
180
+ # resource path
181
+ local_var_path = '/balance'
182
+
183
+ # query parameters
184
+ query_params = opts[:query_params] || {}
185
+
186
+ # header parameters
187
+ header_params = opts[:header_params] || {}
188
+ # HTTP header 'Accept' (if needed)
189
+ header_params['Accept'] = @api_client.select_header_accept(['application/json', 'application/problem+json']) unless header_params['Accept']
190
+
191
+ # form parameters
192
+ form_params = opts[:form_params] || {}
193
+
194
+ # http body (model)
195
+ post_body = opts[:debug_body]
196
+
197
+ # return_type
198
+ return_type = opts[:debug_return_type] || 'Balance'
199
+
200
+ # auth_names
201
+ auth_names = opts[:debug_auth_names] || ['llave']
202
+
203
+ new_options = opts.merge(
204
+ :operation => :"BalanceApi.retrieve_balance",
205
+ :header_params => header_params,
206
+ :query_params => query_params,
207
+ :form_params => form_params,
208
+ :body => post_body,
209
+ :auth_names => auth_names,
210
+ :return_type => return_type
211
+ )
212
+
213
+ data, status_code, headers = @api_client.call_api(:GET, local_var_path, new_options)
214
+ if @api_client.config.debugging
215
+ @api_client.config.logger.debug "API called: BalanceApi#retrieve_balance\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
216
+ end
217
+ return data, status_code, headers
218
+ end
219
+ end
220
+ end