Skip to content

Commit

Permalink
Prepare for 10.0.0-beta05 release (#174)
Browse files Browse the repository at this point in the history
* Remove Polling (#169)

* Add Async Enhanced KYC endpoint (#168)

* Add Async Enhanced KYC endpoint

* Fix URL

* Setting up loading button (#170)

* setting up loading button

* fixed pr comments

* loading button tests

* Include ID Info in Document Verification network request (#173)

* Include ID Info in network request

* Update CHANGELOG

* Fix test

* Update CHANGELOG

---------

Co-authored-by: Juma Allan <[email protected]>
  • Loading branch information
vanshg and jumaallan authored Jul 24, 2023
1 parent 0f5eaf4 commit 1beef41
Show file tree
Hide file tree
Showing 34 changed files with 1,966 additions and 444 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release_sdk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,6 @@ jobs:
git config --local user.email "${{ github.actor }}@users.noreply.github.com"
git config --local user.name "${{ github.actor }}"
git add VERSION
git add ./lib/VERSION
git commit -m "Prepare for next development iteration"
git push
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
# Changelog

## 10.0.0-beta05 (unreleased)
## 10.0.0-beta05

### Added
- Add helper functions which return a Flow of the latest JobStatus for a Job until it is complete
- Add a `JobStatusResponse` interface
- Enhanced KYC Async API endpoint

### Fixed
- Fixed a bug where `id_info` was not included in Document Verification network requests

### Changed
- Kotlin 1.9
- Updated API key exception error message to be actionable
- `SmileID.useSandbox` getter is now publicly accessible
- Bump Sentry to 6.25.2

### Removed
- Removed polling from SmartSelfie Authentication, Document Verification, and Biometric KYC. The
returned `SmileIDResult`s will now contain only the immediate result of job status without waiting
for job completion

## 10.0.0-beta04

Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Smile ID Android SDK

<p align="center">
<img width="200" height="200" style="border-radius:15%" src="sample/listing/ic_launcher-playstore.png" />
</p>

[![Build](https://github.com/smileidentity/android/actions/workflows/build.yaml/badge.svg)](https://github.com/smileidentity/android/actions/workflows/build.yaml)
[![Maven Central](https://img.shields.io/maven-central/v/com.smileidentity/android-sdk)](https://mvnrepository.com/artifact/com.smileidentity/android-sdk)
[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/com.smileidentity/android-sdk?server=https%3A%2F%2Foss.sonatype.org)](https://oss.sonatype.org/content/repositories/snapshots/com/smileidentity/android-sdk/)
Expand All @@ -12,18 +16,17 @@ If you haven’t already,
[sign up](https://www.usesmileid.com/schedule-a-demo/) for a free Smile ID account, which comes
with Sandbox access.

Please see [CHANGELOG.md](CHANGELOG.md) or
[Releases](https://github.com/smileidentity/android/releases) for the most recent version and
Please see [CHANGELOG.md](CHANGELOG.md) or
[Releases](https://github.com/smileidentity/android/releases) for the most recent version and
release notes


## Getting Started

Full documentation is available at https://docs.usesmileid.com/integration-options/mobile

Javadocs are available at https://javadoc.io/doc/com.smileidentity/android-sdk/latest/index.html

The [sample app](sample/src/main/java/com/smileidentity/sample/compose/MainScreen.kt) included in
The [sample app](sample/src/main/java/com/smileidentity/sample/compose/MainScreen.kt) included in
this repo is a good reference implementation

#### 0. Requirements
Expand Down Expand Up @@ -59,9 +62,10 @@ All UI functionality is exposed via either Jetpack Compose or Fragments

#### Jetpack Compose

All Composables are available under the `SmileID` object.
All Composables are available under the `SmileID` object.

e.g.

```kotlin
SmileID.SmartSelfieEnrollment()
SmileID.SmartSelfieAuthentication()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.smileidentity.compose.components

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import org.junit.Assert
import org.junit.Rule
import org.junit.Test

class LoadingButtonTest {
@get:Rule
val composeTestRule = createComposeRule()

@Test
fun testContinueButtonIsClickable() {
// given
var callbackInvoked = false
val onConfirmButtonClicked = { callbackInvoked = true }
val continueButtonText = "Continue"

// when
composeTestRule.setContent {
LoadingButton(
continueButtonText,
onClick = onConfirmButtonClicked,
)
}
composeTestRule.onNodeWithText(continueButtonText).performClick()

// then
Assert.assertTrue(callbackInvoked)
}

@Test
fun testContinueButtonShowsLoadingStateWhenClicked() {
// given
var callbackInvoked = false
val onConfirmButtonClicked = { callbackInvoked = true }
val continueButtonText = "Continue"

// when
composeTestRule.setContent {
LoadingButton(
continueButtonText,
onClick = onConfirmButtonClicked,
)
}
composeTestRule.onNodeWithText(continueButtonText).performClick()

// then
Assert.assertTrue(callbackInvoked)
composeTestRule.onNodeWithText(continueButtonText).assertIsNotDisplayed()
composeTestRule.onNodeWithTag("circular_loading_indicator").assertIsDisplayed()
}
}
5 changes: 3 additions & 2 deletions lib/src/main/java/com/smileidentity/SmileID.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ import java.util.concurrent.TimeUnit
@Suppress("unused", "RemoveRedundantQualifierName")
object SmileID {
@JvmStatic
lateinit var api: SmileIDService private set
lateinit var api: SmileIDService internal set
val moshi: Moshi = initMoshi() // Initialized immediately so it can be used to parse Config

lateinit var config: Config
private lateinit var retrofit: Retrofit

// Can't use lateinit on primitives, this default will be overwritten as soon as init is called
internal var useSandbox: Boolean = true
var useSandbox: Boolean = true
private set

internal var apiKey: String? = null

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.smileidentity.compose.components

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
import com.smileidentity.R
import com.smileidentity.compose.preview.SmilePreviews

@Composable
fun LoadingButton(
buttonText: String,
modifier: Modifier = Modifier,
loading: Boolean = false,
onClick: () -> Unit,
) {
Button(
onClick = onClick,
modifier = modifier.fillMaxWidth(),
enabled = !loading,
) {
Box {
if (loading) {
CircularProgressIndicator(
color = colorResource(id = R.color.si_color_accent),
strokeWidth = 2.dp,
modifier = Modifier
.size(15.dp)
.align(Alignment.Center)
.testTag("circular_loading_indicator"),
)
} else {
Text(text = buttonText)
}
}
}
}

@SmilePreviews
@Composable
fun LoadingButtonPreview() {
LoadingButton(
buttonText = "Continue",
onClick = {},
)
}
6 changes: 5 additions & 1 deletion lib/src/main/java/com/smileidentity/models/Authentication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import kotlinx.parcelize.Parcelize
@Parcelize
@JsonClass(generateAdapter = true)
data class AuthenticationRequest(
@Json(name = "job_type") val jobType: JobType,
@Json(name = "job_type") val jobType: JobType? = null,
@Json(name = "enrollment") val enrollment: Boolean = jobType == SmartSelfieEnrollment,
@Json(name = "country") val country: String? = null,
@Json(name = "id_type") val idType: String? = null,
Expand All @@ -50,6 +50,9 @@ data class AuthenticationRequest(
* [consentInfo] is only populated when a country and ID type are provided in the
* [AuthenticationRequest]. To get information about *all* countries and ID types instead, use
* [com.smileidentity.networking.SmileIDService.getProductsConfig]
*
* [timestamp] is *not* a [java.util.Date] because technically, any arbitrary value could have been
* passed to it. This applies to all other timestamp fields in the SDK.
*/
@Parcelize
@JsonClass(generateAdapter = true)
Expand All @@ -58,6 +61,7 @@ data class AuthenticationResponse(
@Json(name = "signature") val signature: String,
@Json(name = "timestamp") val timestamp: String,
@Json(name = "partner_params") val partnerParams: PartnerParams,
@Json(name = "callback_url") val callbackUrl: String? = null,
@Json(name = "consent_info") val consentInfo: ConsentInfo? = null,
) : Parcelable

Expand Down
5 changes: 5 additions & 0 deletions lib/src/main/java/com/smileidentity/models/EnhancedKyc.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ data class EnhancedKycRequest(
@Json(name = "dob") val dob: String? = null,
@Json(name = "phone_number") val phoneNumber: String? = null,
@Json(name = "bank_code") val bankCode: String? = null,
@Json(name = "callback_url") val callbackUrl: String? = null,
@Json(name = "partner_params") val partnerParams: PartnerParams,
@Json(name = "partner_id") val partnerId: String = SmileID.config.partnerId,
@Json(name = "source_sdk") val sourceSdk: String = "android",
Expand All @@ -44,3 +45,7 @@ data class EnhancedKycResponse(
@Json(name = "DOB") val dob: String?,
@Json(name = "Photo") val base64Photo: String?,
) : Parcelable

@Parcelize
@JsonClass(generateAdapter = true)
data class EnhancedKycAsyncResponse(@Json(name = "success") val success: Boolean) : Parcelable
53 changes: 31 additions & 22 deletions lib/src/main/java/com/smileidentity/models/JobStatus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,50 @@ data class JobStatusRequest(
@Json(name = "signature") val signature: String = calculateSignature(timestamp),
) : Parcelable

interface JobStatusResponse {
val timestamp: String
val jobComplete: Boolean
val jobSuccess: Boolean
val code: Int
val result: JobResult?
val imageLinks: ImageLinks?
}

@Parcelize
@JsonClass(generateAdapter = true)
data class JobStatusResponse(
@Json(name = "timestamp") val timestamp: String,
@Json(name = "job_complete") val jobComplete: Boolean,
@Json(name = "job_success") val jobSuccess: Boolean,
@Json(name = "code") val code: Int,
@Json(name = "result") val result: JobResult?,
data class SmartSelfieJobStatusResponse(
@Json(name = "timestamp") override val timestamp: String,
@Json(name = "job_complete") override val jobComplete: Boolean,
@Json(name = "job_success") override val jobSuccess: Boolean,
@Json(name = "code") override val code: Int,
@Json(name = "result") override val result: JobResult?,
@Json(name = "history") val history: List<JobResult.Entry>?,
@Json(name = "image_links") val imageLinks: ImageLinks?,
) : Parcelable
@Json(name = "image_links") override val imageLinks: ImageLinks?,
) : JobStatusResponse, Parcelable

@Parcelize
@JsonClass(generateAdapter = true)
data class DocVJobStatusResponse(
@Json(name = "timestamp") val timestamp: String,
@Json(name = "job_complete") val jobComplete: Boolean,
@Json(name = "job_success") val jobSuccess: Boolean,
@Json(name = "code") val code: Int,
@Json(name = "result") val result: JobResult?,
@Json(name = "timestamp") override val timestamp: String,
@Json(name = "job_complete") override val jobComplete: Boolean,
@Json(name = "job_success") override val jobSuccess: Boolean,
@Json(name = "code") override val code: Int,
@Json(name = "result") override val result: JobResult?,
@Json(name = "history") val history: List<JobResult.DocVEntry>?,
@Json(name = "image_links") val imageLinks: ImageLinks?,
) : Parcelable
@Json(name = "image_links") override val imageLinks: ImageLinks?,
) : JobStatusResponse, Parcelable

@Parcelize
@JsonClass(generateAdapter = true)
data class BiometricKycJobStatusResponse(
@Json(name = "timestamp") val timestamp: String,
@Json(name = "job_complete") val jobComplete: Boolean,
@Json(name = "job_success") val jobSuccess: Boolean,
@Json(name = "code") val code: Int,
@Json(name = "result") val result: JobResult?,
@Json(name = "timestamp") override val timestamp: String,
@Json(name = "job_complete") override val jobComplete: Boolean,
@Json(name = "job_success") override val jobSuccess: Boolean,
@Json(name = "code") override val code: Int,
@Json(name = "result") override val result: JobResult?,
@Json(name = "history") val history: List<JobResult.BiometricKycEntry>?,
@Json(name = "image_links") val imageLinks: ImageLinks?,
) : Parcelable
@Json(name = "image_links") override val imageLinks: ImageLinks?,
) : JobStatusResponse, Parcelable

/**
* The job result might sometimes be a freeform text field instead of an object (i.e. when the
Expand Down
7 changes: 4 additions & 3 deletions lib/src/main/java/com/smileidentity/models/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
package com.smileidentity.models

import android.os.Parcelable
import com.smileidentity.util.randomJobId
import com.smileidentity.util.randomUserId
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import kotlinx.parcelize.Parcelize
import java.util.UUID

@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
@Parcelize
Expand All @@ -30,8 +31,8 @@ class SmileIDException(val details: Details) : Exception(details.message), Parce
@Parcelize
data class PartnerParams(
val jobType: JobType? = null,
val jobId: String = UUID.randomUUID().toString(),
val userId: String = UUID.randomUUID().toString(),
val jobId: String = randomJobId(),
val userId: String = randomUserId(),
val extras: Map<String, String> = mapOf(),
) : Parcelable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

fun calculateSignature(timestamp: String): String {
val apiKey = SmileID.apiKey ?: throw IllegalStateException("API key not set")
val apiKey = SmileID.apiKey ?: throw IllegalStateException(
"""API key not set. If using the authToken from smile_config.json, ensure you have set the
|signature/timestamp properties on the request from the values returned by
|SmileID.authenticate.signature/timestamp
""".trimMargin().replace("\n", ""),
)
val hashContent = timestamp + SmileID.config.partnerId + "sid_request"
return hashContent.encode().hmacSha256(apiKey.encode()).base64()
}
Expand Down
Loading

0 comments on commit 1beef41

Please sign in to comment.