Skip to content

Commit

Permalink
Add elements/session analytics for CustomerSession on `CustomerSh…
Browse files Browse the repository at this point in the history
…eet`
  • Loading branch information
samer-stripe committed Oct 3, 2024
1 parent 87f527e commit 8f0f43d
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ interface ErrorReporter : FraudDetectionErrorReporter {
CUSTOMER_SHEET_ELEMENTS_SESSION_LOAD_FAILURE(
eventName = "elements.customer_sheet.elements_session.load_failure"
),
CUSTOMER_SHEET_CUSTOMER_SESSION_ELEMENTS_SESSION_LOAD_FAILURE(
eventName = "elements.customer_sheet.customer_session.elements_session.load_failure"
),
CUSTOMER_SHEET_PAYMENT_METHODS_LOAD_FAILURE(
eventName = "elements.customer_sheet.payment_methods.load_failure"
),
Expand Down Expand Up @@ -235,6 +238,9 @@ interface ErrorReporter : FraudDetectionErrorReporter {
CUSTOMER_SHEET_ELEMENTS_SESSION_LOAD_SUCCESS(
eventName = "elements.customer_sheet.elements_session.load_success"
),
CUSTOMER_SHEET_CUSTOMER_SESSION_ELEMENTS_SESSION_LOAD_SUCCESS(
eventName = "elements.customer_sheet.customer_session.elements_session.load_success"
),
CUSTOMER_SHEET_PAYMENT_METHODS_LOAD_SUCCESS(
eventName = "elements.customer_sheet.payment_methods.load_success"
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.android.customersheet.data

import com.stripe.android.common.validation.CustomerSessionClientSecretValidator
import com.stripe.android.core.exception.StripeException
import com.stripe.android.core.injection.IOContext
import com.stripe.android.customersheet.CustomerSheet
import com.stripe.android.customersheet.ExperimentalCustomerSheetApi
Expand Down Expand Up @@ -89,37 +90,64 @@ internal class DefaultCustomerSessionElementsSessionManager @Inject constructor(
clientSecret = customerSessionClientSecret.clientSecret,
),
externalPaymentMethods = listOf(),
).mapCatching { elementsSession ->
val customer = elementsSession.customer ?: run {
errorReporter.report(
ErrorReporter
.UnexpectedErrorEvent
.CUSTOMER_SESSION_ON_CUSTOMER_SHEET_ELEMENTS_SESSION_NO_CUSTOMER_FIELD
)

throw IllegalStateException(
"`customer` field should be available when using `CustomerSession` in elements/session!"
)
}

val customerSession = customer.session

CustomerSessionElementsSession(
elementsSession = elementsSession,
customer = customer,
ephemeralKey = CachedCustomerEphemeralKey(
customerId = customerSession.customerId,
ephemeralKey = customerSession.apiKey,
expiresAt = customerSession.apiKeyExpiry,
)
)
).onSuccess {
reportSuccessfulElementsSessionLoad()
}.onFailure {
reportFailedElementsSessionLoad(it)
}.mapCatching { elementsSession ->
createCustomerSessionElementsSession(elementsSession)
}.onSuccess { customerSessionElementsSession ->
cachedCustomerEphemeralKey = customerSessionElementsSession.ephemeralKey
}.getOrThrow()
}
}
}

private fun createCustomerSessionElementsSession(
elementsSession: ElementsSession
): CustomerSessionElementsSession {
val customer = elementsSession.customer ?: run {
errorReporter.report(
ErrorReporter
.UnexpectedErrorEvent
.CUSTOMER_SESSION_ON_CUSTOMER_SHEET_ELEMENTS_SESSION_NO_CUSTOMER_FIELD
)

throw IllegalStateException(
"`customer` field should be available when using `CustomerSession` in elements/session!"
)
}

val customerSession = customer.session

return CustomerSessionElementsSession(
elementsSession = elementsSession,
customer = customer,
ephemeralKey = CachedCustomerEphemeralKey(
customerId = customerSession.customerId,
ephemeralKey = customerSession.apiKey,
expiresAt = customerSession.apiKeyExpiry,
)
)
}

private fun reportSuccessfulElementsSessionLoad() {
errorReporter.report(
errorEvent = ErrorReporter
.SuccessEvent
.CUSTOMER_SHEET_CUSTOMER_SESSION_ELEMENTS_SESSION_LOAD_SUCCESS,
)
}

private fun reportFailedElementsSessionLoad(cause: Throwable) {
errorReporter.report(
errorEvent = ErrorReporter
.ExpectedErrorEvent
.CUSTOMER_SHEET_CUSTOMER_SESSION_ELEMENTS_SESSION_LOAD_FAILURE,
stripeException = StripeException.create(cause)
)
}

private fun validateCustomerSessionClientSecret(customerSessionClientSecret: String) {
val result = CustomerSessionClientSecretValidator
.validate(customerSessionClientSecret)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ import kotlin.coroutines.coroutineContext
@OptIn(ExperimentalCustomerSheetApi::class, ExperimentalCustomerSessionApi::class)
class DefaultCustomerSessionElementsSessionManagerTest {
@Test
fun `on fetch elements session, should set parameters properly`() = runTest {
fun `on fetch elements session, should set parameters properly & report successful load`() = runTest {
val errorReporter = FakeErrorReporter()
val elementsSessionRepository = FakeElementsSessionRepository(
stripeIntent = PaymentIntentFactory.create(),
error = null,
linkSettings = null,
sessionsCustomer = createDefaultCustomer(),
)

val manager = createElementsSessionManager(
elementsSessionRepository = elementsSessionRepository,
errorReporter = errorReporter,
savedSelection = SavedSelection.PaymentMethod(id = "pm_123"),
intentConfiguration = Result.success(
CustomerSheet.IntentConfiguration.Builder()
Expand Down Expand Up @@ -68,6 +71,39 @@ class DefaultCustomerSessionElementsSessionManagerTest {
assertThat(customer?.id).isEqualTo("cus_1")
assertThat(customer?.accessType)
.isEqualTo(PaymentSheet.CustomerAccessType.CustomerSession(customerSessionClientSecret = "cuss_123"))

assertThat(errorReporter.getLoggedErrors()).containsExactly(
ErrorReporter.SuccessEvent.CUSTOMER_SHEET_CUSTOMER_SESSION_ELEMENTS_SESSION_LOAD_SUCCESS.eventName,
)
}

@Test
fun `on fetch elements session, should fail & report if elements session request failed`() = runTest {
val exception = IllegalStateException("Failed to load!")

val errorReporter = FakeErrorReporter()

val elementsSessionManager = createElementsSessionManager(
elementsSessionRepository = FakeElementsSessionRepository(
stripeIntent = SetupIntentFactory.create(),
error = exception,
linkSettings = null,
sessionsCustomer = null,
),
errorReporter = errorReporter,
)

val elementsSessionResult = elementsSessionManager.fetchElementsSession()

assertThat(elementsSessionResult.isFailure).isTrue()
assertThat(elementsSessionResult.exceptionOrNull()).isEqualTo(exception)

assertThat(errorReporter.getLoggedErrors()).containsExactly(
ErrorReporter
.ExpectedErrorEvent
.CUSTOMER_SHEET_CUSTOMER_SESSION_ELEMENTS_SESSION_LOAD_FAILURE
.eventName,
)
}

@Test
Expand Down Expand Up @@ -288,6 +324,7 @@ class DefaultCustomerSessionElementsSessionManagerTest {
)

assertThat(errorReporter.getLoggedErrors()).containsExactly(
ErrorReporter.SuccessEvent.CUSTOMER_SHEET_CUSTOMER_SESSION_ELEMENTS_SESSION_LOAD_SUCCESS.eventName,
ErrorReporter
.UnexpectedErrorEvent
.CUSTOMER_SESSION_ON_CUSTOMER_SHEET_ELEMENTS_SESSION_NO_CUSTOMER_FIELD
Expand Down Expand Up @@ -353,24 +390,18 @@ class DefaultCustomerSessionElementsSessionManagerTest {
)
},
): CustomerSessionElementsSessionManager {
val defaultCustomer = createDefaultCustomer()

return createElementsSessionManager(
elementsSessionRepository = FakeElementsSessionRepository(
stripeIntent = PaymentIntentFactory.create(),
error = null,
linkSettings = null,
sessionsCustomer = ElementsSession.Customer(
paymentMethods = listOf(),
defaultPaymentMethod = null,
session = ElementsSession.Customer.Session(
id = "cuss_123",
liveMode = true,
sessionsCustomer = defaultCustomer.copy(
session = defaultCustomer.session.copy(
apiKey = apiKey,
apiKeyExpiry = apiKeyExpiry,
customerId = customerId,
components = ElementsSession.Customer.Components(
mobilePaymentElement = ElementsSession.Customer.Components.MobilePaymentElement.Disabled,
customerSheet = ElementsSession.Customer.Components.CustomerSheet.Disabled,
)
)
),
),
Expand Down Expand Up @@ -422,6 +453,24 @@ class DefaultCustomerSessionElementsSessionManagerTest {
)
}

private fun createDefaultCustomer(): ElementsSession.Customer {
return ElementsSession.Customer(
paymentMethods = listOf(),
defaultPaymentMethod = null,
session = ElementsSession.Customer.Session(
id = "cuss_1",
customerId = "cus_1",
apiKey = "ek_123",
apiKeyExpiry = 999999,
components = ElementsSession.Customer.Components(
mobilePaymentElement = ElementsSession.Customer.Components.MobilePaymentElement.Disabled,
customerSheet = ElementsSession.Customer.Components.CustomerSheet.Disabled,
),
liveMode = false,
)
)
}

private fun PaymentSheet.InitializationMode?.asDeferred(): PaymentSheet.InitializationMode.DeferredIntent {
return this as PaymentSheet.InitializationMode.DeferredIntent
}
Expand Down

0 comments on commit 8f0f43d

Please sign in to comment.