focus_nfe 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 (206) hide show
  1. checksums.yaml +7 -0
  2. data/.git-hooks/pre_push/steep.rb +18 -0
  3. data/.git-hooks/pre_push/yard_doc.rb +18 -0
  4. data/.gitattributes +1 -0
  5. data/.overcommit.yml +69 -0
  6. data/.rspec +3 -0
  7. data/.yardopts +11 -0
  8. data/CHANGELOG.md +77 -0
  9. data/CLAUDE.md +118 -0
  10. data/CODE_OF_CONDUCT.md +10 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +348 -0
  13. data/Rakefile +105 -0
  14. data/data/schemas/schema_cte.json +2793 -0
  15. data/data/schemas/schema_cte_os.json +1335 -0
  16. data/data/schemas/schema_cte_os_transporte_rodoviario.json +109 -0
  17. data/data/schemas/schema_cte_transporte_aereo.json +115 -0
  18. data/data/schemas/schema_cte_transporte_aquaviario.json +174 -0
  19. data/data/schemas/schema_cte_transporte_dutoviario.json +65 -0
  20. data/data/schemas/schema_cte_transporte_ferroviario.json +144 -0
  21. data/data/schemas/schema_cte_transporte_multimodal.json +45 -0
  22. data/data/schemas/schema_cte_transporte_rodoviario.json +78 -0
  23. data/data/schemas/schema_dce.json +549 -0
  24. data/data/schemas/schema_mdfe.json +1102 -0
  25. data/data/schemas/schema_mdfe_transporte_aereo.json +44 -0
  26. data/data/schemas/schema_mdfe_transporte_aquaviario.json +209 -0
  27. data/data/schemas/schema_mdfe_transporte_ferroviario.json +99 -0
  28. data/data/schemas/schema_mdfe_transporte_rodoviario.json +628 -0
  29. data/data/schemas/schema_nfcom.json +1859 -0
  30. data/data/schemas/schema_nfe.json +4750 -0
  31. data/data/schemas/schema_nfe_forma_pagamento.json +97 -0
  32. data/data/schemas/schema_nfe_item.json +2574 -0
  33. data/data/schemas/schema_nfgas.json +2316 -0
  34. data/data/schemas/schema_nfse_nacional.json +1847 -0
  35. data/data/schemas/schema_nfse_recebida.json +548 -0
  36. data/lib/focus_nfe/client.rb +162 -0
  37. data/lib/focus_nfe/configuration.rb +104 -0
  38. data/lib/focus_nfe/errors.rb +123 -0
  39. data/lib/focus_nfe/esquemas/campo.rb +171 -0
  40. data/lib/focus_nfe/esquemas/catalogo.rb +34 -0
  41. data/lib/focus_nfe/esquemas/decimal.rb +66 -0
  42. data/lib/focus_nfe/esquemas/esquema.rb +72 -0
  43. data/lib/focus_nfe/esquemas/validador.rb +87 -0
  44. data/lib/focus_nfe/http/adapter.rb +25 -0
  45. data/lib/focus_nfe/http/adapters/net_http.rb +99 -0
  46. data/lib/focus_nfe/http/authentication.rb +23 -0
  47. data/lib/focus_nfe/http/connection.rb +118 -0
  48. data/lib/focus_nfe/http/logging.rb +100 -0
  49. data/lib/focus_nfe/http/response.rb +75 -0
  50. data/lib/focus_nfe/modelos/documento.rb +113 -0
  51. data/lib/focus_nfe/modelos/inutilizacao.rb +75 -0
  52. data/lib/focus_nfe/modelos/pagina.rb +54 -0
  53. data/lib/focus_nfe/recursos/backups.rb +17 -0
  54. data/lib/focus_nfe/recursos/base.rb +91 -0
  55. data/lib/focus_nfe/recursos/ceps.rb +16 -0
  56. data/lib/focus_nfe/recursos/cfops.rb +16 -0
  57. data/lib/focus_nfe/recursos/cnaes.rb +16 -0
  58. data/lib/focus_nfe/recursos/cnpjs.rb +13 -0
  59. data/lib/focus_nfe/recursos/concerns/baixavel.rb +41 -0
  60. data/lib/focus_nfe/recursos/concerns/baixavel_eventos.rb +34 -0
  61. data/lib/focus_nfe/recursos/concerns/cancelavel.rb +25 -0
  62. data/lib/focus_nfe/recursos/concerns/conciliavel.rb +66 -0
  63. data/lib/focus_nfe/recursos/concerns/consultavel.rb +26 -0
  64. data/lib/focus_nfe/recursos/concerns/corrigivel.rb +45 -0
  65. data/lib/focus_nfe/recursos/concerns/corrigivel_cte.rb +60 -0
  66. data/lib/focus_nfe/recursos/concerns/emitivel.rb +51 -0
  67. data/lib/focus_nfe/recursos/concerns/enviavel.rb +40 -0
  68. data/lib/focus_nfe/recursos/concerns/eventavel.rb +46 -0
  69. data/lib/focus_nfe/recursos/concerns/inutilizavel.rb +96 -0
  70. data/lib/focus_nfe/recursos/concerns/listavel.rb +22 -0
  71. data/lib/focus_nfe/recursos/concerns/localizavel.rb +22 -0
  72. data/lib/focus_nfe/recursos/concerns/notificavel.rb +23 -0
  73. data/lib/focus_nfe/recursos/concerns/removivel.rb +20 -0
  74. data/lib/focus_nfe/recursos/concerns/visualizavel.rb +28 -0
  75. data/lib/focus_nfe/recursos/cte.rb +35 -0
  76. data/lib/focus_nfe/recursos/cte_os.rb +29 -0
  77. data/lib/focus_nfe/recursos/ctes_recebidas.rb +38 -0
  78. data/lib/focus_nfe/recursos/dce.rb +16 -0
  79. data/lib/focus_nfe/recursos/emails_bloqueados.rb +31 -0
  80. data/lib/focus_nfe/recursos/empresas.rb +35 -0
  81. data/lib/focus_nfe/recursos/mdfe.rb +78 -0
  82. data/lib/focus_nfe/recursos/municipios.rb +60 -0
  83. data/lib/focus_nfe/recursos/ncms.rb +16 -0
  84. data/lib/focus_nfe/recursos/nfce.rb +19 -0
  85. data/lib/focus_nfe/recursos/nfcom.rb +16 -0
  86. data/lib/focus_nfe/recursos/nfe.rb +106 -0
  87. data/lib/focus_nfe/recursos/nfes_recebidas.rb +56 -0
  88. data/lib/focus_nfe/recursos/nfgas.rb +16 -0
  89. data/lib/focus_nfe/recursos/nfse.rb +18 -0
  90. data/lib/focus_nfe/recursos/nfse_nacional.rb +16 -0
  91. data/lib/focus_nfe/recursos/nfses_nacionais_recebidas.rb +21 -0
  92. data/lib/focus_nfe/recursos/webhooks.rb +22 -0
  93. data/lib/focus_nfe/version.rb +6 -0
  94. data/lib/focus_nfe/webhook.rb +68 -0
  95. data/lib/focus_nfe.rb +124 -0
  96. data/sig/focus_nfe/client.rbs +38 -0
  97. data/sig/focus_nfe/configuration.rbs +29 -0
  98. data/sig/focus_nfe/errors.rbs +59 -0
  99. data/sig/focus_nfe/esquemas/campo.rbs +47 -0
  100. data/sig/focus_nfe/esquemas/catalogo.rbs +8 -0
  101. data/sig/focus_nfe/esquemas/decimal.rbs +25 -0
  102. data/sig/focus_nfe/esquemas/esquema.rbs +30 -0
  103. data/sig/focus_nfe/esquemas/validador.rbs +20 -0
  104. data/sig/focus_nfe/http/adapter.rbs +7 -0
  105. data/sig/focus_nfe/http/adapters/net_http.rbs +25 -0
  106. data/sig/focus_nfe/http/authentication.rbs +9 -0
  107. data/sig/focus_nfe/http/connection.rbs +32 -0
  108. data/sig/focus_nfe/http/logging.rbs +30 -0
  109. data/sig/focus_nfe/http/response.rbs +28 -0
  110. data/sig/focus_nfe/modelos/documento.rbs +34 -0
  111. data/sig/focus_nfe/modelos/inutilizacao.rbs +24 -0
  112. data/sig/focus_nfe/modelos/pagina.rbs +21 -0
  113. data/sig/focus_nfe/recursos/backups.rbs +7 -0
  114. data/sig/focus_nfe/recursos/base.rbs +25 -0
  115. data/sig/focus_nfe/recursos/ceps.rbs +10 -0
  116. data/sig/focus_nfe/recursos/cfops.rbs +10 -0
  117. data/sig/focus_nfe/recursos/cnaes.rbs +10 -0
  118. data/sig/focus_nfe/recursos/cnpjs.rbs +7 -0
  119. data/sig/focus_nfe/recursos/concerns/baixavel.rbs +12 -0
  120. data/sig/focus_nfe/recursos/concerns/baixavel_eventos.rbs +14 -0
  121. data/sig/focus_nfe/recursos/concerns/cancelavel.rbs +9 -0
  122. data/sig/focus_nfe/recursos/concerns/conciliavel.rbs +15 -0
  123. data/sig/focus_nfe/recursos/concerns/consultavel.rbs +9 -0
  124. data/sig/focus_nfe/recursos/concerns/corrigivel.rbs +15 -0
  125. data/sig/focus_nfe/recursos/concerns/corrigivel_cte.rbs +17 -0
  126. data/sig/focus_nfe/recursos/concerns/emitivel.rbs +14 -0
  127. data/sig/focus_nfe/recursos/concerns/enviavel.rbs +15 -0
  128. data/sig/focus_nfe/recursos/concerns/eventavel.rbs +12 -0
  129. data/sig/focus_nfe/recursos/concerns/inutilizavel.rbs +20 -0
  130. data/sig/focus_nfe/recursos/concerns/listavel.rbs +9 -0
  131. data/sig/focus_nfe/recursos/concerns/localizavel.rbs +9 -0
  132. data/sig/focus_nfe/recursos/concerns/notificavel.rbs +9 -0
  133. data/sig/focus_nfe/recursos/concerns/removivel.rbs +9 -0
  134. data/sig/focus_nfe/recursos/concerns/visualizavel.rbs +9 -0
  135. data/sig/focus_nfe/recursos/cte.rbs +17 -0
  136. data/sig/focus_nfe/recursos/cte_os.rbs +17 -0
  137. data/sig/focus_nfe/recursos/ctes_recebidas.rbs +14 -0
  138. data/sig/focus_nfe/recursos/dce.rbs +10 -0
  139. data/sig/focus_nfe/recursos/emails_bloqueados.rbs +12 -0
  140. data/sig/focus_nfe/recursos/empresas.rbs +12 -0
  141. data/sig/focus_nfe/recursos/mdfe.rbs +21 -0
  142. data/sig/focus_nfe/recursos/municipios.rbs +19 -0
  143. data/sig/focus_nfe/recursos/ncms.rbs +10 -0
  144. data/sig/focus_nfe/recursos/nfce.rbs +12 -0
  145. data/sig/focus_nfe/recursos/nfcom.rbs +10 -0
  146. data/sig/focus_nfe/recursos/nfe.rbs +23 -0
  147. data/sig/focus_nfe/recursos/nfes_recebidas.rbs +16 -0
  148. data/sig/focus_nfe/recursos/nfgas.rbs +10 -0
  149. data/sig/focus_nfe/recursos/nfse.rbs +11 -0
  150. data/sig/focus_nfe/recursos/nfse_nacional.rbs +10 -0
  151. data/sig/focus_nfe/recursos/nfses_nacionais_recebidas.rbs +11 -0
  152. data/sig/focus_nfe/recursos/webhooks.rbs +11 -0
  153. data/sig/focus_nfe/webhook.rbs +11 -0
  154. data/sig/focus_nfe.rbs +10 -0
  155. data/spec/focus_nfe/client_spec.rb +208 -0
  156. data/spec/focus_nfe/configuration_spec.rb +121 -0
  157. data/spec/focus_nfe/errors_mapping_spec.rb +68 -0
  158. data/spec/focus_nfe/errors_spec.rb +107 -0
  159. data/spec/focus_nfe/esquemas/campo_spec.rb +291 -0
  160. data/spec/focus_nfe/esquemas/decimal_spec.rb +54 -0
  161. data/spec/focus_nfe/esquemas/esquema_spec.rb +73 -0
  162. data/spec/focus_nfe/esquemas/validador_spec.rb +167 -0
  163. data/spec/focus_nfe/esquemas_spec.rb +42 -0
  164. data/spec/focus_nfe/http/adapter_spec.rb +8 -0
  165. data/spec/focus_nfe/http/adapters/net_http_spec.rb +181 -0
  166. data/spec/focus_nfe/http/authentication_spec.rb +24 -0
  167. data/spec/focus_nfe/http/connection_spec.rb +255 -0
  168. data/spec/focus_nfe/http/logging_spec.rb +83 -0
  169. data/spec/focus_nfe/http/response_spec.rb +161 -0
  170. data/spec/focus_nfe/modelos/documento_spec.rb +150 -0
  171. data/spec/focus_nfe/modelos/inutilizacao_spec.rb +91 -0
  172. data/spec/focus_nfe/modelos/pagina_spec.rb +77 -0
  173. data/spec/focus_nfe/recursos/backups_spec.rb +29 -0
  174. data/spec/focus_nfe/recursos/base_spec.rb +56 -0
  175. data/spec/focus_nfe/recursos/ceps_spec.rb +16 -0
  176. data/spec/focus_nfe/recursos/cfops_spec.rb +16 -0
  177. data/spec/focus_nfe/recursos/cnaes_spec.rb +20 -0
  178. data/spec/focus_nfe/recursos/cnpjs_spec.rb +11 -0
  179. data/spec/focus_nfe/recursos/concerns/emitivel_validacao_spec.rb +158 -0
  180. data/spec/focus_nfe/recursos/cte_os_spec.rb +9 -0
  181. data/spec/focus_nfe/recursos/cte_spec.rb +9 -0
  182. data/spec/focus_nfe/recursos/ctes_recebidas_spec.rb +56 -0
  183. data/spec/focus_nfe/recursos/dce_spec.rb +8 -0
  184. data/spec/focus_nfe/recursos/emails_bloqueados_spec.rb +29 -0
  185. data/spec/focus_nfe/recursos/empresas_spec.rb +45 -0
  186. data/spec/focus_nfe/recursos/mdfe_spec.rb +100 -0
  187. data/spec/focus_nfe/recursos/municipios_spec.rb +58 -0
  188. data/spec/focus_nfe/recursos/ncms_spec.rb +16 -0
  189. data/spec/focus_nfe/recursos/nfce_spec.rb +10 -0
  190. data/spec/focus_nfe/recursos/nfcom_spec.rb +8 -0
  191. data/spec/focus_nfe/recursos/nfe_spec.rb +262 -0
  192. data/spec/focus_nfe/recursos/nfes_recebidas_spec.rb +87 -0
  193. data/spec/focus_nfe/recursos/nfgas_spec.rb +8 -0
  194. data/spec/focus_nfe/recursos/nfse_nacional_spec.rb +8 -0
  195. data/spec/focus_nfe/recursos/nfse_spec.rb +9 -0
  196. data/spec/focus_nfe/recursos/nfses_nacionais_recebidas_spec.rb +17 -0
  197. data/spec/focus_nfe/recursos/webhooks_spec.rb +22 -0
  198. data/spec/focus_nfe/webhook_spec.rb +66 -0
  199. data/spec/focus_nfe_global_configuration_spec.rb +70 -0
  200. data/spec/focus_nfe_require_spec.rb +87 -0
  201. data/spec/focus_nfe_spec.rb +11 -0
  202. data/spec/spec_helper.rb +58 -0
  203. data/spec/support/shared_examples/recurso_fiscal.rb +445 -0
  204. data/spec/support/shared_examples/recurso_leitura.rb +217 -0
  205. data/tools/pull_fields.rb +62 -0
  206. metadata +420 -0
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Recurso de Manifesto Eletrônico de Documentos Fiscais (MDF-e). Emissão
6
+ # assíncrona com consulta de estado, cancelamento e eventos próprios:
7
+ # encerramento, inclusão de condutor e inclusão de DF-e.
8
+ class Mdfe < Base
9
+ include Concerns::Emitivel
10
+ include Concerns::Consultavel
11
+ include Concerns::Cancelavel
12
+ include Concerns::Notificavel
13
+ include Concerns::Eventavel
14
+
15
+ caminho_base "mdfe"
16
+
17
+ # @return [Hash{String=>String}] chave +modal_<tipo>+ no payload => nome do sub-esquema
18
+ MODAIS = {
19
+ "modal_rodoviario" => "mdfe_transporte_rodoviario",
20
+ "modal_aereo" => "mdfe_transporte_aereo",
21
+ "modal_aquaviario" => "mdfe_transporte_aquaviario",
22
+ "modal_ferroviario" => "mdfe_transporte_ferroviario"
23
+ }.freeze
24
+
25
+ # Encerra o MDF-e ao fim da operação de transporte.
26
+ #
27
+ # @param ref [String] referência do manifesto
28
+ # @param data [String] data do encerramento (ISO 8601)
29
+ # @param sigla_uf [String] UF do município de encerramento
30
+ # @param nome_municipio [String] nome do município de encerramento
31
+ # @return [FocusNfe::Modelos::Documento]
32
+ # @raise [ArgumentError] se a +ref+ for inválida
33
+ # @raise [FocusNfe::Errors::HttpError] em respostas não-2xx
34
+ def encerrar(ref, data:, sigla_uf:, nome_municipio:)
35
+ emitir_evento_em(ref, caminho: "encerrar", data: data, sigla_uf: sigla_uf, nome_municipio: nome_municipio)
36
+ end
37
+
38
+ # Inclui um condutor no MDF-e autorizado.
39
+ #
40
+ # @param ref [String] referência do manifesto
41
+ # @param nome [String] nome completo do condutor
42
+ # @param cpf [String] CPF do condutor
43
+ # @return [FocusNfe::Modelos::Documento]
44
+ # @raise [ArgumentError] se a +ref+ for inválida
45
+ # @raise [FocusNfe::Errors::HttpError] em respostas não-2xx
46
+ def incluir_condutor(ref, nome:, cpf:)
47
+ emitir_evento_em(ref, caminho: "inclusao_condutor", nome: nome, cpf: cpf)
48
+ end
49
+
50
+ # Inclui documentos fiscais (DF-e) vinculados ao MDF-e autorizado.
51
+ #
52
+ # @param ref [String] referência do manifesto
53
+ # @param protocolo [String] protocolo de autorização do MDF-e
54
+ # @param codigo_municipio_carregamento [String] código do município de carregamento
55
+ # @param documentos [Array<Hash>] documentos com +chave_nfe+ e +codigo_municipio_descarregamento+
56
+ # @param nome_municipio_carregamento [String, nil] nome do município de carregamento
57
+ # @return [FocusNfe::Modelos::Documento]
58
+ # @raise [ArgumentError] se a +ref+ for inválida
59
+ # @raise [FocusNfe::Errors::HttpError] em respostas não-2xx
60
+ def incluir_dfe(ref, protocolo:, codigo_municipio_carregamento:, documentos:, nome_municipio_carregamento: nil)
61
+ dados = { protocolo: protocolo, codigo_municipio_carregamento: codigo_municipio_carregamento,
62
+ documentos: documentos }
63
+ dados[:nome_municipio_carregamento] = nome_municipio_carregamento if nome_municipio_carregamento
64
+ emitir_evento_em(ref, caminho: "inclusao_dfe", **dados)
65
+ end
66
+
67
+ private
68
+
69
+ # A MDFe não possui campo +modal+: o modal é deduzido pela chave
70
+ # +modal_<tipo>+ presente no payload.
71
+ #
72
+ # @see FocusNfe::Recursos::Base#esquemas_extras
73
+ def esquemas_extras(dados)
74
+ MODAIS.select { |chave, _| dados.key?(chave) }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Consulta de municípios e seus sub-recursos de NFS-e (códigos tributários e
6
+ # itens da lista de serviço). +consultar(codigo)+ usa o código IBGE.
7
+ class Municipios < Base
8
+ include Concerns::Localizavel
9
+ include Concerns::Listavel
10
+
11
+ caminho_base "municipios"
12
+
13
+ # @param codigo_municipio [String] código IBGE do município
14
+ # @param filtros [Hash] filtros/paginação (+codigo:+, +descricao:+, +offset:+, +limit:+)
15
+ # @return [FocusNfe::Modelos::Pagina]
16
+ def listar_codigos_tributarios(codigo_municipio, **filtros)
17
+ sub_listagem(codigo_municipio, "codigos_tributarios_municipio", filtros)
18
+ end
19
+
20
+ # @param codigo_municipio [String] código IBGE do município
21
+ # @param filtros [Hash] filtros/paginação (+codigo:+, +descricao:+, +offset:+, +limit:+)
22
+ # @return [FocusNfe::Modelos::Pagina]
23
+ def listar_itens_lista_servico(codigo_municipio, **filtros)
24
+ sub_listagem(codigo_municipio, "itens_lista_servico", filtros)
25
+ end
26
+
27
+ # @param codigo_municipio [String] código IBGE do município
28
+ # @param codigo [String] código do tributo municipal
29
+ # @return [Hash] corpo cru da resposta
30
+ def consultar_codigo_tributario(codigo_municipio, codigo)
31
+ sub_consulta(codigo_municipio, "codigos_tributarios_municipio", codigo)
32
+ end
33
+
34
+ # @param codigo_municipio [String] código IBGE do município
35
+ # @param codigo [String] código do item na lista de serviço (ex. +"1.06"+)
36
+ # @return [Hash] corpo cru da resposta
37
+ def consultar_item_lista_servico(codigo_municipio, codigo)
38
+ sub_consulta(codigo_municipio, "itens_lista_servico", codigo)
39
+ end
40
+
41
+ # @param codigo_municipio [String] código IBGE do município
42
+ # @return [Hash] JSON de exemplo de NFS-e do município
43
+ def consultar_json(codigo_municipio)
44
+ connection.get("#{caminho_referencia(codigo_municipio)}/json").body
45
+ end
46
+
47
+ private
48
+
49
+ def sub_listagem(codigo_municipio, sufixo, filtros)
50
+ response = connection.get("#{caminho_referencia(codigo_municipio)}/#{sufixo}", params: filtros)
51
+ Modelos::Pagina.from_response(response)
52
+ end
53
+
54
+ def sub_consulta(codigo_municipio, sufixo, codigo)
55
+ segmento = URI.encode_www_form_component(codigo)
56
+ connection.get("#{caminho_referencia(codigo_municipio)}/#{sufixo}/#{segmento}").body
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Consulta de NCMs (Nomenclatura Comum do Mercosul). +consultar(codigo)+ busca
6
+ # um NCM; +buscar+ filtra por código/descrição e componentes.
7
+ class Ncms < Base
8
+ include Concerns::Localizavel
9
+ include Concerns::Listavel
10
+
11
+ caminho_base "ncms"
12
+
13
+ alias buscar listar
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Recurso de Nota Fiscal de Consumidor eletrônica (NFC-e). Emissão síncrona
6
+ # com consulta de estado, cancelamento, inutilização de numeração, envio por
7
+ # e-mail e conciliação financeira (ECONF). A NFC-e não admite Carta de Correção.
8
+ class Nfce < Base
9
+ include Concerns::Emitivel
10
+ include Concerns::Consultavel
11
+ include Concerns::Cancelavel
12
+ include Concerns::Inutilizavel
13
+ include Concerns::Enviavel
14
+ include Concerns::Conciliavel
15
+
16
+ caminho_base "nfce"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Recurso de Nota Fiscal Fatura de Serviço de Comunicação eletrônica
6
+ # (NFCom). Emissão assíncrona com consulta de estado e cancelamento.
7
+ class Nfcom < Base
8
+ include Concerns::Emitivel
9
+ include Concerns::Consultavel
10
+ include Concerns::Cancelavel
11
+ include Concerns::Notificavel
12
+
13
+ caminho_base "nfcom"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Recurso de Nota Fiscal eletrônica (NF-e). Emissão assíncrona com consulta
6
+ # de estado, cancelamento, Carta de Correção Eletrônica (CC-e), inutilização
7
+ # de numeração, prévia da DANFe, envio por e-mail e importação a partir do XML.
8
+ class Nfe < Base
9
+ include Concerns::Emitivel
10
+ include Concerns::Consultavel
11
+ include Concerns::Cancelavel
12
+ include Concerns::Corrigivel
13
+ include Concerns::Inutilizavel
14
+ include Concerns::Visualizavel
15
+ include Concerns::Notificavel
16
+ include Concerns::Enviavel
17
+ include Concerns::Eventavel
18
+ include Concerns::Conciliavel
19
+
20
+ caminho_base "nfe"
21
+ caminho_base_previa "nfe/danfe"
22
+
23
+ # Importa uma NF-e a partir do seu XML autorizado.
24
+ #
25
+ # @param xml [String] XML da NF-e (enviado como +application/xml+)
26
+ # @param ref [String, nil] referência a atribuir; omitida usa a chave da nota
27
+ # @return [FocusNfe::Modelos::Documento]
28
+ # @raise [FocusNfe::Errors::HttpError] em respostas não-2xx
29
+ def importar(xml, ref: nil)
30
+ params = ref ? { ref: ref } : {}
31
+ response = connection.post("#{caminho_base}/importacao",
32
+ params: params, body: xml,
33
+ headers: { "Content-Type" => "application/xml" })
34
+ Modelos::Documento.from_response(response, ref: ref)
35
+ end
36
+
37
+ # Emite um evento genérico sobre a NF-e (+POST /nfe/:ref/evento+).
38
+ #
39
+ # @param ref [String] referência da nota
40
+ # @param tipo_evento [String] tipo do evento suportado pela API
41
+ # @param dados [Hash] campos adicionais específicos do evento
42
+ # @return [FocusNfe::Modelos::Documento]
43
+ # @raise [ArgumentError] se a +ref+ for inválida
44
+ # @raise [FocusNfe::Errors::HttpError] em respostas não-2xx
45
+ def emitir_evento(ref, tipo_evento:, **dados)
46
+ emitir_evento_em(ref, caminho: "evento", tipo_evento: tipo_evento, **dados)
47
+ end
48
+
49
+ # Cancela um evento da NF-e (+DELETE /nfe/:ref/evento+ com +tipo_evento+ no corpo).
50
+ #
51
+ # @param ref [String] referência da nota
52
+ # @param tipo_evento [String] tipo do evento a cancelar
53
+ # @return [FocusNfe::Modelos::Documento]
54
+ # @raise [ArgumentError] se a +ref+ for inválida
55
+ # @raise [FocusNfe::Errors::HttpError] em respostas não-2xx
56
+ def cancelar_evento(ref, tipo_evento:)
57
+ cancelar_evento_em(ref, caminho: "evento", tipo_evento: tipo_evento)
58
+ end
59
+
60
+ # Registra o Ator Interessado da NF-e (+POST /nfe/:ref/ator_interessado+),
61
+ # autorizando um terceiro a acessar o XML. Informe +cpf+ ou +cnpj+.
62
+ #
63
+ # @param ref [String] referência da nota
64
+ # @param permite_autorizacao_terceiros [Boolean] libera o acesso ao terceiro
65
+ # @param cpf [String, nil] CPF do ator interessado
66
+ # @param cnpj [String, nil] CNPJ do ator interessado
67
+ # @return [FocusNfe::Modelos::Documento]
68
+ # @raise [ArgumentError] se a +ref+ for inválida
69
+ # @raise [FocusNfe::Errors::HttpError] em respostas não-2xx
70
+ def registrar_ator_interessado(ref, permite_autorizacao_terceiros:, cpf: nil, cnpj: nil)
71
+ dados = { permite_autorizacao_terceiros: permite_autorizacao_terceiros }
72
+ dados[:cpf] = cpf if cpf
73
+ dados[:cnpj] = cnpj if cnpj
74
+ emitir_evento_em(ref, caminho: "ator_interessado", **dados)
75
+ end
76
+
77
+ # Registra o evento de Insucesso na Entrega (+POST /nfe/:ref/insucesso_entrega+).
78
+ # Quando +motivo_insucesso+ for 4, a API exige +justificativa_insucesso+.
79
+ #
80
+ # @param ref [String] referência da nota
81
+ # @param data_tentativa_entrega [String] data/hora da tentativa (ISO 8601)
82
+ # @param motivo_insucesso [Integer] código do motivo (1 a 4)
83
+ # @param hash_tentativa_entrega [String] hash SHA-1 em Base64 da tentativa
84
+ # @param dados [Hash] campos opcionais (+numero_tentativas+, +justificativa_insucesso+, …)
85
+ # @return [FocusNfe::Modelos::Documento]
86
+ # @raise [ArgumentError] se a +ref+ for inválida
87
+ # @raise [FocusNfe::Errors::HttpError] em respostas não-2xx
88
+ def registrar_insucesso_entrega(ref, data_tentativa_entrega:, motivo_insucesso:, hash_tentativa_entrega:, **dados)
89
+ emitir_evento_em(ref, caminho: "insucesso_entrega",
90
+ data_tentativa_entrega: data_tentativa_entrega,
91
+ motivo_insucesso: motivo_insucesso,
92
+ hash_tentativa_entrega: hash_tentativa_entrega, **dados)
93
+ end
94
+
95
+ # Cancela o evento de Insucesso na Entrega (+DELETE /nfe/:ref/insucesso_entrega+).
96
+ #
97
+ # @param ref [String] referência da nota
98
+ # @return [FocusNfe::Modelos::Documento]
99
+ # @raise [ArgumentError] se a +ref+ for inválida
100
+ # @raise [FocusNfe::Errors::HttpError] em respostas não-2xx
101
+ def cancelar_insucesso_entrega(ref)
102
+ cancelar_evento_em(ref, caminho: "insucesso_entrega")
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Notas fiscais (NF-e) recebidas contra um CNPJ. Listagem com sincronização
6
+ # incremental, consulta por chave, downloads, manifestação do destinatário e
7
+ # eventos.
8
+ class NfesRecebidas < Base
9
+ include Concerns::Listavel
10
+ include Concerns::Baixavel
11
+ include Concerns::BaixavelEventos
12
+ include Concerns::Notificavel
13
+ include Concerns::Eventavel
14
+
15
+ caminho_base "nfes_recebidas"
16
+
17
+ # @param chave [String] chave de acesso da NF-e
18
+ # @param completa [Boolean] quando +true+, pede a resposta completa (+completa=1+)
19
+ # @return [Hash] corpo cru da resposta
20
+ def consultar(chave, completa: false)
21
+ params = completa ? { completa: 1 } : {}
22
+ connection.get(caminho_referencia(chave), params: params).body
23
+ end
24
+
25
+ # Registra a manifestação do destinatário.
26
+ #
27
+ # @param chave [String] chave de acesso da NF-e
28
+ # @param tipo [String] +ciencia+/+confirmacao+/+desconhecimento+/+nao_realizada+
29
+ # @param justificativa [String, nil] obrigatória apenas para +nao_realizada+
30
+ # @return [Hash] corpo cru da resposta
31
+ def manifestar(chave, tipo:, justificativa: nil)
32
+ corpo = { tipo: tipo }
33
+ corpo[:justificativa] = justificativa unless justificativa.nil?
34
+ connection.post("#{caminho_referencia(chave)}/manifesto", body: corpo).body
35
+ end
36
+
37
+ # Emite um evento sobre a NF-e recebida.
38
+ #
39
+ # @param chave [String] chave de acesso da NF-e
40
+ # @param tipo_evento [String] tipo do evento suportado pela API
41
+ # @param dados [Hash] campos adicionais do evento
42
+ # @return [FocusNfe::Modelos::Documento]
43
+ def emitir_evento(chave, tipo_evento:, **dados)
44
+ emitir_evento_em(chave, caminho: "evento", tipo_evento: tipo_evento, **dados)
45
+ end
46
+
47
+ # Cancela o último evento emitido para a NF-e recebida.
48
+ #
49
+ # @param chave [String] chave de acesso da NF-e
50
+ # @return [FocusNfe::Modelos::Documento]
51
+ def cancelar_evento(chave)
52
+ cancelar_evento_em(chave, caminho: "evento")
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Recurso de Nota Fiscal de Gás eletrônica (NFGas, em beta). Emissão
6
+ # assíncrona com consulta de estado e cancelamento.
7
+ class Nfgas < Base
8
+ include Concerns::Emitivel
9
+ include Concerns::Consultavel
10
+ include Concerns::Cancelavel
11
+ include Concerns::Notificavel
12
+
13
+ caminho_base "nfgas"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Recurso de Nota Fiscal de Serviço eletrônica (NFS-e). Pré-validação
6
+ # síncrona seguida de autorização assíncrona; o cancelamento fica sujeito às
7
+ # regras da prefeitura. Suporta reenvio da nota por e-mail.
8
+ class Nfse < Base
9
+ include Concerns::Emitivel
10
+ include Concerns::Consultavel
11
+ include Concerns::Cancelavel
12
+ include Concerns::Notificavel
13
+ include Concerns::Enviavel
14
+
15
+ caminho_base "nfse"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Recurso de NFS-e padrão nacional (DPS). Emissão assíncrona com consulta de
6
+ # estado e cancelamento.
7
+ class NfseNacional < Base
8
+ include Concerns::Emitivel
9
+ include Concerns::Consultavel
10
+ include Concerns::Cancelavel
11
+ include Concerns::Notificavel
12
+
13
+ caminho_base "nfse_nacional"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # NFS-e de padrão nacional recebidas contra um CNPJ. Listagem com
6
+ # sincronização incremental e downloads (incluindo o DANFSe em HTML).
7
+ class NfsesNacionaisRecebidas < Base
8
+ include Concerns::Listavel
9
+ include Concerns::Baixavel
10
+ include Concerns::Notificavel
11
+
12
+ caminho_base "nfsens_recebidas"
13
+
14
+ # @param chave [String] chave de acesso da NFS-e
15
+ # @return [String] DANFSe em HTML cru
16
+ def download_html(chave)
17
+ download(chave, formato: :html)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ module Recursos
5
+ # Gestão de webhooks (gatilhos de notificação). O caminho da API é +hooks+.
6
+ class Webhooks < Base
7
+ include Concerns::Listavel
8
+ include Concerns::Localizavel
9
+ include Concerns::Removivel
10
+
11
+ caminho_base "hooks"
12
+
13
+ # Cria um webhook.
14
+ #
15
+ # @param dados [Hash] dados do gatilho (+event+, +url+, +cnpj+, …)
16
+ # @return [Hash] corpo cru da resposta
17
+ def criar(dados:)
18
+ connection.post(caminho_base, body: dados).body
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FocusNfe
4
+ # @return [String] versão da gem
5
+ VERSION = "1.0.0"
6
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module FocusNfe
6
+ # Recebimento de webhooks inbound: quando a Focus NFe chama a URL da aplicação
7
+ # ao mudar o status de um documento. Fachada stateless (sem token, sem HTTP) —
8
+ # apenas transforma o corpo cru recebido e autentica a chamada.
9
+ #
10
+ # @see FocusNfe::Recursos::Webhooks gestão dos gatilhos (lado saída)
11
+ module Webhook
12
+ module_function
13
+
14
+ # Converte o corpo cru de um webhook inbound em um {Modelos::Documento}, o
15
+ # mesmo objeto devolvido por emissão/consulta.
16
+ #
17
+ # @param raw_body [String, Hash] corpo recebido (JSON cru ou já parseado)
18
+ # @param ref [String, nil] referência conhecida, injetada quando ausente do corpo
19
+ # @return [FocusNfe::Modelos::Documento]
20
+ # @raise [FocusNfe::Errors::WebhookError] quando o corpo é uma String que não é JSON válido
21
+ def parse(raw_body, ref: nil)
22
+ dados = raw_body.is_a?(String) ? JSON.parse(raw_body) : raw_body
23
+ Modelos::Documento.from_payload(dados, ref: ref)
24
+ rescue JSON::ParserError => e
25
+ raise Errors::WebhookError, "corpo do webhook não é JSON válido: #{e.message}"
26
+ end
27
+
28
+ # Verifica se a chamada é autêntica comparando o header recebido com o
29
+ # +authorization+ configurado na criação do gatilho. A comparação é feita em
30
+ # tempo constante para não vazar o segredo por timing.
31
+ #
32
+ # @param headers [#[]] cabeçalhos da requisição recebida (ex.: +request.headers+ do Rails)
33
+ # @param authorization [String] valor esperado, igual ao informado em {Recursos::Webhooks#criar}
34
+ # @param authorization_header [String] nome do header onde a Focus envia o valor
35
+ # @return [Boolean] +true+ somente quando o header existe e bate com o esperado
36
+ def autenticado?(headers:, authorization:, authorization_header:)
37
+ recebido = valor_do_header(headers, authorization_header)
38
+ !recebido.nil? && comparacao_segura?(recebido.to_s, authorization.to_s)
39
+ end
40
+
41
+ # @param headers [#[]] cabeçalhos da requisição
42
+ # @param nome [String] nome do header procurado
43
+ # @return [Object, nil] valor do header, com fallback case-insensitive em Hash
44
+ def valor_do_header(headers, nome)
45
+ direto = headers[nome]
46
+ return direto unless direto.nil?
47
+ return nil unless headers.respond_to?(:each)
48
+
49
+ alvo = nome.to_s.downcase
50
+ headers.each { |chave, valor| return valor if chave.to_s.downcase == alvo }
51
+ nil
52
+ end
53
+
54
+ # @param esquerda [String]
55
+ # @param direita [String]
56
+ # @return [Boolean] comparação de strings resistente a timing attack
57
+ def comparacao_segura?(esquerda, direita)
58
+ return false unless esquerda.bytesize == direita.bytesize
59
+
60
+ bytes_direita = direita.bytes
61
+ diferenca = 0
62
+ esquerda.bytes.each_with_index { |byte, i| diferenca |= byte ^ bytes_direita[i].to_i }
63
+ diferenca.zero?
64
+ end
65
+
66
+ private_class_method :valor_do_header, :comparacao_segura?
67
+ end
68
+ end
data/lib/focus_nfe.rb ADDED
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "focus_nfe/version"
4
+ require_relative "focus_nfe/errors"
5
+ require_relative "focus_nfe/configuration"
6
+ require_relative "focus_nfe/http/authentication"
7
+ require_relative "focus_nfe/http/response"
8
+ require_relative "focus_nfe/http/adapter"
9
+ require_relative "focus_nfe/http/adapters/net_http"
10
+ require_relative "focus_nfe/http/logging"
11
+ require_relative "focus_nfe/http/connection"
12
+ require_relative "focus_nfe/esquemas/catalogo"
13
+ require_relative "focus_nfe/client"
14
+
15
+ # Ponto de entrada da gem e fachada da configuração global (modo de empresa
16
+ # única): {configure}/{configuration}/{client}/{reset_configuration!} operam
17
+ # sobre uma {Configuration} memoizada no nível do módulo. Para multi-empresa,
18
+ # instancie {Client} explicitamente.
19
+ #
20
+ # Apenas o núcleo (configuração, HTTP, cliente) é carregado no +require+. Os
21
+ # recursos de documento, os modelos e a camada de esquemas são registrados via
22
+ # +autoload+ e carregados sob demanda — quem só emite um tipo de documento não
23
+ # paga o custo de carregar os demais.
24
+ module FocusNfe
25
+ # Recebimento de webhooks inbound (parse + autenticação da chamada).
26
+ autoload :Webhook, "focus_nfe/webhook"
27
+
28
+ # Modelos de resposta da API (documento fiscal, página de listagem).
29
+ module Modelos
30
+ autoload :Documento, "focus_nfe/modelos/documento"
31
+ autoload :Inutilizacao, "focus_nfe/modelos/inutilizacao"
32
+ autoload :Pagina, "focus_nfe/modelos/pagina"
33
+ end
34
+
35
+ # Camada opcional de esquemas: introspecção pública dos campos e validação
36
+ # client-side opt-in. Ver {Esquemas.descrever} e {Validador}.
37
+ module Esquemas
38
+ autoload :Esquema, "focus_nfe/esquemas/esquema"
39
+ autoload :ErroDeValidacao, "focus_nfe/esquemas/esquema"
40
+ autoload :Campo, "focus_nfe/esquemas/campo"
41
+ autoload :Decimal, "focus_nfe/esquemas/decimal"
42
+ autoload :Validador, "focus_nfe/esquemas/validador"
43
+ end
44
+
45
+ # Recursos da API, um por tipo de documento ou consulta. Cada um deriva de
46
+ # {Recursos::Base} e compõe seu comportamento com os mixins de {Concerns}.
47
+ module Recursos
48
+ autoload :Base, "focus_nfe/recursos/base"
49
+
50
+ # Mixins de comportamento compartilhados pelos recursos (emissão, consulta,
51
+ # cancelamento, listagem, …).
52
+ module Concerns
53
+ autoload :Emitivel, "focus_nfe/recursos/concerns/emitivel"
54
+ autoload :Consultavel, "focus_nfe/recursos/concerns/consultavel"
55
+ autoload :Cancelavel, "focus_nfe/recursos/concerns/cancelavel"
56
+ autoload :Eventavel, "focus_nfe/recursos/concerns/eventavel"
57
+ autoload :Corrigivel, "focus_nfe/recursos/concerns/corrigivel"
58
+ autoload :CorrigivelCte, "focus_nfe/recursos/concerns/corrigivel_cte"
59
+ autoload :Inutilizavel, "focus_nfe/recursos/concerns/inutilizavel"
60
+ autoload :Listavel, "focus_nfe/recursos/concerns/listavel"
61
+ autoload :Baixavel, "focus_nfe/recursos/concerns/baixavel"
62
+ autoload :BaixavelEventos, "focus_nfe/recursos/concerns/baixavel_eventos"
63
+ autoload :Localizavel, "focus_nfe/recursos/concerns/localizavel"
64
+ autoload :Notificavel, "focus_nfe/recursos/concerns/notificavel"
65
+ autoload :Enviavel, "focus_nfe/recursos/concerns/enviavel"
66
+ autoload :Conciliavel, "focus_nfe/recursos/concerns/conciliavel"
67
+ autoload :Removivel, "focus_nfe/recursos/concerns/removivel"
68
+ autoload :Visualizavel, "focus_nfe/recursos/concerns/visualizavel"
69
+ end
70
+
71
+ autoload :Nfe, "focus_nfe/recursos/nfe"
72
+ autoload :Nfce, "focus_nfe/recursos/nfce"
73
+ autoload :Nfse, "focus_nfe/recursos/nfse"
74
+ autoload :NfseNacional, "focus_nfe/recursos/nfse_nacional"
75
+ autoload :Cte, "focus_nfe/recursos/cte"
76
+ autoload :CteOs, "focus_nfe/recursos/cte_os"
77
+ autoload :Mdfe, "focus_nfe/recursos/mdfe"
78
+ autoload :Nfcom, "focus_nfe/recursos/nfcom"
79
+ autoload :Dce, "focus_nfe/recursos/dce"
80
+ autoload :Nfgas, "focus_nfe/recursos/nfgas"
81
+ autoload :NfesRecebidas, "focus_nfe/recursos/nfes_recebidas"
82
+ autoload :CtesRecebidas, "focus_nfe/recursos/ctes_recebidas"
83
+ autoload :NfsesNacionaisRecebidas, "focus_nfe/recursos/nfses_nacionais_recebidas"
84
+ autoload :Ceps, "focus_nfe/recursos/ceps"
85
+ autoload :Municipios, "focus_nfe/recursos/municipios"
86
+ autoload :Cfops, "focus_nfe/recursos/cfops"
87
+ autoload :Cnaes, "focus_nfe/recursos/cnaes"
88
+ autoload :Ncms, "focus_nfe/recursos/ncms"
89
+ autoload :Cnpjs, "focus_nfe/recursos/cnpjs"
90
+ autoload :Empresas, "focus_nfe/recursos/empresas"
91
+ autoload :Webhooks, "focus_nfe/recursos/webhooks"
92
+ autoload :EmailsBloqueados, "focus_nfe/recursos/emails_bloqueados"
93
+ autoload :Backups, "focus_nfe/recursos/backups"
94
+ end
95
+
96
+ class << self
97
+ # Cede a configuração global ao bloco para ajuste, memoizando-a.
98
+ #
99
+ # @yieldparam configuration [FocusNfe::Configuration]
100
+ # @return [FocusNfe::Configuration] a configuração global
101
+ def configure
102
+ yield(configuration) if block_given?
103
+ configuration
104
+ end
105
+
106
+ # @return [FocusNfe::Configuration] a configuração global, criada como default na primeira chamada
107
+ def configuration
108
+ @configuration ||= Configuration.new
109
+ end
110
+
111
+ # @return [FocusNfe::Client] cliente construído a partir da config global
112
+ # @raise [FocusNfe::Errors::ConfigurationError] se a config global não tiver ao menos um token e ambiente válido
113
+ def client
114
+ Client.new(configuration)
115
+ end
116
+
117
+ # Limpa a configuração global memoizada.
118
+ #
119
+ # @return [void]
120
+ def reset_configuration!
121
+ @configuration = nil
122
+ end
123
+ end
124
+ end