From f165926b1e58f6119241fe908915d86de6161d8f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:14:31 +0000 Subject: [PATCH 1/8] chore(internal): refactor delegating from client to options --- .../api/client/okhttp/LithicOkHttpClient.kt | 76 ++++++++++--------- .../client/okhttp/LithicOkHttpClientAsync.kt | 76 ++++++++++--------- .../com/lithic/api/core/ClientOptions.kt | 28 +++++++ 3 files changed, 106 insertions(+), 74 deletions(-) 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 71838dbe9..3bb00cefc 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 @@ -9,6 +9,7 @@ import com.lithic.api.core.ClientOptions import com.lithic.api.core.Timeout import com.lithic.api.core.http.Headers import com.lithic.api.core.http.QueryParams +import com.lithic.api.core.jsonMapper import java.net.Proxy import java.time.Clock import java.time.Duration @@ -30,12 +31,9 @@ class LithicOkHttpClient private constructor() { class Builder internal constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() - private var timeout: Timeout = Timeout.default() private var proxy: Proxy? = null - fun sandbox() = apply { baseUrl(ClientOptions.SANDBOX_URL) } - - fun baseUrl(baseUrl: String) = apply { clientOptions.baseUrl(baseUrl) } + fun proxy(proxy: Proxy) = apply { this.proxy = proxy } /** * Whether to throw an exception if any of the Jackson versions detected at runtime are @@ -56,6 +54,40 @@ class LithicOkHttpClient private constructor() { fun clock(clock: Clock) = apply { clientOptions.clock(clock) } + fun baseUrl(baseUrl: String?) = apply { clientOptions.baseUrl(baseUrl) } + + /** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */ + fun baseUrl(baseUrl: Optional) = baseUrl(baseUrl.getOrNull()) + + fun sandbox() = apply { clientOptions.sandbox() } + + fun responseValidation(responseValidation: Boolean) = apply { + clientOptions.responseValidation(responseValidation) + } + + fun timeout(timeout: Timeout) = apply { clientOptions.timeout(timeout) } + + /** + * Sets the maximum time allowed for a complete HTTP call, not including retries. + * + * See [Timeout.request] for more details. + * + * For fine-grained control, pass a [Timeout] object. + */ + fun timeout(timeout: Duration) = apply { clientOptions.timeout(timeout) } + + fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) } + + fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) } + + fun webhookSecret(webhookSecret: String?) = apply { + clientOptions.webhookSecret(webhookSecret) + } + + /** Alias for calling [Builder.webhookSecret] with `webhookSecret.orElse(null)`. */ + fun webhookSecret(webhookSecret: Optional) = + webhookSecret(webhookSecret.getOrNull()) + fun headers(headers: Headers) = apply { clientOptions.headers(headers) } fun headers(headers: Map>) = apply { @@ -136,38 +168,6 @@ class LithicOkHttpClient private constructor() { clientOptions.removeAllQueryParams(keys) } - fun timeout(timeout: Timeout) = apply { - clientOptions.timeout(timeout) - this.timeout = timeout - } - - /** - * Sets the maximum time allowed for a complete HTTP call, not including retries. - * - * See [Timeout.request] for more details. - * - * For fine-grained control, pass a [Timeout] object. - */ - fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build()) - - fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) } - - fun proxy(proxy: Proxy) = apply { this.proxy = proxy } - - fun responseValidation(responseValidation: Boolean) = apply { - clientOptions.responseValidation(responseValidation) - } - - fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) } - - fun webhookSecret(webhookSecret: String?) = apply { - clientOptions.webhookSecret(webhookSecret) - } - - /** Alias for calling [Builder.webhookSecret] with `webhookSecret.orElse(null)`. */ - fun webhookSecret(webhookSecret: Optional) = - webhookSecret(webhookSecret.getOrNull()) - fun fromEnv() = apply { clientOptions.fromEnv() } /** @@ -178,7 +178,9 @@ class LithicOkHttpClient private constructor() { fun build(): LithicClient = LithicClientImpl( clientOptions - .httpClient(OkHttpClient.builder().timeout(timeout).proxy(proxy).build()) + .httpClient( + OkHttpClient.builder().timeout(clientOptions.timeout()).proxy(proxy).build() + ) .build() ) } 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 b739a3be2..f9417e4f0 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 @@ -9,6 +9,7 @@ import com.lithic.api.core.ClientOptions import com.lithic.api.core.Timeout import com.lithic.api.core.http.Headers import com.lithic.api.core.http.QueryParams +import com.lithic.api.core.jsonMapper import java.net.Proxy import java.time.Clock import java.time.Duration @@ -30,12 +31,9 @@ class LithicOkHttpClientAsync private constructor() { class Builder internal constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() - private var timeout: Timeout = Timeout.default() private var proxy: Proxy? = null - fun sandbox() = apply { baseUrl(ClientOptions.SANDBOX_URL) } - - fun baseUrl(baseUrl: String) = apply { clientOptions.baseUrl(baseUrl) } + fun proxy(proxy: Proxy) = apply { this.proxy = proxy } /** * Whether to throw an exception if any of the Jackson versions detected at runtime are @@ -56,6 +54,40 @@ class LithicOkHttpClientAsync private constructor() { fun clock(clock: Clock) = apply { clientOptions.clock(clock) } + fun baseUrl(baseUrl: String?) = apply { clientOptions.baseUrl(baseUrl) } + + /** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */ + fun baseUrl(baseUrl: Optional) = baseUrl(baseUrl.getOrNull()) + + fun sandbox() = apply { clientOptions.sandbox() } + + fun responseValidation(responseValidation: Boolean) = apply { + clientOptions.responseValidation(responseValidation) + } + + fun timeout(timeout: Timeout) = apply { clientOptions.timeout(timeout) } + + /** + * Sets the maximum time allowed for a complete HTTP call, not including retries. + * + * See [Timeout.request] for more details. + * + * For fine-grained control, pass a [Timeout] object. + */ + fun timeout(timeout: Duration) = apply { clientOptions.timeout(timeout) } + + fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) } + + fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) } + + fun webhookSecret(webhookSecret: String?) = apply { + clientOptions.webhookSecret(webhookSecret) + } + + /** Alias for calling [Builder.webhookSecret] with `webhookSecret.orElse(null)`. */ + fun webhookSecret(webhookSecret: Optional) = + webhookSecret(webhookSecret.getOrNull()) + fun headers(headers: Headers) = apply { clientOptions.headers(headers) } fun headers(headers: Map>) = apply { @@ -136,38 +168,6 @@ class LithicOkHttpClientAsync private constructor() { clientOptions.removeAllQueryParams(keys) } - fun timeout(timeout: Timeout) = apply { - clientOptions.timeout(timeout) - this.timeout = timeout - } - - /** - * Sets the maximum time allowed for a complete HTTP call, not including retries. - * - * See [Timeout.request] for more details. - * - * For fine-grained control, pass a [Timeout] object. - */ - fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build()) - - fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) } - - fun proxy(proxy: Proxy) = apply { this.proxy = proxy } - - fun responseValidation(responseValidation: Boolean) = apply { - clientOptions.responseValidation(responseValidation) - } - - fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) } - - fun webhookSecret(webhookSecret: String?) = apply { - clientOptions.webhookSecret(webhookSecret) - } - - /** Alias for calling [Builder.webhookSecret] with `webhookSecret.orElse(null)`. */ - fun webhookSecret(webhookSecret: Optional) = - webhookSecret(webhookSecret.getOrNull()) - fun fromEnv() = apply { clientOptions.fromEnv() } /** @@ -178,7 +178,9 @@ class LithicOkHttpClientAsync private constructor() { fun build(): LithicClientAsync = LithicClientAsyncImpl( clientOptions - .httpClient(OkHttpClient.builder().timeout(timeout).proxy(proxy).build()) + .httpClient( + OkHttpClient.builder().timeout(clientOptions.timeout()).proxy(proxy).build() + ) .build() ) } diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt index 47c918a90..f7647481e 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt @@ -9,6 +9,7 @@ import com.lithic.api.core.http.PhantomReachableClosingHttpClient import com.lithic.api.core.http.QueryParams import com.lithic.api.core.http.RetryingHttpClient import java.time.Clock +import java.time.Duration import java.util.Optional import java.util.concurrent.Executor import java.util.concurrent.Executors @@ -20,6 +21,13 @@ class ClientOptions private constructor( private val originalHttpClient: HttpClient, @get:JvmName("httpClient") val httpClient: HttpClient, + /** + * Whether to throw an exception if any of the Jackson versions detected at runtime are + * incompatible with the SDK's minimum supported Jackson version (2.13.4). + * + * Defaults to true. Use extreme caution when disabling this option. There is no guarantee that + * the SDK will work correctly when using an incompatible Jackson version. + */ @get:JvmName("checkJacksonVersionCompatibility") val checkJacksonVersionCompatibility: Boolean, @get:JvmName("jsonMapper") val jsonMapper: JsonMapper, @get:JvmName("streamHandlerExecutor") val streamHandlerExecutor: Executor, @@ -104,6 +112,13 @@ private constructor( this.httpClient = PhantomReachableClosingHttpClient(httpClient) } + /** + * Whether to throw an exception if any of the Jackson versions detected at runtime are + * incompatible with the SDK's minimum supported Jackson version (2.13.4). + * + * Defaults to true. Use extreme caution when disabling this option. There is no guarantee + * that the SDK will work correctly when using an incompatible Jackson version. + */ fun checkJacksonVersionCompatibility(checkJacksonVersionCompatibility: Boolean) = apply { this.checkJacksonVersionCompatibility = checkJacksonVersionCompatibility } @@ -121,12 +136,23 @@ private constructor( /** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */ fun baseUrl(baseUrl: Optional) = baseUrl(baseUrl.getOrNull()) + fun sandbox() = baseUrl(SANDBOX_URL) + fun responseValidation(responseValidation: Boolean) = apply { this.responseValidation = responseValidation } fun timeout(timeout: Timeout) = apply { this.timeout = timeout } + /** + * Sets the maximum time allowed for a complete HTTP call, not including retries. + * + * See [Timeout.request] for more details. + * + * For fine-grained control, pass a [Timeout] object. + */ + fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build()) + fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries } fun apiKey(apiKey: String) = apply { this.apiKey = apiKey } @@ -217,6 +243,8 @@ private constructor( fun removeAllQueryParams(keys: Set) = apply { queryParams.removeAll(keys) } + fun timeout(): Timeout = timeout + fun fromEnv() = apply { System.getenv("LITHIC_BASE_URL")?.let { baseUrl(it) } System.getenv("LITHIC_API_KEY")?.let { apiKey(it) } From ac2c55c13ad7fd93a14e5e64cb8acc548ad84c63 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 22:50:23 +0000 Subject: [PATCH 2/8] feat(client): add https config options --- README.md | 21 ++++++ .../api/client/okhttp/LithicOkHttpClient.kt | 67 ++++++++++++++++++- .../client/okhttp/LithicOkHttpClientAsync.kt | 67 ++++++++++++++++++- .../lithic/api/client/okhttp/OkHttpClient.kt | 31 +++++++++ 4 files changed, 182 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5566ea841..a987002ff 100644 --- a/README.md +++ b/README.md @@ -441,6 +441,27 @@ LithicClient client = LithicOkHttpClient.builder() .build(); ``` +### HTTPS + +> [!NOTE] +> Most applications should not call these methods, and instead use the system defaults. The defaults include +> special optimizations that can be lost if the implementations are modified. + +To configure how HTTPS connections are secured, configure the client using the `sslSocketFactory`, `trustManager`, and `hostnameVerifier` methods: + +```java +import com.lithic.api.client.LithicClient; +import com.lithic.api.client.okhttp.LithicOkHttpClient; + +LithicClient client = LithicOkHttpClient.builder() + .fromEnv() + // If `sslSocketFactory` is set, then `trustManager` must be set, and vice versa. + .sslSocketFactory(yourSSLSocketFactory) + .trustManager(yourTrustManager) + .hostnameVerifier(yourHostnameVerifier) + .build(); +``` + ### Environments The SDK sends requests to the production by default. To send requests to a different environment, configure the client like so: 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 3bb00cefc..d9ecc2ef0 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 @@ -15,6 +15,9 @@ import java.time.Clock import java.time.Duration import java.util.Optional import java.util.concurrent.Executor +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509TrustManager import kotlin.jvm.optionals.getOrNull class LithicOkHttpClient private constructor() { @@ -32,8 +35,62 @@ class LithicOkHttpClient private constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() private var proxy: Proxy? = null + private var sslSocketFactory: SSLSocketFactory? = null + private var trustManager: X509TrustManager? = null + private var hostnameVerifier: HostnameVerifier? = null - fun proxy(proxy: Proxy) = apply { this.proxy = proxy } + fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } + + /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ + fun proxy(proxy: Optional) = proxy(proxy.getOrNull()) + + /** + * The socket factory used to secure HTTPS connections. + * + * If this is set, then [trustManager] must also be set. + * + * If unset, then the system default is used. Most applications should not call this method, + * and instead use the system default. The default include special optimizations that can be + * lost if the implementation is modified. + */ + fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply { + this.sslSocketFactory = sslSocketFactory + } + + /** Alias for calling [Builder.sslSocketFactory] with `sslSocketFactory.orElse(null)`. */ + fun sslSocketFactory(sslSocketFactory: Optional) = + sslSocketFactory(sslSocketFactory.getOrNull()) + + /** + * The trust manager used to secure HTTPS connections. + * + * If this is set, then [sslSocketFactory] must also be set. + * + * If unset, then the system default is used. Most applications should not call this method, + * and instead use the system default. The default include special optimizations that can be + * lost if the implementation is modified. + */ + fun trustManager(trustManager: X509TrustManager?) = apply { + this.trustManager = trustManager + } + + /** Alias for calling [Builder.trustManager] with `trustManager.orElse(null)`. */ + fun trustManager(trustManager: Optional) = + trustManager(trustManager.getOrNull()) + + /** + * The verifier used to confirm that response certificates apply to requested hostnames for + * HTTPS connections. + * + * If unset, then a default hostname verifier is used. + */ + fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply { + this.hostnameVerifier = hostnameVerifier + } + + /** Alias for calling [Builder.hostnameVerifier] with `hostnameVerifier.orElse(null)`. */ + fun hostnameVerifier(hostnameVerifier: Optional) = + hostnameVerifier(hostnameVerifier.getOrNull()) /** * Whether to throw an exception if any of the Jackson versions detected at runtime are @@ -179,7 +236,13 @@ class LithicOkHttpClient private constructor() { LithicClientImpl( clientOptions .httpClient( - OkHttpClient.builder().timeout(clientOptions.timeout()).proxy(proxy).build() + OkHttpClient.builder() + .timeout(clientOptions.timeout()) + .proxy(proxy) + .sslSocketFactory(sslSocketFactory) + .trustManager(trustManager) + .hostnameVerifier(hostnameVerifier) + .build() ) .build() ) 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 f9417e4f0..6fbdea01e 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 @@ -15,6 +15,9 @@ import java.time.Clock import java.time.Duration import java.util.Optional import java.util.concurrent.Executor +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509TrustManager import kotlin.jvm.optionals.getOrNull class LithicOkHttpClientAsync private constructor() { @@ -32,8 +35,62 @@ class LithicOkHttpClientAsync private constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() private var proxy: Proxy? = null + private var sslSocketFactory: SSLSocketFactory? = null + private var trustManager: X509TrustManager? = null + private var hostnameVerifier: HostnameVerifier? = null - fun proxy(proxy: Proxy) = apply { this.proxy = proxy } + fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } + + /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ + fun proxy(proxy: Optional) = proxy(proxy.getOrNull()) + + /** + * The socket factory used to secure HTTPS connections. + * + * If this is set, then [trustManager] must also be set. + * + * If unset, then the system default is used. Most applications should not call this method, + * and instead use the system default. The default include special optimizations that can be + * lost if the implementation is modified. + */ + fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply { + this.sslSocketFactory = sslSocketFactory + } + + /** Alias for calling [Builder.sslSocketFactory] with `sslSocketFactory.orElse(null)`. */ + fun sslSocketFactory(sslSocketFactory: Optional) = + sslSocketFactory(sslSocketFactory.getOrNull()) + + /** + * The trust manager used to secure HTTPS connections. + * + * If this is set, then [sslSocketFactory] must also be set. + * + * If unset, then the system default is used. Most applications should not call this method, + * and instead use the system default. The default include special optimizations that can be + * lost if the implementation is modified. + */ + fun trustManager(trustManager: X509TrustManager?) = apply { + this.trustManager = trustManager + } + + /** Alias for calling [Builder.trustManager] with `trustManager.orElse(null)`. */ + fun trustManager(trustManager: Optional) = + trustManager(trustManager.getOrNull()) + + /** + * The verifier used to confirm that response certificates apply to requested hostnames for + * HTTPS connections. + * + * If unset, then a default hostname verifier is used. + */ + fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply { + this.hostnameVerifier = hostnameVerifier + } + + /** Alias for calling [Builder.hostnameVerifier] with `hostnameVerifier.orElse(null)`. */ + fun hostnameVerifier(hostnameVerifier: Optional) = + hostnameVerifier(hostnameVerifier.getOrNull()) /** * Whether to throw an exception if any of the Jackson versions detected at runtime are @@ -179,7 +236,13 @@ class LithicOkHttpClientAsync private constructor() { LithicClientAsyncImpl( clientOptions .httpClient( - OkHttpClient.builder().timeout(clientOptions.timeout()).proxy(proxy).build() + OkHttpClient.builder() + .timeout(clientOptions.timeout()) + .proxy(proxy) + .sslSocketFactory(sslSocketFactory) + .trustManager(trustManager) + .hostnameVerifier(hostnameVerifier) + .build() ) .build() ) 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 ce991996f..859611b4a 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 @@ -14,6 +14,9 @@ import java.io.InputStream import java.net.Proxy import java.time.Duration import java.util.concurrent.CompletableFuture +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509TrustManager import okhttp3.Call import okhttp3.Callback import okhttp3.HttpUrl.Companion.toHttpUrl @@ -189,6 +192,9 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC private var timeout: Timeout = Timeout.default() private var proxy: Proxy? = null + private var sslSocketFactory: SSLSocketFactory? = null + private var trustManager: X509TrustManager? = null + private var hostnameVerifier: HostnameVerifier? = null fun timeout(timeout: Timeout) = apply { this.timeout = timeout } @@ -196,6 +202,18 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } + fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply { + this.sslSocketFactory = sslSocketFactory + } + + fun trustManager(trustManager: X509TrustManager?) = apply { + this.trustManager = trustManager + } + + fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply { + this.hostnameVerifier = hostnameVerifier + } + fun build(): OkHttpClient = OkHttpClient( okhttp3.OkHttpClient.Builder() @@ -204,6 +222,19 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC .writeTimeout(timeout.write()) .callTimeout(timeout.request()) .proxy(proxy) + .apply { + val sslSocketFactory = sslSocketFactory + val trustManager = trustManager + if (sslSocketFactory != null && trustManager != null) { + sslSocketFactory(sslSocketFactory, trustManager) + } else { + check((sslSocketFactory != null) == (trustManager != null)) { + "Both or none of `sslSocketFactory` and `trustManager` must be set, but only one was set" + } + } + + hostnameVerifier?.let(::hostnameVerifier) + } .build() .apply { // We usually make all our requests to the same host so it makes sense to From 12496f2edfff93ed6a1bbfc72ecba34acf85365f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:28:43 +0000 Subject: [PATCH 3/8] chore(docs): update Account Holder deprecation formatting --- .stats.yml | 4 +- .../com/lithic/api/models/AccountHolder.kt | 37 ++++++++--------- ...tHolderSimulateEnrollmentReviewResponse.kt | 38 +++++++----------- .../api/models/AccountHolderUpdateResponse.kt | 40 ++++++++----------- 4 files changed, 53 insertions(+), 66 deletions(-) diff --git a/.stats.yml b/.stats.yml index c9520d6b7..12b6fc4e3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 167 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-b9c76d077831114f1e4c5c15ff3f1d835ef3e9361b768af8468f8eb07a09ef3e.yml -openapi_spec_hash: 5f9bcf1afd68f962a870727c35628394 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-23922ce40b48fe48252246fb770033946ae5e49e73d90b30eb3415d9812e5947.yml +openapi_spec_hash: b2dd7289d19829df73b2d596576549f7 config_hash: e9a46eb8acb9dc2c236f3e1958a1c4dd diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolder.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolder.kt index 89dd49bbb..0d1264d4c 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolder.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolder.kt @@ -209,9 +209,8 @@ private constructor( controlPerson.getOptional("control_person") /** - * < Deprecated. Use control_person.email when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary email of Account Holder. + * (Deprecated. Use control_person.email when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary email of Account Holder. * * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -254,9 +253,9 @@ private constructor( fun natureOfBusiness(): Optional = natureOfBusiness.getOptional("nature_of_business") /** - * < Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary phone of Account Holder, entered in E.164 format. + * (Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary phone of Account Holder, + * entered in E.164 format. * * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -274,7 +273,7 @@ private constructor( requiredDocuments.getOptional("required_documents") /** - * + * (Deprecated. Use verification_application.status instead) * * KYC and KYB evaluation states. * @@ -287,7 +286,9 @@ private constructor( fun status(): Optional = status.getOptional("status") /** - * Reason for the evaluation status. + * (Deprecated. Use verification_application.status_reasons) + * + * Reason for the evaluation status. * * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -749,9 +750,8 @@ private constructor( } /** - * < Deprecated. Use control_person.email when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary email of Account Holder. + * (Deprecated. Use control_person.email when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary email of Account Holder. */ fun email(email: String) = email(JsonField.of(email)) @@ -828,9 +828,9 @@ private constructor( } /** - * < Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary phone of Account Holder, entered in E.164 format. + * (Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary phone of Account Holder, + * entered in E.164 format. */ fun phoneNumber(phoneNumber: String) = phoneNumber(JsonField.of(phoneNumber)) @@ -874,7 +874,7 @@ private constructor( } /** - * + * (Deprecated. Use verification_application.status instead) * * KYC and KYB evaluation states. * @@ -892,8 +892,9 @@ private constructor( fun status(status: JsonField) = apply { this.status = status } /** - * Reason for the evaluation - * status. + * (Deprecated. Use verification_application.status_reasons) + * + * Reason for the evaluation status. */ fun statusReasons(statusReasons: List) = statusReasons(JsonField.of(statusReasons)) @@ -2074,7 +2075,7 @@ private constructor( } /** - * + * (Deprecated. Use verification_application.status instead) * * KYC and KYB evaluation states. * diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolderSimulateEnrollmentReviewResponse.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolderSimulateEnrollmentReviewResponse.kt index 03fcfa399..a7abd233d 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolderSimulateEnrollmentReviewResponse.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolderSimulateEnrollmentReviewResponse.kt @@ -210,9 +210,8 @@ private constructor( fun created(): Optional = created.getOptional("created") /** - * < Deprecated. Use control_person.email when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary email of Account Holder. + * (Deprecated. Use control_person.email when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary email of Account Holder. * * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -255,9 +254,9 @@ private constructor( fun natureOfBusiness(): Optional = natureOfBusiness.getOptional("nature_of_business") /** - * < Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary phone of Account Holder, entered in E.164 format. + * (Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary phone of Account Holder, + * entered in E.164 format. * * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -275,9 +274,7 @@ private constructor( requiredDocuments.getOptional("required_documents") /** - * - * - * KYC and KYB evaluation states. + * (Deprecated. Use verification_application.status instead) KYC and KYB evaluation states. * * Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the `ADVANCED` * workflow. @@ -288,7 +285,7 @@ private constructor( fun status(): Optional = status.getOptional("status") /** - * Reason for the evaluation status. + * (Deprecated. Use verification_application.status_reasons) Reason for the evaluation status. * * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -750,9 +747,8 @@ private constructor( fun created(created: JsonField) = apply { this.created = created } /** - * < Deprecated. Use control_person.email when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary email of Account Holder. + * (Deprecated. Use control_person.email when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary email of Account Holder. */ fun email(email: String) = email(JsonField.of(email)) @@ -829,9 +825,9 @@ private constructor( } /** - * < Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary phone of Account Holder, entered in E.164 format. + * (Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary phone of Account Holder, + * entered in E.164 format. */ fun phoneNumber(phoneNumber: String) = phoneNumber(JsonField.of(phoneNumber)) @@ -875,9 +871,7 @@ private constructor( } /** - * - * - * KYC and KYB evaluation states. + * (Deprecated. Use verification_application.status instead) KYC and KYB evaluation states. * * Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the `ADVANCED` * workflow. @@ -893,7 +887,7 @@ private constructor( fun status(status: JsonField) = apply { this.status = status } /** - * Reason for the evaluation + * (Deprecated. Use verification_application.status_reasons) Reason for the evaluation * status. */ fun statusReasons(statusReasons: List) = @@ -1989,9 +1983,7 @@ private constructor( } /** - * - * - * KYC and KYB evaluation states. + * (Deprecated. Use verification_application.status instead) KYC and KYB evaluation states. * * Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the `ADVANCED` * workflow. diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolderUpdateResponse.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolderUpdateResponse.kt index d77199cbd..7550c70b4 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolderUpdateResponse.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountHolderUpdateResponse.kt @@ -399,9 +399,8 @@ private constructor( fun created(): Optional = created.getOptional("created") /** - * < Deprecated. Use control_person.email when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary email of Account Holder. + * (Deprecated. Use control_person.email when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary email of Account Holder. * * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -445,9 +444,9 @@ private constructor( natureOfBusiness.getOptional("nature_of_business") /** - * < Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary phone of Account Holder, entered in E.164 format. + * (Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary phone of Account Holder, + * entered in E.164 format. * * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -465,9 +464,7 @@ private constructor( requiredDocuments.getOptional("required_documents") /** - * - * - * KYC and KYB evaluation states. + * (Deprecated. Use verification_application.status instead) KYC and KYB evaluation states. * * Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the `ADVANCED` * workflow. @@ -478,7 +475,7 @@ private constructor( fun status(): Optional = status.getOptional("status") /** - * Reason for the evaluation + * (Deprecated. Use verification_application.status_reasons) Reason for the evaluation * status. * * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the @@ -937,9 +934,9 @@ private constructor( fun created(created: JsonField) = apply { this.created = created } /** - * < Deprecated. Use control_person.email when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary email of Account Holder. + * (Deprecated. Use control_person.email when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary email of Account + * Holder. */ fun email(email: String) = email(JsonField.of(email)) @@ -1021,9 +1018,9 @@ private constructor( } /** - * < Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use - * individual.phone_number when user_type == "INDIVIDUAL". - * > Primary phone of Account Holder, entered in E.164 format. + * (Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use + * individual.phone_number when user_type == "INDIVIDUAL".) Primary phone of Account + * Holder, entered in E.164 format. */ fun phoneNumber(phoneNumber: String) = phoneNumber(JsonField.of(phoneNumber)) @@ -1069,9 +1066,8 @@ private constructor( } /** - * - * - * KYC and KYB evaluation states. + * (Deprecated. Use verification_application.status instead) KYC and KYB evaluation + * states. * * Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the * `ADVANCED` workflow. @@ -1088,7 +1084,7 @@ private constructor( fun status(status: JsonField) = apply { this.status = status } /** - * Reason for the evaluation + * (Deprecated. Use verification_application.status_reasons) Reason for the evaluation * status. */ fun statusReasons(statusReasons: List) = @@ -2212,9 +2208,7 @@ private constructor( } /** - * - * - * KYC and KYB evaluation states. + * (Deprecated. Use verification_application.status instead) KYC and KYB evaluation states. * * Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the `ADVANCED` * workflow. From 3b78f9e12ba76f1422339640ca3bac21eafa408d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:44:20 +0000 Subject: [PATCH 4/8] feat(client): allow configuring env via system properties --- README.md | 29 ++++++++++++------- .../com/lithic/api/core/ClientOptions.kt | 11 +++++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a987002ff..2c0ada2d6 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ import com.lithic.api.client.okhttp.LithicOkHttpClient; import com.lithic.api.models.Card; import com.lithic.api.models.CardCreateParams; -// Configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables +// Configures using the `lithic.apiKey`, `lithic.webhookSecret` and `lithic.baseUrl` system properties +// Or configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables LithicClient client = LithicOkHttpClient.fromEnv(); CardCreateParams params = CardCreateParams.builder() @@ -62,13 +63,14 @@ Card card = client.cards().create(params); ## Client configuration -Configure the client using environment variables: +Configure the client using system properties or environment variables: ```java import com.lithic.api.client.LithicClient; import com.lithic.api.client.okhttp.LithicOkHttpClient; -// Configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables +// Configures using the `lithic.apiKey`, `lithic.webhookSecret` and `lithic.baseUrl` system properties +// Or configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables LithicClient client = LithicOkHttpClient.fromEnv(); ``` @@ -90,7 +92,8 @@ import com.lithic.api.client.LithicClient; import com.lithic.api.client.okhttp.LithicOkHttpClient; LithicClient client = LithicOkHttpClient.builder() - // Configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables + // Configures using the `lithic.apiKey`, `lithic.webhookSecret` and `lithic.baseUrl` system properties + Or configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables .fromEnv() .apiKey("My Lithic API Key") .build(); @@ -98,11 +101,13 @@ LithicClient client = LithicOkHttpClient.builder() See this table for the available options: -| Setter | Environment variable | Required | Default value | -| --------------- | ----------------------- | -------- | -------------------------- | -| `apiKey` | `LITHIC_API_KEY` | true | - | -| `webhookSecret` | `LITHIC_WEBHOOK_SECRET` | false | - | -| `baseUrl` | `LITHIC_BASE_URL` | true | `"https://api.lithic.com"` | +| Setter | System property | Environment variable | Required | Default value | +| --------------- | ---------------------- | ----------------------- | -------- | -------------------------- | +| `apiKey` | `lithic.apiKey` | `LITHIC_API_KEY` | true | - | +| `webhookSecret` | `lithic.webhookSecret` | `LITHIC_WEBHOOK_SECRET` | false | - | +| `baseUrl` | `lithic.baseUrl` | `LITHIC_BASE_URL` | true | `"https://api.lithic.com"` | + +System properties take precedence over environment variables. > [!TIP] > Don't create more than one client in the same application. Each client has a connection pool and @@ -148,7 +153,8 @@ import com.lithic.api.models.Card; import com.lithic.api.models.CardCreateParams; import java.util.concurrent.CompletableFuture; -// Configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables +// Configures using the `lithic.apiKey`, `lithic.webhookSecret` and `lithic.baseUrl` system properties +// Or configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables LithicClient client = LithicOkHttpClient.fromEnv(); CardCreateParams params = CardCreateParams.builder() @@ -166,7 +172,8 @@ import com.lithic.api.models.Card; import com.lithic.api.models.CardCreateParams; import java.util.concurrent.CompletableFuture; -// Configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables +// Configures using the `lithic.apiKey`, `lithic.webhookSecret` and `lithic.baseUrl` system properties +// Or configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables LithicClientAsync client = LithicOkHttpClientAsync.fromEnv(); CardCreateParams params = CardCreateParams.builder() diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt index f7647481e..a51671f55 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt @@ -246,9 +246,14 @@ private constructor( fun timeout(): Timeout = timeout fun fromEnv() = apply { - System.getenv("LITHIC_BASE_URL")?.let { baseUrl(it) } - System.getenv("LITHIC_API_KEY")?.let { apiKey(it) } - System.getenv("LITHIC_WEBHOOK_SECRET")?.let { webhookSecret(it) } + (System.getProperty("lithic.baseUrl") ?: System.getenv("LITHIC_BASE_URL"))?.let { + baseUrl(it) + } + (System.getProperty("lithic.apiKey") ?: System.getenv("LITHIC_API_KEY"))?.let { + apiKey(it) + } + (System.getProperty("lithic.webhookSecret") ?: System.getenv("LITHIC_WEBHOOK_SECRET")) + ?.let { webhookSecret(it) } } /** From ef6b26ea48e9ceca26ae7e4da2805bce7b99d363 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 01:05:08 +0000 Subject: [PATCH 5/8] feat(client): add `{QueryParams,Headers}#put(String, JsonValue)` methods --- .../com/lithic/api/core/http/Headers.kt | 41 +++++++++++++++---- .../com/lithic/api/core/http/QueryParams.kt | 41 +++++++++++++++++++ 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/Headers.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/Headers.kt index 100a49833..d678c9112 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/Headers.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/Headers.kt @@ -1,5 +1,15 @@ +// File generated from our OpenAPI spec by Stainless. + package com.lithic.api.core.http +import com.lithic.api.core.JsonArray +import com.lithic.api.core.JsonBoolean +import com.lithic.api.core.JsonMissing +import com.lithic.api.core.JsonNull +import com.lithic.api.core.JsonNumber +import com.lithic.api.core.JsonObject +import com.lithic.api.core.JsonString +import com.lithic.api.core.JsonValue import com.lithic.api.core.toImmutable import java.util.TreeMap @@ -28,6 +38,19 @@ private constructor( TreeMap(String.CASE_INSENSITIVE_ORDER) private var size: Int = 0 + fun put(name: String, value: JsonValue): Builder = apply { + when (value) { + is JsonMissing, + is JsonNull -> {} + is JsonBoolean -> put(name, value.value.toString()) + is JsonNumber -> put(name, value.value.toString()) + is JsonString -> put(name, value.value) + is JsonArray -> value.values.forEach { put(name, it) } + is JsonObject -> + value.values.forEach { (nestedName, value) -> put("$name.$nestedName", value) } + } + } + fun put(name: String, value: String) = apply { map.getOrPut(name) { mutableListOf() }.add(value) size++ @@ -41,15 +64,6 @@ private constructor( headers.names().forEach { put(it, headers.values(it)) } } - fun remove(name: String) = apply { size -= map.remove(name).orEmpty().size } - - fun removeAll(names: Set) = apply { names.forEach(::remove) } - - fun clear() = apply { - map.clear() - size = 0 - } - fun replace(name: String, value: String) = apply { remove(name) put(name, value) @@ -68,6 +82,15 @@ private constructor( headers.names().forEach { replace(it, headers.values(it)) } } + fun remove(name: String) = apply { size -= map.remove(name).orEmpty().size } + + fun removeAll(names: Set) = apply { names.forEach(::remove) } + + fun clear() = apply { + map.clear() + size = 0 + } + fun build() = Headers( map.mapValuesTo(TreeMap(String.CASE_INSENSITIVE_ORDER)) { (_, values) -> diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/QueryParams.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/QueryParams.kt index 36287e684..305f8d542 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/QueryParams.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/QueryParams.kt @@ -2,6 +2,14 @@ package com.lithic.api.core.http +import com.lithic.api.core.JsonArray +import com.lithic.api.core.JsonBoolean +import com.lithic.api.core.JsonMissing +import com.lithic.api.core.JsonNull +import com.lithic.api.core.JsonNumber +import com.lithic.api.core.JsonObject +import com.lithic.api.core.JsonString +import com.lithic.api.core.JsonValue import com.lithic.api.core.toImmutable class QueryParams @@ -28,6 +36,39 @@ private constructor( private val map: MutableMap> = mutableMapOf() private var size: Int = 0 + fun put(key: String, value: JsonValue): Builder = apply { + when (value) { + is JsonMissing, + is JsonNull -> {} + is JsonBoolean -> put(key, value.value.toString()) + is JsonNumber -> put(key, value.value.toString()) + is JsonString -> put(key, value.value) + is JsonArray -> + put( + key, + value.values + .asSequence() + .mapNotNull { + when (it) { + is JsonMissing, + is JsonNull -> null + is JsonBoolean -> it.value.toString() + is JsonNumber -> it.value.toString() + is JsonString -> it.value + is JsonArray, + is JsonObject -> + throw IllegalArgumentException( + "Cannot comma separate non-primitives in query params" + ) + } + } + .joinToString(","), + ) + is JsonObject -> + value.values.forEach { (nestedKey, value) -> put("$key[$nestedKey]", value) } + } + } + fun put(key: String, value: String) = apply { map.getOrPut(key) { mutableListOf() }.add(value) size++ From b94bfc2ecef5a07742f4f0e0bf36ccd8e38c2b9f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:43:00 +0000 Subject: [PATCH 6/8] feat(api): adds new Auth Rules Scope and Settlement Details type - adds ANY scope to Auth Rules list params - adds COLLABORATION type to Settlement Details --- .stats.yml | 4 ++-- .../kotlin/com/lithic/api/models/AuthRuleV2ListParams.kt | 6 ++++++ .../main/kotlin/com/lithic/api/models/SettlementDetail.kt | 6 ++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 12b6fc4e3..a53b1e2c2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 167 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-23922ce40b48fe48252246fb770033946ae5e49e73d90b30eb3415d9812e5947.yml -openapi_spec_hash: b2dd7289d19829df73b2d596576549f7 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-54e6f7955e86265bc870eaae7f4a9cd716292457a83e032567f22bbb9ce6ad0d.yml +openapi_spec_hash: 6adbff5e5aeda88504047fc2c87e3fa0 config_hash: e9a46eb8acb9dc2c236f3e1958a1c4dd diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/AuthRuleV2ListParams.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/AuthRuleV2ListParams.kt index 2b525913d..59f8780c9 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/AuthRuleV2ListParams.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/AuthRuleV2ListParams.kt @@ -433,6 +433,8 @@ private constructor( @JvmField val CARD = of("CARD") + @JvmField val ANY = of("ANY") + @JvmStatic fun of(value: String) = Scope(JsonField.of(value)) } @@ -441,6 +443,7 @@ private constructor( PROGRAM, ACCOUNT, CARD, + ANY, } /** @@ -456,6 +459,7 @@ private constructor( PROGRAM, ACCOUNT, CARD, + ANY, /** An enum member indicating that [Scope] was instantiated with an unknown value. */ _UNKNOWN, } @@ -472,6 +476,7 @@ private constructor( PROGRAM -> Value.PROGRAM ACCOUNT -> Value.ACCOUNT CARD -> Value.CARD + ANY -> Value.ANY else -> Value._UNKNOWN } @@ -489,6 +494,7 @@ private constructor( PROGRAM -> Known.PROGRAM ACCOUNT -> Known.ACCOUNT CARD -> Known.CARD + ANY -> Known.ANY else -> throw LithicInvalidDataException("Unknown Scope: $value") } diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/SettlementDetail.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/SettlementDetail.kt index 3e2ed4c3d..b37e6f6c1 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/SettlementDetail.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/SettlementDetail.kt @@ -1337,6 +1337,8 @@ private constructor( @JvmField val CLEARING = of("CLEARING") + @JvmField val COLLABORATION = of("COLLABORATION") + @JvmField val FEE = of("FEE") @JvmField val FINANCIAL = of("FINANCIAL") @@ -1356,6 +1358,7 @@ private constructor( ARBITRATION, CHARGEBACK, CLEARING, + COLLABORATION, FEE, FINANCIAL, NON_FINANCIAL, @@ -1377,6 +1380,7 @@ private constructor( ARBITRATION, CHARGEBACK, CLEARING, + COLLABORATION, FEE, FINANCIAL, NON_FINANCIAL, @@ -1399,6 +1403,7 @@ private constructor( ARBITRATION -> Value.ARBITRATION CHARGEBACK -> Value.CHARGEBACK CLEARING -> Value.CLEARING + COLLABORATION -> Value.COLLABORATION FEE -> Value.FEE FINANCIAL -> Value.FINANCIAL NON_FINANCIAL -> Value.NON_FINANCIAL @@ -1422,6 +1427,7 @@ private constructor( ARBITRATION -> Known.ARBITRATION CHARGEBACK -> Known.CHARGEBACK CLEARING -> Known.CLEARING + COLLABORATION -> Known.COLLABORATION FEE -> Known.FEE FINANCIAL -> Known.FINANCIAL NON_FINANCIAL -> Known.NON_FINANCIAL From f1d50687540c2435b14e058007f7bbbfb8025613 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:04:02 +0000 Subject: [PATCH 7/8] docs: fix missing readme comment --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c0ada2d6..bba902871 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ import com.lithic.api.client.okhttp.LithicOkHttpClient; LithicClient client = LithicOkHttpClient.builder() // Configures using the `lithic.apiKey`, `lithic.webhookSecret` and `lithic.baseUrl` system properties - Or configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables + // Or configures using the `LITHIC_API_KEY`, `LITHIC_WEBHOOK_SECRET` and `LITHIC_BASE_URL` environment variables .fromEnv() .apiKey("My Lithic API Key") .build(); From 85755b4d4f1d665dd39c4cc8c35dee2260e4b311 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:04:28 +0000 Subject: [PATCH 8/8] release: 0.97.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 22 ++++++++++++++++++++++ README.md | 10 +++++----- build.gradle.kts | 2 +- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 87d60805f..ca93fdaad 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.96.1" + ".": "0.97.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e9cc8cb8f..e8f2aa175 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## 0.97.0 (2025-07-23) + +Full Changelog: [v0.96.1...v0.97.0](https://github.com/lithic-com/lithic-java/compare/v0.96.1...v0.97.0) + +### Features + +* **api:** adds new Auth Rules Scope and Settlement Details type ([b94bfc2](https://github.com/lithic-com/lithic-java/commit/b94bfc2ecef5a07742f4f0e0bf36ccd8e38c2b9f)) +* **client:** add `{QueryParams,Headers}#put(String, JsonValue)` methods ([ef6b26e](https://github.com/lithic-com/lithic-java/commit/ef6b26ea48e9ceca26ae7e4da2805bce7b99d363)) +* **client:** add https config options ([ac2c55c](https://github.com/lithic-com/lithic-java/commit/ac2c55c13ad7fd93a14e5e64cb8acc548ad84c63)) +* **client:** allow configuring env via system properties ([3b78f9e](https://github.com/lithic-com/lithic-java/commit/3b78f9e12ba76f1422339640ca3bac21eafa408d)) + + +### Chores + +* **docs:** update Account Holder deprecation formatting ([12496f2](https://github.com/lithic-com/lithic-java/commit/12496f2edfff93ed6a1bbfc72ecba34acf85365f)) +* **internal:** refactor delegating from client to options ([f165926](https://github.com/lithic-com/lithic-java/commit/f165926b1e58f6119241fe908915d86de6161d8f)) + + +### Documentation + +* fix missing readme comment ([f1d5068](https://github.com/lithic-com/lithic-java/commit/f1d50687540c2435b14e058007f7bbbfb8025613)) + ## 0.96.1 (2025-07-18) Full Changelog: [v0.96.0...v0.96.1](https://github.com/lithic-com/lithic-java/compare/v0.96.0...v0.96.1) diff --git a/README.md b/README.md index bba902871..2911c1104 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.96.1) -[![javadoc](https://javadoc.io/badge2/com.lithic.api/lithic-java/0.96.1/javadoc.svg)](https://javadoc.io/doc/com.lithic.api/lithic-java/0.96.1) +[![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.97.0) +[![javadoc](https://javadoc.io/badge2/com.lithic.api/lithic-java/0.97.0/javadoc.svg)](https://javadoc.io/doc/com.lithic.api/lithic-java/0.97.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.96.1). +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.97.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.96.1") +implementation("com.lithic.api:lithic-java:0.97.0") ``` ### Maven @@ -33,7 +33,7 @@ implementation("com.lithic.api:lithic-java:0.96.1") com.lithic.api lithic-java - 0.96.1 + 0.97.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 64088c199..10f2bf60e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.lithic.api" - version = "0.96.1" // x-release-please-version + version = "0.97.0" // x-release-please-version } subprojects {