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).

## [1.3.1] - 2026-07-01

### 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)`.

## [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>1.3.1</version>
</dependency>
```

Gradle:

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

## 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>1.3.1</version>
</dependency>
```

Gradle:

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

## 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>1.3.1</version>
<name>facturapi-java</name>
<description>Official Java SDK for Facturapi</description>
<url>https://github.com/facturapi/facturapi-java</url>
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);
}
Comment thread
raul-facturapi marked this conversation as resolved.

/**
* 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
94 changes: 94 additions & 0 deletions src/test/java/io/facturapi/FacturapiResourcesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,100 @@ void invoicePdfCanBeStreamed() throws Exception {
assertEquals("/v2/invoices/inv_1/pdf", request.uri().getPath());
}

@Test
void retentionDraftCreateSupportsDraftStatus() {
StubHttpClient httpClient = new StubHttpClient();
httpClient.enqueueJson(200, "{\"id\":\"ret_draft_1\",\"status\":\"draft\"}");

Facturapi sdk = new Facturapi(
FacturapiConfig.builder("sk_test")
.httpClient(httpClient.client())
.build()
);

Map<String, Object> payload = new java.util.HashMap<>();
payload.put("status", "draft");
payload.put("customer", null);

var response = sdk.retentions().create(payload);

assertEquals("ret_draft_1", response.getId());
assertEquals("draft", response.getStatus());
var request = httpClient.requests().get(0);
assertEquals("POST", request.method());
assertEquals("/v2/retentions", request.uri().getPath());
assertTrue(request.bodyUtf8().contains("\"status\":\"draft\""));
assertTrue(request.bodyUtf8().contains("\"customer\":null"));
}

@Test
void retentionDraftUpdateUsesExpectedPath() {
StubHttpClient httpClient = new StubHttpClient();
httpClient.enqueueJson(200, "{\"id\":\"ret_1\",\"folio_int\":\"R-2026-001\"}");

Facturapi sdk = new Facturapi(
FacturapiConfig.builder("sk_test")
.httpClient(httpClient.client())
.build()
);

var response = sdk.retentions().updateDraft("ret_1", Map.of("folio_int", "R-2026-001"));

assertEquals("ret_1", response.getId());
assertEquals("R-2026-001", response.getFolioInt());
var request = httpClient.requests().get(0);
assertEquals("PUT", request.method());
assertEquals("/v2/retentions/ret_1", request.uri().getPath());
assertTrue(request.bodyUtf8().contains("\"folio_int\":\"R-2026-001\""));
}

@Test
void retentionDraftCopyAndStampUseExpectedPaths() {
StubHttpClient httpClient = new StubHttpClient();
httpClient.enqueueJson(200, "{\"id\":\"ret_copy_1\",\"status\":\"draft\"}");
httpClient.enqueueJson(200, "{\"id\":\"ret_1\",\"status\":\"valid\"}");

Facturapi sdk = new Facturapi(
FacturapiConfig.builder("sk_test")
.httpClient(httpClient.client())
.build()
);

var draft = sdk.retentions().copyToDraft("ret_1");
var stamped = sdk.retentions().stampDraft("ret_copy_1");

assertEquals("ret_copy_1", draft.getId());
assertEquals("ret_1", stamped.getId());
assertEquals("POST", httpClient.requests().get(0).method());
assertEquals("/v2/retentions/ret_1/copy", httpClient.requests().get(0).uri().getPath());
assertEquals("POST", httpClient.requests().get(1).method());
assertEquals("/v2/retentions/ret_copy_1/stamp", httpClient.requests().get(1).uri().getPath());
}

@Test
void retentionDraftCancelAndListUseExpectedPaths() {
StubHttpClient httpClient = new StubHttpClient();
httpClient.enqueueJson(200, "{\"id\":\"ret_draft_1\",\"status\":\"canceled\"}");
httpClient.enqueueJson(200, "{\"data\":[{\"id\":\"ret_draft_2\",\"status\":\"draft\"}]}");

Facturapi sdk = new Facturapi(
FacturapiConfig.builder("sk_test")
.httpClient(httpClient.client())
.build()
);

var deleted = sdk.retentions().cancel("ret_draft_1");
var drafts = sdk.retentions().list(Map.of("status", "draft"));

assertEquals("ret_draft_1", deleted.getId());
assertEquals(1, drafts.getData().size());
assertEquals("DELETE", httpClient.requests().get(0).method());
assertEquals("/v2/retentions/ret_draft_1", httpClient.requests().get(0).uri().getPath());
var listRequest = httpClient.requests().get(1);
assertEquals("GET", listRequest.method());
assertEquals("/v2/retentions?status=draft", listRequest.uri().getPath() + "?" + listRequest.uri().getQuery());
}

@Test
void organizationUploadsAcceptBytes() {
StubHttpClient httpClient = new StubHttpClient();
Expand Down
Loading