Skip to content

Commit

Permalink
Address PR comments
Browse files Browse the repository at this point in the history
  • Loading branch information
samer-stripe committed Sep 30, 2024
1 parent c81c934 commit 76700f2
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,35 @@ package com.stripe.android.customersheet.data
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes

internal data class CachedCustomerEphemeralKey(
val customerId: String,
val ephemeralKey: String,
private val expiresAt: Int,
) {
fun shouldRefresh(currentTimeInMillis: Long): Boolean {
val remainingTime = expiresAt - currentTimeInMillis.milliseconds.inWholeSeconds
internal sealed interface CachedCustomerEphemeralKey {
fun shouldRefresh(currentTimeInMillis: Long): Boolean

return remainingTime <= 5.minutes.inWholeSeconds
data class Available(
val customerId: String,
val ephemeralKey: String,
private val expiresAt: Int,
) : CachedCustomerEphemeralKey {
override fun shouldRefresh(currentTimeInMillis: Long): Boolean {
val remainingTime = expiresAt - currentTimeInMillis.milliseconds.inWholeSeconds
return remainingTime <= 5.minutes.inWholeSeconds
}
}

data object None : CachedCustomerEphemeralKey {
override fun shouldRefresh(currentTimeInMillis: Long): Boolean {
return true
}
}
}


internal fun <T> Result<CachedCustomerEphemeralKey>.mapWithEphemeralKeyCatching(
mapper: (customerId: String, ephemeralKey: String) -> T
): Result<T> {
return mapCatching { cachedKey ->
when (cachedKey) {
is CachedCustomerEphemeralKey.Available -> mapper(cachedKey.customerId, cachedKey.ephemeralKey)
is CachedCustomerEphemeralKey.None -> throw IllegalStateException("No ephemeral key was available!")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,20 @@ internal class DefaultCustomerSessionElementsSessionManager @Inject constructor(
@IOContext private val workContext: CoroutineContext,
) : CustomerSessionElementsSessionManager {
@Volatile
private var cachedCustomerEphemeralKey: CachedCustomerEphemeralKey? = null
private var cachedCustomerEphemeralKey: CachedCustomerEphemeralKey = CachedCustomerEphemeralKey.None

private var intentConfiguration: CustomerSheet.IntentConfiguration? = null

override suspend fun fetchCustomerSessionEphemeralKey(): Result<CachedCustomerEphemeralKey> {
return withContext(workContext) {
cachedCustomerEphemeralKey.takeUnless { cachedCustomerEphemeralKey ->
cachedCustomerEphemeralKey == null || cachedCustomerEphemeralKey.shouldRefresh(
timeProvider()
)
cachedCustomerEphemeralKey.shouldRefresh(timeProvider())
}?.let {
Result.success(it)
} ?: run {
fetchElementsSession().mapCatching {
cachedCustomerEphemeralKey
?: throw IllegalStateException("Should have been initialized from `elements/session`!")
}
} ?: kotlin.runCatching {
fetchElementsSession().getOrThrow()

cachedCustomerEphemeralKey
}
}
}
Expand Down Expand Up @@ -85,7 +82,7 @@ internal class DefaultCustomerSessionElementsSessionManager @Inject constructor(
externalPaymentMethods = listOf(),
).onSuccess { elementsSession ->
elementsSession.customer?.session?.run {
cachedCustomerEphemeralKey = CachedCustomerEphemeralKey(
cachedCustomerEphemeralKey = CachedCustomerEphemeralKey.Available(
customerId = customerId,
ephemeralKey = apiKey,
expiresAt = apiKeyExpiry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ internal class CustomerSessionSavedSelectionDataSource @Inject constructor(
}

private suspend fun createPrefsRepository(): CustomerSheetDataResult<PrefsRepository> {
return elementsSessionManager.fetchCustomerSessionEphemeralKey().mapCatching { ephemeralKey ->
prefsRepositoryFactory(ephemeralKey.customerId)
return elementsSessionManager.fetchCustomerSessionEphemeralKey().mapWithEphemeralKeyCatching { customerId, _ ->
prefsRepositoryFactory(customerId)
}.toCustomerSheetDataResult()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class DefaultCustomerSessionElementsSessionManagerTest {

assertThat(result).isEqualTo(
Result.success(
CachedCustomerEphemeralKey(
CachedCustomerEphemeralKey.Available(
customerId = "cus_1",
ephemeralKey = "ek_123",
expiresAt = 999999,
Expand Down Expand Up @@ -159,6 +159,7 @@ class DefaultCustomerSessionElementsSessionManagerTest {
apiKey = "ek_123",
apiKeyExpiry = 999999,
customerId = "cus_1",
currentTime = 10,
onCustomerSessionClientSecret = {
amountOfCalls++

Expand All @@ -180,7 +181,81 @@ class DefaultCustomerSessionElementsSessionManagerTest {
assertThat(amountOfCalls).isEqualTo(1)
assertThat(lastResult).isEqualTo(
Result.success(
CachedCustomerEphemeralKey(
CachedCustomerEphemeralKey.Available(
customerId = "cus_1",
ephemeralKey = "ek_123",
expiresAt = 999999,
)
)
)
}

@Test
fun `on fetch ephemeral key, should re-fetch key if expired`() = runTest {
var amountOfCalls = 0

val manager = createElementsSessionManagerWithCustomer(
apiKey = "ek_123",
apiKeyExpiry = 200000,
customerId = "cus_1",
currentTime = 210000000,
onCustomerSessionClientSecret = {
amountOfCalls++

Result.success(
CustomerSheet.CustomerSessionClientSecret.create(
customerId = "cus_1",
clientSecret = "cuss_123",
)
)
}
)

manager.fetchCustomerSessionEphemeralKey()
manager.fetchCustomerSessionEphemeralKey()
manager.fetchCustomerSessionEphemeralKey()

val lastResult = manager.fetchCustomerSessionEphemeralKey()

assertThat(amountOfCalls).isEqualTo(4)
assertThat(lastResult).isEqualTo(
Result.success(
CachedCustomerEphemeralKey.Available(
customerId = "cus_1",
ephemeralKey = "ek_123",
expiresAt = 200000,
)
)
)
}

@Test
fun `on fetch elements session, should set ephemeral key & be re-used when fetching ephemeral key`() = runTest {
var amountOfCalls = 0

val manager = createElementsSessionManagerWithCustomer(
apiKeyExpiry = 999999,
currentTime = 10,
onCustomerSessionClientSecret = {
amountOfCalls++

Result.success(
CustomerSheet.CustomerSessionClientSecret.create(
customerId = "cus_1",
clientSecret = "cuss_123",
)
)
},
)

manager.fetchElementsSession()
val ephemeralKey = manager.fetchCustomerSessionEphemeralKey()

assertThat(amountOfCalls).isEqualTo(1)

assertThat(ephemeralKey).isEqualTo(
Result.success(
CachedCustomerEphemeralKey.Available(
customerId = "cus_1",
ephemeralKey = "ek_123",
expiresAt = 999999,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.stripe.android.model.ElementsSession

internal class FakeCustomerSessionElementsSessionManager(
private val ephemeralKey: Result<CachedCustomerEphemeralKey> = Result.success(
CachedCustomerEphemeralKey(
CachedCustomerEphemeralKey.Available(
customerId = "cus_1",
ephemeralKey = "ek_123",
expiresAt = 999999,
Expand Down

0 comments on commit 76700f2

Please sign in to comment.