Skip to content

Commit 80b7823

Browse files
committed
Report event when card requires more than 16 digits
1 parent 6f12d7c commit 80b7823

File tree

11 files changed

+120
-5
lines changed

11 files changed

+120
-5
lines changed

payments-core/src/main/java/com/stripe/android/networking/PaymentAnalyticsEvent.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.stripe.android.networking
22

33
import androidx.annotation.Keep
4+
import androidx.annotation.RestrictTo
45
import com.stripe.android.core.networking.AnalyticsEvent
56

6-
internal enum class PaymentAnalyticsEvent(val code: String) : AnalyticsEvent {
7+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
8+
enum class PaymentAnalyticsEvent(val code: String) : AnalyticsEvent {
79
// Token
810
TokenCreate("token_creation"),
911

@@ -104,7 +106,10 @@ internal enum class PaymentAnalyticsEvent(val code: String) : AnalyticsEvent {
104106

105107
CardMetadataLoadedTooSlow("card_metadata_loaded_too_slow"),
106108
CardMetadataLoadFailure("card_metadata_load_failure"),
107-
CardMetadataMissingRange("card_metadata_missing_range");
109+
CardMetadataMissingRange("card_metadata_missing_range"),
110+
CardMetadataExpectedExtraDigitsButUserEntered16ThenSwitchedFields(
111+
"card_metadata.expected_extra_digits_but_user_entered_16_then_switched_fields"
112+
);
108113

109114
@Keep
110115
override fun toString(): String {

payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/CardNumberController.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import com.stripe.android.core.strings.ResolvableString
2525
import com.stripe.android.core.strings.resolvableString
2626
import com.stripe.android.model.AccountRange
2727
import com.stripe.android.model.CardBrand
28+
import com.stripe.android.networking.PaymentAnalyticsEvent
2829
import com.stripe.android.stripecardscan.cardscan.CardScanSheetResult
2930
import com.stripe.android.ui.core.R
31+
import com.stripe.android.ui.core.elements.events.LocalAnalyticsEventReporter
3032
import com.stripe.android.ui.core.elements.events.LocalCardBrandDisallowedReporter
3133
import com.stripe.android.ui.core.elements.events.LocalCardNumberCompletedEventReporter
3234
import com.stripe.android.uicore.elements.FieldError
@@ -45,6 +47,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
4547
import kotlinx.coroutines.flow.StateFlow
4648
import kotlinx.coroutines.flow.asStateFlow
4749
import kotlinx.coroutines.flow.collectLatest
50+
import kotlinx.coroutines.flow.combine
4851
import kotlinx.coroutines.flow.drop
4952
import kotlin.coroutines.CoroutineContext
5053
import com.stripe.android.R as PaymentsCoreR
@@ -368,9 +371,13 @@ internal class DefaultCardNumberController(
368371
) {
369372
val reporter = LocalCardNumberCompletedEventReporter.current
370373
val disallowedBrandReporter = LocalCardBrandDisallowedReporter.current
374+
val analyticsEventReporter = LocalAnalyticsEventReporter.current
371375

372376
// Remember the last state indicating whether it was a disallowed card brand error
373377
var lastLoggedCardBrand by rememberSaveable { mutableStateOf<CardBrand?>(null) }
378+
var hasReportedIncompleteCardNumberRequiringMoreThan16Digits by rememberSaveable {
379+
mutableStateOf(false)
380+
}
374381

375382
LaunchedEffect(Unit) {
376383
// Drop the set empty value & initial value
@@ -395,6 +402,27 @@ internal class DefaultCardNumberController(
395402
}
396403
}
397404

405+
LaunchedEffect(Unit) {
406+
combine(
407+
fieldState.drop(1),
408+
fieldValue,
409+
_hasFocus,
410+
) { state, fieldValue, hasFocus ->
411+
state is TextFieldStateConstants.Error.Incomplete &&
412+
!hasFocus &&
413+
!hasReportedIncompleteCardNumberRequiringMoreThan16Digits &&
414+
fieldValue.length == 16
415+
}.collectLatest {
416+
if (it) {
417+
analyticsEventReporter.onAnalyticsEvent(
418+
PaymentAnalyticsEvent.CardMetadataExpectedExtraDigitsButUserEntered16ThenSwitchedFields
419+
)
420+
421+
hasReportedIncompleteCardNumberRequiringMoreThan16Digits = true
422+
}
423+
}
424+
}
425+
398426
super.ComposeUI(
399427
enabled,
400428
field,
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.stripe.android.ui.core.elements.events
2+
3+
import androidx.annotation.RestrictTo
4+
import androidx.compose.runtime.staticCompositionLocalOf
5+
import com.stripe.android.core.networking.AnalyticsEvent
6+
import com.stripe.android.uicore.BuildConfig
7+
8+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
9+
fun interface AnalyticsEventReporter {
10+
fun onAnalyticsEvent(event: AnalyticsEvent)
11+
}
12+
13+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
14+
val LocalAnalyticsEventReporter =
15+
staticCompositionLocalOf<AnalyticsEventReporter> {
16+
EmptyAnalyticsEventReporter
17+
}
18+
19+
private object EmptyAnalyticsEventReporter : AnalyticsEventReporter {
20+
override fun onAnalyticsEvent(event: AnalyticsEvent) {
21+
if (BuildConfig.DEBUG) {
22+
error(
23+
"AnalyticsEventReporter.${event.eventName} was not reported"
24+
)
25+
}
26+
}
27+
}

paymentsheet/src/main/java/com/stripe/android/customersheet/CustomerSheetViewAction.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.stripe.android.customersheet
22

3+
import com.stripe.android.core.networking.AnalyticsEvent
34
import com.stripe.android.core.strings.ResolvableString
45
import com.stripe.android.lpmfoundations.luxe.SupportedPaymentMethod
56
import com.stripe.android.model.CardBrand
@@ -13,6 +14,7 @@ internal sealed class CustomerSheetViewAction {
1314
object OnBackPressed : CustomerSheetViewAction()
1415
object OnEditPressed : CustomerSheetViewAction()
1516
object OnCardNumberInputCompleted : CustomerSheetViewAction()
17+
class OnAnalyticsEvent(val event: AnalyticsEvent) : CustomerSheetViewAction()
1618
object OnAddCardPressed : CustomerSheetViewAction()
1719
object OnPrimaryButtonPressed : CustomerSheetViewAction()
1820
object OnCancelClose : CustomerSheetViewAction()

paymentsheet/src/main/java/com/stripe/android/customersheet/CustomerSheetViewModel.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.stripe.android.core.Logger
1616
import com.stripe.android.core.exception.StripeException
1717
import com.stripe.android.core.injection.IOContext
1818
import com.stripe.android.core.injection.IS_LIVE_MODE
19+
import com.stripe.android.core.networking.AnalyticsEvent
1920
import com.stripe.android.core.networking.ApiRequest
2021
import com.stripe.android.core.strings.ResolvableString
2122
import com.stripe.android.core.strings.orEmpty
@@ -271,6 +272,7 @@ internal class CustomerSheetViewModel(
271272
is CustomerSheetViewAction.OnAddCardPressed -> onAddCardPressed()
272273
is CustomerSheetViewAction.OnCardNumberInputCompleted -> onCardNumberInputCompleted()
273274
is CustomerSheetViewAction.OnDisallowedCardBrandEntered -> onDisallowedCardBrandEntered(viewAction.brand)
275+
is CustomerSheetViewAction.OnAnalyticsEvent -> onAnalyticsEvent(viewAction.event)
274276
is CustomerSheetViewAction.OnBackPressed -> onBackPressed()
275277
is CustomerSheetViewAction.OnEditPressed -> onEditPressed()
276278
is CustomerSheetViewAction.OnModifyItem -> onModifyItem(viewAction.paymentMethod)
@@ -917,6 +919,10 @@ internal class CustomerSheetViewModel(
917919
eventReporter.onCardNumberCompleted()
918920
}
919921

922+
private fun onAnalyticsEvent(event: AnalyticsEvent) {
923+
eventReporter.onAnalyticsEvent(event)
924+
}
925+
920926
private fun onDisallowedCardBrandEntered(brand: CardBrand) {
921927
eventReporter.onDisallowedCardBrandEntered(brand)
922928
}

paymentsheet/src/main/java/com/stripe/android/customersheet/analytics/CustomerSheetEventReporter.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.stripe.android.customersheet.analytics
22

3+
import com.stripe.android.core.networking.AnalyticsEvent
34
import com.stripe.android.customersheet.CustomerSheet
45
import com.stripe.android.customersheet.CustomerSheetIntegration
56
import com.stripe.android.model.CardBrand
@@ -129,6 +130,8 @@ internal interface CustomerSheetEventReporter {
129130

130131
fun onDisallowedCardBrandEntered(brand: CardBrand)
131132

133+
fun onAnalyticsEvent(event: AnalyticsEvent)
134+
132135
enum class Screen(val value: String) {
133136
AddPaymentMethod("add_payment_method"),
134137
SelectPaymentMethod("select_payment_method"),

paymentsheet/src/main/java/com/stripe/android/customersheet/analytics/DefaultCustomerSheetEventReporter.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.stripe.android.customersheet.analytics
22

33
import com.stripe.android.core.injection.IOContext
4+
import com.stripe.android.core.networking.AnalyticsEvent
45
import com.stripe.android.core.networking.AnalyticsRequestExecutor
56
import com.stripe.android.core.networking.AnalyticsRequestFactory
67
import com.stripe.android.customersheet.CustomerSheet
@@ -215,6 +216,17 @@ internal class DefaultCustomerSheetEventReporter @Inject constructor(
215216
)
216217
}
217218

219+
override fun onAnalyticsEvent(event: AnalyticsEvent) {
220+
CoroutineScope(workContext).launch {
221+
analyticsRequestExecutor.executeAsync(
222+
analyticsRequestFactory.createRequest(
223+
event = event,
224+
additionalParams = emptyMap(),
225+
)
226+
)
227+
}
228+
}
229+
218230
private fun fireEvent(event: CustomerSheetEvent) {
219231
CoroutineScope(workContext).launch {
220232
analyticsRequestExecutor.executeAsync(

paymentsheet/src/main/java/com/stripe/android/customersheet/ui/CustomerSheetScreen.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp
1818
import com.stripe.android.common.ui.BottomSheetLoadingIndicator
1919
import com.stripe.android.common.ui.BottomSheetScaffold
2020
import com.stripe.android.common.ui.PrimaryButton
21+
import com.stripe.android.core.networking.AnalyticsEvent
2122
import com.stripe.android.core.strings.ResolvableString
2223
import com.stripe.android.customersheet.CustomerSheetViewAction
2324
import com.stripe.android.customersheet.CustomerSheetViewModel
@@ -35,8 +36,10 @@ import com.stripe.android.paymentsheet.utils.PaymentSheetContentPadding
3536
import com.stripe.android.ui.core.elements.H4Text
3637
import com.stripe.android.ui.core.elements.Mandate
3738
import com.stripe.android.ui.core.elements.SimpleDialogElementUI
39+
import com.stripe.android.ui.core.elements.events.AnalyticsEventReporter
3840
import com.stripe.android.ui.core.elements.events.CardBrandDisallowedReporter
3941
import com.stripe.android.ui.core.elements.events.CardNumberCompletedEventReporter
42+
import com.stripe.android.ui.core.elements.events.LocalAnalyticsEventReporter
4043
import com.stripe.android.ui.core.elements.events.LocalCardBrandDisallowedReporter
4144
import com.stripe.android.ui.core.elements.events.LocalCardNumberCompletedEventReporter
4245
import com.stripe.android.uicore.strings.resolve
@@ -231,11 +234,15 @@ internal fun AddPaymentMethod(
231234
DefaultCardBrandDisallowedReporter(viewActionHandler)
232235
}
233236

237+
val analyticsEventReporter = remember(viewActionHandler) {
238+
DefaultAnalyticsEventReporter(viewActionHandler)
239+
}
240+
234241
if (displayForm) {
235242
CompositionLocalProvider(
236243
LocalCardNumberCompletedEventReporter provides eventReporter,
237-
LocalCardBrandDisallowedReporter provides disallowedReporter
238-
244+
LocalCardBrandDisallowedReporter provides disallowedReporter,
245+
LocalAnalyticsEventReporter provides analyticsEventReporter,
239246
) {
240247
PaymentElement(
241248
enabled = viewState.enabled,
@@ -334,6 +341,14 @@ const val CUSTOMER_SHEET_CONFIRM_BUTTON_TEST_TAG = "CustomerSheetConfirmButton"
334341
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
335342
const val CUSTOMER_SHEET_SAVE_BUTTON_TEST_TAG = "CustomerSheetSaveButton"
336343

344+
private class DefaultAnalyticsEventReporter(
345+
private val viewActionHandler: (event: CustomerSheetViewAction) -> Unit
346+
) : AnalyticsEventReporter {
347+
override fun onAnalyticsEvent(event: AnalyticsEvent) {
348+
viewActionHandler.invoke(CustomerSheetViewAction.OnAnalyticsEvent(event))
349+
}
350+
}
351+
337352
private class DefaultCardNumberCompletedEventReporter(
338353
private val viewActionHandler: (CustomerSheetViewAction) -> Unit
339354
) : CardNumberCompletedEventReporter {

paymentsheet/src/main/java/com/stripe/android/paymentsheet/analytics/DefaultEventReporter.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.Context
44
import com.stripe.android.common.analytics.experiment.LoggableExperiment
55
import com.stripe.android.common.model.CommonConfiguration
66
import com.stripe.android.core.injection.IOContext
7+
import com.stripe.android.core.networking.AnalyticsEvent
78
import com.stripe.android.core.networking.AnalyticsRequestExecutor
89
import com.stripe.android.core.networking.AnalyticsRequestV2Executor
910
import com.stripe.android.core.networking.AnalyticsRequestV2Factory
@@ -548,6 +549,17 @@ internal class DefaultEventReporter @Inject internal constructor(
548549
fireEvent(analyticsEvent)
549550
}
550551

552+
override fun onAnalyticsEvent(event: AnalyticsEvent) {
553+
CoroutineScope(workContext).launch {
554+
analyticsRequestExecutor.executeAsync(
555+
paymentAnalyticsRequestFactory.createRequest(
556+
event = event,
557+
additionalParams = emptyMap(),
558+
)
559+
)
560+
}
561+
}
562+
551563
private fun fireEvent(event: PaymentSheetEvent) {
552564
CoroutineScope(workContext).launch {
553565
analyticsRequestExecutor.executeAsync(

paymentsheet/src/main/java/com/stripe/android/paymentsheet/analytics/EventReporter.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.stripe.android.paymentsheet.analytics
33
import androidx.annotation.Keep
44
import com.stripe.android.common.analytics.experiment.LoggableExperiment
55
import com.stripe.android.common.model.CommonConfiguration
6+
import com.stripe.android.core.networking.AnalyticsEvent
67
import com.stripe.android.model.CardBrand
78
import com.stripe.android.model.LinkMode
89
import com.stripe.android.model.PaymentMethodCode
@@ -123,6 +124,8 @@ internal interface EventReporter {
123124

124125
fun onDisallowedCardBrandEntered(brand: CardBrand)
125126

127+
fun onAnalyticsEvent(event: AnalyticsEvent)
128+
126129
/**
127130
* The customer has pressed the confirm button.
128131
*/

paymentsheet/src/main/java/com/stripe/android/paymentsheet/utils/EventReporterProviderUtil.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.stripe.android.paymentsheet.utils
33
import androidx.compose.runtime.Composable
44
import androidx.compose.runtime.CompositionLocalProvider
55
import com.stripe.android.paymentsheet.analytics.EventReporter
6+
import com.stripe.android.ui.core.elements.events.LocalAnalyticsEventReporter
67
import com.stripe.android.ui.core.elements.events.LocalCardBrandDisallowedReporter
78
import com.stripe.android.ui.core.elements.events.LocalCardNumberCompletedEventReporter
89
import com.stripe.android.uicore.elements.LocalAutofillEventReporter
@@ -15,7 +16,8 @@ internal fun EventReporterProvider(
1516
CompositionLocalProvider(
1617
LocalAutofillEventReporter provides eventReporter::onAutofill,
1718
LocalCardNumberCompletedEventReporter provides eventReporter::onCardNumberCompleted,
18-
LocalCardBrandDisallowedReporter provides eventReporter::onDisallowedCardBrandEntered
19+
LocalCardBrandDisallowedReporter provides eventReporter::onDisallowedCardBrandEntered,
20+
LocalAnalyticsEventReporter provides eventReporter::onAnalyticsEvent,
1921
) {
2022
content()
2123
}

0 commit comments

Comments
 (0)