Saludos al Grupo.
En una ocasión me consultaron si se podía automatizar la descarga del registro de compras y ventas desde la página del Servicio de Impuestos Internos de Chile.
Me costó bastante hacerlo pero al fin lo logré y adjunto código para que lo prueben los colegas de Chile y si les sirve también como guía para los demás países.
El único inconveniente es que se necesita si o si un certificado digital del usuario autorizado para la empresa que se consultará, esta parte de firma no lo pude hacer en harbour y tuve que hacer una rutina en C# para resolverlo, si alguien sabe como hacerlo con Harbour puede brindar la ayuda necesaria para no depender de otros lenguajes, en fin ahí va el codigo.
#ifndef __XHARBOUR__
#xcommand TRY => BEGIN SEQUENCE WITH {|o| break(o)}
#xcommand CATCH [<!oErr!>] => RECOVER [USING <oErr>] <-oErr->
#xcommand FINALLY => ALWAYS
#endif
function ConsultaRCV
local cCompras,oHttp,cUrl,cJson,cTokencResponse,cAut,oRestapi,cHtml,cArchivo,cHash
cToken:=Token()
//Creamos cadena Json para enviar //
cCompras:='{"metaData":{ "namespace":"cl.sii.sdi.lob.diii.consdcv.data.api.interfaces.FacadeService/getDetalleCompraExport","conversationId":"'+cToken+'","transactionId":"0","page":null},"data":{ "rutEmisor":"77254882","dvEmisor":"6","ptributario":"202401","operacion":"COMPRA","estadoContab":"REGISTRO","codTipoDoc":"0"}}'
///////////////////////////////////
BEGIN SEQUENCE WITH {|o| break(o)}
oHttp := Win_OleCreateObject( "MSXML2.ServerXMLHTTP" )
RECOVER USING oErr
alert( oErr:Description, "Error Microsoft XML Core Services (MSXML)" )
oHttp:=""
END SEQUENCE
oHttp:Open( "POST", cUrl, .F.)
oHttp:setRequestHeader("Cookie" , "TOKEN="+cToken) //+ CRLF) // Cookie con Token solicitado en autenticacion
oHttp:setRequestHeader("Content-Type" ," application/json; charset=utf-8" ) //+ CRLF)
oHttp:setRequestHeader("Content-Lenght" , 1000) //+ CRLF)
oHttp:Send(cCompras) // Se envia cadena JSON
cResponse:=oHttp:responseText
hb_memowrit("Respuesta.json",cResponse)
oHttp:=Nil
Return (cResponse)
function Token
local cEnvioDTE,sArchivo,oRestApi,CHtml,cUrl,cHeader,cXml,cSemilla,cDatoSemilla,cArmatoken,cFirma,cToken,cEnviaAlSii,cResultado
******************* Solicitar Token al SII **************
cXml:=ComunicacionSII("Semilla")
hb_memowrit("Semilla.xml",cXml)
cSemilla:=hb_memoread("Semilla.xml")
cDatoSemilla:=ProcesoXml(cSemilla,"SEMILLA")
cArmatoKen:=ArmaToken(cDatoSemilla)
hb_memowrit("SolicitaToken.xml",cArmaToken)
cFirma:=FirmaDTE("SolicitaToken.xml",'""')
hb_Memowrit('Token.xml',cFirma)
cXml:=ComunicacionSII("Token",'Token.xml')
cToken:=ProcesoXml(hb_Memoread("TokenSii.xml"),"TOKEN")
Return(cToken)
************************************************************
Function ProcesoXml( c_XmlRecibido, cadena )
local oXMLDoc := ''
local oXMLNode := ''
local aDatosxml:={}
local cDatosxm1
*=================================================
oXmlDoc := TXmlDocument():new()
oXMlDoc:read( c_XmlRecibido )
oXmlNode := oXmlDoc:findFirst(cadena)
cDatosxm1 := oXmlNode:cData
return(cDatosxm1)
function ArmaToken(DatoSemilla)
local cXmlToken
cXmlToken:=''
cXmlToken +='<?xml version="1.0" encoding="UTF-8"?>'
cXmlToken +='<getToken>'
cXmlToken +='<item>'
cXmlToken +='<Semilla>'+DatoSemilla+'</Semilla>'
cXmlToken +='</item>'
cXmlToken +='</getToken>'
return(cXmlToken)
************************************************
function ComunicacionSII(cDato,cArchivoFirmado)
Local cTx, signed, txt, sig , cComando,cArchivoFirma,oSoapClient,cXml2
cArchivoFirma:=alltrim(Hb_Memoread(cArchivoFirmado))
if cDato="Semilla"
// Obtener una semilla --------------------------------------------------
oSoapClient := CreateObject( "MSSOAP.SoapClient30" ) // Conecta a SOAP 3.0
oSoapClient:msSoapInit( cDominio_WebServices )
try
cXml2 := oSoapClient:getSeed()
* alert( cXml )
* hb_MemoWrit( 'Prueba_res.xml', cXml )
catch
cValue := oSOAPClient:faultString + Hb_OsNewLine() + oSOAPClient:detail
alert( "error:"+cValue )
end
oSoapClient := NIL
elseif cDato="Token"
oSoapClient := CreateObject( "MSSOAP.SoapClient30" ) // Conecta a SOAP 3.0
oSoapClient:msSoapInit( cDominio_WebServicesToken )
try
cXml2:= oSoapClient:getToken(cArchivoFirma)
hb_MemoWrit( 'Tokensii.xml', cXml2 )
catch
cValue := oSOAPClient:faultString + Hb_OsNewLine() + oSOAPClient:detail
alert( "Estamos en Token error:"+cValue )
end
oSoapClient := NIL
endif
return (cXml2)
************************************************ Esta Rutina esta creada en C# ******************
******** Aqui necesitamos la ayuda de alguien que sepa como firmar con Harbour *****************
*function FirmaDTE(cRuta,sUri,cRuta2,cRuta3,sUri2)
function FirmaDTE(cRuta,sUri)
Local cTx, signed, txt, sig , privatekey,cComando,cArchivoFirmado
cComando := 'c:\boletael\javsistemas\firma.exe ' + cRuta +' '+sUri
cOutErr1 := ''
cOutErr2 := ''
nErrorLevel := HB_PROCESSRUN(cComando,,@cOutErr1,@cOutErr2,.T.) // EL .T. HACE QUE NO DISPLAYE EN CONSOLA
DO CASE
CASE nErrorLevel==0
// TODO PERFECTO
CASE nErrorLevel==-1
alert('ERROR al ejecutar FirmaDTE'+CRLF+STR(nErrorLevel,5)+' '+ERRORTXT(nErrorLevel))
RETURN(1)
CASE nErrorLevel==2
alert('ERROR al ejecutar FirmaDTE'+CRLF+STR(nErrorLevel,5)+' '+ERRORTXT(nErrorLevel))
RETURN(1)
OTHERWISE
alert('ERROR al ejecutar FirmaDTE'+CRLF+STR(nErrorLevel,5)+' '+cOutErr2)
RETURN(1)
ENDCASE
return trim(cOutErr1)
function ERRORTXT(nError)
return(str(nError))
La rutina en C# para firmar es la siguiente:
using System;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
namespace firmax509
{
class MainClass
{
public static void Main (string[] args)
{
string cArchivo = args[0];
string uri = args[1];
Firma firma = new Firma(@"C:\boletael\javsistemas\certificado.pfx", "08122000");
// xml no firmado
string xDocument =File.ReadAllText(@cArchivo, Encoding.Default);
System.Text.StringBuilder sbEx = new System.Text.StringBuilder();
sbEx.Append(xDocument); //+ dia.ToShortDateString() + ".");
string unsignedXml = sbEx.ToString();
const string fic = @"c:\boletael\ArchivoFirmado.xml";
string signedXml = firma.Firmar(unsignedXml, referenceUri: uri, addTransform: true);
System.IO.StreamWriter sw = new System.IO.StreamWriter(fic,false, Encoding.Default);
sw.WriteLine(signedXml);
sw.Close();
Console.WriteLine (signedXml);
}
}
}
public class Firma
{
private X509Certificate2 certificado { get; set; }
public Firma(string certificatePath, string password)
{
certificado = new X509Certificate2(certificatePath, password);
}
///
/// Se le pasa un xml en string y lo devuelve firmado
///
/// xml no firmado
/// si se quiere firmar una parte del xml se debe poner el #id, si no: ""
/// para firmar semilla se requiere, para firmar envioDTE y DTE no se requiere
public string Firmar(string xml, string referenceUri, bool addTransform)
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.PreserveWhitespace = true;
xmlDocument.LoadXml(xml);
SignedXml signedXml = new SignedXml(xmlDocument);
signedXml.SigningKey = certificado.PrivateKey;
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new RSAKeyValue((RSA)certificado.PrivateKey));
keyInfo.AddClause(new KeyInfoX509Data(certificado));
Reference reference = new Reference();
reference.Uri = referenceUri;
if (addTransform)
{
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
}
Signature signature = signedXml.Signature;
signature.SignedInfo.AddReference(reference);
signature.KeyInfo = keyInfo;
// Generar firma
signedXml.ComputeSignature();
// Insertar la firma en xmlDocument
xmlDocument.DocumentElement.AppendChild(xmlDocument.ImportNode(signedXml.GetXml(), true));
return xmlDocument.InnerXml;
}
}
Saludos.