'; echo 'PROCESO DE TIMBRADO, RECEPCIÓN DE PAGOS. CFDI VERSIÓN 4.0'; echo ''; echo 'LogoSAT'; ### 1. CONFIGURACIÓN INICIAL ###################################################### # 1.1 Configuración de zona horaria date_default_timezone_set('America/Mexico_City'); $Fec1 = date("d/m/Y"); $Fec2 = date("Y/m/d"); $Hora = date("H:i:s"); echo $Fec1." | ".$Hora."

"; # 1.2 Muestra la zona horaria predeterminada del servidor (opcional a mostrar) echo '
'; echo 'ZONA HORARIA PREDETERMINADA'; echo '
'; echo '
'; echo date_default_timezone_get(); echo '

'; ### 2. ASIGNACIÓN DE VALORES A VARIABLES ################################################### $SendaPEMS = "archs_pem/"; // 2.1 Directorio en donde se encuentran los archivos *.cer.pem y *.key.pem (para efectos de demostración se utilizan los que proporciona el SAT para pruebas). $SendaCFDI = "archs_cfdi/"; // 2.2 Directorio en donde se almacenarán los archivos *.xml (CFDIs). $SendaGRAFS = "archs_graf/"; // 2.3 Directorio en donde se almacenan los archivos .jpg (logo de la empresa) y .png (códigos bidimensionales). // 2.5 Datos de acceso del usuario (proporcionados por www.finkok.com) modo de integración (para pruebas) o producción. $username = "appwebpuntodeventa@gmail.com"; $password = "appPVweb@440"; ### MUESTRA LOS DATOS DEL USUARIO QUE ESTÁ TIMBRANDO (OPCIONAL A MOSTRAR) ###### echo '
'; echo 'DATOS DEL USUARIO QUE ESTÁ TIMBRANDO'; echo '
'; echo '
'; echo 'USUARIO: '.$username."
"; echo 'PASSWORD: '.$password."
"; echo '

'; ### 3. DEFINICIÓN DE VARIABLES INICIALES ########################################## $noCertificado = "30001000000400002434"; // 3.1 Número de certificado. $file_cer = "EKU9003173C9.cer.pem"; // 3.2 Nombre del archivo .cer.pem $file_key = "EKU9003173C9.key.pem"; // 3.3 Nombre del archivo .cer.key ################################################################################### ### 4. DATOS GENERALES DE LA FACTURA ################################################## $fact_serie = "A"; // 4.1 Número de serie. $fact_folio = mt_rand(1000, 9999); // 4.2 Número de folio (para efectos de demostración se asigna de manera aleatoria). $NoFac = $fact_serie.$fact_folio; // 4.3 Serie de la factura concatenado con el número de folio. $fact_tipcompr = "P"; // 4.4 Tipo de comprobante. $fact_exportacion = "01"; // 4.5 Atributo requerido para expresar si el comprobante ampara una operación de exportación. $subTotal = 0; // 4.7 Subtotal, suma de los importes antes de descuentos e impuestos (se calculan mas abajo). $IVA = 0; // 4.9 IVA, suma de los impuestos (se calculan mas abajo). $total = 0; // 4.10 Total, Subtotal - Descuentos + Impuestos (se calculan mas abajo). $fecha_fact = date("Y-m-d")."T".date("H:i:s"); // 4.11 Fecha y hora de facturación. $NumCtaPago = "6473"; // 4.12 Número de cuenta (sólo últimos 4 dígitos, opcional). $LugarExpedicion = "45079"; // 4.17 Lugar de expedición (código postal). $moneda = "XXX"; // 4.18 Moneda $SumaPagos = 0; ### No. DE CFDI ASIGNADO (CONTROL INTERNO) ###### echo '
'; echo 'No. DE CFDI'; echo '
'; echo '
'; echo $NoFac; echo '

'; ### 5. MUESTRA LA ZONA HORARIA PREDETERMINADA DEL SERVIDOR (OPCIONAL A MOSTRAR) ###### echo '
'; echo 'FECHA Y HORA DE SOLICITUD DE TIMBRADO'; echo '
'; echo '
'; echo $fecha_fact; // 5.1 Se muestra solo para consultar y confirmar que sea la correcta. echo '

'; ### 6. ARRAYS QUE CONTIENEN LOS ARTICULOS QUE FORMAN PARTE DE LA VENTA ##################### $Array_ClaveProdServ = ['84111506']; // 6.1 Clave del SAT correspondiente al artículo o servicio (consultar el catálogo de productos del SAT). $Array_NoIdentificacion = ['xxxx']; // 6.2 Clave asignada al artículo o servicio, sistema local. $Array_Cantidad = ['1']; // 6.3 Cantidad. $Array_ClaveUnidad = ['ACT']; // 6.4 Clave del SAT correspondiente a la unidad de medida (consultar el catálogo de productos del SAT). $Array_Descripcion = ['Pago']; // 6.6 Descripción del artículo o servicio. $Array_ValorUnitario = ['0']; // 6.7 Valor unitario del artículo o servicio. $Array_Importe = ['0']; // 6.8 Importe del artículo o servicio. $Array_ObjetoImp = ['01']; ### 7. ARRAYS QUE CONTIENEN LAS RECEPCIONES DE PAGOS ########################### $ArrayDocRel_IdDocumento = ['8B797440-C422-4900-A9BA-DCC74A7BBC02', '320C2B8F-DC38-496D-ACBF-DE5838C457C6']; // 7.1 $ArrayDocRel_Serie = ['A', 'A']; // 7.2 $ArrayDocRel_Folio = ['001', '002']; // 7.3 $ArrayDocRel_MonedaDR = ['MXN', 'MXN']; // 7.4 $ArrayDocRel_EquivalenciaDR = ['1', '1']; $ArrayDocRel_NumParcialidad = ['1', '4']; // 7.6 $ArrayDocRel_ImpSaldoAnt = ['10000.00', '8000']; // 7.7 $ArrayDocRel_ImpPagado = ['2000.00', '2000']; // 7.8 $ArrayDocRel_ImpSaldoInsoluto = ['8000.00', '6000']; // 7.9 $ArrayDocRel_ObjetoImp = ['01', '01']; // Atributo requerido para expresar si el pago del documento relacionado es objeto o no de impuesto. // Determinando el importe total de pagos. ============== for ($i=0; $icreateElement("cfdi:Comprobante"); $root = $xml->appendChild($root); $cadena_original='||'; $noatt= array(); #== 11.2 Se crea e inserta el primer nodo donde se declaran los namespaces ====== cargaAtt($root, array( "xmlns:pago20"=>"http://www.sat.gob.mx/Pagos20", "xmlns:cfdi"=>"http://www.sat.gob.mx/cfd/4", "xmlns:xs"=>"http://www.w3.org/2001/XMLSchema", "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation"=>"http://www.sat.gob.mx/Pagos20 http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd" ) ); $mifecha= date('Y-m-d H:i:s'); $NuevaFecha = strtotime ( '+0 hour' , strtotime ($mifecha) ) ; $NuevaFecha = strtotime ( '-1 minute' , $NuevaFecha ) ; $NuevaFecha = strtotime ( '+0 second' , $NuevaFecha ) ; $NuevaHora = date ( 'H:i:s' , $NuevaFecha); echo $mifecha . " | " . $NuevaFecha; echo "

"; #== 11.3 Rutina de integración de nodos ========================================= cargaAtt($root, array( "Version"=>"4.0", "Serie"=>$fact_serie, "Folio"=>$fact_folio, "Fecha"=>date("Y-m-d")."T". $NuevaHora, "NoCertificado"=>$noCertificado, "SubTotal"=>$subTotal, "Moneda"=>$moneda, "Total"=>$total, "TipoDeComprobante"=>$fact_tipcompr, "Exportacion"=>$fact_exportacion, "LugarExpedicion"=>$LugarExpedicion ) ); $emisor = $xml->createElement("cfdi:Emisor"); $emisor = $root->appendChild($emisor); cargaAtt($emisor, array("Rfc"=>$emisor_rfc, "Nombre"=>$emisor_rs, "RegimenFiscal"=>$emisor_ClaRegFis ) ); $receptor = $xml->createElement("cfdi:Receptor"); $receptor = $root->appendChild($receptor); cargaAtt($receptor, array( "Rfc"=>$receptor_rfc, "Nombre"=>$receptor_rs, "DomicilioFiscalReceptor"=>$DomicilioFiscalReceptor, "RegimenFiscalReceptor"=>$RegimenFiscalReceptor, "UsoCFDI"=>$UsoCFDI ) ); $conceptos = $xml->createElement("cfdi:Conceptos"); $conceptos = $root->appendChild($conceptos); #== 11.4 Ciclo "for", recopilación de datos de artículos e integración de sus respectivos nodos = $concepto = $xml->createElement("cfdi:Concepto"); $concepto = $conceptos->appendChild($concepto); cargaAtt($concepto, array( "ClaveProdServ"=>$Array_ClaveProdServ[0], "Cantidad"=>$Array_Cantidad[0], "ClaveUnidad"=>$Array_ClaveUnidad[0], "Descripcion"=>$Array_Descripcion[0], "ValorUnitario"=>$Array_ValorUnitario[0], "Importe"=>$Array_Importe[0], "ObjetoImp"=>$Array_ObjetoImp[0], ) ); // 11.4 COMPLEMENTO PARA RECEPCIÓN DE PAGOS ========================================= $complemento = $xml->createElement("cfdi:Complemento"); $complemento = $root->appendChild($complemento); $pagos = $xml->createElement("pago20:Pagos"); $pagos = $complemento->appendChild($pagos); cargaAtt($pagos, array( "Version"=>"2.0", ) ); $pago = $xml->createElement("pago20:Totales"); $pago = $pagos->appendChild($pago); cargaAtt($pago, array( "MontoTotalPagos"=>number_format($SumaPagos,2,'.','') ) ); $pago = $xml->createElement("pago20:Pago"); $pago = $pagos->appendChild($pago); cargaAtt($pago, array( "FechaPago"=>"2017-03-22T09:00:00", "FormaDePagoP"=>"06", "MonedaP"=>"MXN", "TipoCambioP"=>"1", "Monto"=>"4000.00", "NumOperacion"=>"AUT. 898872" ) ); for ($i=0; $icreateElement("pago20:DoctoRelacionado"); $docRel = $pago->appendChild($docRel); cargaAtt($docRel, array( "IdDocumento"=>$ArrayDocRel_IdDocumento[$i], "Serie"=>$ArrayDocRel_Serie[$i], "Folio"=>$ArrayDocRel_Folio[$i], "MonedaDR"=>$ArrayDocRel_MonedaDR[$i], "EquivalenciaDR"=>$ArrayDocRel_EquivalenciaDR[$i], "NumParcialidad"=>$ArrayDocRel_NumParcialidad[$i], "ImpSaldoAnt"=>number_format($ArrayDocRel_ImpSaldoAnt[$i],2,'.',''), "ImpPagado"=>number_format($ArrayDocRel_ImpPagado[$i],2,'.',''), "ImpSaldoInsoluto"=>number_format($ArrayDocRel_ImpSaldoInsoluto[$i],2,'.',''), "ObjetoImpDR"=>$ArrayDocRel_ObjetoImp[$i], ) ); } //============================================================================== #== 11.6 Termina de conformarse la "Cadena original" con doble || $cadena_original .= "|"; // Descomentar si se desea obtener en archivo .TXT la Cadena Original. // $file = fopen($SendaCFDI."CadenaOriginal_Factura_".$NoFac.".txt", "w"); // fwrite($file, $cadena_original . PHP_EOL); // fclose($file); // chmod($SendaCFDI."CadenaOriginal_Factura_".$NoFac.".txt", 0777); #=== Muestra la cadena original (opcional a mostrar) ======================= echo '
'; echo 'CADENA ORIGINAL'; echo '
'; echo '
'; echo $cadena_original; echo '

'; ## Fin de la integración de la Addenda. #################################### #== 11.8 Proceso para obtener el sello digital del archivo .pem.key ======== $keyid = openssl_get_privatekey(file_get_contents($SendaPEMS.$file_key)); openssl_sign($cadena_original, $crypttext, $keyid, OPENSSL_ALGO_SHA256); openssl_free_key($keyid); // openssl_sign($datos, $firma, $private_key_pem, OPENSSL_ALGO_SHA256); // ***** #== 11.9 Se convierte la cadena digital a Base 64 ========================== $sello = base64_encode($crypttext); // Firma. #=== Muestra el sello (opcional a mostrar) ================================= echo '
'; echo 'SELLO'; echo '
'; echo '
'; echo $sello; echo '

'; #== 11.10 Proceso para extraer el certificado del sello digital ============ $file = $SendaPEMS.$file_cer; // Ruta al archivo $datos = file($file); $certificado = ""; $carga=false; for ($i=0; $i'; echo 'CERTIFICADO DEL SELLO DIGITAL'; echo ''; echo '
'; echo $certificado; echo '

'; #== 11.12 Se continua con la integración de nodos =========================== $root->setAttribute("Sello",$sello); $root->setAttribute("Certificado",$certificado); # Certificado. #== Fin de la integración de nodos ========================================= $NomArchCFDI = $SendaCFDI."PreCFDI-Pagos_".$NoFac.".xml"; #=== 11.12 Se guarda el archivo .XML antes de ser timbrado ======================= $cfdi = $xml->saveXML(); $xml->formatOutput = true; $xml->save($NomArchCFDI); // Guarda el archivo .XML (sin timbrar) en el directorio predeterminado. unset($xml); #=== 11.13 Se dan permisos de escritura al archivo .xml. ========================= chmod($NomArchCFDI, 0777); ##### FIN DE LA CREACIÓN DEL ARCHIVO .XML ANTES DE SER TIMBRADO #################################################### ### 12. PROCESO DE TIMBRADO ######################################################## #=== Se muestra el .XML antes de ser timbrado (opcional a mostrar)========== echo '
'; echo 'FACTURA .XML A TIMBRAR'; echo '
'; echo '
'; echo htmlspecialchars($cfdi); echo '

'; #== 12.1 Se crea una variable de tipo DOM y se le carga el CFDI ================================= $xml2 = new DOMDocument(); $xml2->loadXML($cfdi); #== 12.2 Convirtiendo el contenido del CFDI a BASE 64 ====================== $xml_cfdi_base64 = base64_encode($cfdi); #== Modo transición ======================================== // $process = curl_init('https://demo-transicion.finkok.com'); #== 12.3 Datos de acceso al servidor de pruebas ============================ $process = curl_init('https://demo-facturacion.finkok.com/servicios/soap/stamp.wsdl'); #== 12.4 Datos de acceso al servidor de producción ========================= # $process = curl_init('https://facturacion.finkok.com/servicios/soap/stamp.wsdl'); #== 12.5 Creando el SOAP de envío ============================================== $cfdixml = << $xml_cfdi_base64 $username $password XML; #== 12.6 Proceso para guardar los datos que se envían al servidor en un archivo .XML ======================== $NomArchSoap = $SendaCFDI."SOAP_Envio_CFDI_".$NoFac.".xml"; #== 12.6.1 Si el archivo ya se encuentra se elimina =========================== if (file_exists ($NomArchSoap)==true){ unlink($NomArchSoap); } #== 12.6.2 Se crea el archivo .XML con el SOAP ================================ // Descomentar si se desea grabar en archivo el SOAP de envío. // $fp = fopen($NomArchSoap,"a"); // fwrite($fp, $cfdixml); // fclose($fp); // chmod($NomArchSoap, 0777); #=== 12.7 Muestra el contenido del SOAP que se envía al servidor del PAC (REQUEST) ========================= echo '
'; echo 'CONTENIDO DEL SOAP QUE SE ENVIA AL SERVIDOR DEL PAC'; echo '
'; echo '
'; echo htmlspecialchars($cfdixml); echo '

'; #== 12.8 Se envía el contenido del SOAP al servidor del PAC ===================== curl_setopt($process, CURLOPT_HTTPHEADER, array('Content-Type: text/xml',' charset=utf-8')); curl_setopt($process, CURLOPT_POSTFIELDS, $cfdixml); curl_setopt($process, CURLOPT_RETURNTRANSFER, true); curl_setopt($process, CURLOPT_POST, true); curl_setopt($process, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($process, CURLOPT_SSL_VERIFYHOST, false); $RespServ = curl_exec($process); #== 12.9 Se muestra la respuesta del servidor del PAC (opcional a mostrar) ================ echo '
'; echo 'RESPUESTA DEL SERVIDOR DEL PAC'; echo '
'; echo '
'; echo htmlspecialchars($RespServ); echo '

'; curl_close($process); ## FIN DEL PROCESO DE TIMBRADO ################################################# ## 13. PROCESOS POSTERIORES AL TIMBRADO ######################################## $Total = 0; $tim_uuid =""; $Emisor_RFC=""; $Receptor_RFC=""; #== 13.1 Se asigna la respuesta del servidor a una variable de tipo DOM ==== $VarXML = new DOMDocument(); $VarXML->loadXML($RespServ); #== 13.2 Se graba la respuesta del servidor a un archivo .xml // $VarXML->save($SendaCFDI."RespServ_Pagos_".$NoFac.".xml"); // chmod($SendaCFDI."RespServ_Pagos_".$NoFac.".xml", 0777); echo "
"; #== 13.3 Se asigna el contenido del tag "xml" a una variable =============== $RespServ = $VarXML->getElementsByTagName('xml'); #== 13.4 Se obtiene el valor del nodo ====================================== foreach($RespServ as $Nodo){ $valor_del_nodo = $Nodo->nodeValue; } #== Si el nodo contiene datos se realizan los siguientes procesos ====== if($valor_del_nodo != ""){ // unlink($SendaCFDI."xlst_".$NoFac.".xml"); <-- Puede ser descomentado para eliminar el archivo .XML sin timbrar. #== 13.5 Se muestra el .XML ya timbrado (CFDI V 3.2), opcional a mostrar ===== echo '
'; echo 'FACTURA .XML (CFDI) YA TIMBRADA'; echo '
'; echo '
'; echo htmlspecialchars($Nodo->nodeValue); echo '

'; #=== 13.6 Guardando el CFDI en archivo .XML ======================= $NomArchXML = "CFDI40_Pagos_".$NoFac.".xml"; $NomArchPDF = "CFDI40_Pagos_".$NoFac.".pdf"; $xmlt = new DOMDocument(); $xmlt->loadXML($valor_del_nodo); $xmlt->save($SendaCFDI.$NomArchXML); chmod($SendaCFDI.$NomArchXML, 0777); // 13.6.1 Convertir archivo .XML a UTF-8 (OPCIONAL). // $file_name = $SendaCFDI.$NomArchXML; // $f = file_get_contents($file_name); // $f = iconv("WINDOWS-1252", "UTF-8", $f); // file_put_contents($file_name, "\xEF\xBB\xBF".$f); #== 13.7 Procesos para extraer datos del Timbre Fiscal del CFDI ==== $docXML = new DOMDocument(); $docXML->load($SendaCFDI."CFDI40_Pagos_".$NoFac.".xml"); $params = $docXML->getElementsByTagName("Comprobante"); foreach ($params as $param) { $VersionCFDI = $param->getAttribute("Version"); } $comprobante = $docXML->getElementsByTagName("TimbreFiscalDigital"); #== 13.8 Se obtienen contenidos de los atributos y se asignan a variables para ser mostrados ======= foreach($comprobante as $timFis){ $version_timbre = $timFis->getAttribute('Version'); $sello_SAT = $timFis->getAttribute('SelloSAT'); $cert_SAT = $timFis->getAttribute('NoCertificadoSAT'); $sello_CFD = $timFis->getAttribute('SelloCFD'); $tim_fecha = $timFis->getAttribute('FechaTimbrado'); $tim_uuid = $timFis->getAttribute('UUID'); echo 'Versión de CFDI: '.$VersionCFDI.'
'; echo 'Versión de timbre: '.$version_timbre.'
'; echo 'Sello del SAT: '.$sello_SAT.'
'; echo 'Certificado del SAT: '.$cert_SAT.'
'; echo 'Sello del CFDI: '.$sello_CFD.'
'; echo 'Fecha de timbrado: '.$tim_fecha.'
'; echo 'Folio fiscal: '.$tim_uuid.'
'; } #== Se muestra el número de factura asignado por el sistema local (no asingado por el SAT). echo 'No. de factura asignado por el sistema local: '.$NoFac.'

'; $params = $docXML->getElementsByTagName('Emisor'); foreach ($params as $param) { $Emisor_RFC = $param->getAttribute('Rfc'); } $params = $docXML->getElementsByTagName('Receptor'); foreach ($params as $param) { $Receptor_RFC = $param->getAttribute('Rfc'); } $params = $docXML->getElementsByTagName('Comprobante'); foreach ($params as $param) { $total = $param->getAttribute('Total'); } #== 13.9 Se crea el archivo .PNG con codigo bidimensional ========== $filename = "archs_graf/Img_".$tim_uuid.".png"; $CadImpTot = ProcesImpTot($total); $Cadena = "?re=".$Emisor_RFC."&rr=".$Receptor_RFC."&tt=".$CadImpTot."&id=".$tim_uuid; QRcode::png($Cadena, $filename, 'H', 3, 2); chmod($filename, 0777); echo '
'; echo 'GRÁFICO "QR" RESULTANTE.'; echo '
'; echo ''.$filename.''; #== 13.10 Se crea código HTML para mostrar opciones al usuario. ?> TODO supply a title
        
getElementsByTagName('CodigoError'); foreach($codigoError as $NodoStatus){ $valorNod = $NodoStatus->nodeValue; } echo '
'; echo 'CÓDIGO DE ERROR.'; echo '
'; echo '
'; echo $valorNod; echo '
'; } ##### FIN DE PROCEDIMIENTOS #################################################### ### 14. FUNCIONES DEL MÓDULO ################################################### # 14.1 Función que integra los nodos al archivo .XML y forma la "Cadena original". function cargaAtt(&$nodo, $attr){ global $xml, $cadena_original; $quitar = array('sello'=>1,'noCertificado'=>1,'certificado'=>1); foreach ($attr as $key => $val){ $val = preg_replace('/\s\s+/', ' ', $val); $val = trim($val); if (strlen($val)>0){ $val = utf8_encode(str_replace("|","/",$val)); $nodo->setAttribute($key,$val); if (!isset($quitar[$key])) if (substr($key,0,3) != "xml" && substr($key,0,4) != "xsi:") $cadena_original .= $val . "|"; } } } # 14.2 Función que integra los nodos al archivo .XML sin integrar a la "Cadena original". function cargaAttSinIntACad(&$nodo, $attr){ global $xml; $quitar = array('sello'=>1,'noCertificado'=>1,'certificado'=>1); foreach ($attr as $key => $val){ $val = preg_replace('/\s\s+/', ' ', $val); $val = trim($val); if (strlen($val)>0){ $val = str_replace("|","/",$val); $nodo->setAttribute($key,$val); if (!isset($quitar[$key])) if (substr($key,0,3) != "xml" && substr($key,0,4) != "xsi:"); } } } # 14.3 Funciónes que da formato al "Importe total" como lo requiere el SAT para ser integrado al código QR. function ProcesImpTot($ImpTot){ $ImpTot = number_format($ImpTot, 4); // <== Se agregó el 30 de abril de 2017. $ArrayImpTot = explode(".", $ImpTot); $NumEnt = $ArrayImpTot[0]; $NumDec = ProcesDecFac($ArrayImpTot[1]); return $NumEnt.".".$NumDec; } function ProcesDecFac($Num){ $FolDec = ""; if ($Num < 10){$FolDec = "00000".$Num;} if ($Num > 9 and $Num < 100){$FolDec = $Num."0000";} if ($Num > 99 and $Num < 1000){$FolDec = $Num."000";} if ($Num > 999 and $Num < 10000){$FolDec = $Num."00";} if ($Num > 9999 and $Num < 100000){$FolDec = $Num."0";} return $FolDec; }