Skip to content

Commit

Permalink
[Identity] Example app UX change for phone verification
Browse files Browse the repository at this point in the history
  • Loading branch information
ccen-stripe committed May 15, 2023
1 parent 9337dfb commit 20725a0
Show file tree
Hide file tree
Showing 11 changed files with 891 additions and 459 deletions.
1 change: 1 addition & 0 deletions identity-example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ android {

dependencies {
implementation project(':identity')
implementation project(':stripe-ui-core')

implementation libs.accompanist.themeAdapter
implementation libs.androidx.appCompat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import androidx.lifecycle.liveData
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitStringResult
import com.github.kittinunf.result.Result
import com.stripe.android.identity.example.ui.IdentitySubmissionState
import com.stripe.android.identity.example.ui.VerificationType
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json

internal class IdentityExampleViewModel(application: Application) : AndroidViewModel(application) {
@OptIn(ExperimentalSerializationApi::class)
private val json by lazy {
Json {
ignoreUnknownKeys = true
Expand All @@ -20,39 +23,144 @@ internal class IdentityExampleViewModel(application: Application) : AndroidViewM
}

fun postForResult(
type: VerificationType,
allowDrivingLicense: Boolean,
allowPassport: Boolean,
allowId: Boolean,
requireLiveCapture: Boolean,
requireId: Boolean,
requireSelfie: Boolean,
requireAddress: Boolean
submissionState: IdentitySubmissionState
) = liveData {
val result = Fuel.post(EXAMPLE_BACKEND_URL)
.header("content-type", "application/json")
.body(
json.encodeToString(
VerificationSessionCreationRequest.serializer(),
VerificationSessionCreationRequest(
type = type.value,
type = submissionState.verificationType.value,
options =
if (type == VerificationType.DOCUMENT) {
VerificationSessionCreationRequest.Options(
document = VerificationSessionCreationRequest.Document(
requireIdNumber = requireId,
requireMatchingSelfie = requireSelfie,
requireLiveCapture = requireLiveCapture,
requireAddress = requireAddress,
allowedTypes = mutableListOf<String>().also {
if (allowDrivingLicense) it.add(DRIVING_LICENSE)
if (allowPassport) it.add(PASSPORT)
if (allowId) it.add(ID_CARD)
}
)
)
} else {
null
when (submissionState.verificationType) {
VerificationType.DOCUMENT -> {
if (submissionState.requirePhoneVerification == true) {
VerificationSessionCreationRequest.Options(
document = VerificationSessionCreationRequest.Options.Document(
requireIdNumber = submissionState.requireId,
requireMatchingSelfie = submissionState.requireSelfie,
requireLiveCapture = submissionState.requireLiveCapture,
requireAddress = submissionState.requireAddress,
allowedTypes = mutableListOf<DocumentType>().also {
if (submissionState.allowDrivingLicense) {
it.add(
DocumentType.DrivingLicense
)
}
if (submissionState.allowPassport) {
it.add(
DocumentType.Passport
)
}
if (submissionState.allowId) it.add(DocumentType.IdCard)
}
),
phone = VerificationSessionCreationRequest.Options.Phone(
requireVerification = true
)
)
} else {
VerificationSessionCreationRequest.Options(
document = VerificationSessionCreationRequest.Options.Document(
requireIdNumber = submissionState.requireId,
requireMatchingSelfie = submissionState.requireSelfie,
requireLiveCapture = submissionState.requireLiveCapture,
requireAddress = submissionState.requireAddress,
allowedTypes = mutableListOf<DocumentType>().also {
if (submissionState.allowDrivingLicense) {
it.add(
DocumentType.DrivingLicense
)
}
if (submissionState.allowPassport) {
it.add(
DocumentType.Passport
)
}
if (submissionState.allowId) it.add(DocumentType.IdCard)
}
)
)
}
}
VerificationType.PHONE -> {
if (submissionState.useDocumentFallback == true) {
VerificationSessionCreationRequest.Options(
document = VerificationSessionCreationRequest.Options.Document(
allowedTypes = listOf(DocumentType.DrivingLicense)
),
phoneRecords = VerificationSessionCreationRequest.Options.PhoneRecords(
fallback = Fallback.Document
),
phoneOtp = VerificationSessionCreationRequest.Options.PhoneOTP(
check = requireNotNull(submissionState.phoneOtpCheck)
)
)
} else {
null
}
}
VerificationType.ID_NUMBER -> {
if (submissionState.requirePhoneVerification == true) {
VerificationSessionCreationRequest.Options(
phone = VerificationSessionCreationRequest.Options.Phone(
requireVerification = true
)
)
} else {
null
}
}
VerificationType.ADDRESS -> {
if (submissionState.requirePhoneVerification == true) {
VerificationSessionCreationRequest.Options(
phone = VerificationSessionCreationRequest.Options.Phone(
requireVerification = true
)
)
} else {
null
}
}
},
providedDetails = when (submissionState.verificationType) {
VerificationType.DOCUMENT -> {
if (submissionState.requirePhoneVerification == true) {
VerificationSessionCreationRequest.ProvidedDetails(
phone = requireNotNull(
submissionState.providedPhoneNumber
)
)
} else {
null
}
}
VerificationType.ADDRESS -> {
if (submissionState.requirePhoneVerification == true) {
VerificationSessionCreationRequest.ProvidedDetails(
phone = requireNotNull(
submissionState.providedPhoneNumber
)
)
} else {
null
}
}
VerificationType.ID_NUMBER -> {
if (submissionState.requirePhoneVerification == true) {
VerificationSessionCreationRequest.ProvidedDetails(
phone = requireNotNull(
submissionState.providedPhoneNumber
)
)
} else {
null
}
}
VerificationType.PHONE -> {
null
}
}
)
)
Expand All @@ -75,11 +183,7 @@ internal class IdentityExampleViewModel(application: Application) : AndroidViewM
}

private companion object {
const val DRIVING_LICENSE = "driving_license"
const val PASSPORT = "passport"
const val ID_CARD = "id_card"

const val EXAMPLE_BACKEND_URL =
"https://stripe-mobile-identity-verification-playground.glitch.me/create-verification-session"
"https://reflective-fossil-rib.glitch.me/create-verification-session"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,80 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class VerificationSessionCreationResponse(
internal data class VerificationSessionCreationResponse(
@SerialName("client_secret") val clientSecret: String,
@SerialName("ephemeral_key_secret") val ephemeralKeySecret: String,
@SerialName("id") val verificationSessionId: String,
@SerialName("url") val url: String
)

@Serializable
data class VerificationSessionCreationRequest(
internal data class VerificationSessionCreationRequest(
@SerialName("type") val type: String = "document",
@SerialName("options") val options: Options? = null,
@SerialName("type") val type: String = "document"
@SerialName("provided_details") val providedDetails: ProvidedDetails? = null
) {
@Serializable
data class Options(
@SerialName("document") val document: Document? = null
)
@SerialName("document") val document: Document? = null,
@SerialName("phone") val phone: Phone? = null,
@SerialName("phone_otp") val phoneOtp: PhoneOTP? = null,
@SerialName("phone_records") val phoneRecords: PhoneRecords? = null
) {
@Serializable
data class Phone(
@SerialName("require_verification") val requireVerification: Boolean? = null
)

@Serializable
data class Document(
@SerialName("allowed_types") val allowedTypes: List<DocumentType>? = null,
@SerialName("require_id_number") val requireIdNumber: Boolean? = null,
@SerialName("require_live_capture") val requireLiveCapture: Boolean? = null,
@SerialName("require_matching_selfie") val requireMatchingSelfie: Boolean? = null,
@SerialName("require_address") val requireAddress: Boolean? = null
)

@Serializable
data class PhoneOTP(
@SerialName("check") val check: PhoneOTPCheck? = null,
)

@Serializable
data class PhoneRecords(
@SerialName("fallback") val fallback: Fallback? = null,
)
}

@Serializable
data class Document(
@SerialName("allowed_types") val allowedTypes: List<String>? = null,
@SerialName("require_id_number") val requireIdNumber: Boolean? = null,
@SerialName("require_live_capture") val requireLiveCapture: Boolean? = null,
@SerialName("require_matching_selfie") val requireMatchingSelfie: Boolean? = null,
@SerialName("require_address") val requireAddress: Boolean? = null
data class ProvidedDetails(
@SerialName("phone") val phone: String? = null
)
}

internal enum class DocumentType {
@SerialName("driving_license")
DrivingLicense,

@SerialName("passport")
Passport,

@SerialName("id_card")
IdCard
}

internal enum class Fallback {
@SerialName("document")
Document
}

internal enum class PhoneOTPCheck(val value: String) {
@SerialName("none")
None("none"),

@SerialName("attempt")
Attempt("attempt"),

@SerialName("required")
Required("required")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.stripe.android.identity.example.ui

import androidx.compose.runtime.Composable

@Composable
internal fun AddressUI() {
// no configuration for Address for now
}
Loading

0 comments on commit 20725a0

Please sign in to comment.