Skip to content

Commit

Permalink
Add Link status to analytics events (#5386)
Browse files Browse the repository at this point in the history
  • Loading branch information
brnunes-stripe authored Aug 10, 2022
1 parent 1990573 commit 23038ce
Show file tree
Hide file tree
Showing 17 changed files with 313 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class CookieStore @Inject internal constructor(
private val store: EncryptedStore
) {

constructor(context: Context): this(EncryptedStore(context))
constructor(context: Context) : this(EncryptedStore(context))

/**
* Clear all local data.
Expand Down
14 changes: 11 additions & 3 deletions paymentsheet/api/paymentsheet.api
Original file line number Diff line number Diff line change
Expand Up @@ -878,11 +878,19 @@ public final class com/stripe/android/paymentsheet/addresselement/analytics/Defa
}

public final class com/stripe/android/paymentsheet/analytics/DefaultEventReporter_Factory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lcom/stripe/android/paymentsheet/analytics/DefaultEventReporter_Factory;
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lcom/stripe/android/paymentsheet/analytics/DefaultEventReporter_Factory;
public fun get ()Lcom/stripe/android/paymentsheet/analytics/DefaultEventReporter;
public synthetic fun get ()Ljava/lang/Object;
public static fun newInstance (Lcom/stripe/android/paymentsheet/analytics/EventReporter$Mode;Lcom/stripe/android/core/networking/AnalyticsRequestExecutor;Lcom/stripe/android/networking/PaymentAnalyticsRequestFactory;Lkotlin/coroutines/CoroutineContext;)Lcom/stripe/android/paymentsheet/analytics/DefaultEventReporter;
public static fun newInstance (Lcom/stripe/android/paymentsheet/analytics/EventReporter$Mode;Lcom/stripe/android/core/networking/AnalyticsRequestExecutor;Lcom/stripe/android/networking/PaymentAnalyticsRequestFactory;Lcom/stripe/android/paymentsheet/analytics/EventTimeProvider;Lkotlin/coroutines/CoroutineContext;)Lcom/stripe/android/paymentsheet/analytics/DefaultEventReporter;
}

public final class com/stripe/android/paymentsheet/analytics/EventTimeProvider_Factory : dagger/internal/Factory {
public fun <init> ()V
public static fun create ()Lcom/stripe/android/paymentsheet/analytics/EventTimeProvider_Factory;
public fun get ()Lcom/stripe/android/paymentsheet/analytics/EventTimeProvider;
public synthetic fun get ()Ljava/lang/Object;
public static fun newInstance ()Lcom/stripe/android/paymentsheet/analytics/EventTimeProvider;
}

public final class com/stripe/android/paymentsheet/databinding/ActivityPaymentOptionsBinding : androidx/viewbinding/ViewBinding {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ internal abstract class BaseAddPaymentMethodFragment : Fragment() {
}
}

sheetViewModel.eventReporter.onShowNewPaymentOptionForm()
sheetViewModel.eventReporter.onShowNewPaymentOptionForm(
linkEnabled = sheetViewModel.isLinkEnabled.value ?: false,
activeLinkSession = sheetViewModel.activeLinkSession.value ?: false
)
}

private fun setupRecyclerView(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ internal abstract class BasePaymentMethodsListFragment(
this.config = nullableConfig

setHasOptionsMenu(!sheetViewModel.paymentMethods.value.isNullOrEmpty())
sheetViewModel.eventReporter.onShowExistingPaymentOptions()
sheetViewModel.eventReporter.onShowExistingPaymentOptions(
linkEnabled = sheetViewModel.isLinkEnabled.value ?: false,
activeLinkSession = sheetViewModel.activeLinkSession.value ?: false
)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ internal class PaymentOptionsViewModel @Inject constructor(
stripeIntent.paymentMethodTypes.contains(PaymentMethod.Type.Link.code)
) {
viewModelScope.launch {
when (linkLauncher.setup(stripeIntent, this)) {
val accountStatus = linkLauncher.setup(stripeIntent, this)
when (accountStatus) {
AccountStatus.Verified,
AccountStatus.VerificationStarted,
AccountStatus.NeedsVerification -> {
Expand All @@ -178,6 +179,7 @@ internal class PaymentOptionsViewModel @Inject constructor(
}
AccountStatus.SignedOut -> {}
}
activeLinkSession.value = accountStatus == AccountStatus.Verified
_isLinkEnabled.value = true
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,8 @@ internal class PaymentSheetViewModel @Inject internal constructor(
stripeIntent.paymentMethodTypes.contains(PaymentMethod.Type.Link.code)
) {
viewModelScope.launch {
when (linkLauncher.setup(stripeIntent, this)) {
val accountStatus = linkLauncher.setup(stripeIntent, this)
when (accountStatus) {
AccountStatus.Verified -> launchLink()
AccountStatus.VerificationStarted,
AccountStatus.NeedsVerification -> {
Expand All @@ -449,6 +450,7 @@ internal class PaymentSheetViewModel @Inject internal constructor(
}
AccountStatus.SignedOut -> {}
}
activeLinkSession.value = accountStatus == AccountStatus.Verified
_isLinkEnabled.value = true
}
} else {
Expand Down Expand Up @@ -548,7 +550,9 @@ internal class PaymentSheetViewModel @Inject internal constructor(
}
}
else -> {
eventReporter.onPaymentFailure(selection.value)
if (paymentResult is PaymentResult.Failed) {
eventReporter.onPaymentFailure(selection.value)
}

runCatching {
stripeIntentValidator.requireValid(stripeIntent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ internal class DefaultEventReporter @Inject internal constructor(
private val mode: EventReporter.Mode,
private val analyticsRequestExecutor: AnalyticsRequestExecutor,
private val paymentAnalyticsRequestFactory: PaymentAnalyticsRequestFactory,
private val eventTimeProvider: EventTimeProvider,
@IOContext private val workContext: CoroutineContext
) : EventReporter {
private var paymentSheetShownMillis: Long? = null

override fun onInit(configuration: PaymentSheet.Configuration?) {
fireEvent(
Expand All @@ -36,18 +38,24 @@ internal class DefaultEventReporter @Inject internal constructor(
)
}

override fun onShowExistingPaymentOptions() {
override fun onShowExistingPaymentOptions(linkEnabled: Boolean, activeLinkSession: Boolean) {
paymentSheetShownMillis = eventTimeProvider.currentTimeMillis()
fireEvent(
PaymentSheetEvent.ShowExistingPaymentOptions(
mode = mode
mode = mode,
linkEnabled = linkEnabled,
activeLinkSession = activeLinkSession
)
)
}

override fun onShowNewPaymentOptionForm() {
override fun onShowNewPaymentOptionForm(linkEnabled: Boolean, activeLinkSession: Boolean) {
paymentSheetShownMillis = eventTimeProvider.currentTimeMillis()
fireEvent(
PaymentSheetEvent.ShowNewPaymentOptionForm(
mode = mode
mode = mode,
linkEnabled = linkEnabled,
activeLinkSession = activeLinkSession
)
)
}
Expand All @@ -66,6 +74,7 @@ internal class DefaultEventReporter @Inject internal constructor(
PaymentSheetEvent.Payment(
mode = mode,
paymentSelection = paymentSelection,
durationMillis = durationMillisFrom(paymentSheetShownMillis),
result = PaymentSheetEvent.Payment.Result.Success
)
)
Expand All @@ -76,6 +85,7 @@ internal class DefaultEventReporter @Inject internal constructor(
PaymentSheetEvent.Payment(
mode = mode,
paymentSelection = paymentSelection,
durationMillis = durationMillisFrom(paymentSheetShownMillis),
result = PaymentSheetEvent.Payment.Result.Failure
)
)
Expand All @@ -97,4 +107,8 @@ internal class DefaultEventReporter @Inject internal constructor(
)
}
}

private fun durationMillisFrom(start: Long?) = start?.let {
eventTimeProvider.currentTimeMillis() - it
}?.takeIf { it > 0 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ internal interface EventReporter {

fun onDismiss()

fun onShowExistingPaymentOptions()
fun onShowExistingPaymentOptions(linkEnabled: Boolean, activeLinkSession: Boolean)

fun onShowNewPaymentOptionForm()
fun onShowNewPaymentOptionForm(linkEnabled: Boolean, activeLinkSession: Boolean)

fun onSelectPaymentOption(paymentSelection: PaymentSelection)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.stripe.android.paymentsheet.analytics

import javax.inject.Inject

internal class EventTimeProvider @Inject constructor() {
fun currentTimeMillis() = System.currentTimeMillis()
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,27 @@ internal sealed class PaymentSheetEvent : AnalyticsEvent {
}

class ShowNewPaymentOptionForm(
mode: EventReporter.Mode
mode: EventReporter.Mode,
linkEnabled: Boolean,
activeLinkSession: Boolean
) : PaymentSheetEvent() {
override val eventName: String = formatEventName(mode, "sheet_newpm_show")
override val additionalParams: Map<String, Any> = mapOf()
override val additionalParams: Map<String, Any> = mapOf(
"link_enabled" to linkEnabled,
"active_link_session" to activeLinkSession
)
}

class ShowExistingPaymentOptions(
mode: EventReporter.Mode
mode: EventReporter.Mode,
linkEnabled: Boolean,
activeLinkSession: Boolean
) : PaymentSheetEvent() {
override val eventName: String = formatEventName(mode, "sheet_savedpm_show")
override val additionalParams: Map<String, Any> = mapOf()
override val additionalParams: Map<String, Any> = mapOf(
"link_enabled" to linkEnabled,
"active_link_session" to activeLinkSession
)
}

class SelectPaymentOption(
Expand All @@ -113,11 +123,14 @@ internal sealed class PaymentSheetEvent : AnalyticsEvent {
class Payment(
mode: EventReporter.Mode,
result: Result,
durationMillis: Long?,
paymentSelection: PaymentSelection?
) : PaymentSheetEvent() {
override val eventName: String =
formatEventName(mode, "payment_${analyticsValue(paymentSelection)}_$result")
override val additionalParams: Map<String, Any> = mapOf()
override val additionalParams: Map<String, Any> = durationMillis?.let {
mapOf("duration" to it / 1000f)
} ?: mapOf()

enum class Result(private val code: String) {
Success("success"),
Expand All @@ -139,6 +152,8 @@ internal sealed class PaymentSheetEvent : AnalyticsEvent {
) = when (paymentSelection) {
PaymentSelection.GooglePay -> "googlepay"
is PaymentSelection.Saved -> "savedpm"
PaymentSelection.Link,
is PaymentSelection.New.LinkInline -> "link"
is PaymentSelection.New -> "newpm"
else -> "unknown"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import android.os.Parcelable
import androidx.activity.result.ActivityResultCaller
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ViewModelStoreOwner
import com.stripe.android.PaymentConfiguration
import com.stripe.android.core.injection.ENABLE_LOGGING
Expand Down Expand Up @@ -134,9 +132,8 @@ internal class DefaultFlowController @Inject internal constructor(

init {
lifecycleOwner.lifecycle.addObserver(
object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate() {
object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
paymentLauncher = paymentLauncherFactory.create(
{ lazyPaymentConfiguration.get().publishableKey },
{ lazyPaymentConfiguration.get().stripeAccountId },
Expand All @@ -147,8 +144,7 @@ internal class DefaultFlowController @Inject internal constructor(
)
}

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
override fun onDestroy(owner: LifecycleOwner) {
paymentLauncher = null
}
}
Expand Down Expand Up @@ -316,7 +312,8 @@ internal class DefaultFlowController @Inject internal constructor(
}.fold(
onSuccess = { initData ->
val paymentSelection = PaymentSelection.Saved(
googlePayResult.paymentMethod
googlePayResult.paymentMethod,
isGooglePay = true
)
viewModel.paymentSelection = paymentSelection
confirmPaymentSelection(
Expand Down Expand Up @@ -422,13 +419,29 @@ internal class DefaultFlowController @Inject internal constructor(
}

internal fun onPaymentResult(paymentResult: PaymentResult) {
logPaymentResult(paymentResult)
lifecycleScope.launch {
paymentResultCallback.onPaymentSheetResult(
paymentResult.convertToPaymentSheetResult()
)
}
}

private fun logPaymentResult(paymentResult: PaymentResult?) {
when (paymentResult) {
is PaymentResult.Completed -> {
if ((viewModel.paymentSelection as? PaymentSelection.Saved)?.isGooglePay == true) {
// Google Pay is treated as a saved PM after confirmation
eventReporter.onPaymentSuccess(PaymentSelection.GooglePay)
} else {
eventReporter.onPaymentSuccess(viewModel.paymentSelection)
}
}
is PaymentResult.Failed -> eventReporter.onPaymentFailure(viewModel.paymentSelection)
else -> {}
}
}

private fun confirmLink(
paymentSelection: PaymentSelection,
initData: InitData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ sealed class PaymentSelection : Parcelable {
@Parcelize
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class Saved(
val paymentMethod: PaymentMethod
val paymentMethod: PaymentMethod,
internal val isGooglePay: Boolean = false
) : PaymentSelection()

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
Expand Down Expand Up @@ -213,7 +214,7 @@ internal class PaymentSheetAddPaymentMethodFragmentTest : PaymentSheetViewModelT
fun `started fragment should report onShowNewPaymentOptionForm() event`() {
createFragment { _, _, _ ->
idleLooper()
verify(eventReporter).onShowNewPaymentOptionForm()
verify(eventReporter).onShowNewPaymentOptionForm(any(), any())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.robolectric.RobolectricTestRunner
Expand Down Expand Up @@ -192,7 +193,7 @@ internal class PaymentSheetListFragmentTest : PaymentSheetViewModelTestInjection
@Test
fun `started fragment should report onShowExistingPaymentOptions() event`() {
createScenario().onFragment {
verify(eventReporter).onShowExistingPaymentOptions()
verify(eventReporter).onShowExistingPaymentOptions(any(), any())
}
}

Expand Down
Loading

0 comments on commit 23038ce

Please sign in to comment.