diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f3343e21..f38100a4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.125.0" + ".": "0.126.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 883f26c0..9a940850 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 191 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic/lithic-0f374e78a0212145a2f55a55dfc39a612de19094d5152ae26b1bc74b01b9e343.yml -openapi_spec_hash: ec888cdaebea979a2cd6231ca04c346c -config_hash: 01dfc901bb6d54b0f582155d779bcbe0 +configured_endpoints: 193 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic/lithic-00f07b0edcc0c3c5ef79920ced7f58dac2434df5e4c27ff6041783e8228315f9.yml +openapi_spec_hash: 963688b09480159a06865075c94a2577 +config_hash: 265a2b679964f4ad5706de101ad2a942 diff --git a/CHANGELOG.md b/CHANGELOG.md index 273dd1b8..e1207996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.126.0 (2026-05-08) + +Full Changelog: [v0.125.0...v0.126.0](https://github.com/lithic-com/lithic-java/compare/v0.125.0...v0.126.0) + +### Features + +* **api:** add retrieve signals method to accounts and cards ([767ea0d](https://github.com/lithic-com/lithic-java/commit/767ea0d26ee83791aa33f7ce96091d87fcd2f3e0)) +* **api:** add travel/distance attributes, unit param to authorization actions ([f5d17dc](https://github.com/lithic-com/lithic-java/commit/f5d17dcfe179f2612c3b1f93504948b5097bb69b)) + + +### Chores + +* redact api-key headers in debug logs ([7bcf3e6](https://github.com/lithic-com/lithic-java/commit/7bcf3e6916b9cbc628a78e0a97d78057cab7fe3a)) + ## 0.125.0 (2026-05-06) Full Changelog: [v0.124.0...v0.125.0](https://github.com/lithic-com/lithic-java/compare/v0.124.0...v0.125.0) diff --git a/README.md b/README.md index b323db83..251eadd5 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.125.0) -[![javadoc](https://javadoc.io/badge2/com.lithic.api/lithic-java/0.125.0/javadoc.svg)](https://javadoc.io/doc/com.lithic.api/lithic-java/0.125.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.126.0) +[![javadoc](https://javadoc.io/badge2/com.lithic.api/lithic-java/0.126.0/javadoc.svg)](https://javadoc.io/doc/com.lithic.api/lithic-java/0.126.0) @@ -22,7 +22,7 @@ Use the Lithic MCP Server to enable AI assistants to interact with this API, all -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.125.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.126.0). @@ -33,7 +33,7 @@ The REST API documentation can be found on [docs.lithic.com](https://docs.lithic ### Gradle ```kotlin -implementation("com.lithic.api:lithic-java:0.125.0") +implementation("com.lithic.api:lithic-java:0.126.0") ``` ### Maven @@ -42,7 +42,7 @@ implementation("com.lithic.api:lithic-java:0.125.0") com.lithic.api lithic-java - 0.125.0 + 0.126.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 9cf695ab..4b8f86ba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.lithic.api" - version = "0.125.0" // x-release-please-version + version = "0.126.0" // x-release-please-version } subprojects { diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/LoggingHttpClient.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/LoggingHttpClient.kt index 43cf6062..1703337f 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/LoggingHttpClient.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/LoggingHttpClient.kt @@ -31,7 +31,7 @@ private constructor( /** * Sensitive headers to redact from logs. * - * Defaults to `Set.of("Authorization")`. + * Defaults to `Set.of("authorization", "api-key", "x-api-key", "cookie", "set-cookie")`. */ @get:JvmName("redactedHeaders") val redactedHeaders: SortedSet, /** @@ -192,7 +192,8 @@ private constructor( class Builder internal constructor() { private var httpClient: HttpClient? = null - private var redactedHeaders: Set = setOf("Authorization") + private var redactedHeaders: Set = + setOf("authorization", "api-key", "x-api-key", "cookie", "set-cookie") private var clock: Clock = Clock.systemUTC() private var level: LogLevel? = null @@ -210,7 +211,7 @@ private constructor( /** * Sensitive headers to redact from logs. * - * Defaults to `Set.of("Authorization")`. + * Defaults to `Set.of("authorization", "api-key", "x-api-key", "cookie", "set-cookie")`. */ fun redactedHeaders(redactedHeaders: Set) = apply { this.redactedHeaders = redactedHeaders diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountRetrieveSignalsParams.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountRetrieveSignalsParams.kt new file mode 100644 index 00000000..5d48c346 --- /dev/null +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/AccountRetrieveSignalsParams.kt @@ -0,0 +1,205 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.lithic.api.models + +import com.lithic.api.core.Params +import com.lithic.api.core.http.Headers +import com.lithic.api.core.http.QueryParams +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Returns behavioral feature state derived from an account's transaction history. + * + * These signals expose the same data used by behavioral rule attributes (e.g. `AMOUNT_Z_SCORE` with + * `scope: ACCOUNT`, `IS_NEW_COUNTRY` with `scope: ACCOUNT`) and custom code + * `TRANSACTION_HISTORY_SIGNALS` features, allowing clients to inspect feature values before writing + * rules and debug rule behavior. + * + * Note: 3DS fields are not available at the account scope and will be null. + */ +class AccountRetrieveSignalsParams +private constructor( + private val accountToken: String?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + fun accountToken(): Optional = Optional.ofNullable(accountToken) + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun none(): AccountRetrieveSignalsParams = builder().build() + + /** + * Returns a mutable builder for constructing an instance of [AccountRetrieveSignalsParams]. + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [AccountRetrieveSignalsParams]. */ + class Builder internal constructor() { + + private var accountToken: String? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + + @JvmSynthetic + internal fun from(accountRetrieveSignalsParams: AccountRetrieveSignalsParams) = apply { + accountToken = accountRetrieveSignalsParams.accountToken + additionalHeaders = accountRetrieveSignalsParams.additionalHeaders.toBuilder() + additionalQueryParams = accountRetrieveSignalsParams.additionalQueryParams.toBuilder() + } + + fun accountToken(accountToken: String?) = apply { this.accountToken = accountToken } + + /** Alias for calling [Builder.accountToken] with `accountToken.orElse(null)`. */ + fun accountToken(accountToken: Optional) = accountToken(accountToken.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + /** + * Returns an immutable instance of [AccountRetrieveSignalsParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): AccountRetrieveSignalsParams = + AccountRetrieveSignalsParams( + accountToken, + additionalHeaders.build(), + additionalQueryParams.build(), + ) + } + + fun _pathParam(index: Int): String = + when (index) { + 0 -> accountToken ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is AccountRetrieveSignalsParams && + accountToken == other.accountToken && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = + Objects.hash(accountToken, additionalHeaders, additionalQueryParams) + + override fun toString() = + "AccountRetrieveSignalsParams{accountToken=$accountToken, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardRetrieveSignalsParams.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardRetrieveSignalsParams.kt new file mode 100644 index 00000000..9a4ef132 --- /dev/null +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/CardRetrieveSignalsParams.kt @@ -0,0 +1,202 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.lithic.api.models + +import com.lithic.api.core.Params +import com.lithic.api.core.http.Headers +import com.lithic.api.core.http.QueryParams +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Returns behavioral feature state derived from a card's transaction history. + * + * These signals expose the same data used by behavioral rule attributes (e.g. `AMOUNT_Z_SCORE` with + * `scope: CARD`, `IS_NEW_COUNTRY` with `scope: CARD`) and custom code `TRANSACTION_HISTORY_SIGNALS` + * features, allowing clients to inspect feature values before writing rules and debug rule + * behavior. + */ +class CardRetrieveSignalsParams +private constructor( + private val cardToken: String?, + private val additionalHeaders: Headers, + private val additionalQueryParams: QueryParams, +) : Params { + + fun cardToken(): Optional = Optional.ofNullable(cardToken) + + /** Additional headers to send with the request. */ + fun _additionalHeaders(): Headers = additionalHeaders + + /** Additional query param to send with the request. */ + fun _additionalQueryParams(): QueryParams = additionalQueryParams + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun none(): CardRetrieveSignalsParams = builder().build() + + /** + * Returns a mutable builder for constructing an instance of [CardRetrieveSignalsParams]. + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [CardRetrieveSignalsParams]. */ + class Builder internal constructor() { + + private var cardToken: String? = null + private var additionalHeaders: Headers.Builder = Headers.builder() + private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() + + @JvmSynthetic + internal fun from(cardRetrieveSignalsParams: CardRetrieveSignalsParams) = apply { + cardToken = cardRetrieveSignalsParams.cardToken + additionalHeaders = cardRetrieveSignalsParams.additionalHeaders.toBuilder() + additionalQueryParams = cardRetrieveSignalsParams.additionalQueryParams.toBuilder() + } + + fun cardToken(cardToken: String?) = apply { this.cardToken = cardToken } + + /** Alias for calling [Builder.cardToken] with `cardToken.orElse(null)`. */ + fun cardToken(cardToken: Optional) = cardToken(cardToken.getOrNull()) + + fun additionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllAdditionalHeaders(additionalHeaders) + } + + fun putAdditionalHeader(name: String, value: String) = apply { + additionalHeaders.put(name, value) + } + + fun putAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.put(name, values) + } + + fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun putAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.putAll(additionalHeaders) + } + + fun replaceAdditionalHeaders(name: String, value: String) = apply { + additionalHeaders.replace(name, value) + } + + fun replaceAdditionalHeaders(name: String, values: Iterable) = apply { + additionalHeaders.replace(name, values) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun replaceAllAdditionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.replaceAll(additionalHeaders) + } + + fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) } + + fun removeAllAdditionalHeaders(names: Set) = apply { + additionalHeaders.removeAll(names) + } + + fun additionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllAdditionalQueryParams(additionalQueryParams) + } + + fun putAdditionalQueryParam(key: String, value: String) = apply { + additionalQueryParams.put(key, value) + } + + fun putAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.put(key, values) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun putAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.putAll(additionalQueryParams) + } + + fun replaceAdditionalQueryParams(key: String, value: String) = apply { + additionalQueryParams.replace(key, value) + } + + fun replaceAdditionalQueryParams(key: String, values: Iterable) = apply { + additionalQueryParams.replace(key, values) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun replaceAllAdditionalQueryParams(additionalQueryParams: Map>) = + apply { + this.additionalQueryParams.replaceAll(additionalQueryParams) + } + + fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) } + + fun removeAllAdditionalQueryParams(keys: Set) = apply { + additionalQueryParams.removeAll(keys) + } + + /** + * Returns an immutable instance of [CardRetrieveSignalsParams]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): CardRetrieveSignalsParams = + CardRetrieveSignalsParams( + cardToken, + additionalHeaders.build(), + additionalQueryParams.build(), + ) + } + + fun _pathParam(index: Int): String = + when (index) { + 0 -> cardToken ?: "" + else -> "" + } + + override fun _headers(): Headers = additionalHeaders + + override fun _queryParams(): QueryParams = additionalQueryParams + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is CardRetrieveSignalsParams && + cardToken == other.cardToken && + additionalHeaders == other.additionalHeaders && + additionalQueryParams == other.additionalQueryParams + } + + override fun hashCode(): Int = Objects.hash(cardToken, additionalHeaders, additionalQueryParams) + + override fun toString() = + "CardRetrieveSignalsParams{cardToken=$cardToken, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" +} diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/ConditionalAuthorizationActionParameters.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/ConditionalAuthorizationActionParameters.kt index d9a2e04e..a6f9f407 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/models/ConditionalAuthorizationActionParameters.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/ConditionalAuthorizationActionParameters.kt @@ -490,6 +490,15 @@ private constructor( * values are `TRUE`, `FALSE`. Card-scoped only; no `parameters` required. * * `THREE_DS_SUCCESS_RATE`: The 3DS authentication success rate for the card, as a * percentage from 0.0 to 100.0. Card-scoped only; no `parameters` required. + * * `TRAVEL_SPEED`: The estimated speed of travel derived from the distance between the + * postal code centers of the last card-present transaction and the current transaction, + * divided by the elapsed time. Null if there is no prior card-present transaction, if + * either postal code cannot be geocoded, or if elapsed time is zero. Requires + * `parameters.unit` set to `MPH` or `KPH`. + * * `DISTANCE_FROM_LAST_TRANSACTION`: The estimated distance between the postal code + * centers of the last card-present transaction and the current transaction. Null if there + * is no prior card-present transaction or if either postal code cannot be geocoded. + * Requires `parameters.unit` set to `MILES` or `KILOMETERS`. * * @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). @@ -513,11 +522,12 @@ private constructor( fun value(): ConditionalValue = value.getRequired("value") /** - * Additional parameters required for transaction history signal attributes. Required when - * `attribute` is one of `AMOUNT_Z_SCORE`, `AVG_TRANSACTION_AMOUNT`, - * `STDEV_TRANSACTION_AMOUNT`, `IS_NEW_COUNTRY`, `IS_NEW_MCC`, `IS_FIRST_TRANSACTION`, - * `CONSECUTIVE_DECLINES`, `TIME_SINCE_LAST_TRANSACTION`, or `DISTINCT_COUNTRY_COUNT`. Not - * used for other attributes. + * Additional parameters for certain attributes. Required when `attribute` is one of + * `AMOUNT_Z_SCORE`, `AVG_TRANSACTION_AMOUNT`, `STDEV_TRANSACTION_AMOUNT`, `IS_NEW_COUNTRY`, + * `IS_NEW_MCC`, `IS_FIRST_TRANSACTION`, `CONSECUTIVE_DECLINES`, + * `TIME_SINCE_LAST_TRANSACTION`, or `DISTINCT_COUNTRY_COUNT` (require `scope`); or + * `TRAVEL_SPEED` or `DISTANCE_FROM_LAST_TRANSACTION` (require `unit`). Not used for other + * attributes. * * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -702,6 +712,15 @@ private constructor( * Valid values are `TRUE`, `FALSE`. Card-scoped only; no `parameters` required. * * `THREE_DS_SUCCESS_RATE`: The 3DS authentication success rate for the card, as a * percentage from 0.0 to 100.0. Card-scoped only; no `parameters` required. + * * `TRAVEL_SPEED`: The estimated speed of travel derived from the distance between the + * postal code centers of the last card-present transaction and the current + * transaction, divided by the elapsed time. Null if there is no prior card-present + * transaction, if either postal code cannot be geocoded, or if elapsed time is zero. + * Requires `parameters.unit` set to `MPH` or `KPH`. + * * `DISTANCE_FROM_LAST_TRANSACTION`: The estimated distance between the postal code + * centers of the last card-present transaction and the current transaction. Null if + * there is no prior card-present transaction or if either postal code cannot be + * geocoded. Requires `parameters.unit` set to `MILES` or `KILOMETERS`. */ fun attribute(attribute: Attribute) = attribute(JsonField.of(attribute)) @@ -754,11 +773,12 @@ private constructor( fun value(timestamp: OffsetDateTime) = value(ConditionalValue.ofTimestamp(timestamp)) /** - * Additional parameters required for transaction history signal attributes. Required - * when `attribute` is one of `AMOUNT_Z_SCORE`, `AVG_TRANSACTION_AMOUNT`, - * `STDEV_TRANSACTION_AMOUNT`, `IS_NEW_COUNTRY`, `IS_NEW_MCC`, `IS_FIRST_TRANSACTION`, - * `CONSECUTIVE_DECLINES`, `TIME_SINCE_LAST_TRANSACTION`, or `DISTINCT_COUNTRY_COUNT`. - * Not used for other attributes. + * Additional parameters for certain attributes. Required when `attribute` is one of + * `AMOUNT_Z_SCORE`, `AVG_TRANSACTION_AMOUNT`, `STDEV_TRANSACTION_AMOUNT`, + * `IS_NEW_COUNTRY`, `IS_NEW_MCC`, `IS_FIRST_TRANSACTION`, `CONSECUTIVE_DECLINES`, + * `TIME_SINCE_LAST_TRANSACTION`, or `DISTINCT_COUNTRY_COUNT` (require `scope`); or + * `TRAVEL_SPEED` or `DISTANCE_FROM_LAST_TRANSACTION` (require `unit`). Not used for + * other attributes. */ fun parameters(parameters: Parameters) = parameters(JsonField.of(parameters)) @@ -955,6 +975,15 @@ private constructor( * values are `TRUE`, `FALSE`. Card-scoped only; no `parameters` required. * * `THREE_DS_SUCCESS_RATE`: The 3DS authentication success rate for the card, as a * percentage from 0.0 to 100.0. Card-scoped only; no `parameters` required. + * * `TRAVEL_SPEED`: The estimated speed of travel derived from the distance between the + * postal code centers of the last card-present transaction and the current transaction, + * divided by the elapsed time. Null if there is no prior card-present transaction, if + * either postal code cannot be geocoded, or if elapsed time is zero. Requires + * `parameters.unit` set to `MPH` or `KPH`. + * * `DISTANCE_FROM_LAST_TRANSACTION`: The estimated distance between the postal code + * centers of the last card-present transaction and the current transaction. Null if there + * is no prior card-present transaction or if either postal code cannot be geocoded. + * Requires `parameters.unit` set to `MILES` or `KILOMETERS`. */ class Attribute @JsonCreator private constructor(private val value: JsonField) : Enum { @@ -1045,6 +1074,10 @@ private constructor( @JvmField val THREE_DS_SUCCESS_RATE = of("THREE_DS_SUCCESS_RATE") + @JvmField val TRAVEL_SPEED = of("TRAVEL_SPEED") + + @JvmField val DISTANCE_FROM_LAST_TRANSACTION = of("DISTANCE_FROM_LAST_TRANSACTION") + @JvmStatic fun of(value: String) = Attribute(JsonField.of(value)) } @@ -1087,6 +1120,8 @@ private constructor( DISTINCT_COUNTRY_COUNT, IS_NEW_MERCHANT, THREE_DS_SUCCESS_RATE, + TRAVEL_SPEED, + DISTANCE_FROM_LAST_TRANSACTION, } /** @@ -1136,6 +1171,8 @@ private constructor( DISTINCT_COUNTRY_COUNT, IS_NEW_MERCHANT, THREE_DS_SUCCESS_RATE, + TRAVEL_SPEED, + DISTANCE_FROM_LAST_TRANSACTION, /** * An enum member indicating that [Attribute] was instantiated with an unknown * value. @@ -1189,6 +1226,8 @@ private constructor( DISTINCT_COUNTRY_COUNT -> Value.DISTINCT_COUNTRY_COUNT IS_NEW_MERCHANT -> Value.IS_NEW_MERCHANT THREE_DS_SUCCESS_RATE -> Value.THREE_DS_SUCCESS_RATE + TRAVEL_SPEED -> Value.TRAVEL_SPEED + DISTANCE_FROM_LAST_TRANSACTION -> Value.DISTANCE_FROM_LAST_TRANSACTION else -> Value._UNKNOWN } @@ -1240,6 +1279,8 @@ private constructor( DISTINCT_COUNTRY_COUNT -> Known.DISTINCT_COUNTRY_COUNT IS_NEW_MERCHANT -> Known.IS_NEW_MERCHANT THREE_DS_SUCCESS_RATE -> Known.THREE_DS_SUCCESS_RATE + TRAVEL_SPEED -> Known.TRAVEL_SPEED + DISTANCE_FROM_LAST_TRANSACTION -> Known.DISTANCE_FROM_LAST_TRANSACTION else -> throw LithicInvalidDataException("Unknown Attribute: $value") } @@ -1308,17 +1349,19 @@ private constructor( } /** - * Additional parameters required for transaction history signal attributes. Required when - * `attribute` is one of `AMOUNT_Z_SCORE`, `AVG_TRANSACTION_AMOUNT`, - * `STDEV_TRANSACTION_AMOUNT`, `IS_NEW_COUNTRY`, `IS_NEW_MCC`, `IS_FIRST_TRANSACTION`, - * `CONSECUTIVE_DECLINES`, `TIME_SINCE_LAST_TRANSACTION`, or `DISTINCT_COUNTRY_COUNT`. Not - * used for other attributes. + * Additional parameters for certain attributes. Required when `attribute` is one of + * `AMOUNT_Z_SCORE`, `AVG_TRANSACTION_AMOUNT`, `STDEV_TRANSACTION_AMOUNT`, `IS_NEW_COUNTRY`, + * `IS_NEW_MCC`, `IS_FIRST_TRANSACTION`, `CONSECUTIVE_DECLINES`, + * `TIME_SINCE_LAST_TRANSACTION`, or `DISTINCT_COUNTRY_COUNT` (require `scope`); or + * `TRAVEL_SPEED` or `DISTANCE_FROM_LAST_TRANSACTION` (require `unit`). Not used for other + * attributes. */ class Parameters @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( private val interval: JsonField, private val scope: JsonField, + private val unit: JsonField, private val additionalProperties: MutableMap, ) { @@ -1328,7 +1371,8 @@ private constructor( @ExcludeMissing interval: JsonField = JsonMissing.of(), @JsonProperty("scope") @ExcludeMissing scope: JsonField = JsonMissing.of(), - ) : this(interval, scope, mutableMapOf()) + @JsonProperty("unit") @ExcludeMissing unit: JsonField = JsonMissing.of(), + ) : this(interval, scope, unit, mutableMapOf()) /** * The time window for statistical attributes (`AMOUNT_Z_SCORE`, @@ -1348,6 +1392,19 @@ private constructor( */ fun scope(): Optional = scope.getOptional("scope") + /** + * The unit for impossible travel attributes. Required when `attribute` is + * `TRAVEL_SPEED` or `DISTANCE_FROM_LAST_TRANSACTION`. + * + * For `TRAVEL_SPEED`: `MPH` (miles per hour) or `KPH` (kilometers per hour). + * + * For `DISTANCE_FROM_LAST_TRANSACTION`: `MILES` or `KILOMETERS`. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun unit(): Optional = unit.getOptional("unit") + /** * Returns the raw JSON value of [interval]. * @@ -1365,6 +1422,13 @@ private constructor( */ @JsonProperty("scope") @ExcludeMissing fun _scope(): JsonField = scope + /** + * Returns the raw JSON value of [unit]. + * + * Unlike [unit], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("unit") @ExcludeMissing fun _unit(): JsonField = unit + @JsonAnySetter private fun putAdditionalProperty(key: String, value: JsonValue) { additionalProperties.put(key, value) @@ -1388,12 +1452,14 @@ private constructor( private var interval: JsonField = JsonMissing.of() private var scope: JsonField = JsonMissing.of() + private var unit: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @JvmSynthetic internal fun from(parameters: Parameters) = apply { interval = parameters.interval scope = parameters.scope + unit = parameters.unit additionalProperties = parameters.additionalProperties.toMutableMap() } @@ -1425,6 +1491,25 @@ private constructor( */ fun scope(scope: JsonField) = apply { this.scope = scope } + /** + * The unit for impossible travel attributes. Required when `attribute` is + * `TRAVEL_SPEED` or `DISTANCE_FROM_LAST_TRANSACTION`. + * + * For `TRAVEL_SPEED`: `MPH` (miles per hour) or `KPH` (kilometers per hour). + * + * For `DISTANCE_FROM_LAST_TRANSACTION`: `MILES` or `KILOMETERS`. + */ + fun unit(unit: Unit) = unit(JsonField.of(unit)) + + /** + * Sets [Builder.unit] to an arbitrary JSON value. + * + * You should usually call [Builder.unit] with a well-typed [Unit] value instead. + * This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun unit(unit: JsonField) = apply { this.unit = unit } + fun additionalProperties(additionalProperties: Map) = apply { this.additionalProperties.clear() putAllAdditionalProperties(additionalProperties) @@ -1453,7 +1538,7 @@ private constructor( * Further updates to this [Builder] will not mutate the returned instance. */ fun build(): Parameters = - Parameters(interval, scope, additionalProperties.toMutableMap()) + Parameters(interval, scope, unit, additionalProperties.toMutableMap()) } private var validated: Boolean = false @@ -1475,6 +1560,7 @@ private constructor( interval().ifPresent { it.validate() } scope().ifPresent { it.validate() } + unit().ifPresent { it.validate() } validated = true } @@ -1495,7 +1581,8 @@ private constructor( @JvmSynthetic internal fun validity(): Int = (interval.asKnown().getOrNull()?.validity() ?: 0) + - (scope.asKnown().getOrNull()?.validity() ?: 0) + (scope.asKnown().getOrNull()?.validity() ?: 0) + + (unit.asKnown().getOrNull()?.validity() ?: 0) /** * The time window for statistical attributes (`AMOUNT_Z_SCORE`, @@ -1803,6 +1890,166 @@ private constructor( override fun toString() = value.toString() } + /** + * The unit for impossible travel attributes. Required when `attribute` is + * `TRAVEL_SPEED` or `DISTANCE_FROM_LAST_TRANSACTION`. + * + * For `TRAVEL_SPEED`: `MPH` (miles per hour) or `KPH` (kilometers per hour). + * + * For `DISTANCE_FROM_LAST_TRANSACTION`: `MILES` or `KILOMETERS`. + */ + class Unit @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 MPH = of("MPH") + + @JvmField val KPH = of("KPH") + + @JvmField val MILES = of("MILES") + + @JvmField val KILOMETERS = of("KILOMETERS") + + @JvmStatic fun of(value: String) = Unit(JsonField.of(value)) + } + + /** An enum containing [Unit]'s known values. */ + enum class Known { + MPH, + KPH, + MILES, + KILOMETERS, + } + + /** + * An enum containing [Unit]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Unit] 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 { + MPH, + KPH, + MILES, + KILOMETERS, + /** + * An enum member indicating that [Unit] 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) { + MPH -> Value.MPH + KPH -> Value.KPH + MILES -> Value.MILES + KILOMETERS -> Value.KILOMETERS + 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) { + MPH -> Known.MPH + KPH -> Known.KPH + MILES -> Known.MILES + KILOMETERS -> Known.KILOMETERS + else -> throw LithicInvalidDataException("Unknown Unit: $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 + + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws LithicInvalidDataException if any value type in this object doesn't match + * its expected type. + */ + fun validate(): Unit = 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 Unit && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + override fun equals(other: Any?): Boolean { if (this === other) { return true @@ -1811,17 +2058,18 @@ private constructor( return other is Parameters && interval == other.interval && scope == other.scope && + unit == other.unit && additionalProperties == other.additionalProperties } private val hashCode: Int by lazy { - Objects.hash(interval, scope, additionalProperties) + Objects.hash(interval, scope, unit, additionalProperties) } override fun hashCode(): Int = hashCode override fun toString() = - "Parameters{interval=$interval, scope=$scope, additionalProperties=$additionalProperties}" + "Parameters{interval=$interval, scope=$scope, unit=$unit, additionalProperties=$additionalProperties}" } override fun equals(other: Any?): Boolean { diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/SignalsResponse.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/SignalsResponse.kt new file mode 100644 index 00000000..6cf4e030 --- /dev/null +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/SignalsResponse.kt @@ -0,0 +1,2103 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.lithic.api.models + +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import com.lithic.api.core.ExcludeMissing +import com.lithic.api.core.JsonField +import com.lithic.api.core.JsonMissing +import com.lithic.api.core.JsonValue +import com.lithic.api.core.checkKnown +import com.lithic.api.core.checkRequired +import com.lithic.api.core.toImmutable +import com.lithic.api.errors.LithicInvalidDataException +import java.time.OffsetDateTime +import java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Behavioral feature state for a card or account derived from its transaction history. + * + * Derived statistical features (averages, standard deviations, z-scores) are computed using + * Welford's online algorithm over approved transactions. Average fields are null when fewer than 5 + * approved transactions have been recorded. Standard deviation fields are null when fewer than 30 + * approved transactions have been recorded. + * + * 3DS fields (`three_ds_success_rate`, `three_ds_success_count`, `three_ds_total_count`) are + * card-scoped and will be null for account responses. + * + * Raw fields (`seen_countries`, `seen_mccs`, `approved_txn_amount_m2`, etc.) are included so + * clients can compute their own transaction-specific derivations, such as checking whether a new + * transaction's country is in `seen_countries` to determine `is_new_country`, or computing a + * z-score using the raw mean and M2 values. + */ +class SignalsResponse +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val approvedTxnAmountM2: JsonField, + private val approvedTxnAmountM2_30d: JsonField, + private val approvedTxnAmountM2_7d: JsonField, + private val approvedTxnAmountM2_90d: JsonField, + private val approvedTxnCount: JsonField, + private val approvedTxnCount30d: JsonField, + private val approvedTxnCount7d: JsonField, + private val approvedTxnCount90d: JsonField, + private val avgTransactionAmount: JsonField, + private val avgTransactionAmount30d: JsonField, + private val avgTransactionAmount7d: JsonField, + private val avgTransactionAmount90d: JsonField, + private val distinctCountryCount: JsonField, + private val distinctMccCount: JsonField, + private val firstTxnAt: JsonField, + private val isFirstTransaction: JsonField, + private val lastCpCountry: JsonField, + private val lastCpPostalCode: JsonField, + private val lastCpTimestamp: JsonField, + private val lastTxnApprovedAt: JsonField, + private val seenCountries: JsonField>, + private val seenMccs: JsonField>, + private val seenMerchants: JsonField>, + private val stdevTransactionAmount: JsonField, + private val stdevTransactionAmount30d: JsonField, + private val stdevTransactionAmount7d: JsonField, + private val stdevTransactionAmount90d: JsonField, + private val threeDSSuccessCount: JsonField, + private val threeDSSuccessRate: JsonField, + private val threeDSTotalCount: JsonField, + private val timeSinceLastTransactionDays: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("approved_txn_amount_m2") + @ExcludeMissing + approvedTxnAmountM2: JsonField = JsonMissing.of(), + @JsonProperty("approved_txn_amount_m2_30d") + @ExcludeMissing + approvedTxnAmountM2_30d: JsonField = JsonMissing.of(), + @JsonProperty("approved_txn_amount_m2_7d") + @ExcludeMissing + approvedTxnAmountM2_7d: JsonField = JsonMissing.of(), + @JsonProperty("approved_txn_amount_m2_90d") + @ExcludeMissing + approvedTxnAmountM2_90d: JsonField = JsonMissing.of(), + @JsonProperty("approved_txn_count") + @ExcludeMissing + approvedTxnCount: JsonField = JsonMissing.of(), + @JsonProperty("approved_txn_count_30d") + @ExcludeMissing + approvedTxnCount30d: JsonField = JsonMissing.of(), + @JsonProperty("approved_txn_count_7d") + @ExcludeMissing + approvedTxnCount7d: JsonField = JsonMissing.of(), + @JsonProperty("approved_txn_count_90d") + @ExcludeMissing + approvedTxnCount90d: JsonField = JsonMissing.of(), + @JsonProperty("avg_transaction_amount") + @ExcludeMissing + avgTransactionAmount: JsonField = JsonMissing.of(), + @JsonProperty("avg_transaction_amount_30d") + @ExcludeMissing + avgTransactionAmount30d: JsonField = JsonMissing.of(), + @JsonProperty("avg_transaction_amount_7d") + @ExcludeMissing + avgTransactionAmount7d: JsonField = JsonMissing.of(), + @JsonProperty("avg_transaction_amount_90d") + @ExcludeMissing + avgTransactionAmount90d: JsonField = JsonMissing.of(), + @JsonProperty("distinct_country_count") + @ExcludeMissing + distinctCountryCount: JsonField = JsonMissing.of(), + @JsonProperty("distinct_mcc_count") + @ExcludeMissing + distinctMccCount: JsonField = JsonMissing.of(), + @JsonProperty("first_txn_at") + @ExcludeMissing + firstTxnAt: JsonField = JsonMissing.of(), + @JsonProperty("is_first_transaction") + @ExcludeMissing + isFirstTransaction: JsonField = JsonMissing.of(), + @JsonProperty("last_cp_country") + @ExcludeMissing + lastCpCountry: JsonField = JsonMissing.of(), + @JsonProperty("last_cp_postal_code") + @ExcludeMissing + lastCpPostalCode: JsonField = JsonMissing.of(), + @JsonProperty("last_cp_timestamp") + @ExcludeMissing + lastCpTimestamp: JsonField = JsonMissing.of(), + @JsonProperty("last_txn_approved_at") + @ExcludeMissing + lastTxnApprovedAt: JsonField = JsonMissing.of(), + @JsonProperty("seen_countries") + @ExcludeMissing + seenCountries: JsonField> = JsonMissing.of(), + @JsonProperty("seen_mccs") + @ExcludeMissing + seenMccs: JsonField> = JsonMissing.of(), + @JsonProperty("seen_merchants") + @ExcludeMissing + seenMerchants: JsonField> = JsonMissing.of(), + @JsonProperty("stdev_transaction_amount") + @ExcludeMissing + stdevTransactionAmount: JsonField = JsonMissing.of(), + @JsonProperty("stdev_transaction_amount_30d") + @ExcludeMissing + stdevTransactionAmount30d: JsonField = JsonMissing.of(), + @JsonProperty("stdev_transaction_amount_7d") + @ExcludeMissing + stdevTransactionAmount7d: JsonField = JsonMissing.of(), + @JsonProperty("stdev_transaction_amount_90d") + @ExcludeMissing + stdevTransactionAmount90d: JsonField = JsonMissing.of(), + @JsonProperty("three_ds_success_count") + @ExcludeMissing + threeDSSuccessCount: JsonField = JsonMissing.of(), + @JsonProperty("three_ds_success_rate") + @ExcludeMissing + threeDSSuccessRate: JsonField = JsonMissing.of(), + @JsonProperty("three_ds_total_count") + @ExcludeMissing + threeDSTotalCount: JsonField = JsonMissing.of(), + @JsonProperty("time_since_last_transaction_days") + @ExcludeMissing + timeSinceLastTransactionDays: JsonField = JsonMissing.of(), + ) : this( + approvedTxnAmountM2, + approvedTxnAmountM2_30d, + approvedTxnAmountM2_7d, + approvedTxnAmountM2_90d, + approvedTxnCount, + approvedTxnCount30d, + approvedTxnCount7d, + approvedTxnCount90d, + avgTransactionAmount, + avgTransactionAmount30d, + avgTransactionAmount7d, + avgTransactionAmount90d, + distinctCountryCount, + distinctMccCount, + firstTxnAt, + isFirstTransaction, + lastCpCountry, + lastCpPostalCode, + lastCpTimestamp, + lastTxnApprovedAt, + seenCountries, + seenMccs, + seenMerchants, + stdevTransactionAmount, + stdevTransactionAmount30d, + stdevTransactionAmount7d, + stdevTransactionAmount90d, + threeDSSuccessCount, + threeDSSuccessRate, + threeDSTotalCount, + timeSinceLastTransactionDays, + mutableMapOf(), + ) + + /** + * The Welford M2 accumulator for lifetime approved transaction amounts. Used together with + * `avg_transaction_amount` and `approved_txn_count` to compute the z-score of a new transaction + * amount (variance = M2 / (count - 1)). + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun approvedTxnAmountM2(): Optional = + approvedTxnAmountM2.getOptional("approved_txn_amount_m2") + + /** + * The Welford M2 accumulator for approved transaction amounts over the last 30 days. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun approvedTxnAmountM2_30d(): Optional = + approvedTxnAmountM2_30d.getOptional("approved_txn_amount_m2_30d") + + /** + * The Welford M2 accumulator for approved transaction amounts over the last 7 days. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun approvedTxnAmountM2_7d(): Optional = + approvedTxnAmountM2_7d.getOptional("approved_txn_amount_m2_7d") + + /** + * The Welford M2 accumulator for approved transaction amounts over the last 90 days. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun approvedTxnAmountM2_90d(): Optional = + approvedTxnAmountM2_90d.getOptional("approved_txn_amount_m2_90d") + + /** + * The total number of approved transactions over the entity's lifetime. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun approvedTxnCount(): Optional = approvedTxnCount.getOptional("approved_txn_count") + + /** + * The number of approved transactions in the last 30 days. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun approvedTxnCount30d(): Optional = + approvedTxnCount30d.getOptional("approved_txn_count_30d") + + /** + * The number of approved transactions in the last 7 days. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun approvedTxnCount7d(): Optional = + approvedTxnCount7d.getOptional("approved_txn_count_7d") + + /** + * The number of approved transactions in the last 90 days. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun approvedTxnCount90d(): Optional = + approvedTxnCount90d.getOptional("approved_txn_count_90d") + + /** + * The average approved transaction amount over the entity's lifetime, in cents. Null if fewer + * than 5 approved transactions have been recorded. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun avgTransactionAmount(): Optional = + avgTransactionAmount.getOptional("avg_transaction_amount") + + /** + * The average approved transaction amount over the last 30 days, in cents. Null if fewer than 5 + * approved transactions in window. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun avgTransactionAmount30d(): Optional = + avgTransactionAmount30d.getOptional("avg_transaction_amount_30d") + + /** + * The average approved transaction amount over the last 7 days, in cents. Null if fewer than 5 + * approved transactions in window. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun avgTransactionAmount7d(): Optional = + avgTransactionAmount7d.getOptional("avg_transaction_amount_7d") + + /** + * The average approved transaction amount over the last 90 days, in cents. Null if fewer than 5 + * approved transactions in window. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun avgTransactionAmount90d(): Optional = + avgTransactionAmount90d.getOptional("avg_transaction_amount_90d") + + /** + * The number of distinct merchant countries seen in the entity's transaction history. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun distinctCountryCount(): Optional = + distinctCountryCount.getOptional("distinct_country_count") + + /** + * The number of distinct MCCs seen in the entity's transaction history. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun distinctMccCount(): Optional = distinctMccCount.getOptional("distinct_mcc_count") + + /** + * The timestamp of the first approved transaction for the entity, in ISO 8601 format. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun firstTxnAt(): Optional = firstTxnAt.getOptional("first_txn_at") + + /** + * Whether the entity has no prior transaction history. Returns true if no history is found. + * Null if transaction history exists but a first transaction timestamp is unavailable. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun isFirstTransaction(): Optional = + isFirstTransaction.getOptional("is_first_transaction") + + /** + * The merchant country of the last card-present transaction. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastCpCountry(): Optional = lastCpCountry.getOptional("last_cp_country") + + /** + * The merchant postal code of the last card-present transaction. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastCpPostalCode(): Optional = lastCpPostalCode.getOptional("last_cp_postal_code") + + /** + * The timestamp of the last card-present transaction, in ISO 8601 format. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastCpTimestamp(): Optional = + lastCpTimestamp.getOptional("last_cp_timestamp") + + /** + * The timestamp of the most recent approved transaction for the entity, in ISO 8601 format. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun lastTxnApprovedAt(): Optional = + lastTxnApprovedAt.getOptional("last_txn_approved_at") + + /** + * The set of merchant countries seen in the entity's transaction history. Clients can use this + * to determine whether a new transaction's country is novel (i.e. compute `is_new_country`). + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun seenCountries(): Optional> = seenCountries.getOptional("seen_countries") + + /** + * The set of MCCs seen in the entity's transaction history. Clients can use this to determine + * whether a new transaction's MCC is novel (i.e. compute `is_new_mcc`). + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun seenMccs(): Optional> = seenMccs.getOptional("seen_mccs") + + /** + * The set of card acceptor IDs seen in the card's approved transaction history, capped at the + * 1000 most recently seen. Null for account responses. Clients can use this to determine + * whether a new transaction's merchant is novel (i.e. compute `is_new_merchant`). + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun seenMerchants(): Optional> = seenMerchants.getOptional("seen_merchants") + + /** + * The standard deviation of approved transaction amounts over the entity's lifetime, in cents. + * Null if fewer than 30 approved transactions have been recorded. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun stdevTransactionAmount(): Optional = + stdevTransactionAmount.getOptional("stdev_transaction_amount") + + /** + * The standard deviation of approved transaction amounts over the last 30 days, in cents. Null + * if fewer than 30 approved transactions in window. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun stdevTransactionAmount30d(): Optional = + stdevTransactionAmount30d.getOptional("stdev_transaction_amount_30d") + + /** + * The standard deviation of approved transaction amounts over the last 7 days, in cents. Null + * if fewer than 30 approved transactions in window. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun stdevTransactionAmount7d(): Optional = + stdevTransactionAmount7d.getOptional("stdev_transaction_amount_7d") + + /** + * The standard deviation of approved transaction amounts over the last 90 days, in cents. Null + * if fewer than 30 approved transactions in window. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun stdevTransactionAmount90d(): Optional = + stdevTransactionAmount90d.getOptional("stdev_transaction_amount_90d") + + /** + * The number of successful 3DS authentications for the card. Null for account responses. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun threeDSSuccessCount(): Optional = + threeDSSuccessCount.getOptional("three_ds_success_count") + + /** + * The 3DS authentication success rate for the card, as a percentage from 0.0 to 100.0. Null for + * account responses. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun threeDSSuccessRate(): Optional = + threeDSSuccessRate.getOptional("three_ds_success_rate") + + /** + * The total number of 3DS authentication attempts for the card. Null for account responses. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun threeDSTotalCount(): Optional = threeDSTotalCount.getOptional("three_ds_total_count") + + /** + * The number of days since the last approved transaction on the entity. + * + * @throws LithicInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun timeSinceLastTransactionDays(): Optional = + timeSinceLastTransactionDays.getOptional("time_since_last_transaction_days") + + /** + * Returns the raw JSON value of [approvedTxnAmountM2]. + * + * Unlike [approvedTxnAmountM2], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("approved_txn_amount_m2") + @ExcludeMissing + fun _approvedTxnAmountM2(): JsonField = approvedTxnAmountM2 + + /** + * Returns the raw JSON value of [approvedTxnAmountM2_30d]. + * + * Unlike [approvedTxnAmountM2_30d], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("approved_txn_amount_m2_30d") + @ExcludeMissing + fun _approvedTxnAmountM2_30d(): JsonField = approvedTxnAmountM2_30d + + /** + * Returns the raw JSON value of [approvedTxnAmountM2_7d]. + * + * Unlike [approvedTxnAmountM2_7d], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("approved_txn_amount_m2_7d") + @ExcludeMissing + fun _approvedTxnAmountM2_7d(): JsonField = approvedTxnAmountM2_7d + + /** + * Returns the raw JSON value of [approvedTxnAmountM2_90d]. + * + * Unlike [approvedTxnAmountM2_90d], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("approved_txn_amount_m2_90d") + @ExcludeMissing + fun _approvedTxnAmountM2_90d(): JsonField = approvedTxnAmountM2_90d + + /** + * Returns the raw JSON value of [approvedTxnCount]. + * + * Unlike [approvedTxnCount], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("approved_txn_count") + @ExcludeMissing + fun _approvedTxnCount(): JsonField = approvedTxnCount + + /** + * Returns the raw JSON value of [approvedTxnCount30d]. + * + * Unlike [approvedTxnCount30d], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("approved_txn_count_30d") + @ExcludeMissing + fun _approvedTxnCount30d(): JsonField = approvedTxnCount30d + + /** + * Returns the raw JSON value of [approvedTxnCount7d]. + * + * Unlike [approvedTxnCount7d], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("approved_txn_count_7d") + @ExcludeMissing + fun _approvedTxnCount7d(): JsonField = approvedTxnCount7d + + /** + * Returns the raw JSON value of [approvedTxnCount90d]. + * + * Unlike [approvedTxnCount90d], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("approved_txn_count_90d") + @ExcludeMissing + fun _approvedTxnCount90d(): JsonField = approvedTxnCount90d + + /** + * Returns the raw JSON value of [avgTransactionAmount]. + * + * Unlike [avgTransactionAmount], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("avg_transaction_amount") + @ExcludeMissing + fun _avgTransactionAmount(): JsonField = avgTransactionAmount + + /** + * Returns the raw JSON value of [avgTransactionAmount30d]. + * + * Unlike [avgTransactionAmount30d], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("avg_transaction_amount_30d") + @ExcludeMissing + fun _avgTransactionAmount30d(): JsonField = avgTransactionAmount30d + + /** + * Returns the raw JSON value of [avgTransactionAmount7d]. + * + * Unlike [avgTransactionAmount7d], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("avg_transaction_amount_7d") + @ExcludeMissing + fun _avgTransactionAmount7d(): JsonField = avgTransactionAmount7d + + /** + * Returns the raw JSON value of [avgTransactionAmount90d]. + * + * Unlike [avgTransactionAmount90d], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("avg_transaction_amount_90d") + @ExcludeMissing + fun _avgTransactionAmount90d(): JsonField = avgTransactionAmount90d + + /** + * Returns the raw JSON value of [distinctCountryCount]. + * + * Unlike [distinctCountryCount], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("distinct_country_count") + @ExcludeMissing + fun _distinctCountryCount(): JsonField = distinctCountryCount + + /** + * Returns the raw JSON value of [distinctMccCount]. + * + * Unlike [distinctMccCount], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("distinct_mcc_count") + @ExcludeMissing + fun _distinctMccCount(): JsonField = distinctMccCount + + /** + * Returns the raw JSON value of [firstTxnAt]. + * + * Unlike [firstTxnAt], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("first_txn_at") + @ExcludeMissing + fun _firstTxnAt(): JsonField = firstTxnAt + + /** + * Returns the raw JSON value of [isFirstTransaction]. + * + * Unlike [isFirstTransaction], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("is_first_transaction") + @ExcludeMissing + fun _isFirstTransaction(): JsonField = isFirstTransaction + + /** + * Returns the raw JSON value of [lastCpCountry]. + * + * Unlike [lastCpCountry], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("last_cp_country") + @ExcludeMissing + fun _lastCpCountry(): JsonField = lastCpCountry + + /** + * Returns the raw JSON value of [lastCpPostalCode]. + * + * Unlike [lastCpPostalCode], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("last_cp_postal_code") + @ExcludeMissing + fun _lastCpPostalCode(): JsonField = lastCpPostalCode + + /** + * Returns the raw JSON value of [lastCpTimestamp]. + * + * Unlike [lastCpTimestamp], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("last_cp_timestamp") + @ExcludeMissing + fun _lastCpTimestamp(): JsonField = lastCpTimestamp + + /** + * Returns the raw JSON value of [lastTxnApprovedAt]. + * + * Unlike [lastTxnApprovedAt], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("last_txn_approved_at") + @ExcludeMissing + fun _lastTxnApprovedAt(): JsonField = lastTxnApprovedAt + + /** + * Returns the raw JSON value of [seenCountries]. + * + * Unlike [seenCountries], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("seen_countries") + @ExcludeMissing + fun _seenCountries(): JsonField> = seenCountries + + /** + * Returns the raw JSON value of [seenMccs]. + * + * Unlike [seenMccs], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("seen_mccs") @ExcludeMissing fun _seenMccs(): JsonField> = seenMccs + + /** + * Returns the raw JSON value of [seenMerchants]. + * + * Unlike [seenMerchants], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("seen_merchants") + @ExcludeMissing + fun _seenMerchants(): JsonField> = seenMerchants + + /** + * Returns the raw JSON value of [stdevTransactionAmount]. + * + * Unlike [stdevTransactionAmount], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("stdev_transaction_amount") + @ExcludeMissing + fun _stdevTransactionAmount(): JsonField = stdevTransactionAmount + + /** + * Returns the raw JSON value of [stdevTransactionAmount30d]. + * + * Unlike [stdevTransactionAmount30d], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("stdev_transaction_amount_30d") + @ExcludeMissing + fun _stdevTransactionAmount30d(): JsonField = stdevTransactionAmount30d + + /** + * Returns the raw JSON value of [stdevTransactionAmount7d]. + * + * Unlike [stdevTransactionAmount7d], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("stdev_transaction_amount_7d") + @ExcludeMissing + fun _stdevTransactionAmount7d(): JsonField = stdevTransactionAmount7d + + /** + * Returns the raw JSON value of [stdevTransactionAmount90d]. + * + * Unlike [stdevTransactionAmount90d], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("stdev_transaction_amount_90d") + @ExcludeMissing + fun _stdevTransactionAmount90d(): JsonField = stdevTransactionAmount90d + + /** + * Returns the raw JSON value of [threeDSSuccessCount]. + * + * Unlike [threeDSSuccessCount], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("three_ds_success_count") + @ExcludeMissing + fun _threeDSSuccessCount(): JsonField = threeDSSuccessCount + + /** + * Returns the raw JSON value of [threeDSSuccessRate]. + * + * Unlike [threeDSSuccessRate], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("three_ds_success_rate") + @ExcludeMissing + fun _threeDSSuccessRate(): JsonField = threeDSSuccessRate + + /** + * Returns the raw JSON value of [threeDSTotalCount]. + * + * Unlike [threeDSTotalCount], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("three_ds_total_count") + @ExcludeMissing + fun _threeDSTotalCount(): JsonField = threeDSTotalCount + + /** + * Returns the raw JSON value of [timeSinceLastTransactionDays]. + * + * Unlike [timeSinceLastTransactionDays], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("time_since_last_transaction_days") + @ExcludeMissing + fun _timeSinceLastTransactionDays(): JsonField = timeSinceLastTransactionDays + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [SignalsResponse]. + * + * The following fields are required: + * ```java + * .approvedTxnAmountM2() + * .approvedTxnAmountM2_30d() + * .approvedTxnAmountM2_7d() + * .approvedTxnAmountM2_90d() + * .approvedTxnCount() + * .approvedTxnCount30d() + * .approvedTxnCount7d() + * .approvedTxnCount90d() + * .avgTransactionAmount() + * .avgTransactionAmount30d() + * .avgTransactionAmount7d() + * .avgTransactionAmount90d() + * .distinctCountryCount() + * .distinctMccCount() + * .firstTxnAt() + * .isFirstTransaction() + * .lastCpCountry() + * .lastCpPostalCode() + * .lastCpTimestamp() + * .lastTxnApprovedAt() + * .seenCountries() + * .seenMccs() + * .seenMerchants() + * .stdevTransactionAmount() + * .stdevTransactionAmount30d() + * .stdevTransactionAmount7d() + * .stdevTransactionAmount90d() + * .threeDSSuccessCount() + * .threeDSSuccessRate() + * .threeDSTotalCount() + * .timeSinceLastTransactionDays() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [SignalsResponse]. */ + class Builder internal constructor() { + + private var approvedTxnAmountM2: JsonField? = null + private var approvedTxnAmountM2_30d: JsonField? = null + private var approvedTxnAmountM2_7d: JsonField? = null + private var approvedTxnAmountM2_90d: JsonField? = null + private var approvedTxnCount: JsonField? = null + private var approvedTxnCount30d: JsonField? = null + private var approvedTxnCount7d: JsonField? = null + private var approvedTxnCount90d: JsonField? = null + private var avgTransactionAmount: JsonField? = null + private var avgTransactionAmount30d: JsonField? = null + private var avgTransactionAmount7d: JsonField? = null + private var avgTransactionAmount90d: JsonField? = null + private var distinctCountryCount: JsonField? = null + private var distinctMccCount: JsonField? = null + private var firstTxnAt: JsonField? = null + private var isFirstTransaction: JsonField? = null + private var lastCpCountry: JsonField? = null + private var lastCpPostalCode: JsonField? = null + private var lastCpTimestamp: JsonField? = null + private var lastTxnApprovedAt: JsonField? = null + private var seenCountries: JsonField>? = null + private var seenMccs: JsonField>? = null + private var seenMerchants: JsonField>? = null + private var stdevTransactionAmount: JsonField? = null + private var stdevTransactionAmount30d: JsonField? = null + private var stdevTransactionAmount7d: JsonField? = null + private var stdevTransactionAmount90d: JsonField? = null + private var threeDSSuccessCount: JsonField? = null + private var threeDSSuccessRate: JsonField? = null + private var threeDSTotalCount: JsonField? = null + private var timeSinceLastTransactionDays: JsonField? = null + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(signalsResponse: SignalsResponse) = apply { + approvedTxnAmountM2 = signalsResponse.approvedTxnAmountM2 + approvedTxnAmountM2_30d = signalsResponse.approvedTxnAmountM2_30d + approvedTxnAmountM2_7d = signalsResponse.approvedTxnAmountM2_7d + approvedTxnAmountM2_90d = signalsResponse.approvedTxnAmountM2_90d + approvedTxnCount = signalsResponse.approvedTxnCount + approvedTxnCount30d = signalsResponse.approvedTxnCount30d + approvedTxnCount7d = signalsResponse.approvedTxnCount7d + approvedTxnCount90d = signalsResponse.approvedTxnCount90d + avgTransactionAmount = signalsResponse.avgTransactionAmount + avgTransactionAmount30d = signalsResponse.avgTransactionAmount30d + avgTransactionAmount7d = signalsResponse.avgTransactionAmount7d + avgTransactionAmount90d = signalsResponse.avgTransactionAmount90d + distinctCountryCount = signalsResponse.distinctCountryCount + distinctMccCount = signalsResponse.distinctMccCount + firstTxnAt = signalsResponse.firstTxnAt + isFirstTransaction = signalsResponse.isFirstTransaction + lastCpCountry = signalsResponse.lastCpCountry + lastCpPostalCode = signalsResponse.lastCpPostalCode + lastCpTimestamp = signalsResponse.lastCpTimestamp + lastTxnApprovedAt = signalsResponse.lastTxnApprovedAt + seenCountries = signalsResponse.seenCountries.map { it.toMutableList() } + seenMccs = signalsResponse.seenMccs.map { it.toMutableList() } + seenMerchants = signalsResponse.seenMerchants.map { it.toMutableList() } + stdevTransactionAmount = signalsResponse.stdevTransactionAmount + stdevTransactionAmount30d = signalsResponse.stdevTransactionAmount30d + stdevTransactionAmount7d = signalsResponse.stdevTransactionAmount7d + stdevTransactionAmount90d = signalsResponse.stdevTransactionAmount90d + threeDSSuccessCount = signalsResponse.threeDSSuccessCount + threeDSSuccessRate = signalsResponse.threeDSSuccessRate + threeDSTotalCount = signalsResponse.threeDSTotalCount + timeSinceLastTransactionDays = signalsResponse.timeSinceLastTransactionDays + additionalProperties = signalsResponse.additionalProperties.toMutableMap() + } + + /** + * The Welford M2 accumulator for lifetime approved transaction amounts. Used together with + * `avg_transaction_amount` and `approved_txn_count` to compute the z-score of a new + * transaction amount (variance = M2 / (count - 1)). + */ + fun approvedTxnAmountM2(approvedTxnAmountM2: Double?) = + approvedTxnAmountM2(JsonField.ofNullable(approvedTxnAmountM2)) + + /** + * Alias for [Builder.approvedTxnAmountM2]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun approvedTxnAmountM2(approvedTxnAmountM2: Double) = + approvedTxnAmountM2(approvedTxnAmountM2 as Double?) + + /** + * Alias for calling [Builder.approvedTxnAmountM2] with `approvedTxnAmountM2.orElse(null)`. + */ + fun approvedTxnAmountM2(approvedTxnAmountM2: Optional) = + approvedTxnAmountM2(approvedTxnAmountM2.getOrNull()) + + /** + * Sets [Builder.approvedTxnAmountM2] to an arbitrary JSON value. + * + * You should usually call [Builder.approvedTxnAmountM2] with a well-typed [Double] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun approvedTxnAmountM2(approvedTxnAmountM2: JsonField) = apply { + this.approvedTxnAmountM2 = approvedTxnAmountM2 + } + + /** The Welford M2 accumulator for approved transaction amounts over the last 30 days. */ + fun approvedTxnAmountM2_30d(approvedTxnAmountM2_30d: Double?) = + approvedTxnAmountM2_30d(JsonField.ofNullable(approvedTxnAmountM2_30d)) + + /** + * Alias for [Builder.approvedTxnAmountM2_30d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun approvedTxnAmountM2_30d(approvedTxnAmountM2_30d: Double) = + approvedTxnAmountM2_30d(approvedTxnAmountM2_30d as Double?) + + /** + * Alias for calling [Builder.approvedTxnAmountM2_30d] with + * `approvedTxnAmountM2_30d.orElse(null)`. + */ + fun approvedTxnAmountM2_30d(approvedTxnAmountM2_30d: Optional) = + approvedTxnAmountM2_30d(approvedTxnAmountM2_30d.getOrNull()) + + /** + * Sets [Builder.approvedTxnAmountM2_30d] to an arbitrary JSON value. + * + * You should usually call [Builder.approvedTxnAmountM2_30d] with a well-typed [Double] + * value instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun approvedTxnAmountM2_30d(approvedTxnAmountM2_30d: JsonField) = apply { + this.approvedTxnAmountM2_30d = approvedTxnAmountM2_30d + } + + /** The Welford M2 accumulator for approved transaction amounts over the last 7 days. */ + fun approvedTxnAmountM2_7d(approvedTxnAmountM2_7d: Double?) = + approvedTxnAmountM2_7d(JsonField.ofNullable(approvedTxnAmountM2_7d)) + + /** + * Alias for [Builder.approvedTxnAmountM2_7d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun approvedTxnAmountM2_7d(approvedTxnAmountM2_7d: Double) = + approvedTxnAmountM2_7d(approvedTxnAmountM2_7d as Double?) + + /** + * Alias for calling [Builder.approvedTxnAmountM2_7d] with + * `approvedTxnAmountM2_7d.orElse(null)`. + */ + fun approvedTxnAmountM2_7d(approvedTxnAmountM2_7d: Optional) = + approvedTxnAmountM2_7d(approvedTxnAmountM2_7d.getOrNull()) + + /** + * Sets [Builder.approvedTxnAmountM2_7d] to an arbitrary JSON value. + * + * You should usually call [Builder.approvedTxnAmountM2_7d] with a well-typed [Double] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun approvedTxnAmountM2_7d(approvedTxnAmountM2_7d: JsonField) = apply { + this.approvedTxnAmountM2_7d = approvedTxnAmountM2_7d + } + + /** The Welford M2 accumulator for approved transaction amounts over the last 90 days. */ + fun approvedTxnAmountM2_90d(approvedTxnAmountM2_90d: Double?) = + approvedTxnAmountM2_90d(JsonField.ofNullable(approvedTxnAmountM2_90d)) + + /** + * Alias for [Builder.approvedTxnAmountM2_90d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun approvedTxnAmountM2_90d(approvedTxnAmountM2_90d: Double) = + approvedTxnAmountM2_90d(approvedTxnAmountM2_90d as Double?) + + /** + * Alias for calling [Builder.approvedTxnAmountM2_90d] with + * `approvedTxnAmountM2_90d.orElse(null)`. + */ + fun approvedTxnAmountM2_90d(approvedTxnAmountM2_90d: Optional) = + approvedTxnAmountM2_90d(approvedTxnAmountM2_90d.getOrNull()) + + /** + * Sets [Builder.approvedTxnAmountM2_90d] to an arbitrary JSON value. + * + * You should usually call [Builder.approvedTxnAmountM2_90d] with a well-typed [Double] + * value instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun approvedTxnAmountM2_90d(approvedTxnAmountM2_90d: JsonField) = apply { + this.approvedTxnAmountM2_90d = approvedTxnAmountM2_90d + } + + /** The total number of approved transactions over the entity's lifetime. */ + fun approvedTxnCount(approvedTxnCount: Long?) = + approvedTxnCount(JsonField.ofNullable(approvedTxnCount)) + + /** + * Alias for [Builder.approvedTxnCount]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun approvedTxnCount(approvedTxnCount: Long) = approvedTxnCount(approvedTxnCount as Long?) + + /** Alias for calling [Builder.approvedTxnCount] with `approvedTxnCount.orElse(null)`. */ + fun approvedTxnCount(approvedTxnCount: Optional) = + approvedTxnCount(approvedTxnCount.getOrNull()) + + /** + * Sets [Builder.approvedTxnCount] to an arbitrary JSON value. + * + * You should usually call [Builder.approvedTxnCount] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun approvedTxnCount(approvedTxnCount: JsonField) = apply { + this.approvedTxnCount = approvedTxnCount + } + + /** The number of approved transactions in the last 30 days. */ + fun approvedTxnCount30d(approvedTxnCount30d: Long?) = + approvedTxnCount30d(JsonField.ofNullable(approvedTxnCount30d)) + + /** + * Alias for [Builder.approvedTxnCount30d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun approvedTxnCount30d(approvedTxnCount30d: Long) = + approvedTxnCount30d(approvedTxnCount30d as Long?) + + /** + * Alias for calling [Builder.approvedTxnCount30d] with `approvedTxnCount30d.orElse(null)`. + */ + fun approvedTxnCount30d(approvedTxnCount30d: Optional) = + approvedTxnCount30d(approvedTxnCount30d.getOrNull()) + + /** + * Sets [Builder.approvedTxnCount30d] to an arbitrary JSON value. + * + * You should usually call [Builder.approvedTxnCount30d] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun approvedTxnCount30d(approvedTxnCount30d: JsonField) = apply { + this.approvedTxnCount30d = approvedTxnCount30d + } + + /** The number of approved transactions in the last 7 days. */ + fun approvedTxnCount7d(approvedTxnCount7d: Long?) = + approvedTxnCount7d(JsonField.ofNullable(approvedTxnCount7d)) + + /** + * Alias for [Builder.approvedTxnCount7d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun approvedTxnCount7d(approvedTxnCount7d: Long) = + approvedTxnCount7d(approvedTxnCount7d as Long?) + + /** + * Alias for calling [Builder.approvedTxnCount7d] with `approvedTxnCount7d.orElse(null)`. + */ + fun approvedTxnCount7d(approvedTxnCount7d: Optional) = + approvedTxnCount7d(approvedTxnCount7d.getOrNull()) + + /** + * Sets [Builder.approvedTxnCount7d] to an arbitrary JSON value. + * + * You should usually call [Builder.approvedTxnCount7d] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun approvedTxnCount7d(approvedTxnCount7d: JsonField) = apply { + this.approvedTxnCount7d = approvedTxnCount7d + } + + /** The number of approved transactions in the last 90 days. */ + fun approvedTxnCount90d(approvedTxnCount90d: Long?) = + approvedTxnCount90d(JsonField.ofNullable(approvedTxnCount90d)) + + /** + * Alias for [Builder.approvedTxnCount90d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun approvedTxnCount90d(approvedTxnCount90d: Long) = + approvedTxnCount90d(approvedTxnCount90d as Long?) + + /** + * Alias for calling [Builder.approvedTxnCount90d] with `approvedTxnCount90d.orElse(null)`. + */ + fun approvedTxnCount90d(approvedTxnCount90d: Optional) = + approvedTxnCount90d(approvedTxnCount90d.getOrNull()) + + /** + * Sets [Builder.approvedTxnCount90d] to an arbitrary JSON value. + * + * You should usually call [Builder.approvedTxnCount90d] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun approvedTxnCount90d(approvedTxnCount90d: JsonField) = apply { + this.approvedTxnCount90d = approvedTxnCount90d + } + + /** + * The average approved transaction amount over the entity's lifetime, in cents. Null if + * fewer than 5 approved transactions have been recorded. + */ + fun avgTransactionAmount(avgTransactionAmount: Double?) = + avgTransactionAmount(JsonField.ofNullable(avgTransactionAmount)) + + /** + * Alias for [Builder.avgTransactionAmount]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun avgTransactionAmount(avgTransactionAmount: Double) = + avgTransactionAmount(avgTransactionAmount as Double?) + + /** + * Alias for calling [Builder.avgTransactionAmount] with + * `avgTransactionAmount.orElse(null)`. + */ + fun avgTransactionAmount(avgTransactionAmount: Optional) = + avgTransactionAmount(avgTransactionAmount.getOrNull()) + + /** + * Sets [Builder.avgTransactionAmount] to an arbitrary JSON value. + * + * You should usually call [Builder.avgTransactionAmount] with a well-typed [Double] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun avgTransactionAmount(avgTransactionAmount: JsonField) = apply { + this.avgTransactionAmount = avgTransactionAmount + } + + /** + * The average approved transaction amount over the last 30 days, in cents. Null if fewer + * than 5 approved transactions in window. + */ + fun avgTransactionAmount30d(avgTransactionAmount30d: Double?) = + avgTransactionAmount30d(JsonField.ofNullable(avgTransactionAmount30d)) + + /** + * Alias for [Builder.avgTransactionAmount30d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun avgTransactionAmount30d(avgTransactionAmount30d: Double) = + avgTransactionAmount30d(avgTransactionAmount30d as Double?) + + /** + * Alias for calling [Builder.avgTransactionAmount30d] with + * `avgTransactionAmount30d.orElse(null)`. + */ + fun avgTransactionAmount30d(avgTransactionAmount30d: Optional) = + avgTransactionAmount30d(avgTransactionAmount30d.getOrNull()) + + /** + * Sets [Builder.avgTransactionAmount30d] to an arbitrary JSON value. + * + * You should usually call [Builder.avgTransactionAmount30d] with a well-typed [Double] + * value instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun avgTransactionAmount30d(avgTransactionAmount30d: JsonField) = apply { + this.avgTransactionAmount30d = avgTransactionAmount30d + } + + /** + * The average approved transaction amount over the last 7 days, in cents. Null if fewer + * than 5 approved transactions in window. + */ + fun avgTransactionAmount7d(avgTransactionAmount7d: Double?) = + avgTransactionAmount7d(JsonField.ofNullable(avgTransactionAmount7d)) + + /** + * Alias for [Builder.avgTransactionAmount7d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun avgTransactionAmount7d(avgTransactionAmount7d: Double) = + avgTransactionAmount7d(avgTransactionAmount7d as Double?) + + /** + * Alias for calling [Builder.avgTransactionAmount7d] with + * `avgTransactionAmount7d.orElse(null)`. + */ + fun avgTransactionAmount7d(avgTransactionAmount7d: Optional) = + avgTransactionAmount7d(avgTransactionAmount7d.getOrNull()) + + /** + * Sets [Builder.avgTransactionAmount7d] to an arbitrary JSON value. + * + * You should usually call [Builder.avgTransactionAmount7d] with a well-typed [Double] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun avgTransactionAmount7d(avgTransactionAmount7d: JsonField) = apply { + this.avgTransactionAmount7d = avgTransactionAmount7d + } + + /** + * The average approved transaction amount over the last 90 days, in cents. Null if fewer + * than 5 approved transactions in window. + */ + fun avgTransactionAmount90d(avgTransactionAmount90d: Double?) = + avgTransactionAmount90d(JsonField.ofNullable(avgTransactionAmount90d)) + + /** + * Alias for [Builder.avgTransactionAmount90d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun avgTransactionAmount90d(avgTransactionAmount90d: Double) = + avgTransactionAmount90d(avgTransactionAmount90d as Double?) + + /** + * Alias for calling [Builder.avgTransactionAmount90d] with + * `avgTransactionAmount90d.orElse(null)`. + */ + fun avgTransactionAmount90d(avgTransactionAmount90d: Optional) = + avgTransactionAmount90d(avgTransactionAmount90d.getOrNull()) + + /** + * Sets [Builder.avgTransactionAmount90d] to an arbitrary JSON value. + * + * You should usually call [Builder.avgTransactionAmount90d] with a well-typed [Double] + * value instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun avgTransactionAmount90d(avgTransactionAmount90d: JsonField) = apply { + this.avgTransactionAmount90d = avgTransactionAmount90d + } + + /** The number of distinct merchant countries seen in the entity's transaction history. */ + fun distinctCountryCount(distinctCountryCount: Long?) = + distinctCountryCount(JsonField.ofNullable(distinctCountryCount)) + + /** + * Alias for [Builder.distinctCountryCount]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun distinctCountryCount(distinctCountryCount: Long) = + distinctCountryCount(distinctCountryCount as Long?) + + /** + * Alias for calling [Builder.distinctCountryCount] with + * `distinctCountryCount.orElse(null)`. + */ + fun distinctCountryCount(distinctCountryCount: Optional) = + distinctCountryCount(distinctCountryCount.getOrNull()) + + /** + * Sets [Builder.distinctCountryCount] to an arbitrary JSON value. + * + * You should usually call [Builder.distinctCountryCount] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun distinctCountryCount(distinctCountryCount: JsonField) = apply { + this.distinctCountryCount = distinctCountryCount + } + + /** The number of distinct MCCs seen in the entity's transaction history. */ + fun distinctMccCount(distinctMccCount: Long?) = + distinctMccCount(JsonField.ofNullable(distinctMccCount)) + + /** + * Alias for [Builder.distinctMccCount]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun distinctMccCount(distinctMccCount: Long) = distinctMccCount(distinctMccCount as Long?) + + /** Alias for calling [Builder.distinctMccCount] with `distinctMccCount.orElse(null)`. */ + fun distinctMccCount(distinctMccCount: Optional) = + distinctMccCount(distinctMccCount.getOrNull()) + + /** + * Sets [Builder.distinctMccCount] to an arbitrary JSON value. + * + * You should usually call [Builder.distinctMccCount] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun distinctMccCount(distinctMccCount: JsonField) = apply { + this.distinctMccCount = distinctMccCount + } + + /** The timestamp of the first approved transaction for the entity, in ISO 8601 format. */ + fun firstTxnAt(firstTxnAt: OffsetDateTime?) = firstTxnAt(JsonField.ofNullable(firstTxnAt)) + + /** Alias for calling [Builder.firstTxnAt] with `firstTxnAt.orElse(null)`. */ + fun firstTxnAt(firstTxnAt: Optional) = firstTxnAt(firstTxnAt.getOrNull()) + + /** + * Sets [Builder.firstTxnAt] to an arbitrary JSON value. + * + * You should usually call [Builder.firstTxnAt] with a well-typed [OffsetDateTime] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun firstTxnAt(firstTxnAt: JsonField) = apply { + this.firstTxnAt = firstTxnAt + } + + /** + * Whether the entity has no prior transaction history. Returns true if no history is found. + * Null if transaction history exists but a first transaction timestamp is unavailable. + */ + fun isFirstTransaction(isFirstTransaction: Boolean?) = + isFirstTransaction(JsonField.ofNullable(isFirstTransaction)) + + /** + * Alias for [Builder.isFirstTransaction]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun isFirstTransaction(isFirstTransaction: Boolean) = + isFirstTransaction(isFirstTransaction as Boolean?) + + /** + * Alias for calling [Builder.isFirstTransaction] with `isFirstTransaction.orElse(null)`. + */ + fun isFirstTransaction(isFirstTransaction: Optional) = + isFirstTransaction(isFirstTransaction.getOrNull()) + + /** + * Sets [Builder.isFirstTransaction] to an arbitrary JSON value. + * + * You should usually call [Builder.isFirstTransaction] with a well-typed [Boolean] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun isFirstTransaction(isFirstTransaction: JsonField) = apply { + this.isFirstTransaction = isFirstTransaction + } + + /** The merchant country of the last card-present transaction. */ + fun lastCpCountry(lastCpCountry: String?) = + lastCpCountry(JsonField.ofNullable(lastCpCountry)) + + /** Alias for calling [Builder.lastCpCountry] with `lastCpCountry.orElse(null)`. */ + fun lastCpCountry(lastCpCountry: Optional) = + lastCpCountry(lastCpCountry.getOrNull()) + + /** + * Sets [Builder.lastCpCountry] to an arbitrary JSON value. + * + * You should usually call [Builder.lastCpCountry] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun lastCpCountry(lastCpCountry: JsonField) = apply { + this.lastCpCountry = lastCpCountry + } + + /** The merchant postal code of the last card-present transaction. */ + fun lastCpPostalCode(lastCpPostalCode: String?) = + lastCpPostalCode(JsonField.ofNullable(lastCpPostalCode)) + + /** Alias for calling [Builder.lastCpPostalCode] with `lastCpPostalCode.orElse(null)`. */ + fun lastCpPostalCode(lastCpPostalCode: Optional) = + lastCpPostalCode(lastCpPostalCode.getOrNull()) + + /** + * Sets [Builder.lastCpPostalCode] to an arbitrary JSON value. + * + * You should usually call [Builder.lastCpPostalCode] with a well-typed [String] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun lastCpPostalCode(lastCpPostalCode: JsonField) = apply { + this.lastCpPostalCode = lastCpPostalCode + } + + /** The timestamp of the last card-present transaction, in ISO 8601 format. */ + fun lastCpTimestamp(lastCpTimestamp: OffsetDateTime?) = + lastCpTimestamp(JsonField.ofNullable(lastCpTimestamp)) + + /** Alias for calling [Builder.lastCpTimestamp] with `lastCpTimestamp.orElse(null)`. */ + fun lastCpTimestamp(lastCpTimestamp: Optional) = + lastCpTimestamp(lastCpTimestamp.getOrNull()) + + /** + * Sets [Builder.lastCpTimestamp] to an arbitrary JSON value. + * + * You should usually call [Builder.lastCpTimestamp] with a well-typed [OffsetDateTime] + * value instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun lastCpTimestamp(lastCpTimestamp: JsonField) = apply { + this.lastCpTimestamp = lastCpTimestamp + } + + /** + * The timestamp of the most recent approved transaction for the entity, in ISO 8601 format. + */ + fun lastTxnApprovedAt(lastTxnApprovedAt: OffsetDateTime?) = + lastTxnApprovedAt(JsonField.ofNullable(lastTxnApprovedAt)) + + /** Alias for calling [Builder.lastTxnApprovedAt] with `lastTxnApprovedAt.orElse(null)`. */ + fun lastTxnApprovedAt(lastTxnApprovedAt: Optional) = + lastTxnApprovedAt(lastTxnApprovedAt.getOrNull()) + + /** + * Sets [Builder.lastTxnApprovedAt] to an arbitrary JSON value. + * + * You should usually call [Builder.lastTxnApprovedAt] with a well-typed [OffsetDateTime] + * value instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun lastTxnApprovedAt(lastTxnApprovedAt: JsonField) = apply { + this.lastTxnApprovedAt = lastTxnApprovedAt + } + + /** + * The set of merchant countries seen in the entity's transaction history. Clients can use + * this to determine whether a new transaction's country is novel (i.e. compute + * `is_new_country`). + */ + fun seenCountries(seenCountries: List?) = + seenCountries(JsonField.ofNullable(seenCountries)) + + /** Alias for calling [Builder.seenCountries] with `seenCountries.orElse(null)`. */ + fun seenCountries(seenCountries: Optional>) = + seenCountries(seenCountries.getOrNull()) + + /** + * Sets [Builder.seenCountries] to an arbitrary JSON value. + * + * You should usually call [Builder.seenCountries] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun seenCountries(seenCountries: JsonField>) = apply { + this.seenCountries = seenCountries.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [seenCountries]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addSeenCountry(seenCountry: String) = apply { + seenCountries = + (seenCountries ?: JsonField.of(mutableListOf())).also { + checkKnown("seenCountries", it).add(seenCountry) + } + } + + /** + * The set of MCCs seen in the entity's transaction history. Clients can use this to + * determine whether a new transaction's MCC is novel (i.e. compute `is_new_mcc`). + */ + fun seenMccs(seenMccs: List?) = seenMccs(JsonField.ofNullable(seenMccs)) + + /** Alias for calling [Builder.seenMccs] with `seenMccs.orElse(null)`. */ + fun seenMccs(seenMccs: Optional>) = seenMccs(seenMccs.getOrNull()) + + /** + * Sets [Builder.seenMccs] to an arbitrary JSON value. + * + * You should usually call [Builder.seenMccs] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun seenMccs(seenMccs: JsonField>) = apply { + this.seenMccs = seenMccs.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [seenMccs]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addSeenMcc(seenMcc: String) = apply { + seenMccs = + (seenMccs ?: JsonField.of(mutableListOf())).also { + checkKnown("seenMccs", it).add(seenMcc) + } + } + + /** + * The set of card acceptor IDs seen in the card's approved transaction history, capped at + * the 1000 most recently seen. Null for account responses. Clients can use this to + * determine whether a new transaction's merchant is novel (i.e. compute `is_new_merchant`). + */ + fun seenMerchants(seenMerchants: List?) = + seenMerchants(JsonField.ofNullable(seenMerchants)) + + /** Alias for calling [Builder.seenMerchants] with `seenMerchants.orElse(null)`. */ + fun seenMerchants(seenMerchants: Optional>) = + seenMerchants(seenMerchants.getOrNull()) + + /** + * Sets [Builder.seenMerchants] to an arbitrary JSON value. + * + * You should usually call [Builder.seenMerchants] with a well-typed `List` value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun seenMerchants(seenMerchants: JsonField>) = apply { + this.seenMerchants = seenMerchants.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [seenMerchants]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addSeenMerchant(seenMerchant: String) = apply { + seenMerchants = + (seenMerchants ?: JsonField.of(mutableListOf())).also { + checkKnown("seenMerchants", it).add(seenMerchant) + } + } + + /** + * The standard deviation of approved transaction amounts over the entity's lifetime, in + * cents. Null if fewer than 30 approved transactions have been recorded. + */ + fun stdevTransactionAmount(stdevTransactionAmount: Double?) = + stdevTransactionAmount(JsonField.ofNullable(stdevTransactionAmount)) + + /** + * Alias for [Builder.stdevTransactionAmount]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun stdevTransactionAmount(stdevTransactionAmount: Double) = + stdevTransactionAmount(stdevTransactionAmount as Double?) + + /** + * Alias for calling [Builder.stdevTransactionAmount] with + * `stdevTransactionAmount.orElse(null)`. + */ + fun stdevTransactionAmount(stdevTransactionAmount: Optional) = + stdevTransactionAmount(stdevTransactionAmount.getOrNull()) + + /** + * Sets [Builder.stdevTransactionAmount] to an arbitrary JSON value. + * + * You should usually call [Builder.stdevTransactionAmount] with a well-typed [Double] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun stdevTransactionAmount(stdevTransactionAmount: JsonField) = apply { + this.stdevTransactionAmount = stdevTransactionAmount + } + + /** + * The standard deviation of approved transaction amounts over the last 30 days, in cents. + * Null if fewer than 30 approved transactions in window. + */ + fun stdevTransactionAmount30d(stdevTransactionAmount30d: Double?) = + stdevTransactionAmount30d(JsonField.ofNullable(stdevTransactionAmount30d)) + + /** + * Alias for [Builder.stdevTransactionAmount30d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun stdevTransactionAmount30d(stdevTransactionAmount30d: Double) = + stdevTransactionAmount30d(stdevTransactionAmount30d as Double?) + + /** + * Alias for calling [Builder.stdevTransactionAmount30d] with + * `stdevTransactionAmount30d.orElse(null)`. + */ + fun stdevTransactionAmount30d(stdevTransactionAmount30d: Optional) = + stdevTransactionAmount30d(stdevTransactionAmount30d.getOrNull()) + + /** + * Sets [Builder.stdevTransactionAmount30d] to an arbitrary JSON value. + * + * You should usually call [Builder.stdevTransactionAmount30d] with a well-typed [Double] + * value instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun stdevTransactionAmount30d(stdevTransactionAmount30d: JsonField) = apply { + this.stdevTransactionAmount30d = stdevTransactionAmount30d + } + + /** + * The standard deviation of approved transaction amounts over the last 7 days, in cents. + * Null if fewer than 30 approved transactions in window. + */ + fun stdevTransactionAmount7d(stdevTransactionAmount7d: Double?) = + stdevTransactionAmount7d(JsonField.ofNullable(stdevTransactionAmount7d)) + + /** + * Alias for [Builder.stdevTransactionAmount7d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun stdevTransactionAmount7d(stdevTransactionAmount7d: Double) = + stdevTransactionAmount7d(stdevTransactionAmount7d as Double?) + + /** + * Alias for calling [Builder.stdevTransactionAmount7d] with + * `stdevTransactionAmount7d.orElse(null)`. + */ + fun stdevTransactionAmount7d(stdevTransactionAmount7d: Optional) = + stdevTransactionAmount7d(stdevTransactionAmount7d.getOrNull()) + + /** + * Sets [Builder.stdevTransactionAmount7d] to an arbitrary JSON value. + * + * You should usually call [Builder.stdevTransactionAmount7d] with a well-typed [Double] + * value instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun stdevTransactionAmount7d(stdevTransactionAmount7d: JsonField) = apply { + this.stdevTransactionAmount7d = stdevTransactionAmount7d + } + + /** + * The standard deviation of approved transaction amounts over the last 90 days, in cents. + * Null if fewer than 30 approved transactions in window. + */ + fun stdevTransactionAmount90d(stdevTransactionAmount90d: Double?) = + stdevTransactionAmount90d(JsonField.ofNullable(stdevTransactionAmount90d)) + + /** + * Alias for [Builder.stdevTransactionAmount90d]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun stdevTransactionAmount90d(stdevTransactionAmount90d: Double) = + stdevTransactionAmount90d(stdevTransactionAmount90d as Double?) + + /** + * Alias for calling [Builder.stdevTransactionAmount90d] with + * `stdevTransactionAmount90d.orElse(null)`. + */ + fun stdevTransactionAmount90d(stdevTransactionAmount90d: Optional) = + stdevTransactionAmount90d(stdevTransactionAmount90d.getOrNull()) + + /** + * Sets [Builder.stdevTransactionAmount90d] to an arbitrary JSON value. + * + * You should usually call [Builder.stdevTransactionAmount90d] with a well-typed [Double] + * value instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun stdevTransactionAmount90d(stdevTransactionAmount90d: JsonField) = apply { + this.stdevTransactionAmount90d = stdevTransactionAmount90d + } + + /** + * The number of successful 3DS authentications for the card. Null for account responses. + */ + fun threeDSSuccessCount(threeDSSuccessCount: Long?) = + threeDSSuccessCount(JsonField.ofNullable(threeDSSuccessCount)) + + /** + * Alias for [Builder.threeDSSuccessCount]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun threeDSSuccessCount(threeDSSuccessCount: Long) = + threeDSSuccessCount(threeDSSuccessCount as Long?) + + /** + * Alias for calling [Builder.threeDSSuccessCount] with `threeDSSuccessCount.orElse(null)`. + */ + fun threeDSSuccessCount(threeDSSuccessCount: Optional) = + threeDSSuccessCount(threeDSSuccessCount.getOrNull()) + + /** + * Sets [Builder.threeDSSuccessCount] to an arbitrary JSON value. + * + * You should usually call [Builder.threeDSSuccessCount] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun threeDSSuccessCount(threeDSSuccessCount: JsonField) = apply { + this.threeDSSuccessCount = threeDSSuccessCount + } + + /** + * The 3DS authentication success rate for the card, as a percentage from 0.0 to 100.0. Null + * for account responses. + */ + fun threeDSSuccessRate(threeDSSuccessRate: Double?) = + threeDSSuccessRate(JsonField.ofNullable(threeDSSuccessRate)) + + /** + * Alias for [Builder.threeDSSuccessRate]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun threeDSSuccessRate(threeDSSuccessRate: Double) = + threeDSSuccessRate(threeDSSuccessRate as Double?) + + /** + * Alias for calling [Builder.threeDSSuccessRate] with `threeDSSuccessRate.orElse(null)`. + */ + fun threeDSSuccessRate(threeDSSuccessRate: Optional) = + threeDSSuccessRate(threeDSSuccessRate.getOrNull()) + + /** + * Sets [Builder.threeDSSuccessRate] to an arbitrary JSON value. + * + * You should usually call [Builder.threeDSSuccessRate] with a well-typed [Double] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun threeDSSuccessRate(threeDSSuccessRate: JsonField) = apply { + this.threeDSSuccessRate = threeDSSuccessRate + } + + /** + * The total number of 3DS authentication attempts for the card. Null for account responses. + */ + fun threeDSTotalCount(threeDSTotalCount: Long?) = + threeDSTotalCount(JsonField.ofNullable(threeDSTotalCount)) + + /** + * Alias for [Builder.threeDSTotalCount]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun threeDSTotalCount(threeDSTotalCount: Long) = + threeDSTotalCount(threeDSTotalCount as Long?) + + /** Alias for calling [Builder.threeDSTotalCount] with `threeDSTotalCount.orElse(null)`. */ + fun threeDSTotalCount(threeDSTotalCount: Optional) = + threeDSTotalCount(threeDSTotalCount.getOrNull()) + + /** + * Sets [Builder.threeDSTotalCount] to an arbitrary JSON value. + * + * You should usually call [Builder.threeDSTotalCount] with a well-typed [Long] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun threeDSTotalCount(threeDSTotalCount: JsonField) = apply { + this.threeDSTotalCount = threeDSTotalCount + } + + /** The number of days since the last approved transaction on the entity. */ + fun timeSinceLastTransactionDays(timeSinceLastTransactionDays: Double?) = + timeSinceLastTransactionDays(JsonField.ofNullable(timeSinceLastTransactionDays)) + + /** + * Alias for [Builder.timeSinceLastTransactionDays]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun timeSinceLastTransactionDays(timeSinceLastTransactionDays: Double) = + timeSinceLastTransactionDays(timeSinceLastTransactionDays as Double?) + + /** + * Alias for calling [Builder.timeSinceLastTransactionDays] with + * `timeSinceLastTransactionDays.orElse(null)`. + */ + fun timeSinceLastTransactionDays(timeSinceLastTransactionDays: Optional) = + timeSinceLastTransactionDays(timeSinceLastTransactionDays.getOrNull()) + + /** + * Sets [Builder.timeSinceLastTransactionDays] to an arbitrary JSON value. + * + * You should usually call [Builder.timeSinceLastTransactionDays] with a well-typed [Double] + * value instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun timeSinceLastTransactionDays(timeSinceLastTransactionDays: JsonField) = apply { + this.timeSinceLastTransactionDays = timeSinceLastTransactionDays + } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [SignalsResponse]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .approvedTxnAmountM2() + * .approvedTxnAmountM2_30d() + * .approvedTxnAmountM2_7d() + * .approvedTxnAmountM2_90d() + * .approvedTxnCount() + * .approvedTxnCount30d() + * .approvedTxnCount7d() + * .approvedTxnCount90d() + * .avgTransactionAmount() + * .avgTransactionAmount30d() + * .avgTransactionAmount7d() + * .avgTransactionAmount90d() + * .distinctCountryCount() + * .distinctMccCount() + * .firstTxnAt() + * .isFirstTransaction() + * .lastCpCountry() + * .lastCpPostalCode() + * .lastCpTimestamp() + * .lastTxnApprovedAt() + * .seenCountries() + * .seenMccs() + * .seenMerchants() + * .stdevTransactionAmount() + * .stdevTransactionAmount30d() + * .stdevTransactionAmount7d() + * .stdevTransactionAmount90d() + * .threeDSSuccessCount() + * .threeDSSuccessRate() + * .threeDSTotalCount() + * .timeSinceLastTransactionDays() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): SignalsResponse = + SignalsResponse( + checkRequired("approvedTxnAmountM2", approvedTxnAmountM2), + checkRequired("approvedTxnAmountM2_30d", approvedTxnAmountM2_30d), + checkRequired("approvedTxnAmountM2_7d", approvedTxnAmountM2_7d), + checkRequired("approvedTxnAmountM2_90d", approvedTxnAmountM2_90d), + checkRequired("approvedTxnCount", approvedTxnCount), + checkRequired("approvedTxnCount30d", approvedTxnCount30d), + checkRequired("approvedTxnCount7d", approvedTxnCount7d), + checkRequired("approvedTxnCount90d", approvedTxnCount90d), + checkRequired("avgTransactionAmount", avgTransactionAmount), + checkRequired("avgTransactionAmount30d", avgTransactionAmount30d), + checkRequired("avgTransactionAmount7d", avgTransactionAmount7d), + checkRequired("avgTransactionAmount90d", avgTransactionAmount90d), + checkRequired("distinctCountryCount", distinctCountryCount), + checkRequired("distinctMccCount", distinctMccCount), + checkRequired("firstTxnAt", firstTxnAt), + checkRequired("isFirstTransaction", isFirstTransaction), + checkRequired("lastCpCountry", lastCpCountry), + checkRequired("lastCpPostalCode", lastCpPostalCode), + checkRequired("lastCpTimestamp", lastCpTimestamp), + checkRequired("lastTxnApprovedAt", lastTxnApprovedAt), + checkRequired("seenCountries", seenCountries).map { it.toImmutable() }, + checkRequired("seenMccs", seenMccs).map { it.toImmutable() }, + checkRequired("seenMerchants", seenMerchants).map { it.toImmutable() }, + checkRequired("stdevTransactionAmount", stdevTransactionAmount), + checkRequired("stdevTransactionAmount30d", stdevTransactionAmount30d), + checkRequired("stdevTransactionAmount7d", stdevTransactionAmount7d), + checkRequired("stdevTransactionAmount90d", stdevTransactionAmount90d), + checkRequired("threeDSSuccessCount", threeDSSuccessCount), + checkRequired("threeDSSuccessRate", threeDSSuccessRate), + checkRequired("threeDSTotalCount", threeDSTotalCount), + checkRequired("timeSinceLastTransactionDays", timeSinceLastTransactionDays), + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws LithicInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): SignalsResponse = apply { + if (validated) { + return@apply + } + + approvedTxnAmountM2() + approvedTxnAmountM2_30d() + approvedTxnAmountM2_7d() + approvedTxnAmountM2_90d() + approvedTxnCount() + approvedTxnCount30d() + approvedTxnCount7d() + approvedTxnCount90d() + avgTransactionAmount() + avgTransactionAmount30d() + avgTransactionAmount7d() + avgTransactionAmount90d() + distinctCountryCount() + distinctMccCount() + firstTxnAt() + isFirstTransaction() + lastCpCountry() + lastCpPostalCode() + lastCpTimestamp() + lastTxnApprovedAt() + seenCountries() + seenMccs() + seenMerchants() + stdevTransactionAmount() + stdevTransactionAmount30d() + stdevTransactionAmount7d() + stdevTransactionAmount90d() + threeDSSuccessCount() + threeDSSuccessRate() + threeDSTotalCount() + timeSinceLastTransactionDays() + 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 (approvedTxnAmountM2.asKnown().isPresent) 1 else 0) + + (if (approvedTxnAmountM2_30d.asKnown().isPresent) 1 else 0) + + (if (approvedTxnAmountM2_7d.asKnown().isPresent) 1 else 0) + + (if (approvedTxnAmountM2_90d.asKnown().isPresent) 1 else 0) + + (if (approvedTxnCount.asKnown().isPresent) 1 else 0) + + (if (approvedTxnCount30d.asKnown().isPresent) 1 else 0) + + (if (approvedTxnCount7d.asKnown().isPresent) 1 else 0) + + (if (approvedTxnCount90d.asKnown().isPresent) 1 else 0) + + (if (avgTransactionAmount.asKnown().isPresent) 1 else 0) + + (if (avgTransactionAmount30d.asKnown().isPresent) 1 else 0) + + (if (avgTransactionAmount7d.asKnown().isPresent) 1 else 0) + + (if (avgTransactionAmount90d.asKnown().isPresent) 1 else 0) + + (if (distinctCountryCount.asKnown().isPresent) 1 else 0) + + (if (distinctMccCount.asKnown().isPresent) 1 else 0) + + (if (firstTxnAt.asKnown().isPresent) 1 else 0) + + (if (isFirstTransaction.asKnown().isPresent) 1 else 0) + + (if (lastCpCountry.asKnown().isPresent) 1 else 0) + + (if (lastCpPostalCode.asKnown().isPresent) 1 else 0) + + (if (lastCpTimestamp.asKnown().isPresent) 1 else 0) + + (if (lastTxnApprovedAt.asKnown().isPresent) 1 else 0) + + (seenCountries.asKnown().getOrNull()?.size ?: 0) + + (seenMccs.asKnown().getOrNull()?.size ?: 0) + + (seenMerchants.asKnown().getOrNull()?.size ?: 0) + + (if (stdevTransactionAmount.asKnown().isPresent) 1 else 0) + + (if (stdevTransactionAmount30d.asKnown().isPresent) 1 else 0) + + (if (stdevTransactionAmount7d.asKnown().isPresent) 1 else 0) + + (if (stdevTransactionAmount90d.asKnown().isPresent) 1 else 0) + + (if (threeDSSuccessCount.asKnown().isPresent) 1 else 0) + + (if (threeDSSuccessRate.asKnown().isPresent) 1 else 0) + + (if (threeDSTotalCount.asKnown().isPresent) 1 else 0) + + (if (timeSinceLastTransactionDays.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is SignalsResponse && + approvedTxnAmountM2 == other.approvedTxnAmountM2 && + approvedTxnAmountM2_30d == other.approvedTxnAmountM2_30d && + approvedTxnAmountM2_7d == other.approvedTxnAmountM2_7d && + approvedTxnAmountM2_90d == other.approvedTxnAmountM2_90d && + approvedTxnCount == other.approvedTxnCount && + approvedTxnCount30d == other.approvedTxnCount30d && + approvedTxnCount7d == other.approvedTxnCount7d && + approvedTxnCount90d == other.approvedTxnCount90d && + avgTransactionAmount == other.avgTransactionAmount && + avgTransactionAmount30d == other.avgTransactionAmount30d && + avgTransactionAmount7d == other.avgTransactionAmount7d && + avgTransactionAmount90d == other.avgTransactionAmount90d && + distinctCountryCount == other.distinctCountryCount && + distinctMccCount == other.distinctMccCount && + firstTxnAt == other.firstTxnAt && + isFirstTransaction == other.isFirstTransaction && + lastCpCountry == other.lastCpCountry && + lastCpPostalCode == other.lastCpPostalCode && + lastCpTimestamp == other.lastCpTimestamp && + lastTxnApprovedAt == other.lastTxnApprovedAt && + seenCountries == other.seenCountries && + seenMccs == other.seenMccs && + seenMerchants == other.seenMerchants && + stdevTransactionAmount == other.stdevTransactionAmount && + stdevTransactionAmount30d == other.stdevTransactionAmount30d && + stdevTransactionAmount7d == other.stdevTransactionAmount7d && + stdevTransactionAmount90d == other.stdevTransactionAmount90d && + threeDSSuccessCount == other.threeDSSuccessCount && + threeDSSuccessRate == other.threeDSSuccessRate && + threeDSTotalCount == other.threeDSTotalCount && + timeSinceLastTransactionDays == other.timeSinceLastTransactionDays && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash( + approvedTxnAmountM2, + approvedTxnAmountM2_30d, + approvedTxnAmountM2_7d, + approvedTxnAmountM2_90d, + approvedTxnCount, + approvedTxnCount30d, + approvedTxnCount7d, + approvedTxnCount90d, + avgTransactionAmount, + avgTransactionAmount30d, + avgTransactionAmount7d, + avgTransactionAmount90d, + distinctCountryCount, + distinctMccCount, + firstTxnAt, + isFirstTransaction, + lastCpCountry, + lastCpPostalCode, + lastCpTimestamp, + lastTxnApprovedAt, + seenCountries, + seenMccs, + seenMerchants, + stdevTransactionAmount, + stdevTransactionAmount30d, + stdevTransactionAmount7d, + stdevTransactionAmount90d, + threeDSSuccessCount, + threeDSSuccessRate, + threeDSTotalCount, + timeSinceLastTransactionDays, + additionalProperties, + ) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "SignalsResponse{approvedTxnAmountM2=$approvedTxnAmountM2, approvedTxnAmountM2_30d=$approvedTxnAmountM2_30d, approvedTxnAmountM2_7d=$approvedTxnAmountM2_7d, approvedTxnAmountM2_90d=$approvedTxnAmountM2_90d, approvedTxnCount=$approvedTxnCount, approvedTxnCount30d=$approvedTxnCount30d, approvedTxnCount7d=$approvedTxnCount7d, approvedTxnCount90d=$approvedTxnCount90d, avgTransactionAmount=$avgTransactionAmount, avgTransactionAmount30d=$avgTransactionAmount30d, avgTransactionAmount7d=$avgTransactionAmount7d, avgTransactionAmount90d=$avgTransactionAmount90d, distinctCountryCount=$distinctCountryCount, distinctMccCount=$distinctMccCount, firstTxnAt=$firstTxnAt, isFirstTransaction=$isFirstTransaction, lastCpCountry=$lastCpCountry, lastCpPostalCode=$lastCpPostalCode, lastCpTimestamp=$lastCpTimestamp, lastTxnApprovedAt=$lastTxnApprovedAt, seenCountries=$seenCountries, seenMccs=$seenMccs, seenMerchants=$seenMerchants, stdevTransactionAmount=$stdevTransactionAmount, stdevTransactionAmount30d=$stdevTransactionAmount30d, stdevTransactionAmount7d=$stdevTransactionAmount7d, stdevTransactionAmount90d=$stdevTransactionAmount90d, threeDSSuccessCount=$threeDSSuccessCount, threeDSSuccessRate=$threeDSSuccessRate, threeDSTotalCount=$threeDSTotalCount, timeSinceLastTransactionDays=$timeSinceLastTransactionDays, additionalProperties=$additionalProperties}" +} diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/AccountServiceAsync.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/AccountServiceAsync.kt index 46db932c..22657b18 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/AccountServiceAsync.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/AccountServiceAsync.kt @@ -9,9 +9,11 @@ import com.lithic.api.models.Account import com.lithic.api.models.AccountListPageAsync import com.lithic.api.models.AccountListParams import com.lithic.api.models.AccountRetrieveParams +import com.lithic.api.models.AccountRetrieveSignalsParams import com.lithic.api.models.AccountRetrieveSpendLimitsParams import com.lithic.api.models.AccountSpendLimits import com.lithic.api.models.AccountUpdateParams +import com.lithic.api.models.SignalsResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer @@ -115,6 +117,51 @@ interface AccountServiceAsync { fun list(requestOptions: RequestOptions): CompletableFuture = list(AccountListParams.none(), requestOptions) + /** + * Returns behavioral feature state derived from an account's transaction history. + * + * These signals expose the same data used by behavioral rule attributes (e.g. `AMOUNT_Z_SCORE` + * with `scope: ACCOUNT`, `IS_NEW_COUNTRY` with `scope: ACCOUNT`) and custom code + * `TRANSACTION_HISTORY_SIGNALS` features, allowing clients to inspect feature values before + * writing rules and debug rule behavior. + * + * Note: 3DS fields are not available at the account scope and will be null. + */ + fun retrieveSignals(accountToken: String): CompletableFuture = + retrieveSignals(accountToken, AccountRetrieveSignalsParams.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + accountToken: String, + params: AccountRetrieveSignalsParams = AccountRetrieveSignalsParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture = + retrieveSignals(params.toBuilder().accountToken(accountToken).build(), requestOptions) + + /** @see retrieveSignals */ + fun retrieveSignals( + accountToken: String, + params: AccountRetrieveSignalsParams = AccountRetrieveSignalsParams.none(), + ): CompletableFuture = + retrieveSignals(accountToken, params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + params: AccountRetrieveSignalsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture + + /** @see retrieveSignals */ + fun retrieveSignals(params: AccountRetrieveSignalsParams): CompletableFuture = + retrieveSignals(params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + accountToken: String, + requestOptions: RequestOptions, + ): CompletableFuture = + retrieveSignals(accountToken, AccountRetrieveSignalsParams.none(), requestOptions) + /** * Get an Account's available spend limits, which is based on the spend limit configured on the * Account and the amount already spent over the spend limit's duration. For example, if the @@ -274,6 +321,49 @@ interface AccountServiceAsync { ): CompletableFuture> = list(AccountListParams.none(), requestOptions) + /** + * Returns a raw HTTP response for `get /v1/accounts/{account_token}/signals`, but is + * otherwise the same as [AccountServiceAsync.retrieveSignals]. + */ + fun retrieveSignals( + accountToken: String + ): CompletableFuture> = + retrieveSignals(accountToken, AccountRetrieveSignalsParams.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + accountToken: String, + params: AccountRetrieveSignalsParams = AccountRetrieveSignalsParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> = + retrieveSignals(params.toBuilder().accountToken(accountToken).build(), requestOptions) + + /** @see retrieveSignals */ + fun retrieveSignals( + accountToken: String, + params: AccountRetrieveSignalsParams = AccountRetrieveSignalsParams.none(), + ): CompletableFuture> = + retrieveSignals(accountToken, params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + params: AccountRetrieveSignalsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> + + /** @see retrieveSignals */ + fun retrieveSignals( + params: AccountRetrieveSignalsParams + ): CompletableFuture> = + retrieveSignals(params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + accountToken: String, + requestOptions: RequestOptions, + ): CompletableFuture> = + retrieveSignals(accountToken, AccountRetrieveSignalsParams.none(), requestOptions) + /** * Returns a raw HTTP response for `get /v1/accounts/{account_token}/spend_limits`, but is * otherwise the same as [AccountServiceAsync.retrieveSpendLimits]. diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/AccountServiceAsyncImpl.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/AccountServiceAsyncImpl.kt index fd5397a0..5d2a134d 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/AccountServiceAsyncImpl.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/AccountServiceAsyncImpl.kt @@ -21,9 +21,11 @@ import com.lithic.api.models.AccountListPageAsync import com.lithic.api.models.AccountListPageResponse import com.lithic.api.models.AccountListParams import com.lithic.api.models.AccountRetrieveParams +import com.lithic.api.models.AccountRetrieveSignalsParams import com.lithic.api.models.AccountRetrieveSpendLimitsParams import com.lithic.api.models.AccountSpendLimits import com.lithic.api.models.AccountUpdateParams +import com.lithic.api.models.SignalsResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer import kotlin.jvm.optionals.getOrNull @@ -61,6 +63,13 @@ class AccountServiceAsyncImpl internal constructor(private val clientOptions: Cl // get /v1/accounts withRawResponse().list(params, requestOptions).thenApply { it.parse() } + override fun retrieveSignals( + params: AccountRetrieveSignalsParams, + requestOptions: RequestOptions, + ): CompletableFuture = + // get /v1/accounts/{account_token}/signals + withRawResponse().retrieveSignals(params, requestOptions).thenApply { it.parse() } + override fun retrieveSpendLimits( params: AccountRetrieveSpendLimitsParams, requestOptions: RequestOptions, @@ -185,6 +194,39 @@ class AccountServiceAsyncImpl internal constructor(private val clientOptions: Cl } } + private val retrieveSignalsHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun retrieveSignals( + params: AccountRetrieveSignalsParams, + requestOptions: RequestOptions, + ): CompletableFuture> { + // We check here instead of in the params builder because this can be specified + // positionally or in the params class. + checkRequired("accountToken", params.accountToken().getOrNull()) + val request = + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("v1", "accounts", params._pathParam(0), "signals") + .build() + .prepareAsync(clientOptions, params) + val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) + return request + .thenComposeAsync { clientOptions.httpClient.executeAsync(it, requestOptions) } + .thenApply { response -> + errorHandler.handle(response).parseable { + response + .use { retrieveSignalsHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + } + private val retrieveSpendLimitsHandler: Handler = jsonHandler(clientOptions.jsonMapper) diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/CardServiceAsync.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/CardServiceAsync.kt index a22126fc..d203fac7 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/CardServiceAsync.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/CardServiceAsync.kt @@ -18,12 +18,14 @@ import com.lithic.api.models.CardProvisionResponse import com.lithic.api.models.CardReissueParams import com.lithic.api.models.CardRenewParams import com.lithic.api.models.CardRetrieveParams +import com.lithic.api.models.CardRetrieveSignalsParams import com.lithic.api.models.CardRetrieveSpendLimitsParams import com.lithic.api.models.CardSearchByPanParams import com.lithic.api.models.CardSpendLimits import com.lithic.api.models.CardUpdateParams import com.lithic.api.models.CardWebProvisionParams import com.lithic.api.models.CardWebProvisionResponse +import com.lithic.api.models.SignalsResponse import com.lithic.api.services.async.cards.BalanceServiceAsync import com.lithic.api.services.async.cards.FinancialTransactionServiceAsync import java.util.concurrent.CompletableFuture @@ -321,6 +323,49 @@ interface CardServiceAsync { requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture + /** + * Returns behavioral feature state derived from a card's transaction history. + * + * These signals expose the same data used by behavioral rule attributes (e.g. `AMOUNT_Z_SCORE` + * with `scope: CARD`, `IS_NEW_COUNTRY` with `scope: CARD`) and custom code + * `TRANSACTION_HISTORY_SIGNALS` features, allowing clients to inspect feature values before + * writing rules and debug rule behavior. + */ + fun retrieveSignals(cardToken: String): CompletableFuture = + retrieveSignals(cardToken, CardRetrieveSignalsParams.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + cardToken: String, + params: CardRetrieveSignalsParams = CardRetrieveSignalsParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture = + retrieveSignals(params.toBuilder().cardToken(cardToken).build(), requestOptions) + + /** @see retrieveSignals */ + fun retrieveSignals( + cardToken: String, + params: CardRetrieveSignalsParams = CardRetrieveSignalsParams.none(), + ): CompletableFuture = + retrieveSignals(cardToken, params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + params: CardRetrieveSignalsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture + + /** @see retrieveSignals */ + fun retrieveSignals(params: CardRetrieveSignalsParams): CompletableFuture = + retrieveSignals(params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + cardToken: String, + requestOptions: RequestOptions, + ): CompletableFuture = + retrieveSignals(cardToken, CardRetrieveSignalsParams.none(), requestOptions) + /** * Get a Card's available spend limit, which is based on the spend limit configured on the Card * and the amount already spent over the spend limit's duration. For example, if the Card has a @@ -713,6 +758,49 @@ interface CardServiceAsync { requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture> + /** + * Returns a raw HTTP response for `get /v1/cards/{card_token}/signals`, but is otherwise + * the same as [CardServiceAsync.retrieveSignals]. + */ + fun retrieveSignals( + cardToken: String + ): CompletableFuture> = + retrieveSignals(cardToken, CardRetrieveSignalsParams.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + cardToken: String, + params: CardRetrieveSignalsParams = CardRetrieveSignalsParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> = + retrieveSignals(params.toBuilder().cardToken(cardToken).build(), requestOptions) + + /** @see retrieveSignals */ + fun retrieveSignals( + cardToken: String, + params: CardRetrieveSignalsParams = CardRetrieveSignalsParams.none(), + ): CompletableFuture> = + retrieveSignals(cardToken, params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + params: CardRetrieveSignalsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> + + /** @see retrieveSignals */ + fun retrieveSignals( + params: CardRetrieveSignalsParams + ): CompletableFuture> = + retrieveSignals(params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + cardToken: String, + requestOptions: RequestOptions, + ): CompletableFuture> = + retrieveSignals(cardToken, CardRetrieveSignalsParams.none(), requestOptions) + /** * Returns a raw HTTP response for `get /v1/cards/{card_token}/spend_limits`, but is * otherwise the same as [CardServiceAsync.retrieveSpendLimits]. diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/CardServiceAsyncImpl.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/CardServiceAsyncImpl.kt index dd8e6417..5aedc961 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/CardServiceAsyncImpl.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/CardServiceAsyncImpl.kt @@ -31,12 +31,14 @@ import com.lithic.api.models.CardProvisionResponse import com.lithic.api.models.CardReissueParams import com.lithic.api.models.CardRenewParams import com.lithic.api.models.CardRetrieveParams +import com.lithic.api.models.CardRetrieveSignalsParams import com.lithic.api.models.CardRetrieveSpendLimitsParams import com.lithic.api.models.CardSearchByPanParams import com.lithic.api.models.CardSpendLimits import com.lithic.api.models.CardUpdateParams import com.lithic.api.models.CardWebProvisionParams import com.lithic.api.models.CardWebProvisionResponse +import com.lithic.api.models.SignalsResponse import com.lithic.api.services.async.cards.BalanceServiceAsync import com.lithic.api.services.async.cards.BalanceServiceAsyncImpl import com.lithic.api.services.async.cards.FinancialTransactionServiceAsync @@ -134,6 +136,13 @@ class CardServiceAsyncImpl internal constructor(private val clientOptions: Clien // post /v1/cards/{card_token}/renew withRawResponse().renew(params, requestOptions).thenApply { it.parse() } + override fun retrieveSignals( + params: CardRetrieveSignalsParams, + requestOptions: RequestOptions, + ): CompletableFuture = + // get /v1/cards/{card_token}/signals + withRawResponse().retrieveSignals(params, requestOptions).thenApply { it.parse() } + override fun retrieveSpendLimits( params: CardRetrieveSpendLimitsParams, requestOptions: RequestOptions, @@ -473,6 +482,39 @@ class CardServiceAsyncImpl internal constructor(private val clientOptions: Clien } } + private val retrieveSignalsHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun retrieveSignals( + params: CardRetrieveSignalsParams, + requestOptions: RequestOptions, + ): CompletableFuture> { + // We check here instead of in the params builder because this can be specified + // positionally or in the params class. + checkRequired("cardToken", params.cardToken().getOrNull()) + val request = + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("v1", "cards", params._pathParam(0), "signals") + .build() + .prepareAsync(clientOptions, params) + val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) + return request + .thenComposeAsync { clientOptions.httpClient.executeAsync(it, requestOptions) } + .thenApply { response -> + errorHandler.handle(response).parseable { + response + .use { retrieveSignalsHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + } + private val retrieveSpendLimitsHandler: Handler = jsonHandler(clientOptions.jsonMapper) diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/AccountService.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/AccountService.kt index 90f07aaa..65c70098 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/AccountService.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/AccountService.kt @@ -10,9 +10,11 @@ import com.lithic.api.models.Account import com.lithic.api.models.AccountListPage import com.lithic.api.models.AccountListParams import com.lithic.api.models.AccountRetrieveParams +import com.lithic.api.models.AccountRetrieveSignalsParams import com.lithic.api.models.AccountRetrieveSpendLimitsParams import com.lithic.api.models.AccountSpendLimits import com.lithic.api.models.AccountUpdateParams +import com.lithic.api.models.SignalsResponse import java.util.function.Consumer interface AccountService { @@ -109,6 +111,47 @@ interface AccountService { fun list(requestOptions: RequestOptions): AccountListPage = list(AccountListParams.none(), requestOptions) + /** + * Returns behavioral feature state derived from an account's transaction history. + * + * These signals expose the same data used by behavioral rule attributes (e.g. `AMOUNT_Z_SCORE` + * with `scope: ACCOUNT`, `IS_NEW_COUNTRY` with `scope: ACCOUNT`) and custom code + * `TRANSACTION_HISTORY_SIGNALS` features, allowing clients to inspect feature values before + * writing rules and debug rule behavior. + * + * Note: 3DS fields are not available at the account scope and will be null. + */ + fun retrieveSignals(accountToken: String): SignalsResponse = + retrieveSignals(accountToken, AccountRetrieveSignalsParams.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + accountToken: String, + params: AccountRetrieveSignalsParams = AccountRetrieveSignalsParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): SignalsResponse = + retrieveSignals(params.toBuilder().accountToken(accountToken).build(), requestOptions) + + /** @see retrieveSignals */ + fun retrieveSignals( + accountToken: String, + params: AccountRetrieveSignalsParams = AccountRetrieveSignalsParams.none(), + ): SignalsResponse = retrieveSignals(accountToken, params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + params: AccountRetrieveSignalsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): SignalsResponse + + /** @see retrieveSignals */ + fun retrieveSignals(params: AccountRetrieveSignalsParams): SignalsResponse = + retrieveSignals(params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals(accountToken: String, requestOptions: RequestOptions): SignalsResponse = + retrieveSignals(accountToken, AccountRetrieveSignalsParams.none(), requestOptions) + /** * Get an Account's available spend limits, which is based on the spend limit configured on the * Account and the amount already spent over the spend limit's duration. For example, if the @@ -268,6 +311,52 @@ interface AccountService { fun list(requestOptions: RequestOptions): HttpResponseFor = list(AccountListParams.none(), requestOptions) + /** + * Returns a raw HTTP response for `get /v1/accounts/{account_token}/signals`, but is + * otherwise the same as [AccountService.retrieveSignals]. + */ + @MustBeClosed + fun retrieveSignals(accountToken: String): HttpResponseFor = + retrieveSignals(accountToken, AccountRetrieveSignalsParams.none()) + + /** @see retrieveSignals */ + @MustBeClosed + fun retrieveSignals( + accountToken: String, + params: AccountRetrieveSignalsParams = AccountRetrieveSignalsParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor = + retrieveSignals(params.toBuilder().accountToken(accountToken).build(), requestOptions) + + /** @see retrieveSignals */ + @MustBeClosed + fun retrieveSignals( + accountToken: String, + params: AccountRetrieveSignalsParams = AccountRetrieveSignalsParams.none(), + ): HttpResponseFor = + retrieveSignals(accountToken, params, RequestOptions.none()) + + /** @see retrieveSignals */ + @MustBeClosed + fun retrieveSignals( + params: AccountRetrieveSignalsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor + + /** @see retrieveSignals */ + @MustBeClosed + fun retrieveSignals( + params: AccountRetrieveSignalsParams + ): HttpResponseFor = retrieveSignals(params, RequestOptions.none()) + + /** @see retrieveSignals */ + @MustBeClosed + fun retrieveSignals( + accountToken: String, + requestOptions: RequestOptions, + ): HttpResponseFor = + retrieveSignals(accountToken, AccountRetrieveSignalsParams.none(), requestOptions) + /** * Returns a raw HTTP response for `get /v1/accounts/{account_token}/spend_limits`, but is * otherwise the same as [AccountService.retrieveSpendLimits]. diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/AccountServiceImpl.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/AccountServiceImpl.kt index 17b17230..b79a8d68 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/AccountServiceImpl.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/AccountServiceImpl.kt @@ -21,9 +21,11 @@ import com.lithic.api.models.AccountListPage import com.lithic.api.models.AccountListPageResponse import com.lithic.api.models.AccountListParams import com.lithic.api.models.AccountRetrieveParams +import com.lithic.api.models.AccountRetrieveSignalsParams import com.lithic.api.models.AccountRetrieveSpendLimitsParams import com.lithic.api.models.AccountSpendLimits import com.lithic.api.models.AccountUpdateParams +import com.lithic.api.models.SignalsResponse import java.util.function.Consumer import kotlin.jvm.optionals.getOrNull @@ -51,6 +53,13 @@ class AccountServiceImpl internal constructor(private val clientOptions: ClientO // get /v1/accounts withRawResponse().list(params, requestOptions).parse() + override fun retrieveSignals( + params: AccountRetrieveSignalsParams, + requestOptions: RequestOptions, + ): SignalsResponse = + // get /v1/accounts/{account_token}/signals + withRawResponse().retrieveSignals(params, requestOptions).parse() + override fun retrieveSpendLimits( params: AccountRetrieveSpendLimitsParams, requestOptions: RequestOptions, @@ -165,6 +174,36 @@ class AccountServiceImpl internal constructor(private val clientOptions: ClientO } } + private val retrieveSignalsHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun retrieveSignals( + params: AccountRetrieveSignalsParams, + requestOptions: RequestOptions, + ): HttpResponseFor { + // We check here instead of in the params builder because this can be specified + // positionally or in the params class. + checkRequired("accountToken", params.accountToken().getOrNull()) + val request = + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("v1", "accounts", params._pathParam(0), "signals") + .build() + .prepare(clientOptions, params) + val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) + val response = clientOptions.httpClient.execute(request, requestOptions) + return errorHandler.handle(response).parseable { + response + .use { retrieveSignalsHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + private val retrieveSpendLimitsHandler: Handler = jsonHandler(clientOptions.jsonMapper) diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/CardService.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/CardService.kt index 05b865b2..9011c4e1 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/CardService.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/CardService.kt @@ -19,12 +19,14 @@ import com.lithic.api.models.CardProvisionResponse import com.lithic.api.models.CardReissueParams import com.lithic.api.models.CardRenewParams import com.lithic.api.models.CardRetrieveParams +import com.lithic.api.models.CardRetrieveSignalsParams import com.lithic.api.models.CardRetrieveSpendLimitsParams import com.lithic.api.models.CardSearchByPanParams import com.lithic.api.models.CardSpendLimits import com.lithic.api.models.CardUpdateParams import com.lithic.api.models.CardWebProvisionParams import com.lithic.api.models.CardWebProvisionResponse +import com.lithic.api.models.SignalsResponse import com.lithic.api.services.blocking.cards.BalanceService import com.lithic.api.services.blocking.cards.FinancialTransactionService import java.util.function.Consumer @@ -292,6 +294,45 @@ interface CardService { /** @see renew */ fun renew(params: CardRenewParams, requestOptions: RequestOptions = RequestOptions.none()): Card + /** + * Returns behavioral feature state derived from a card's transaction history. + * + * These signals expose the same data used by behavioral rule attributes (e.g. `AMOUNT_Z_SCORE` + * with `scope: CARD`, `IS_NEW_COUNTRY` with `scope: CARD`) and custom code + * `TRANSACTION_HISTORY_SIGNALS` features, allowing clients to inspect feature values before + * writing rules and debug rule behavior. + */ + fun retrieveSignals(cardToken: String): SignalsResponse = + retrieveSignals(cardToken, CardRetrieveSignalsParams.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + cardToken: String, + params: CardRetrieveSignalsParams = CardRetrieveSignalsParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): SignalsResponse = + retrieveSignals(params.toBuilder().cardToken(cardToken).build(), requestOptions) + + /** @see retrieveSignals */ + fun retrieveSignals( + cardToken: String, + params: CardRetrieveSignalsParams = CardRetrieveSignalsParams.none(), + ): SignalsResponse = retrieveSignals(cardToken, params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals( + params: CardRetrieveSignalsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): SignalsResponse + + /** @see retrieveSignals */ + fun retrieveSignals(params: CardRetrieveSignalsParams): SignalsResponse = + retrieveSignals(params, RequestOptions.none()) + + /** @see retrieveSignals */ + fun retrieveSignals(cardToken: String, requestOptions: RequestOptions): SignalsResponse = + retrieveSignals(cardToken, CardRetrieveSignalsParams.none(), requestOptions) + /** * Get a Card's available spend limit, which is based on the spend limit configured on the Card * and the amount already spent over the spend limit's duration. For example, if the Card has a @@ -688,6 +729,51 @@ interface CardService { requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor + /** + * Returns a raw HTTP response for `get /v1/cards/{card_token}/signals`, but is otherwise + * the same as [CardService.retrieveSignals]. + */ + @MustBeClosed + fun retrieveSignals(cardToken: String): HttpResponseFor = + retrieveSignals(cardToken, CardRetrieveSignalsParams.none()) + + /** @see retrieveSignals */ + @MustBeClosed + fun retrieveSignals( + cardToken: String, + params: CardRetrieveSignalsParams = CardRetrieveSignalsParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor = + retrieveSignals(params.toBuilder().cardToken(cardToken).build(), requestOptions) + + /** @see retrieveSignals */ + @MustBeClosed + fun retrieveSignals( + cardToken: String, + params: CardRetrieveSignalsParams = CardRetrieveSignalsParams.none(), + ): HttpResponseFor = + retrieveSignals(cardToken, params, RequestOptions.none()) + + /** @see retrieveSignals */ + @MustBeClosed + fun retrieveSignals( + params: CardRetrieveSignalsParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): HttpResponseFor + + /** @see retrieveSignals */ + @MustBeClosed + fun retrieveSignals(params: CardRetrieveSignalsParams): HttpResponseFor = + retrieveSignals(params, RequestOptions.none()) + + /** @see retrieveSignals */ + @MustBeClosed + fun retrieveSignals( + cardToken: String, + requestOptions: RequestOptions, + ): HttpResponseFor = + retrieveSignals(cardToken, CardRetrieveSignalsParams.none(), requestOptions) + /** * Returns a raw HTTP response for `get /v1/cards/{card_token}/spend_limits`, but is * otherwise the same as [CardService.retrieveSpendLimits]. diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/CardServiceImpl.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/CardServiceImpl.kt index f290bdeb..76115967 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/CardServiceImpl.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/CardServiceImpl.kt @@ -31,12 +31,14 @@ import com.lithic.api.models.CardProvisionResponse import com.lithic.api.models.CardReissueParams import com.lithic.api.models.CardRenewParams import com.lithic.api.models.CardRetrieveParams +import com.lithic.api.models.CardRetrieveSignalsParams import com.lithic.api.models.CardRetrieveSpendLimitsParams import com.lithic.api.models.CardSearchByPanParams import com.lithic.api.models.CardSpendLimits import com.lithic.api.models.CardUpdateParams import com.lithic.api.models.CardWebProvisionParams import com.lithic.api.models.CardWebProvisionResponse +import com.lithic.api.models.SignalsResponse import com.lithic.api.services.blocking.cards.BalanceService import com.lithic.api.services.blocking.cards.BalanceServiceImpl import com.lithic.api.services.blocking.cards.FinancialTransactionService @@ -111,6 +113,13 @@ class CardServiceImpl internal constructor(private val clientOptions: ClientOpti // post /v1/cards/{card_token}/renew withRawResponse().renew(params, requestOptions).parse() + override fun retrieveSignals( + params: CardRetrieveSignalsParams, + requestOptions: RequestOptions, + ): SignalsResponse = + // get /v1/cards/{card_token}/signals + withRawResponse().retrieveSignals(params, requestOptions).parse() + override fun retrieveSpendLimits( params: CardRetrieveSpendLimitsParams, requestOptions: RequestOptions, @@ -418,6 +427,36 @@ class CardServiceImpl internal constructor(private val clientOptions: ClientOpti } } + private val retrieveSignalsHandler: Handler = + jsonHandler(clientOptions.jsonMapper) + + override fun retrieveSignals( + params: CardRetrieveSignalsParams, + requestOptions: RequestOptions, + ): HttpResponseFor { + // We check here instead of in the params builder because this can be specified + // positionally or in the params class. + checkRequired("cardToken", params.cardToken().getOrNull()) + val request = + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl(clientOptions.baseUrl()) + .addPathSegments("v1", "cards", params._pathParam(0), "signals") + .build() + .prepare(clientOptions, params) + val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) + val response = clientOptions.httpClient.execute(request, requestOptions) + return errorHandler.handle(response).parseable { + response + .use { retrieveSignalsHandler.handle(it) } + .also { + if (requestOptions.responseValidation!!) { + it.validate() + } + } + } + } + private val retrieveSpendLimitsHandler: Handler = jsonHandler(clientOptions.jsonMapper) diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/core/http/LoggingHttpClientTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/core/http/LoggingHttpClientTest.kt index bb24d42f..7439d811 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/core/http/LoggingHttpClientTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/core/http/LoggingHttpClientTest.kt @@ -870,7 +870,8 @@ internal class LoggingHttpClientTest { httpClient: HttpClient, level: LogLevel, clock: Clock = clockFrom(Instant.parse("1998-04-21T00:00:00Z")), - redactedHeaders: Set = setOf("Authorization"), + redactedHeaders: Set = + setOf("authorization", "api-key", "x-api-key", "cookie", "set-cookie"), ): LoggingHttpClient = LoggingHttpClient.builder() .httpClient(httpClient) diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/models/AccountRetrieveSignalsParamsTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/models/AccountRetrieveSignalsParamsTest.kt new file mode 100644 index 00000000..0f68c34d --- /dev/null +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/models/AccountRetrieveSignalsParamsTest.kt @@ -0,0 +1,28 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.lithic.api.models + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class AccountRetrieveSignalsParamsTest { + + @Test + fun create() { + AccountRetrieveSignalsParams.builder() + .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .build() + } + + @Test + fun pathParams() { + val params = + AccountRetrieveSignalsParams.builder() + .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .build() + + assertThat(params._pathParam(0)).isEqualTo("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + // out-of-bound path param + assertThat(params._pathParam(1)).isEqualTo("") + } +} diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/models/CardRetrieveSignalsParamsTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/models/CardRetrieveSignalsParamsTest.kt new file mode 100644 index 00000000..d6c4ff21 --- /dev/null +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/models/CardRetrieveSignalsParamsTest.kt @@ -0,0 +1,28 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.lithic.api.models + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class CardRetrieveSignalsParamsTest { + + @Test + fun create() { + CardRetrieveSignalsParams.builder() + .cardToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .build() + } + + @Test + fun pathParams() { + val params = + CardRetrieveSignalsParams.builder() + .cardToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .build() + + assertThat(params._pathParam(0)).isEqualTo("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + // out-of-bound path param + assertThat(params._pathParam(1)).isEqualTo("") + } +} diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/models/ConditionalAuthorizationActionParametersTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/models/ConditionalAuthorizationActionParametersTest.kt index 7eb7694e..ea753b57 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/models/ConditionalAuthorizationActionParametersTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/models/ConditionalAuthorizationActionParametersTest.kt @@ -31,6 +31,11 @@ internal class ConditionalAuthorizationActionParametersTest { .Scope .CARD ) + .unit( + ConditionalAuthorizationActionParameters.Condition.Parameters + .Unit + .MPH + ) .build() ) .build() @@ -56,6 +61,10 @@ internal class ConditionalAuthorizationActionParametersTest { ConditionalAuthorizationActionParameters.Condition.Parameters.Scope .CARD ) + .unit( + ConditionalAuthorizationActionParameters.Condition.Parameters.Unit + .MPH + ) .build() ) .build() @@ -85,6 +94,11 @@ internal class ConditionalAuthorizationActionParametersTest { .Scope .CARD ) + .unit( + ConditionalAuthorizationActionParameters.Condition.Parameters + .Unit + .MPH + ) .build() ) .build() diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/models/SignalsResponseTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/models/SignalsResponseTest.kt new file mode 100644 index 00000000..3384148f --- /dev/null +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/models/SignalsResponseTest.kt @@ -0,0 +1,133 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.lithic.api.models + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.lithic.api.core.jsonMapper +import java.time.OffsetDateTime +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class SignalsResponseTest { + + @Test + fun create() { + val signalsResponse = + SignalsResponse.builder() + .approvedTxnAmountM2(0.0) + .approvedTxnAmountM2_30d(0.0) + .approvedTxnAmountM2_7d(0.0) + .approvedTxnAmountM2_90d(0.0) + .approvedTxnCount(0L) + .approvedTxnCount30d(0L) + .approvedTxnCount7d(0L) + .approvedTxnCount90d(0L) + .avgTransactionAmount(0.0) + .avgTransactionAmount30d(0.0) + .avgTransactionAmount7d(0.0) + .avgTransactionAmount90d(0.0) + .distinctCountryCount(0L) + .distinctMccCount(0L) + .firstTxnAt(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .isFirstTransaction(true) + .lastCpCountry("last_cp_country") + .lastCpPostalCode("last_cp_postal_code") + .lastCpTimestamp(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .lastTxnApprovedAt(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .addSeenCountry("string") + .addSeenMcc("string") + .addSeenMerchant("string") + .stdevTransactionAmount(0.0) + .stdevTransactionAmount30d(0.0) + .stdevTransactionAmount7d(0.0) + .stdevTransactionAmount90d(0.0) + .threeDSSuccessCount(0L) + .threeDSSuccessRate(0.0) + .threeDSTotalCount(0L) + .timeSinceLastTransactionDays(0.0) + .build() + + assertThat(signalsResponse.approvedTxnAmountM2()).contains(0.0) + assertThat(signalsResponse.approvedTxnAmountM2_30d()).contains(0.0) + assertThat(signalsResponse.approvedTxnAmountM2_7d()).contains(0.0) + assertThat(signalsResponse.approvedTxnAmountM2_90d()).contains(0.0) + assertThat(signalsResponse.approvedTxnCount()).contains(0L) + assertThat(signalsResponse.approvedTxnCount30d()).contains(0L) + assertThat(signalsResponse.approvedTxnCount7d()).contains(0L) + assertThat(signalsResponse.approvedTxnCount90d()).contains(0L) + assertThat(signalsResponse.avgTransactionAmount()).contains(0.0) + assertThat(signalsResponse.avgTransactionAmount30d()).contains(0.0) + assertThat(signalsResponse.avgTransactionAmount7d()).contains(0.0) + assertThat(signalsResponse.avgTransactionAmount90d()).contains(0.0) + assertThat(signalsResponse.distinctCountryCount()).contains(0L) + assertThat(signalsResponse.distinctMccCount()).contains(0L) + assertThat(signalsResponse.firstTxnAt()) + .contains(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + assertThat(signalsResponse.isFirstTransaction()).contains(true) + assertThat(signalsResponse.lastCpCountry()).contains("last_cp_country") + assertThat(signalsResponse.lastCpPostalCode()).contains("last_cp_postal_code") + assertThat(signalsResponse.lastCpTimestamp()) + .contains(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + assertThat(signalsResponse.lastTxnApprovedAt()) + .contains(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + assertThat(signalsResponse.seenCountries().getOrNull()).containsExactly("string") + assertThat(signalsResponse.seenMccs().getOrNull()).containsExactly("string") + assertThat(signalsResponse.seenMerchants().getOrNull()).containsExactly("string") + assertThat(signalsResponse.stdevTransactionAmount()).contains(0.0) + assertThat(signalsResponse.stdevTransactionAmount30d()).contains(0.0) + assertThat(signalsResponse.stdevTransactionAmount7d()).contains(0.0) + assertThat(signalsResponse.stdevTransactionAmount90d()).contains(0.0) + assertThat(signalsResponse.threeDSSuccessCount()).contains(0L) + assertThat(signalsResponse.threeDSSuccessRate()).contains(0.0) + assertThat(signalsResponse.threeDSTotalCount()).contains(0L) + assertThat(signalsResponse.timeSinceLastTransactionDays()).contains(0.0) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val signalsResponse = + SignalsResponse.builder() + .approvedTxnAmountM2(0.0) + .approvedTxnAmountM2_30d(0.0) + .approvedTxnAmountM2_7d(0.0) + .approvedTxnAmountM2_90d(0.0) + .approvedTxnCount(0L) + .approvedTxnCount30d(0L) + .approvedTxnCount7d(0L) + .approvedTxnCount90d(0L) + .avgTransactionAmount(0.0) + .avgTransactionAmount30d(0.0) + .avgTransactionAmount7d(0.0) + .avgTransactionAmount90d(0.0) + .distinctCountryCount(0L) + .distinctMccCount(0L) + .firstTxnAt(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .isFirstTransaction(true) + .lastCpCountry("last_cp_country") + .lastCpPostalCode("last_cp_postal_code") + .lastCpTimestamp(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .lastTxnApprovedAt(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .addSeenCountry("string") + .addSeenMcc("string") + .addSeenMerchant("string") + .stdevTransactionAmount(0.0) + .stdevTransactionAmount30d(0.0) + .stdevTransactionAmount7d(0.0) + .stdevTransactionAmount90d(0.0) + .threeDSSuccessCount(0L) + .threeDSSuccessRate(0.0) + .threeDSTotalCount(0L) + .timeSinceLastTransactionDays(0.0) + .build() + + val roundtrippedSignalsResponse = + jsonMapper.readValue( + jsonMapper.writeValueAsString(signalsResponse), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSignalsResponse).isEqualTo(signalsResponse) + } +} diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/AccountServiceAsyncTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/AccountServiceAsyncTest.kt index c5517d3e..a68bf9b8 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/AccountServiceAsyncTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/services/async/AccountServiceAsyncTest.kt @@ -79,6 +79,22 @@ internal class AccountServiceAsyncTest { page.response().validate() } + @Test + fun retrieveSignals() { + val client = + LithicOkHttpClientAsync.builder() + .baseUrl(TestServerExtension.BASE_URL) + .apiKey("My Lithic API Key") + .build() + val accountServiceAsync = client.accounts() + + val signalsResponseFuture = + accountServiceAsync.retrieveSignals("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + + val signalsResponse = signalsResponseFuture.get() + signalsResponse.validate() + } + @Test fun retrieveSpendLimits() { val client = 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 04ab7e35..cfe02d9c 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 @@ -301,6 +301,22 @@ internal class CardServiceAsyncTest { card.validate() } + @Test + fun retrieveSignals() { + val client = + LithicOkHttpClientAsync.builder() + .baseUrl(TestServerExtension.BASE_URL) + .apiKey("My Lithic API Key") + .build() + val cardServiceAsync = client.cards() + + val signalsResponseFuture = + cardServiceAsync.retrieveSignals("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + + val signalsResponse = signalsResponseFuture.get() + signalsResponse.validate() + } + @Test fun retrieveSpendLimits() { val client = diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/AccountServiceTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/AccountServiceTest.kt index ea01b8c4..0278e99c 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/AccountServiceTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/AccountServiceTest.kt @@ -76,6 +76,20 @@ internal class AccountServiceTest { page.response().validate() } + @Test + fun retrieveSignals() { + val client = + LithicOkHttpClient.builder() + .baseUrl(TestServerExtension.BASE_URL) + .apiKey("My Lithic API Key") + .build() + val accountService = client.accounts() + + val signalsResponse = accountService.retrieveSignals("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + + signalsResponse.validate() + } + @Test fun retrieveSpendLimits() { val client = 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 3c03e597..4bbc9829 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 @@ -293,6 +293,20 @@ internal class CardServiceTest { card.validate() } + @Test + fun retrieveSignals() { + val client = + LithicOkHttpClient.builder() + .baseUrl(TestServerExtension.BASE_URL) + .apiKey("My Lithic API Key") + .build() + val cardService = client.cards() + + val signalsResponse = cardService.retrieveSignals("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + + signalsResponse.validate() + } + @Test fun retrieveSpendLimits() { val client =