diff --git a/.stats.yml b/.stats.yml index adbb7cab..97e2fbfc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 130 +configured_endpoints: 131 diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/models/ThreeDSDecisioningChallengeResponseParams.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/models/ThreeDSDecisioningChallengeResponseParams.kt new file mode 100644 index 00000000..d257ceac --- /dev/null +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/models/ThreeDSDecisioningChallengeResponseParams.kt @@ -0,0 +1,354 @@ +// 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.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.lithic.api.core.Enum +import com.lithic.api.core.ExcludeMissing +import com.lithic.api.core.JsonField +import com.lithic.api.core.JsonValue +import com.lithic.api.core.NoAutoDetect +import com.lithic.api.core.toUnmodifiable +import com.lithic.api.errors.LithicInvalidDataException +import com.lithic.api.models.* +import java.util.Objects + +class ThreeDSDecisioningChallengeResponseParams +constructor( + private val token: String, + private val challengeResponse: ChallengeResponse, + private val additionalQueryParams: Map>, + private val additionalHeaders: Map>, + private val additionalBodyProperties: Map, +) { + + fun token(): String = token + + fun challengeResponse(): ChallengeResponse = challengeResponse + + @JvmSynthetic + internal fun getBody(): ThreeDSDecisioningChallengeResponseBody { + return ThreeDSDecisioningChallengeResponseBody( + token, + challengeResponse, + additionalBodyProperties, + ) + } + + @JvmSynthetic internal fun getQueryParams(): Map> = additionalQueryParams + + @JvmSynthetic internal fun getHeaders(): Map> = additionalHeaders + + @JsonDeserialize(builder = ThreeDSDecisioningChallengeResponseBody.Builder::class) + @NoAutoDetect + class ThreeDSDecisioningChallengeResponseBody + internal constructor( + private val token: String?, + private val challengeResponse: ChallengeResponse?, + private val additionalProperties: Map, + ) { + + private var hashCode: Int = 0 + + /** + * Globally unique identifier for the 3DS authentication. This token is sent as part of the + * initial 3DS Decisioning Request and as part of the 3DS Challenge Event in the + * [ThreeDSAuthentication](#/components/schemas/ThreeDSAuthentication) object + */ + @JsonProperty("token") fun token(): String? = token + + /** Whether the Cardholder has Approved or Declined the issued Challenge */ + @JsonProperty("challenge_response") + fun challengeResponse(): ChallengeResponse? = challengeResponse + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = additionalProperties + + fun toBuilder() = Builder().from(this) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ThreeDSDecisioningChallengeResponseBody && + this.token == other.token && + this.challengeResponse == other.challengeResponse && + this.additionalProperties == other.additionalProperties + } + + override fun hashCode(): Int { + if (hashCode == 0) { + hashCode = + Objects.hash( + token, + challengeResponse, + additionalProperties, + ) + } + return hashCode + } + + override fun toString() = + "ThreeDSDecisioningChallengeResponseBody{token=$token, challengeResponse=$challengeResponse, additionalProperties=$additionalProperties}" + + companion object { + + @JvmStatic fun builder() = Builder() + } + + class Builder { + + private var token: String? = null + private var challengeResponse: ChallengeResponse? = null + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from( + threeDSDecisioningChallengeResponseBody: ThreeDSDecisioningChallengeResponseBody + ) = apply { + this.token = threeDSDecisioningChallengeResponseBody.token + this.challengeResponse = threeDSDecisioningChallengeResponseBody.challengeResponse + additionalProperties(threeDSDecisioningChallengeResponseBody.additionalProperties) + } + + /** + * Globally unique identifier for the 3DS authentication. This token is sent as part of + * the initial 3DS Decisioning Request and as part of the 3DS Challenge Event in the + * [ThreeDSAuthentication](#/components/schemas/ThreeDSAuthentication) object + */ + @JsonProperty("token") fun token(token: String) = apply { this.token = token } + + /** Whether the Cardholder has Approved or Declined the issued Challenge */ + @JsonProperty("challenge_response") + fun challengeResponse(challengeResponse: ChallengeResponse) = apply { + this.challengeResponse = challengeResponse + } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + this.additionalProperties.putAll(additionalProperties) + } + + @JsonAnySetter + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + this.additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun build(): ThreeDSDecisioningChallengeResponseBody = + ThreeDSDecisioningChallengeResponseBody( + checkNotNull(token) { "`token` is required but was not set" }, + checkNotNull(challengeResponse) { + "`challengeResponse` is required but was not set" + }, + additionalProperties.toUnmodifiable(), + ) + } + } + + fun _additionalQueryParams(): Map> = additionalQueryParams + + fun _additionalHeaders(): Map> = additionalHeaders + + fun _additionalBodyProperties(): Map = additionalBodyProperties + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ThreeDSDecisioningChallengeResponseParams && + this.token == other.token && + this.challengeResponse == other.challengeResponse && + this.additionalQueryParams == other.additionalQueryParams && + this.additionalHeaders == other.additionalHeaders && + this.additionalBodyProperties == other.additionalBodyProperties + } + + override fun hashCode(): Int { + return Objects.hash( + token, + challengeResponse, + additionalQueryParams, + additionalHeaders, + additionalBodyProperties, + ) + } + + override fun toString() = + "ThreeDSDecisioningChallengeResponseParams{token=$token, challengeResponse=$challengeResponse, additionalQueryParams=$additionalQueryParams, additionalHeaders=$additionalHeaders, additionalBodyProperties=$additionalBodyProperties}" + + fun toBuilder() = Builder().from(this) + + companion object { + + @JvmStatic fun builder() = Builder() + } + + @NoAutoDetect + class Builder { + + private var token: String? = null + private var challengeResponse: ChallengeResponse? = null + private var additionalQueryParams: MutableMap> = mutableMapOf() + private var additionalHeaders: MutableMap> = mutableMapOf() + private var additionalBodyProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from( + threeDSDecisioningChallengeResponseParams: ThreeDSDecisioningChallengeResponseParams + ) = apply { + this.token = threeDSDecisioningChallengeResponseParams.token + this.challengeResponse = threeDSDecisioningChallengeResponseParams.challengeResponse + additionalQueryParams(threeDSDecisioningChallengeResponseParams.additionalQueryParams) + additionalHeaders(threeDSDecisioningChallengeResponseParams.additionalHeaders) + additionalBodyProperties( + threeDSDecisioningChallengeResponseParams.additionalBodyProperties + ) + } + + /** + * Globally unique identifier for the 3DS authentication. This token is sent as part of the + * initial 3DS Decisioning Request and as part of the 3DS Challenge Event in the + * [ThreeDSAuthentication](#/components/schemas/ThreeDSAuthentication) object + */ + fun token(token: String) = apply { this.token = token } + + /** Whether the Cardholder has Approved or Declined the issued Challenge */ + fun challengeResponse(challengeResponse: ChallengeResponse) = apply { + this.challengeResponse = challengeResponse + } + + fun additionalQueryParams(additionalQueryParams: Map>) = apply { + this.additionalQueryParams.clear() + putAllQueryParams(additionalQueryParams) + } + + fun putQueryParam(name: String, value: String) = apply { + this.additionalQueryParams.getOrPut(name) { mutableListOf() }.add(value) + } + + fun putQueryParams(name: String, values: Iterable) = apply { + this.additionalQueryParams.getOrPut(name) { mutableListOf() }.addAll(values) + } + + fun putAllQueryParams(additionalQueryParams: Map>) = apply { + additionalQueryParams.forEach(this::putQueryParams) + } + + fun removeQueryParam(name: String) = apply { + this.additionalQueryParams.put(name, mutableListOf()) + } + + fun additionalHeaders(additionalHeaders: Map>) = apply { + this.additionalHeaders.clear() + putAllHeaders(additionalHeaders) + } + + fun putHeader(name: String, value: String) = apply { + this.additionalHeaders.getOrPut(name) { mutableListOf() }.add(value) + } + + fun putHeaders(name: String, values: Iterable) = apply { + this.additionalHeaders.getOrPut(name) { mutableListOf() }.addAll(values) + } + + fun putAllHeaders(additionalHeaders: Map>) = apply { + additionalHeaders.forEach(this::putHeaders) + } + + fun removeHeader(name: String) = apply { this.additionalHeaders.put(name, mutableListOf()) } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + this.additionalBodyProperties.clear() + this.additionalBodyProperties.putAll(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + this.additionalBodyProperties.put(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + this.additionalBodyProperties.putAll(additionalBodyProperties) + } + + fun build(): ThreeDSDecisioningChallengeResponseParams = + ThreeDSDecisioningChallengeResponseParams( + checkNotNull(token) { "`token` is required but was not set" }, + checkNotNull(challengeResponse) { + "`challengeResponse` is required but was not set" + }, + additionalQueryParams.mapValues { it.value.toUnmodifiable() }.toUnmodifiable(), + additionalHeaders.mapValues { it.value.toUnmodifiable() }.toUnmodifiable(), + additionalBodyProperties.toUnmodifiable(), + ) + } + + class ChallengeResponse + @JsonCreator + private constructor( + private val value: JsonField, + ) : Enum { + + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ChallengeResponse && this.value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + + companion object { + + @JvmField val APPROVE = ChallengeResponse(JsonField.of("APPROVE")) + + @JvmField + val DECLINE_BY_CUSTOMER = ChallengeResponse(JsonField.of("DECLINE_BY_CUSTOMER")) + + @JvmStatic fun of(value: String) = ChallengeResponse(JsonField.of(value)) + } + + enum class Known { + APPROVE, + DECLINE_BY_CUSTOMER, + } + + enum class Value { + APPROVE, + DECLINE_BY_CUSTOMER, + _UNKNOWN, + } + + fun value(): Value = + when (this) { + APPROVE -> Value.APPROVE + DECLINE_BY_CUSTOMER -> Value.DECLINE_BY_CUSTOMER + else -> Value._UNKNOWN + } + + fun known(): Known = + when (this) { + APPROVE -> Known.APPROVE + DECLINE_BY_CUSTOMER -> Known.DECLINE_BY_CUSTOMER + else -> throw LithicInvalidDataException("Unknown ChallengeResponse: $value") + } + + fun asString(): String = _value().asStringOrThrow() + } +} diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/threeDS/DecisioningServiceAsync.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/threeDS/DecisioningServiceAsync.kt index 8adfbea8..34648472 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/threeDS/DecisioningServiceAsync.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/threeDS/DecisioningServiceAsync.kt @@ -6,12 +6,20 @@ package com.lithic.api.services.async.threeDS import com.lithic.api.core.RequestOptions import com.lithic.api.models.DecisioningRetrieveSecretResponse +import com.lithic.api.models.ThreeDSDecisioningChallengeResponseParams import com.lithic.api.models.ThreeDSDecisioningRetrieveSecretParams import com.lithic.api.models.ThreeDSDecisioningRotateSecretParams import java.util.concurrent.CompletableFuture interface DecisioningServiceAsync { + /** Card program's response to a 3DS Challenge Request (CReq) */ + @JvmOverloads + fun challengeResponse( + params: ThreeDSDecisioningChallengeResponseParams, + requestOptions: RequestOptions = RequestOptions.none() + ): CompletableFuture + /** * Retrieve the 3DS Decisioning HMAC secret key. If one does not exist for your program yet, * calling this endpoint will create one for you. The headers (which you can use to verify 3DS diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/threeDS/DecisioningServiceAsyncImpl.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/threeDS/DecisioningServiceAsyncImpl.kt index 5f80c1e5..ab357666 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/threeDS/DecisioningServiceAsyncImpl.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/async/threeDS/DecisioningServiceAsyncImpl.kt @@ -9,6 +9,7 @@ import com.lithic.api.core.http.HttpRequest import com.lithic.api.core.http.HttpResponse.Handler import com.lithic.api.errors.LithicError import com.lithic.api.models.DecisioningRetrieveSecretResponse +import com.lithic.api.models.ThreeDSDecisioningChallengeResponseParams import com.lithic.api.models.ThreeDSDecisioningRetrieveSecretParams import com.lithic.api.models.ThreeDSDecisioningRotateSecretParams import com.lithic.api.services.emptyHandler @@ -25,6 +26,30 @@ constructor( private val errorHandler: Handler = errorHandler(clientOptions.jsonMapper) + private val challengeResponseHandler: Handler = + emptyHandler().withErrorHandler(errorHandler) + + /** Card program's response to a 3DS Challenge Request (CReq) */ + override fun challengeResponse( + params: ThreeDSDecisioningChallengeResponseParams, + requestOptions: RequestOptions + ): CompletableFuture { + val request = + HttpRequest.builder() + .method(HttpMethod.POST) + .addPathSegments("three_ds_decisioning", "challenge_response") + .putAllQueryParams(clientOptions.queryParams) + .putAllQueryParams(params.getQueryParams()) + .putAllHeaders(clientOptions.headers) + .putAllHeaders(params.getHeaders()) + .body(json(clientOptions.jsonMapper, params.getBody())) + .build() + return clientOptions.httpClient.executeAsync(request, requestOptions).thenApply { response + -> + response.use { challengeResponseHandler.handle(it) } + } + } + private val retrieveSecretHandler: Handler = jsonHandler(clientOptions.jsonMapper) .withErrorHandler(errorHandler) diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningService.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningService.kt index eedd11c8..9fbf7419 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningService.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningService.kt @@ -6,11 +6,19 @@ package com.lithic.api.services.blocking.threeDS import com.lithic.api.core.RequestOptions import com.lithic.api.models.DecisioningRetrieveSecretResponse +import com.lithic.api.models.ThreeDSDecisioningChallengeResponseParams import com.lithic.api.models.ThreeDSDecisioningRetrieveSecretParams import com.lithic.api.models.ThreeDSDecisioningRotateSecretParams interface DecisioningService { + /** Card program's response to a 3DS Challenge Request (CReq) */ + @JvmOverloads + fun challengeResponse( + params: ThreeDSDecisioningChallengeResponseParams, + requestOptions: RequestOptions = RequestOptions.none() + ) + /** * Retrieve the 3DS Decisioning HMAC secret key. If one does not exist for your program yet, * calling this endpoint will create one for you. The headers (which you can use to verify 3DS diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningServiceImpl.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningServiceImpl.kt index ed57f857..7ef34e09 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningServiceImpl.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningServiceImpl.kt @@ -9,6 +9,7 @@ import com.lithic.api.core.http.HttpRequest import com.lithic.api.core.http.HttpResponse.Handler import com.lithic.api.errors.LithicError import com.lithic.api.models.DecisioningRetrieveSecretResponse +import com.lithic.api.models.ThreeDSDecisioningChallengeResponseParams import com.lithic.api.models.ThreeDSDecisioningRetrieveSecretParams import com.lithic.api.models.ThreeDSDecisioningRotateSecretParams import com.lithic.api.services.emptyHandler @@ -24,6 +25,29 @@ constructor( private val errorHandler: Handler = errorHandler(clientOptions.jsonMapper) + private val challengeResponseHandler: Handler = + emptyHandler().withErrorHandler(errorHandler) + + /** Card program's response to a 3DS Challenge Request (CReq) */ + override fun challengeResponse( + params: ThreeDSDecisioningChallengeResponseParams, + requestOptions: RequestOptions + ) { + val request = + HttpRequest.builder() + .method(HttpMethod.POST) + .addPathSegments("three_ds_decisioning", "challenge_response") + .putAllQueryParams(clientOptions.queryParams) + .putAllQueryParams(params.getQueryParams()) + .putAllHeaders(clientOptions.headers) + .putAllHeaders(params.getHeaders()) + .body(json(clientOptions.jsonMapper, params.getBody())) + .build() + clientOptions.httpClient.execute(request, requestOptions).let { response -> + response.use { challengeResponseHandler.handle(it) } + } + } + private val retrieveSecretHandler: Handler = jsonHandler(clientOptions.jsonMapper) .withErrorHandler(errorHandler) diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/models/ThreeDSDecisioningChallengeResponseParamsTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/models/ThreeDSDecisioningChallengeResponseParamsTest.kt new file mode 100644 index 00000000..352170ef --- /dev/null +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/models/ThreeDSDecisioningChallengeResponseParamsTest.kt @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.lithic.api.models + +import com.lithic.api.models.* +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class ThreeDSDecisioningChallengeResponseParamsTest { + + @Test + fun createThreeDSDecisioningChallengeResponseParams() { + ThreeDSDecisioningChallengeResponseParams.builder() + .token("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .challengeResponse(ThreeDSDecisioningChallengeResponseParams.ChallengeResponse.APPROVE) + .build() + } + + @Test + fun getBody() { + val params = + ThreeDSDecisioningChallengeResponseParams.builder() + .token("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .challengeResponse( + ThreeDSDecisioningChallengeResponseParams.ChallengeResponse.APPROVE + ) + .build() + val body = params.getBody() + assertThat(body).isNotNull + assertThat(body.token()).isEqualTo("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + assertThat(body.challengeResponse()) + .isEqualTo(ThreeDSDecisioningChallengeResponseParams.ChallengeResponse.APPROVE) + } + + @Test + fun getBodyWithoutOptionalFields() { + val params = + ThreeDSDecisioningChallengeResponseParams.builder() + .token("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .challengeResponse( + ThreeDSDecisioningChallengeResponseParams.ChallengeResponse.APPROVE + ) + .build() + val body = params.getBody() + assertThat(body).isNotNull + assertThat(body.token()).isEqualTo("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + assertThat(body.challengeResponse()) + .isEqualTo(ThreeDSDecisioningChallengeResponseParams.ChallengeResponse.APPROVE) + } +} diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningServiceTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningServiceTest.kt index ffb04aee..1e53b443 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningServiceTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/services/blocking/threeDS/DecisioningServiceTest.kt @@ -11,6 +11,24 @@ import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(TestServerExtension::class) class DecisioningServiceTest { + @Test + fun callChallengeResponse() { + val client = + LithicOkHttpClient.builder() + .baseUrl(TestServerExtension.BASE_URL) + .apiKey("My Lithic API Key") + .build() + val decisioningService = client.threeDS().decisioning() + decisioningService.challengeResponse( + ThreeDSDecisioningChallengeResponseParams.builder() + .token("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .challengeResponse( + ThreeDSDecisioningChallengeResponseParams.ChallengeResponse.APPROVE + ) + .build() + ) + } + @Test fun callRetrieveSecret() { val client =