NOTA DEL MODERADOR:=======================================
Como hemos hecho con otros temas similares, voy a "fijar" este hilo para dedcarlo a la Facturación electrónica (Facturae).
Además adjunto links de otros hilos (que no quedan fijados) sobre el mismo tema:
=======================================================
[Movido desde esta otra conversación]
Bueno, voy a aportar lo que tengo por ahora, que es la parte que crea el .XML (o .XSIG para la Admón. Pública) y lo firma con certificado digital o DNIe. Pero esto no puede quedarse ahí, hay que seguir con la tercera parte (que es la finalidad de este post), que es enviar la factura al webservice de face, a ver si entre todos lo conseguimos.
Con este código se puede crear un .XML (manualmente) y firmarlo para que sea válido y aceptado en la web de facturae. La web de facturae tiene un "validador online" aquí...
http://sedeaplicaciones2.minetur.gob.es/FacturaE/
...que nos servirá para comprobar si las facturas creadas son correctas, están bien firmadas, etc.
Componentes necesarios de SecureBlackBox: TEIWinCertStorage y TEIX509Certificate.
Otros componentes: TXMLDocument
(los componentes SecureBlackBox vienen con un montón de proyectos de ejemplo, entre ellos uno para firmar archivos PDF o XML).
Primera parte: crear el .XML
Yo he decidido crearlo "a mano". Un XML no es más que un archivo de texto con una estructura concreta, así que utilizo un Memo y le voy añadiendo las líneas con cuidado...
No voy a ponerlo todo porque es muy largo. Quizás más tarde subiré el código completo. El XML requiere un montón de valores variables que debemos rellenar (datos del emisor, receptor, factura, artículos, totales, impuestos...). Esta parte es algo tediosa, pero muy sencilla.
Antes de nada, cargamos los certificados digitales instalados en el sistema, por ejemplo en el OnCreate del Form1, en un ComboBox.
Esto hará que en el combobox veamos los certificados digitales instalados en Windows, ya sean certificados de empresa, el del DNIe, etc.
Nos harán falta más tarde para la firma del XML.
Código Delphi
[-]
procedure TForm1.FormCreate(Sender: TObject);
var i:integer;
begin
for i := 0 to WinCertStorage.Count - 1 do
begin
Cert := WinCertStorage.Certificates[i];
ComboCertificate.Items.Add('Título: ' + Cert.SubjectName.CommonName + ', Emisor: ' + Cert.IssuerName.CommonName);
end;
end;
Empezamos a crear el XML a mano...
Código Delphi
[-]
memo1.Lines.Clear;
memo1.lines.append('');
memo1.lines.append(' xmlns:fe="http://www.facturae.es/Facturae/2009/v3.2/Facturae" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"');
memo1.lines.append('');
memo1.lines.append('3.2');
Cuando tengamos todo el texto del XML, guardamos el memo en un archivo con extensión xml.
Código Delphi
[-]
memo1.Lines.SaveToFile('c:\pruebas\factura.xml');
Ahora abrimos el archivo con el componente XMLDocument para tenerlo ahí cargado, y lo volvemos a guardar. ¿por qué? Porque así se guarda bien estructurado, identado, etc. y es más fácil leerlo si nos hiciera falta.
Código Delphi
[-]
XMLDocument1.LoadFromFile(archivo);
xmldocument1.Active:=true;
xmldocument1.SaveToFile(archivo);
Y ahora a firmar...
Segunda parte: Firmar el XML
Este código no es mío. Tiene partes extraídas de clubdelphi, de los ejemplos de secureblackbox y de otros ejemplos de firma digital.
En el código le decimos qué certificado utilizar para la firma, de entre los cargados en el combobox anterior.
Probablemente el código se pueda mejorar. He dejado los comentarios del creador original porque explican bastante bien lo que se hace en él.
Código Delphi
[-]
function Firma:boolean;
var XML_Doc:ElXMLDOMDocument;
XML_Refs:TElXMLReferenceList;
XML_RefDocu,XML_RefCert:TElXMLReference;
XML_Signer:TElXMLSigner;
XML_XAdES:TElXAdESSigner;
XML_KeyData:TElXMLKeyInfoX509Data;
XML_Nodo:ElXMLDOMNode;
MS:TStream;
XML_Buf:ByteArray;
F: {$ifndef DELPHI_NET}TFileStream{$else}FileStream{$endif};
begin
result:=false;
with form1 do
begin
XML_Doc:= ElXMLDOMDocument.create; XML_Refs:= TElXMLReferenceList.create; XML_RefDocu:= TElXMLReference.create; XML_RefCert:= TElXMLReference.create; XML_Signer:= TElXMLSigner.Create(nil); XML_XAdES:= TElXAdESSigner.Create(nil); XML_KeyData:= TElXMLKeyInfoX509Data.create(false);
try
{$ifndef DELPHI_NET}
F := TFileStream.Create(archivo, fmOpenRead or fmShareDenyWrite);
{$else}
F := FileStream.Create(archivo, FileMode.Open, FileAccess.Read);
{$endif}
try
XML_Doc.LoadFromStream(F, '', true);
except
on E : Exception do
begin
MessageDlg('Error: ' + E.Message, mtError, [mbOk], 0);
end;
end;
FreeAndNil(F);
if not XML_Doc.Loaded then
raise Exception.create('Firma XML: No se pudo cargar el documento XML.');
XML_Signer.References:= XML_Refs;
XML_Signer.SignatureMethodType:= xmtSig;
XML_Signer.SignatureType:= xstEnveloped;
XML_Signer.CanonicalizationMethod:= xcmCanon;
XML_Signer.SignatureMethod:= xsmRSA_SHA1;
XML_Signer.IncludeKey:= true;
XML_XAdES:= TElXAdESSigner.Create(nil);
XML_Signer.XAdESProcessor:= XML_XAdES;
XML_Signer.XAdESProcessor.XAdESVersion:= XAdES_v1_3_2;
XML_Signer.XAdESProcessor.Included := [xipSignerRole];
XML_Signer.XAdESProcessor.SignerRole.ClaimedRoles.AddText(
XML_Signer.XAdESProcessor.XAdESVersion, XML_Doc, 'supplier');
XML_Signer.XAdESProcessor.PolicyId.SigPolicyId.Identifier:= 'http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf';
XML_Signer.XAdESProcessor.PolicyId.SigPolicyId.Description:= 'Política de firma electrónica para facturación electrónica con formato Facturae';
XML_Signer.XAdESProcessor.PolicyId.SigPolicyHash.DigestMethod:= 'http://www.w3.org/2000/09/xmldsig#sha1';
SetLength(XML_Buf,0); if not HexStr2ByteArray('3A18B197ABA90FA6AFF0DEE912F0C006110BEA13', XML_Buf) then
raise Exception.Create('Firma XML: Error convirtiendo digest de política de firmado a Base64.');
XML_Signer.XAdESProcessor.PolicyId.SigPolicyHash.DigestValue:= XML_Buf;
XML_Signer.XAdESProcessor.SigningTime := LocalTimeToUTCTime(Now);
XML_Signer.XAdESProcessor.Generate;
XML_Signer.UpdateReferencesDigest;
XML_KeyData.IncludeKeyValue:= true;
Cert := WinCertStorage.Certificates[comboCertificate.ItemIndex];
if Cert.PrivateKeyExists then
XML_KeyData.certificate:= Cert;
if not Assigned(XML_KeyData.certificate) then
raise Exception.create('FirmaXML: No se cargó un certificado válido para firmar, no contiene clave privada.');
XML_Signer.KeyData:= XML_KeyData;
XML_RefDocu.DigestMethod:= xdmSHA1; XML_RefDocu.URINode:= XML_Doc.DocumentElement; XML_RefDocu.URI:= ''; XML_RefDocu.TransformChain.Add(TElXMLEnvelopedSignatureTransform.Create);
XML_Refs.Add(XML_RefDocu); XML_Signer.UpdateReferencesDigest;
XML_RefCert.URI:= '#Certificate1';
XML_RefCert.DigestMethod:= xdmSHA1;
XML_Refs.Add(XML_RefCert); XML_Signer.Sign;
XML_Signer.Signature.KeyInfo.ID:= 'Certificate1';
XML_Nodo:= ElXMLDOMNode(XML_Doc.DocumentElement);
XML_Signer.Save(XML_Nodo);
{$ifndef DELPHI_NET}
F := TFileStream.Create(archivo, fmCreate or fmOpenWrite);
{$else}
F := System.IO.FileStream.Create(archivo, FileMode.Create, FileAccess.ReadWrite);
{$endif}
try
XML_Doc.SaveToStream(F, xcmNone, '');
except
on E : Exception do
begin
MessageDlg('Error: ' + E.Message, mtError, [mbOk], 0);
end;
end;
FreeAndNil(F);
result:= true;
finally
XML_Doc.free;
XML_Refs.free;
XML_Signer.free;
XML_XAdES.free;
XML_KeyData.Free;
end;
end;
end;
Y ya
tenemos firmado nuestro XML. Y si el certificado es válido, la fecha/hora es válida, etc. entonces la factura electrónica es perfectamente legal. Podremos comprobarlo en la web de validación de facturae, que además nos dirá donde está el error, si lo hubiera.
IMPORTANTE:
Cuando la factura es para una Administración Pública, el .XML debe tener unas secciones obligatorias (Oficina contable, órgano gestor, etc.) y el archivo, en vez de tener extensión XML, lo guardaremos al final como .xsig. Y eso es todo.
Y ahora, lo difícil...
Enviar la factura al webservice...
Importamos el WSDL del webservice del face desde Delphi. Hay dos versiones, la de producción y la de desarrollo para pruebas. El de pruebas es este:
https://se-face-webservice.redsara.es/sspp?wsdl
En el IDE, vamos a Component -> Import WSDL e indicamos la url anterior. Siguiente -> Siguiente y Finish.
Esto creará una unit llamada 'sspp.pas', que guardaremos en la misma carpeta de nuestro proyecto, y añadiremos al uses de nuestro form.
Y hasta aquí puedo leer. Y no porque no quiera seguir, sino porque no tengo ni idea.
Este es el código, supuestamente, para enviar la factura al webservice, pero antes hay que hacerle varias cosas que desconozco. Creo que incluso puede hacerse sin los SecureBlackBox, pero de verdad que estoy atascado.
Código Delphi
[-]
var facturasspp : SSPPFactura;
fichero_fac : SSPPFicheroFactura;
answ : SSPPResultadoEnviarFactura;
PO:SSPPWebServiceProxyPort;
begin
PO := GetSSPPWebServiceProxyPort(FALSE, '', nil);
facturasspp := ssppfactura.Create;
facturasspp.correo := 'correo@micorreo.com';
fichero_fac := ssppficherofactura.Create;
fichero_fac.nombre := 'c:\pruebas\factura.xml';
fichero_fac.factura := '12345';
fichero_fac.mime := 'application/xml';
facturasspp.fichero_factura := fichero_fac;
try
answ:=PO.enviarFactura(facturasspp);
except
on e:exception do showmessage(e.Message);
end;
end;