Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ 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-07-17

### Changed

- Change `FacturapiException.getErrorCode()` to return strings, including converted legacy numeric codes.

## [1.3.0] - 2026-06-07

### Added
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; }
}
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());
}
}
16 changes: 16 additions & 0 deletions src/test/java/io/facturapi/FacturapiResourcesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
import io.facturapi.enums.Taxability;
import io.facturapi.http.FacturapiConfig;
import io.facturapi.models.Customer;
import io.facturapi.models.InvoiceItem;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -320,4 +322,18 @@ void objectMapperDeserializesCodeEnums() throws Exception {
assertEquals(TaxType.IEPS, tax.getType());
assertEquals(TaxFactor.EXENTO, tax.getFactor());
}

@Test
void objectMapperDeserializesPropertyTaxAccountsAsArrays() throws Exception {
var mapper = FacturapiConfig.builder("sk_test").build().getObjectMapper();

var empty = mapper.readValue("{\"property_tax_account\":[]}", InvoiceItem.class);
var accounts = mapper.readValue(
"{\"property_tax_account\":[\"0102030405\"]}",
InvoiceItem.class
);

assertEquals(List.of(), empty.getPropertyTaxAccounts());
assertEquals(List.of("0102030405"), accounts.getPropertyTaxAccounts());
}
}
Loading