diff --git a/README.md b/README.md index d397baa..ea20ac1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -

@@ -21,7 +20,7 @@ Dependencias ---------------- Instalación --------- -Para poder hacer uso de nuestro SDK para consumir el servio **REST** que **SmarterWeb** le provee primero es necesario tener instalado una version de PHP ya sea la **5.6** o la version **7** y posteriormente instalar manejador de paquetes de PHP **Composer** +Para poder hacer uso de nuestro SDK para consumir el servio **REST** que **SmarterWeb** le provee primero es necesario tener instalado una versión de PHP ya sea la **5.6** o la versión **7** y posteriormente instalar manejador de paquetes de PHP **Composer** #### Instalar Composer ##### * Paso 1: @@ -31,11 +30,11 @@ Dar click en **Download** * Paso 3: Dar clic en **Composer-Setup.exe** esto abrira una ventana en su explorador para que guarde el archivo composer * Paso 4: -Ejecutar el archivo descargado **Composer-Setup.exe** y seguir los pasos de instalacion +Ejecutar el archivo descargado **Composer-Setup.exe** y seguir los pasos de instalación #### Preparar nuestro ambiente de Desarrollo ##### * Paso 1: -Necesitaremos crear un archivo llamador **composer.json** y dentro de el ingresaremos la libreria de la cual queremos hacer uso en nuestro ejemplo es **lunasoft/sw-sdk-php** +Necesitaremos crear un archivo llamador **composer.json** y dentro de el ingresaremos la librería de la cual queremos hacer uso en nuestro ejemplo es **lunasoft/sw-sdk-php** ```php { @@ -57,10 +56,10 @@ Dentro de tu carpeta de tu proyecto abrir **CMD** o **PowerShell** y escribir lo ``` composer install ``` -De esta manera descarga las dependencias que antes escribimos dentro del require que en nuestro caso es el **SDK** +De esta manera descarga las dependencias que antes escribimos dentro del requiere que en nuestro caso es el **SDK** #### En caso de no usar composer #### -Se puede hacer uso de las clases mediante la implementacion manual haciendo uso del archivo SWSDK.php en lugar del archivo vendor.php +Se puede hacer uso de las clases mediante la implementan manual haciendo uso del archivo SWSDK.php en lugar del archivo vendor.php ```php include('SWSDK.php'); @@ -74,7 +73,7 @@ La librería cuenta con dos servicios principales los que son la Autenticacion y **Usuario de Pruebas:** demo **Constraseña de Pruebas:** 123456789 #### Nueva funcionalidad para el soporte con servidores Proxy #### -Si tu posees un servidor proxy en tu empresa y deseas que la libreria lo use, debes pasar un parametro extra llamado "proxy" con el host y puerto de tu servidor proxy. +Si tu posees un servidor proxy en tu empresa y deseas que la librería lo use, debes pasar un parámetro extra llamado "proxy" con el host y puerto de tu servidor proxy. ```php $params = array( "url"=>"http://services.test.sw.com.mx", @@ -82,7 +81,7 @@ Si tu posees un servidor proxy en tu empresa y deseas que la libreria lo use, de ); ``` ## Autenticación ### -El servicio de Autenticación es utilizado principalmente para obtener el **token** el cual sera utilizado para poder timbrar nuestro CFDI (xml) ya emitido (sellado), para poder utilizar este servicio es necesario que cuente con un **usuario** y **contraseña** para posteriormente obtenga el token, usted puede utilizar los que estan en este ejemplo para el ambiente de **Pruebas**. +El servicio de Autenticación es utilizado principalmente para obtener el **token** el cual sera utilizado para poder timbrar nuestro CFDI (xml) ya emitido (sellado), para poder utilizar este servicio es necesario que cuente con un **usuario** y **contraseña** para posteriormente obtenga el token, usted puede utilizar los que están en este ejemplo para el ambiente de **Pruebas**. **Obtener Token** ```php @@ -182,7 +181,7 @@ El ejemplo anterior la respuesta es un objeto tipo **JSON** y dentro de el se en ``` ## Timbrar CFDI V2 ## -**StampV2** Recibe el contenido de un **XML** ya emitido (sellado) en formato **String**, posteriormente si la factura y el token son correctos devuelve el complemento timbre en un string (**TFD**),asi como el comprobante ya timbrado en formato string (**CFDI**), en caso contrario lanza una excepción. +**StampV2** Recibe el contenido de un **XML** ya emitido (sellado) en formato **String**, posteriormente si la factura y el token son correctos devuelve el complemento timbre en un string (**TFD**), así como el comprobante ya timbrado en formato string (**CFDI**), en caso contrario lanza una excepción. **Timbrar XML en formato string utilizando usuario y contraseña** ```php @@ -283,7 +282,7 @@ El ejemplo anterior la respuesta es un objeto tipo **JSON** y dentro de el se en ```json {"data":{ - "cfdi":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjxjZmRpOkNvbXByb2JhbnRlIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL3d3dy5zYXQuZ29iLm14L2NmZC8zIGh0dHA6Ly93...", + "cfdi":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjxjZmRpOkNvbXByb2JhbnRlIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL3d3dy5zYXQuZ29iLm14L2NmZC8zIGh0dHA6Ly93...", "tfd":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjxjZmRpOkNvbXByb2JhbnRlIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL3d3dy5zYXQuZ29iLm14L2NmZC8zIGh0dHA6Ly93...", "status":"success"} ``` @@ -357,7 +356,7 @@ El ejemplo anterior la respuesta es un objeto tipo **JSON** y dentro de el se en "status":"success"} ``` **Timbrar XML en formato base64 utilizando token/credenciales**
-Si se desea, se puede usar la version 3 en la modalidad base64, esto quiere decir que se puede enviar el xml previamente sellado en formato base64, y la libreria le respondera la misma estructura de respuesta que se usa en v3 normal con el cfdi en base64 tambien. +Si se desea, se puede usar la versión 3 en la modalidad base64, esto quiere decir que se puede enviar el xml previamente sellado en formato base64, y la librería le responderá la misma estructura de respuesta que se usa en v3 normal con el cfdi en base64 tambien. ```php -Si se desea, se puede usar la version 4 en la modalidad base64, esto quiere decir que se puede enviar el xml previamente sellado en formato base64, y la libreria le respondera la misma estructura de respuesta que se usa en v4 normal con el cfdi en base64 tambien. +Si se desea, se puede usar la versión 4 en la modalidad base64, esto quiere decir que se puede enviar el xml previamente sellado en formato base64, y la librería le responderá la misma estructura de respuesta que se usa en v4 normal con el cfdi en base64 también. ```php "http://services.test.sw.com.mx", "user"=>"demo", @@ -952,6 +950,73 @@ En este caso se recibe un mensaje JSON, el cual contiene los siguientes datos: } ``` +## Validación LRFC ## +En este servicio se hace la consulta en la lista LRFC para conocer si el RFC receptor es válido y susceptible de recibir facturas. +Ejemplo de uso +```php +require_once 'SWSDK.php'; +use SWServices\Validation\ValidaLrfc as ValidaLrfc; +try { + $params = array("url"=>"http://services.test.sw.com.mx", + "user"=>"demo", + "password"=> "123456789" + ); + ValidaLrfc::Set($params); + $resultadoLRFC = ValidaLrfc::ValidaLrfc('LAN8507268IA'); + var_dump($resultadoLRFC); +} +catch(Exception $e){ + echo 'Caught exception: ', $e->getMessage(), "\n"; +} +``` + +## Validación LCO## +En este servicio se hace la consulta en la lista LCO conocer si el número de certificado consultado receptor se encuentra en dicha lista, y conocer los datos del mismo. +Ejemplo de uso +```php +require_once 'SWSDK.php'; +use SWServices\Validation\ValidaLco as ValidaLco; +try { + $params = array("url"=>"http://services.test.sw.com.mx", + "user"=>"demo", + "password"=> "123456789" + ); + ValidaLco::Set($params); + $resultadoLCO = ValidaLco::ValidaLco('20001000000300022816'); + var_dump($resultadoLCO); +} +catch(Exception $e){ + echo 'Caught exception: ', $e->getMessage(), "\n"; +} +``` + +## Validación XML## +En este servicio se hace la validación del CFDI 3.3, validando los siguientes criterios: + +- Integridad. +- Sello. +- Errores de estructura. +- Matriz de errores del SAT (incluye complementos). +- Estatus de la factura en el SAT (en caso de incluir TFD). + +Ejemplo de uso +```php +require_once 'SWSDK.php'; +use SWServices\Validation\ValidarXML as ValidarXML; +try { + $params = array("url"=>"http://services.test.sw.com.mx", + "user"=>"demo", + "password"=> "123456789" + ); + ValidarXML::Set($params); + $resultadoValida = ValidarXML::ValidaXML($xml); // $xml es el string del cfdi + var_dump($resultadoValida); +} +catch(Exception $e){ + echo 'Caught exception: ', $e->getMessage(), "\n"; +} +``` + ## Consulta Status SAT ## **Consulta Status SAT** Recibe los parámetros de ***URL Soap***, ***RFC Emisor***, ***RFC Receptor***, ***Total***, y ***UUID*** en formato **String**, posteriormente hace la consulta en el SOAP proporcionado sobre el estatus de la factura. diff --git a/SWSDK.php b/SWSDK.php index dafecb5..280f4d1 100644 --- a/SWSDK.php +++ b/SWSDK.php @@ -5,6 +5,7 @@ require_once dirname(__FILE__) . '/SWServices/Authentication/AuthRequest.php'; require_once dirname(__FILE__) . '/SWServices/Stamp/StampRequest.php'; require_once dirname(__FILE__) . '/SWServices/Stamp/StampService.php'; + require_once dirname(__FILE__) . '/SWServices/Stamp/StampServiceCached.php'; require_once dirname(__FILE__) . '/SWServices/Issuer/IssuerService.php'; require_once dirname(__FILE__) . '/SWServices/Cancelation/CancelationService.php'; require_once dirname(__FILE__) . '/SWServices/Cancelation/CancelationRequest.php'; diff --git a/SWServices/Cancelation/CancelationService.php b/SWServices/Cancelation/CancelationService.php index 93e34ac..432cf6a 100644 --- a/SWServices/Cancelation/CancelationService.php +++ b/SWServices/Cancelation/CancelationService.php @@ -7,9 +7,16 @@ use Exception; class CancelationService extends Services { + private static $_cfdiData = null; + private static $_xml = null; public function __construct($params) { parent::__construct($params); + $c = count($params); + if($c == 7 || $c == 8) + self::setCSD($params); + else if (($c == 3 || $c == 4) && isset($params['xml'])) + self::setXml($params); } public static function Set($params){ @@ -67,6 +74,34 @@ public static function ConsultarCFDIRelacionadosPFX($rfc, $pfxB64, $password, $u public static function ConsultarCFDIRelacionadosXML($xml){ return cancelationRequest::sendReqXML(Services::get_url(), Services::get_token(), $xml, Services::get_proxy(), '/relations/xml'); } + + public static function CancelationByCSDParams() { + return cancelationRequest::sendReqCSD(Services::get_url(), Services::get_token(), self::$_cfdiData["rfc"], cancelationHandler::uuidReq(self::$_cfdiData["uuid"]), self::$_cfdiData["b64Cer"], self::$_cfdiData["b64Key"], self::$_cfdiData["password"], Services::get_proxy(), '/cfdi33/cancel/csd'); + } + public static function CancelationByXMLParams() { + return cancelationRequest::sendReqXML(Services::get_url(), Services::get_token(), self::$_xml, Services::get_proxy(), '/cfdi33/cancel/xml'); + } + + private static function setCSD($params) { + if(isset($params['url']) && isset($params['token']) && isset($params['uuid']) && isset($params['password']) && isset($params['rfc']) && isset($params['b64Cer']) && isset($params['b64Key'])) { + self::$_cfdiData = [ + 'uuid'=> $params['uuid'], + 'password'=> $params['password'], + 'rfc'=> $params['rfc'], + 'b64Cer'=> $params['b64Cer'], + 'b64Key'=> $params['b64Key'] + ]; + } else { + throw new Exception('Parámetros incompletos. Debe especificarse uuid, password, rfc, b64Cer, b64Key'); + } + } + private static function setXml($params) { + if(isset($params['url']) && isset($params['token']) && isset($params['xml'])) { + self::$_xml = $params['xml']; + } else { + throw new Exception('Parámetros incompletos. Debe especificarse url, token, y archivo xml'); + } + } } ?> \ No newline at end of file diff --git a/SWServices/Services.php b/SWServices/Services.php index de34a29..c3df48b 100644 --- a/SWServices/Services.php +++ b/SWServices/Services.php @@ -35,7 +35,7 @@ public function __construct($params) { self::$_token = $params['token']; date_default_timezone_set("America/Mexico_City"); self::$_expirationDate = new \DateTime('NOW'); - self::$_expirationDate->add(new \DateInterval("PT5Y")); + self::$_expirationDate->add(new \DateInterval("P5Y")); } } diff --git a/SWServices/Stamp/StampService.php b/SWServices/Stamp/StampService.php index 0a3fac1..2f7026c 100644 --- a/SWServices/Stamp/StampService.php +++ b/SWServices/Stamp/StampService.php @@ -1,15 +1,19 @@ getMessage()); + } + } + public static function StampV2($xml, $isb64 = false, $ttl = 600){ + try{ + $sello = sha1(self::getSignXml($xml)); + if(apc_add($sello, sha1($xml), $ttl)){ + $response = stampRequest::sendReq(Services::get_url(), Services::get_token(), $xml, "v2", $isb64, Services::get_proxy(), '/cfdi33/stamp/'); + return $response; + } + else{ + return self::showError($ttl, apc_fetch($sello)); + } + } + catch(Exeption $ex){ + throw new Exception("Exception: ".$ex->getMessage()); + } + } + public static function StampV3($xml, $isb64 = false, $ttl = 600){ + try{ + $sello = sha1(self::getSignXml($xml)); + if(apc_add($sello, sha1($xml), $ttl)){ + $response = stampRequest::sendReq(Services::get_url(), Services::get_token(), $xml, "v3", $isb64, Services::get_proxy(), '/cfdi33/stamp/'); + return $response; + } + else{ + return self::showError($ttl, apc_fetch($sello)); + } + } + catch(Exeption $ex){ + throw new Exception("Exception: ".$ex->getMessage()); + } + } + public static function StampV4($xml, $isb64 = false, $ttl = 600){ + try{ + $sello = sha1(self::getSignXml($xml)); + if(apc_add($sello, sha1($xml), $ttl)){ + $response = stampRequest::sendReq(Services::get_url(), Services::get_token(), $xml, "v4", $isb64, Services::get_proxy(), '/cfdi33/stamp/'); + return $response; + } + else{ + return self::showError($ttl, apc_fetch($sello)); + } + } + catch(Exeption $ex){ + throw new Exception("Exception: ".$ex->getMessage()); + } + } + + public static function StampVersion2V1($xml, $isb64 = false, $ttl = 600){ + try{ + $sello = sha1(self::getSignXml($xml)); + if(apc_add($sello, sha1($xml), $ttl)){ + $response = stampRequest::sendReq(Services::get_url(), Services::get_token(), $xml, "v1", $isb64, Services::get_proxy(), '/cfdi33/v2/stamp/'); + return $response; + } + else{ + return self::showError($ttl, apc_fetch($sello)); + } + } + catch(Exeption $ex){ + throw new Exception("Exception: ".$ex->getMessage()); + } + } + public static function StampVersion2V2($xml, $isb64 = false, $ttl = 600){ + try{ + $sello = sha1(self::getSignXml($xml)); + if(apc_add($sello, sha1($xml), $ttl)){ + $response = stampRequest::sendReq(Services::get_url(), Services::get_token(), $xml, "v2", $isb64, Services::get_proxy(), '/cfdi33/v2/stamp/'); + return $response; + } + else{ + return self::showError($ttl, apc_fetch($sello)); + } + } + catch(Exeption $ex){ + throw new Exception("Exception: ".$ex->getMessage()); + } + } + public static function StampVersion2V3($xml, $isb64 = false, $ttl = 600){ + try{ + $sello = sha1(self::getSignXml($xml)); + if(apc_add($sello, sha1($xml), $ttl)){ + $response = stampRequest::sendReq(Services::get_url(), Services::get_token(), $xml, "v3", $isb64, Services::get_proxy(), '/cfdi33/v2/stamp/'); + return $response; + } + else{ + return self::showError($ttl, apc_fetch($sello)); + } + } + catch(Exeption $ex){ + throw new Exception("Exception: ".$ex->getMessage()); + } + } + public static function StampVersion2V4($xml, $isb64 = false, $ttl = 600){ + try{ + $sello = sha1(self::getSignXml($xml)); + if(apc_add($sello, sha1($xml), $ttl)){ + $response = stampRequest::sendReq(Services::get_url(), Services::get_token(), $xml, "v4", $isb64, Services::get_proxy(), '/cfdi33/v2/stamp/'); + return $response; + } + else{ + return self::showError($ttl, apc_fetch($sello)); + } + } + catch(Exeption $ex){ + throw new Exception("Exception: ".$ex->getMessage()); + } + } + + private static function getSignXml($xml){ + $xmlA = simplexml_load_string($xml); + $json = json_encode($xmlA); + $xmlA = json_decode($json, TRUE); + return $xmlA["@attributes"]["Sello"]; + } + + private static function showError($ttl, $hash){ + $error = array( + "message" => "Se evitó un duplicado de la factura. Verifique si la misma ya fue timbrada por alguien más.", + "messageDetail" => "Factura registrada en la caché hace menos de $ttl segundos por otro proceso, hash(sha1) del xml: $hash", + "data" => null, + "status" => "error" + ); + return json_decode(json_encode($error)); + } +} + +?> \ No newline at end of file