Skip to content

Commit

Permalink
PAY-2080: Retry pledge after error with PaymentSheet (#1710)
Browse files Browse the repository at this point in the history
  • Loading branch information
Arkariang committed Nov 29, 2022
1 parent c2c6986 commit a27a397
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 45 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Expand Up @@ -224,7 +224,7 @@ dependencies {
implementation "com.jakewharton.rxbinding:rxbinding-support-v4:$rx_binding_version"
implementation "com.jakewharton.timber:timber:5.0.1"
implementation 'com.optimizely.ab:android-sdk:3.13.2'
implementation 'com.stripe:stripe-android:20.14.0'
implementation 'com.stripe:stripe-android:20.16.1'
final okhttp_version = '4.10.+'
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
implementation "com.squareup.okhttp3:okhttp-urlconnection:$okhttp_version"
Expand Down
Expand Up @@ -262,3 +262,31 @@ fun Project.updateStartedProjectAndDiscoveryParamsList(

return listOfProjects
}

/**
* Extension function that will return a reduced copy of the the target project, the fields available
* on the reduce copy are those ones required on the next Screens: BackingAddons, PledgeFragment, ThanksActivity
*
* The end goal is to reduce to the bare minimum the amount of memory required to be serialized on Intents
* when presenting screens in order to avoid `android.os.TransactionTooLargeException`
*/
fun Project.reduce(): Project {
return Project.Builder()
.id(this.id())
.slug(this.slug())
.name(this.name())
.location(this.location())
.deadline(this.deadline())
.staticUsdRate(this.staticUsdRate())
.fxRate(this.fxRate())
.country(this.country())
.currentCurrency(this.currentCurrency())
.currency(this.currency())
.currencySymbol(this.currencySymbol())
.currencyTrailingCode(this.currencyTrailingCode())
.isBacking(this.isBacking())
.backing(backing())
.availableCardTypes(this.availableCardTypes())
.category(this.category())
.build()
}
Expand Up @@ -160,14 +160,14 @@ class ActivityFeedActivity : BaseActivity<ActivityFeedViewModel.ViewModel>() {

private fun startProjectActivity(project: Project) {
val intent = Intent().getProjectIntent(this)
.putExtra(IntentKey.PROJECT, project)
.putExtra(IntentKey.PROJECT_PARAM, project.slug())
.putExtra(IntentKey.REF_TAG, RefTag.activity())
startActivityWithTransition(intent, R.anim.slide_in_right, R.anim.fade_out_slide_out_left)
}

private fun startUpdateActivity(activity: Activity) {
val intent = Intent(this, UpdateActivity::class.java)
.putExtra(IntentKey.PROJECT, activity.project())
.putExtra(IntentKey.PROJECT_PARAM, activity.project()?.slug())
.putExtra(IntentKey.UPDATE, activity.update())
startActivityWithTransition(intent, R.anim.slide_in_right, R.anim.fade_out_slide_out_left)
}
Expand Down
Expand Up @@ -736,7 +736,6 @@ class ProjectPageActivity :
val pledgeData = checkoutDatandProjectData.second
val projectData = pledgeData.projectData()
if (clearFragmentBackStack()) {
updateFragments(projectData)
startActivity(
Intent(this, ThanksActivity::class.java)
.putExtra(IntentKey.PROJECT, projectData.project())
Expand Down
Expand Up @@ -144,7 +144,7 @@ class ThanksActivity : BaseActivity<ThanksViewModel.ViewModel>() {

private fun startProjectActivity(projectAndRefTagAndIsFfEnabled: Pair<Project, RefTag>) {
val intent = Intent().getProjectIntent(this)
.putExtra(IntentKey.PROJECT, projectAndRefTagAndIsFfEnabled.first)
.putExtra(IntentKey.PROJECT_PARAM, projectAndRefTagAndIsFfEnabled.first?.slug())
.putExtra(IntentKey.REF_TAG, projectAndRefTagAndIsFfEnabled.second)
startActivityWithTransition(intent, R.anim.slide_in_right, R.anim.fade_out_slide_out_left)
}
Expand Down
Expand Up @@ -272,7 +272,7 @@ class DiscoveryFragment : BaseFragment<DiscoveryFragmentViewModel.ViewModel>() {
private fun startProjectActivity(project: Project, refTag: RefTag) {
context?.let {
val intent = Intent().getProjectIntent(it)
.putExtra(IntentKey.PROJECT, project)
.putExtra(IntentKey.PROJECT_PARAM, project.slug())
.putExtra(IntentKey.REF_TAG, refTag)
startActivity(intent)
TransitionUtils.transition(it, TransitionUtils.slideInFromRight())
Expand Down
Expand Up @@ -721,7 +721,8 @@ class PledgeFragment :
is PaymentSheetResult.Failed -> {
binding?.pledgeContent?.let { pledgeContent ->
context?.let {
showErrorToast(it, pledgeContent, getString(R.string.general_error_something_wrong))
val errorMessage = paymentSheetResult.error.localizedMessage ?: getString(R.string.general_error_something_wrong)
showErrorToast(it, pledgeContent, errorMessage)
}
}
}
Expand Down
Expand Up @@ -16,6 +16,7 @@ import com.kickstarter.libs.rx.transformers.Transformers.observeForUI
import com.kickstarter.libs.utils.NumberUtils
import com.kickstarter.libs.utils.RewardDecoration
import com.kickstarter.libs.utils.ViewUtils
import com.kickstarter.libs.utils.extensions.reduce
import com.kickstarter.libs.utils.extensions.selectPledgeFragment
import com.kickstarter.models.Reward
import com.kickstarter.ui.adapters.RewardsAdapter
Expand Down Expand Up @@ -170,7 +171,14 @@ class RewardsFragment : BaseFragment<RewardsFragmentViewModel.ViewModel>(), Rewa

private fun showAddonsFragment(pledgeDataAndReason: Pair<PledgeData, PledgeReason>) {
if (this.isVisible && this.parentFragmentManager.findFragmentByTag(BackingAddOnsFragment::class.java.simpleName) == null) {
val addOnsFragment = BackingAddOnsFragment.newInstance(pledgeDataAndReason)

val reducedProject = pledgeDataAndReason.first.projectData().project().reduce()

val reducedProjectData = pledgeDataAndReason.first.projectData().toBuilder().project(reducedProject).build()
val reducedPledgeData = pledgeDataAndReason.first.toBuilder().projectData(reducedProjectData).build()

val addOnsFragment = BackingAddOnsFragment.newInstance(Pair(reducedPledgeData, pledgeDataAndReason.second))

this.parentFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, 0, 0, R.anim.slide_out_right)
.add(
Expand Down
Expand Up @@ -14,7 +14,6 @@ import com.kickstarter.libs.rx.transformers.Transformers.combineLatestPair
import com.kickstarter.libs.rx.transformers.Transformers.errors
import com.kickstarter.libs.rx.transformers.Transformers.ignoreValues
import com.kickstarter.libs.rx.transformers.Transformers.neverError
import com.kickstarter.libs.rx.transformers.Transformers.takePairWhen
import com.kickstarter.libs.rx.transformers.Transformers.takeWhen
import com.kickstarter.libs.rx.transformers.Transformers.values
import com.kickstarter.libs.rx.transformers.Transformers.zipPair
Expand Down Expand Up @@ -524,6 +523,7 @@ interface PledgeFragmentViewModel {

backing
.map { it.locationId() == null }
.compose(bindToLifecycle())
.subscribe {
this.shouldLoadDefaultLocation.onNext(it)
}
Expand Down Expand Up @@ -562,6 +562,7 @@ interface PledgeFragmentViewModel {

pledgeData
.map { it.shippingRule() == null && RewardUtils.isShippable(it.reward()) }
.compose(bindToLifecycle())
.subscribe { this.shouldLoadDefaultLocation.onNext(it) }

val preSelectedShippingRule = Observable.merge(initShippingRule, backingShippingRule, backingShippingRuleUpdate)
Expand All @@ -587,7 +588,6 @@ interface PledgeFragmentViewModel {

backing
.compose<Pair<Backing, Reward>>(combineLatestPair(this.selectedReward))
.compose(bindToLifecycle())
.filter { it.first != null && it.second != null }

backing
Expand Down Expand Up @@ -830,6 +830,7 @@ interface PledgeFragmentViewModel {

Observable.merge(this.decreaseBonusButtonClicked, this.decreasePledgeButtonClicked, this.increaseBonusButtonClicked, this.increasePledgeButtonClicked)
.distinctUntilChanged()
.compose(bindToLifecycle())
.subscribe {
this.bonusAmountHasChanged.onNext(true)
}
Expand Down Expand Up @@ -877,8 +878,8 @@ interface PledgeFragmentViewModel {

shippingAmount
.compose<Pair<Double, Project>>(combineLatestPair(project))
.compose(bindToLifecycle())
.distinctUntilChanged()
.compose(bindToLifecycle())
.subscribe {
shippingAmountSelectedRw.onNext(it.first)
this.shippingAmount.onNext(ProjectViewUtils.styleCurrency(it.first, it.second, this.ksCurrency))
Expand Down Expand Up @@ -1093,6 +1094,7 @@ interface PledgeFragmentViewModel {
return@combineLatest rule.id() != dfRule.id()
}
.distinctUntilChanged()
.compose(bindToLifecycle())
.subscribe(this.shippingRuleUpdated)

val shippingOrAmountChanged = Observable.combineLatest(shippingRuleUpdated, this.bonusAmountHasChanged, amountUpdated, pledgeReason) { shippingUpdated, bHasChanged, aUpdated, pReason ->
Expand Down Expand Up @@ -1164,6 +1166,17 @@ interface PledgeFragmentViewModel {
.filter { ObjectUtils.isNotNull(it) }
.map { it as Pair<StoredCard, Int> }

// - When setupIntent finishes with error reload the payment methods
this.paymentSheetResult
.filter {
it != PaymentSheetResult.Completed
}
.withLatestFrom(cardsAndProject) { _, cardsAndProject ->
return@withLatestFrom cardsAndProject
}
.compose(bindToLifecycle())
.subscribe(this.cardsAndProject)

this.cardSaved
.compose<Pair<StoredCard, Project>>(combineLatestPair(project))
.compose(bindToLifecycle())
Expand Down Expand Up @@ -1244,9 +1257,9 @@ interface PledgeFragmentViewModel {

val experimentData = Observable.combineLatest(this.currentUser.observable(), projectData) { u, p -> ExperimentData(u, p.refTagFromIntent(), p.refTagFromCookie()) }

this.pledgeButtonClicked
.compose(combineLatestPair(experimentData))
.filter { this.optimizely?.variant(OptimizelyExperiment.Key.NATIVE_RISK_MESSAGING, it.second) != OptimizelyExperiment.Variant.CONTROL }
experimentData
.compose(takeWhen(this.pledgeButtonClicked))
.filter { this.optimizely?.variant(OptimizelyExperiment.Key.NATIVE_RISK_MESSAGING, it) != OptimizelyExperiment.Variant.CONTROL }
.withLatestFrom(riskConfirmationFlag) { _, flag -> flag }
.filter { !it }
.compose(combineLatestPair(pledgeReason))
Expand All @@ -1267,24 +1280,16 @@ interface PledgeFragmentViewModel {
changePledgeSectionAccountabilityFragmentVisiablity.onNext(it.first)
}

this.pledgeButtonClicked
.compose(combineLatestPair(experimentData))
.filter { this.optimizely?.variant(OptimizelyExperiment.Key.NATIVE_RISK_MESSAGING, it.second) == OptimizelyExperiment.Variant.CONTROL }
experimentData
.compose(takeWhen(this.pledgeButtonClicked))
.filter { this.optimizely?.variant(OptimizelyExperiment.Key.NATIVE_RISK_MESSAGING, it) == OptimizelyExperiment.Variant.CONTROL }
.withLatestFrom(riskConfirmationFlag) { _, flag -> flag }
.filter { !it }
.compose(combineLatestPair(pledgeReason))
.filter { it.second == PledgeReason.PLEDGE }
.compose(bindToLifecycle())
.subscribe { this.riskConfirmationFlag.onNext(true) }

val pledgeButtonClicked = userIsLoggedIn
.compose(takePairWhen(this.riskConfirmationFlag))
.compose(combineLatestPair(pledgeReason))
.filter {
it.first.first && it.second == PledgeReason.PLEDGE && it.first.second
}
.compose(ignoreValues())

// An observable of the ref tag stored in the cookie for the project. Can emit `null`.
val cookieRefTag = project
.take(1)
Expand All @@ -1306,6 +1311,11 @@ interface PledgeFragmentViewModel {
val extendedListForCheckOut = rewardAndAddOns
.map { extendAddOns(it) }

val pledgeButtonClicked = pledgeReason
.compose<PledgeReason>(takeWhen(this.pledgeButtonClicked))
.filter { it == PledgeReason.PLEDGE }
.compose(ignoreValues())

val createBackingNotification = Observable.combineLatest(
project,
total.map { it.toString() },
Expand Down
Expand Up @@ -8,6 +8,9 @@ import com.kickstarter.mock.factories.ConfigFactory
import com.kickstarter.mock.factories.ProjectFactory
import com.kickstarter.mock.factories.UserFactory
import com.kickstarter.models.Project
import com.kickstarter.models.ProjectFaq
import com.kickstarter.models.Reward
import com.kickstarter.models.User
import com.kickstarter.services.DiscoveryParams
import org.joda.time.DateTime
import org.junit.Test
Expand Down Expand Up @@ -349,4 +352,34 @@ class ProjectExtTest : KSRobolectricTestCase() {

assertTrue(project.deadline() == secondProject.deadline())
}

@Test
fun testReduce_project() {
val project = ProjectFactory.project()
val reducedProject = project.reduce()

assertEquals(project.id(), reducedProject.id())
assertEquals(project.name(), reducedProject.name())
assertEquals(project.slug(), reducedProject.slug())
assertEquals(project.location(), reducedProject.location())
assertEquals(project.deadline(), reducedProject.deadline())
assertEquals(project.staticUsdRate(), reducedProject.staticUsdRate())
assertEquals(project.fxRate(), reducedProject.fxRate())
assertEquals(project.country(), reducedProject.country())
assertEquals(project.currency(), reducedProject.currency())
assertEquals(project.currentCurrency(), reducedProject.currentCurrency())
assertEquals(project.currencySymbol(), reducedProject.currencySymbol())
assertEquals(project.currencyTrailingCode(), reducedProject.currencyTrailingCode())
assertEquals(project.currencyTrailingCode(), reducedProject.currencyTrailingCode())
assertEquals(project.isBacking(), reducedProject.isBacking())
assertEquals(project.availableCardTypes(), reducedProject.availableCardTypes())
assertEquals(project.category(), reducedProject.category())

assertEquals(reducedProject.rewards(), emptyList<Reward>()) // Default builder value
assertEquals(reducedProject.creator(), User.builder().build()) // Default builder value
assertEquals(reducedProject.video(), null) // Default builder value
assertEquals(reducedProject.projectFaqs(), emptyList<ProjectFaq>()) // Default builder value
assertEquals(reducedProject.photo(), null) // Default builder value
assertEquals(reducedProject.story(), "") // Default builder value
}
}

0 comments on commit a27a397

Please sign in to comment.