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 +4 -4
- data/README.md +83 -24
- data/lib/sat_mx/authentication.rb +2 -0
- data/lib/sat_mx/body.rb +1 -0
- data/lib/sat_mx/client.rb +1 -0
- data/lib/sat_mx/configuration.rb +1 -0
- data/lib/sat_mx/download_petition.rb +3 -2
- data/lib/sat_mx/download_petition_body.rb +1 -0
- data/lib/sat_mx/download_request.rb +3 -2
- data/lib/sat_mx/download_request_body.rb +1 -0
- data/lib/sat_mx/result.rb +1 -0
- data/lib/sat_mx/signer.rb +1 -0
- data/lib/sat_mx/verify_request.rb +3 -2
- data/lib/sat_mx/verify_request_body.rb +1 -0
- data/lib/sat_mx/version.rb +1 -1
- data/lib/sat_mx.rb +135 -3
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24d8984a200bf9e2d95a2fcb90e10097f54a4ed496e418c3880df1848dd5c302
|
|
4
|
+
data.tar.gz: 01a221e8d1bcab95919dc4990677ca9c63bc775e1e1f03281c5c07be2653805c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e1905940a68f281204884f848fea09806e6cc7ce242389aba92154e95777d5563b0b400dd731832982f824c626f4353bf204138f75a5ca26b1f4db063adabbe
|
|
7
|
+
data.tar.gz: 3535c79dafbb37cc18fc887a3d266a7ce9f1474e4f0d5add1ae6cfae652c88e64dd72b7fc73867c4d9a82b941c5179a2f447943df63170ee8ad01a0c69f1e18c
|
data/README.md
CHANGED
|
@@ -1,39 +1,98 @@
|
|
|
1
1
|
# SatMx
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
7
|
+
```bash
|
|
8
|
+
gem install sat_mx
|
|
9
|
+
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Or add to your Gemfile:
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
```ruby
|
|
14
|
+
gem "sat_mx"
|
|
15
|
+
```
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
## Configuration
|
|
16
18
|
|
|
17
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
data/lib/sat_mx/client.rb
CHANGED
data/lib/sat_mx/configuration.rb
CHANGED
|
@@ -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
|
-
|
|
45
|
-
|
|
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 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
|
-
|
|
60
|
-
|
|
60
|
+
cod_estatus: download_result_tag.attr("CodEstatus").value,
|
|
61
|
+
mensaje: download_result_tag.attr("Mensaje").value
|
|
61
62
|
},
|
|
62
63
|
xml:
|
|
63
64
|
)
|
data/lib/sat_mx/result.rb
CHANGED
data/lib/sat_mx/signer.rb
CHANGED
|
@@ -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
|
-
|
|
53
|
-
|
|
53
|
+
cod_status: download_result_tag.attr("CodEstatus").value,
|
|
54
|
+
mensaje: download_result_tag.attr("Mensaje").value
|
|
54
55
|
},
|
|
55
56
|
xml:
|
|
56
57
|
)
|
data/lib/sat_mx/version.rb
CHANGED
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:
|
|
59
|
-
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.
|
|
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:
|
|
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.
|
|
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: []
|