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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ 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.1.0] - 2026-09-04

### Added

- Add `invoices.paymentSummary(String id, double amount)` to get the related-document object needed to build a payment complement (complemento de pago): installment number, previous balance, and taxes prorated to the paid amount.
- Add the `PaymentSummary` model.

## [2.0.0] - 2026-08-24

### 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>2.0.0</version>
<version>2.1.0</version>
<name>facturapi-java</name>
<description>Official Java SDK for Facturapi</description>
<url>https://github.com/facturapi/facturapi-java</url>
Expand Down
144 changes: 144 additions & 0 deletions src/main/java/io/facturapi/models/PaymentSummary.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package io.facturapi.models;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.ArrayList;
import java.util.List;

/**
* Related document summary for a payment complement (complemento de pago).
* Ready to be used as an element of the complement's related documents.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class PaymentSummary {
private String uuid;
private Integer folioNumber;
private String series;
private Integer installment;
private Double lastBalance;
private Double total;
private String currency;
private Double amount;
private List<PaymentSummaryTax> taxes = new ArrayList<>();

public String getUuid() {
return uuid;
}

public void setUuid(String uuid) {
this.uuid = uuid;
}

public Integer getFolioNumber() {
return folioNumber;
}

public void setFolioNumber(Integer folioNumber) {
this.folioNumber = folioNumber;
}

public String getSeries() {
return series;
}

public void setSeries(String series) {
this.series = series;
}

public Integer getInstallment() {
return installment;
}

public void setInstallment(Integer installment) {
this.installment = installment;
}

public Double getLastBalance() {
return lastBalance;
}

public void setLastBalance(Double lastBalance) {
this.lastBalance = lastBalance;
}

public Double getTotal() {
return total;
}

public void setTotal(Double total) {
this.total = total;
}

public String getCurrency() {
return currency;
}

public void setCurrency(String currency) {
this.currency = currency;
}

public Double getAmount() {
return amount;
}

public void setAmount(Double amount) {
this.amount = amount;
}

public List<PaymentSummaryTax> getTaxes() {
return taxes;
}

public void setTaxes(List<PaymentSummaryTax> taxes) {
this.taxes = taxes;
}

/** Prorated tax breakdown for the paid amount. */
@JsonIgnoreProperties(ignoreUnknown = true)
public static class PaymentSummaryTax {
private Double base;
private Double rate;
private String type;
private String factor;
private Boolean withholding;

public Double getBase() {
return base;
}

public void setBase(Double base) {
this.base = base;
}

public Double getRate() {
return rate;
}

public void setRate(Double rate) {
this.rate = rate;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public String getFactor() {
return factor;
}

public void setFactor(String factor) {
this.factor = factor;
}

public Boolean getWithholding() {
return withholding;
}

public void setWithholding(Boolean withholding) {
this.withholding = withholding;
}
}
}
17 changes: 17 additions & 0 deletions src/main/java/io/facturapi/resources/InvoicesResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.facturapi.http.FacturapiHttpClient;
import io.facturapi.models.GenericResponse;
import io.facturapi.models.Invoice;
import io.facturapi.models.PaymentSummary;
import io.facturapi.models.SearchResult;
import java.io.InputStream;
import java.util.Map;
Expand Down Expand Up @@ -52,6 +53,22 @@ public Invoice retrieve(String id) {
return get("/invoices/" + id, null, Invoice.class);
}

/**
* Gets the information needed to add this invoice as a related document in a
* payment complement (complemento de pago): the installment number according
* to the payment history, the previous balance, and the invoice tax breakdown
* prorated to the amount being paid.
*
* @param id Invoice id.
* @param amount Amount being paid, expressed in the invoice currency. Cannot
* exceed the outstanding balance.
* @return Payment summary ready to be used as a related document.
* @see <a href="https://docs.facturapi.io/api#operation/getInvoicePaymentSummary">API reference</a>
*/
public PaymentSummary paymentSummary(String id, double amount) {
return get("/invoices/" + id + "/payment-summary", Map.of("amount", amount), PaymentSummary.class);
}

/**
* Cancels an invoice.
*
Expand Down
30 changes: 30 additions & 0 deletions src/test/java/io/facturapi/FacturapiResourcesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,36 @@ void invoiceCreateUsesExpectedPath() {
assertEquals("/v2/invoices?test=true", request.uri().getPath() + "?" + request.uri().getQuery());
}

@Test
void invoicePaymentSummaryUsesExpectedPath() {
StubHttpClient httpClient = new StubHttpClient();
httpClient.enqueueJson(
200,
"{\"uuid\":\"6CF6CE33-1BD2-4F88-A443-33013C069169\",\"installment\":1,"
+ "\"last_balance\":100,\"total\":100,\"currency\":\"MXN\",\"amount\":58,"
+ "\"taxes\":[{\"base\":50,\"rate\":0.16,\"type\":\"IVA\",\"factor\":\"Tasa\",\"withholding\":false}]}"
);

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

var response = sdk.invoices().paymentSummary("inv_1", 58.0);

assertEquals("6CF6CE33-1BD2-4F88-A443-33013C069169", response.getUuid());
assertEquals(1, response.getInstallment());
assertEquals(50.0, response.getTaxes().get(0).getBase());

var request = httpClient.requests().get(0);
assertEquals("GET", request.method());
assertEquals(
"/v2/invoices/inv_1/payment-summary?amount=58.0",
request.uri().getPath() + "?" + request.uri().getQuery()
);
}

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