From 3dc7d3f2903edec88f307dfa5d17e6b5f377e3f0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 21:10:14 +0000 Subject: [PATCH 01/17] chore: configure new SDK language --- .stats.yml | 2 +- README.md | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index ddb484b7..44b43de3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 176 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-4ecc06edca2cfad4eaf11573611e89823fda5f56370bac5cd02a498a6b277d09.yml openapi_spec_hash: 8f4a30bec4348cbde85b1e65bef9189a -config_hash: 9dddee5f7af579864599849cb28a0770 +config_hash: 751a4cc75aa0276b40cc2c7879b24dea diff --git a/README.md b/README.md index 234c6d0f..e6493697 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,15 @@ The Lithic Java SDK provides convenient access to the [Lithic REST API](https:// The Lithic Java SDK is similar to the Lithic Kotlin SDK but with minor differences that make it more ergonomic for use in Java, such as `Optional` instead of nullable values, `Stream` instead of `Sequence`, and `CompletableFuture` instead of suspend functions. +## MCP Server + +Use the Lithic MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=lithic-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImxpdGhpYy1tY3AiXX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22lithic-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22lithic-mcp%22%5D%7D) + +> Note: You may need to set environment variables in your MCP client. + The REST API documentation can be found on [docs.lithic.com](https://docs.lithic.com). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.lithic.api/lithic-java/0.115.0). From 6b3f7ee38b9f387e28cce9d19d5fec3a6718cfbf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 22:34:58 +0000 Subject: [PATCH 02/17] chore(internal): codegen related update --- .../api/client/okhttp/LithicOkHttpClient.kt | 22 +++++++++++++++++++ .../client/okhttp/LithicOkHttpClientAsync.kt | 22 +++++++++++++++++++ .../lithic/api/client/okhttp/OkHttpClient.kt | 9 ++++++++ 3 files changed, 53 insertions(+) diff --git a/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClient.kt b/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClient.kt index 1eb00e26..ac00e64a 100644 --- a/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClient.kt +++ b/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClient.kt @@ -18,6 +18,7 @@ import java.time.Clock import java.time.Duration import java.util.Optional import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager @@ -46,11 +47,31 @@ class LithicOkHttpClient private constructor() { class Builder internal constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() + private var dispatcherExecutorService: ExecutorService? = null private var proxy: Proxy? = null private var sslSocketFactory: SSLSocketFactory? = null private var trustManager: X509TrustManager? = null private var hostnameVerifier: HostnameVerifier? = null + /** + * The executor service to use for running HTTP requests. + * + * Defaults to OkHttp's + * [default executor service](https://github.com/square/okhttp/blob/ace792f443b2ffb17974f5c0d1cecdf589309f26/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dispatcher.kt#L98-L104). + * + * This class takes ownership of the executor service and shuts it down when closed. + */ + fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply { + this.dispatcherExecutorService = dispatcherExecutorService + } + + /** + * Alias for calling [Builder.dispatcherExecutorService] with + * `dispatcherExecutorService.orElse(null)`. + */ + fun dispatcherExecutorService(dispatcherExecutorService: Optional) = + dispatcherExecutorService(dispatcherExecutorService.getOrNull()) + fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ @@ -323,6 +344,7 @@ class LithicOkHttpClient private constructor() { OkHttpClient.builder() .timeout(clientOptions.timeout()) .proxy(proxy) + .dispatcherExecutorService(dispatcherExecutorService) .sslSocketFactory(sslSocketFactory) .trustManager(trustManager) .hostnameVerifier(hostnameVerifier) diff --git a/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClientAsync.kt b/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClientAsync.kt index 0f0acb3e..aebe796e 100644 --- a/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClientAsync.kt +++ b/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClientAsync.kt @@ -18,6 +18,7 @@ import java.time.Clock import java.time.Duration import java.util.Optional import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager @@ -46,11 +47,31 @@ class LithicOkHttpClientAsync private constructor() { class Builder internal constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() + private var dispatcherExecutorService: ExecutorService? = null private var proxy: Proxy? = null private var sslSocketFactory: SSLSocketFactory? = null private var trustManager: X509TrustManager? = null private var hostnameVerifier: HostnameVerifier? = null + /** + * The executor service to use for running HTTP requests. + * + * Defaults to OkHttp's + * [default executor service](https://github.com/square/okhttp/blob/ace792f443b2ffb17974f5c0d1cecdf589309f26/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dispatcher.kt#L98-L104). + * + * This class takes ownership of the executor service and shuts it down when closed. + */ + fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply { + this.dispatcherExecutorService = dispatcherExecutorService + } + + /** + * Alias for calling [Builder.dispatcherExecutorService] with + * `dispatcherExecutorService.orElse(null)`. + */ + fun dispatcherExecutorService(dispatcherExecutorService: Optional) = + dispatcherExecutorService(dispatcherExecutorService.getOrNull()) + fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ @@ -323,6 +344,7 @@ class LithicOkHttpClientAsync private constructor() { OkHttpClient.builder() .timeout(clientOptions.timeout()) .proxy(proxy) + .dispatcherExecutorService(dispatcherExecutorService) .sslSocketFactory(sslSocketFactory) .trustManager(trustManager) .hostnameVerifier(hostnameVerifier) diff --git a/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/OkHttpClient.kt b/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/OkHttpClient.kt index 1b37107b..1417591d 100644 --- a/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/OkHttpClient.kt +++ b/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/OkHttpClient.kt @@ -15,11 +15,13 @@ import java.net.Proxy import java.time.Duration import java.util.concurrent.CancellationException import java.util.concurrent.CompletableFuture +import java.util.concurrent.ExecutorService import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager import okhttp3.Call import okhttp3.Callback +import okhttp3.Dispatcher import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType @@ -198,6 +200,7 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien private var timeout: Timeout = Timeout.default() private var proxy: Proxy? = null + private var dispatcherExecutorService: ExecutorService? = null private var sslSocketFactory: SSLSocketFactory? = null private var trustManager: X509TrustManager? = null private var hostnameVerifier: HostnameVerifier? = null @@ -208,6 +211,10 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } + fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply { + this.dispatcherExecutorService = dispatcherExecutorService + } + fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply { this.sslSocketFactory = sslSocketFactory } @@ -229,6 +236,8 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien .callTimeout(timeout.request()) .proxy(proxy) .apply { + dispatcherExecutorService?.let { dispatcher(Dispatcher(it)) } + val sslSocketFactory = sslSocketFactory val trustManager = trustManager if (sslSocketFactory != null && trustManager != null) { From da510920d18bdd6b4e2a655755e9fe21f145bf9f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 23:03:56 +0000 Subject: [PATCH 03/17] chore(internal): support uploading Maven repo artifacts to stainless package server --- .github/workflows/ci.yml | 18 ++++ .../src/main/kotlin/lithic.publish.gradle.kts | 17 +++- scripts/upload-artifacts | 96 +++++++++++++++++++ 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100755 scripts/upload-artifacts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6e0ed7d..e3fe4992 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,9 @@ jobs: build: timeout-minutes: 15 name: build + permissions: + contents: read + id-token: write runs-on: ${{ github.repository == 'stainless-sdks/lithic-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork @@ -61,6 +64,21 @@ jobs: - name: Build SDK run: ./scripts/build + - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/lithic-java' + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Build and upload Maven artifacts + if: github.repository == 'stainless-sdks/lithic-java' + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + PROJECT: lithic-java + run: ./scripts/upload-artifacts test: timeout-minutes: 15 name: test diff --git a/buildSrc/src/main/kotlin/lithic.publish.gradle.kts b/buildSrc/src/main/kotlin/lithic.publish.gradle.kts index e5ca4e7e..bda852fe 100644 --- a/buildSrc/src/main/kotlin/lithic.publish.gradle.kts +++ b/buildSrc/src/main/kotlin/lithic.publish.gradle.kts @@ -7,6 +7,17 @@ plugins { id("com.vanniktech.maven.publish") } +publishing { + repositories { + if (project.hasProperty("publishLocal")) { + maven { + name = "LocalFileSystem" + url = uri("${rootProject.layout.buildDirectory.get()}/local-maven-repo") + } + } + } +} + repositories { gradlePluginPortal() mavenCentral() @@ -17,8 +28,10 @@ extra["signingInMemoryKeyId"] = System.getenv("GPG_SIGNING_KEY_ID") extra["signingInMemoryKeyPassword"] = System.getenv("GPG_SIGNING_PASSWORD") configure { - signAllPublications() - publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + if (!project.hasProperty("publishLocal")) { + signAllPublications() + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + } coordinates(project.group.toString(), project.name, project.version.toString()) configure( diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts new file mode 100755 index 00000000..729e6f22 --- /dev/null +++ b/scripts/upload-artifacts @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# ANSI Color Codes +GREEN='\033[32m' +RED='\033[31m' +NC='\033[0m' # No Color + +log_error() { + local msg="$1" + local headers="$2" + local body="$3" + echo -e "${RED}${msg}${NC}" + [[ -f "$headers" ]] && echo -e "${RED}Headers:$(cat "$headers")${NC}" + echo -e "${RED}Body: ${body}${NC}" + exit 1 +} + +upload_file() { + local file_name="$1" + local tmp_headers + tmp_headers=$(mktemp) + + if [ -f "$file_name" ]; then + echo -e "${GREEN}Processing file: $file_name${NC}" + pkg_file_name="mvn${file_name#./build/local-maven-repo}" + + # Get signed URL for uploading artifact file + signed_url_response=$(curl -X POST -G "$URL" \ + -sS --retry 5 \ + -D "$tmp_headers" \ + --data-urlencode "filename=$pkg_file_name" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + + # Validate JSON and extract URL + if ! signed_url=$(echo "$signed_url_response" | jq -e -r '.url' 2>/dev/null) || [[ "$signed_url" == "null" ]]; then + log_error "Failed to get valid signed URL" "$tmp_headers" "$signed_url_response" + fi + + # Set content-type based on file extension + local extension="${file_name##*.}" + local content_type + case "$extension" in + jar) content_type="application/java-archive" ;; + md5|sha1|sha256|sha512) content_type="text/plain" ;; + module) content_type="application/json" ;; + pom|xml) content_type="application/xml" ;; + *) content_type="application/octet-stream" ;; + esac + + # Upload file + upload_response=$(curl -v -X PUT \ + --retry 5 \ + -D "$tmp_headers" \ + -H "Content-Type: $content_type" \ + --data-binary "@${file_name}" "$signed_url" 2>&1) + + if ! echo "$upload_response" | grep -q "HTTP/[0-9.]* 200"; then + log_error "Failed upload artifact file" "$tmp_headers" "$upload_response" + fi + + # Insert small throttle to reduce rate limiting risk + sleep 0.1 + fi +} + +walk_tree() { + local current_dir="$1" + + for entry in "$current_dir"/*; do + # Check that entry is valid + [ -e "$entry" ] || [ -h "$entry" ] || continue + + if [ -d "$entry" ]; then + walk_tree "$entry" + else + upload_file "$entry" + fi + done +} + +cd "$(dirname "$0")/.." + +echo "::group::Creating local Maven content" +./gradlew publishMavenPublicationToLocalFileSystemRepository -PpublishLocal +echo "::endgroup::" + +echo "::group::Uploading to pkg.stainless.com" +walk_tree "./build/local-maven-repo" +echo "::endgroup::" + +echo "::group::Generating instructions" +echo "Configure maven or gradle to use the repo located at 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'" +echo "::endgroup::" From 6122eccf30d48db7aaba2fd6f8e94bf76fc7ceb4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:15:57 +0000 Subject: [PATCH 04/17] feat(api): make filter optional for Spend Velocity Auth Rules fix(api): rename WIRE_DRAWDOWN_REQUEST to WIRE_INBOUND_DRAWDOWN_REQUEST --- .stats.yml | 6 +- README.md | 9 - .../kotlin/com/lithic/api/models/Payment.kt | 10 +- .../lithic/api/models/VelocityLimitParams.kt | 342 +++++++++--------- .../api/models/VelocityLimitParamsTest.kt | 48 +-- 5 files changed, 202 insertions(+), 213 deletions(-) diff --git a/.stats.yml b/.stats.yml index 44b43de3..ac611e10 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 176 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-4ecc06edca2cfad4eaf11573611e89823fda5f56370bac5cd02a498a6b277d09.yml -openapi_spec_hash: 8f4a30bec4348cbde85b1e65bef9189a -config_hash: 751a4cc75aa0276b40cc2c7879b24dea +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-c06c4d54858775fef1de57e33d471e997cb28fe0c925fd7b08ba45fdd335e938.yml +openapi_spec_hash: 23745357b2171bcdfb5d30bfef1df48d +config_hash: 9dddee5f7af579864599849cb28a0770 diff --git a/README.md b/README.md index e6493697..234c6d0f 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,6 @@ The Lithic Java SDK provides convenient access to the [Lithic REST API](https:// The Lithic Java SDK is similar to the Lithic Kotlin SDK but with minor differences that make it more ergonomic for use in Java, such as `Optional` instead of nullable values, `Stream` instead of `Sequence`, and `CompletableFuture` instead of suspend functions. -## MCP Server - -Use the Lithic MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. - -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=lithic-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImxpdGhpYy1tY3AiXX0) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22lithic-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22lithic-mcp%22%5D%7D) - -> Note: You may need to set environment variables in your MCP client. - The REST API documentation can be found on [docs.lithic.com](https://docs.lithic.com). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.lithic.api/lithic-java/0.115.0). diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/Payment.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/Payment.kt index 5927991a..f1079776 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/Payment.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/Payment.kt @@ -4769,7 +4769,7 @@ private constructor( @JvmField val WIRE_OUTBOUND_ADMIN = of("WIRE_OUTBOUND_ADMIN") - @JvmField val WIRE_DRAWDOWN_REQUEST = of("WIRE_DRAWDOWN_REQUEST") + @JvmField val WIRE_INBOUND_DRAWDOWN_REQUEST = of("WIRE_INBOUND_DRAWDOWN_REQUEST") @JvmStatic fun of(value: String) = TransferType(JsonField.of(value)) } @@ -4784,7 +4784,7 @@ private constructor( WIRE_INBOUND_ADMIN, WIRE_OUTBOUND_PAYMENT, WIRE_OUTBOUND_ADMIN, - WIRE_DRAWDOWN_REQUEST, + WIRE_INBOUND_DRAWDOWN_REQUEST, } /** @@ -4805,7 +4805,7 @@ private constructor( WIRE_INBOUND_ADMIN, WIRE_OUTBOUND_PAYMENT, WIRE_OUTBOUND_ADMIN, - WIRE_DRAWDOWN_REQUEST, + WIRE_INBOUND_DRAWDOWN_REQUEST, /** * An enum member indicating that [TransferType] was instantiated with an unknown value. */ @@ -4829,7 +4829,7 @@ private constructor( WIRE_INBOUND_ADMIN -> Value.WIRE_INBOUND_ADMIN WIRE_OUTBOUND_PAYMENT -> Value.WIRE_OUTBOUND_PAYMENT WIRE_OUTBOUND_ADMIN -> Value.WIRE_OUTBOUND_ADMIN - WIRE_DRAWDOWN_REQUEST -> Value.WIRE_DRAWDOWN_REQUEST + WIRE_INBOUND_DRAWDOWN_REQUEST -> Value.WIRE_INBOUND_DRAWDOWN_REQUEST else -> Value._UNKNOWN } @@ -4852,7 +4852,7 @@ private constructor( WIRE_INBOUND_ADMIN -> Known.WIRE_INBOUND_ADMIN WIRE_OUTBOUND_PAYMENT -> Known.WIRE_OUTBOUND_PAYMENT WIRE_OUTBOUND_ADMIN -> Known.WIRE_OUTBOUND_ADMIN - WIRE_DRAWDOWN_REQUEST -> Known.WIRE_DRAWDOWN_REQUEST + WIRE_INBOUND_DRAWDOWN_REQUEST -> Known.WIRE_INBOUND_DRAWDOWN_REQUEST else -> throw LithicInvalidDataException("Unknown TransferType: $value") } diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/VelocityLimitParams.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/VelocityLimitParams.kt index 4ec1ed19..5f98fc8d 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/VelocityLimitParams.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/VelocityLimitParams.kt @@ -23,9 +23,9 @@ import kotlin.jvm.optionals.getOrNull class VelocityLimitParams @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( - private val filters: JsonField, private val period: JsonField, private val scope: JsonField, + private val filters: JsonField, private val limitAmount: JsonField, private val limitCount: JsonField, private val additionalProperties: MutableMap, @@ -33,24 +33,18 @@ private constructor( @JsonCreator private constructor( - @JsonProperty("filters") - @ExcludeMissing - filters: JsonField = JsonMissing.of(), @JsonProperty("period") @ExcludeMissing period: JsonField = JsonMissing.of(), @JsonProperty("scope") @ExcludeMissing scope: JsonField = JsonMissing.of(), + @JsonProperty("filters") + @ExcludeMissing + filters: JsonField = JsonMissing.of(), @JsonProperty("limit_amount") @ExcludeMissing limitAmount: JsonField = JsonMissing.of(), @JsonProperty("limit_count") @ExcludeMissing limitCount: JsonField = JsonMissing.of(), - ) : this(filters, period, scope, limitAmount, limitCount, mutableMapOf()) - - /** - * @throws LithicInvalidDataException if the JSON field has an unexpected type or is - * unexpectedly missing or null (e.g. if the server responded with an unexpected value). - */ - fun filters(): VelocityLimitFilters = filters.getRequired("filters") + ) : this(period, scope, filters, limitAmount, limitCount, mutableMapOf()) /** * Velocity over the current day since 00:00 / 12 AM in Eastern Time @@ -68,6 +62,12 @@ private constructor( */ fun scope(): VelocityScope = scope.getRequired("scope") + /** + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun filters(): Optional = filters.getOptional("filters") + /** * The maximum amount of spend velocity allowed in the period in minor units (the smallest unit * of a currency, e.g. cents for USD). Transactions exceeding this limit will be declined. @@ -88,15 +88,6 @@ private constructor( */ fun limitCount(): Optional = limitCount.getOptional("limit_count") - /** - * Returns the raw JSON value of [filters]. - * - * Unlike [filters], this method doesn't throw if the JSON field has an unexpected type. - */ - @JsonProperty("filters") - @ExcludeMissing - fun _filters(): JsonField = filters - /** * Returns the raw JSON value of [period]. * @@ -111,6 +102,15 @@ private constructor( */ @JsonProperty("scope") @ExcludeMissing fun _scope(): JsonField = scope + /** + * Returns the raw JSON value of [filters]. + * + * Unlike [filters], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("filters") + @ExcludeMissing + fun _filters(): JsonField = filters + /** * Returns the raw JSON value of [limitAmount]. * @@ -144,7 +144,6 @@ private constructor( * * The following fields are required: * ```java - * .filters() * .period() * .scope() * ``` @@ -155,34 +154,23 @@ private constructor( /** A builder for [VelocityLimitParams]. */ class Builder internal constructor() { - private var filters: JsonField? = null private var period: JsonField? = null private var scope: JsonField? = null + private var filters: JsonField = JsonMissing.of() private var limitAmount: JsonField = JsonMissing.of() private var limitCount: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @JvmSynthetic internal fun from(velocityLimitParams: VelocityLimitParams) = apply { - filters = velocityLimitParams.filters period = velocityLimitParams.period scope = velocityLimitParams.scope + filters = velocityLimitParams.filters limitAmount = velocityLimitParams.limitAmount limitCount = velocityLimitParams.limitCount additionalProperties = velocityLimitParams.additionalProperties.toMutableMap() } - fun filters(filters: VelocityLimitFilters) = filters(JsonField.of(filters)) - - /** - * Sets [Builder.filters] to an arbitrary JSON value. - * - * You should usually call [Builder.filters] with a well-typed [VelocityLimitFilters] value - * instead. This method is primarily for setting the field to an undocumented or not yet - * supported value. - */ - fun filters(filters: JsonField) = apply { this.filters = filters } - /** Velocity over the current day since 00:00 / 12 AM in Eastern Time */ fun period(period: VelocityLimitPeriod) = period(JsonField.of(period)) @@ -239,6 +227,17 @@ private constructor( */ fun scope(scope: JsonField) = apply { this.scope = scope } + fun filters(filters: VelocityLimitFilters) = filters(JsonField.of(filters)) + + /** + * Sets [Builder.filters] to an arbitrary JSON value. + * + * You should usually call [Builder.filters] with a well-typed [VelocityLimitFilters] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun filters(filters: JsonField) = apply { this.filters = filters } + /** * The maximum amount of spend velocity allowed in the period in minor units (the smallest * unit of a currency, e.g. cents for USD). Transactions exceeding this limit will be @@ -317,7 +316,6 @@ private constructor( * * The following fields are required: * ```java - * .filters() * .period() * .scope() * ``` @@ -326,9 +324,9 @@ private constructor( */ fun build(): VelocityLimitParams = VelocityLimitParams( - checkRequired("filters", filters), checkRequired("period", period), checkRequired("scope", scope), + filters, limitAmount, limitCount, additionalProperties.toMutableMap(), @@ -342,9 +340,9 @@ private constructor( return@apply } - filters().validate() period().validate() scope().validate() + filters().ifPresent { it.validate() } limitAmount() limitCount() validated = true @@ -365,12 +363,142 @@ private constructor( */ @JvmSynthetic internal fun validity(): Int = - (filters.asKnown().getOrNull()?.validity() ?: 0) + - (period.asKnown().getOrNull()?.validity() ?: 0) + + (period.asKnown().getOrNull()?.validity() ?: 0) + (scope.asKnown().getOrNull()?.validity() ?: 0) + + (filters.asKnown().getOrNull()?.validity() ?: 0) + (if (limitAmount.asKnown().isPresent) 1 else 0) + (if (limitCount.asKnown().isPresent) 1 else 0) + /** The scope the velocity is calculated for */ + class VelocityScope @JsonCreator private constructor(private val value: JsonField) : + Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that doesn't + * match any known member, and you want to know that value. For example, if the SDK is on an + * older version than the API, then the API may respond with new members that the SDK is + * unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val CARD = of("CARD") + + @JvmField val ACCOUNT = of("ACCOUNT") + + @JvmStatic fun of(value: String) = VelocityScope(JsonField.of(value)) + } + + /** An enum containing [VelocityScope]'s known values. */ + enum class Known { + CARD, + ACCOUNT, + } + + /** + * An enum containing [VelocityScope]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [VelocityScope] can contain an unknown value in a couple of cases: + * - It was deserialized from data that doesn't match any known member. For example, if the + * SDK is on an older version than the API, then the API may respond with new members that + * the SDK is unaware of. + * - It was constructed with an arbitrary value using the [of] method. + */ + enum class Value { + CARD, + ACCOUNT, + /** + * An enum member indicating that [VelocityScope] was instantiated with an unknown + * value. + */ + _UNKNOWN, + } + + /** + * Returns an enum member corresponding to this class instance's value, or [Value._UNKNOWN] + * if the class was instantiated with an unknown value. + * + * Use the [known] method instead if you're certain the value is always known or if you want + * to throw for the unknown case. + */ + fun value(): Value = + when (this) { + CARD -> Value.CARD + ACCOUNT -> Value.ACCOUNT + else -> Value._UNKNOWN + } + + /** + * Returns an enum member corresponding to this class instance's value. + * + * Use the [value] method instead if you're uncertain the value is always known and don't + * want to throw for the unknown case. + * + * @throws LithicInvalidDataException if this class instance's value is a not a known + * member. + */ + fun known(): Known = + when (this) { + CARD -> Known.CARD + ACCOUNT -> Known.ACCOUNT + else -> throw LithicInvalidDataException("Unknown VelocityScope: $value") + } + + /** + * Returns this class instance's primitive wire representation. + * + * This differs from the [toString] method because that method is primarily for debugging + * and generally doesn't throw. + * + * @throws LithicInvalidDataException if this class instance's value does not have the + * expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { LithicInvalidDataException("Value is not a String") } + + private var validated: Boolean = false + + fun validate(): VelocityScope = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: LithicInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is VelocityScope && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + class VelocityLimitFilters @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( @@ -1037,156 +1165,26 @@ private constructor( "VelocityLimitFilters{excludeCountries=$excludeCountries, excludeMccs=$excludeMccs, includeCountries=$includeCountries, includeMccs=$includeMccs, includePanEntryModes=$includePanEntryModes, additionalProperties=$additionalProperties}" } - /** The scope the velocity is calculated for */ - class VelocityScope @JsonCreator private constructor(private val value: JsonField) : - Enum { - - /** - * Returns this class instance's raw value. - * - * This is usually only useful if this instance was deserialized from data that doesn't - * match any known member, and you want to know that value. For example, if the SDK is on an - * older version than the API, then the API may respond with new members that the SDK is - * unaware of. - */ - @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value - - companion object { - - @JvmField val CARD = of("CARD") - - @JvmField val ACCOUNT = of("ACCOUNT") - - @JvmStatic fun of(value: String) = VelocityScope(JsonField.of(value)) - } - - /** An enum containing [VelocityScope]'s known values. */ - enum class Known { - CARD, - ACCOUNT, - } - - /** - * An enum containing [VelocityScope]'s known values, as well as an [_UNKNOWN] member. - * - * An instance of [VelocityScope] can contain an unknown value in a couple of cases: - * - It was deserialized from data that doesn't match any known member. For example, if the - * SDK is on an older version than the API, then the API may respond with new members that - * the SDK is unaware of. - * - It was constructed with an arbitrary value using the [of] method. - */ - enum class Value { - CARD, - ACCOUNT, - /** - * An enum member indicating that [VelocityScope] was instantiated with an unknown - * value. - */ - _UNKNOWN, - } - - /** - * Returns an enum member corresponding to this class instance's value, or [Value._UNKNOWN] - * if the class was instantiated with an unknown value. - * - * Use the [known] method instead if you're certain the value is always known or if you want - * to throw for the unknown case. - */ - fun value(): Value = - when (this) { - CARD -> Value.CARD - ACCOUNT -> Value.ACCOUNT - else -> Value._UNKNOWN - } - - /** - * Returns an enum member corresponding to this class instance's value. - * - * Use the [value] method instead if you're uncertain the value is always known and don't - * want to throw for the unknown case. - * - * @throws LithicInvalidDataException if this class instance's value is a not a known - * member. - */ - fun known(): Known = - when (this) { - CARD -> Known.CARD - ACCOUNT -> Known.ACCOUNT - else -> throw LithicInvalidDataException("Unknown VelocityScope: $value") - } - - /** - * Returns this class instance's primitive wire representation. - * - * This differs from the [toString] method because that method is primarily for debugging - * and generally doesn't throw. - * - * @throws LithicInvalidDataException if this class instance's value does not have the - * expected primitive type. - */ - fun asString(): String = - _value().asString().orElseThrow { LithicInvalidDataException("Value is not a String") } - - private var validated: Boolean = false - - fun validate(): VelocityScope = apply { - if (validated) { - return@apply - } - - known() - validated = true - } - - fun isValid(): Boolean = - try { - validate() - true - } catch (e: LithicInvalidDataException) { - false - } - - /** - * Returns a score indicating how many valid values are contained in this object - * recursively. - * - * Used for best match union deserialization. - */ - @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - - return other is VelocityScope && value == other.value - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - } - override fun equals(other: Any?): Boolean { if (this === other) { return true } return other is VelocityLimitParams && - filters == other.filters && period == other.period && scope == other.scope && + filters == other.filters && limitAmount == other.limitAmount && limitCount == other.limitCount && additionalProperties == other.additionalProperties } private val hashCode: Int by lazy { - Objects.hash(filters, period, scope, limitAmount, limitCount, additionalProperties) + Objects.hash(period, scope, filters, limitAmount, limitCount, additionalProperties) } override fun hashCode(): Int = hashCode override fun toString() = - "VelocityLimitParams{filters=$filters, period=$period, scope=$scope, limitAmount=$limitAmount, limitCount=$limitCount, additionalProperties=$additionalProperties}" + "VelocityLimitParams{period=$period, scope=$scope, filters=$filters, limitAmount=$limitAmount, limitCount=$limitCount, additionalProperties=$additionalProperties}" } diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/models/VelocityLimitParamsTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/models/VelocityLimitParamsTest.kt index aa7693fb..7d219a89 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/models/VelocityLimitParamsTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/models/VelocityLimitParamsTest.kt @@ -13,6 +13,13 @@ internal class VelocityLimitParamsTest { fun create() { val velocityLimitParams = VelocityLimitParams.builder() + .period( + VelocityLimitPeriod.TrailingWindowObject.builder() + .duration(10L) + .type(VelocityLimitPeriod.TrailingWindowObject.Type.CUSTOM) + .build() + ) + .scope(VelocityLimitParams.VelocityScope.CARD) .filters( VelocityLimitParams.VelocityLimitFilters.builder() .addExcludeCountry("USD") @@ -24,19 +31,22 @@ internal class VelocityLimitParamsTest { ) .build() ) - .period( + .limitAmount(10000L) + .limitCount(0L) + .build() + + assertThat(velocityLimitParams.period()) + .isEqualTo( + VelocityLimitPeriod.ofTrailingWindowObject( VelocityLimitPeriod.TrailingWindowObject.builder() .duration(10L) .type(VelocityLimitPeriod.TrailingWindowObject.Type.CUSTOM) .build() ) - .scope(VelocityLimitParams.VelocityScope.CARD) - .limitAmount(10000L) - .limitCount(0L) - .build() - + ) + assertThat(velocityLimitParams.scope()).isEqualTo(VelocityLimitParams.VelocityScope.CARD) assertThat(velocityLimitParams.filters()) - .isEqualTo( + .contains( VelocityLimitParams.VelocityLimitFilters.builder() .addExcludeCountry("USD") .addExcludeMcc("5542") @@ -47,16 +57,6 @@ internal class VelocityLimitParamsTest { ) .build() ) - assertThat(velocityLimitParams.period()) - .isEqualTo( - VelocityLimitPeriod.ofTrailingWindowObject( - VelocityLimitPeriod.TrailingWindowObject.builder() - .duration(10L) - .type(VelocityLimitPeriod.TrailingWindowObject.Type.CUSTOM) - .build() - ) - ) - assertThat(velocityLimitParams.scope()).isEqualTo(VelocityLimitParams.VelocityScope.CARD) assertThat(velocityLimitParams.limitAmount()).contains(10000L) assertThat(velocityLimitParams.limitCount()).contains(0L) } @@ -66,6 +66,13 @@ internal class VelocityLimitParamsTest { val jsonMapper = jsonMapper() val velocityLimitParams = VelocityLimitParams.builder() + .period( + VelocityLimitPeriod.TrailingWindowObject.builder() + .duration(10L) + .type(VelocityLimitPeriod.TrailingWindowObject.Type.CUSTOM) + .build() + ) + .scope(VelocityLimitParams.VelocityScope.CARD) .filters( VelocityLimitParams.VelocityLimitFilters.builder() .addExcludeCountry("USD") @@ -77,13 +84,6 @@ internal class VelocityLimitParamsTest { ) .build() ) - .period( - VelocityLimitPeriod.TrailingWindowObject.builder() - .duration(10L) - .type(VelocityLimitPeriod.TrailingWindowObject.Type.CUSTOM) - .build() - ) - .scope(VelocityLimitParams.VelocityScope.CARD) .limitAmount(10000L) .limitCount(0L) .build() From e9f3ae9d5e5797a6458138d27e3b7a21fee0e9bd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:17:58 +0000 Subject: [PATCH 05/17] fix(api): Correct field name from ach_hold__period to ach_hold_period --- .stats.yml | 6 +++--- .../kotlin/com/lithic/api/models/PaymentCreateParams.kt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.stats.yml b/.stats.yml index ac611e10..ddf55aa9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 176 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-c06c4d54858775fef1de57e33d471e997cb28fe0c925fd7b08ba45fdd335e938.yml -openapi_spec_hash: 23745357b2171bcdfb5d30bfef1df48d -config_hash: 9dddee5f7af579864599849cb28a0770 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-64e9dd6979ae45f1bb6e77b40e8244ee46673332112ebf33fe2f3a287467ce85.yml +openapi_spec_hash: ce885445b66e95c5671ee72c01882d79 +config_hash: 07f0e0f3036a4a5825cee527bc46b0b6 diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt index 5c7703b5..1769e780 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt @@ -1097,7 +1097,7 @@ private constructor( @JsonProperty("sec_code") @ExcludeMissing secCode: JsonField = JsonMissing.of(), - @JsonProperty("ach_hold__period") + @JsonProperty("ach_hold_period") @ExcludeMissing achHoldPeriod: JsonField = JsonMissing.of(), @JsonProperty("addenda") @ExcludeMissing addenda: JsonField = JsonMissing.of(), @@ -1115,7 +1115,7 @@ private constructor( * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun achHoldPeriod(): Optional = achHoldPeriod.getOptional("ach_hold__period") + fun achHoldPeriod(): Optional = achHoldPeriod.getOptional("ach_hold_period") /** * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the @@ -1136,7 +1136,7 @@ private constructor( * Unlike [achHoldPeriod], this method doesn't throw if the JSON field has an unexpected * type. */ - @JsonProperty("ach_hold__period") + @JsonProperty("ach_hold_period") @ExcludeMissing fun _achHoldPeriod(): JsonField = achHoldPeriod From bb604fbee3a368cfbe5c4e132f5974b691121255 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:03:09 +0000 Subject: [PATCH 06/17] chore: Rework event type generation to support spec splitting --- .stats.yml | 6 +++--- .../kotlin/com/lithic/api/models/PaymentCreateParams.kt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.stats.yml b/.stats.yml index ddf55aa9..ae10d57f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 176 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-64e9dd6979ae45f1bb6e77b40e8244ee46673332112ebf33fe2f3a287467ce85.yml -openapi_spec_hash: ce885445b66e95c5671ee72c01882d79 -config_hash: 07f0e0f3036a4a5825cee527bc46b0b6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-6e800837b020104545778d65b7b16bae277e6667d98044e83f3bfeacebdb489b.yml +openapi_spec_hash: 94788968e119e8665a1b0d4742565984 +config_hash: 2af43c32faa12490c9c9caa2ce62bccb diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt index 1769e780..5c7703b5 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt @@ -1097,7 +1097,7 @@ private constructor( @JsonProperty("sec_code") @ExcludeMissing secCode: JsonField = JsonMissing.of(), - @JsonProperty("ach_hold_period") + @JsonProperty("ach_hold__period") @ExcludeMissing achHoldPeriod: JsonField = JsonMissing.of(), @JsonProperty("addenda") @ExcludeMissing addenda: JsonField = JsonMissing.of(), @@ -1115,7 +1115,7 @@ private constructor( * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun achHoldPeriod(): Optional = achHoldPeriod.getOptional("ach_hold_period") + fun achHoldPeriod(): Optional = achHoldPeriod.getOptional("ach_hold__period") /** * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the @@ -1136,7 +1136,7 @@ private constructor( * Unlike [achHoldPeriod], this method doesn't throw if the JSON field has an unexpected * type. */ - @JsonProperty("ach_hold_period") + @JsonProperty("ach_hold__period") @ExcludeMissing fun _achHoldPeriod(): JsonField = achHoldPeriod From e4de24f1c5a533c8d8074fac447d64236d276dc4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 20:08:43 +0000 Subject: [PATCH 07/17] feat(api): Add idempotency key to cards POST and PATCH endpoints --- .stats.yml | 4 +- .../com/lithic/api/models/CardCreateParams.kt | 32 ++++++++-- .../lithic/api/models/CardCreateParamsTest.kt | 63 +++++++++++++++++++ .../FinancialAccountCreateParamsTest.kt | 8 +-- .../lithic/api/services/ErrorHandlingTest.kt | 17 +++++ .../lithic/api/services/ServiceParamsTest.kt | 1 + .../services/async/CardServiceAsyncTest.kt | 1 + .../async/FinancialAccountServiceAsyncTest.kt | 2 +- .../api/services/blocking/CardServiceTest.kt | 1 + .../blocking/FinancialAccountServiceTest.kt | 2 +- 10 files changed, 119 insertions(+), 12 deletions(-) diff --git a/.stats.yml b/.stats.yml index ae10d57f..bd16674f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 176 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-6e800837b020104545778d65b7b16bae277e6667d98044e83f3bfeacebdb489b.yml -openapi_spec_hash: 94788968e119e8665a1b0d4742565984 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-eeeb62a4869ba1436c9252f9630006a829695178e86305aea232f6be0d1e3d81.yml +openapi_spec_hash: 25bf9c499cd22240949862e622c534f2 config_hash: 2af43c32faa12490c9c9caa2ce62bccb diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardCreateParams.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardCreateParams.kt index dfb0bfaa..6503adf8 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardCreateParams.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardCreateParams.kt @@ -27,11 +27,14 @@ import kotlin.jvm.optionals.getOrNull */ class CardCreateParams private constructor( + private val idempotencyKey: String?, private val body: Body, private val additionalHeaders: Headers, private val additionalQueryParams: QueryParams, ) : Params { + fun idempotencyKey(): Optional = Optional.ofNullable(idempotencyKey) + /** * Card types: * * `VIRTUAL` - Card will authorize at any merchant and can be added to a digital wallet like @@ -437,17 +440,25 @@ private constructor( /** A builder for [CardCreateParams]. */ class Builder internal constructor() { + private var idempotencyKey: String? = null private var body: Body.Builder = Body.builder() private var additionalHeaders: Headers.Builder = Headers.builder() private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() @JvmSynthetic internal fun from(cardCreateParams: CardCreateParams) = apply { + idempotencyKey = cardCreateParams.idempotencyKey body = cardCreateParams.body.toBuilder() additionalHeaders = cardCreateParams.additionalHeaders.toBuilder() additionalQueryParams = cardCreateParams.additionalQueryParams.toBuilder() } + fun idempotencyKey(idempotencyKey: String?) = apply { this.idempotencyKey = idempotencyKey } + + /** Alias for calling [Builder.idempotencyKey] with `idempotencyKey.orElse(null)`. */ + fun idempotencyKey(idempotencyKey: Optional) = + idempotencyKey(idempotencyKey.getOrNull()) + /** * Sets the entire request body. * @@ -974,12 +985,23 @@ private constructor( * @throws IllegalStateException if any required field is unset. */ fun build(): CardCreateParams = - CardCreateParams(body.build(), additionalHeaders.build(), additionalQueryParams.build()) + CardCreateParams( + idempotencyKey, + body.build(), + additionalHeaders.build(), + additionalQueryParams.build(), + ) } fun _body(): Body = body - override fun _headers(): Headers = additionalHeaders + override fun _headers(): Headers = + Headers.builder() + .apply { + idempotencyKey?.let { put("Idempotency-Key", it) } + putAll(additionalHeaders) + } + .build() override fun _queryParams(): QueryParams = additionalQueryParams @@ -2835,13 +2857,15 @@ private constructor( } return other is CardCreateParams && + idempotencyKey == other.idempotencyKey && body == other.body && additionalHeaders == other.additionalHeaders && additionalQueryParams == other.additionalQueryParams } - override fun hashCode(): Int = Objects.hash(body, additionalHeaders, additionalQueryParams) + override fun hashCode(): Int = + Objects.hash(idempotencyKey, body, additionalHeaders, additionalQueryParams) override fun toString() = - "CardCreateParams{body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "CardCreateParams{idempotencyKey=$idempotencyKey, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/models/CardCreateParamsTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/models/CardCreateParamsTest.kt index d570ffb8..a46f5a6d 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/models/CardCreateParamsTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/models/CardCreateParamsTest.kt @@ -2,6 +2,7 @@ package com.lithic.api.models +import com.lithic.api.core.http.Headers import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -10,6 +11,7 @@ internal class CardCreateParamsTest { @Test fun create() { CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -47,10 +49,71 @@ internal class CardCreateParamsTest { .build() } + @Test + fun headers() { + val params = + CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") + .type(CardCreateParams.Type.VIRTUAL) + .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") + .cardProgramToken("5e9483eb-8103-4e16-9794-2106111b2eca") + .carrier(Carrier.builder().qrCodeUrl("qr_code_url").build()) + .digitalCardArtToken("5e9483eb-8103-4e16-9794-2106111b2eca") + .expMonth("06") + .expYear("2027") + .memo("New Card") + .pin("pin") + .productId("1") + .replacementAccountToken("5e9483eb-8103-4e16-9794-2106111b2eca") + .replacementComment("replacement_comment") + .replacementFor("5e9483eb-8103-4e16-9794-2106111b2eca") + .replacementSubstatus(CardCreateParams.ReplacementSubstatus.LOST) + .shippingAddress( + ShippingAddress.builder() + .address1("5 Broad Street") + .city("NEW YORK") + .country("USA") + .firstName("Michael") + .lastName("Bluth") + .postalCode("10001-1809") + .state("NY") + .address2("Unit 25A") + .email("johnny@appleseed.com") + .line2Text("The Bluth Company") + .phoneNumber("+15555555555") + .build() + ) + .shippingMethod(CardCreateParams.ShippingMethod._2_DAY) + .spendLimit(1000L) + .spendLimitDuration(SpendLimitDuration.TRANSACTION) + .state(CardCreateParams.State.OPEN) + .build() + + val headers = params._headers() + + assertThat(headers) + .isEqualTo( + Headers.builder() + .put("Idempotency-Key", "65a9dad4-1b60-4686-83fd-65b25078a4b4") + .build() + ) + } + + @Test + fun headersWithoutOptionalFields() { + val params = CardCreateParams.builder().type(CardCreateParams.Type.VIRTUAL).build() + + val headers = params._headers() + + assertThat(headers).isEqualTo(Headers.builder().build()) + } + @Test fun body() { val params = CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/models/FinancialAccountCreateParamsTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/models/FinancialAccountCreateParamsTest.kt index ddb3a154..e536da76 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/models/FinancialAccountCreateParamsTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/models/FinancialAccountCreateParamsTest.kt @@ -11,7 +11,7 @@ internal class FinancialAccountCreateParamsTest { @Test fun create() { FinancialAccountCreateParams.builder() - .idempotencyKey("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .nickname("nickname") .type(FinancialAccountCreateParams.Type.OPERATING) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") @@ -23,7 +23,7 @@ internal class FinancialAccountCreateParamsTest { fun headers() { val params = FinancialAccountCreateParams.builder() - .idempotencyKey("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .nickname("nickname") .type(FinancialAccountCreateParams.Type.OPERATING) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") @@ -35,7 +35,7 @@ internal class FinancialAccountCreateParamsTest { assertThat(headers) .isEqualTo( Headers.builder() - .put("Idempotency-Key", "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .put("Idempotency-Key", "65a9dad4-1b60-4686-83fd-65b25078a4b4") .build() ) } @@ -57,7 +57,7 @@ internal class FinancialAccountCreateParamsTest { fun body() { val params = FinancialAccountCreateParams.builder() - .idempotencyKey("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .nickname("nickname") .type(FinancialAccountCreateParams.Type.OPERATING) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/services/ErrorHandlingTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/services/ErrorHandlingTest.kt index 29d4f88b..d9911f25 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/services/ErrorHandlingTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/services/ErrorHandlingTest.kt @@ -75,6 +75,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -132,6 +133,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -189,6 +191,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -246,6 +249,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -303,6 +307,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -360,6 +365,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -417,6 +423,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -474,6 +481,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -531,6 +539,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -588,6 +597,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -645,6 +655,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -702,6 +713,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -759,6 +771,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -816,6 +829,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -873,6 +887,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -930,6 +945,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") @@ -985,6 +1001,7 @@ internal class ErrorHandlingTest { assertThrows { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/services/ServiceParamsTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/services/ServiceParamsTest.kt index 4e1916e3..43908cda 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/services/ServiceParamsTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/services/ServiceParamsTest.kt @@ -45,6 +45,7 @@ internal class ServiceParamsTest { cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/CardServiceAsyncTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/CardServiceAsyncTest.kt index 0c172028..04ab7e35 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/CardServiceAsyncTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/CardServiceAsyncTest.kt @@ -34,6 +34,7 @@ internal class CardServiceAsyncTest { val cardFuture = cardServiceAsync.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/FinancialAccountServiceAsyncTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/FinancialAccountServiceAsyncTest.kt index 3635d8fa..e7d77c8c 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/FinancialAccountServiceAsyncTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/FinancialAccountServiceAsyncTest.kt @@ -26,7 +26,7 @@ internal class FinancialAccountServiceAsyncTest { val financialAccountFuture = financialAccountServiceAsync.create( FinancialAccountCreateParams.builder() - .idempotencyKey("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .nickname("nickname") .type(FinancialAccountCreateParams.Type.OPERATING) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/CardServiceTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/CardServiceTest.kt index 133e2da4..e24c6e1d 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/CardServiceTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/CardServiceTest.kt @@ -34,6 +34,7 @@ internal class CardServiceTest { val card = cardService.create( CardCreateParams.builder() + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .type(CardCreateParams.Type.VIRTUAL) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") .bulkOrderToken("5e9483eb-8103-4e16-9794-2106111b2eca") diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/FinancialAccountServiceTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/FinancialAccountServiceTest.kt index e67c7c0f..3ecb6bad 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/FinancialAccountServiceTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/FinancialAccountServiceTest.kt @@ -26,7 +26,7 @@ internal class FinancialAccountServiceTest { val financialAccount = financialAccountService.create( FinancialAccountCreateParams.builder() - .idempotencyKey("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .idempotencyKey("65a9dad4-1b60-4686-83fd-65b25078a4b4") .nickname("nickname") .type(FinancialAccountCreateParams.Type.OPERATING) .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") From 7838ba8e1c5a14df27507a5aa6b7fb6444531a98 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 19:40:07 +0000 Subject: [PATCH 08/17] chore(internal): clean up maven repo artifact script and add html documentation to repo root --- scripts/upload-artifacts | 44 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts index 729e6f22..df0c8d9f 100755 --- a/scripts/upload-artifacts +++ b/scripts/upload-artifacts @@ -7,6 +7,8 @@ GREEN='\033[32m' RED='\033[31m' NC='\033[0m' # No Color +MAVEN_REPO_PATH="./build/local-maven-repo" + log_error() { local msg="$1" local headers="$2" @@ -24,7 +26,7 @@ upload_file() { if [ -f "$file_name" ]; then echo -e "${GREEN}Processing file: $file_name${NC}" - pkg_file_name="mvn${file_name#./build/local-maven-repo}" + pkg_file_name="mvn${file_name#"${MAVEN_REPO_PATH}"}" # Get signed URL for uploading artifact file signed_url_response=$(curl -X POST -G "$URL" \ @@ -47,6 +49,7 @@ upload_file() { md5|sha1|sha256|sha512) content_type="text/plain" ;; module) content_type="application/json" ;; pom|xml) content_type="application/xml" ;; + html) content_type="text/html" ;; *) content_type="application/octet-stream" ;; esac @@ -81,6 +84,41 @@ walk_tree() { done } +generate_instructions() { + cat << EOF > "$MAVEN_REPO_PATH/index.html" + + + + Maven Repo + + +

Stainless SDK Maven Repository

+

This is the Maven repository for your Stainless Java SDK build.

+ +

Directions

+

To use the uploaded Maven repository, add the following to your project's pom.xml:

+
<repositories>
+    <repository>
+        <id>stainless-sdk-repo</id>
+        <url>https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn</url>
+    </repository>
+</repositories>
+ +

If you're using Gradle, add the following to your build.gradle file:

+
repositories {
+    maven {
+        url 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'
+    }
+}
+ + +EOF + upload_file "${MAVEN_REPO_PATH}/index.html" + + echo "Configure maven or gradle to use the repo located at 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'" + echo "For more details, see the directions in https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn/index.html" +} + cd "$(dirname "$0")/.." echo "::group::Creating local Maven content" @@ -88,9 +126,9 @@ echo "::group::Creating local Maven content" echo "::endgroup::" echo "::group::Uploading to pkg.stainless.com" -walk_tree "./build/local-maven-repo" +walk_tree "$MAVEN_REPO_PATH" echo "::endgroup::" echo "::group::Generating instructions" -echo "Configure maven or gradle to use the repo located at 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'" +generate_instructions echo "::endgroup::" From ad467a4ae4e51a2dfd5e9b7be5800ae9d4ca56fc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 06:16:01 +0000 Subject: [PATCH 09/17] chore: test on Jackson 2.14.0 to avoid encountering FasterXML/jackson-databind#3240 in tests fix: date time deserialization leniency --- README.md | 2 ++ lithic-java-core/build.gradle.kts | 18 ++++++----- .../com/lithic/api/core/ObjectMappers.kt | 32 ++++++++++++------- .../api/models/CardProvisionResponse.kt | 2 +- .../com/lithic/api/models/ConditionalValue.kt | 2 +- .../com/lithic/api/core/ObjectMappersTest.kt | 15 ++------- .../lithic/api/models/ConditionalValueTest.kt | 17 +++++++--- lithic-java-proguard-test/build.gradle.kts | 2 +- 8 files changed, 51 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 234c6d0f..d23ab76b 100644 --- a/README.md +++ b/README.md @@ -416,6 +416,8 @@ If the SDK threw an exception, but you're _certain_ the version is compatible, t > [!CAUTION] > We make no guarantee that the SDK works correctly when the Jackson version check is disabled. +Also note that there are bugs in older Jackson versions that can affect the SDK. We don't work around all Jackson bugs ([example](https://github.com/FasterXML/jackson-databind/issues/3240)) and expect users to upgrade Jackson for those instead. + ## Network options ### Retries diff --git a/lithic-java-core/build.gradle.kts b/lithic-java-core/build.gradle.kts index 477ecb78..151c17eb 100644 --- a/lithic-java-core/build.gradle.kts +++ b/lithic-java-core/build.gradle.kts @@ -5,14 +5,16 @@ plugins { configurations.all { resolutionStrategy { - // Compile and test against a lower Jackson version to ensure we're compatible with it. - // We publish with a higher version (see below) to ensure users depend on a secure version by default. - force("com.fasterxml.jackson.core:jackson-core:2.13.4") - force("com.fasterxml.jackson.core:jackson-databind:2.13.4") - force("com.fasterxml.jackson.core:jackson-annotations:2.13.4") - force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.4") - force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4") - force("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4") + // Compile and test against a lower Jackson version to ensure we're compatible with it. Note that + // we generally support 2.13.4, but test against 2.14.0 because 2.13.4 has some annoying (but + // niche) bugs (users should upgrade if they encounter them). We publish with a higher version + // (see below) to ensure users depend on a secure version by default. + force("com.fasterxml.jackson.core:jackson-core:2.14.0") + force("com.fasterxml.jackson.core:jackson-databind:2.14.0") + force("com.fasterxml.jackson.core:jackson-annotations:2.14.0") + force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.0") + force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.0") + force("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0") } } diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt index 1154cadf..2414c30a 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt @@ -37,7 +37,7 @@ fun jsonMapper(): JsonMapper = .addModule( SimpleModule() .addSerializer(InputStreamSerializer) - .addDeserializer(LocalDateTime::class.java, LenientLocalDateTimeDeserializer()) + .addDeserializer(OffsetDateTime::class.java, LenientOffsetDateTimeDeserializer()) .addDeserializer(OffsetDateTime::class.java, LenientOffsetDateTimeDeserializer()) ) .withCoercionConfig(LogicalType.Boolean) { @@ -66,6 +66,12 @@ fun jsonMapper(): JsonMapper = .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) } + .withCoercionConfig(LogicalType.DateTime) { + it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Float, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) + } .withCoercionConfig(LogicalType.Array) { it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail) .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) @@ -126,10 +132,10 @@ private object InputStreamSerializer : BaseSerializer(InputStream:: } /** - * A deserializer that can deserialize [LocalDateTime] from datetimes, dates, and zoned datetimes. + * A deserializer that can deserialize [OffsetDateTime] from datetimes, dates, and zoned datetimes. */ -private class LenientLocalDateTimeDeserializer : - StdDeserializer(LocalDateTime::class.java) { +private class LenientOffsetDateTimeDeserializer : + StdDeserializer(OffsetDateTime::class.java) { companion object { @@ -143,7 +149,7 @@ private class LenientLocalDateTimeDeserializer : override fun logicalType(): LogicalType = LogicalType.DateTime - override fun deserialize(p: JsonParser, context: DeserializationContext?): LocalDateTime { + override fun deserialize(p: JsonParser, context: DeserializationContext): OffsetDateTime { val exceptions = mutableListOf() for (formatter in DATE_TIME_FORMATTERS) { @@ -151,18 +157,20 @@ private class LenientLocalDateTimeDeserializer : val temporal = formatter.parse(p.text) return when { - !temporal.isSupported(ChronoField.HOUR_OF_DAY) -> - LocalDate.from(temporal).atStartOfDay() - !temporal.isSupported(ChronoField.OFFSET_SECONDS) -> - LocalDateTime.from(temporal) - else -> ZonedDateTime.from(temporal).toLocalDateTime() - } + !temporal.isSupported(ChronoField.HOUR_OF_DAY) -> + LocalDate.from(temporal).atStartOfDay() + !temporal.isSupported(ChronoField.OFFSET_SECONDS) -> + LocalDateTime.from(temporal) + else -> ZonedDateTime.from(temporal).toLocalDateTime() + } + .atZone(context.timeZone.toZoneId()) + .toOffsetDateTime() } catch (e: DateTimeException) { exceptions.add(e) } } - throw JsonParseException(p, "Cannot parse `LocalDateTime` from value: ${p.text}").apply { + throw JsonParseException(p, "Cannot parse `OffsetDateTime` from value: ${p.text}").apply { exceptions.forEach { addSuppressed(it) } } } diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardProvisionResponse.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardProvisionResponse.kt index bdbfb8a8..8382e238 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardProvisionResponse.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardProvisionResponse.kt @@ -364,7 +364,7 @@ private constructor( .toList() return when (bestMatches.size) { // This can happen if what we're deserializing is completely incompatible with - // all the possible variants (e.g. deserializing from array). + // all the possible variants (e.g. deserializing from boolean). 0 -> ProvisioningPayload(_json = json) 1 -> bestMatches.single() // If there's more than one match with the highest validity, then use the first diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/ConditionalValue.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/ConditionalValue.kt index 228c8504..9ba914ec 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/ConditionalValue.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/ConditionalValue.kt @@ -247,7 +247,7 @@ private constructor( .toList() return when (bestMatches.size) { // This can happen if what we're deserializing is completely incompatible with all - // the possible variants (e.g. deserializing from object). + // the possible variants (e.g. deserializing from boolean). 0 -> ConditionalValue(_json = json) 1 -> bestMatches.single() // If there's more than one match with the highest validity, then use the first diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt index 3977f517..6dd2c82b 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt @@ -3,7 +3,6 @@ package com.lithic.api.core import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.exc.MismatchedInputException import com.fasterxml.jackson.module.kotlin.readValue -import java.time.LocalDateTime import java.time.OffsetDateTime import kotlin.reflect.KClass import org.assertj.core.api.Assertions.assertThat @@ -59,14 +58,6 @@ internal class ObjectMappersTest { LONG to DOUBLE, LONG to INTEGER, CLASS to MAP, - // These aren't actually valid, but coercion configs don't work for String until - // v2.14.0: https://github.com/FasterXML/jackson-databind/issues/3240 - // We currently test on v2.13.4. - BOOLEAN to STRING, - FLOAT to STRING, - DOUBLE to STRING, - INTEGER to STRING, - LONG to STRING, ) } } @@ -85,7 +76,7 @@ internal class ObjectMappersTest { } } - enum class LenientLocalDateTimeTestCase(val string: String) { + enum class LenientOffsetDateTimeTestCase(val string: String) { DATE("1998-04-21"), DATE_TIME("1998-04-21T04:00:00"), ZONED_DATE_TIME_1("1998-04-21T04:00:00+03:00"), @@ -94,11 +85,11 @@ internal class ObjectMappersTest { @ParameterizedTest @EnumSource - fun readLocalDateTime_lenient(testCase: LenientLocalDateTimeTestCase) { + fun readOffsetDateTime_lenient(testCase: LenientOffsetDateTimeTestCase) { val jsonMapper = jsonMapper() val json = jsonMapper.writeValueAsString(testCase.string) - assertDoesNotThrow { jsonMapper().readValue(json) } + assertDoesNotThrow { jsonMapper().readValue(json) } } enum class LenientOffsetDateTimeTestCase(val string: String) { diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/models/ConditionalValueTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/models/ConditionalValueTest.kt index 2c02b8bd..62d66056 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/models/ConditionalValueTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/models/ConditionalValueTest.kt @@ -10,6 +10,8 @@ import java.time.OffsetDateTime import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource internal class ConditionalValueTest { @@ -118,10 +120,17 @@ internal class ConditionalValueTest { assertThat(roundtrippedConditionalValue).isEqualTo(conditionalValue) } - @Test - fun incompatibleJsonShapeDeserializesToUnknown() { - val value = JsonValue.from(mapOf("invalid" to "object")) - val conditionalValue = jsonMapper().convertValue(value, jacksonTypeRef()) + enum class IncompatibleJsonShapeTestCase(val value: JsonValue) { + BOOLEAN(JsonValue.from(false)), + FLOAT(JsonValue.from(3.14)), + OBJECT(JsonValue.from(mapOf("invalid" to "object"))), + } + + @ParameterizedTest + @EnumSource + fun incompatibleJsonShapeDeserializesToUnknown(testCase: IncompatibleJsonShapeTestCase) { + val conditionalValue = + jsonMapper().convertValue(testCase.value, jacksonTypeRef()) val e = assertThrows { conditionalValue.validate() } assertThat(e).hasMessageStartingWith("Unknown ") diff --git a/lithic-java-proguard-test/build.gradle.kts b/lithic-java-proguard-test/build.gradle.kts index d4d64f68..9f9c7fba 100644 --- a/lithic-java-proguard-test/build.gradle.kts +++ b/lithic-java-proguard-test/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { testImplementation(kotlin("test")) testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3") testImplementation("org.assertj:assertj-core:3.25.3") - testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4") + testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0") } tasks.shadowJar { From 464fcaeefe3040303874976e8746601fb1d2cb0b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 12:57:39 +0000 Subject: [PATCH 10/17] fix(api): mark AppleWebPushProvisioningResponse fields required --- .stats.yml | 6 +-- .../api/models/CardWebProvisionResponse.kt | 39 ++++++++++++++----- .../lithic/api/models/PaymentCreateParams.kt | 6 +-- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/.stats.yml b/.stats.yml index bd16674f..021c6dec 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 176 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-eeeb62a4869ba1436c9252f9630006a829695178e86305aea232f6be0d1e3d81.yml -openapi_spec_hash: 25bf9c499cd22240949862e622c534f2 -config_hash: 2af43c32faa12490c9c9caa2ce62bccb +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-d6e80e52c9f20d95780f2cf4869f80ee2c4b270ff9470941dc057e79d15bda1a.yml +openapi_spec_hash: f2bb7084cd5225769302589cd1563241 +config_hash: 31d71922d7838f34ae0875c9b8026d99 diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardWebProvisionResponse.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardWebProvisionResponse.kt index 687d915a..28fece95 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardWebProvisionResponse.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardWebProvisionResponse.kt @@ -20,6 +20,7 @@ import com.lithic.api.core.JsonField import com.lithic.api.core.JsonMissing import com.lithic.api.core.JsonValue import com.lithic.api.core.allMaxBy +import com.lithic.api.core.checkRequired import com.lithic.api.core.getOrThrow import com.lithic.api.errors.LithicInvalidDataException import java.util.Collections @@ -256,18 +257,18 @@ private constructor( /** * JWS object required for handoff to Apple's script. * - * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the - * server responded with an unexpected value). + * @throws LithicInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). */ - fun jws(): Optional = jws.getOptional("jws") + fun jws(): WebPushProvisioningResponseJws = jws.getRequired("jws") /** * A unique identifier for the JWS object. * - * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the - * server responded with an unexpected value). + * @throws LithicInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). */ - fun state(): Optional = state.getOptional("state") + fun state(): String = state.getRequired("state") /** * Returns the raw JSON value of [jws]. @@ -302,6 +303,12 @@ private constructor( /** * Returns a mutable builder for constructing an instance of * [AppleWebPushProvisioningResponse]. + * + * The following fields are required: + * ```java + * .jws() + * .state() + * ``` */ @JvmStatic fun builder() = Builder() } @@ -309,8 +316,8 @@ private constructor( /** A builder for [AppleWebPushProvisioningResponse]. */ class Builder internal constructor() { - private var jws: JsonField = JsonMissing.of() - private var state: JsonField = JsonMissing.of() + private var jws: JsonField? = null + private var state: JsonField? = null private var additionalProperties: MutableMap = mutableMapOf() @JvmSynthetic @@ -369,9 +376,21 @@ private constructor( * Returns an immutable instance of [AppleWebPushProvisioningResponse]. * * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .jws() + * .state() + * ``` + * + * @throws IllegalStateException if any required field is unset. */ fun build(): AppleWebPushProvisioningResponse = - AppleWebPushProvisioningResponse(jws, state, additionalProperties.toMutableMap()) + AppleWebPushProvisioningResponse( + checkRequired("jws", jws), + checkRequired("state", state), + additionalProperties.toMutableMap(), + ) } private var validated: Boolean = false @@ -381,7 +400,7 @@ private constructor( return@apply } - jws().ifPresent { it.validate() } + jws().validate() state() validated = true } diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt index 5c7703b5..1769e780 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/PaymentCreateParams.kt @@ -1097,7 +1097,7 @@ private constructor( @JsonProperty("sec_code") @ExcludeMissing secCode: JsonField = JsonMissing.of(), - @JsonProperty("ach_hold__period") + @JsonProperty("ach_hold_period") @ExcludeMissing achHoldPeriod: JsonField = JsonMissing.of(), @JsonProperty("addenda") @ExcludeMissing addenda: JsonField = JsonMissing.of(), @@ -1115,7 +1115,7 @@ private constructor( * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun achHoldPeriod(): Optional = achHoldPeriod.getOptional("ach_hold__period") + fun achHoldPeriod(): Optional = achHoldPeriod.getOptional("ach_hold_period") /** * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the @@ -1136,7 +1136,7 @@ private constructor( * Unlike [achHoldPeriod], this method doesn't throw if the JSON field has an unexpected * type. */ - @JsonProperty("ach_hold__period") + @JsonProperty("ach_hold_period") @ExcludeMissing fun _achHoldPeriod(): JsonField = achHoldPeriod From 737c025a59820d23176d4e45c50f9ab0781a791c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:08:43 +0000 Subject: [PATCH 11/17] chore(internal): improve maven repo docs --- scripts/upload-artifacts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts index df0c8d9f..548d1527 100755 --- a/scripts/upload-artifacts +++ b/scripts/upload-artifacts @@ -56,12 +56,13 @@ upload_file() { # Upload file upload_response=$(curl -v -X PUT \ --retry 5 \ + --retry-all-errors \ -D "$tmp_headers" \ -H "Content-Type: $content_type" \ --data-binary "@${file_name}" "$signed_url" 2>&1) if ! echo "$upload_response" | grep -q "HTTP/[0-9.]* 200"; then - log_error "Failed upload artifact file" "$tmp_headers" "$upload_response" + log_error "Failed to upload artifact file" "$tmp_headers" "$upload_response" fi # Insert small throttle to reduce rate limiting risk @@ -110,6 +111,10 @@ generate_instructions() { url 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn' } } + +

Once you've added the repository, you can include dependencies from it as usual. See your + project README + for more details.

EOF From ee7c05b2ab02d8cef3e3283ef2b4a1007d997e95 Mon Sep 17 00:00:00 2001 From: Tomer Aberbach Date: Fri, 16 Jan 2026 11:32:43 -0500 Subject: [PATCH 12/17] chore: fix build error --- .../com/lithic/api/core/ObjectMappers.kt | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt index 2414c30a..434ee65a 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt @@ -22,12 +22,7 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.kotlin.kotlinModule import java.io.InputStream import java.time.DateTimeException -import java.time.LocalDate -import java.time.LocalDateTime import java.time.OffsetDateTime -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.time.temporal.ChronoField fun jsonMapper(): JsonMapper = JsonMapper.builder() @@ -38,7 +33,6 @@ fun jsonMapper(): JsonMapper = SimpleModule() .addSerializer(InputStreamSerializer) .addDeserializer(OffsetDateTime::class.java, LenientOffsetDateTimeDeserializer()) - .addDeserializer(OffsetDateTime::class.java, LenientOffsetDateTimeDeserializer()) ) .withCoercionConfig(LogicalType.Boolean) { it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) @@ -131,51 +125,6 @@ private object InputStreamSerializer : BaseSerializer(InputStream:: } } -/** - * A deserializer that can deserialize [OffsetDateTime] from datetimes, dates, and zoned datetimes. - */ -private class LenientOffsetDateTimeDeserializer : - StdDeserializer(OffsetDateTime::class.java) { - - companion object { - - private val DATE_TIME_FORMATTERS = - listOf( - DateTimeFormatter.ISO_LOCAL_DATE_TIME, - DateTimeFormatter.ISO_LOCAL_DATE, - DateTimeFormatter.ISO_ZONED_DATE_TIME, - ) - } - - override fun logicalType(): LogicalType = LogicalType.DateTime - - override fun deserialize(p: JsonParser, context: DeserializationContext): OffsetDateTime { - val exceptions = mutableListOf() - - for (formatter in DATE_TIME_FORMATTERS) { - try { - val temporal = formatter.parse(p.text) - - return when { - !temporal.isSupported(ChronoField.HOUR_OF_DAY) -> - LocalDate.from(temporal).atStartOfDay() - !temporal.isSupported(ChronoField.OFFSET_SECONDS) -> - LocalDateTime.from(temporal) - else -> ZonedDateTime.from(temporal).toLocalDateTime() - } - .atZone(context.timeZone.toZoneId()) - .toOffsetDateTime() - } catch (e: DateTimeException) { - exceptions.add(e) - } - } - - throw JsonParseException(p, "Cannot parse `OffsetDateTime` from value: ${p.text}").apply { - exceptions.forEach { addSuppressed(it) } - } - } -} - /** * A deserializer that can deserialize [OffsetDateTime], assuming UTC when a timezone isn't given. */ From d9046f9a5eca4e5d4b8964c70a1c2144941d9b6a Mon Sep 17 00:00:00 2001 From: Tomer Aberbach Date: Fri, 16 Jan 2026 12:26:24 -0500 Subject: [PATCH 13/17] chore: fix build error --- .../com/lithic/api/core/ObjectMappersTest.kt | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt index 6dd2c82b..48cc29c6 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt @@ -91,19 +91,4 @@ internal class ObjectMappersTest { assertDoesNotThrow { jsonMapper().readValue(json) } } - - enum class LenientOffsetDateTimeTestCase(val string: String) { - DATE_TIME("1998-04-21T04:00:00"), - ZONED_DATE_TIME_1("1998-04-21T04:00:00+03:00"), - ZONED_DATE_TIME_2("1998-04-21T04:00:00Z"), - } - - @ParameterizedTest - @EnumSource - fun readOffsetDateTime_lenient(testCase: LenientOffsetDateTimeTestCase) { - val jsonMapper = jsonMapper() - val json = jsonMapper.writeValueAsString(testCase.string) - - assertDoesNotThrow { jsonMapper().readValue(json) } - } } From 29f6004a8cbba567f64ce4f0f681d4d5c7ef94f3 Mon Sep 17 00:00:00 2001 From: Tomer Aberbach Date: Fri, 16 Jan 2026 12:36:04 -0500 Subject: [PATCH 14/17] chore: fix test --- .../src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt index 48cc29c6..47d462ea 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt @@ -77,7 +77,6 @@ internal class ObjectMappersTest { } enum class LenientOffsetDateTimeTestCase(val string: String) { - DATE("1998-04-21"), DATE_TIME("1998-04-21T04:00:00"), ZONED_DATE_TIME_1("1998-04-21T04:00:00+03:00"), ZONED_DATE_TIME_2("1998-04-21T04:00:00Z"), From 126f8126438b97c2cc516aba5d85bf4abbe08352 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:24:37 +0000 Subject: [PATCH 15/17] fix(client): disallow coercion from float to int --- .../src/main/kotlin/com/lithic/api/core/ObjectMappers.kt | 1 + .../src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt index 434ee65a..bf3c1e2d 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ObjectMappers.kt @@ -43,6 +43,7 @@ fun jsonMapper(): JsonMapper = } .withCoercionConfig(LogicalType.Integer) { it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Float, CoercionAction.Fail) .setCoercion(CoercionInputShape.String, CoercionAction.Fail) .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt index 47d462ea..31927eea 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/core/ObjectMappersTest.kt @@ -46,11 +46,7 @@ internal class ObjectMappersTest { val VALID_CONVERSIONS = listOf( FLOAT to DOUBLE, - FLOAT to INTEGER, - FLOAT to LONG, DOUBLE to FLOAT, - DOUBLE to INTEGER, - DOUBLE to LONG, INTEGER to FLOAT, INTEGER to DOUBLE, INTEGER to LONG, From 6764fb4c842d48cffa5a6742f4c0a9ef132e0329 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:33:36 +0000 Subject: [PATCH 16/17] chore(internal): update `actions/checkout` version --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish-sonatype.yml | 2 +- .github/workflows/release-doctor.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3fe4992..5d11fa22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 @@ -47,7 +47,7 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 @@ -85,7 +85,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/lithic-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml index cd7b92ee..8db04218 100644 --- a/.github/workflows/publish-sonatype.yml +++ b/.github/workflows/publish-sonatype.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 445c0f6f..8cee1761 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'lithic-com/lithic-java' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Check release environment run: | From 401ef470fecba2a751f42d2dc1fdfb831e1aa1b7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:34:03 +0000 Subject: [PATCH 17/17] release: 0.116.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ README.md | 10 +++++----- build.gradle.kts | 2 +- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d3dc9f51..988e843f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.115.0" + ".": "0.116.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c879280a..231c10b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## 0.116.0 (2026-01-16) + +Full Changelog: [v0.115.0...v0.116.0](https://github.com/lithic-com/lithic-java/compare/v0.115.0...v0.116.0) + +### Features + +* **api:** Add idempotency key to cards POST and PATCH endpoints ([e4de24f](https://github.com/lithic-com/lithic-java/commit/e4de24f1c5a533c8d8074fac447d64236d276dc4)) +* **api:** make filter optional for Spend Velocity Auth Rules ([6122ecc](https://github.com/lithic-com/lithic-java/commit/6122eccf30d48db7aaba2fd6f8e94bf76fc7ceb4)) + + +### Bug Fixes + +* **api:** Correct field name from ach_hold__period to ach_hold_period ([e9f3ae9](https://github.com/lithic-com/lithic-java/commit/e9f3ae9d5e5797a6458138d27e3b7a21fee0e9bd)) +* **api:** mark AppleWebPushProvisioningResponse fields required ([464fcae](https://github.com/lithic-com/lithic-java/commit/464fcaeefe3040303874976e8746601fb1d2cb0b)) +* **api:** rename WIRE_DRAWDOWN_REQUEST to WIRE_INBOUND_DRAWDOWN_REQUEST ([6122ecc](https://github.com/lithic-com/lithic-java/commit/6122eccf30d48db7aaba2fd6f8e94bf76fc7ceb4)) +* **client:** disallow coercion from float to int ([126f812](https://github.com/lithic-com/lithic-java/commit/126f8126438b97c2cc516aba5d85bf4abbe08352)) +* date time deserialization leniency ([ad467a4](https://github.com/lithic-com/lithic-java/commit/ad467a4ae4e51a2dfd5e9b7be5800ae9d4ca56fc)) + + +### Chores + +* configure new SDK language ([3dc7d3f](https://github.com/lithic-com/lithic-java/commit/3dc7d3f2903edec88f307dfa5d17e6b5f377e3f0)) +* fix build error ([d9046f9](https://github.com/lithic-com/lithic-java/commit/d9046f9a5eca4e5d4b8964c70a1c2144941d9b6a)) +* fix build error ([ee7c05b](https://github.com/lithic-com/lithic-java/commit/ee7c05b2ab02d8cef3e3283ef2b4a1007d997e95)) +* fix test ([29f6004](https://github.com/lithic-com/lithic-java/commit/29f6004a8cbba567f64ce4f0f681d4d5c7ef94f3)) +* **internal:** clean up maven repo artifact script and add html documentation to repo root ([7838ba8](https://github.com/lithic-com/lithic-java/commit/7838ba8e1c5a14df27507a5aa6b7fb6444531a98)) +* **internal:** codegen related update ([6b3f7ee](https://github.com/lithic-com/lithic-java/commit/6b3f7ee38b9f387e28cce9d19d5fec3a6718cfbf)) +* **internal:** improve maven repo docs ([737c025](https://github.com/lithic-com/lithic-java/commit/737c025a59820d23176d4e45c50f9ab0781a791c)) +* **internal:** support uploading Maven repo artifacts to stainless package server ([da51092](https://github.com/lithic-com/lithic-java/commit/da510920d18bdd6b4e2a655755e9fe21f145bf9f)) +* **internal:** update `actions/checkout` version ([6764fb4](https://github.com/lithic-com/lithic-java/commit/6764fb4c842d48cffa5a6742f4c0a9ef132e0329)) +* Rework event type generation to support spec splitting ([bb604fb](https://github.com/lithic-com/lithic-java/commit/bb604fbee3a368cfbe5c4e132f5974b691121255)) +* test on Jackson 2.14.0 to avoid encountering FasterXML/jackson-databind[#3240](https://github.com/lithic-com/lithic-java/issues/3240) in tests ([ad467a4](https://github.com/lithic-com/lithic-java/commit/ad467a4ae4e51a2dfd5e9b7be5800ae9d4ca56fc)) + ## 0.115.0 (2026-01-08) Full Changelog: [v0.114.0...v0.115.0](https://github.com/lithic-com/lithic-java/compare/v0.114.0...v0.115.0) diff --git a/README.md b/README.md index d23ab76b..7829b853 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.lithic.api/lithic-java)](https://central.sonatype.com/artifact/com.lithic.api/lithic-java/0.115.0) -[![javadoc](https://javadoc.io/badge2/com.lithic.api/lithic-java/0.115.0/javadoc.svg)](https://javadoc.io/doc/com.lithic.api/lithic-java/0.115.0) +[![Maven Central](https://img.shields.io/maven-central/v/com.lithic.api/lithic-java)](https://central.sonatype.com/artifact/com.lithic.api/lithic-java/0.116.0) +[![javadoc](https://javadoc.io/badge2/com.lithic.api/lithic-java/0.116.0/javadoc.svg)](https://javadoc.io/doc/com.lithic.api/lithic-java/0.116.0) @@ -13,7 +13,7 @@ The Lithic Java SDK is similar to the Lithic Kotlin SDK but with minor differenc -The REST API documentation can be found on [docs.lithic.com](https://docs.lithic.com). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.lithic.api/lithic-java/0.115.0). +The REST API documentation can be found on [docs.lithic.com](https://docs.lithic.com). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.lithic.api/lithic-java/0.116.0). @@ -24,7 +24,7 @@ The REST API documentation can be found on [docs.lithic.com](https://docs.lithic ### Gradle ```kotlin -implementation("com.lithic.api:lithic-java:0.115.0") +implementation("com.lithic.api:lithic-java:0.116.0") ``` ### Maven @@ -33,7 +33,7 @@ implementation("com.lithic.api:lithic-java:0.115.0") com.lithic.api lithic-java - 0.115.0 + 0.116.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 1c30b48a..72a101ec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.lithic.api" - version = "0.115.0" // x-release-please-version + version = "0.116.0" // x-release-please-version } subprojects {