sat_mx 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecf0432037eb033ade4de9f89e9d9edfcd4a7174391758d2c1dc47c5771e44b0
4
- data.tar.gz: 0aaf19d6965d16cc6c28047964fe4d00be5c925e9cc921f37f3e86985f50993e
3
+ metadata.gz: 24d8984a200bf9e2d95a2fcb90e10097f54a4ed496e418c3880df1848dd5c302
4
+ data.tar.gz: 01a221e8d1bcab95919dc4990677ca9c63bc775e1e1f03281c5c07be2653805c
5
5
  SHA512:
6
- metadata.gz: 21ce855b92d37223820e13056c03468880473bd0a791987d2ba8adc65c7d6da11f5f058a52a7d2f3ca26b1cd4d2434c1308cd9e3f10235add7d6ab9a1b7c5a98
7
- data.tar.gz: 8a5b7724f51b3698d1a71b15da5e75fa905082ce7b631d261e2c20fec9f0cc47fb4ea4f2992012437afc94759ae419b34b4aa565f79bace0ca1f8632eed97084
6
+ metadata.gz: 3e1905940a68f281204884f848fea09806e6cc7ce242389aba92154e95777d5563b0b400dd731832982f824c626f4353bf204138f75a5ca26b1f4db063adabbe
7
+ data.tar.gz: 3535c79dafbb37cc18fc887a3d266a7ce9f1474e4f0d5add1ae6cfae652c88e64dd72b7fc73867c4d9a82b941c5179a2f447943df63170ee8ad01a0c69f1e18c
data/README.md CHANGED
@@ -1,39 +1,98 @@
1
1
  # SatMx
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
4
-
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/sat_mx`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Ruby client for SAT (Mexican Tax Administration) web services to download CFDI invoices.
6
4
 
7
5
  ## Installation
8
6
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
7
+ ```bash
8
+ gem install sat_mx
9
+ ```
10
10
 
11
- Install the gem and add to the application's Gemfile by executing:
11
+ Or add to your Gemfile:
12
12
 
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
13
+ ```ruby
14
+ gem "sat_mx"
15
+ ```
14
16
 
15
- If bundler is not being used to manage dependencies, install the gem by executing:
17
+ ## Configuration
16
18
 
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
19
+ ```ruby
20
+ SatMx.configure do |config|
21
+ config[:certificate] = "path/to/certificate.cer"
22
+ config[:private_key] = "path/to/private.key"
23
+ config[:password] = "key_password"
24
+ end
25
+ ```
18
26
 
19
27
  ## Usage
20
28
 
21
- TODO: Write usage instructions here
22
-
23
- ## Development
24
-
25
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
-
27
- 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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
-
29
- ## Contributing
30
-
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sat_mx. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/sat_mx/blob/master/CODE_OF_CONDUCT.md).
29
+ ```ruby
30
+ # 1. Authenticate
31
+ result = SatMx.authenticate
32
+ raise "Auth failed" unless result.success?
33
+
34
+ token = result.value
35
+
36
+ # 2. Request download
37
+ result = SatMx.download_request(
38
+ start_date: Time.new(2024, 1, 1),
39
+ end_date: Time.new(2024, 1, 31),
40
+ request_type: :cfdi,
41
+ issuing_rfc: "ABC010101ABC",
42
+ recipient_rfcs: ["XYZ020202XYZ"],
43
+ requester_rfc: "ABC010101ABC",
44
+ access_token: token
45
+ )
46
+ raise "Request failed" unless result.success?
47
+
48
+ request_id = result.value
49
+
50
+ # 3. Verify status (poll until ready)
51
+ loop do
52
+ result = SatMx.verify_request(
53
+ request_id: request_id,
54
+ requester_rfc: "ABC010101ABC",
55
+ access_token: token
56
+ )
57
+
58
+ case result.value[:request_status]
59
+ when :finished
60
+ break result.value[:package_ids]
61
+ when :error, :rejected, :expired
62
+ raise "Request failed: #{result.value}"
63
+ end
64
+
65
+ sleep 5
66
+ end
67
+
68
+ # 4. Download packages
69
+ package_ids.each do |package_id|
70
+ result = SatMx.download_petition(
71
+ package_id: package_id,
72
+ requester_rfc: "ABC010101ABC",
73
+ access_token: token
74
+ )
75
+
76
+ File.write("#{package_id}.zip", result.value) if result.success?
77
+ end
78
+ ```
79
+
80
+ ## API
81
+
82
+ | Method | Description |
83
+ |--------|-------------|
84
+ | `SatMx.configure` | Configure certificate and private key |
85
+ | `SatMx.configuration` | Get current configuration |
86
+ | `SatMx.authenticate` | Get access token |
87
+ | `SatMx.download_request` | Request CFDI download |
88
+ | `SatMx.verify_request` | Check request status |
89
+ | `SatMx.download_petition` | Download package |
90
+
91
+ All methods return a `Result` object with:
92
+ - `success?` - Boolean indicating success
93
+ - `value` - Data on success, error hash `{:cod_estatus, :mensaje}` on failure
94
+ - `xml` - Raw XML response
32
95
 
33
96
  ## License
34
97
 
35
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
36
-
37
- ## Code of Conduct
38
-
39
- Everyone interacting in the SatMx project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/sat_mx/blob/master/CODE_OF_CONDUCT.md).
98
+ MIT
@@ -3,6 +3,7 @@ require "time"
3
3
  require "base64"
4
4
 
5
5
  module SatMx
6
+ # @api private
6
7
  class Authentication
7
8
  def self.authenticate(certificate:, private_key:, uuid: SecureRandom.uuid)
8
9
  new(
@@ -43,6 +44,7 @@ module SatMx
43
44
  attr_reader :xml_auth_body, :client
44
45
  end
45
46
 
47
+ # @api private
46
48
  class XmlAuthBody
47
49
  def initialize(certificate:, uuid:)
48
50
  @certificate = certificate
data/lib/sat_mx/body.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "base64"
2
2
 
3
3
  module SatMx
4
+ # @api private
4
5
  module Body
5
6
  S11 = "S11"
6
7
  XMLNS = "xmlns"
data/lib/sat_mx/client.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module SatMx
2
+ # @api private
2
3
  class Client
3
4
  HEADERS = {
4
5
  "content-type" => "text/xml; charset=utf-8",
@@ -1,4 +1,5 @@
1
1
  module SatMx
2
+ # @api private
2
3
  Configuration = Data.define(:certificate, :private_key) do
3
4
  def initialize(certificate:, private_key:, password:)
4
5
  super(
@@ -1,4 +1,5 @@
1
1
  module SatMx
2
+ # @api private
2
3
  class DownloadPetition
3
4
  def self.call(package_id:, requester_rfc:, access_token:, certificate:, private_key:)
4
5
  new(
@@ -41,8 +42,8 @@ module SatMx
41
42
  success?: false,
42
43
  xml: response.xml,
43
44
  value: {
44
- CodEstatus: response_tag["CodEstatus"],
45
- Mensaje: response_tag["Mensaje"]
45
+ cod_estatus: response_tag["CodEstatus"],
46
+ mensaje: response_tag["Mensaje"]
46
47
  }
47
48
  )
48
49
  end
@@ -1,4 +1,5 @@
1
1
  module SatMx
2
+ # @api private
2
3
  class DownloadPetitionBody
3
4
  include Body
4
5
 
@@ -1,4 +1,5 @@
1
1
  module SatMx
2
+ # @api private
2
3
  class DownloadRequest
3
4
  def self.call(start_date:,
4
5
  end_date:,
@@ -56,8 +57,8 @@ module SatMx
56
57
  Result.new(
57
58
  success?: false,
58
59
  value: {
59
- CodEstatus: download_result_tag.attr("CodEstatus").value,
60
- Mensaje: download_result_tag.attr("Mensaje").value
60
+ cod_estatus: download_result_tag.attr("CodEstatus").value,
61
+ mensaje: download_result_tag.attr("Mensaje").value
61
62
  },
62
63
  xml:
63
64
  )
@@ -1,6 +1,7 @@
1
1
  require "time"
2
2
 
3
3
  module SatMx
4
+ # @api private
4
5
  class DownloadRequestBody
5
6
  include Body
6
7
 
data/lib/sat_mx/result.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  module SatMx
2
+ # @api private
2
3
  Result = Data.define(:success?, :value, :xml)
3
4
  end
data/lib/sat_mx/signer.rb CHANGED
@@ -2,6 +2,7 @@ require "openssl"
2
2
  require "xmldsig"
3
3
 
4
4
  module SatMx
5
+ # @api private
5
6
  class Signer
6
7
  def self.sign(document:, private_key:)
7
8
  new(document:, private_key:).sign
@@ -1,4 +1,5 @@
1
1
  module SatMx
2
+ # @api private
2
3
  class VerifyRequest
3
4
  STATUS = {
4
5
  "1" => :accepted,
@@ -49,8 +50,8 @@ module SatMx
49
50
  Result.new(
50
51
  success?: false,
51
52
  value: {
52
- CodEstatus: download_result_tag.attr("CodEstatus").value,
53
- Mensaje: download_result_tag.attr("Mensaje").value
53
+ cod_status: download_result_tag.attr("CodEstatus").value,
54
+ mensaje: download_result_tag.attr("Mensaje").value
54
55
  },
55
56
  xml:
56
57
  )
@@ -1,4 +1,5 @@
1
1
  module SatMx
2
+ # @api private
2
3
  class VerifyRequestBody
3
4
  include Body
4
5
 
@@ -1,3 +1,3 @@
1
1
  module SatMx
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/sat_mx.rb CHANGED
@@ -46,6 +46,9 @@ module SatMx
46
46
  # puts "Authentication failed"
47
47
  # end
48
48
  #
49
+ # @param certificate [OpenSSL::X509::Certificate, nil] Certificate object (uses configuration if nil)
50
+ # @param private_key [OpenSSL::PKey::RSA, nil] Private key object (uses configuration if nil)
51
+ #
49
52
  # @return [SatMx::Result] A Result object containing:
50
53
  # - success?: [Boolean] whether the authentication was successful
51
54
  # - value: [String, nil] the authentication token if successful, nil otherwise
@@ -53,10 +56,139 @@ module SatMx
53
56
  #
54
57
  # @see SatMx::Authentication
55
58
  # @see SatMx::Result
56
- def authenticate
59
+ def authenticate(certificate: nil, private_key: nil)
60
+ cert = certificate || configuration.certificate
61
+ key = private_key || configuration.private_key
57
62
  Authentication.authenticate(
58
- certificate: configuration.certificate,
59
- private_key: configuration.private_key
63
+ certificate: cert,
64
+ private_key: key
65
+ )
66
+ end
67
+
68
+ # Requests a download of CFDI documents from the SAT web service.
69
+ #
70
+ # result = SatMx.download_request(
71
+ # start_date: Time.new(2024, 1, 1),
72
+ # end_date: Time.new(2024, 1, 31),
73
+ # request_type: :cfdi,
74
+ # issuing_rfc: "ABC010101ABC",
75
+ # recipient_rfcs: ["XYZ020202XYZ"],
76
+ # requester_rfc: "ABC010101ABC",
77
+ # access_token: "your_access_token"
78
+ # )
79
+ # if result.success?
80
+ # puts "Request ID: #{result.value}"
81
+ # else
82
+ # puts "Request failed: #{result.value}"
83
+ # end
84
+ #
85
+ # @param start_date [Time] Start date for the search range
86
+ # @param end_date [Time] End date for the search range
87
+ # @param request_type [Symbol] Type of request (:cfdi or :retentions)
88
+ # @param issuing_rfc [String] RFC of the issuer
89
+ # @param recipient_rfcs [Array<String>] RFCs of the recipients
90
+ # @param requester_rfc [String] RFC of the requester
91
+ # @param access_token [String] Authentication token from SatMx.authenticate
92
+ # @param certificate [String, nil] Path to certificate file (uses configuration if nil)
93
+ # @param private_key [String, nil] Path to private key file (uses configuration if nil)
94
+ #
95
+ # @return [SatMx::Result] A Result object containing:
96
+ # - success?: [Boolean] whether the request was successful
97
+ # - value: [String, nil] the request ID if successful, or {cod_estatus:, mensaje:} on failure
98
+ # - xml: [Nokogiri::XML::Document] the raw XML response from the service
99
+ #
100
+ # @see SatMx::DownloadRequest
101
+ # @see SatMx::Result
102
+ def download_request(start_date:, end_date:, request_type:, issuing_rfc:, recipient_rfcs:, requester_rfc:, access_token:, **options)
103
+ certificate = options[:certificate] || configuration.certificate
104
+ private_key = options[:private_key] || configuration.private_key
105
+ DownloadRequest.call(
106
+ start_date:,
107
+ end_date:,
108
+ request_type:,
109
+ issuing_rfc:,
110
+ recipient_rfcs:,
111
+ requester_rfc:,
112
+ access_token:,
113
+ certificate:,
114
+ private_key:
115
+ )
116
+ end
117
+
118
+ # Verifies the status of a previously submitted download request.
119
+ #
120
+ # result = SatMx.verify_request(
121
+ # request_id: "606c5667-345a-4630-8979-0769734ac80b",
122
+ # requester_rfc: "ABC010101ABC",
123
+ # access_token: "your_access_token"
124
+ # )
125
+ # if result.success?
126
+ # puts "Status: #{result.value[:request_status]}"
127
+ # puts "Packages: #{result.value[:package_ids]}"
128
+ # else
129
+ # puts "Verification failed: #{result.value}"
130
+ # end
131
+ #
132
+ # @param request_id [String] The ID returned from SatMx.download_request
133
+ # @param requester_rfc [String] RFC of the requester
134
+ # @param access_token [String] Authentication token from SatMx.authenticate
135
+ # @param certificate [String, nil] Path to certificate file (uses configuration if nil)
136
+ # @param private_key [String, nil] Path to private key file (uses configuration if nil)
137
+ #
138
+ # @return [SatMx::Result] A Result object containing:
139
+ # - success?: [Boolean] whether the verification was successful
140
+ # - value: [Hash, nil] containing :request_status and :package_ids if successful, or {cod_estatus:, mensaje:} on failure
141
+ # - xml: [Nokogiri::XML::Document] the raw XML response from the service
142
+ #
143
+ # @see SatMx::VerifyRequest
144
+ # @see SatMx::Result
145
+ def verify_request(request_id:, requester_rfc:, access_token:, **options)
146
+ certificate = options[:certificate] || configuration.certificate
147
+ private_key = options[:private_key] || configuration.private_key
148
+ VerifyRequest.call(
149
+ request_id:,
150
+ requester_rfc:,
151
+ access_token:,
152
+ certificate:,
153
+ private_key:
154
+ )
155
+ end
156
+
157
+ # Downloads a package of CFDI documents from the SAT web service.
158
+ #
159
+ # result = SatMx.download_petition(
160
+ # package_id: "18015570-C084-4BE8-BE36-476F5D46A133_01",
161
+ # requester_rfc: "ABC010101ABC",
162
+ # access_token: "your_access_token"
163
+ # )
164
+ # if result.success?
165
+ # File.write("package.zip", result.value)
166
+ # else
167
+ # puts "Download failed: #{result.value}"
168
+ # end
169
+ #
170
+ # @param package_id [String] The package ID from SatMx.verify_request
171
+ # @param requester_rfc [String] RFC of the requester
172
+ # @param access_token [String] Authentication token from SatMx.authenticate
173
+ # @param certificate [String, nil] Path to certificate file (uses configuration if nil)
174
+ # @param private_key [String, nil] Path to private key file (uses configuration if nil)
175
+ #
176
+ # @return [SatMx::Result] A Result object containing:
177
+ # - success?: [Boolean] whether the download was successful
178
+ # - value: [String, nil] Base64 encoded ZIP content if successful, or {cod_estatus:, mensaje:} on failure
179
+ # - xml: [Nokogiri::XML::Document] the raw XML response from the service
180
+ #
181
+ # @see SatMx::DownloadPetition
182
+ # @see SatMx::Result
183
+ def download_petition(package_id:, requester_rfc:, access_token:, **options)
184
+ certificate = options[:certificate] || configuration.certificate
185
+ private_key = options[:private_key] || configuration.private_key
186
+ DownloadPetition.call(
187
+ package_id:,
188
+ requester_rfc:,
189
+ access_token:,
190
+ certificate:,
191
+ private_key:
60
192
  )
61
193
  end
62
194
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sat_mx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oscar Rivas
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-17 00:00:00.000000000 Z
11
+ date: 2026-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xmldsig
@@ -143,7 +143,7 @@ licenses:
143
143
  metadata:
144
144
  homepage_uri: https://github.com/kadru/sat_mx
145
145
  changelog_uri: https://github.com/kadru/sat_mx/CHANGELOG.md
146
- post_install_message:
146
+ post_install_message:
147
147
  rdoc_options: []
148
148
  require_paths:
149
149
  - lib
@@ -158,8 +158,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
158
  - !ruby/object:Gem::Version
159
159
  version: '0'
160
160
  requirements: []
161
- rubygems_version: 3.5.23
162
- signing_key:
161
+ rubygems_version: 3.5.11
162
+ signing_key:
163
163
  specification_version: 4
164
164
  summary: a client to connect to SAT web services
165
165
  test_files: []