diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6e8aef..44b5d87 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
+## [0.4.0] - 2024-07-??
+
+### Added
+- Verify v1 API
+
## [0.3.1] - 2024-07-12
### Changed
diff --git a/pom.xml b/pom.xml
index e191b27..d149214 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,9 +5,8 @@
com.vonage
server-sdk-kotlin
- 0.3.1
+ 0.4.0
- jar
Vonage Kotlin Server SDK
Kotlin client for Vonage APIs
https://github.com/Vonage/vonage-kotlin-sdk
@@ -48,6 +47,7 @@
2.0
2.0
1.8
+ 8
@@ -59,7 +59,7 @@
com.vonage
server-sdk
- 8.9.2
+ 8.9.3
org.jetbrains.kotlin
@@ -76,7 +76,7 @@
org.wiremock
wiremock-standalone
- 3.8.0
+ 3.9.0
test
@@ -93,7 +93,6 @@
${project.basedir}/src/test/kotlin
- org.apache.maven.plugins
maven-enforcer-plugin
3.5.0
@@ -108,7 +107,7 @@
3.6.3
- 1.8
+ ${java.version}
@@ -116,9 +115,8 @@
- org.apache.maven.plugins
maven-surefire-plugin
- 3.3.0
+ 3.3.1
org.jacoco
@@ -154,7 +152,7 @@
maven-javadoc-plugin
- 3.7.0
+ 3.8.0
dokka-jar
@@ -170,7 +168,6 @@
- org.apache.maven.plugins
maven-jar-plugin
3.4.2
@@ -185,13 +182,12 @@
- org.apache.maven.plugins
maven-compiler-plugin
3.13.0
-
- 8
- 8
+ ${java.version}
+
+ ${java.version}
diff --git a/src/main/kotlin/com/vonage/client/kt/VerifyLegacy.kt b/src/main/kotlin/com/vonage/client/kt/VerifyLegacy.kt
new file mode 100644
index 0000000..186fd00
--- /dev/null
+++ b/src/main/kotlin/com/vonage/client/kt/VerifyLegacy.kt
@@ -0,0 +1,44 @@
+package com.vonage.client.kt
+
+import com.vonage.client.verify.*
+
+class VerifyLegacy(private val verifyClient: VerifyClient) {
+
+ fun verify(number: String, brand: String, properties: (VerifyRequest.Builder.() -> Unit) = {}): VerifyResponse =
+ verifyClient.verify(VerifyRequest.builder(number, brand).apply(properties).build())
+
+ fun psd2Verify(number: String, amount: Double, payee: String,
+ properties: (Psd2Request.Builder.() -> Unit) = {}): VerifyResponse =
+ verifyClient.psd2Verify(Psd2Request.builder(number, amount, payee).apply(properties).build())
+
+ fun search(vararg requestIds: String): SearchVerifyResponse = verifyClient.search(*requestIds)
+
+ fun request(requestId: String): ExistingRequest = ExistingRequest(requestId)
+
+ fun request(response: VerifyResponse): ExistingRequest = request(response.requestId)
+
+ inner class ExistingRequest internal constructor(private val requestId: String) {
+
+ fun cancel(): ControlResponse = verifyClient.cancelVerification(requestId)
+
+ fun advance(): ControlResponse = verifyClient.advanceVerification(requestId)
+
+ fun check(code: String): CheckResponse = verifyClient.check(requestId, code)
+
+ fun search(): SearchVerifyResponse = verifyClient.search(requestId)
+
+ @Override
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ other as ExistingRequest
+ return requestId == other.requestId
+ }
+
+ @Override
+ override fun hashCode(): Int {
+ return requestId.hashCode()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/vonage/client/kt/Vonage.kt b/src/main/kotlin/com/vonage/client/kt/Vonage.kt
index 24b6224..1187f63 100644
--- a/src/main/kotlin/com/vonage/client/kt/Vonage.kt
+++ b/src/main/kotlin/com/vonage/client/kt/Vonage.kt
@@ -11,6 +11,7 @@ class Vonage(init: VonageClient.Builder.() -> Unit) {
val sms = Sms(vonageClient.smsClient)
val conversion = Conversion(vonageClient.conversionClient)
val redact = Redact(vonageClient.redactClient)
+ val verifyLegacy = VerifyLegacy(vonageClient.verifyClient)
}
fun VonageClient.Builder.authFromEnv(): VonageClient.Builder {
diff --git a/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt b/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt
index dcfb9a4..92b49ce 100644
--- a/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt
+++ b/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt
@@ -1,6 +1,5 @@
package com.vonage.client.kt
-import com.fasterxml.jackson.databind.ObjectMapper
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.client.WireMock.*
@@ -12,6 +11,7 @@ import com.vonage.client.common.HttpMethod
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.assertThrows
+import com.fasterxml.jackson.databind.ObjectMapper
import java.net.URI
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
@@ -25,9 +25,9 @@ abstract class AbstractTest {
private val apiSecret = "1234567890abcdef"
private val apiKeySecretEncoded = "YTFiMmMzZDQ6MTIzNDU2Nzg5MGFiY2RlZg=="
private val privateKeyPath = "src/test/resources/com/vonage/client/kt/application_key"
+ private val signatureSecretName = "sig"
private val apiSecretName = "api_secret"
private val apiKeyName = "api_key"
- private val signatureSecretName = "sig"
protected val testUuidStr = "aaaaaaaa-bbbb-4ccc-8ddd-0123456789ab"
protected val testUuid: UUID = UUID.fromString(testUuidStr)
protected val toNumber = "447712345689"
@@ -43,9 +43,13 @@ abstract class AbstractTest {
protected val endTime: Instant = Instant.parse(endTimeStr)
protected val timestampStr = "2016-11-14T07:45:14Z"
protected val timestampDateStr = "2016-11-14 07:45:14"
+ protected val timestampDate = strToDate(timestampDateStr)
+ protected val timestampDate2Str = "2019-03-02 18:46:57"
+ protected val timestampDate2 = strToDate(timestampDate2Str)
protected val timestamp: Instant = Instant.parse(timestampStr)
protected val timestamp2Str = "2020-01-29T14:08:30.201Z"
protected val timestamp2: Instant = Instant.parse(timestamp2Str)
+ protected val currency = "EUR"
private val port = 8081
private val wiremock: WireMockServer = WireMockServer(
@@ -71,6 +75,9 @@ abstract class AbstractTest {
wiremock.stop()
}
+ protected fun strToDate(dateStr: String): Date =
+ Date(Instant.parse(dateStr.replace(' ', 'T') + 'Z').toEpochMilli())
+
protected enum class ContentType(val mime: String) {
APPLICATION_JSON("application/json"),
FORM_URLENCODED("application/x-www-form-urlencoded");
diff --git a/src/test/kotlin/com/vonage/client/kt/ConversionTest.kt b/src/test/kotlin/com/vonage/client/kt/ConversionTest.kt
index 006bf43..dae717c 100644
--- a/src/test/kotlin/com/vonage/client/kt/ConversionTest.kt
+++ b/src/test/kotlin/com/vonage/client/kt/ConversionTest.kt
@@ -18,7 +18,7 @@ class ConversionTest : AbstractTest() {
fun `submit sms conversion with timestamp`() {
val delivered = true
mockSuccess(smsMessageId, smsEndpoint, delivered, true)
- conversionClient.convertSms(smsMessageId, delivered, timestamp)
+ conversionClient.convertSms(smsMessageId, delivered, timestampDate.toInstant())
}
@Test
diff --git a/src/test/kotlin/com/vonage/client/kt/VerifyLegacyTest.kt b/src/test/kotlin/com/vonage/client/kt/VerifyLegacyTest.kt
new file mode 100644
index 0000000..fbca0ce
--- /dev/null
+++ b/src/test/kotlin/com/vonage/client/kt/VerifyLegacyTest.kt
@@ -0,0 +1,276 @@
+package com.vonage.client.kt
+
+import com.vonage.client.verify.*
+import java.math.BigDecimal
+import java.util.*
+import kotlin.test.*
+
+class VerifyLegacyTest : AbstractTest() {
+ private val verifyClient = vonage.verifyLegacy
+ private val requestId = "abcdef0123456789abcdef0123456789"
+ private val existingRequest = verifyClient.request(requestId)
+ private val eventId = "0A00000012345678"
+ private val accountId = "abcdef01"
+ private val payee = "Acme Inc"
+ private val amount = 48.31
+ private val pinExpiry = 60
+ private val nextEventWait = 750
+ private val pinCode = "987123"
+ private val codeLength = 6
+ private val price = "0.10000000"
+ private val estimatedPriceMessagesSent = "0.03330000"
+
+ private fun getBaseUri(endpoint: String): String =
+ "/verify/$endpoint/json".replace("//", "/")
+
+ private fun assertVerify(params: Map, invocation: VerifyLegacy.() -> VerifyResponse) {
+ val expectedUrl = getBaseUri(if (params.containsKey("payee")) "psd2" else "")
+ val successResponse =
+ mockPostQueryParams(expectedUrl, params, expectedResponseParams = mapOf(
+ "request_id" to requestId,
+ "status" to "0"
+ ))
+ val successParsed = invocation.invoke(verifyClient)
+ assertNotNull(successParsed)
+ assertEquals(requestId, successParsed.requestId)
+ assertEquals(VerifyStatus.OK, successParsed.status)
+ assertEquals(existingRequest, verifyClient.request(successParsed))
+
+ val errorText = "Your request is incomplete and missing the mandatory parameter `number`"
+ mockPostQueryParams(expectedUrl, params, expectedResponseParams = mapOf(
+ "request_id" to requestId,
+ "status" to "2",
+ "error_text" to errorText,
+ "network" to networkCode
+ ))
+ val failureParsed = invocation.invoke(verifyClient)
+ assertNotNull(failureParsed)
+ assertEquals(requestId, failureParsed.requestId)
+ assertEquals(VerifyStatus.MISSING_PARAMS, failureParsed.status)
+ assertEquals(errorText, failureParsed.errorText)
+ assertEquals(networkCode, failureParsed.network)
+ }
+
+ private fun invokeControl(command: VerifyControlCommand) = when(command) {
+ VerifyControlCommand.CANCEL -> existingRequest.cancel()
+ VerifyControlCommand.TRIGGER_NEXT_EVENT -> existingRequest.advance()
+ }
+
+ private fun assertControl(command: VerifyControlCommand) {
+ val cmdStr = command.name.lowercase()
+ val expectedUrl = getBaseUri("control")
+ val expectedRequestParams = mapOf(
+ "request_id" to requestId,
+ "cmd" to cmdStr
+ )
+ mockPostQueryParams(expectedUrl, expectedRequestParams,
+ expectedResponseParams = mapOf(
+ "status" to "0",
+ "command" to cmdStr
+ )
+ )
+ val parsedSuccess = invokeControl(command)
+ assertNotNull(parsedSuccess)
+ assertEquals(command, parsedSuccess.command)
+ assertEquals(VerifyStatus.OK, VerifyStatus.fromInt(parsedSuccess.status.toInt()))
+ assertNull(parsedSuccess.errorText)
+
+ val errorText = "Your account does not have sufficient credit to process this request."
+ mockPostQueryParams(expectedUrl, expectedRequestParams,
+ expectedResponseParams = mapOf(
+ "status" to "9",
+ "error_text" to errorText
+ )
+ )
+
+ try {
+ val parsedFailure = invokeControl(command)
+ fail("Expected VerifyException but got $parsedFailure")
+ }
+ catch (ex: VerifyException) {
+ assertEquals(VerifyStatus.PARTNER_QUOTA_EXCEEDED, VerifyStatus.fromInt(ex.status.toInt()))
+ assertEquals(errorText, ex.errorText)
+ }
+ }
+
+ private fun assertSearch(vararg requestIds: String) {
+ val single = requestIds.size == 1
+ mockPostQueryParams(
+ expectedUrl = getBaseUri("search"),
+ expectedRequestParams = if (single)
+ mapOf("request_id" to requestIds.first()) else
+ mapOf("request_ids" to requestIds.last()), // Will contain duplicates otherwise
+ expectedResponseParams = mapOf(
+ "status" to "0",
+ "verification_requests" to listOf(
+ mapOf("status" to "EXPIRED"),
+ mapOf(
+ "request_id" to requestId,
+ "account_id" to accountId,
+ "number" to toNumber,
+ "currency" to currency,
+ "sender_id" to payee,
+ "date_submitted" to timestampDateStr,
+ "date_finalized" to timestampDate2Str,
+ "first_event_date" to timestampDateStr,
+ "last_event_date" to timestampDate2Str,
+ "status" to "IN PROGRESS",
+ "price" to price,
+ "estimated_price_messages_sent" to estimatedPriceMessagesSent
+ ),
+ mapOf()
+ )
+ )
+ )
+ val response = if (single) existingRequest.search() else verifyClient.search(*requestIds)
+ assertNotNull(response)
+ assertNull(response.errorText)
+ assertEquals(3, response.verificationRequests.size)
+ assertNotNull(response.verificationRequests[2])
+ assertEquals(VerifyDetails.Status.EXPIRED, response.verificationRequests[0].status)
+ val detail = response.verificationRequests[1]
+ assertNotNull(detail)
+ assertEquals(requestId, detail.requestId)
+ assertEquals(accountId, detail.accountId)
+ assertEquals(toNumber, detail.number)
+ assertEquals(currency, detail.currency)
+ assertEquals(payee, detail.senderId)
+ assertEquals(timestampDate, detail.dateSubmitted)
+ assertEquals(timestampDate2, detail.dateFinalized)
+ assertEquals(timestampDate, detail.firstEventDate)
+ assertEquals(timestampDate2, detail.lastEventDate)
+ assertEquals(VerifyDetails.Status.IN_PROGRESS, detail.status)
+ assertEquals(BigDecimal(price), detail.price)
+ assertEquals(BigDecimal(estimatedPriceMessagesSent), detail.estimatedPriceMessagesSent)
+ }
+
+ @Test
+ fun `existing request hashCode is based on the requestId`() {
+ assertEquals(requestId.hashCode(), existingRequest.hashCode())
+ assertEquals(existingRequest, verifyClient.request(requestId))
+ }
+
+ @Test
+ fun `verify request success required parameters`() {
+ assertVerify(mapOf("brand" to payee, "number" to toNumber)) {
+ verify(toNumber, payee)
+ }
+ }
+
+ @Test
+ fun `verify request all parameters`() {
+ assertVerify(mapOf(
+ "brand" to payee, "number" to toNumber,
+ "sender_id" to altNumber,
+ "pin_expiry" to pinExpiry,
+ "pin_code" to pinCode,
+ "next_event_wait" to nextEventWait,
+ "country" to "GB",
+ "lg" to "en-gb",
+ "workflow_id" to 2
+ )) {
+ verify(toNumber, payee) {
+ senderId(altNumber); pinCode(pinCode)
+ pinExpiry(pinExpiry); nextEventWait(nextEventWait)
+ locale(Locale.UK); country("GB")
+ workflow(VerifyRequest.Workflow.SMS_SMS_TTS)
+ }
+ }
+ }
+
+ @Test
+ fun `psd2 request required parameters`() {
+ assertVerify(mapOf(
+ "number" to toNumber, "amount" to amount, "payee" to payee
+ )) {
+ psd2Verify(toNumber, amount, payee)
+ }
+ }
+
+ @Test
+ fun `psd2 request all parameters`() {
+ val country = "DE"
+ assertVerify(mapOf(
+ "number" to toNumber,
+ "amount" to amount,
+ "payee" to payee,
+ "code_length" to codeLength,
+ "pin_expiry" to pinExpiry,
+ "next_event_wait" to nextEventWait,
+ "country" to country,
+ "lg" to "de-${country.lowercase()}",
+ "workflow_id" to 5
+
+ )) {
+ psd2Verify(toNumber, amount, payee) {
+ length(codeLength); pinExpiry(pinExpiry)
+ nextEventWait(nextEventWait); country(country)
+ locale(Locale.GERMANY)
+ workflow(Psd2Request.Workflow.SMS_TTS)
+ }
+ }
+ }
+
+ @Test
+ fun `check verification code`() {
+ val expectedUrl = getBaseUri("check")
+ val expectedRequestParams = mapOf(
+ "request_id" to requestId,
+ "code" to pinCode
+ )
+ mockPostQueryParams(expectedUrl, expectedRequestParams,
+ expectedResponseParams = mapOf(
+ "request_id" to requestId,
+ "event_id" to eventId,
+ "status" to 0,
+ "price" to price,
+ "currency" to currency,
+ "estimated_price_messages_sent" to estimatedPriceMessagesSent
+ )
+ )
+
+ val parsedSuccess = existingRequest.check(pinCode)
+ assertNotNull(parsedSuccess)
+ assertNull(parsedSuccess.errorText)
+ assertEquals(requestId, parsedSuccess.requestId)
+ assertEquals(eventId, parsedSuccess.eventId)
+ assertEquals(VerifyStatus.OK, parsedSuccess.status)
+ assertEquals(currency, parsedSuccess.currency)
+ assertEquals(BigDecimal(estimatedPriceMessagesSent), parsedSuccess.estimatedPriceMessagesSent)
+
+ val errorText = "The code inserted does not match the expected value"
+ mockPostQueryParams(expectedUrl, expectedRequestParams,
+ expectedResponseParams = mapOf(
+ "request_id" to requestId,
+ "status" to 16,
+ "error_text" to errorText
+ )
+ )
+
+ val parsedFailure = existingRequest.check(pinCode)
+ assertNotNull(parsedFailure)
+ assertEquals(requestId, parsedFailure.requestId)
+ assertEquals(VerifyStatus.INVALID_CODE, parsedFailure.status)
+ assertEquals(errorText, parsedFailure.errorText)
+ }
+
+ @Test
+ fun `cancel verification`() {
+ assertControl(VerifyControlCommand.CANCEL)
+ }
+
+ @Test
+ fun `advance verification`() {
+ assertControl(VerifyControlCommand.TRIGGER_NEXT_EVENT)
+ }
+
+ @Test
+ fun `search single request`() {
+ assertSearch(requestId)
+ }
+
+ @Test
+ fun `search multiple requests`() {
+ assertSearch(requestId, textHexEncoded, testUuidStr)
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt b/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt
index 81228bb..2d016b4 100644
--- a/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt
+++ b/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt
@@ -4,7 +4,6 @@ import com.vonage.client.common.HttpMethod
import com.vonage.client.voice.*
import com.vonage.client.voice.ncco.*
import java.net.URI
-import java.time.Instant
import java.util.*
import kotlin.test.Test
import kotlin.test.*