sat_mx 0.1.0 → 0.2.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/Rakefile +0 -2
- data/lib/sat_mx/{bulk_download.rb → authentication.rb} +33 -41
- data/lib/sat_mx/body.rb +65 -0
- data/lib/sat_mx/client.rb +69 -0
- data/lib/sat_mx/configuration.rb +13 -0
- data/lib/sat_mx/download_petition.rb +64 -0
- data/lib/sat_mx/download_petition_body.rb +28 -0
- data/lib/sat_mx/download_request.rb +67 -0
- data/lib/sat_mx/download_request_body.rb +59 -0
- data/lib/sat_mx/result.rb +3 -0
- data/lib/sat_mx/signer.rb +25 -0
- data/lib/sat_mx/verify_request.rb +69 -0
- data/lib/sat_mx/verify_request_body.rb +28 -0
- data/lib/sat_mx/version.rb +1 -3
- data/lib/sat_mx.rb +58 -3
- metadata +29 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecf0432037eb033ade4de9f89e9d9edfcd4a7174391758d2c1dc47c5771e44b0
|
4
|
+
data.tar.gz: 0aaf19d6965d16cc6c28047964fe4d00be5c925e9cc921f37f3e86985f50993e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 21ce855b92d37223820e13056c03468880473bd0a791987d2ba8adc65c7d6da11f5f058a52a7d2f3ca26b1cd4d2434c1308cd9e3f10235add7d6ab9a1b7c5a98
|
7
|
+
data.tar.gz: 8a5b7724f51b3698d1a71b15da5e75fa905082ce7b631d261e2c20fec9f0cc47fb4ea4f2992012437afc94759ae419b34b4aa565f79bace0ca1f8632eed97084
|
data/Rakefile
CHANGED
@@ -1,43 +1,38 @@
|
|
1
|
-
require "openssl"
|
2
1
|
require "httpx"
|
3
|
-
require "xmldsig"
|
4
2
|
require "time"
|
3
|
+
require "base64"
|
5
4
|
|
6
5
|
module SatMx
|
7
|
-
|
8
|
-
|
9
|
-
class BulkDownload
|
10
|
-
AUTH_URL = "https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/Autenticacion/Autenticacion.svc".freeze
|
11
|
-
HEADERS = {
|
12
|
-
"content-type" => "text/xml; charset=utf-8",
|
13
|
-
"accept" => "text/xml",
|
14
|
-
"SOAPAction" => "http://DescargaMasivaTerceros.gob.mx/IAutenticacion/Autentica"
|
15
|
-
}.freeze
|
16
|
-
private_constant :AUTH_URL
|
17
|
-
private_constant :HEADERS
|
18
|
-
|
19
|
-
def self.authenticate(certificate:, private_key:, id: nil)
|
6
|
+
class Authentication
|
7
|
+
def self.authenticate(certificate:, private_key:, uuid: SecureRandom.uuid)
|
20
8
|
new(
|
21
|
-
xml_auth_body: XmlAuthBody.new(
|
9
|
+
xml_auth_body: XmlAuthBody.new(
|
10
|
+
certificate:,
|
11
|
+
uuid:
|
12
|
+
),
|
13
|
+
client: Client.new(
|
14
|
+
private_key:,
|
15
|
+
access_token: ""
|
16
|
+
)
|
22
17
|
).authenticate
|
23
18
|
end
|
24
19
|
|
25
|
-
def initialize(xml_auth_body:)
|
20
|
+
def initialize(xml_auth_body:, client:)
|
26
21
|
@xml_auth_body = xml_auth_body
|
22
|
+
@client = client
|
27
23
|
end
|
28
24
|
|
29
25
|
def authenticate
|
30
|
-
response =
|
31
|
-
AUTH_URL,
|
32
|
-
headers: HEADERS,
|
33
|
-
body: xml_auth_body.sign
|
34
|
-
)
|
26
|
+
response = client.authenticate(xml_auth_body.generate)
|
35
27
|
|
36
28
|
case response.status
|
37
29
|
when 200..299
|
38
|
-
Result.new(success?: true,
|
30
|
+
Result.new(success?: true,
|
31
|
+
value: response.xml.xpath("//xmlns:AutenticaResult",
|
32
|
+
xmlns: "http://DescargaMasivaTerceros.gob.mx").inner_text,
|
33
|
+
xml: response.xml)
|
39
34
|
when 400..599
|
40
|
-
Result.new(success?: false, value: response.xml)
|
35
|
+
Result.new(success?: false, value: nil, xml: response.xml)
|
41
36
|
else
|
42
37
|
SatMx::Error
|
43
38
|
end
|
@@ -45,39 +40,28 @@ module SatMx
|
|
45
40
|
|
46
41
|
private
|
47
42
|
|
48
|
-
attr_reader :xml_auth_body
|
43
|
+
attr_reader :xml_auth_body, :client
|
49
44
|
end
|
50
45
|
|
51
46
|
class XmlAuthBody
|
52
|
-
def initialize(certificate:,
|
47
|
+
def initialize(certificate:, uuid:)
|
53
48
|
@certificate = certificate
|
54
|
-
@
|
55
|
-
@id = id
|
49
|
+
@uuid = uuid
|
56
50
|
end
|
57
51
|
|
58
|
-
def
|
59
|
-
Xmldsig::SignedDocument.new(xml_document).sign do |data|
|
60
|
-
private_key.sign(OpenSSL::Digest.new("SHA1"), data)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
attr_reader :private_key, :certificate, :id
|
67
|
-
|
68
|
-
def xml_document
|
52
|
+
def generate
|
69
53
|
<<~XML
|
70
54
|
<S11:Envelope xmlns:S11="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
|
71
55
|
<S11:Header>
|
72
56
|
<wsse:Security S11:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
73
57
|
#{timestamp}
|
74
|
-
<wsse:BinarySecurityToken wsu:Id="#{
|
58
|
+
<wsse:BinarySecurityToken wsu:Id="#{uuid}" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">#{Base64.strict_encode64(certificate.to_der)}</wsse:BinarySecurityToken>
|
75
59
|
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
|
76
60
|
#{signed_info}
|
77
61
|
<SignatureValue></SignatureValue>
|
78
62
|
<KeyInfo>
|
79
63
|
<wsse:SecurityTokenReference>
|
80
|
-
<wsse:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="##{
|
64
|
+
<wsse:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="##{uuid}" />
|
81
65
|
</wsse:SecurityTokenReference>
|
82
66
|
</KeyInfo>
|
83
67
|
</Signature>
|
@@ -90,6 +74,10 @@ module SatMx
|
|
90
74
|
XML
|
91
75
|
end
|
92
76
|
|
77
|
+
private
|
78
|
+
|
79
|
+
attr_reader :certificate
|
80
|
+
|
93
81
|
def timestamp
|
94
82
|
current_time = Time.now.utc
|
95
83
|
<<~XML
|
@@ -115,5 +103,9 @@ module SatMx
|
|
115
103
|
</SignedInfo>
|
116
104
|
XML
|
117
105
|
end
|
106
|
+
|
107
|
+
def uuid
|
108
|
+
"uuid-#{@uuid}-1"
|
109
|
+
end
|
118
110
|
end
|
119
111
|
end
|
data/lib/sat_mx/body.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require "base64"
|
2
|
+
|
3
|
+
module SatMx
|
4
|
+
module Body
|
5
|
+
S11 = "S11"
|
6
|
+
XMLNS = "xmlns"
|
7
|
+
DES = "des"
|
8
|
+
DS = "ds"
|
9
|
+
|
10
|
+
ENVELOPE_ATTRS = {
|
11
|
+
"#{XMLNS}:#{S11}" => "http://schemas.xmlsoap.org/soap/envelope/",
|
12
|
+
"#{XMLNS}:#{DES}" => "http://DescargaMasivaTerceros.sat.gob.mx",
|
13
|
+
"#{XMLNS}:#{DS}" => "http://www.w3.org/2000/09/xmldsig#"
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
NAMESPACE = ENVELOPE_ATTRS["#{XMLNS}:#{DES}"]
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def envelope
|
21
|
+
Nokogiri::XML::Builder.new do |xml|
|
22
|
+
xml[S11].Envelope(
|
23
|
+
ENVELOPE_ATTRS
|
24
|
+
) do
|
25
|
+
xml[S11].Header
|
26
|
+
xml[S11].Body do
|
27
|
+
yield xml
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end.doc.root.to_xml
|
31
|
+
end
|
32
|
+
|
33
|
+
def signature(xml)
|
34
|
+
xml.Signature(XMLNS => "http://www.w3.org/2000/09/xmldsig#") do
|
35
|
+
xml.SignedInfo do
|
36
|
+
xml.CanonicalizationMethod("Algorithm" => "http://www.w3.org/TR/2001/REC-xml-c14n-20010315")
|
37
|
+
xml.SignatureMethod("Algorithm" => "http://www.w3.org/2000/09/xmldsig#rsa-sha1")
|
38
|
+
xml.Reference("URI" => "") do
|
39
|
+
xml.Transforms do
|
40
|
+
xml.Transform("Algorithm" => "http://www.w3.org/2000/09/xmldsig#enveloped-signature")
|
41
|
+
end
|
42
|
+
xml.DigestMethod("Algorithm" => "http://www.w3.org/2000/09/xmldsig#sha1")
|
43
|
+
xml.DigestValue
|
44
|
+
end
|
45
|
+
end
|
46
|
+
xml.SignatureValue
|
47
|
+
xml.KeyInfo do
|
48
|
+
xml.X509Data do
|
49
|
+
xml.X509IssuerSerial do
|
50
|
+
xml.X509IssuerName(certificate_issuer)
|
51
|
+
xml.X509SerialNumber(certificate_serial)
|
52
|
+
end
|
53
|
+
xml.X509Certificate(encoded_certificate)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def certificate_issuer = certificate.issuer.to_s(OpenSSL::X509::Name::RFC2253)
|
60
|
+
|
61
|
+
def certificate_serial = certificate.serial
|
62
|
+
|
63
|
+
def encoded_certificate = Base64.strict_encode64(certificate.to_der)
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module SatMx
|
2
|
+
class Client
|
3
|
+
HEADERS = {
|
4
|
+
"content-type" => "text/xml; charset=utf-8",
|
5
|
+
"accept" => "text/xml"
|
6
|
+
}.freeze
|
7
|
+
private_constant :HEADERS
|
8
|
+
|
9
|
+
def initialize(private_key:, access_token:)
|
10
|
+
@private_key = private_key
|
11
|
+
@access_token = access_token
|
12
|
+
end
|
13
|
+
|
14
|
+
def authenticate(payload)
|
15
|
+
HTTPX.post(
|
16
|
+
"https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/Autenticacion/Autenticacion.svc",
|
17
|
+
headers: {
|
18
|
+
"SOAPAction" => "http://DescargaMasivaTerceros.gob.mx/IAutenticacion/Autentica"
|
19
|
+
}.merge(HEADERS),
|
20
|
+
body: sign(payload)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def download_request(payload)
|
25
|
+
HTTPX.post(
|
26
|
+
"https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/SolicitaDescargaService.svc",
|
27
|
+
headers: {
|
28
|
+
"SOAPAction" => "http://DescargaMasivaTerceros.sat.gob.mx/ISolicitaDescargaService/SolicitaDescarga"
|
29
|
+
}.merge(authorization)
|
30
|
+
.merge(HEADERS),
|
31
|
+
body: sign(payload)
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify_request(payload)
|
36
|
+
HTTPX.post(
|
37
|
+
"https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/VerificaSolicitudDescargaService.svc",
|
38
|
+
headers: {
|
39
|
+
"SOAPAction" => "http://DescargaMasivaTerceros.sat.gob.mx/IVerificaSolicitudDescargaService/VerificaSolicitudDescarga"
|
40
|
+
}.merge(authorization)
|
41
|
+
.merge(HEADERS),
|
42
|
+
body: sign(payload)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def download_petition(payload)
|
47
|
+
HTTPX.post(
|
48
|
+
"https://cfdidescargamasiva.clouda.sat.gob.mx/DescargaMasivaService.svc",
|
49
|
+
headers: {
|
50
|
+
"SOAPAction" => "http://DescargaMasivaTerceros.sat.gob.mx/IDescargaMasivaTercerosService/Descargar"
|
51
|
+
}.merge(authorization)
|
52
|
+
.merge(HEADERS),
|
53
|
+
body: sign(payload)
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_reader :private_key, :access_token
|
60
|
+
|
61
|
+
def authorization
|
62
|
+
{"Authorization" => "WRAP access_token=\"#{access_token}\""}
|
63
|
+
end
|
64
|
+
|
65
|
+
def sign(payload)
|
66
|
+
Signer.sign(document: payload, private_key:)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SatMx
|
2
|
+
Configuration = Data.define(:certificate, :private_key) do
|
3
|
+
def initialize(certificate:, private_key:, password:)
|
4
|
+
super(
|
5
|
+
certificate: OpenSSL::X509::Certificate.new(File.read(certificate)),
|
6
|
+
private_key: OpenSSL::PKey::RSA.new(
|
7
|
+
File.read(private_key),
|
8
|
+
password
|
9
|
+
)
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module SatMx
|
2
|
+
class DownloadPetition
|
3
|
+
def self.call(package_id:, requester_rfc:, access_token:, certificate:, private_key:)
|
4
|
+
new(
|
5
|
+
body: DownloadPetitionBody.new(
|
6
|
+
package_id:,
|
7
|
+
requester_rfc:,
|
8
|
+
certificate:
|
9
|
+
),
|
10
|
+
client: Client.new(private_key:, access_token:)
|
11
|
+
).call
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(body:, client:)
|
15
|
+
@body = body
|
16
|
+
@client = client
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
response = client.download_petition(body.generate)
|
21
|
+
|
22
|
+
case response.status
|
23
|
+
when 200..299
|
24
|
+
xml = response.xml
|
25
|
+
response_tag = xml.xpath(
|
26
|
+
"//xmlns:respuesta",
|
27
|
+
xmlns: "http://DescargaMasivaTerceros.sat.gob.mx"
|
28
|
+
)[0]
|
29
|
+
|
30
|
+
if response_tag["CodEstatus"] == "5000"
|
31
|
+
Result.new(
|
32
|
+
success?: true,
|
33
|
+
xml: response.xml,
|
34
|
+
value: response.xml.xpath(
|
35
|
+
"//xmlns:Paquete",
|
36
|
+
xmlns: "http://DescargaMasivaTerceros.sat.gob.mx"
|
37
|
+
).inner_text
|
38
|
+
)
|
39
|
+
else
|
40
|
+
Result.new(
|
41
|
+
success?: false,
|
42
|
+
xml: response.xml,
|
43
|
+
value: {
|
44
|
+
CodEstatus: response_tag["CodEstatus"],
|
45
|
+
Mensaje: response_tag["Mensaje"]
|
46
|
+
}
|
47
|
+
)
|
48
|
+
end
|
49
|
+
when 400..599
|
50
|
+
Result.new(
|
51
|
+
success?: false,
|
52
|
+
xml: nil,
|
53
|
+
value: nil
|
54
|
+
)
|
55
|
+
else
|
56
|
+
SatMx::Error
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
attr_reader :client, :body
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SatMx
|
2
|
+
class DownloadPetitionBody
|
3
|
+
include Body
|
4
|
+
|
5
|
+
def initialize(package_id:, requester_rfc:, certificate:)
|
6
|
+
@package_id = package_id
|
7
|
+
@requester_rfc = requester_rfc
|
8
|
+
@certificate = certificate
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate
|
12
|
+
envelope do |xml|
|
13
|
+
xml[Body::DES].PeticionDescargaMasivaTercerosEntrada do
|
14
|
+
xml[Body::DES].peticionDescarga(
|
15
|
+
IdPaquete: package_id,
|
16
|
+
RfcSolicitante: requester_rfc
|
17
|
+
) do
|
18
|
+
signature(xml)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :package_id, :requester_rfc, :certificate
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module SatMx
|
2
|
+
class DownloadRequest
|
3
|
+
def self.call(start_date:,
|
4
|
+
end_date:,
|
5
|
+
request_type:,
|
6
|
+
issuing_rfc:,
|
7
|
+
recipient_rfcs:,
|
8
|
+
requester_rfc:,
|
9
|
+
access_token:,
|
10
|
+
certificate:,
|
11
|
+
private_key:)
|
12
|
+
new(
|
13
|
+
download_request_body: DownloadRequestBody.new(
|
14
|
+
start_date:,
|
15
|
+
end_date:,
|
16
|
+
request_type:,
|
17
|
+
issuing_rfc:,
|
18
|
+
recipient_rfcs:,
|
19
|
+
requester_rfc:,
|
20
|
+
certificate:
|
21
|
+
),
|
22
|
+
client: Client.new(private_key:, access_token:)
|
23
|
+
).call
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(download_request_body:, client:)
|
27
|
+
@download_request_body = download_request_body
|
28
|
+
@client = client
|
29
|
+
end
|
30
|
+
|
31
|
+
def call
|
32
|
+
response = client.download_request(download_request_body.generate)
|
33
|
+
|
34
|
+
case response.status
|
35
|
+
when 200..299
|
36
|
+
check_body_status response.xml
|
37
|
+
when 400..599
|
38
|
+
Result.new(success?: false, value: nil, xml: response.xml)
|
39
|
+
else
|
40
|
+
SatMx::Error
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :download_request_body, :client
|
47
|
+
|
48
|
+
def check_body_status(xml)
|
49
|
+
download_result_tag = xml.xpath("//xmlns:SolicitaDescargaResult",
|
50
|
+
xmlns: Body::NAMESPACE)
|
51
|
+
if download_result_tag.attr("CodEstatus").value == "5000"
|
52
|
+
Result.new(success?: true,
|
53
|
+
value: download_result_tag.attr("IdSolicitud").value,
|
54
|
+
xml: xml)
|
55
|
+
else
|
56
|
+
Result.new(
|
57
|
+
success?: false,
|
58
|
+
value: {
|
59
|
+
CodEstatus: download_result_tag.attr("CodEstatus").value,
|
60
|
+
Mensaje: download_result_tag.attr("Mensaje").value
|
61
|
+
},
|
62
|
+
xml:
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
module SatMx
|
4
|
+
class DownloadRequestBody
|
5
|
+
include Body
|
6
|
+
|
7
|
+
REQUEST_TYPES = {
|
8
|
+
cfdi: "CFDI",
|
9
|
+
metadata: "Metadata"
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def initialize(start_date:,
|
13
|
+
end_date:,
|
14
|
+
request_type:,
|
15
|
+
issuing_rfc:,
|
16
|
+
recipient_rfcs:,
|
17
|
+
requester_rfc:,
|
18
|
+
certificate:)
|
19
|
+
@start_date = start_date
|
20
|
+
@end_date = end_date
|
21
|
+
@request_type = request_type
|
22
|
+
@issuing_rfc = issuing_rfc
|
23
|
+
@recipient_rfcs = recipient_rfcs
|
24
|
+
@requester_rfc = requester_rfc
|
25
|
+
@certificate = certificate
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate
|
29
|
+
envelope do |xml|
|
30
|
+
xml[Body::DES].SolicitaDescarga do
|
31
|
+
xml[Body::DES].solicitud(
|
32
|
+
"FechaInicial" => start_date,
|
33
|
+
"FechaFinal" => end_date,
|
34
|
+
"RfcEmisor" => issuing_rfc,
|
35
|
+
"RfcSolicitante" => requester_rfc,
|
36
|
+
"TipoSolicitud" => request_type
|
37
|
+
) do
|
38
|
+
xml[Body::DES].RfcReceptores do
|
39
|
+
@recipient_rfcs.each do |rfc|
|
40
|
+
xml[Body::DES].RfcReceptor(rfc)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
signature(xml)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :issuing_rfc, :requester_rfc, :certificate
|
52
|
+
|
53
|
+
def start_date = @start_date.strftime("%Y-%m-%dT%H:%M:%S")
|
54
|
+
|
55
|
+
def end_date = @end_date.strftime("%Y-%m-%dT%H:%M:%S")
|
56
|
+
|
57
|
+
def request_type = REQUEST_TYPES.fetch(@request_type)
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "openssl"
|
2
|
+
require "xmldsig"
|
3
|
+
|
4
|
+
module SatMx
|
5
|
+
class Signer
|
6
|
+
def self.sign(document:, private_key:)
|
7
|
+
new(document:, private_key:).sign
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(document:, private_key:)
|
11
|
+
@unsigned_document = Xmldsig::SignedDocument.new(document)
|
12
|
+
@private_key = private_key
|
13
|
+
end
|
14
|
+
|
15
|
+
def sign
|
16
|
+
unsigned_document.sign do |data|
|
17
|
+
private_key.sign(OpenSSL::Digest.new("SHA1"), data)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :unsigned_document, :private_key
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module SatMx
|
2
|
+
class VerifyRequest
|
3
|
+
STATUS = {
|
4
|
+
"1" => :accepted,
|
5
|
+
"2" => :in_proccess,
|
6
|
+
"3" => :finished,
|
7
|
+
"4" => :error,
|
8
|
+
"5" => :rejected,
|
9
|
+
"6" => :expired
|
10
|
+
}
|
11
|
+
private_constant :STATUS
|
12
|
+
|
13
|
+
def self.call(request_id:, requester_rfc:, access_token:, private_key:, certificate:)
|
14
|
+
new(
|
15
|
+
body: VerifyRequestBody.new(request_id:, requester_rfc:, certificate:),
|
16
|
+
client: Client.new(private_key:, access_token:)
|
17
|
+
).call
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(body:, client:)
|
21
|
+
@body = body
|
22
|
+
@client = client
|
23
|
+
end
|
24
|
+
|
25
|
+
def call
|
26
|
+
response = client.verify_request(body.generate)
|
27
|
+
case response.status
|
28
|
+
when 200..299
|
29
|
+
check_body_status(response.xml)
|
30
|
+
when 400..599
|
31
|
+
Result.new(success?: false, value: nil, xml: response.xml)
|
32
|
+
else
|
33
|
+
SatMx::Error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :body, :client
|
40
|
+
|
41
|
+
def check_body_status(xml)
|
42
|
+
download_result_tag = xml.xpath("//xmlns:VerificaSolicitudDescargaResult",
|
43
|
+
xmlns: Body::NAMESPACE)
|
44
|
+
if download_result_tag.attr("CodEstatus").value == "5000"
|
45
|
+
Result.new(success?: true,
|
46
|
+
value: value(download_result_tag, xml),
|
47
|
+
xml: xml)
|
48
|
+
else
|
49
|
+
Result.new(
|
50
|
+
success?: false,
|
51
|
+
value: {
|
52
|
+
CodEstatus: download_result_tag.attr("CodEstatus").value,
|
53
|
+
Mensaje: download_result_tag.attr("Mensaje").value
|
54
|
+
},
|
55
|
+
xml:
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def value(tag, xml)
|
61
|
+
{
|
62
|
+
request_status: STATUS.fetch(
|
63
|
+
tag.attribute("EstadoSolicitud").value
|
64
|
+
),
|
65
|
+
package_ids: xml.xpath("//xmlns:IdsPaquetes", xmlns: Body::NAMESPACE).map(&:inner_text)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SatMx
|
2
|
+
class VerifyRequestBody
|
3
|
+
include Body
|
4
|
+
|
5
|
+
def initialize(certificate:, request_id:, requester_rfc:)
|
6
|
+
@certificate = certificate
|
7
|
+
@request_id = request_id
|
8
|
+
@requester_rfc = requester_rfc
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate
|
12
|
+
envelope do |xml|
|
13
|
+
xml[Body::DES].VerificaSolicitudDescarga do
|
14
|
+
xml[Body::DES].solicitud(
|
15
|
+
"IdSolicitud" => request_id,
|
16
|
+
"RfcSolicitante" => requester_rfc
|
17
|
+
) do
|
18
|
+
signature(xml)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :certificate, :request_id, :requester_rfc
|
27
|
+
end
|
28
|
+
end
|
data/lib/sat_mx/version.rb
CHANGED
data/lib/sat_mx.rb
CHANGED
@@ -1,8 +1,63 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require "httpx"
|
3
2
|
require_relative "sat_mx/version"
|
4
3
|
|
5
4
|
module SatMx
|
6
5
|
class Error < StandardError; end
|
7
|
-
autoload(:
|
6
|
+
autoload(:Configuration, "sat_mx/configuration")
|
7
|
+
autoload(:Authentication, "sat_mx/authentication")
|
8
|
+
autoload(:DownloadRequest, "sat_mx/download_request")
|
9
|
+
autoload(:DownloadRequestBody, "sat_mx/download_request_body")
|
10
|
+
autoload(:VerifyRequest, "sat_mx/verify_request")
|
11
|
+
autoload(:VerifyRequestBody, "sat_mx/verify_request_body")
|
12
|
+
autoload(:DownloadPetition, "sat_mx/download_petition")
|
13
|
+
autoload(:DownloadPetitionBody, "sat_mx/download_petition_body")
|
14
|
+
autoload(:Body, "sat_mx/body")
|
15
|
+
autoload(:Result, "sat_mx/result")
|
16
|
+
autoload(:Signer, "sat_mx/signer")
|
17
|
+
autoload(:Client, "sat_mx/client")
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Configures the gem using a block, its not threadsafe, so its recommended call only when you're initializing
|
21
|
+
# your application, e.g. in your initializers directory of your rails app
|
22
|
+
# @example
|
23
|
+
# SatMx.configure do |config|
|
24
|
+
# config[:certificate] = "path/to/certificate.cer"
|
25
|
+
# config[:private_key] = "path/to/private.key"
|
26
|
+
# config[:password] = "key_password"
|
27
|
+
# end
|
28
|
+
def configure
|
29
|
+
config = {}
|
30
|
+
yield(config)
|
31
|
+
@configuration = Configuration.new(**config)
|
32
|
+
end
|
33
|
+
|
34
|
+
def configuration
|
35
|
+
@configuration ||= Configuration.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# Authenticates with the SAT web service using the configured certificate and private key.
|
39
|
+
# This method uses SOAP to communicate with the SAT authentication service and returns
|
40
|
+
# a token that can be used for subsequent requests.
|
41
|
+
#
|
42
|
+
# result = SatMx.authenticate
|
43
|
+
# if result.success?
|
44
|
+
# puts "Authentication token: #{result.value}"
|
45
|
+
# else
|
46
|
+
# puts "Authentication failed"
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# @return [SatMx::Result] A Result object containing:
|
50
|
+
# - success?: [Boolean] whether the authentication was successful
|
51
|
+
# - value: [String, nil] the authentication token if successful, nil otherwise
|
52
|
+
# - xml: [Nokogiri::XML::Document] the raw XML response from the service
|
53
|
+
#
|
54
|
+
# @see SatMx::Authentication
|
55
|
+
# @see SatMx::Result
|
56
|
+
def authenticate
|
57
|
+
Authentication.authenticate(
|
58
|
+
certificate: configuration.certificate,
|
59
|
+
private_key: configuration.private_key
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
8
63
|
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.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oscar Rivas
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: xmldsig
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: base64
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "<"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "<"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: standard
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,7 +123,18 @@ files:
|
|
109
123
|
- README.md
|
110
124
|
- Rakefile
|
111
125
|
- lib/sat_mx.rb
|
112
|
-
- lib/sat_mx/
|
126
|
+
- lib/sat_mx/authentication.rb
|
127
|
+
- lib/sat_mx/body.rb
|
128
|
+
- lib/sat_mx/client.rb
|
129
|
+
- lib/sat_mx/configuration.rb
|
130
|
+
- lib/sat_mx/download_petition.rb
|
131
|
+
- lib/sat_mx/download_petition_body.rb
|
132
|
+
- lib/sat_mx/download_request.rb
|
133
|
+
- lib/sat_mx/download_request_body.rb
|
134
|
+
- lib/sat_mx/result.rb
|
135
|
+
- lib/sat_mx/signer.rb
|
136
|
+
- lib/sat_mx/verify_request.rb
|
137
|
+
- lib/sat_mx/verify_request_body.rb
|
113
138
|
- lib/sat_mx/version.rb
|
114
139
|
- sig/sat_mx.rbs
|
115
140
|
homepage: https://github.com/kadru/sat_mx
|
@@ -133,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
158
|
- !ruby/object:Gem::Version
|
134
159
|
version: '0'
|
135
160
|
requirements: []
|
136
|
-
rubygems_version: 3.5.
|
161
|
+
rubygems_version: 3.5.23
|
137
162
|
signing_key:
|
138
163
|
specification_version: 4
|
139
164
|
summary: a client to connect to SAT web services
|