facturacr 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +7 -0
  7. data/README.md +181 -0
  8. data/Rakefile +10 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/bin/signer/signer.jar +0 -0
  12. data/config/config.yml +10 -0
  13. data/exe/facturacr +3 -0
  14. data/facturacr.gemspec +31 -0
  15. data/lib/facturacr.rb +37 -0
  16. data/lib/facturacr/api.rb +68 -0
  17. data/lib/facturacr/api/document_status.rb +36 -0
  18. data/lib/facturacr/builder.rb +223 -0
  19. data/lib/facturacr/cli.rb +116 -0
  20. data/lib/facturacr/cli/generate.rb +133 -0
  21. data/lib/facturacr/configuration.rb +51 -0
  22. data/lib/facturacr/credit_note.rb +33 -0
  23. data/lib/facturacr/debit_note.rb +33 -0
  24. data/lib/facturacr/document.rb +171 -0
  25. data/lib/facturacr/document/code.rb +4 -0
  26. data/lib/facturacr/document/exoneration.rb +49 -0
  27. data/lib/facturacr/document/fax.rb +24 -0
  28. data/lib/facturacr/document/identification_document.rb +41 -0
  29. data/lib/facturacr/document/issuer.rb +56 -0
  30. data/lib/facturacr/document/item.rb +67 -0
  31. data/lib/facturacr/document/location.rb +49 -0
  32. data/lib/facturacr/document/phone.rb +22 -0
  33. data/lib/facturacr/document/phone_type.rb +37 -0
  34. data/lib/facturacr/document/receiver.rb +59 -0
  35. data/lib/facturacr/document/reference.rb +46 -0
  36. data/lib/facturacr/document/regulation.rb +26 -0
  37. data/lib/facturacr/document/summary.rb +64 -0
  38. data/lib/facturacr/document/tax.rb +44 -0
  39. data/lib/facturacr/invoice.rb +34 -0
  40. data/lib/facturacr/signed_document.rb +19 -0
  41. data/lib/facturacr/signer/signer.rb +245 -0
  42. data/lib/facturacr/ticket.rb +34 -0
  43. data/lib/facturacr/version.rb +3 -0
  44. data/lib/facturacr/xml_document.rb +161 -0
  45. data/resources/FacturaElectronica_V.4.2.xsd +1571 -0
  46. data/resources/NotaCreditoElectronica_V4.2.xsd +1657 -0
  47. data/resources/credit_note.xml +86 -0
  48. data/resources/data.yml +73 -0
  49. data/resources/debit_note.xml +86 -0
  50. data/resources/invoice.xml +94 -0
  51. data/resources/pruebas.xml +37 -0
  52. data/resources/test.p12 +0 -0
  53. metadata +235 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 442248d98ebb602ee4d8f647bf9a24939b07e87a
4
+ data.tar.gz: d6786825ba580d22c565b0714a744fadf34f5347
5
+ SHA512:
6
+ metadata.gz: 68d71ab7ae6715f6e61fbc92e09ca4da4d062e2ef9bc1c215ebea38c6bf4f865f9214a2344e48463f6ec3b3bda71fd1855de64b6b58e4a994fa7b65a0daf90df
7
+ data.tar.gz: cc15f59583e0d342fd11148a948e48137abf52ae27a2886e7e151d2add273d69628f97754142ea6c9d0f70a3d406686eaa1f7e81093b2ae3c9df8a2b9a0be72c
Binary file
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.1
4
+ before_install: gem install bundler -v 1.10.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in facturacr.gemspec
4
+ gemspec
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2018 Josef Sauter
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,181 @@
1
+ # FE
2
+
3
+ Esta librería implementa los procesos de facturación electrónica del Ministerio de Hacienda de Costa Rica en Ruby. Puede ser utilizada como punto de partida para realizar la integración necesaria con el Ministerio de Hacienda.
4
+
5
+ Actualmente cuenta con las siguientes características:
6
+
7
+ - Generación de XML
8
+ - Firmado de XML (utilizando JAVA)
9
+ - Comunicación con el API del ministerio de hacienda
10
+ - Línea de comando para realizar los procesos
11
+
12
+ ## Instalación
13
+
14
+ Agregue la siguiente línea en el Gemfile
15
+
16
+ ```ruby
17
+ gem 'facturacr'
18
+ ```
19
+
20
+ Execute el comando
21
+
22
+ $ bundle
23
+
24
+ O instale el gem
25
+
26
+ $ gem install facturacr
27
+
28
+ ## Utilziación
29
+
30
+ Para generar documentos:
31
+
32
+ ```ruby
33
+ require 'facturacr'
34
+
35
+ id_document = FE::Document::IdentificationDocument.new type: "01", number: "112345678"
36
+ phone = FE::Document::Phone.new country_code: "506", "22222222"
37
+ location = FE::Document::Location.new province: "1",county: "01", district: "01", others: "Otras señas"
38
+ issuer = FE::Document::Issuer.new name: "EMISON EJEMPLO", identification_document: id_document, location: location, phone: phone, email: "emisor@ejemplo.com"
39
+
40
+ reciever_id_document = FE::Document::IdentificationDocument.new type: "02", number: "3102123456"
41
+ receiver = FE::Document::Receiver.new name: "RECEPTOR EJEMPLO", identification_document: id_document
42
+
43
+ items = []
44
+ items << FE::Document::Item.new(code: "001", line_number: 1, quantity: 1, unit: "Sp", description: "Desarrollo de Software y Mantenimiento", unit_price: 300, subtotal: 300, net_total: 300, total: 300)
45
+ taxes = [FE::Document::Tax.new(code: "01", rate: 13, total: (100 * 0.13))]
46
+ items << FE::Document::Item.new(code: "002", line_number: 2, quantity: 2, unit: "Unid", description: "Impresora de POS", unit_price: 50, total: 100, taxes: taxes, net_total: 113, subtotal: 100)
47
+ summary = FE::Document::Summary.new currency: "USD", exchange_rate: 575, services_exent_total: 300, goods_taxable_total: 100, exent_total: 300, taxable_total: 100, subtotal: 400, gross_total: 400, tax_total: 13, net_total: 413
48
+
49
+
50
+ invoice = FE::Invoice.new date: date, issuer: issuer, receiver: receiver, number: number, items: items, condition: condition, credit_term: credit_term, summary: summary, security_code: "12345678", document_situation: "1"
51
+
52
+ # Para generar el XML como string
53
+ xml = invoice.generate
54
+
55
+ # Escribir el archivo
56
+ File.open("/path/to/file.xml","w"){|f| f.write(xml)}
57
+ ```
58
+
59
+ Para configurar el API / Firmador:
60
+ ```ruby
61
+
62
+ FE.configure do |config|
63
+ config.api_username "su_usuario_api_atv"
64
+ config.api_password = "su_password_api_atv"
65
+ config.key_path = "tmp/llave_criptografica.p12"
66
+ config.key_password = "99999999999999"
67
+ # api hacienda: valores default
68
+ config.api_client_id = 'api-stag'
69
+ config.documents_endpoint = "https://api.comprobanteselectronicos.go.cr/recepcion-sandbox/v1"
70
+ config.authentication_endpoint = "https://idp.comprobanteselectronicos.go.cr/auth/realms/rut-stag/protocol/openid-connect/token"s
71
+ end
72
+ ```
73
+
74
+ Para firmar documentos. (Debe tener java instalado)
75
+ ```ruby
76
+
77
+ signer = FE::JavaSigner.new FE.configuration.key_path, FE.configuration.key_password, "/path/to/unsigned.xml", "/path/to/signed.xml"
78
+ signer.sign
79
+ ```
80
+
81
+
82
+ Para enviar documentos al API
83
+ ```ruby
84
+
85
+ api = FE::API.new
86
+ # document is FE::Document
87
+ signed_document = FE::SignedDocument.new(document,path)
88
+ api = FE::Api.new
89
+ if api.send_document(signed_document.payload)
90
+ puts "Document sent!"
91
+ else
92
+ puts "Error: #{api.errors}"
93
+ end
94
+ ```
95
+
96
+ Para chequear el estatus del documento
97
+ ```ruby
98
+
99
+ api = FE::API.new
100
+ FE::Utils.configure(options[:config_file])
101
+ api = FE::Api.new
102
+ document_status = api.get_document_status(key)
103
+ puts document_status.to_h
104
+ #=> {key: "50601011600310112345600100010100000000011999999999", date: "2016-01-01T00:00:00-0600", status: "aceptado", datails: ""}
105
+ ```
106
+
107
+ ## Lína de comando
108
+
109
+ Para utilziar la línea de comando y generar documentos de prueba, debe generar los archivos de configuración en un directorio temporal. Navegue al directorio donde desee ejectuar el proceso:
110
+
111
+ $ facturacr setup /path/to/directory
112
+
113
+ Modifique los archivos config.yml y data.yml para utilziar la línea de comando
114
+
115
+ Para generar documentos:
116
+
117
+ $ facturacr generate help
118
+
119
+ Commands:
120
+ facturacr generate credit_note # generates an XML credit note
121
+ facturacr generate debit_note # generates an XML debit note
122
+ facturacr generate help [COMMAND] # Describe subcommands or one specific subcommand
123
+ facturacr generate invoice # generates an XML invoice
124
+
125
+ Por ejemplo, para generar una factura
126
+
127
+
128
+ $ facturacr generate invoice --number 112
129
+
130
+ Details:
131
+ ISSUER: Cédula Jurídica 3102123456 - EMISOR EJEMPLO
132
+ RECEIVER: Cédula Fisica 111900158 - RECEPTOR EJEMPLO
133
+ KEY: 50601041800310212345600100001010000000112199999999
134
+
135
+ Adicionalmente se pueden especificar las opciones para firma y enviar a hacienda
136
+
137
+ $ facturacr generate invoice --number 112 --sign --send
138
+
139
+ Details:
140
+ ISSUER: Cédula Jurídica 3102123456 - EMISOR EJEMPLO
141
+ RECEIVER: Cédula Fisica 111900158 - RECEPTOR EJEMPLO
142
+ KEY: 50601041800310212345600100001010000000112199999999
143
+
144
+ => SIGN ...............
145
+ => SEND TO API .................
146
+ Document Sent
147
+ KEY: 50601041800310212345600100001010000000112199999999
148
+ Wait 5 seconds before check...
149
+ {
150
+ :key => "50601041800310212345600100001010000000112199999999",
151
+ :date => "2018-04-01T01:00:30-06:00",
152
+ :status => "aceptado",
153
+ :details => "Este comprobante fue procesado en el ambiente de pruebas, por lo cual no tiene validez para fines tributarios."
154
+ }
155
+
156
+
157
+ Para firmar documentos
158
+
159
+ $ facturacr sign_document /path/to/unsigned.xml /path/to/signed.xml
160
+
161
+ Para enviar documentos
162
+
163
+ $ facturacr send_document /path/to/signed.xml
164
+
165
+ ## To Do
166
+
167
+ - Mejorar y extender la generación de XML
168
+ - Implemntar un mapeo más sencillo para generación de XML
169
+ - Implementar Aceptación y Rechazo
170
+ - Finalizar el firmador en ruby
171
+
172
+ ## Development
173
+
174
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
175
+
176
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
177
+
178
+ ## Contributing
179
+
180
+ Bug reports and pull requests are welcome on GitHub at https://github.com/apokalipto/facturacr.
181
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "facturacr"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
Binary file
@@ -0,0 +1,10 @@
1
+ development:
2
+ api_username: su_usuario_api_atv
3
+ api_password: su_password_api_atv
4
+ key_path: tmp/llave_criptografica.p12
5
+ key_password: "99999999999999"
6
+
7
+ # api hacienda: valores default
8
+ api_client_id: 'api-stag'
9
+ documents_endpoint: "https://api.comprobanteselectronicos.go.cr/recepcion-sandbox/v1"
10
+ authentication_endpoint: "https://idp.comprobanteselectronicos.go.cr/auth/realms/rut-stag/protocol/openid-connect/token"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'facturacr/cli'
3
+ FE::CLI.start(ARGV)
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'facturacr/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "facturacr"
8
+ spec.version = FE::VERSION
9
+ spec.authors = ["Josef Sauter"]
10
+ spec.email = ["Josef.Sauter@gmail.com"]
11
+
12
+ spec.summary = %q{Facturación Electrónica de Costa Rica}
13
+ spec.homepage = "https://github.com/apokalipto/facturacr"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.10"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "minitest"
23
+ spec.add_development_dependency "minitest-colorize"
24
+
25
+ spec.add_dependency 'rest-client'
26
+ spec.add_dependency 'nokogiri'
27
+ spec.add_dependency 'colorize'
28
+ spec.add_dependency 'thor'
29
+ spec.add_dependency 'activemodel'
30
+ spec.add_dependency 'awesome_print'
31
+ end
@@ -0,0 +1,37 @@
1
+ require 'awesome_print'
2
+ require 'facturacr'
3
+ require 'facturacr/configuration'
4
+ require 'facturacr/document'
5
+ require 'facturacr/invoice'
6
+ require 'facturacr/credit_note'
7
+ require 'facturacr/debit_note'
8
+ require 'facturacr/ticket'
9
+ require 'facturacr/signed_document'
10
+ require 'facturacr/xml_document'
11
+ require 'facturacr/api'
12
+ require 'facturacr/builder'
13
+ require 'facturacr/version'
14
+ require 'facturacr/signer/signer'
15
+
16
+ module FE
17
+ class << self
18
+ attr_accessor :configuration
19
+ end
20
+
21
+ def self.configure
22
+ if self.configuration.nil?
23
+ self.configuration ||= Configuration.new
24
+ end
25
+ yield(configuration)
26
+ configuration.read_config_file if configuration.file?
27
+ end
28
+
29
+ def self.root
30
+ File.dirname __dir__
31
+ end
32
+
33
+ def self.bin
34
+ File.join root, 'bin'
35
+ end
36
+
37
+ end
@@ -0,0 +1,68 @@
1
+ require 'facturacr/api/document_status'
2
+
3
+ require 'base64'
4
+ require 'rest-client'
5
+ require 'json'
6
+
7
+ module FE
8
+ class Api
9
+
10
+ attr_accessor :authentication_endpoint, :document_endpoint, :username, :password, :client_id, :errors
11
+
12
+ def initialize(configuration = nil)
13
+ @authentication_endpoint = (configuration || FE.configuration).authentication_endpoint
14
+ @document_endpoint = (configuration || FE.configuration).documents_endpoint
15
+ @username = (configuration || FE.configuration).api_username
16
+ @password = (configuration || FE.configuration).api_password
17
+ @client_id = (configuration || FE.configuration).api_client_id
18
+ @errors = {}
19
+ end
20
+
21
+ def authenticate
22
+ response = RestClient.post @authentication_endpoint, auth_data
23
+ @token = JSON.parse(response)["access_token"]
24
+
25
+ @token
26
+ rescue => e
27
+ raise "AUTH ERROR"
28
+ end
29
+
30
+
31
+ def send_document(payload)
32
+ authenticate
33
+ response = RestClient.post "#{@document_endpoint}/recepcion", payload.to_json, {:Authorization=> "bearer #{@token}", content_type: :json}
34
+ return true if response.code.eql?(200) || response.code.eql?(202)
35
+ rescue => e
36
+ @errors[:request] = {message: e.message, response: e.response}
37
+ return false
38
+ end
39
+
40
+ def get_document_status(key)
41
+ authenticate
42
+ response = RestClient.get "#{@document_endpoint}/recepcion/#{key}", {:Authorization=> "bearer #{@token}", content_type: :json}
43
+ return FE::Api::DocumentStatus.new(response)
44
+ end
45
+
46
+ def get_document(key)
47
+ authenticate
48
+ response = RestClient.get "#{@document_endpoint}/comprogantes/#{key}", {:Authorization=> "bearer #{@token}", content_type: :json}
49
+ JSON.parse(response)
50
+ end
51
+
52
+
53
+ private
54
+
55
+
56
+
57
+ def auth_data
58
+ {
59
+ grant_type: 'password',
60
+ client_id: @client_id,
61
+ username: @username,
62
+ password: @password,
63
+ client_secret: '',
64
+ scope: ''
65
+ }
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,36 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'nokogiri'
4
+
5
+ module FE
6
+ class Api
7
+ class DocumentStatus
8
+
9
+ attr_reader :document
10
+ attr_accessor :json, :xml, :key, :date, :status
11
+
12
+ def initialize(json)
13
+ @json = json
14
+ @response = JSON.parse(json)
15
+ @xml = Base64.decode64(@response["respuesta-xml"]) if @response["respuesta-xml"]
16
+ @status = @response["ind-estado"]
17
+ @date = @response["fecha"]
18
+ @key = @response["clave"]
19
+ @document = Nokogiri::XML(@xml) if @xml
20
+ end
21
+
22
+ def details
23
+ @document.css("MensajeHacienda DetalleMensaje").first.text if @document
24
+ end
25
+
26
+ def to_h
27
+ {
28
+ key: @key,
29
+ date: @date,
30
+ status: @status,
31
+ details: details
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end