Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.0] - 2026-08-24

### Added

- Add draft support methods for retentions: `retentions.updateDraft(String id, Map<String, Object> data)`, `retentions.copyToDraft(String id)`, `retentions.stampDraft(String id)`, and `retentions.cancel(String id)`.

- Add `invoices.createZipRequest(Map<String, Object> data)` for `POST /invoices/zip-requests`.
- Add `invoices.listZipRequests(Map<String, ?> params)` for `GET /invoices/zip-requests`.
- Add `invoices.retrieveZipRequest(String id)` for `GET /invoices/zip-requests/{id}`.
- Add `invoices.downloadZipRequestStream(String id)` for streaming `GET /invoices/zip-requests/{id}/zip`.

### Changed

- Change `FacturapiException.getErrorCode()` to return strings, including converted legacy numeric codes.
- Change `InvoiceItem.property_tax_account` to a list of property tax account numbers.

## [1.3.0] - 2026-06-07

### Added
Expand Down
4 changes: 2 additions & 2 deletions README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ Maven:
<dependency>
<groupId>io.facturapi</groupId>
<artifactId>facturapi-java</artifactId>
<version>1.1.0</version>
<version>2.0.0</version>
</dependency>
```

Gradle:

```gradle
implementation("io.facturapi:facturapi-java:1.1.0")
implementation("io.facturapi:facturapi-java:2.0.0")
```

## Inicio rápido
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ Maven:
<dependency>
<groupId>io.facturapi</groupId>
<artifactId>facturapi-java</artifactId>
<version>1.1.0</version>
<version>2.0.0</version>
</dependency>
```

Gradle:

```gradle
implementation("io.facturapi:facturapi-java:1.1.0")
implementation("io.facturapi:facturapi-java:2.0.0")
```

## Quickstart
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>io.facturapi</groupId>
<artifactId>facturapi-java</artifactId>
<version>1.3.0</version>
<version>2.0.0</version>
<name>facturapi-java</name>
<description>Official Java SDK for Facturapi</description>
<url>https://github.com/facturapi/facturapi-java</url>
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/io/facturapi/FacturapiException.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

public class FacturapiException extends RuntimeException {
private final int statusCode;
private final Object errorCode;
private final String errorCode;
private final String errorPath;
private final String errorLocation;
private final JsonNode errors;
Expand All @@ -29,14 +29,14 @@ public FacturapiException(String message, Throwable cause) {
this.headers = Collections.emptyMap();
}

public FacturapiException(String message, int statusCode, Object errorCode, String errorPath) {
public FacturapiException(String message, int statusCode, String errorCode, String errorPath) {
this(message, statusCode, errorCode, errorPath, null, null, null, Collections.emptyMap());
}

public FacturapiException(
String message,
int statusCode,
Object errorCode,
String errorCode,
String errorPath,
String errorLocation,
JsonNode errors,
Expand All @@ -57,7 +57,7 @@ public int getStatusCode() {
return statusCode;
}

public Object getErrorCode() {
public String getErrorCode() {
return errorCode;
}

Expand Down
16 changes: 3 additions & 13 deletions src/main/java/io/facturapi/http/FacturapiHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ private static JsonNode firstDefined(JsonNode node, String... keys) {
private FacturapiException buildApiException(String bodyText, Response response) {
int statusCode = response.code();
int resolvedStatus = statusCode;
Object errorCode = null;
String errorCode = null;
String errorPath = null;
String errorLocation = null;
JsonNode errors = null;
Expand Down Expand Up @@ -245,18 +245,8 @@ private FacturapiException buildApiException(String bodyText, Response response)
}

JsonNode codeNode = firstDefined(root, "code");
if (codeNode != null && !codeNode.isNull()) {
if (codeNode.isTextual()) {
errorCode = codeNode.asText();
} else if (codeNode.isIntegralNumber()) {
errorCode = codeNode.intValue();
} else if (codeNode.isNumber()) {
errorCode = codeNode.numberValue();
} else if (codeNode.isBoolean()) {
errorCode = codeNode.asBoolean();
} else {
errorCode = codeNode.toString();
}
if (codeNode != null && (codeNode.isTextual() || codeNode.isNumber())) {
errorCode = codeNode.asText();
}

JsonNode pathNode = firstDefined(root, "path");
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/io/facturapi/models/InvoiceItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public class InvoiceItem {
private InvoiceItemThirdParty thirdParty;
private String complement;
private List<InvoiceItemPart> parts = new ArrayList<>();
private String propertyTaxAccount;
@JsonProperty("property_tax_account")
private List<String> propertyTaxAccounts = new ArrayList<>();

public Double getQuantity() { return quantity; }
public void setQuantity(Double quantity) { this.quantity = quantity; }
Expand All @@ -30,6 +31,8 @@ public class InvoiceItem {
public void setComplement(String complement) { this.complement = complement; }
public List<InvoiceItemPart> getParts() { return parts; }
public void setParts(List<InvoiceItemPart> parts) { this.parts = parts; }
public String getPropertyTaxAccount() { return propertyTaxAccount; }
public void setPropertyTaxAccount(String propertyTaxAccount) { this.propertyTaxAccount = propertyTaxAccount; }
@JsonProperty("property_tax_account")
public List<String> getPropertyTaxAccounts() { return propertyTaxAccounts; }
@JsonProperty("property_tax_account")
public void setPropertyTaxAccounts(List<String> propertyTaxAccounts) { this.propertyTaxAccounts = propertyTaxAccounts; }
}
57 changes: 57 additions & 0 deletions src/main/java/io/facturapi/resources/InvoicesResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,63 @@ public InputStream downloadZipStream(String id) {
return client.getStream("/invoices/" + id + "/zip");
}

/**
* Creates a ZIP request or returns the existing request for the same criteria.
*
* @param data ZIP request criteria.
* @return ZIP request data.
* @see <a href="https://docs.facturapi.io/api#tag/Invoices">API reference</a>
*/
public Map<String, Object> createZipRequest(Map<String, Object> data) {
return post(
"/invoices/zip-requests",
data,
null,
new TypeReference<Map<String, Object>>() {}
);
}

/**
* Gets a paginated list of ZIP requests.
*
* @param params Search and pagination parameters.
* @return Paginated ZIP request result.
* @see <a href="https://docs.facturapi.io/api#tag/Invoices">API reference</a>
*/
public SearchResult<Map<String, Object>> listZipRequests(Map<String, ?> params) {
return get(
"/invoices/zip-requests",
params,
new TypeReference<SearchResult<Map<String, Object>>>() {}
);
}

/**
* Gets a ZIP request by id.
*
* @param id ZIP request id.
* @return ZIP request data.
* @see <a href="https://docs.facturapi.io/api#tag/Invoices">API reference</a>
*/
public Map<String, Object> retrieveZipRequest(String id) {
return get(
"/invoices/zip-requests/" + id,
null,
new TypeReference<Map<String, Object>>() {}
);
}

/**
* Downloads the ZIP generated for a request.
*
* @param id ZIP request id.
* @return ZIP stream. Caller owns closing it.
* @see <a href="https://docs.facturapi.io/api#tag/Invoices">API reference</a>
*/
public InputStream downloadZipRequestStream(String id) {
return client.getStream("/invoices/zip-requests/" + id + "/zip");
}

/**
* Downloads the cancellation receipt XML file.
*
Expand Down
47 changes: 46 additions & 1 deletion src/main/java/io/facturapi/resources/RetentionsResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public RetentionsResource(FacturapiHttpClient client) {
}

/**
* Creates a new valid retention (CFDI).
* Creates a new retention (CFDI).
*
* @param data Retention payload.
* @return Created retention.
Expand Down Expand Up @@ -63,6 +63,51 @@ public Retention cancel(String id, Map<String, ?> params) {
return delete("/retentions/" + id, params, Retention.class);
}

/**
* Cancels a retention, or deletes it directly when it is a draft.
*
* @param id Retention id.
* @return Canceled or deleted retention.
* @see <a href="https://docs.facturapi.io/api#operation/cancelRetention">API reference</a>
*/
public Retention cancel(String id) {
return delete("/retentions/" + id, null, Retention.class);
}

/**
* Updates a draft retention.
*
* @param id Retention id.
* @param data Retention updates.
* @return Updated draft retention.
* @see <a href="https://docs.facturapi.io/api#operation/updateDraftRetention">API reference</a>
*/
public Retention updateDraft(String id, Map<String, Object> data) {
return put("/retentions/" + id, data, null, Retention.class);
}

/**
* Creates a draft copy from an existing retention.
*
* @param id Retention id.
* @return Draft retention.
* @see <a href="https://docs.facturapi.io/api#operation/copyToDraftRetention">API reference</a>
*/
public Retention copyToDraft(String id) {
return post("/retentions/" + id + "/copy", null, null, Retention.class);
}

/**
* Stamps an existing draft retention.
*
* @param id Retention id.
* @return Stamped retention.
* @see <a href="https://docs.facturapi.io/api#operation/stampDraftRetention">API reference</a>
*/
public Retention stampDraft(String id) {
return post("/retentions/" + id + "/stamp", null, null, Retention.class);
}

/**
* Sends the retention to the customer's email.
*
Expand Down
19 changes: 19 additions & 0 deletions src/test/java/io/facturapi/FacturapiHttpClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,23 @@ void throwsFacturapiExceptionWithApiMessage() {
assertEquals("3", ex.getHeaders().get("Retry-After").get(0));
assertEquals("log_123", ex.getHeaders().get("x-facturapi-log-id").get(0));
}

@Test
void convertsNumericApiErrorCodesToStrings() {
StubHttpClient httpClient = new StubHttpClient();
httpClient.enqueueJson(400, "{\"message\":\"Invalid customer\",\"code\":400}");

FacturapiHttpClient client = new FacturapiHttpClient(
FacturapiConfig.builder("sk_test_123")
.httpClient(httpClient.client())
.build()
);

FacturapiException ex = assertThrows(
FacturapiException.class,
() -> client.get("/customers/cus_1", null, GenericResponse.class)
);

assertEquals("400", ex.getErrorCode());
}
}
Loading
Loading