From a1aa171b86314c51d7e85c56a890a3e9d6a0a05c Mon Sep 17 00:00:00 2001 From: javorosas Date: Fri, 4 Sep 2026 13:24:20 +0200 Subject: [PATCH] feat: add invoices.paymentSummary for payment complements Calls GET /invoices/{id}/payment-summary?amount=, which returns the related document object needed to build a payment complement (complemento de pago): installment from the payment history, previous balance, and taxes prorated to the paid amount. Bumps version to 2.1.0. --- CHANGELOG.md | 7 + pom.xml | 2 +- .../io/facturapi/models/PaymentSummary.java | 144 ++++++++++++++++++ .../facturapi/resources/InvoicesResource.java | 17 +++ .../io/facturapi/FacturapiResourcesTest.java | 30 ++++ 5 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/facturapi/models/PaymentSummary.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f13e13c..79a7267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/pom.xml b/pom.xml index a69187f..de17cc0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.facturapi facturapi-java - 2.0.0 + 2.1.0 facturapi-java Official Java SDK for Facturapi https://github.com/facturapi/facturapi-java diff --git a/src/main/java/io/facturapi/models/PaymentSummary.java b/src/main/java/io/facturapi/models/PaymentSummary.java new file mode 100644 index 0000000..3244b70 --- /dev/null +++ b/src/main/java/io/facturapi/models/PaymentSummary.java @@ -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 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 getTaxes() { + return taxes; + } + + public void setTaxes(List 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; + } + } +} diff --git a/src/main/java/io/facturapi/resources/InvoicesResource.java b/src/main/java/io/facturapi/resources/InvoicesResource.java index 6dac015..7cdbdd4 100644 --- a/src/main/java/io/facturapi/resources/InvoicesResource.java +++ b/src/main/java/io/facturapi/resources/InvoicesResource.java @@ -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; @@ -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 API reference + */ + public PaymentSummary paymentSummary(String id, double amount) { + return get("/invoices/" + id + "/payment-summary", Map.of("amount", amount), PaymentSummary.class); + } + /** * Cancels an invoice. * diff --git a/src/test/java/io/facturapi/FacturapiResourcesTest.java b/src/test/java/io/facturapi/FacturapiResourcesTest.java index ef43828..e425076 100644 --- a/src/test/java/io/facturapi/FacturapiResourcesTest.java +++ b/src/test/java/io/facturapi/FacturapiResourcesTest.java @@ -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();