diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml new file mode 100644 index 00000000..827d2e72 --- /dev/null +++ b/.github/workflows/publish-maven.yml @@ -0,0 +1,40 @@ +name: Publish to Maven Central + +on: + push: + tags: + - 'release-*' + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'temurin' + cache: maven + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + + - name: Build and verify + run: mvn clean verify -DskipTests + + - name: Publish to Maven Central + run: mvn deploy -Prelease -DskipTests + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..6e19bec1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,103 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +SW-JAVA is a Java SDK for consuming SW sapien® CFDI (Mexican electronic invoicing) services. It provides authentication, stamping (timbrado), cancellation, validation, and other tax document operations. + +## Build Commands + +```bash +# Build the project +mvn clean package + +# Build with dependencies JAR +mvn clean package assembly:single + +# Run all tests +mvn test + +# Run a single test class +mvn test -Dtest=SWAuthenticationServiceTest + +# Run a specific test method +mvn test -Dtest=SWAuthenticationServiceTest#testAuth + +# Build for release (includes GPG signing) +mvn clean deploy -Prelease +``` + +## Test Environment Variables + +Tests require the following environment variables: +- `SDKTEST_USER` - SW account username +- `SDKTEST_PASSWORD` - SW account password +- `SDKTEST_TOKEN` - SW authentication token + +Test endpoints: +- Services URL: `https://services.test.sw.com.mx` +- API URL: `https://api.test.sw.com.mx` + +## Architecture + +### Service Layer (`Services/`) +All services extend the abstract `SWService` base class which handles: +- Authentication (user/password or token-based) +- Token auto-refresh based on expiration +- Proxy support +- TLS 1.2 enforcement + +Key services: +- `SWAuthenticationService` - Token generation +- `SWStampService` / `SWStampServiceV2` / `SWStampServiceV4` - CFDI stamping +- `SWIssueService` / `SWIssueServiceV2` / `SWIssueServiceV4` - CFDI sealing and stamping +- `SWCancelationService` - Invoice cancellation +- `SWValidateService` - CFDI validation +- `SWBalanceAccountService` - Account balance queries +- `SWPdfService` - PDF generation from CFDI +- `SWStorageService` - Document storage operations + +### Request/Response Pattern (`Utils/Requests/`, `Utils/Responses/`) +Each service has corresponding request and response classes: +- Request classes handle HTTP communication to SW APIs +- Response classes model the API responses with status, data, and error information +- Response versions (V1-V4) provide different levels of detail for stamping operations + +### Response Versions for Stamping +| Version | Response Content | +|---------|------------------| +| V1 | Timbre fiscal digital (TFD) only | +| V2 | TFD + timbrado CFDI | +| V3 | Timbrado CFDI only | +| V4 | All stamping data | + +### Exceptions (`Exceptions/`) +- `AuthException` - Authentication failures +- `GeneralException` - General API errors +- `ValidationException` - Input validation errors + +### Helpers (`Utils/Helpers/`) +- `BuildResponseV1-V4` - Response builders for different stamp versions +- `RequestHelper` / `RequestZipHelper` - HTTP request utilities +- `Validations` - Input validation utilities + +## CFDI Types Supported + +Test resources in `src/test/resources/CFDI40/` show supported document types: +- Standard invoices (Ingreso) +- Payments (Pagos20) +- Payroll (Nomina12) +- Foreign trade (ComercioExterior11) +- Transportation (CartaPorte20) +- Retentions (Retenciones20) +- Donations (Donatarias11) +- Other complements (INE, Addenda, etc.) + +## Dependencies + +The SDK uses: +- Apache HttpClient 5 for HTTP operations +- org.json and Gson for JSON processing +- JUnit Jupiter 5 for testing +- External dependency `sw-resources-java` for CFDI cadena original generation diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..78416b76 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Lunasoft® + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2fd63f4d..68adbe87 100644 --- a/README.md +++ b/README.md @@ -418,6 +418,205 @@ public class ExampleReadme { ``` +# Cancelación de Retenciones # + +Este servicio se utiliza para cancelar retenciones, aquí los métodos que se ofrecen: + +
+ +Cancelación de Retenciones por CSD + +Como su nombre lo indica, este método recibe todos los elementos que componen el CSD los cuales son los siguientes: + +- Certificado (.cer) en Base64 +- Key (.key) en Base64 +- RFC emisor +- Password del archivo key +- UUID +- Motivo +- Folio Sustitución (requerido sólo cuando Motivo es 01) + +**Ejemplo de consumo de la librería para cancelar retenciones con CSD** + +```java +package com.mycompany.examplereadme; + +import Exceptions.AuthException; +import Exceptions.GeneralException; +import Services.CancelationRetention.SWCancelationRetentionService; +import Utils.Responses.Cancelation.CancelationResponse; +import java.io.IOException; + +public class ExampleReadme { + + public static void main(String[] args) { + try { + //Instancia del servicio y autenticación + SWCancelationRetentionService sdk = new SWCancelationRetentionService("user", "password", "https://services.test.sw.com.mx"); + CancelationResponse response = null; + //Paso de datos de cancelación + response = (CancelationResponse) sdk.Cancelation("uuid", "password_csd", "rfc", "b64Cer", "b64Key", "motivo", "folio_sustitucion"); + //Muestra los resultados + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.acuse); + System.out.println(response.uuid); + System.out.println(response.uuidStatusCode); + //En caso de obtener un error, este puede obtenerse de los campos + System.out.println(response.message); + System.out.println(response.messageDetail); + } catch (AuthException | GeneralException | IOException e) { + System.out.println(e); + } + + } +} +``` + +**Cancelar retenciones con CSD utilizando token** + +```java + //Basta con sustituir esta linea en el ejemplo anterior, colocarás el token de tu cuenta y la URL base del ambiente que requieres acceder + SWCancelationRetentionService sdk = new SWCancelationRetentionService("tokenUser", "https://services.test.sw.com.mx"); +``` +
+ +
+ +Cancelación de Retenciones por PFX + + +Este método recibe los siguientes parámetros: + +- Archivo PFX en Base64 +- RFC emisor +- Password de PFX +- UUID +- Motivo +- Folio Sustitución (requerido sólo cuando Motivo es 01) + +**Ejemplo de consumo de la librería para cancelar retenciones con PFX** + +```java +package com.mycompany.examplereadme; + +import Exceptions.AuthException; +import Exceptions.GeneralException; +import Services.CancelationRetention.SWCancelationRetentionService; +import Utils.Responses.Cancelation.CancelationResponse; +import java.io.IOException; + +public class ExampleReadme { + + public static void main(String[] args) { + try { + //Instancia del servicio y autenticación + SWCancelationRetentionService sdk = new SWCancelationRetentionService("user", "password", "https://services.test.sw.com.mx"); + CancelationResponse response = null; + //Paso de datos de cancelación + response = (CancelationResponse) sdk.Cancelation("uuid", "password_pfx", "rfc", "pfxb64", "motivo", "folio_sustitucion"); + //Muestra los resultados + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.acuse); + System.out.println(response.uuid); + System.out.println(response.uuidStatusCode); + //En caso de obtener un error, este puede obtenerse de los campos + System.out.println(response.message); + System.out.println(response.messageDetail); + } catch (AuthException | GeneralException | IOException e) { + System.out.println(e); + } + + } +} +``` + +**Cancelar retenciones con PFX utilizando token** + +```java + //Basta con sustituir esta linea en el ejemplo anterior, colocarás el token de tu cuenta y la URL base del ambiente que requieres acceder + SWCancelationRetentionService sdk = new SWCancelationRetentionService("tokenUser", "https://services.test.sw.com.mx"); +``` +
+ +
+ +Cancelación de Retenciones por XML + + +Este método recibe únicamente el XML sellado con los UUID a cancelar. + +**Ejemplo de XML para cancelar retenciones** +```xml + + + + + + + + + +``` + +Para caso de motivo 01 deberá añadir el atributo "FolioSustitucion" dentro del Nodo +**Ejemplo nodo Folio motivo 01** +```xml + + + +``` + +**Ejemplo de consumo de la librería para cancelar retenciones por XML** + +```java +package com.mycompany.examplereadme; + +import Exceptions.AuthException; +import Exceptions.GeneralException; +import Services.CancelationRetention.SWCancelationRetentionService; +import Utils.Responses.Cancelation.CancelationResponse; +import java.io.IOException; + +public class ExampleReadme { + + public static void main(String[] args) { + try { + //Instancia del servicio y autenticación + SWCancelationRetentionService sdk = new SWCancelationRetentionService("user", "password", "https://services.test.sw.com.mx"); + CancelationResponse response = null; + //Paso de XML de cancelación + response = (CancelationResponse) sdk.Cancelation("xmlCancelacion"); + //Muestra los resultados + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.acuse); + System.out.println(response.uuid); + System.out.println(response.uuidStatusCode); + //En caso de obtener un error, este puede obtenerse de los campos + System.out.println(response.message); + System.out.println(response.messageDetail); + } catch (AuthException | GeneralException | IOException e) { + System.out.println(e); + } + + } +} +``` + +**Cancelar retenciones por XML utilizando token** + +```java + //Basta con sustituir esta linea en el ejemplo anterior, colocarás el token de tu cuenta y la URL base del ambiente que requieres acceder + SWCancelationRetentionService sdk = new SWCancelationRetentionService("tokenUser", "https://services.test.sw.com.mx"); +``` +
+ # Validación # @@ -628,6 +827,145 @@ public class ExampleReadme { ``` +
+ +Consulta de saldo de un usuario específico + + +
Este método recibe los siguientes parametros: +* Usuario y contraseña o Token +* Url APIs SW (Url Servicios SW solo es necesaria cuando se usa autenticación con usuario y contraseña) +* IdUser (UUID del usuario del cual se desea obtener el saldo) + +**Ejemplo de consumo de la libreria para consultar el saldo de un usuario específico utilizando Token** + +```java +package com.mycompany.examplereadme; + +import Exceptions.AuthException; +import Exceptions.GeneralException; +import Services.BalanceAccount.SWBalanceAccountService; +import Utils.Responses.BalanceAccount.BalanceAcctResponse; +import java.io.IOException; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ExampleReadme { + + public static void main(String[] args) { + + try { + //Intancia del servicio de Consulta de saldo y autenticación con Token + SWBalanceAccountService sdk = new SWBalanceAccountService("T2lYQ0t4L0R...", "https://api.test.sw.com.mx"); + BalanceAcctResponse response = null; + response = (BalanceAcctResponse) sdk.GetUserBalanceAccount(UUID.fromString("828f19b1-77dc-48bc-9cfa-d48b5cf7e30c")); + + //Imprimimos los datos de la respuesta que se obtuvo + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.idUserBalance); + System.out.println(response.idUser); + System.out.println(response.stampsBalance); + System.out.println(response.stampsUsed); + System.out.println(response.expirationDate); + System.out.println(response.isUnlimited); + System.out.println(response.stampsAssigned); + if (response.lastTransaction != null) { + System.out.println("Folio: " + response.lastTransaction.folio); + System.out.println("ID Usuario: " + response.lastTransaction.idUser); + System.out.println("ID Usuario Receptor: " + response.lastTransaction.idUserReceiver); + System.out.println("Nombre Receptor: " + response.lastTransaction.nameReceiver); + System.out.println("Stamps In: " + response.lastTransaction.stampsIn); + System.out.println("Stamps Out: " + + (response.lastTransaction.stampsOut != null ? response.lastTransaction.stampsOut : "null")); + System.out.println("Stamps Current: " + response.lastTransaction.stampsCurrent); + System.out.println("Comentario: " + response.lastTransaction.comment); + System.out.println("Fecha: " + response.lastTransaction.date); + System.out.println("Email Enviado: " + response.lastTransaction.isEmailSent); + } else { + System.out.println("No hay transacción disponible."); + } + //En caso de obtener error, este puede obtenerse de los siguientes campos + System.out.println(response.message); + System.out.println(response.messageDetail); + } catch (AuthException ex) { + System.out.println(ex); + } catch (GeneralException ex) { + Logger.getLogger(ExampleReadme.class.getName()).log(Level.SEVERE, null, ex); + } catch (IOException ex) { + Logger.getLogger(ExampleReadme.class.getName()).log(Level.SEVERE, null, ex); + } + } +} + +``` + +**Ejemplo de consumo de la libreria para consultar el saldo de un usuario específico utilizando Usuario y Contraseña** + +```java +package com.mycompany.examplereadme; + +import Exceptions.AuthException; +import Exceptions.GeneralException; +import Services.BalanceAccount.SWBalanceAccountService; +import Utils.Responses.BalanceAccount.BalanceAcctResponse; +import java.io.IOException; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ExampleReadme { + + public static void main(String[] args) { + + try { + //Intancia del servicio de Consulta de saldo y autenticación con Usuario y Contraseña + SWBalanceAccountService sdk = new SWBalanceAccountService("user", "password", "https://services.test.sw.com.mx", "https://api.test.sw.com.mx"); + BalanceAcctResponse response = null; + response = (BalanceAcctResponse) sdk.GetUserBalanceAccount(UUID.fromString("828f19b1-77dc-48bc-9cfa-d48b5cf7e30c")); + + //Imprimimos los datos de la respuesta que se obtuvo + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.idUserBalance); + System.out.println(response.idUser); + System.out.println(response.stampsBalance); + System.out.println(response.stampsUsed); + System.out.println(response.expirationDate); + System.out.println(response.isUnlimited); + System.out.println(response.stampsAssigned); + if (response.lastTransaction != null) { + System.out.println("Folio: " + response.lastTransaction.folio); + System.out.println("ID Usuario: " + response.lastTransaction.idUser); + System.out.println("ID Usuario Receptor: " + response.lastTransaction.idUserReceiver); + System.out.println("Nombre Receptor: " + response.lastTransaction.nameReceiver); + System.out.println("Stamps In: " + response.lastTransaction.stampsIn); + System.out.println("Stamps Out: " + + (response.lastTransaction.stampsOut != null ? response.lastTransaction.stampsOut : "null")); + System.out.println("Stamps Current: " + response.lastTransaction.stampsCurrent); + System.out.println("Comentario: " + response.lastTransaction.comment); + System.out.println("Fecha: " + response.lastTransaction.date); + System.out.println("Email Enviado: " + response.lastTransaction.isEmailSent); + } else { + System.out.println("No hay transacción disponible."); + } + //En caso de obtener error, este puede obtenerse de los siguientes campos + System.out.println(response.message); + System.out.println(response.messageDetail); + } catch (AuthException ex) { + System.out.println(ex); + } catch (GeneralException ex) { + Logger.getLogger(ExampleReadme.class.getName()).log(Level.SEVERE, null, ex); + } catch (IOException ex) { + Logger.getLogger(ExampleReadme.class.getName()).log(Level.SEVERE, null, ex); + } + } +} + +``` +
+
Agregar timbres @@ -2479,7 +2817,7 @@ namespace ExampleReadme Emisión Timbrado (IssueV4) **Ejemplo del consumo de la librería para el servicio IssueV4 (PDF) Json en formato string mediante usuario y contraseña.** -```cs +```java import Services.Issue.SWIssueService; import Utils.Responses.Stamp.SuccessV1Response; @@ -2546,7 +2884,72 @@ public class ExampleReadme { SWIssueServiceV4 stamp = new SWIssueServiceV4("tokenUser", "http://services.test.sw.com.mx"); ```
----------------- + +# Timbrado Retenciones # +
Timbrado Retenciones + +
Recibe el contenido de un XML ya emitido (sellado) en formato String, posteriormente si la factura y el token son correctos devuelve el CFDI timbrado, en caso contrario lanza una excepción. + +Este método recibe los siguientes parámetros: +* Archivo en formato **String** ó **Base64** +* Usuario y contraseña ó Token +* Url Servicios SW + +**Ejemplo de consumo de la librería para timbrar XML en formato string utilizando usuario y contraseña** +```java +import Utils.Responses.StampRetention.SuccessV3Response; +import Services.StampRetention.SWStampRetentionService; + +public class ExampleReadme { + public static void main(String[] args) { + try { + // Inicializar el objeto con la información de la cuenta y especifica la URL base para acceder al entorno deseado + SWStampRetentionService api = new SWStampRetentionService("user", "password", "http://services.test.sw.com.mx"); + // Inicializar un objeto de respuesta para almacenar la respuesta + SuccessV3Response response = null; + //Se llama al método StampRetention y se envia el xml y versión de respuesta + response = (SuccessV3Response) api.StampRetention(stringXML, "v3"); + // En response se mostrará la informacion de respuesta del servicio + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.retencion); + } catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +**Ejemplo de consumo de la librería para timbrar XML en formato string utilizando token** [¿Como obtener token?](http://developers.sw.com.mx/knowledge-base/generar-un-token-infinito/) +```java +import Utils.Responses.StampRetention.SuccessV3Response; +import Services.StampRetention.SWStampRetentionService; + +public class ExampleReadme { + public static void main(String[] args) { + try { + // Inicializar el objeto con la información del token de acceso y especifica la URL base para acceder al entorno deseado + SWStampRetentionService api = new SWStampRetentionService("tokenUser", "http://services.test.sw.com.mx"); + // Inicializar un objeto de respuesta para almacenar la respuesta + SuccessV3Response response = null; + //Se llama al método StampRetention y se envia el xml y versión de respuesta + response = (SuccessV3Response) api.StampRetention(stringXML, "v3"); + // En response se mostrará la informacion de respuesta del servicio + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.retencion); + } catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +
+ +| Version | Respuesta | +|---------|---------------------------------------------------------------| +| V3 | Devuelve el CFDI timbrado | Para mayor referencia de un listado completo de los servicios favor de visitar nuestro [sitio developers](https://developers.sw.com.mx/). diff --git a/pom.xml b/pom.xml index 381ba7ff..26e015a3 100644 --- a/pom.xml +++ b/pom.xml @@ -6,19 +6,19 @@ ISO-8859-1 SW-JAVA - 1.0.21.3 + 1.0.26.1 jar https://github.com/lunasoft/sw-sdk-java scm:git:git@github.com:lunasoft/sw-sdk-java.git scm:git:git@github.com:lunasoft/sw-sdk-java.git - - - GNU General Public License (GPL) - http://www.gnu.org/licenses/gpl.txt - - + + + MIT License + https://opensource.org/licenses/MIT + + RichBarusta @@ -52,16 +52,6 @@ ${project.groupId}:${project.artifactId} SW SDK JAVA https://sw.com.mx/ - - - ossrh - https://s01.oss.sonatype.org/content/repositories/snapshots/ - - - ossrh - https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - - src/main/java src/test/java @@ -94,14 +84,14 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 true - ossrh - https://s01.oss.sonatype.org/ - true + central + true + published @@ -206,14 +196,14 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 true - ossrh - https://oss.sonatype.org/ - true + central + true + published diff --git a/src/main/java/Services/BalanceAccount/SWBalanceAccountService.java b/src/main/java/Services/BalanceAccount/SWBalanceAccountService.java index 57ed7e31..2948a903 100644 --- a/src/main/java/Services/BalanceAccount/SWBalanceAccountService.java +++ b/src/main/java/Services/BalanceAccount/SWBalanceAccountService.java @@ -6,12 +6,13 @@ import java.io.IOException; import java.util.UUID; + import Exceptions.AuthException; import Exceptions.GeneralException; import Services.SWService; +import Utils.Helpers.EnumBalanceStamp.AccountBalanceAction; import Utils.Requests.BalanceAccount.BalanceAcctOptionsRequest; import Utils.Requests.BalanceAccount.BalanceAcctRequest; -import Utils.Helpers.EnumBalanceStamp.AccountBalanceAction; import Utils.Responses.IResponse; /** @@ -59,6 +60,22 @@ public IResponse GetBalanceAccount() throws AuthException, GeneralException, IOE return BalanceAcctRequest.createBalanceAcctRequest(settings); } + /** + * Obtiene el saldo de la cuenta de un usuario específico. + * + * @param idUser ID del usuario del cual se desea obtener el saldo. + * @return IResponse con el resultado de la operación. + * @throws AuthException Si la autenticación falla. + * @throws GeneralException Si ocurre un error general. + * @throws IOException Si hay un error de entrada/salida. + */ + public IResponse GetUserBalanceAccount(UUID idUser) throws AuthException, GeneralException, IOException { + BalanceAcctOptionsRequest settings = BalanceAcctOptionsRequest.getUserBalanceRequest(getToken(), + getURIAPI() == null ? getURI() : getURIAPI(), idUser, + getProxyHost(), getProxyPort()); + return BalanceAcctRequest.createUserBalanceRequest(settings); + } + /** * Realiza un movimiento de agregar saldo en la cuenta. * diff --git a/src/main/java/Services/Cancelation/SWCancelationService.java b/src/main/java/Services/Cancelation/SWCancelationService.java index dc1d2934..99046fdb 100644 --- a/src/main/java/Services/Cancelation/SWCancelationService.java +++ b/src/main/java/Services/Cancelation/SWCancelationService.java @@ -30,25 +30,25 @@ public SWCancelationService(String token, String URI, String proxyHost, int prox } public IResponse Cancelation(String uuid, String password, String rfc, String b64Cer, String b64Key, String motivo, String folioSustitucion) throws AuthException, GeneralException, IOException { - CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), uuid, password, rfc, b64Cer, b64Key, motivo, folioSustitucion, getProxyHost(), getProxyPort()); + CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), uuid, password, rfc, b64Cer, b64Key, motivo, folioSustitucion, false, getProxyHost(), getProxyPort()); CancelationRequest req = new CancelationRequest(); return req.sendRequest(settings); } public IResponse Cancelation(String xml) throws AuthException, GeneralException, IOException { - CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), xml, getProxyHost(), getProxyPort()); + CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), xml, false, getProxyHost(), getProxyPort()); CancelationRequest req = new CancelationRequest(); return req.sendRequestXml(settings, true); } public IResponse Cancelation(String uuid, String password, String rfc, String b64Pfx, String motivo, String folioSustitucion) throws AuthException, GeneralException, IOException { - CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), uuid, password, rfc, b64Pfx, motivo, folioSustitucion, getProxyHost(), getProxyPort()); + CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), uuid, password, rfc, b64Pfx, motivo, folioSustitucion, false, getProxyHost(), getProxyPort()); CancelationRequest req = new CancelationRequest(); return req.sendRequestPfx(settings); } public IResponse Cancelation(String uuid, String rfc, String motivo, String folioSustitucion) throws AuthException, GeneralException, IOException { - CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), uuid, rfc, motivo, folioSustitucion, getProxyHost(), getProxyPort()); + CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), uuid, rfc, motivo, folioSustitucion, false, getProxyHost(), getProxyPort()); CancelationRequest req = new CancelationRequest(); return req.sendRequestUuid(settings); } diff --git a/src/main/java/Services/CancelationRetention/SWCancelationRetentionService.java b/src/main/java/Services/CancelationRetention/SWCancelationRetentionService.java new file mode 100644 index 00000000..8db62145 --- /dev/null +++ b/src/main/java/Services/CancelationRetention/SWCancelationRetentionService.java @@ -0,0 +1,94 @@ +package Services.CancelationRetention; + +import java.io.IOException; +import Exceptions.AuthException; +import Exceptions.GeneralException; +import Services.SWService; +import Utils.Requests.Cancelation.CancelationOptionsRequest; +import Utils.Requests.Cancelation.CancelationRequest; +import Utils.Responses.IResponse; + +/** + * Servicio para implementaci�n de cancelaci�n de retenciones. + */ +public class SWCancelationRetentionService extends SWService { + + public SWCancelationRetentionService(String user, String password, String URI) throws AuthException { + super(user, password, URI); + } + + public SWCancelationRetentionService(String token, String URI) { + super(token, URI); + } + + public SWCancelationRetentionService(String user, String password, String URI, String proxyHost, int proxyPort) + throws AuthException { + super(user, password, URI, proxyHost, proxyPort); + } + + public SWCancelationRetentionService(String token, String URI, String proxyHost, int proxyPort) { + super(token, URI, proxyHost, proxyPort); + } + + /** + * Realiza la cancelaci�n de retenciones utilizando el certificado CSD. + * + * @param uuid uuid factura. + * @param password password de llave privada. + * @param rfc rfc emisor. + * @param csd String base64 del certificado. + * @param key String base64 de llave privada. + * @param motivo motivo de cancelacion. + * @param folioSustitucion uuid factura que sustituye. + * @throws AuthException Si ocurre un error de autenticaci�n. + * @throws GeneralException Si ocurre un error general en el proceso. + * @throws IOException Si ocurre un error de entrada/salida. + * @return {@link IResponse} La respuesta del servicio de cancelaci�n. + */ + public IResponse Cancelation(String uuid, String password, String rfc, String b64Cer, String b64Key, String motivo, + String folioSustitucion) throws AuthException, GeneralException, IOException { + CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), uuid, password, rfc, + b64Cer, b64Key, motivo, folioSustitucion, true, getProxyHost(), getProxyPort()); + CancelationRequest req = new CancelationRequest(); + return req.sendRequest(settings); + } + + /** + * Realiza la cancelaci�n de retenciones mediante PFX. + * + * @param uuid uuid factura. + * @param password password de llave privada. + * @param rfc rfc emisor. + * @param b64Pfx El archivo PFX del contribuyente codificado en + * Base64. + * @param motivo motivo de cancelacion. + * @param folioSustitucion uuid factura que sustituye. + * @throws AuthException Si ocurre un error de autenticaci�n. + * @throws GeneralException Si ocurre un error general en el proceso. + * @throws IOException Si ocurre un error de entrada/salida. + * @return {@link IResponse} La respuesta del servicio de cancelaci�n. + */ + public IResponse Cancelation(String uuid, String password, String rfc, String b64Pfx, String motivo, + String folioSustitucion) throws AuthException, GeneralException, IOException { + CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), uuid, password, rfc, + b64Pfx, motivo, folioSustitucion, true, getProxyHost(), getProxyPort()); + CancelationRequest req = new CancelationRequest(); + return req.sendRequestPfx(settings); + } + + /** + * Realiza la cancelaci�n de retenciones mediante XML. + * + * @param xml String XML de cancelaci�n de retenciones. + * @throws AuthException Si ocurre un error de autenticaci�n. + * @throws GeneralException Si ocurre un error general en el proceso. + * @throws IOException Si ocurre un error de entrada/salida. + * @return {@link IResponse} respuesta del servicio de cancelaci�n. + */ + public IResponse Cancelation(String xml) throws AuthException, GeneralException, IOException { + CancelationOptionsRequest settings = new CancelationOptionsRequest(getToken(), getURI(), xml, true, getProxyHost(), + getProxyPort()); + CancelationRequest req = new CancelationRequest(); + return req.sendRequestXml(settings, true); + } +} \ No newline at end of file diff --git a/src/main/java/Services/StampRetention/SWStampRetentionService.java b/src/main/java/Services/StampRetention/SWStampRetentionService.java new file mode 100644 index 00000000..16b994cb --- /dev/null +++ b/src/main/java/Services/StampRetention/SWStampRetentionService.java @@ -0,0 +1,128 @@ +package Services.StampRetention; +import java.io.IOException; +import java.nio.charset.Charset; + +import Exceptions.AuthException; +import Exceptions.GeneralException; +import Services.SWService; +import Utils.Requests.StampRetention.StampRetentionOptionsRequest; +import Utils.Requests.StampRetention.StampRetentionRequest; +import Utils.Responses.IResponse; + +public class SWStampRetentionService extends SWService { + + public SWStampRetentionService(String user, String password, String URI) throws AuthException { + super(user, password, URI); + } + + public SWStampRetentionService(String token, String URI) { + super(token, URI); + } + + public SWStampRetentionService(String user, String password, String URI, String proxyHost, int proxyPort) + throws AuthException { + super(user, password, URI, proxyHost, proxyPort); + } + + public SWStampRetentionService(String token, String URI, String proxyHost, int proxyPort) { + super(token, URI, proxyHost, proxyPort); + } + + /** + * Servicio para timbrar un CFDI de retenciones en formato XML. + * + * Realiza el timbrado de un CFDI de retenciones en formato XML. + * + * @param xml String del CFDI en formato XML. + * @param version Versión del servicio. + * @return Respuesta del servicio. + * @throws AuthException Excepción de autenticación. + * @throws GeneralException Excepción general. + * @throws IOException Excepción de entrada/salida. + */ + public IResponse StampRetention(String xml, String version) throws AuthException, GeneralException, IOException{ + StampRetentionOptionsRequest settings = new StampRetentionOptionsRequest(getToken(), getURI(), xml, version, getProxyHost(), + getProxyPort()); + StampRetentionRequest req = new StampRetentionRequest(); + return req.sendRequest(settings); + } + + /** + * Servicio para timbrar un CFDI de retenciones en formato XML o Base64 dependiendo de la + * bandera isb64. + * + * Realiza el timbrado de un CFDI de retenciones en formato XML. + * + * @param xml String del CFDI en formato XML. + * @param version Versión del servicio. + * @param isb64 Bandera que indica si el XML está en formato Base64. + * @return Respuesta del servicio. + * @throws AuthException Excepción de autenticación. + * @throws GeneralException Excepción general. + * @throws IOException Excepción de entrada/salida. + */ + public IResponse StampRetention(String xml, String version, boolean isb64) throws AuthException, GeneralException, IOException{ + if (isb64){ + StampRetentionOptionsRequest settings = new StampRetentionOptionsRequest(getToken(), getURI(), xml, version, isb64, getProxyHost(), + getProxyPort()); + StampRetentionRequest req = new StampRetentionRequest(); + return req.sendRequest(settings); + } + else{ + StampRetentionOptionsRequest settings = new StampRetentionOptionsRequest(getToken(), getURI(), xml, version, getProxyHost(), + getProxyPort()); + StampRetentionRequest req = new StampRetentionRequest(); + return req.sendRequest(settings); + } + } + + /** + * Servicio para timbrar un CFDI de retenciones en formato XML a partir de un archivo byte[] + * + * Realiza el timbrado de un CFDI de retenciones en formato XML. + * + * @param xmlFile Arreglo de bytes que representa el archivo XML o Base64 del CFDI de retención. + * @param version Versión del servicio. + * @return Respuesta del servicio. + * @throws AuthException Excepción de autenticación. + * @throws GeneralException Excepción general. + * @throws IOException Excepción de entrada/salida. + */ + public IResponse StampRetention(byte[] xmlFile, String version) throws AuthException, GeneralException, IOException{ + String xmlProcess = new String(xmlFile, Charset.forName("UTF-8")); + StampRetentionOptionsRequest settings = new StampRetentionOptionsRequest(getToken(), getURI(), xmlProcess, version, getProxyHost(), + getProxyPort()); + StampRetentionRequest req = new StampRetentionRequest(); + return req.sendRequest(settings); + } + + /** + * Servicio para timbrar un CFDI de retenciones en formato XML a partir de un archivo byte[] o + * Base64. + * + * Realiza el timbrado de un CFDI de retenciones en formato XML. + * + * @param xmlFile Arreglo de bytes que representa el archivo XML o Base64 del CFDI de retención. + * @param version Versión del servicio. + * @param isb64 Bandera que indica si el XML está en formato Base64. + * @return Respuesta del servicio. + * @throws AuthException Excepción de autenticación. + * @throws GeneralException Excepción general. + * @throws IOException Excepción de entrada/salida. + */ + public IResponse StampRetention(byte[] xmlFile, String version, boolean isb64) throws AuthException, GeneralException, IOException{ + String xmlProcess = new String(xmlFile, Charset.forName("UTF-8")); + if (isb64){ + StampRetentionOptionsRequest settings = new StampRetentionOptionsRequest(getToken(), getURI(), xmlProcess, version, isb64, getProxyHost(), + getProxyPort()); + StampRetentionRequest req = new StampRetentionRequest(); + return req.sendRequest(settings); + } + else{ + StampRetentionOptionsRequest settings = new StampRetentionOptionsRequest(getToken(), getURI(), xmlProcess, version, getProxyHost(), + getProxyPort()); + StampRetentionRequest req = new StampRetentionRequest(); + return req.sendRequest(settings); + } + } +} diff --git a/src/main/java/Utils/Constants.java b/src/main/java/Utils/Constants.java index e97f9b68..54706c95 100644 --- a/src/main/java/Utils/Constants.java +++ b/src/main/java/Utils/Constants.java @@ -4,6 +4,7 @@ public class Constants { public static String BASE_PATH = "https://services.test.sw.com.mx"; public static String AUTH_PATH_V2 = "/v2/security/authenticate"; public static String STAMP_PATH = "/cfdi33/stamp/"; + public static String STAMP_RETENTION_PATH = "/retencion/stamp/"; public static String STAMP_ZIP_PATH = "/cfdi/stamp/v1/zip/"; public static String STAMP_V2_PATH = "/cfdi33/v2/stamp/"; public static String ISSUE_JSON_PATH = "/v3/cfdi33/issue/json/"; @@ -15,6 +16,9 @@ public class Constants { public static String CANCELATION_CSD_PATH = "/cfdi33/cancel/csd"; public static String CANCELATION_XML_PATH = "/cfdi33/cancel/xml"; public static String CANCELATION_PFX_PATH = "/cfdi33/cancel/pfx"; + public static String CANCELATION_RET_CSD_PATH = "/retencion/cancel/csd"; + public static String CANCELATION_RET_XML_PATH = "/retencion/cancel/xml"; + public static String CANCELATION_RET_PFX_PATH = "/retencion/cancel/pfx"; public static String CANCELATION_UUID_PATH = "/cfdi33/cancel/"; public static String BALANCE_ACCOUNT_PATH = "/account/balance/"; public static String BALANCE_ACCOUNT_MANAGEMENT_PATH = "/management/api/balance/"; diff --git a/src/main/java/Utils/Helpers/BuildRetentionResponseV3.java b/src/main/java/Utils/Helpers/BuildRetentionResponseV3.java new file mode 100644 index 00000000..5033b1d3 --- /dev/null +++ b/src/main/java/Utils/Helpers/BuildRetentionResponseV3.java @@ -0,0 +1,32 @@ +package Utils.Helpers; +import org.json.JSONObject; +import Utils.Responses.IResponse; +import Utils.Responses.StampRetention.SuccessV3Response; + +public class BuildRetentionResponseV3 extends ResponseStamp { + public IResponse getResponse(){ + if(!response.trim().isEmpty() && status < 500){ + JSONObject body = new JSONObject(response); + if(status == 200){ + JSONObject data = body.getJSONObject("data"); + return new SuccessV3Response(status, body.getString("status"), data.getString("retencion"), "OK", "OK"); + } + else{ + String messageDetail = ""; + if (!body.isNull("messageDetail")){ + messageDetail = body.getString("messageDetail"); + } + if(body.getString("message").equals("307. El comprobante contiene un timbre previo.")) { + if(!body.isNull("data")) { + JSONObject data = body.getJSONObject("data"); + return new SuccessV3Response(status, body.getString("status"), data.getString("retencion"), body.getString("message"), messageDetail); + } + } + return new SuccessV3Response(status, body.getString("status"), "", body.getString("message"), messageDetail); + } + } + else { + return new SuccessV3Response(status, "error", "","Error con código "+status+": "+reason.getReasonPhrase(), response); + } + } +} \ No newline at end of file diff --git a/src/main/java/Utils/Helpers/ResponseStampRetentionBuilder.java b/src/main/java/Utils/Helpers/ResponseStampRetentionBuilder.java new file mode 100644 index 00000000..012380af --- /dev/null +++ b/src/main/java/Utils/Helpers/ResponseStampRetentionBuilder.java @@ -0,0 +1,10 @@ +package Utils.Helpers; + +public class ResponseStampRetentionBuilder { + public static ResponseStamp StampedRetention(char version){ + switch(version){ + case '3': return new BuildRetentionResponseV3(); + default: return null; + } + } +} diff --git a/src/main/java/Utils/Requests/BalanceAccount/BalanceAcctOptionsRequest.java b/src/main/java/Utils/Requests/BalanceAccount/BalanceAcctOptionsRequest.java index 5f22072f..c46348cb 100644 --- a/src/main/java/Utils/Requests/BalanceAccount/BalanceAcctOptionsRequest.java +++ b/src/main/java/Utils/Requests/BalanceAccount/BalanceAcctOptionsRequest.java @@ -1,9 +1,10 @@ package Utils.Requests.BalanceAccount; import java.util.UUID; + import Utils.Constants; -import Utils.Requests.IRequest; import Utils.Helpers.EnumBalanceStamp.AccountBalanceAction; +import Utils.Requests.IRequest; /** * La clase BalanceAcctOptionsRequest representa las opciones de solicitud para @@ -33,6 +34,16 @@ public static BalanceAcctOptionsRequest sendRequest(String token, String URIAPI, proxyHost, proxyPort); } + /** + * Método estático para crear una solicitud de obtención de saldo de cuenta de un usuario específico. + */ + public static BalanceAcctOptionsRequest getUserBalanceRequest(String token, String URIAPI, UUID idUser, + String proxyHost, int proxyPort) { + return new BalanceAcctOptionsRequest(token, + URIAPI + Constants.BALANCE_ACCOUNTV2_MANAGEMENT_PATH + "dealers/balance/users/" + idUser, + proxyHost, proxyPort); + } + /** * Método estático para crear una solicitud de movimiento de saldo de cuenta. */ diff --git a/src/main/java/Utils/Requests/BalanceAccount/BalanceAcctRequest.java b/src/main/java/Utils/Requests/BalanceAccount/BalanceAcctRequest.java index 2232a6d5..154f8adc 100644 --- a/src/main/java/Utils/Requests/BalanceAccount/BalanceAcctRequest.java +++ b/src/main/java/Utils/Requests/BalanceAccount/BalanceAcctRequest.java @@ -1,13 +1,8 @@ package Utils.Requests.BalanceAccount; -import Exceptions.AuthException; -import Exceptions.GeneralException; -import Utils.Helpers.RequestHelper; -import Utils.Requests.IRequest; -import Utils.Responses.IResponse; -import Utils.Responses.BalanceAccount.BalanceAcctResponse; import java.io.IOException; import java.net.URI; + import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; @@ -20,6 +15,13 @@ import org.json.JSONException; import org.json.JSONObject; +import Exceptions.AuthException; +import Exceptions.GeneralException; +import Utils.Helpers.RequestHelper; +import Utils.Requests.IRequest; +import Utils.Responses.BalanceAccount.BalanceAcctResponse; +import Utils.Responses.IResponse; + /** * La clase BalanceAcctRequest maneja las solicitudes relacionadas con las * operaciones de saldo de cuentas. @@ -36,6 +38,11 @@ public static IResponse createBalanceStampRequest(IRequest request, String actio return new BalanceAcctRequest().balanceAcctStampRequest(request, action, stamps, comment); } + public static IResponse createUserBalanceRequest(IRequest request) + throws GeneralException, AuthException, IOException { + return new BalanceAcctRequest().balanceAcctRequest(request); + } + private IResponse balanceAcctRequest(IRequest request) throws GeneralException, AuthException, IOException { try { CloseableHttpClient client = HttpClients.createDefault(); diff --git a/src/main/java/Utils/Requests/Cancelation/CancelationOptionsRequest.java b/src/main/java/Utils/Requests/Cancelation/CancelationOptionsRequest.java index 4678f041..2c1eb0e4 100644 --- a/src/main/java/Utils/Requests/Cancelation/CancelationOptionsRequest.java +++ b/src/main/java/Utils/Requests/Cancelation/CancelationOptionsRequest.java @@ -13,10 +13,11 @@ public class CancelationOptionsRequest extends IRequest{ private String xml; private String motivo; private String folioSustitucion; + private boolean isRetention; - public CancelationOptionsRequest(String token, String URI, String uuid, String password, String rfc, String b64Cer, String b64Key, String motivo, String folioSustitucion, String proxyHost, int proxyPort) { - super(token, URI+ Constants.CANCELATION_CSD_PATH, proxyHost, proxyPort); + public CancelationOptionsRequest(String token, String URI, String uuid, String password, String rfc, String b64Cer, String b64Key, String motivo, String folioSustitucion, boolean isRetention, String proxyHost, int proxyPort) { + super(token, URI+ (isRetention ? Constants.CANCELATION_RET_CSD_PATH : Constants.CANCELATION_CSD_PATH), proxyHost, proxyPort); this.uuid = uuid; this.password = password; this.rfc = rfc; @@ -26,8 +27,8 @@ public CancelationOptionsRequest(String token, String URI, String uuid, String p this.folioSustitucion = folioSustitucion; } - public CancelationOptionsRequest(String token, String URI, String uuid, String password, String rfc, String b64Pfx, String motivo, String folioSustitucion, String proxyHost, int proxyPort) { - super(token, URI+ Constants.CANCELATION_PFX_PATH, proxyHost, proxyPort); + public CancelationOptionsRequest(String token, String URI, String uuid, String password, String rfc, String b64Pfx, String motivo, String folioSustitucion, boolean isRetention, String proxyHost, int proxyPort) { + super(token, URI+ (isRetention ? Constants.CANCELATION_RET_PFX_PATH : Constants.CANCELATION_PFX_PATH), proxyHost, proxyPort); this.uuid = uuid; this.password = password; this.rfc = rfc; @@ -36,11 +37,11 @@ public CancelationOptionsRequest(String token, String URI, String uuid, String p this.folioSustitucion = folioSustitucion; } - public CancelationOptionsRequest(String token, String URI, String xml, String proxyHost, int proxyPort) { - super(token, URI+ Constants.CANCELATION_XML_PATH, proxyHost, proxyPort); + public CancelationOptionsRequest(String token, String URI, String xml, boolean isRetention, String proxyHost, int proxyPort) { + super(token, URI+ (isRetention ? Constants.CANCELATION_RET_XML_PATH : Constants.CANCELATION_XML_PATH), proxyHost, proxyPort); this.xml = xml; } - public CancelationOptionsRequest(String token, String URI, String uuid, String rfc, String motivo, String folioSustitucion, String proxyHost, int proxyPort) { + public CancelationOptionsRequest(String token, String URI, String uuid, String rfc, String motivo, String folioSustitucion, boolean isRetention, String proxyHost, int proxyPort) { //super(token, URI + Constants.CANCELATION_UUID_PATH + rfc + "/" + uuid + "/" + motivo + "/" + foliosustitucion, proxyHost, proxyPort); super(token, URI + Constants.CANCELATION_UUID_PATH + String.format("%s/%s/%s/%s", rfc, uuid, motivo, folioSustitucion ), proxyHost, proxyPort); this.uuid = uuid; @@ -81,4 +82,7 @@ public String getMotivo() { public String getFolioSustitucion() { return folioSustitucion; } + public boolean getRetention() { + return isRetention; + } } diff --git a/src/main/java/Utils/Requests/StampRetention/StampRetentionOptionsRequest.java b/src/main/java/Utils/Requests/StampRetention/StampRetentionOptionsRequest.java new file mode 100644 index 00000000..fc9075f9 --- /dev/null +++ b/src/main/java/Utils/Requests/StampRetention/StampRetentionOptionsRequest.java @@ -0,0 +1,21 @@ +package Utils.Requests.StampRetention; +import Utils.Constants; +import Utils.Requests.IRequest; + +public class StampRetentionOptionsRequest extends IRequest{ + private String xml; + + public StampRetentionOptionsRequest(String token, String URI, String xml, String version, String proxyHost, int proxyPort){ + super(token, URI + Constants.STAMP_RETENTION_PATH + version, xml, version, proxyHost, proxyPort); + this.xml = xml; + } + + public StampRetentionOptionsRequest(String token, String URI, String xml, String version, boolean isb64, String proxyHost, int proxyPort){ + super(token, URI + Constants.STAMP_RETENTION_PATH + version + "/b64", xml, version, proxyHost, proxyPort); + this.xml = xml; + } + + public String getXml() { + return xml; + } +} diff --git a/src/main/java/Utils/Requests/StampRetention/StampRetentionRequest.java b/src/main/java/Utils/Requests/StampRetention/StampRetentionRequest.java new file mode 100644 index 00000000..35b8b8b1 --- /dev/null +++ b/src/main/java/Utils/Requests/StampRetention/StampRetentionRequest.java @@ -0,0 +1,67 @@ +package Utils.Requests.StampRetention; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.UUID; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MIME; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.json.JSONException; +import Exceptions.AuthException; +import Exceptions.GeneralException; +import Utils.Helpers.RequestHelper; +import Utils.Helpers.ResponseStamp; +import Utils.Helpers.ResponseStampRetentionBuilder; +import Utils.Requests.IRequest; +import Utils.Requests.IRequestor; +import Utils.Responses.IResponse; + +public class StampRetentionRequest implements IRequestor { + + public IResponse sendRequest(IRequest request) throws GeneralException, AuthException { + try { + String xmlStr = ((StampRetentionOptionsRequest) request).getXml(); + String boundary = UUID.randomUUID().toString(); + String raw = "--" + boundary + + "\r\nContent-Disposition: form-data; name=xml; filename=xml\r\nContent-Type: application/xml\r\n\r\n" + + xmlStr + "\r\n--" + boundary + "--"; + CloseableHttpClient client = HttpClients.createDefault(); + HttpPost httppost = new HttpPost(request.URI); + RequestHelper.setTimeOut(request.options, raw.length()); + RequestHelper.setProxy(request.options, request.proxyHost, request.proxyPort); + httppost.setConfig(request.options.build()); + httppost.setHeader("Authorization", "bearer " + request.Token); + httppost.addHeader("Content-Type", "multipart/form-data; boundary=" + boundary); + httppost.addHeader("Content-Disposition", "form-data; name=xml; filename=xml"); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + Charset chars = Charset.forName("UTF-8"); + builder.setCharset(chars); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + builder.addTextBody("xml", raw, ContentType.create("text/plain", MIME.UTF8_CHARSET)); + httppost.setEntity(builder.build()); + CloseableHttpResponse responseB = client.execute(httppost); + HttpEntity entity = responseB.getEntity(); + String responseString = EntityUtils.toString(entity, "UTF-8"); + int status = responseB.getStatusLine().getStatusCode(); + client.close(); + responseB.close(); + ResponseStamp R = ResponseStampRetentionBuilder.StampedRetention(request.version.charAt(request.version.length() - 1)); + R.setReason(responseB.getStatusLine()); + R.setResponse(responseString); + R.setStatus(status); + return R.getResponse(); + + } catch (JSONException e) { + throw new GeneralException(500, e.getMessage()); + } catch (IOException e) { + e.printStackTrace(); + throw new GeneralException(500, e.getMessage()); + } + } +} diff --git a/src/main/java/Utils/Responses/StampRetention/SuccessV3Response.java b/src/main/java/Utils/Responses/StampRetention/SuccessV3Response.java new file mode 100644 index 00000000..c16396c6 --- /dev/null +++ b/src/main/java/Utils/Responses/StampRetention/SuccessV3Response.java @@ -0,0 +1,10 @@ +package Utils.Responses.StampRetention; +import Utils.Responses.IResponse; + +public class SuccessV3Response extends IResponse{ + public String retencion; + public SuccessV3Response(int httpStatusCode, String status, String _retencion, String msg, String msgDetail) { + super(httpStatusCode, status, msg, msgDetail); + this.retencion = _retencion; + } +} diff --git a/src/test/java/Tests/BalanceAccount/SWBalanceAccountServiceTest.java b/src/test/java/Tests/BalanceAccount/SWBalanceAccountServiceTest.java index cf36b830..4f3698b0 100644 --- a/src/test/java/Tests/BalanceAccount/SWBalanceAccountServiceTest.java +++ b/src/test/java/Tests/BalanceAccount/SWBalanceAccountServiceTest.java @@ -1,16 +1,17 @@ package Tests.BalanceAccount; -import Exceptions.AuthException; -import Exceptions.GeneralException; -import Services.BalanceAccount.SWBalanceAccountService; -import Tests.Utils; -import Utils.Responses.BalanceAccount.BalanceAcctResponse; import java.io.IOException; import java.util.UUID; import org.junit.Assert; import org.junit.Test; +import Exceptions.AuthException; +import Exceptions.GeneralException; +import Services.BalanceAccount.SWBalanceAccountService; +import Tests.Utils; +import Utils.Responses.BalanceAccount.BalanceAcctResponse; + public class SWBalanceAccountServiceTest { @Test public void testBalanceAccountService() throws AuthException, GeneralException, IOException { @@ -116,6 +117,113 @@ public void testBalanceAccountService_incorrectToken() { } } + @Test + public void testGetUserBalanceAccount() throws AuthException, GeneralException, IOException { + SWBalanceAccountService app = new SWBalanceAccountService(Utils.userSW, Utils.passwordSW, Utils.urlSW, + Utils.urlApiSW); + BalanceAcctResponse response = (BalanceAcctResponse) app + .GetUserBalanceAccount(UUID.fromString("828f19b1-77dc-48bc-9cfa-d48b5cf7e30c")); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.idUserBalance); + System.out.println(response.idUser); + System.out.println(response.stampsBalance); + System.out.println(response.stampsUsed); + System.out.println(response.expirationDate); + System.out.println(response.isUnlimited); + System.out.println(response.stampsAssigned); + if (response.lastTransaction != null) { + System.out.println("Folio: " + response.lastTransaction.folio); + System.out.println("ID Usuario: " + response.lastTransaction.idUser); + System.out.println("ID Usuario Receptor: " + response.lastTransaction.idUserReceiver); + System.out.println("Nombre Receptor: " + response.lastTransaction.nameReceiver); + System.out.println("Stamps In: " + response.lastTransaction.stampsIn); + System.out.println("Stamps Out: " + + (response.lastTransaction.stampsOut != null ? response.lastTransaction.stampsOut : "null")); + System.out.println("Stamps Current: " + response.lastTransaction.stampsCurrent); + System.out.println("Comentario: " + response.lastTransaction.comment); + System.out.println("Fecha: " + response.lastTransaction.date); + System.out.println("Email Enviado: " + response.lastTransaction.isEmailSent); + } else { + System.out.println("No hay transacción disponible."); + } + String expect_status = "success"; + Assert.assertTrue(expect_status.equalsIgnoreCase(response.Status)); + } + + @Test + public void testGetUserBalanceAccount_authToken() throws Exception { + SWBalanceAccountService app = new SWBalanceAccountService(Utils.tokenSW, Utils.urlApiSW); + BalanceAcctResponse response = (BalanceAcctResponse) app + .GetUserBalanceAccount(UUID.fromString("828f19b1-77dc-48bc-9cfa-d48b5cf7e30c")); + + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.idUserBalance); + System.out.println(response.idUser); + System.out.println(response.stampsBalance); + System.out.println(response.stampsUsed); + System.out.println(response.expirationDate); + System.out.println(response.isUnlimited); + System.out.println(response.stampsAssigned); + if (response.lastTransaction != null) { + System.out.println("Folio: " + response.lastTransaction.folio); + System.out.println("ID Usuario: " + response.lastTransaction.idUser); + System.out.println("ID Usuario Receptor: " + response.lastTransaction.idUserReceiver); + System.out.println("Nombre Receptor: " + response.lastTransaction.nameReceiver); + System.out.println("Stamps In: " + response.lastTransaction.stampsIn); + System.out.println("Stamps Out: " + + (response.lastTransaction.stampsOut != null ? response.lastTransaction.stampsOut : "null")); + System.out.println("Stamps Current: " + response.lastTransaction.stampsCurrent); + System.out.println("Comentario: " + response.lastTransaction.comment); + System.out.println("Fecha: " + response.lastTransaction.date); + System.out.println("Email Enviado: " + response.lastTransaction.isEmailSent); + } else { + System.out.println("No hay transacción disponible."); + } + String expect_status = "success"; + Assert.assertTrue(expect_status.equalsIgnoreCase(response.Status)); + } + + @Test + public void testGetUserBalanceAccount_incorrectToken() { + try { + SWBalanceAccountService app = new SWBalanceAccountService("wrong token", Utils.urlApiSW); + BalanceAcctResponse response = (BalanceAcctResponse) app + .GetUserBalanceAccount(UUID.fromString("828f19b1-77dc-48bc-9cfa-d48b5cf7e30c")); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.idUserBalance); + System.out.println(response.idUser); + System.out.println(response.stampsBalance); + System.out.println(response.stampsUsed); + System.out.println(response.expirationDate); + System.out.println(response.isUnlimited); + System.out.println(response.stampsAssigned); + if (response.lastTransaction != null) { + System.out.println("Folio: " + response.lastTransaction.folio); + System.out.println("ID Usuario: " + response.lastTransaction.idUser); + System.out.println("ID Usuario Receptor: " + response.lastTransaction.idUserReceiver); + System.out.println("Nombre Receptor: " + response.lastTransaction.nameReceiver); + System.out.println("Stamps In: " + response.lastTransaction.stampsIn); + System.out.println("Stamps Out: " + + (response.lastTransaction.stampsOut != null ? response.lastTransaction.stampsOut : "null")); + System.out.println("Stamps Current: " + response.lastTransaction.stampsCurrent); + System.out.println("Comentario: " + response.lastTransaction.comment); + System.out.println("Fecha: " + response.lastTransaction.date); + System.out.println("Email Enviado: " + response.lastTransaction.isEmailSent); + } else { + System.out.println("No hay transacción disponible."); + } + } catch (AuthException e) { + Assert.fail(); + } catch (GeneralException e) { + Assert.fail(); + } catch (IOException e) { + Assert.fail(); + } + } + @Test public void testAddStampsByToken_Sucess() { diff --git a/src/test/java/Tests/CancelationRetention/SWCancelationRetetentionTest.java b/src/test/java/Tests/CancelationRetention/SWCancelationRetetentionTest.java new file mode 100644 index 00000000..99566c23 --- /dev/null +++ b/src/test/java/Tests/CancelationRetention/SWCancelationRetetentionTest.java @@ -0,0 +1,134 @@ +package Tests.CancelationRetention; + +import Services.CancelationRetention.SWCancelationRetentionService; +import Tests.Utils; +import Utils.Responses.Cancelation.CancelationResponse; +import org.junit.Assert; +import org.junit.Test; + +public class SWCancelationRetetentionTest { + @Test + public void testCancelationRetentionCSD_authUser() throws Exception { + SWCancelationRetentionService app = new SWCancelationRetentionService(Utils.userSW, Utils.passwordSW, + Utils.urlSW); + CancelationResponse response = null; + response = (CancelationResponse) app.Cancelation(Utils.uuidRetencion, Utils.passwordCsd, Utils.rfc, + Utils.cerb64, Utils.keyb64, "01", + Utils.foliosustitucion); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.acuse); + System.out.println(response.uuid); + System.out.println(response.uuidStatusCode); + String expect_status = "success"; + Assert.assertTrue(expect_status.equalsIgnoreCase(response.Status)); + } + + @Test + public void testCancelationServiceCSD_authToken() throws Exception { + SWCancelationRetentionService app = new SWCancelationRetentionService(Utils.tokenSW, Utils.urlSW); + CancelationResponse response = null; + response = (CancelationResponse) app.Cancelation(Utils.uuid, Utils.passwordCsd, Utils.rfc, Utils.cerb64, + Utils.keyb64, "01", + Utils.foliosustitucion); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.acuse); + System.out.println(response.uuid); + System.out.println(response.uuidStatusCode); + String expect_status = "success"; + Assert.assertTrue(expect_status.equalsIgnoreCase(response.Status)); + } + + @Test + public void testCancelationServiceCSD_incorrectParams() throws Exception { + SWCancelationRetentionService app = new SWCancelationRetentionService(Utils.tokenSW, Utils.urlSW); + CancelationResponse response = null; + response = (CancelationResponse) app.Cancelation("123456", "123456", "123456", "123456", "123456", "123456", + "123456"); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.message); + System.out.println(response.messageDetail); + String expect_status = "error"; + Assert.assertTrue(expect_status.equalsIgnoreCase(response.Status)); + } + + @Test + public void testCancelationRetentionXML_validXML() throws Exception { + SWCancelationRetentionService app = new SWCancelationRetentionService(Utils.userSW, Utils.passwordSW, + Utils.urlSW); + CancelationResponse response = null; + response = (CancelationResponse) app.Cancelation(Utils.cancelacionXmlRet); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.message); + System.out.println(response.messageDetail); + System.out.println(response.acuse); + System.out.println(response.uuid); + System.out.println(response.uuidStatusCode); + String expect_status = "success"; + Assert.assertTrue(expect_status.equalsIgnoreCase(response.Status)); + } + + @Test + public void testCancelationServiceXML_invalidXML() throws Exception { + SWCancelationRetentionService app = new SWCancelationRetentionService(Utils.userSW, Utils.passwordSW, + Utils.urlSW); + CancelationResponse response = null; + response = (CancelationResponse) app.Cancelation("wrong xml"); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.message); + System.out.println(response.messageDetail); + String expect_status = "error"; + Assert.assertTrue(expect_status.equalsIgnoreCase(response.Status)); + Assert.assertTrue(response.message.contains("CASD - Acuse")); + } + + @Test + public void testCancelationServicePfx_authToken() throws Exception { + SWCancelationRetentionService app = new SWCancelationRetentionService(Utils.tokenSW, Utils.urlSW); + CancelationResponse response = null; + response = (CancelationResponse) app.Cancelation(Utils.uuidRetencion, Utils.passwordPfx, Utils.rfc, + Utils.pfxb64, "01", Utils.folioSustitucionRet); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.acuse); + System.out.println(response.uuid); + System.out.println(response.uuidStatusCode); + String expect_status = "success"; + Assert.assertTrue(expect_status.equalsIgnoreCase(response.Status)); + } + + @Test + public void testCancelationServicePfx_incorrectToken() throws Exception { + SWCancelationRetentionService app = new SWCancelationRetentionService("wrong token", Utils.urlSW); + CancelationResponse response = null; + response = (CancelationResponse) app.Cancelation(Utils.uuid, Utils.passwordPfx, Utils.rfc, Utils.pfxb64, "01", + Utils.foliosustitucion); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.message); + System.out.println(response.messageDetail); + Assert.assertTrue(response.HttpStatusCode == 401); + } + + @Test + public void testCancelationServicePfx_emptyUserParams() throws Exception { + try { + SWCancelationRetentionService app = new SWCancelationRetentionService("", "", ""); + CancelationResponse response = null; + response = (CancelationResponse) app.Cancelation(Utils.uuid, Utils.passwordPfx, Utils.rfc, Utils.pfxb64, + "01", Utils.foliosustitucion); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.message); + System.out.println(response.messageDetail); + } catch (Exception e) { + System.out.println("Something bad happened"); + System.out.println(e.getMessage()); + Assert.assertNotNull("Something bad happened", e); + } + } +} diff --git a/src/test/java/Tests/Pdf/SWPdfServiceTest.java b/src/test/java/Tests/Pdf/SWPdfServiceTest.java index 9d61858f..238fe8ca 100644 --- a/src/test/java/Tests/Pdf/SWPdfServiceTest.java +++ b/src/test/java/Tests/Pdf/SWPdfServiceTest.java @@ -10,12 +10,9 @@ import Utils.Responses.Pdf.PdfResponse; import Utils.Responses.Stamp.SuccessV3Response; -import java.io.Console; import java.io.IOException; import java.util.HashMap; -import java.util.Map; -import org.apache.commons.logging.Log; import org.junit.Assert; import org.junit.Ignore; import org.junit.Rule; @@ -244,6 +241,7 @@ public void GeneratePdf_Nomina12__Success() throws AuthException, GeneralExcepti Assert.assertTrue(false); } } + @Ignore @Test public void GeneratePdf_CP20__Success() throws AuthException, GeneralException, IOException { @@ -273,19 +271,21 @@ public void GeneratePdf_CP20__Success() throws AuthException, GeneralException, Assert.assertTrue(false); } } + @Test public void RegeneratePdf_Success() throws GeneralException, AuthException, IOException { SWPdfService pdf = new SWPdfService(Utils.tokenSW, Utils.urlApiSW); - PdfResponse response = (PdfResponse)pdf.RegeneratePdf("da3b7571-1cfd-4fb7-8bcd-123ef1cba77f"); + PdfResponse response = (PdfResponse)pdf.RegeneratePdf("10db53c4-f816-4c9f-a3eb-2c5b336f828d"); Assert.assertNotNull(response); System.out.println(response.message); Assert.assertTrue(response.Status.equals("success")); Assert.assertTrue(!response.message.isEmpty()); } + @Test public void RegeneratePdf_Auth_Success() throws AuthException, GeneralException, IOException{ SWPdfService pdf = new SWPdfService(Utils.userSW, Utils.passwordSW, Utils.urlApiSW, Utils.urlSW); - PdfResponse response = (PdfResponse)pdf.RegeneratePdf("da3b7571-1cfd-4fb7-8bcd-123ef1cba77f"); + PdfResponse response = (PdfResponse)pdf.RegeneratePdf("10db53c4-f816-4c9f-a3eb-2c5b336f828d"); Assert.assertNotNull(response); System.out.println(response.message); Assert.assertTrue(response.Status.equals("success")); diff --git a/src/test/java/Tests/StampRetention/SWStampRetentionTest.java b/src/test/java/Tests/StampRetention/SWStampRetentionTest.java new file mode 100644 index 00000000..ec4891f2 --- /dev/null +++ b/src/test/java/Tests/StampRetention/SWStampRetentionTest.java @@ -0,0 +1,40 @@ +package Tests.StampRetention; +import org.junit.Assert; +import org.junit.Test; +import Tests.Utils; +import Utils.Responses.StampRetention.SuccessV3Response; +import Services.StampRetention.SWStampRetentionService; + +public class SWStampRetentionTest { + + @Test + public void testStampRetention_XML_STRING_USER_PASSWORD_AUTH_V3() throws Exception { + SWStampRetentionService api = new SWStampRetentionService(Utils.userSW, Utils.passwordSW, Utils.urlSW); + SuccessV3Response response = null; + Utils ut = new Utils(); + String xml = ut.GetRetention(); + response = (SuccessV3Response) api.StampRetention(xml, "v3"); + Assert.assertNotNull(response.retencion); + Assert.assertTrue(response.Status.equals("success") || response.message.contains("307") || response.message.contains("401")); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.retencion); + System.out.println(response.message); + + } + + @Test + public void testStampRetention_XML_STRING_TOKEN_AUTH_V3() throws Exception { + SWStampRetentionService api = new SWStampRetentionService(Utils.tokenSW, Utils.urlSW); + SuccessV3Response response = null; + Utils ut = new Utils(); + String xml = ut.GetRetention(); + response = (SuccessV3Response) api.StampRetention(xml, "v3"); + System.out.println(response.Status); + System.out.println(response.HttpStatusCode); + System.out.println(response.retencion); + System.out.println(response.message); + Assert.assertTrue(response.Status.equals("success") || response.message.contains("307")|| response.message.contains("401")); + Assert.assertNotNull(response.retencion); + } +} diff --git a/src/test/java/Tests/StatusCfdi/StatusCfdiServiceTest.java b/src/test/java/Tests/StatusCfdi/StatusCfdiServiceTest.java index 91f4aa04..06a8cca0 100644 --- a/src/test/java/Tests/StatusCfdi/StatusCfdiServiceTest.java +++ b/src/test/java/Tests/StatusCfdi/StatusCfdiServiceTest.java @@ -3,11 +3,13 @@ import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import org.junit.jupiter.api.Disabled; + import Services.StatusCfdi.StatusCfdiService; import Utils.Responses.StatusCfdi.StatusCfdiResponse; public class StatusCfdiServiceTest { - @Test + @Disabled("Pendiente de revisión por cambios en el servicio SAT") public void testStatusCancelationService_Real() throws Exception { StatusCfdiService app = new StatusCfdiService("https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc", "http://tempuri.org/IConsultaCFDIService/Consulta"); StatusCfdiResponse response = null; diff --git a/src/test/java/Tests/Utils.java b/src/test/java/Tests/Utils.java index 53f97bc7..5f37f761 100644 --- a/src/test/java/Tests/Utils.java +++ b/src/test/java/Tests/Utils.java @@ -44,8 +44,11 @@ public class Utils { public static String rfc = "EKU9003173C9"; public static String cancelacionXml = loadResourceAsString("src/test/resources/Extras/CancelacionXML.xml"); public static String aceptacionRechazoXml = loadResourceAsString("src/test/resources/Extras/AceptacionRechazo.xml"); + public static String cancelacionXmlRet = loadResourceAsString("src/test/resources/Extras/CancelacionXMLRet.xml"); public static String uuid = "1f0110e0-6e11-49b9-b78c-5929cc3bfc01"; public static String foliosustitucion = "9509174a-f367-474e-bde7-4fb3347a9a22"; + public static String uuidRetencion = "42270add-4b74-401b-ad65-7db8ca6ca985"; + public static String folioSustitucionRet = "5c45cffb-63e3-48e1-9023-d9d0873ffd7a"; /** * Genera un CFDI especifico y lo sella en caso de indicarse. @@ -78,6 +81,24 @@ public String getCFDI(String fileName, boolean signed, String version, boolean i return cfdi; } + /** + * Obtiene un CFDI de retenciones. + * + * @param fileName + * @return String + */ + public String getCFDIRetention(String fileName) { + + String xml = ""; + try { + xml = new String(Files.readAllBytes(Paths.get(fileName)), "UTF-8"); + } catch (IOException e) { + e.printStackTrace(); + } + + return xml; + } + /** * Genera un CFDI especifico. * @@ -114,7 +135,7 @@ public String getJsonCFDI(String fileName, boolean isBase64) { } /** - * Genera un CFDI único y lo sella en caso de indicarse. + * Genera un CFDI especifico y lo sella en caso de indicarse. * * @param xml * @param signed @@ -242,6 +263,9 @@ public String JsonGenBasico(boolean isBase64) { public static boolean isValidB64(String value) { return Base64.isBase64(value.getBytes()); } + public String GetRetention() { + return getCFDIRetention("src/test/resources/Retenciones20/retencion20.xml"); + } public static String getCertificadoB64() { byte[] fileContent; diff --git a/src/test/resources/Extras/CancelacionXMLRet.xml b/src/test/resources/Extras/CancelacionXMLRet.xml new file mode 100644 index 00000000..49b66e0d --- /dev/null +++ b/src/test/resources/Extras/CancelacionXMLRet.xml @@ -0,0 +1 @@ +kd8XPP9pN8EnRyL6Wz0Tmty0gQA=FKRdq1JTqEraf2SCU2stEfvLpmdakOZpWji4t6GmquORUaN2QgAhxp12SGwKFr798HMQPIx7Ira24oVHn6MMpDNw6AF9ohDUtoVPCBedqjr7JJ5x3zSeQS4VDbT8p1SoTB9xn3ezr/dZKzs7z3c4ij69hNuNTlGrV2nfQXy3eQ2oPKdck7gLsAZ/zIfbSWuUXztcUa9fdAbCWGLvVFXeLte4vp8Lttuj8LHtOt7yOK5JXhn2PGa6uBSPnFWuw9rKQYZ+qCjSqjqEiN119SL46dX8PHhwKnumoIYapIFjwn9S4yqpN2KxTfQsonYjRSI62KfnmxIibO7e1RBifsWGVg==MIIFsDCCA5igAwIBAgIUMzAwMDEwMDAwMDA1MDAwMDM0MTYwDQYJKoZIhvcNAQELBQAwggErMQ8wDQYDVQQDDAZBQyBVQVQxLjAsBgNVBAoMJVNFUlZJQ0lPIERFIEFETUlOSVNUUkFDSU9OIFRSSUJVVEFSSUExGjAYBgNVBAsMEVNBVC1JRVMgQXV0aG9yaXR5MSgwJgYJKoZIhvcNAQkBFhlvc2Nhci5tYXJ0aW5lekBzYXQuZ29iLm14MR0wGwYDVQQJDBQzcmEgY2VycmFkYSBkZSBjYWxpejEOMAwGA1UEEQwFMDYzNzAxCzAJBgNVBAYTAk1YMRkwFwYDVQQIDBBDSVVEQUQgREUgTUVYSUNPMREwDwYDVQQHDAhDT1lPQUNBTjERMA8GA1UELRMIMi41LjQuNDUxJTAjBgkqhkiG9w0BCQITFnJlc3BvbnNhYmxlOiBBQ0RNQS1TQVQwHhcNMjMwNTE4MTE0MzUxWhcNMjcwNTE4MTE0MzUxWjCB1zEnMCUGA1UEAxMeRVNDVUVMQSBLRU1QRVIgVVJHQVRFIFNBIERFIENWMScwJQYDVQQpEx5FU0NVRUxBIEtFTVBFUiBVUkdBVEUgU0EgREUgQ1YxJzAlBgNVBAoTHkVTQ1VFTEEgS0VNUEVSIFVSR0FURSBTQSBERSBDVjElMCMGA1UELRMcRUtVOTAwMzE3M0M5IC8gVkFEQTgwMDkyN0RKMzEeMBwGA1UEBRMVIC8gVkFEQTgwMDkyN0hTUlNSTDA1MRMwEQYDVQQLEwpTdWN1cnNhbCAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtmecO6n2GS0zL025gbHGQVxznPDICoXzR2uUngz4DqxVUC/w9cE6FxSiXm2ap8Gcjg7wmcZfm85EBaxCx/0J2u5CqnhzIoGCdhBPuhWQnIh5TLgj/X6uNquwZkKChbNe9aeFirU/JbyN7Egia9oKH9KZUsodiM/pWAH00PCtoKJ9OBcSHMq8Rqa3KKoBcfkg1ZrgueffwRLws9yOcRWLb02sDOPzGIm/jEFicVYt2Hw1qdRE5xmTZ7AGG0UHs+unkGjpCVeJ+BEBn0JPLWVvDKHZAQMj6s5Bku35+d/MyATkpOPsGT/VTnsouxekDfikJD1f7A1ZpJbqDpkJnss3vQIDAQABox0wGzAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIGwDANBgkqhkiG9w0BAQsFAAOCAgEAFaUgj5PqgvJigNMgtrdXZnbPfVBbukAbW4OGnUhNrA7SRAAfv2BSGk16PI0nBOr7qF2mItmBnjgEwk+DTv8Zr7w5qp7vleC6dIsZFNJoa6ZndrE/f7KO1CYruLXr5gwEkIyGfJ9NwyIagvHHMszzyHiSZIA850fWtbqtythpAliJ2jF35M5pNS+YTkRB+T6L/c6m00ymN3q9lT1rB03YywxrLreRSFZOSrbwWfg34EJbHfbFXpCSVYdJRfiVdvHnewN0r5fUlPtR9stQHyuqewzdkyb5jTTw02D2cUfL57vlPStBj7SEi3uOWvLrsiDnnCIxRMYJ2UA2ktDKHk+zWnsDmaeleSzonv2CHW42yXYPCvWi88oE1DJNYLNkIjua7MxAnkNZbScNw01A6zbLsZ3y8G6eEYnxSTRfwjd8EP4kdiHNJftm7Z4iRU7HOVh79/lRWB+gd171s3d/mI9kte3MRy6V8MMEMCAnMboGpaooYwgAmwclI2XZCczNWXfhaWe0ZS5PmytD/GDpXzkX0oEgY9K/uYo5V77NdZbGAjmyi8cE2B2ogvyaN2XfIInrZPgEffJ4AB7kFA2mwesdLOCh0BLD9itmCve3A1FGR4+stO2ANUoiI3w3Tv2yQSg4bjeDlJ08lXaaFCLW2peEXMXjQUk7fmpb5MNuOUTW6BE=CN=AC UAT, O=SERVICIO DE ADMINISTRACION TRIBUTARIA, OU=SAT-IES Authority, E=oscar.martinez@sat.gob.mx, STREET=3ra cerrada de caliz, PostalCode=06370, C=MX, ST=CIUDAD DE MEXICO, L=COYOACAN, OID.2.5.4.45=2.5.4.45, OID.1.2.840.113549.1.9.2=responsable: ACDMA-SAT3330303031303030303030353030303033343136 \ No newline at end of file diff --git a/src/test/resources/Retenciones20/retencion20.xml b/src/test/resources/Retenciones20/retencion20.xml new file mode 100644 index 00000000..25716838 --- /dev/null +++ b/src/test/resources/Retenciones20/retencion20.xml @@ -0,0 +1 @@ + \ No newline at end of file