Skip to content

Commit

Permalink
MBL-1481: 3DS Validation Card - 3DS validation CTA flow (#2092)
Browse files Browse the repository at this point in the history
* Add infinite scroll to PPO screen

* Attempting to fix tests

* Test paging source

* linter

* Merge conflicts

* Linter

* Fixes

* Linter

* Testing

* Fix

* update graphql schema

* fix

* Hooked up requery and tests

* lint

* Cleanup

* trying to fix tests

* Fixed flaky tests

* Cleanup

* lint

* cleanup

* linter

* 3ds validation

* remove reference to stripe card ID, no longer needed

* Linter

---------

Co-authored-by: Yun <[email protected]>
Co-authored-by: Leigh Douglas <[email protected]>
  • Loading branch information
3 people authored Aug 8, 2024
1 parent 07cc7e4 commit 7d505bf
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 32 deletions.
1 change: 1 addition & 0 deletions app/src/main/graphql/fragments.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ fragment shippingRule on ShippingRule {

fragment ppoCard on Backing {
id
clientSecret
amount {
...amount
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class PPOCard private constructor(
val address: String?,
val addressID: String?,
val amount: String?,
val clientSecret: String?,
val currencyCode: CurrencyCode?,
val currencySymbol: String?,
val projectName: String?,
Expand All @@ -30,6 +31,7 @@ class PPOCard private constructor(
fun address() = this.address
fun addressID() = this.addressID
fun amount() = this.amount
fun clientSecret() = this.clientSecret
fun currencyCode() = this.currencyCode
fun currencySymbol() = this.currencySymbol
fun projectName() = this.projectName
Expand All @@ -49,6 +51,7 @@ class PPOCard private constructor(
var address: String? = null,
var addressID: String? = null,
var amount: String? = null,
var clientSecret: String? = null,
var currencyCode: CurrencyCode? = null,
var currencySymbol: String? = null,
var projectName: String? = null,
Expand All @@ -67,6 +70,7 @@ class PPOCard private constructor(
fun address(address: String?) = apply { this.address = address }
fun addressID(addressID: String?) = apply { this.addressID = addressID }
fun amount(amount: String?) = apply { this.amount = amount }
fun clientSecret(clientSecret: String?) = apply { this.clientSecret = clientSecret }
fun currencyCode(currencyCode: CurrencyCode?) = apply { this.currencyCode = currencyCode }
fun currencySymbol(currencySymbol: String?) = apply { this.currencySymbol = currencySymbol }
fun projectName(projectName: String?) = apply { this.projectName = projectName }
Expand All @@ -85,6 +89,7 @@ class PPOCard private constructor(
address = address,
addressID = addressID,
amount = amount,
clientSecret = clientSecret,
currencyCode = currencyCode,
currencySymbol = currencySymbol,
projectName = projectName,
Expand All @@ -105,6 +110,7 @@ class PPOCard private constructor(
address = address,
addressID = addressID,
amount = amount,
clientSecret = clientSecret,
currencyCode = currencyCode,
currencySymbol = currencySymbol,
projectName = projectName,
Expand All @@ -131,6 +137,7 @@ class PPOCard private constructor(
address() == other.address() &&
addressID() == other.addressID() &&
amount() == other.amount() &&
clientSecret() == other.clientSecret() &&
currencyCode() == other.currencyCode() &&
currencySymbol() == other.currencySymbol() &&
projectName() == other.projectName() &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,26 @@ class PPOCardFactory private constructor() {
viewType = PPOCardViewType.FIX_PAYMENT
)
}

fun authenticationRequiredCard(): PPOCard {
// 3ds card
return ppoCard(
backingID = "1234",
amount = "$12.00",
address = "Firsty Lasty\n123 First Street, Apt #5678\nLos Angeles, CA 90025-1234\nUnited States",
addressID = "12234",
currencySymbol = "$",
currencyCode = CurrencyCode.USD,
projectName = "Super Duper Project",
projectId = "12345",
projectSlug = "project/slug",
imageUrl = "image/url",
creatorName = "Creator Name",
backingDetailsUrl = "backing/details/url",
timeNumberForAction = 7,
showBadge = false,
viewType = PPOCardViewType.AUTHENTICATE_CARD
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ fun PPOCardPreview() {
item {
PPOCardView(
viewType = PPOCardViewType.AUTHENTICATE_CARD,
onCardClick = {},
onCardClick = { },
projectName = "Sugardew Island - Your cozy farm shop let’s pretend this is a longer title let’s pretend this is a longer title",
pledgeAmount = "$60.00",
creatorName = "Some really really really really really really really long name",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.kickstarter.R
import com.kickstarter.features.pledgedprojectsoverview.viewmodel.PledgedProjectsOverviewViewModel
import com.kickstarter.libs.MessagePreviousScreenType
import com.kickstarter.libs.RefTag
Expand All @@ -31,13 +33,21 @@ import com.kickstarter.ui.SharedPreferenceKey
import com.kickstarter.ui.activities.AppThemes
import com.kickstarter.ui.activities.ProfileActivity
import com.kickstarter.ui.compose.designsystem.KickstarterApp
import com.kickstarter.ui.extensions.setUpConnectivityStatusCheck
import com.kickstarter.ui.extensions.showSnackbar
import com.kickstarter.ui.extensions.startCreatorMessageActivity
import com.kickstarter.ui.extensions.transition
import com.stripe.android.ApiResultCallback
import com.stripe.android.PaymentIntentResult
import com.stripe.android.Stripe
import com.stripe.android.StripeIntentResult
import kotlinx.coroutines.launch

class PledgedProjectsOverviewActivity : AppCompatActivity() {

private lateinit var viewModelFactory: PledgedProjectsOverviewViewModel.Factory
private lateinit var snackbarHostState: SnackbarHostState
private lateinit var stripe: Stripe
private val viewModel: PledgedProjectsOverviewViewModel by viewModels { viewModelFactory }
private var theme = AppThemes.MATCH_SYSTEM.ordinal
private var startForResult =
Expand All @@ -61,10 +71,13 @@ class PledgedProjectsOverviewActivity : AppCompatActivity() {
?.getInt(SharedPreferenceKey.APP_THEME, AppThemes.MATCH_SYSTEM.ordinal)
?: AppThemes.MATCH_SYSTEM.ordinal

stripe = requireNotNull(env.stripe())
snackbarHostState = remember { SnackbarHostState() }
setUpConnectivityStatusCheck(lifecycle)

val ppoUIState by viewModel.ppoUIState.collectAsStateWithLifecycle()

val lazyListState = rememberLazyListState()
val snackbarHostState = remember { SnackbarHostState() }
val totalAlerts = viewModel.totalAlertsState.collectAsStateWithLifecycle().value

val ppoCardPagingSource = viewModel.ppoCardsState.collectAsLazyPagingItems()
Expand Down Expand Up @@ -102,6 +115,13 @@ class PledgedProjectsOverviewActivity : AppCompatActivity() {
},
onPrimaryActionButtonClicked = { PPOCard ->
when (PPOCard.viewType()) {
PPOCardViewType.AUTHENTICATE_CARD -> {
lifecycleScope.launch {
viewModel.showLoadingState(true)
}
stripeNextAction(PPOCard.clientSecret() ?: "", stripe)
}

PPOCardViewType.FIX_PAYMENT -> {
openManagePledge(
PPOCard.projectSlug ?: "",
Expand Down Expand Up @@ -201,4 +221,45 @@ class PledgedProjectsOverviewActivity : AppCompatActivity() {
TransitionUtils.transition(it, TransitionUtils.slideInFromRight())
}
}

private fun stripeNextAction(it: String, stripe: Stripe) {
try {
// - PaymentIntent format
if (it.contains("pi_")) {
stripe.handleNextActionForPayment(this, it)
} else {
// - SetupIntent format
stripe.handleNextActionForSetupIntent(this, it)
}
} catch (exception: Exception) {
FirebaseCrashlytics.getInstance().recordException(exception)
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
stripe.onPaymentResult(
requestCode, intent,
object : ApiResultCallback<PaymentIntentResult> {
override fun onSuccess(result: PaymentIntentResult) {
lifecycleScope.launch {
viewModel.showLoadingState(false)
}
if (result.outcome == StripeIntentResult.Outcome.SUCCEEDED) {
viewModel.showHeadsUpSnackbar(R.string.successful_validation_please_pull_to_refresh_fpo)
viewModel.getPledgedProjects()
} else if (result.outcome == StripeIntentResult.Outcome.FAILED ||
result.outcome == StripeIntentResult.Outcome.TIMEDOUT ||
result.outcome == StripeIntentResult.Outcome.UNKNOWN
) viewModel.showErrorSnackbar(R.string.general_error_something_wrong)
}
override fun onError(e: Exception) {
lifecycleScope.launch {
viewModel.showLoadingState(false)
}
viewModel.showErrorSnackbar(R.string.general_error_something_wrong)
}
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,17 @@ fun PledgedProjectsOverviewScreen(
}
}
}
if (isLoading) {
Box(
modifier = Modifier
.fillMaxSize()
.background(KSTheme.colors.backgroundAccentGraySubtle.copy(alpha = 0.5f))
.clickable(enabled = false) { },
contentAlignment = Alignment.Center
) {
KSCircularProgressIndicator()
}
}
}
}

Expand All @@ -295,18 +306,6 @@ fun PledgedProjectsOverviewScreen(
)
}
}

if (isLoading) {
Box(
modifier = Modifier
.fillMaxSize()
.background(KSTheme.colors.backgroundAccentGraySubtle.copy(alpha = 0.5f))
.clickable(enabled = false) { },
contentAlignment = Alignment.Center
) {
KSCircularProgressIndicator()
}
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ class PledgedProjectsOverviewViewModel(
private val mutablePPOUIState = MutableStateFlow(PledgedProjectsOverviewUIState())
val ppoCardsState: StateFlow<PagingData<PPOCard>> = mutablePpoCards.asStateFlow()

private var mutablePaymentRequiresAction = MutableSharedFlow<String>()
val paymentRequiresAction: SharedFlow<String>
get() = mutablePaymentRequiresAction.asSharedFlow()

private var pagingSource = PledgedProjectsPagingSource(apolloClient, mutableTotalAlerts, PAGE_LIMIT)

val ppoUIState: StateFlow<PledgedProjectsOverviewUIState>
Expand Down Expand Up @@ -196,6 +200,10 @@ class PledgedProjectsOverviewViewModel(
this.snackbarMessage = snackBarMessage
}

suspend fun showLoadingState(isLoading: Boolean) {
emitCurrentState(isLoading = isLoading)
}

private suspend fun emitCurrentState(isLoading: Boolean = false, isErrored: Boolean = false) {
mutablePPOUIState.emit(
PledgedProjectsOverviewUIState(
Expand All @@ -205,11 +213,11 @@ class PledgedProjectsOverviewViewModel(
)
}

private fun showHeadsUpSnackbar(messageId: Int) {
fun showHeadsUpSnackbar(messageId: Int) {
snackbarMessage.invoke(messageId, KSSnackbarTypes.KS_HEADS_UP.name)
}

private fun showErrorSnackbar(messageId: Int) {
fun showErrorSnackbar(messageId: Int) {
snackbarMessage.invoke(messageId, KSSnackbarTypes.KS_ERROR.name)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -917,22 +917,24 @@ fun getPledgedProjectsOverviewQuery(queryInput: PledgedProjectsOverviewQueryData
}

fun pledgedProjectsOverviewEnvelopeTransformer(ppoResponse: PledgedProjectsOverviewQuery.PledgeProjectsOverview): PledgedProjectsOverviewEnvelope {
val ppoCards = ppoResponse.pledges()?.edges()?.map {
val ppoBackingData = it.node()?.backing()?.fragments()?.ppoCard()
PPOCard.builder()
.backingId(ppoBackingData?.id())
.amount(ppoBackingData?.amount()?.fragments()?.amount()?.amount())
.currencyCode(ppoBackingData?.amount()?.fragments()?.amount()?.currency())
.currencySymbol(ppoBackingData?.amount()?.fragments()?.amount()?.symbol())
.projectName(ppoBackingData?.project()?.name())
.projectId(ppoBackingData?.project()?.id())
.projectSlug(ppoBackingData?.project()?.slug())
.imageUrl(ppoBackingData?.project()?.fragments()?.full()?.image()?.url())
.creatorName(ppoBackingData?.project()?.creator()?.name())
.viewType(getTierType(it.node()?.tierType()))
.addressID(ppoBackingData?.deliveryAddress()?.id())
.build()
}
val ppoCards =
ppoResponse.pledges()?.edges()?.map {
val ppoBackingData = it.node()?.backing()?.fragments()?.ppoCard()
PPOCard.builder()
.backingId(ppoBackingData?.id())
.clientSecret(ppoBackingData?.clientSecret())
.amount(ppoBackingData?.amount()?.fragments()?.amount()?.amount())
.currencyCode(ppoBackingData?.amount()?.fragments()?.amount()?.currency())
.currencySymbol(ppoBackingData?.amount()?.fragments()?.amount()?.symbol())
.projectName(ppoBackingData?.project()?.name())
.projectId(ppoBackingData?.project()?.id())
.projectSlug(ppoBackingData?.project()?.slug())
.imageUrl(ppoBackingData?.project()?.fragments()?.full()?.image()?.url())
.creatorName(ppoBackingData?.project()?.creator()?.name())
.viewType(getTierType(it.node()?.tierType()))
.addressID(ppoBackingData?.deliveryAddress()?.id())
.build()
}

val pageInfoEnvelope = ppoResponse.pledges()?.pageInfo().let {
PageInfoEnvelope.builder()
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,6 @@
<string name="address_confirmed_snackbar_text_fpo">Address confirmed! Need to change your address before it locks? Visit your backing details on our website.</string>
<string name="backing_details_fpo">Backing details</string>
<string name="something_went_wrong_pull_to_refresh_fpo">Something went wrong - Pull to refresh</string>
<string name="successful_validation_please_pull_to_refresh_fpo">Validation successful! Please pull to refresh</string>

</resources>

0 comments on commit 7d505bf

Please sign in to comment.