Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3DS2 Analytics Delegate #9430

Open
wants to merge 6 commits into
base: feature/3ds2-2.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,8 @@ class MainActivity : AppCompatActivity() {
StripeThreeDs2ServiceImpl(
this,
enableLogging = true,
workContext = Dispatchers.IO
workContext = Dispatchers.IO,
analyticsDelegate = null
)
}

Expand Down
29 changes: 24 additions & 5 deletions 3ds2sdk/api/3ds2sdk.api
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,16 @@ public final class com/stripe/android/stripe3ds2/security/StripeEphemeralKeyPair
public fun generate ()Ljava/security/KeyPair;
}

public final class com/stripe/android/stripe3ds2/service/AnalyticsSingleton {
public static final field Companion Lcom/stripe/android/stripe3ds2/service/AnalyticsSingleton$Companion;
public final fun getAnalyticsDelegate ()Lcom/stripe/android/stripe3ds2/utils/AnalyticsDelegate;
public final fun setAnalyticsDelegate (Lcom/stripe/android/stripe3ds2/utils/AnalyticsDelegate;)V
}

public final class com/stripe/android/stripe3ds2/service/AnalyticsSingleton$Companion {
public final fun getInstance ()Lcom/stripe/android/stripe3ds2/service/AnalyticsSingleton;
}

public abstract interface class com/stripe/android/stripe3ds2/service/StripeThreeDs2Service {
public abstract fun cleanup ()V
public abstract fun createTransaction (Lcom/stripe/android/stripe3ds2/transaction/SdkTransactionId;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/util/List;Ljava/security/PublicKey;Ljava/lang/String;Lcom/stripe/android/stripe3ds2/init/ui/StripeUiCustomization;)Lcom/stripe/android/stripe3ds2/transaction/Transaction;
Expand All @@ -408,11 +418,11 @@ public abstract interface class com/stripe/android/stripe3ds2/service/StripeThre
}

public final class com/stripe/android/stripe3ds2/service/StripeThreeDs2ServiceImpl : com/stripe/android/stripe3ds2/service/StripeThreeDs2Service {
public fun <init> (Landroid/content/Context;Ljava/lang/String;ZLkotlin/coroutines/CoroutineContext;)V
public synthetic fun <init> (Landroid/content/Context;Ljava/lang/String;ZLkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Landroid/content/Context;Lkotlin/coroutines/CoroutineContext;)V
public fun <init> (Landroid/content/Context;ZLkotlin/coroutines/CoroutineContext;)V
public synthetic fun <init> (Landroid/content/Context;ZLkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Landroid/content/Context;Lcom/stripe/android/stripe3ds2/utils/AnalyticsDelegate;Lkotlin/coroutines/CoroutineContext;)V
public fun <init> (Landroid/content/Context;Ljava/lang/String;ZLcom/stripe/android/stripe3ds2/utils/AnalyticsDelegate;Lkotlin/coroutines/CoroutineContext;)V
public synthetic fun <init> (Landroid/content/Context;Ljava/lang/String;ZLcom/stripe/android/stripe3ds2/utils/AnalyticsDelegate;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Landroid/content/Context;ZLcom/stripe/android/stripe3ds2/utils/AnalyticsDelegate;Lkotlin/coroutines/CoroutineContext;)V
public synthetic fun <init> (Landroid/content/Context;ZLcom/stripe/android/stripe3ds2/utils/AnalyticsDelegate;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun cleanup ()V
public fun createTransaction (Lcom/stripe/android/stripe3ds2/transaction/SdkTransactionId;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/util/List;Ljava/security/PublicKey;Ljava/lang/String;Lcom/stripe/android/stripe3ds2/init/ui/StripeUiCustomization;)Lcom/stripe/android/stripe3ds2/transaction/Transaction;
public fun getPublicKey (Ljava/lang/String;)Ljava/security/PublicKey;
Expand Down Expand Up @@ -1393,6 +1403,15 @@ public final class com/stripe/android/stripe3ds2/transactions/UiType : java/lang
public static fun values ()[Lcom/stripe/android/stripe3ds2/transactions/UiType;
}

public abstract interface class com/stripe/android/stripe3ds2/utils/AnalyticsDelegate {
public abstract fun cancelButtonTappedWithTransactionId (Ljava/lang/String;)V
public abstract fun didReceiveChallengeResponseWithTransactionId (Ljava/lang/String;Ljava/lang/String;)V
public abstract fun oobContinueButtonTappedWithTransactionID (Ljava/lang/String;)V
public abstract fun oobFlowDidPause (Ljava/lang/String;)V
public abstract fun oobFlowDidResume (Ljava/lang/String;)V
public abstract fun otpSubmitButtonTappedWithTransactionID (Ljava/lang/String;)V
}

public final class com/stripe/android/stripe3ds2/utils/CustomizeUtils {
public static final field INSTANCE Lcom/stripe/android/stripe3ds2/utils/CustomizeUtils;
public final fun buildStyledText (Landroid/content/Context;Ljava/lang/String;Lcom/stripe/android/stripe3ds2/init/ui/Customization;)Landroid/text/SpannableString;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.stripe.android.stripe3ds2.transaction.MessageVersionRegistry
import com.stripe.android.stripe3ds2.transaction.SdkTransactionId
import com.stripe.android.stripe3ds2.transaction.Transaction
import com.stripe.android.stripe3ds2.transaction.TransactionFactory
import com.stripe.android.stripe3ds2.utils.AnalyticsDelegate
import com.stripe.android.stripe3ds2.utils.ImageCache
import com.stripe.android.stripe3ds2.views.Brand
import java.security.PublicKey
Expand All @@ -36,30 +37,36 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
private val errorReporter: ErrorReporter,
private val transactionFactory: TransactionFactory,
private val publicKeyFactory: PublicKeyFactory,
private val analyticsDelegate: AnalyticsDelegate?,
override val warnings: List<Warning>,
) : StripeThreeDs2Service {

@JvmOverloads
constructor(
context: Context,
enableLogging: Boolean = false,
analyticsDelegate: AnalyticsDelegate?,
workContext: CoroutineContext
) : this(
context,
STRIPE_SDK_REFERENCE_NUMBER,
enableLogging,
analyticsDelegate,
workContext
)

constructor(
context: Context,
sdkReferenceNumber: String,
enableLogging: Boolean = false,
analyticsDelegate: AnalyticsDelegate?,
workContext: CoroutineContext
) : this(
context,
ImageCache.Default,
sdkReferenceNumber,
enableLogging,
analyticsDelegate,
workContext = workContext
)

Expand All @@ -68,6 +75,7 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
imageCache: ImageCache,
sdkReferenceNumber: String,
enableLogging: Boolean,
analyticsDelegate: AnalyticsDelegate?,
workContext: CoroutineContext
) : this(
context,
Expand All @@ -78,6 +86,7 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
context = context.applicationContext,
logger = Logger.get(enableLogging)
),
analyticsDelegate,
workContext
)

Expand All @@ -86,6 +95,7 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
imageCache: ImageCache,
sdkReferenceNumber: String,
errorReporter: ErrorReporter,
analyticsDelegate: AnalyticsDelegate?,
workContext: CoroutineContext
) : this(
context,
Expand All @@ -95,6 +105,7 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
StripeEphemeralKeyPairGenerator(errorReporter),
DefaultSecurityChecker(),
MessageVersionRegistry(),
analyticsDelegate,
DefaultAppInfoRepository(context, workContext),
workContext
)
Expand All @@ -107,6 +118,7 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
ephemeralKeyPairGenerator: EphemeralKeyPairGenerator,
securityChecker: SecurityChecker,
messageVersionRegistry: MessageVersionRegistry,
analyticsDelegate: AnalyticsDelegate?,
appInfoRepository: AppInfoRepository,
workContext: CoroutineContext
) : this(
Expand All @@ -132,10 +144,15 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
ephemeralKeyPairGenerator,
sdkReferenceNumber,
),
analyticsDelegate = analyticsDelegate,
warnings = securityChecker.getWarnings(),
publicKeyFactory = PublicKeyFactory(context, errorReporter)
)

init {
AnalyticsSingleton.getInstance().analyticsDelegate = analyticsDelegate
}

@Throws(InvalidInputException::class, SDKRuntimeException::class)
override fun createTransaction(
sdkTransactionId: SdkTransactionId,
Expand Down Expand Up @@ -200,3 +217,18 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
private const val STRIPE_SDK_REFERENCE_NUMBER = "3DS_LOA_SDK_STIN_020100_00142"
}
}

class AnalyticsSingleton private constructor() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work because it will not survive process death. I discussed with the team and we decided a java service provider will be the best approach. Can you remove this and replace with a ServiceLoader?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes sorry, thought I posted a comment about the Singleton to ask how your team wanted to handle that. 👍🏼


var analyticsDelegate: AnalyticsDelegate? = null

companion object {

@Volatile private var instance: AnalyticsSingleton? = null // Volatile modifier is necessary

fun getInstance() =
instance ?: synchronized(this) { // synchronized to avoid concurrency problem
instance ?: AnalyticsSingleton().also { instance = it }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.stripe.android.stripe3ds2.utils

interface AnalyticsDelegate {
fun didReceiveChallengeResponseWithTransactionId(transactionId: String, flow: String)

fun cancelButtonTappedWithTransactionId(transactionId: String)

fun otpSubmitButtonTappedWithTransactionID(transactionId: String)

fun oobContinueButtonTappedWithTransactionID(transactionId: String)

fun oobFlowDidPause(transactionId: String)

fun oobFlowDidResume(transactionId: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.stripe.android.stripe3ds2.init.ui.UiCustomization
import com.stripe.android.stripe3ds2.observability.DefaultErrorReporter
import com.stripe.android.stripe3ds2.observability.ErrorReporter
import com.stripe.android.stripe3ds2.observability.Stripe3ds2ErrorReporterConfig
import com.stripe.android.stripe3ds2.service.AnalyticsSingleton
import com.stripe.android.stripe3ds2.transaction.ChallengeAction
import com.stripe.android.stripe3ds2.transaction.ChallengeActionHandler
import com.stripe.android.stripe3ds2.transaction.ChallengeResult
Expand Down Expand Up @@ -93,11 +94,14 @@ class ChallengeActivity : AppCompatActivity() {
}

private var progressDialog: Dialog? = null
private var currentUITypeCode = ""

private var currentChallengeResponseData: ChallengeResponseData? = null
private val analyticsDelegate = AnalyticsSingleton.getInstance().analyticsDelegate

override fun onCreate(savedInstanceState: Bundle?) {
supportFragmentManager.fragmentFactory = ChallengeFragmentFactory(
uiCustomization = viewArgs.uiCustomization,
analyticsDelegate = analyticsDelegate,
transactionTimer = transactionTimer,
errorRequestExecutor = errorRequestExecutor,
errorReporter = errorReporter,
Expand Down Expand Up @@ -158,7 +162,7 @@ class ChallengeActivity : AppCompatActivity() {
if (cres != null) {
startFragment(cres)

currentUITypeCode = cres.uiType?.code.orEmpty()
currentChallengeResponseData = cres
}
}

Expand All @@ -170,13 +174,15 @@ class ChallengeActivity : AppCompatActivity() {
if (isTimeout == true) {
viewModel.onFinish(
ChallengeResult.Timeout(
currentUITypeCode,
currentChallengeResponseData?.uiType?.code.orEmpty(),
viewArgs.cresData.uiType,
viewArgs.intentData
)
)
}
}

currentChallengeResponseData = viewArgs.cresData
}

private fun startFragment(
Expand Down Expand Up @@ -215,7 +221,11 @@ class ChallengeActivity : AppCompatActivity() {
override fun onPause() {
super.onPause()
viewModel.shouldRefreshUi = true
viewModel.shouldAutoSubmitOOB = UiType.fromCode(currentUITypeCode) == UiType.OutOfBand

val uiType = UiType.fromCode(currentChallengeResponseData?.uiType?.code.orEmpty())
val isOutOfBandChallenge = uiType == UiType.OutOfBand
viewModel.shouldAutoSubmitOOB = isOutOfBandChallenge

dismissKeyboard()
}

Expand All @@ -240,6 +250,10 @@ class ChallengeActivity : AppCompatActivity() {
cancelButton?.setOnClickListener {
cancelButton.isClickable = false
viewModel.submit(ChallengeAction.Cancel)

currentChallengeResponseData?.let {
analyticsDelegate?.cancelButtonTappedWithTransactionId(it.serverTransId)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ import com.stripe.android.stripe3ds2.transactions.ChallengeRequestData
import com.stripe.android.stripe3ds2.transactions.ChallengeResponseData
import com.stripe.android.stripe3ds2.transactions.ErrorData
import com.stripe.android.stripe3ds2.transactions.UiType
import com.stripe.android.stripe3ds2.utils.AnalyticsDelegate
import kotlin.coroutines.CoroutineContext

internal class ChallengeFragment(
private val uiCustomization: StripeUiCustomization,
private val analyticsDelegate: AnalyticsDelegate?,
private val transactionTimer: TransactionTimer,
private val errorRequestExecutor: ErrorRequestExecutor,
private val errorReporter: ErrorReporter,
Expand Down Expand Up @@ -119,7 +121,9 @@ internal class ChallengeFragment(
)
return
}

cresData = nullableCres
analyticsDelegate?.didReceiveChallengeResponseWithTransactionId(cresData.serverTransId, uiTypeCode)

_viewBinding = StripeChallengeFragmentBinding.bind(view)

Expand Down Expand Up @@ -147,6 +151,22 @@ internal class ChallengeFragment(
configureInformationZoneView()
}

override fun onResume() {
super.onResume()

if (cresData.uiType == UiType.OutOfBand) {
analyticsDelegate?.oobFlowDidResume(cresData.serverTransId)
}
}

override fun onPause() {
super.onPause()

if (cresData.uiType == UiType.OutOfBand) {
analyticsDelegate?.oobFlowDidPause(cresData.serverTransId)
}
}

override fun onDestroyView() {
super.onDestroyView()
_viewBinding = null
Expand Down Expand Up @@ -269,6 +289,15 @@ internal class ChallengeFragment(

challengeZoneView.setSubmitButtonClickListener {
viewModel.onSubmitClicked(challengeAction)

when (cresData.uiType) {
UiType.Text ->
analyticsDelegate?.otpSubmitButtonTappedWithTransactionID(cresData.serverTransId)
UiType.OutOfBand ->
analyticsDelegate?.oobContinueButtonTappedWithTransactionID((cresData.serverTransId))
UiType.SingleSelect, UiType.MultiSelect, UiType.Html -> { }
null -> { }
}
}
challengeZoneView.setResendButtonClickListener {
viewModel.submit(ChallengeAction.Resend)
Expand Down Expand Up @@ -340,6 +369,8 @@ internal class ChallengeFragment(
} else {
viewModel.onNextScreen(cresData)
}

analyticsDelegate?.didReceiveChallengeResponseWithTransactionId(cresData.serverTransId, uiTypeCode)
}

private fun onError(data: ErrorData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import com.stripe.android.stripe3ds2.transaction.ErrorRequestExecutor
import com.stripe.android.stripe3ds2.transaction.IntentData
import com.stripe.android.stripe3ds2.transaction.TransactionTimer
import com.stripe.android.stripe3ds2.transactions.UiType
import com.stripe.android.stripe3ds2.utils.AnalyticsDelegate
import kotlin.coroutines.CoroutineContext

internal class ChallengeFragmentFactory(
private val uiCustomization: StripeUiCustomization,
private val analyticsDelegate: AnalyticsDelegate?,
private val transactionTimer: TransactionTimer,
private val errorRequestExecutor: ErrorRequestExecutor,
private val errorReporter: ErrorReporter,
Expand All @@ -26,6 +28,7 @@ internal class ChallengeFragmentFactory(
ChallengeFragment::class.java.name -> {
ChallengeFragment(
uiCustomization,
analyticsDelegate,
transactionTimer,
errorRequestExecutor,
errorReporter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class StripeThreeDs2ServiceImplTest {
errorReporter,
transactionFactory,
publicKeyFactory,
null,
warnings
)
}
Expand Down
Loading