From 05c7b7ab46c64282947ce8b9c3c24bc46847e577 Mon Sep 17 00:00:00 2001 From: Juan Cernadas Date: Tue, 16 Jun 2026 13:30:41 -0300 Subject: [PATCH 1/4] feat: add new Open Finance credit card fields Pluggy's backend (commons 6.48.0) added new Open Finance credit-card fields to the canonical types, which the Pluggy API now returns in credit-card responses. This models them in the Java SDK so consumers can read them. - CreditData: add brandAdditionalInfo and disaggregatedCreditLimits. - DisaggregatedCreditLimit (new): lineName (CreditCardLimitLineName), limitAmountReason, customizedLimitAmount, customizedLimitAmountCurrencyCode. - TransactionCreditCardMetadata: add feeType, feeTypeAdditionalInfo, otherCreditsType, otherCreditsAdditionalInfo. - New enums CreditCardLimitLineName, CreditCardAccountFeeType and CreditCardAccountOtherCreditType, following the existing Gson @SerializedName enum style. currencyCode fields are modeled as String, matching the SDK's existing convention (no CurrencyCode type exists in this SDK). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../response/CreditCardAccountFeeType.java | 28 +++++++++++++++++++ .../CreditCardAccountOtherCreditType.java | 20 +++++++++++++ .../response/CreditCardLimitLineName.java | 24 ++++++++++++++++ .../ai/pluggy/client/response/CreditData.java | 4 +++ .../response/DisaggregatedCreditLimit.java | 14 ++++++++++ .../TransactionCreditCardMetadata.java | 4 +++ 6 files changed, 94 insertions(+) create mode 100644 src/main/java/ai/pluggy/client/response/CreditCardAccountFeeType.java create mode 100644 src/main/java/ai/pluggy/client/response/CreditCardAccountOtherCreditType.java create mode 100644 src/main/java/ai/pluggy/client/response/CreditCardLimitLineName.java create mode 100644 src/main/java/ai/pluggy/client/response/DisaggregatedCreditLimit.java diff --git a/src/main/java/ai/pluggy/client/response/CreditCardAccountFeeType.java b/src/main/java/ai/pluggy/client/response/CreditCardAccountFeeType.java new file mode 100644 index 0000000..4d449a2 --- /dev/null +++ b/src/main/java/ai/pluggy/client/response/CreditCardAccountFeeType.java @@ -0,0 +1,28 @@ +package ai.pluggy.client.response; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public enum CreditCardAccountFeeType { + @SerializedName("ANNUAL_FEE") + ANNUAL_FEE("ANNUAL_FEE"), + @SerializedName("ATM_WITHDRAWAL_DOMESTIC") + ATM_WITHDRAWAL_DOMESTIC("ATM_WITHDRAWAL_DOMESTIC"), + @SerializedName("ATM_WITHDRAWAL_INTERNATIONAL") + ATM_WITHDRAWAL_INTERNATIONAL("ATM_WITHDRAWAL_INTERNATIONAL"), + @SerializedName("EMERGENCY_CREDIT_EVALUATION") + EMERGENCY_CREDIT_EVALUATION("EMERGENCY_CREDIT_EVALUATION"), + @SerializedName("CARD_REISSUE") + CARD_REISSUE("CARD_REISSUE"), + @SerializedName("BILL_PAYMENT_FEE") + BILL_PAYMENT_FEE("BILL_PAYMENT_FEE"), + @SerializedName("SMS") + SMS("SMS"), + @SerializedName("OTHER") + OTHER("OTHER"); + + @Getter + private String value; +} diff --git a/src/main/java/ai/pluggy/client/response/CreditCardAccountOtherCreditType.java b/src/main/java/ai/pluggy/client/response/CreditCardAccountOtherCreditType.java new file mode 100644 index 0000000..7ff49b1 --- /dev/null +++ b/src/main/java/ai/pluggy/client/response/CreditCardAccountOtherCreditType.java @@ -0,0 +1,20 @@ +package ai.pluggy.client.response; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public enum CreditCardAccountOtherCreditType { + @SerializedName("REVOLVING_CREDIT") + REVOLVING_CREDIT("REVOLVING_CREDIT"), + @SerializedName("BILL_INSTALLMENT") + BILL_INSTALLMENT("BILL_INSTALLMENT"), + @SerializedName("LOAN") + LOAN("LOAN"), + @SerializedName("OTHER") + OTHER("OTHER"); + + @Getter + private String value; +} diff --git a/src/main/java/ai/pluggy/client/response/CreditCardLimitLineName.java b/src/main/java/ai/pluggy/client/response/CreditCardLimitLineName.java new file mode 100644 index 0000000..2dd7c97 --- /dev/null +++ b/src/main/java/ai/pluggy/client/response/CreditCardLimitLineName.java @@ -0,0 +1,24 @@ +package ai.pluggy.client.response; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public enum CreditCardLimitLineName { + @SerializedName("CREDITO_A_VISTA") + CREDITO_A_VISTA("CREDITO_A_VISTA"), + @SerializedName("CREDITO_PARCELADO") + CREDITO_PARCELADO("CREDITO_PARCELADO"), + @SerializedName("SAQUE_CREDITO_BRASIL") + SAQUE_CREDITO_BRASIL("SAQUE_CREDITO_BRASIL"), + @SerializedName("SAQUE_CREDITO_EXTERIOR") + SAQUE_CREDITO_EXTERIOR("SAQUE_CREDITO_EXTERIOR"), + @SerializedName("EMPRESTIMO_CARTAO_CONSIGNADO") + EMPRESTIMO_CARTAO_CONSIGNADO("EMPRESTIMO_CARTAO_CONSIGNADO"), + @SerializedName("OUTROS") + OUTROS("OUTROS"); + + @Getter + private String value; +} diff --git a/src/main/java/ai/pluggy/client/response/CreditData.java b/src/main/java/ai/pluggy/client/response/CreditData.java index cd33570..4168fa9 100644 --- a/src/main/java/ai/pluggy/client/response/CreditData.java +++ b/src/main/java/ai/pluggy/client/response/CreditData.java @@ -3,12 +3,15 @@ import lombok.Builder; import lombok.Data; +import java.util.List; + @Data @Builder public class CreditData { String level; String brand; + String brandAdditionalInfo; String balanceCloseDate; String balanceDueDate; Double availableCreditLimit; @@ -17,4 +20,5 @@ public class CreditData { Double creditLimit; HolderType holderType; CreditCardStatus status; + List disaggregatedCreditLimits; } diff --git a/src/main/java/ai/pluggy/client/response/DisaggregatedCreditLimit.java b/src/main/java/ai/pluggy/client/response/DisaggregatedCreditLimit.java new file mode 100644 index 0000000..5c0cb41 --- /dev/null +++ b/src/main/java/ai/pluggy/client/response/DisaggregatedCreditLimit.java @@ -0,0 +1,14 @@ +package ai.pluggy.client.response; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class DisaggregatedCreditLimit { + + CreditCardLimitLineName lineName; + String limitAmountReason; + Double customizedLimitAmount; + String customizedLimitAmountCurrencyCode; +} diff --git a/src/main/java/ai/pluggy/client/response/TransactionCreditCardMetadata.java b/src/main/java/ai/pluggy/client/response/TransactionCreditCardMetadata.java index d5abeea..580ea0a 100644 --- a/src/main/java/ai/pluggy/client/response/TransactionCreditCardMetadata.java +++ b/src/main/java/ai/pluggy/client/response/TransactionCreditCardMetadata.java @@ -13,4 +13,8 @@ public class TransactionCreditCardMetadata { Date purchaseDate; String cardNumber; String billId; + CreditCardAccountFeeType feeType; + String feeTypeAdditionalInfo; + CreditCardAccountOtherCreditType otherCreditsType; + String otherCreditsAdditionalInfo; } From ec0f539187623e3c281d39f6eb415f9b4494abcc Mon Sep 17 00:00:00 2001 From: Juan Cernadas Date: Tue, 16 Jun 2026 15:21:38 -0300 Subject: [PATCH 2/4] chore(release): bump version to 1.11.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minor bump (semver) for the new Open Finance credit-card fields added in this branch — feat commits since v1.10.0 warrant a minor release. The pom version is the single source of truth that triggers the release workflow on merge to master. Co-Authored-By: Claude Opus 4.8 (1M context) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c7ad738..4c6984e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ai.pluggy pluggy-java - 1.10.0 + 1.11.0 jar From be0b7c1dacf2862b545999004105bef49a6fbb0d Mon Sep 17 00:00:00 2001 From: Juan Cernadas Date: Tue, 16 Jun 2026 15:22:56 -0300 Subject: [PATCH 3/4] chore: update hardcoded User-Agent to PluggyJava/1.11.0 The User-Agent string is hardcoded in two places (PluggyClient.authenticate and ApiKeyAuthInterceptor) and is not derived from the pom version, so it had drifted to a stale 0.16.2. Sync it with the 1.11.0 release. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/main/java/ai/pluggy/client/PluggyClient.java | 2 +- src/main/java/ai/pluggy/client/auth/ApiKeyAuthInterceptor.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ai/pluggy/client/PluggyClient.java b/src/main/java/ai/pluggy/client/PluggyClient.java index eda479c..70c299a 100644 --- a/src/main/java/ai/pluggy/client/PluggyClient.java +++ b/src/main/java/ai/pluggy/client/PluggyClient.java @@ -292,7 +292,7 @@ public String authenticate() throws IOException, PluggyException { .post(body) .addHeader("content-type", "application/json") .addHeader("cache-control", "no-cache") - .addHeader("User-Agent", "PluggyJava/0.16.2") + .addHeader("User-Agent", "PluggyJava/1.11.0") .build(); String apiKey; diff --git a/src/main/java/ai/pluggy/client/auth/ApiKeyAuthInterceptor.java b/src/main/java/ai/pluggy/client/auth/ApiKeyAuthInterceptor.java index fff3926..55c4c25 100644 --- a/src/main/java/ai/pluggy/client/auth/ApiKeyAuthInterceptor.java +++ b/src/main/java/ai/pluggy/client/auth/ApiKeyAuthInterceptor.java @@ -115,7 +115,7 @@ private Request requestWithAuth(Request originalRequest, String apiKey) { return originalRequest.newBuilder() .header(X_API_KEY_HEADER, apiKey) // TOOD: add dynamic version - .header("User-Agent", "PluggyJava/0.16.2") + .header("User-Agent", "PluggyJava/1.11.0") .build(); } From 9317cd1965642aa4074fa3952b39b2de9239fde4 Mon Sep 17 00:00:00 2001 From: Juan Cernadas Date: Tue, 16 Jun 2026 15:27:56 -0300 Subject: [PATCH 4/4] refactor: derive User-Agent version from the pom at build time Replace the hardcoded "PluggyJava/" User-Agent (which had to be updated by hand and had drifted to a stale value) with a version read from a build-time-filtered pluggy.properties, so it always matches the pom version. Adds a Version helper, the filtered resource and its filtering config, and a unit test that fails if the filtering is ever removed. Co-Authored-By: Claude Opus 4.8 (1M context) --- pom.xml | 18 ++++++++ .../java/ai/pluggy/client/PluggyClient.java | 3 +- .../client/auth/ApiKeyAuthInterceptor.java | 4 +- src/main/java/ai/pluggy/utils/Version.java | 46 +++++++++++++++++++ src/main/resources/pluggy.properties | 1 + .../java/ai/pluggy/utils/VersionTest.java | 24 ++++++++++ 6 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ai/pluggy/utils/Version.java create mode 100644 src/main/resources/pluggy.properties create mode 100644 src/test/java/ai/pluggy/utils/VersionTest.java diff --git a/pom.xml b/pom.xml index 4c6984e..55a6f5c 100644 --- a/pom.xml +++ b/pom.xml @@ -86,6 +86,24 @@ + + + + src/main/resources + true + + pluggy.properties + + + + src/main/resources + false + + pluggy.properties + + + diff --git a/src/main/java/ai/pluggy/client/PluggyClient.java b/src/main/java/ai/pluggy/client/PluggyClient.java index 70c299a..f46227e 100644 --- a/src/main/java/ai/pluggy/client/PluggyClient.java +++ b/src/main/java/ai/pluggy/client/PluggyClient.java @@ -9,6 +9,7 @@ import ai.pluggy.client.response.ErrorResponse; import ai.pluggy.exception.PluggyException; import ai.pluggy.utils.Utils; +import ai.pluggy.utils.Version; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -292,7 +293,7 @@ public String authenticate() throws IOException, PluggyException { .post(body) .addHeader("content-type", "application/json") .addHeader("cache-control", "no-cache") - .addHeader("User-Agent", "PluggyJava/1.11.0") + .addHeader("User-Agent", Version.userAgent()) .build(); String apiKey; diff --git a/src/main/java/ai/pluggy/client/auth/ApiKeyAuthInterceptor.java b/src/main/java/ai/pluggy/client/auth/ApiKeyAuthInterceptor.java index 55c4c25..fe2af9b 100644 --- a/src/main/java/ai/pluggy/client/auth/ApiKeyAuthInterceptor.java +++ b/src/main/java/ai/pluggy/client/auth/ApiKeyAuthInterceptor.java @@ -2,6 +2,7 @@ import static ai.pluggy.utils.Asserts.assertNotNull; +import ai.pluggy.utils.Version; import com.google.gson.Gson; import java.io.IOException; @@ -114,8 +115,7 @@ private Request requestWithAuth(Request originalRequest, String apiKey) { // override the apiKey of the original request with the new one return originalRequest.newBuilder() .header(X_API_KEY_HEADER, apiKey) - // TOOD: add dynamic version - .header("User-Agent", "PluggyJava/1.11.0") + .header("User-Agent", Version.userAgent()) .build(); } diff --git a/src/main/java/ai/pluggy/utils/Version.java b/src/main/java/ai/pluggy/utils/Version.java new file mode 100644 index 0000000..fb0f35e --- /dev/null +++ b/src/main/java/ai/pluggy/utils/Version.java @@ -0,0 +1,46 @@ +package ai.pluggy.utils; + +import java.io.InputStream; +import java.util.Properties; + +/** + * Exposes the SDK version, derived from the Maven {@code project.version} at build time via + * resource filtering of {@code pluggy.properties}. Used to build the {@code User-Agent} header so it + * stays in sync with {@code pom.xml} automatically. + */ +public final class Version { + + private static final String UNKNOWN = "unknown"; + private static final String VERSION = load(); + + private Version() { + } + + /** SDK version (e.g. {@code "1.11.0"}), or {@code "unknown"} if it can't be resolved. */ + public static String get() { + return VERSION; + } + + /** {@code User-Agent} header value, e.g. {@code "PluggyJava/1.11.0"}. */ + public static String userAgent() { + return "PluggyJava/" + VERSION; + } + + private static String load() { + try (InputStream in = Version.class.getResourceAsStream("/pluggy.properties")) { + if (in == null) { + return UNKNOWN; + } + Properties props = new Properties(); + props.load(in); + String version = props.getProperty("version"); + // Guard against an unfiltered placeholder (e.g. if resource filtering didn't run). + if (version == null || version.isEmpty() || version.startsWith("${")) { + return UNKNOWN; + } + return version; + } catch (Exception e) { + return UNKNOWN; + } + } +} diff --git a/src/main/resources/pluggy.properties b/src/main/resources/pluggy.properties new file mode 100644 index 0000000..defbd48 --- /dev/null +++ b/src/main/resources/pluggy.properties @@ -0,0 +1 @@ +version=${project.version} diff --git a/src/test/java/ai/pluggy/utils/VersionTest.java b/src/test/java/ai/pluggy/utils/VersionTest.java new file mode 100644 index 0000000..ee63c63 --- /dev/null +++ b/src/test/java/ai/pluggy/utils/VersionTest.java @@ -0,0 +1,24 @@ +package ai.pluggy.utils; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class VersionTest { + + @Test + public void version_isResolvedFromFilteredProperties() { + // Resource filtering replaces ${project.version} at build time. If the filtering config + // is removed, Version falls back to "unknown" and these assertions fail. + String version = Version.get(); + assertFalse(version.isEmpty(), "version should not be empty"); + assertFalse("unknown".equals(version), "version should resolve from pluggy.properties"); + assertFalse(version.startsWith("${"), "version placeholder should be filtered"); + } + + @Test + public void userAgent_hasExpectedPrefix() { + assertTrue(Version.userAgent().startsWith("PluggyJava/")); + } +}