Libreria Para facturacion electronica en Delphi
#21
Escrito 13 septiembre 2013 - 07:49
Es indiferente, realmente..., pero por convención de la ONU, el nombre del país es México, con 'x'...
[/quote]
En serio es indiferente?? Jelipe ó Felipe?
- Por cierto, excelente herramienta y como agua en el desierto. Estoy comenzando a programar CFDI.
#22
Escrito 13 septiembre 2013 - 08:30
sobre lo del TByteArray = array[0..32767] of Byte; (es la definicion que viene en Sysutils.pas),
como no tengo los fuentes de esos componentes, buscaba una alternativa
El lunes le diré a mi jefe que los compre, que si no, no hay factura electronica
Un saludo y buen finde.
#23
Escrito 16 septiembre 2013 - 03:47
Archivos adjuntos
#24
Escrito 16 septiembre 2013 - 05:55
Un millón de Gracias, Sergio !!
De nada chaval, recuerda lo de las birras si pasas por Murcia algún día!
#25
Escrito 19 febrero 2014 - 05:22
Muchas gracias por publicar tu código. Me ha sido de gran ayuda!!
Un saludo.
#26
Escrito 19 febrero 2014 - 02:19
Hola Sergio,
Muchas gracias por publicar tu código. Me ha sido de gran ayuda!!
Un saludo.
De nada, me alegrar evitar el calvario que me tocó para entender el lio del FacturaE, que por cierto para los que no seaís españolitos: no se ha llegado a utilizar realmente, tras varios años siendo "obligatorio" para facturar a la administración aún no me he cruzado ni con una verdadera factura en ese formato ni con ningún cliente preguntándome sobre el tema (y muchos trabajan para la administración), por lo que es un fracaso total.
En fín, como diría el genial humorista Gila: "Me habeis matado un hijo, pero y lo que nos hemos reido?" (aconsejo mirarlo por YouTube, habla de la guerra normalmente y es muy personal su humos, por cierto, Gila fué fusilado en la guerra civil española, por los años 30, pero no murió, así que él puede bromear con la guerra todo lo que quiera, se lo ganó).
#27
Escrito 19 febrero 2014 - 04:32
Hola Sergio,
Muchas gracias por publicar tu código. Me ha sido de gran ayuda!!
Un saludo.
De nada, me alegrar evitar el calvario que me tocó para entender el lio del FacturaE, que por cierto para los que no seaís españolitos: no se ha llegado a utilizar realmente, tras varios años siendo "obligatorio" para facturar a la administración aún no me he cruzado ni con una verdadera factura en ese formato ni con ningún cliente preguntándome sobre el tema (y muchos trabajan para la administración), por lo que es un fracaso total.
En fín, como diría el genial humorista Gila: "Me habeis matado un hijo, pero y lo que nos hemos reido?" (aconsejo mirarlo por YouTube, habla de la guerra normalmente y es muy personal su humos, por cierto, Gila fué fusilado en la guerra civil española, por los años 30, pero no murió, así que él puede bromear con la guerra todo lo que quiera, se lo ganó).
Contrariamente acá en México a entrado con todo éste año, sólo faltamos los que facturamos por honorarios y que entrará en vigor en Abril de éste año. Lo bueno es que hay un mercado creciente de sistemas de facturación
Saludos
#28
Escrito 20 febrero 2014 - 04:55
Contrariamente acá en México a entrado con todo éste año, sólo faltamos los que facturamos por honorarios y que entrará en vigor en Abril de éste año. Lo bueno es que hay un mercado creciente de sistemas de facturación
Saludos
El otro día leí que en suramerica ahora se nos llama "euracas" a nosotros lo tenemos bien merecido por este tipo de cosas!
#29
Escrito 04 marzo 2015 - 08:17
Saludos a todos.
Gracias por anticipado por vuestras explicaciones.
#30
Escrito 21 mayo 2021 - 05:02
Bueno, tienes tres tareas, así que te pongo tres trozos de código: Crear en XML la cabecera (datos del emisor y receptor, total de las facturas incluidas, etc), luego añadir cada una de las facturas (una o varias) con todos los datos obligatorios correctos y esas cosas (dentro van las líneas de factura), y finalmente tienes el tema de firmarlo como te piden.
Empiezo con código para el XML de cabecera, usamos un objetito simple que nos abre y cierra ramas en el XML, hay muchas clases parecidas y todas son sencillas de usar, pero nos creamos la nuestra porque... pues porque sí.
Veras algunas variables que no se definen, son parte del objeto que usamos, pero los nombres están claros: "total" es el toal de las facturas, etc.
delphi
salida:= TXMLGenerador.create; QP:= TQueryPack.Create(Self); // Leo los datos de la empresa emisora: QP.EjecutaSQL('select direccion, CodigoPostal, Poblacion, cif, '+ '(select p.nombre from Provincia p '+ ' where p.provincia=substring(codigopostal from 1 for 2)) '+ ' from empresa where empresa=1'); salida.TextoLibre('<?xml version="1.0" encoding="ISO-8859-1"?>'); salida.Abre('fe:Facturae','xmlns:fe="http://www.facturae.es/Facturae/2009/v3.2/Facturae" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"'); salida.Abre('FileHeader'); salida.Valor('SchemaVersion','3.2'); salida.Valor('Modality','I'); salida.Valor('InvoiceIssuerType','EM'); salida.Abre('Batch'); salida.Valor('BatchIdentifier', BatchID); salida.Valor('InvoicesCount',IntToStr(count)); salida.Abre('TotalInvoicesAmount'); salida.Valor('TotalAmount',FloatToSQL(Total)); salida.Cierra; salida.Abre('TotalOutstandingAmount'); salida.Valor('TotalAmount',FloatToSQL(Total)); salida.Cierra; salida.Abre('TotalExecutableAmount'); salida.Valor('TotalAmount',FloatToSQL(Total)); salida.Cierra; salida.Valor('InvoiceCurrencyCode','EUR'); salida.Cierra; salida.Cierra; // Partes salida.Abre('Parties'); // Parte vendedora salida.Abre('SellerParty'); salida.Abre('TaxIdentification'); salida.Valor('PersonTypeCode','J'); salida.Valor('ResidenceTypeCode','R'); salida.Valor('TaxIdentificationNumber',NifEmpresa); if not DNIValido(NifEmpresa) then result:= result + iif(result='','',', ')+'NIF "'+Trim(NifEmpresa)+'" inválido en la empresa'; salida.Cierra; salida.Abre('LegalEntity'); salida.Valor('CorporateName',Base.Empresa); salida.Abre('AddressInSpain'); salida.Valor('Address',QP[0].asString); salida.Valor('PostCode',QP[1].asString); if Length(Trim(QP[1].asString))<>5 then result:= result + iif(result='','',', ')+'Código postal de la empresa "'+Trim(QP[1].asString)+'" no es válido'; salida.Valor('Town',QP[2].asString); salida.Valor('Province',QP[4].asString); salida.Valor('CountryCode','ESP'); salida.Cierra; salida.Cierra; salida.Cierra; // Parte compradora QP.EjecutaSQL('select Nif, Nombre, direccion, cpostal, poblacion, '+ '(select p.nombre from Provincia p '+ ' where p.provincia=substring(cpostal from 1 for 2)), cliente '+ 'from cliente where Cliente='+IntToStr(Factura(0).Cliente)); salida.Abre('BuyerParty'); salida.Abre('TaxIdentification'); salida.Valor('PersonTypeCode','J'); salida.Valor('ResidenceTypeCode','R'); salida.Valor('TaxIdentificationNumber',QP[0].asString); if not DNIValido(Trim(QP[0].asString)) then result:= result + iif(result='','',', ')+'NIF "'+Trim(QP[0].asString)+'" invalido en el cliente nº'+QP[6].asString+' "'+Trim(QP[0].asString)+'"'; salida.Cierra; salida.Abre('LegalEntity'); salida.Valor('CorporateName',QP[1].asString); salida.Abre('AddressInSpain'); salida.Valor('Address',QP[2].asString); salida.Valor('PostCode',QP[3].asString); if Length(Trim(QP[3].asString))<>5 then result:= result + iif(result='','',', ')+'Código postal del cliente nº'+QP[6].asString+' "'+Trim(QP[3].asString)+'" no es válido'; salida.Valor('Town',QP[4].asString); salida.Valor('Province',QP[5].asString); salida.Valor('CountryCode','ESP'); salida.Cierra; salida.Cierra; salida.Cierra; // Se acaban las partes salida.Cierra; // Empezamos con las facturas salida.Abre('Invoices'); for i:= 0 to count-1 do Factura(i).SaveAsXML(salida); //Ver en el siguiente trozo de codigo // Se acabarón las facturas Salida.Cierra; //Se acabo el facturae Salida.Cierra; //A grabarse! salida.SaveToStream(Stream); if Base.FirmaDigital.Activa then Base.FirmaDigital.FirmaStream(TStream(stream), '', tfXML); //Ver parte 3 de codigo result:= iif(result='','',result+'.'); salida.Free; QP.Free;
Por cierto, el formato XML para floats es el americano.
Bueno, ahora como añadir cada factura:delphi
procedure TXMLFacturaE.SaveAsXML(face: TXMLGenerador); var tipoiva, cuota: double; obra, refcli, fecvto: string; QP: TQueryPack; function Fecha(fecha: TDateTime): string; var d,m,a: word; begin DecodeDate(fecha,a,m,d); result:= IntToStr(a)+'-'+IntCero(m,2)+'-'+IntCero(d,2); end; function Descripcion: string; begin if QP[0].asInteger = 1 then result:= QP[2].asString else result:= QP[1].asString+' '+QP[2].asString; end; begin inherited; QP:= TQueryPack.Create(Self); QP.EjecutaSQL('select f.FacturaTxt, f.factura, f.ejercicio, f.serie'+ ',f.fecha,i.base, i.total, f.tipoiva, i.IVA '+ ',f.obra, o.referenciacliente, f.fechavence '+ ' from factura f '+ ' left join obra o on o.obra=f.obra '+ ' left join FacturaImportes('+IDFacturaStr+') i on 1=1 '+ ' where f.idfactura='+IDFacturaStr); facE.Abre('Invoice'); facE.Abre('InvoiceHeader'); facE.Valor('InvoiceNumber',QP[1].AsString); facE.Valor('InvoiceSeriesCode',SerieEjercicio); facE.Valor('InvoiceDocumentType','FC'); facE.Valor('InvoiceClass','OO'); facE.Cierra; facE.Abre('InvoiceIssueData'); facE.Valor('IssueDate',fecha(QP[4].asDateTime)); facE.Valor('InvoiceCurrencyCode','EUR'); facE.Valor('TaxCurrencyCode','EUR'); facE.Valor('LanguageName','es'); facE.Cierra; // Impuestos facE.Abre('TaxesOutputs'); facE.Abre('Tax'); facE.Valor('TaxTypeCode','01'); facE.Valor('TaxRate',format('%1.2f',[QP[7].asDouble])); facE.Abre('TaxableBase'); facE.Valor('TotalAmount',format('%1.2f',[QP[5].asDouble])); facE.Cierra; facE.Abre('TaxAmount'); facE.Valor('TotalAmount',format('%1.2f',[QP[8].asDouble])); facE.Cierra; facE.Cierra; facE.Cierra; // Totales facE.Abre('InvoiceTotals'); facE.valor('TotalGrossAmount',format('%1.2f',[QP[5].asDouble])); facE.valor('TotalGrossAmountBeforeTaxes',format('%1.2f',[QP[5].asDouble])); facE.valor('TotalTaxOutputs',format('%1.2f',[QP[8].asDouble])); // es obligatorio en f 3.2 facE.Valor('TotalTaxesWithheld','0.00'); facE.valor('InvoiceTotal',format('%1.2f',[QP[6].asDouble])); facE.valor('TotalOutstandingAmount',format('%1.2f',[QP[6].asDouble])); // es obligatorio en f 3.2 facE.abre('AmountsWithheld'); facE.Valor('WithholdingReason','Retencion'); facE.Valor('WithholdingRate','0.0000'); facE.Valor('WithholdingAmount','0.00'); facE.Cierra; facE.valor('TotalExecutableAmount',format('%1.2f',[QP[6].asDouble])); facE.Cierra; tipoiva:= QP[7].asDouble; obra:= QP[9].asString; refcli:= QP[10].asString; fecvto:= fecha(QP[11].AsDate); // Lineas de factura facE.Abre('Items'); QP.EjecutaSQL('select SinDescripcion,Descripcion,Notas, '+ ' unidades, precio, cast(unidades*precio as numeric(15,2)),l.ensayo '+ ' from FacturaLineas('+IDFacturaStr+') l'); while not QP.Eof do begin cuota:= QP[5].AsDouble - Redondeo(QP[5].AsDouble/(1+(tipoiva/100)),2); facE.Abre('InvoiceLine'); facE.valor('IssuerContractReference',Obra); if RefCli <> '' then facE.valor('ReceiverContractReference',RefCli,20); facE.valor('ItemDescription', Descripcion); facE.valor('Quantity',format('%1.2f',[QP[3].AsDouble])); facE.valor('UnitOfMeasure','01'); facE.valor('UnitPriceWithoutTax',format('%1.6f',[QP[4].AsDouble])); facE.valor('TotalCost',format('%1.6f',[QP[5].AsDouble])); facE.valor('GrossAmount',format('%1.6f',[QP[5].AsDouble])); facE.Abre('TaxesOutputs'); facE.Abre('Tax'); facE.valor('TaxTypeCode','01'); facE.valor('TaxRate',format('%1.2f',[tipoiva])); facE.Abre('TaxableBase'); facE.valor('TotalAmount',format('%1.2f',[QP[5].AsDouble-cuota])); facE.Cierra; facE.Abre('TaxAmount'); facE.valor('TotalAmount',format('%1.2f',[cuota])); facE.Cierra; facE.Cierra; facE.Cierra; facE.valor('ArticleCode',trim(QP[6].AsString)); facE.Cierra; QP.Next; end; facE.Cierra; // Condiciones de pago No es obligatorio facE.Abre('PaymentDetails'); facE.Abre('Installment'); facE.valor('InstallmentDueDate', fecvto); facE.valor('InstallmentAmount',format('%1.2f',[FTotal])); facE.valor('PaymentMeans','01'); facE.Cierra; facE.Cierra; facE.Cierra; QP.Free; end;
Y ahora la firma, yo utilizo la libreria SecureBlackBox, hay otras, pero la verdad, yo no la cambiaría, es barata, fiable, flexible, potente.. y las preguntas te las responden en minutos!
Por cierto, en su día publiqué en sus foros este mismo código, y en las versiones actuales de la librería tienes ya la opción de firmar en formato FacturaE basado en ese post, así que seguramente te puedas ahorrar todo este código, aunque yo lo prefiero así, sé lo que hace y porqué lo hace.
Veras que el código tiene muchos comentarios, es porque la información oficial está llena de errores y huecos, y solo mirando los ejemplos por dentro y probando con los validadores -que tampoco erán muy fiables cuando los probé- consegui que todo funcionase.delphi
case TipoFirma of tfXML: //Fichero XML para FacturaE (con politica de firmado V3.1) begin // ************************ // ** Firmar fichero XML ** // ************************ // //Usamos FacturaE politica de firma V3.1: //http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf // //Creo objetos involucrados en la firma... XML_Doc:= ElXMLDOMDocument.create; //Documento XML a ser firmado XML_Refs:= TElXMLReferenceList.create; //Lista de nodos a ser firmados XML_RefDocu:= TElXMLReference.create; //Nodo que representa al XML completo XML_RefCert:= TElXMLReference.create; //Nodo del certificado utilizado XML_Signer:= TElXMLSigner.Create(nil); //Objeto firmador en XML... XML_XAdES:= TElXAdESSigner.Create(nil); //Subjeto firmador con info adicional XAdES XML_KeyData:= TElXMLKeyInfoX509Data.create(false); //Certificado a utilizar... try //Leo el fichero XML //================== // Por defecto en UTF-8 y normalizando los finales de linea (si llegan // CR+LF entonces la firma no se validará luego pues los digest no // deberían incluir estos CR+LF). XML_Doc.LoadFromStream(MS, '', true); if not XML_Doc.Loaded then raise Exception.create('Firma XML: No se pudo cargar el documento XML.'); //Configuro el objeto firmador de XML //=================================== // Se han de firmar la lista de nodos referenciados en XML_Refs XML_Signer.References:= XML_Refs; // Quiero firma electronica, no claves secretas (MAC) XML_Signer.SignatureMethodType:= xmtSig; // Se firma en formato "enveloped" XML_Signer.SignatureType:= xstEnveloped; // Se canoniza -ver ejemplos de la web facturae- el XML XML_Signer.CanonicalizationMethod:= xcmCanon; // La firma con el metodo mas estandar posible XML_Signer.SignatureMethod:= xsmRSA_SHA1; // La parte publica del certificado se debe incluir XML_Signer.IncludeKey:= true; // Necesito incrustar info adicional "XAdES" (XML Advanced Electronic Signatures )... XML_XAdES:= TElXAdESSigner.Create(nil); XML_Signer.XAdESProcessor:= XML_XAdES; //Configuro la parte XAdES del objeto firmador //============================================ // // Se pide usar XAdES V1.2.2 o superior pero ha de ser "compatible" (?) // Algunos ejemplos usando V1.3.2 validan, asi que lo uso. XML_Signer.XAdESProcessor.XAdESVersion:= XAdES_v1_3_2; //SignerRole (en calidad de qué firmamos): supplier, customer o third_party // Firmamos facturas emitidas por nosotros (supplier) en este caso. // Si recibimos una factura firmada por el supplier, la podemos volver // a firmar como que la damos por recibida usando "customer". //NOTA: El validador de [url=http://www.facturae.es]www.facturae.es[/url] no comprueba este dato (2-2010) XML_Signer.XAdESProcessor.Included := [xipSignerRole]; XML_Signer.XAdESProcessor.SignerRole.ClaimedRoles.AddText( XML_Signer.XAdESProcessor.XAdESVersion, XML_Doc, 'supplier'); // La politica de firmado es la definida en el PDF V3.1 sobre firmado Facturae XML_Signer.XAdESProcessor.PolicyId.SigPolicyId.Identifier:= '[url=http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf';]http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf';[/url] //NOTA: Si vas a la web, este PDF esta en otro link diferente (WTF): //'[url=http://www.facturae.es/es-ES/Documentacion/Politicas/Politicas/Versi%C3%B3n%203_1/Politica_Firma_formato_facturae_v3_1.pdf';]http://www.facturae.es/es-ES/Documentacion/Politicas/Politicas/Versi%C3%B3n%203_1/Politica_Firma_formato_facturae_v3_1.pdf';[/url] XML_Signer.XAdESProcessor.PolicyId.SigPolicyId.Description:= 'Política de firma electrónica para facturación electrónica con formato Facturae'; //Ahora el hash del propio PDF en SHA1 (en la web esta debajo del PDF)... XML_Signer.XAdESProcessor.PolicyId.SigPolicyHash.DigestMethod:= '[url=http://www.w3.org/2000/09/xmldsig#sha1';]http://www.w3.org/2000/09/xmldsig#sha1';[/url] //El digest en SHA-1 de la web (en formato hex), esta MAL, no corresponde //con ese PDF, y ademas, en su ejemplo de la V3.0 el digest es correcto //y NO se corresponde con el digest que dan! // '613c46e7bac7df5b266e6be0349b5fe8bb4944e2' ESTA MAL EN LA WEB //Uso el digest SHA1 correcto generado a partir del PDF: SetLength(XML_Buf,0); //Evito un warning tonto if not HexStr2ByteArray('3A18B197ABA90FA6AFF0DEE912F0C006110BEA13', XML_Buf) then raise Exception.Create('Firma XML: Error convirtiendo digest de política de firmado a Base64.'); {NOTA: Sustituido por HexStr2ByteArray() XML_Digest:= LowerCase('3A18B197ABA90FA6AFF0DEE912F0C006110BEA13'); SetLength(XML_Buf, Length(XML_Digest) div 2); //En delphi 7 HexToBin va bien, en Delphis mas moderno, no vale porque //WideChar y pChar se tratan de forma diferente, asi que se hace a mano. //HexToBin(PChar(XML_Digest), PChar(XML_Buf), Length(XML_Digest) div 2); for j:= 0 to Length(XML_Buf)-1 do begin k1:= SBMath.HexToDecDigit(XML_Digest[j*2 + 1]); k2:= SBMath.HexToDecDigit(XML_Digest[j*2 + 2]); if (k1<0) or (k2<0) then raise Exception.Create('Firma XML: Error convirtiendo digest de política de firmado a Base64.'); XML_Buf[j]:= k1 shl 4 + k2; end;} XML_Signer.XAdESProcessor.PolicyId.SigPolicyHash.DigestValue:= XML_Buf; //Anoto la fecha y hora del firmado (del PC, no es legalmente valida) XML_Signer.XAdESProcessor.SigningTime := UtcNow; //Pre-Genero los nodos de firmado necesarios //========================================== // Esta es la parte mas "confusa": He de firmar partes del XML que no // existen hasta que este firmado ¿Como? El objeto Signer, junto con el // prepocesador XAdES, crean esos nuevos nodos para que podamos // referenciarlos -decirle al firmador que son unas referecnias a ser // firmadas- y al final, al firmar, unira el XML original con estos // nuevos nodos -formato de firma "enveloped"- y se graba el XML final. // // Primero genero la zona de informacion XAdES: XML_Signer.XAdESProcessor.Generate; // Ahora, al objeto firmador "generico" le pido lo mismo, con lo que // tambien se crea el nodo "KeyInfo" con el certificado a utilizar. XML_Signer.UpdateReferencesDigest; //Cargo certificado para firmar //============================= // Cargo el certificado a usarse en la firma en el objeto firmador // NOTA: Se incluira la parte publica del certificado en base64. // De los certificados que me pases, elijo el primero con parte privada, // ya que no quiero incluir el certificado raiz del emisor (no se // menciona en la politica de firmado y no aparece en los ejemplos): XML_KeyData.IncludeKeyValue:= true; for i:= 0 to Cajon.Count-1 do begin if Cajon.Certificates[i].PrivateKeyExists then begin XML_KeyData.certificate:= Cajon.Certificates[i]; break; end; end; //Compruebo que ha quedado algún certificado válido... 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; //Añado a la lista de nodos a firmar //================================== //NOTA: El metodo SHA1 no es el mas seguro, pero el SHA256 no se usa mucho //y puede dar problemas (WinXP SP2 no lo admite) por eso el DNIe tampoco //lo usa aun, asi que mejor dejo el valor por defecto. // // NODO 1: Se ha de firmar el documento original XML completo... // XML_RefDocu.DigestMethod:= xdmSHA1; //El mas estandard/compatible de todos XML_RefDocu.URINode:= XML_Doc.DocumentElement; //El XML completo XML_RefDocu.URI:= ''; //No hay nombre especifico para este nodo // En los ejemplos aparece la transformacion "Enveloped-Signature" // aunque en el PDF de la firma V3.1 no lo menciona. XML_RefDocu.TransformChain.Add(TElXMLEnvelopedSignatureTransform.Create); XML_Refs.Add(XML_RefDocu); //Lo sumo a las cosas a ser firmadas // Actualizo digest con este nuevo nodo. XML_Signer.UpdateReferencesDigest; // NODO 2: Se han de firmar las propiedades de firmado (XAdES).... // // El nodo SignedProperties es parte de la informacion XAdES que se // creo en XML_Signer.XAdESProcessor.Generate y se firma siempre en // el standard XAdES. Dejo el codigo abajo como referencia solo, pero // no funcionaría si se activa porque ya esta siendo firmado una vez. //XML_Signer.XAdESProcessor.QualifyingProperties.SignedProperties.ID:= 'SignedPropertiesID'; //XML_RefProp.DigestMethod:= xdmSHA1; //XML_RefProp.URI:= '#SignedPropertiesID'; //XML_Refs.Add(XML_RefProp); //XML_Signer.UpdateReferencesDigest; // // NODO 3: Se ha de firmar el propio certificado utilizado // // El nodo KeyInfo ha de ir identificado por 'Certificate1' -segun los // ejemplos, el PDF no da ningun nombre concreto- // si se firma dos veces un mismo XML no chocan aunque el // nombre coincida por estar dentro de diferentes nodos "Signature". XML_RefCert.URI:= '#Certificate1'; XML_RefCert.DigestMethod:= xdmSHA1; XML_Refs.Add(XML_RefCert); //Lo sumo a las cosas a ser firmadas //Como ya tengo todos los nodos, ahora genero la firma y consigo que //exista el nodo KeyInfo, de forma que pueda darle el nombre que le toca //antes de unir el XML original con el nuevo nodo de la firma. XML_Signer.Sign; XML_Signer.Signature.KeyInfo.ID:= 'Certificate1'; //Añado la firma al XML //===================== XML_Nodo:= ElXMLDOMNode(XML_Doc.DocumentElement); XML_Signer.Save(XML_Nodo); //Actualizo el stream MS con el XML final firmado. MS.Position:= 0; XML_Doc.SaveToStream(MS); result:= true; //Guardo XML firmado a fichero //============================ // Out_Stream:= TFileStream.create(SAveDialog1.FileName, fmCreate); // XML_Doc.SaveToStream(Out_Stream); // FreeAndNil(Out_Stream); finally //NOTA: Los XML_Ref se destruyen solos junto al XML_Refs XML_Doc.free; XML_Refs.free; XML_Signer.free; XML_XAdES.free; XML_KeyData.Free; end; end; //XML
Bueno, un tocho como ves, me costos meses dejarlo funcionando -eso sí, aprendí de XML y firmas que no veas- así que me debes X cervezas, con X >> 10
Por favor, ¿podrías incluir el objeto Abre y Cierra? las instrucciones que incluyes son una verdadera maravilla, pero "no soy capaz" de realizar esas operaciones con éxito.
Gracias.
#31
Escrito 18 agosto 2021 - 01:49
Por favor, ¿podrías incluir el objeto Abre y Cierra? las instrucciones que incluyes son una verdadera maravilla, pero "no soy capaz" de realizar esas operaciones con éxito.
Gracias.
Mira si esta clase te puede servir, tiene los metodos AbreNodo, CierraNodo y ValorNodo (abre el nodo, añade el valor y lo cierra):
unit ClaseXMLGenerador; interface uses Classes, SysUtils, StrUtils;//, AnsiStrings; type TXMLGenerador = class(TComponent) private { private declarations } FListaTexto: TStringList; function GetStrings:string; procedure SetStrings(Valor:string); protected { protected declarations } public { public declarations } constructor Create(AOwner:TComponent); override; destructor Destroy; override; procedure TextoLibre(const Texto:String); procedure AbreNodo(const Nodo:String); procedure CierraNodo(const Nodo:String); procedure ValorNodo(const Nodo, Valor:String); procedure GuardarEnFichero(const Fichero:String); procedure GuardarEnStream(var VariableStream: TStream); published property Texto: string read GetStrings write SetStrings; end; function QuitarSaltosLinea(Strs: TStrings; CharReplace:String=''):String; implementation function QuitarSaltosLinea(Strs: TStrings; CharReplace:String=''):String; var Str1, Str2:string; begin Str1 := StrUtils.AnsiReplaceStr(Strs.Text, #10, CharReplace); Str2 := StrUtils.AnsiReplaceStr(Str1, #13, CharReplace); Result := StrUtils.AnsiReplaceStr(Str2, '&', CharReplace); end; { tXMLGenerador } procedure tXMLGenerador.AbreNodo(const Nodo: String); begin FListaTexto.Add('<'+Nodo+'>'); end; procedure tXMLGenerador.CierraNodo(const Nodo: String); begin FListaTexto.Add('</'+Nodo+'>'); end; constructor tXMLGenerador.Create(AOwner:TComponent); begin inherited Create(AOwner); FListaTexto := TStringList.Create; end; destructor tXMLGenerador.Destroy; begin FListaTexto.Free; inherited; end; function TXMLGenerador.GetStrings: string; begin Result := FListaTexto.Text; end; procedure tXMLGenerador.GuardarEnFichero(const Fichero: String); var AuxStr:string; begin AuxStr := QuitarSaltosLinea(FListaTexto,''); FListaTexto.Text := AuxStr; FListaTexto.SaveToFile(Fichero); end; procedure TXMLGenerador.GuardarEnStream(var VariableStream: TStream); var AuxStr:string; begin AuxStr := QuitarSaltosLinea(FListaTexto,''); FListaTexto.Text := AuxStr; FListaTexto.SaveToStream(VariableStream); end; procedure TXMLGenerador.SetStrings(Valor: string); begin FListaTexto.Text := Valor; end; procedure tXMLGenerador.TextoLibre(const Texto: String); begin FListaTexto.Add(Texto); end; procedure tXMLGenerador.ValorNodo(const Nodo, Valor: String); begin AbreNodo(Nodo); TextoLibre(Valor); CierraNodo(Nodo); end; end.
#32
Escrito 18 agosto 2021 - 07:29
Muchas gracias por el aporte amigo defcon1_es.
Saludos