diff --git a/app/src/main/graphql/fragments.graphql b/app/src/main/graphql/fragments.graphql index 28a249524e..cb55881e2a 100644 --- a/app/src/main/graphql/fragments.graphql +++ b/app/src/main/graphql/fragments.graphql @@ -159,6 +159,7 @@ fragment backing on Backing { cancelable pledgedOn backerCompleted + isPostCampaign project { ... project } @@ -290,6 +291,12 @@ fragment reward on Reward { amount { ... amount } + pledgeAmount { + ... amount + } + latePledgeAmount { + ... amount + } convertedAmount{ ... amount } diff --git a/app/src/main/java/com/kickstarter/mock/factories/RewardFactory.kt b/app/src/main/java/com/kickstarter/mock/factories/RewardFactory.kt index edc982ceb4..d693002d97 100644 --- a/app/src/main/java/com/kickstarter/mock/factories/RewardFactory.kt +++ b/app/src/main/java/com/kickstarter/mock/factories/RewardFactory.kt @@ -53,6 +53,8 @@ object RewardFactory { .description(description) .estimatedDeliveryOn(ESTIMATED_DELIVERY) .minimum(20.0) + .pledgeAmount(20.0) + .latePledgeAmount(30.0) .shippingPreference("unrestricted") .shippingType(Reward.SHIPPING_TYPE_NO_SHIPPING) .title("Digital Bundle") @@ -96,6 +98,8 @@ object RewardFactory { return reward().toBuilder() .id(rewardId) .minimum(10.0) + .pledgeAmount(10.0) + .latePledgeAmount(20.0) .isAddOn(true) .addOnsItems( listOf( @@ -124,6 +128,8 @@ object RewardFactory { fun maxReward(country: Country): Reward { return reward().toBuilder() .minimum(country.maxPledge.toDouble()) + .pledgeAmount(country.maxPledge.toDouble()) + .latePledgeAmount(country.maxPledge.toDouble() + 10.0) .backersCount(0) .build() } @@ -137,6 +143,8 @@ object RewardFactory { .description("A digital download of the album and documentary.") .limit(50) .minimum(20.0) + .pledgeAmount(20.0) + .latePledgeAmount(30.0) .remaining(0) .title("Digital Bundle") .build() @@ -188,6 +196,8 @@ object RewardFactory { .estimatedDeliveryOn(null) .description("No reward") .minimum(1.0) + .pledgeAmount(1.0) + .latePledgeAmount(1.0) .build() } diff --git a/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt b/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt index def4656b11..9500ada2b3 100644 --- a/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt +++ b/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt @@ -163,6 +163,7 @@ open class MockApolloClientV2 : ApolloClientTypeV2 { override fun updateBacking(updateBackingData: UpdateBackingData): io.reactivex.Observable { return io.reactivex.Observable.empty() } + override fun createBacking(createBackingData: CreateBackingData): io.reactivex.Observable { return io.reactivex.Observable.empty() } diff --git a/app/src/main/java/com/kickstarter/models/Backing.kt b/app/src/main/java/com/kickstarter/models/Backing.kt index 59c1c68633..41d289b730 100644 --- a/app/src/main/java/com/kickstarter/models/Backing.kt +++ b/app/src/main/java/com/kickstarter/models/Backing.kt @@ -34,6 +34,7 @@ class Backing private constructor( private val status: String, private val addOns: List?, private val bonusAmount: Double, + private val isPostCampaign: Boolean ) : Parcelable, Relay { fun amount() = this.amount fun backer() = this.backer @@ -61,6 +62,7 @@ class Backing private constructor( fun status() = this.status fun addOns() = this.addOns fun bonusAmount() = this.bonusAmount + fun isPostCampaign() = this.isPostCampaign @Parcelize data class Builder( @@ -90,6 +92,7 @@ class Backing private constructor( private var status: String = "", private var addOns: List? = null, private var bonusAmount: Double = 0.0, + private var isPostCampaign: Boolean = false ) : Parcelable { fun amount(amount: Double?) = apply { this.amount = amount ?: 0.0 } fun backer(backer: User?) = apply { this.backer = backer } @@ -116,6 +119,7 @@ class Backing private constructor( fun status(status: String?) = apply { this.status = status ?: "" } fun addOns(addOns: List?) = apply { this.addOns = addOns ?: emptyList() } fun bonusAmount(bonusAmount: Double?) = apply { this.bonusAmount = bonusAmount ?: 0.0 } + fun isPostCampaign(isPostCampaign: Boolean) = apply { this.isPostCampaign = isPostCampaign } fun build() = Backing( amount = amount, backer = backer, @@ -141,7 +145,8 @@ class Backing private constructor( shippingAmount = shippingAmount, status = status, addOns = addOns, - bonusAmount = bonusAmount + bonusAmount = bonusAmount, + isPostCampaign = isPostCampaign ) } @@ -170,7 +175,8 @@ class Backing private constructor( shippingAmount = shippingAmount, status = status, addOns = addOns, - bonusAmount = bonusAmount + bonusAmount = bonusAmount, + isPostCampaign = isPostCampaign ) override fun equals(other: Any?): Boolean { diff --git a/app/src/main/java/com/kickstarter/models/Reward.kt b/app/src/main/java/com/kickstarter/models/Reward.kt index e56da5b80d..282847de0f 100644 --- a/app/src/main/java/com/kickstarter/models/Reward.kt +++ b/app/src/main/java/com/kickstarter/models/Reward.kt @@ -15,6 +15,8 @@ class Reward private constructor( private val id: Long, private val limit: Int?, private val minimum: Double, + private val pledgeAmount: Double, + private val latePledgeAmount: Double, private val estimatedDeliveryOn: DateTime?, private val remaining: Int?, private val rewardsItems: List?, @@ -57,6 +59,8 @@ class Reward private constructor( override fun id() = this.id fun limit() = this.limit fun minimum() = this.minimum + fun pledgeAmount() = this.pledgeAmount + fun latePledgeAmount() = this.latePledgeAmount fun estimatedDeliveryOn() = this.estimatedDeliveryOn fun remaining() = this.remaining fun rewardsItems() = this.rewardsItems @@ -85,6 +89,8 @@ class Reward private constructor( private var id: Long = 0L, private var limit: Int? = null, private var minimum: Double = 0.0, + private var pledgeAmount: Double = 0.0, + private var latePledgeAmount: Double = 0.0, private var estimatedDeliveryOn: DateTime? = null, private var remaining: Int? = null, private var rewardsItems: List? = emptyList(), @@ -110,6 +116,8 @@ class Reward private constructor( fun id(id: Long?) = apply { this.id = id ?: -1L } fun limit(limit: Int?) = apply { this.limit = limit } fun minimum(minimum: Double?) = apply { this.minimum = minimum ?: 0.0 } + fun pledgeAmount(pledgeAmount: Double?) = apply { this.pledgeAmount = pledgeAmount ?: 0.0 } + fun latePledgeAmount(latePledgeAmount: Double?) = apply { this.latePledgeAmount = latePledgeAmount ?: 0.0 } fun estimatedDeliveryOn(estimatedDeliveryOn: DateTime?) = apply { this.estimatedDeliveryOn = estimatedDeliveryOn } @@ -136,6 +144,8 @@ class Reward private constructor( id = id, limit = limit, minimum = minimum, + pledgeAmount = pledgeAmount, + latePledgeAmount = latePledgeAmount, estimatedDeliveryOn = estimatedDeliveryOn, remaining = remaining, rewardsItems = rewardsItems, @@ -173,6 +183,8 @@ class Reward private constructor( id = id, limit = limit, minimum = minimum, + pledgeAmount = pledgeAmount, + latePledgeAmount = latePledgeAmount, estimatedDeliveryOn = estimatedDeliveryOn, remaining = remaining, rewardsItems = rewardsItems, @@ -201,6 +213,8 @@ class Reward private constructor( id() == other.id() && limit() == other.limit() && minimum() == other.minimum() && + pledgeAmount() == other.pledgeAmount() && + latePledgeAmount() == other.latePledgeAmount() && estimatedDeliveryOn() == other.estimatedDeliveryOn() && remaining() == other.remaining() && rewardsItems() == other.rewardsItems() && diff --git a/app/src/main/java/com/kickstarter/services/KSApolloClientV2.kt b/app/src/main/java/com/kickstarter/services/KSApolloClientV2.kt index 8162a8eb80..a4fbf5fe41 100644 --- a/app/src/main/java/com/kickstarter/services/KSApolloClientV2.kt +++ b/app/src/main/java/com/kickstarter/services/KSApolloClientV2.kt @@ -699,7 +699,7 @@ class KSApolloClientV2(val service: ApolloClient, val gson: Gson) : ApolloClient return Observable.defer { val updateBackingMutation = UpdateBackingMutation.builder() .backingId(encodeRelayId(updateBackingData.backing)) - .amount(updateBackingData.amount.toString()) + .amount(updateBackingData.amount) .locationId(updateBackingData.locationId) .rewardIds(updateBackingData.rewardsIds?.let { list -> list.map { encodeRelayId(it) } }) .apply { diff --git a/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt b/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt index eebaf92824..4fd0801c51 100644 --- a/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt +++ b/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt @@ -122,6 +122,8 @@ fun rewardTransformer( addOnItems: List = emptyList() ): Reward { val amount = rewardGr.amount().fragments().amount().amount()?.toDouble() ?: 0.0 + val latePledgeAmount = rewardGr.latePledgeAmount().fragments().amount().amount()?.toDouble() ?: 0.0 + val pledgeAmount = rewardGr.pledgeAmount().fragments().amount().amount()?.toDouble() ?: 0.0 val convertedAmount = rewardGr.convertedAmount().fragments().amount().amount()?.toDouble() ?: 0.0 val desc = rewardGr.description() @@ -156,6 +158,8 @@ fun rewardTransformer( .title(title) .convertedMinimum(convertedAmount) .minimum(amount) + .pledgeAmount(pledgeAmount) + .latePledgeAmount(latePledgeAmount) .limit(limit) .remaining(remaining) .endsAt(endsAt) @@ -712,6 +716,8 @@ fun backingTransformer(backingGr: fragment.Backing?): Backing { .build() val status = backingGr?.status()?.rawValue() ?: "" + val isPostCampaign = backingGr?.isPostCampaign ?: false + return Backing.builder() .amount(backingGr?.amount()?.fragments()?.amount()?.amount()?.toDouble() ?: 0.0) .bonusAmount(backingGr?.bonusAmount()?.fragments()?.amount()?.amount()?.toDouble() ?: 0.0) @@ -733,6 +739,7 @@ fun backingTransformer(backingGr: fragment.Backing?): Backing { .status(status) .cancelable(backingGr?.cancelable() ?: false) .completedByBacker(completedByBacker) + .isPostCampaign(isPostCampaign) .build() } diff --git a/app/src/main/java/com/kickstarter/viewmodels/AddOnViewHolderViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/AddOnViewHolderViewModel.kt index 8933dc58a1..5d5b7cb35b 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/AddOnViewHolderViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/AddOnViewHolderViewModel.kt @@ -188,7 +188,20 @@ interface AddOnViewHolderViewModel { this.ksCurrency.format(it.second.convertedMinimum(), it.first, true, RoundingMode.HALF_UP, true) private fun buildCurrency(project: Project, reward: Reward): String { - val completeCurrency = ksCurrency.format(reward.minimum(), project, RoundingMode.HALF_UP) + val completeCurrency = if (project.backing()?.isPostCampaign() == true) { + if (reward.latePledgeAmount() > 0) { + ksCurrency.format(reward.latePledgeAmount(), project, RoundingMode.HALF_UP) + } else { + ksCurrency.format(reward.minimum(), project, RoundingMode.HALF_UP) + } + } else { + if (reward.pledgeAmount() > 0) { + ksCurrency.format(reward.pledgeAmount(), project, RoundingMode.HALF_UP) + } else { + ksCurrency.format(reward.minimum(), project, RoundingMode.HALF_UP) + } + } + val country = Country.findByCurrencyCode(project.currency()) ?: "" return completeCurrency.removePrefix(country.toString()) diff --git a/app/src/main/java/com/kickstarter/viewmodels/PledgeFragmentViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/PledgeFragmentViewModel.kt index 6bade10b6f..258473b965 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/PledgeFragmentViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/PledgeFragmentViewModel.kt @@ -619,7 +619,7 @@ interface PledgeFragmentViewModel { val pledgeAmountHeader = this.rewardAndAddOns .filter { !RewardUtils.isNoReward(it.first()) } - .map { getPledgeAmount(it) } + .map { getPledgeAmount(it, backing.blockingLast(Backing.builder().build()).isPostCampaign()) } pledgeAmountHeader .compose>(combineLatestPair(project)) @@ -1363,13 +1363,18 @@ interface PledgeFragmentViewModel { totalString, locationId, extendedListForCheckOut, - paymentMethod - ) { b, a, l, r, pMethod -> - this.getUpdateBackingData(b, a, l, r, pMethod) + paymentMethod, + project + ) { b, a, l, r, pMethod, pro -> + if (pro.isBacking() && pro.backing()?.amount().toString() == a) { + Pair(this.getUpdateBackingData(b, null, l, r, pMethod), pro) + } else { + Pair(this.getUpdateBackingData(b, a, l, r, pMethod), pro) + } } - .compose(takeWhenV2(Observable.merge(updatePledgeClick, updatePaymentClick, fixPaymentClick))) + .compose>(takeWhenV2(Observable.merge(updatePledgeClick, updatePaymentClick, fixPaymentClick))) .switchMap { - this.apolloClient.updateBacking(it) + this.apolloClient.updateBacking(it.first) .doOnSubscribe { this.pledgeProgressIsGone.onNext(false) this.pledgeButtonIsEnabled.onNext(false) @@ -1690,11 +1695,29 @@ interface PledgeFragmentViewModel { /** * Calculate the pledge amount for the selected reward + addOns */ - private fun getPledgeAmount(rewards: List): Double { + private fun getPledgeAmount(rewards: List, isLatePledge: Boolean): Double { var totalPledgeAmount = 0.0 rewards.forEach { - totalPledgeAmount += if (RewardUtils.isNoReward(it) && !it.isAddOn()) it.minimum() // - Cost of the selected Reward - else it.quantity()?.let { q -> (q * it.minimum()) } ?: it.minimum() // - Cost of each addOn + totalPledgeAmount += if (isLatePledge) { + if (it.latePledgeAmount() > 0) { + if (RewardUtils.isNoReward(it) && !it.isAddOn()) it.latePledgeAmount() // - Cost of the selected Reward + else it.quantity()?.let { q -> (q * it.latePledgeAmount()) } ?: it.latePledgeAmount() // - Cost of each addOn + } else { + // We don't have a late pledge amount to work with, use the default minimum + if (RewardUtils.isNoReward(it) && !it.isAddOn()) it.minimum() // - Default cost of the selected Reward + else it.quantity()?.let { q -> (q * it.minimum()) } ?: it.minimum() // - Default cost of each addOn + } + } else { + // We have a pledge amount to work with, use it + if (it.pledgeAmount() > 0.0) { + if (RewardUtils.isNoReward(it) && !it.isAddOn()) it.pledgeAmount() // - Cost of the selected Reward during the campaign + else it.quantity()?.let { q -> (q * it.pledgeAmount()) } ?: it.pledgeAmount() // - Cost of each addOn during the campaign + } else { + // We don't have a pledge amount to work with, use the default minimum + if (RewardUtils.isNoReward(it) && !it.isAddOn()) it.minimum() // - Default cost of the selected Reward + else it.quantity()?.let { q -> (q * it.minimum()) } ?: it.minimum() // - Default cost of each addOn + } + } } return totalPledgeAmount } @@ -2074,7 +2097,7 @@ fun PledgeFragmentViewModel.PledgeFragmentViewModel.getUpdateBackingData( backing: Backing, amount: String? = null, locationId: String? = null, - rewardsList: List, + rewardsList: List = listOf(), pMethod: StoredCard? = null ): UpdateBackingData { return pMethod?.let { card -> diff --git a/app/src/test/java/com/kickstarter/viewmodels/PledgeFragmentViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/PledgeFragmentViewModelTest.kt index 5e5b66dbd7..ce7896ee45 100644 --- a/app/src/test/java/com/kickstarter/viewmodels/PledgeFragmentViewModelTest.kt +++ b/app/src/test/java/com/kickstarter/viewmodels/PledgeFragmentViewModelTest.kt @@ -138,6 +138,7 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { private val showError = TestSubscriber() private val loadingState = TestSubscriber() private val thirdPartyEvent = TestSubscriber() + private val pledgeAmountHeader = TestSubscriber() private val disposables = CompositeDisposable() @After @@ -246,6 +247,7 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { this.vm.outputs.showError().subscribe { this.showError.onNext(it) }.addToDisposable(disposables) this.vm.outputs.setState().subscribe { this.loadingState.onNext(it) }.addToDisposable(disposables) this.vm.outputs.eventSent().subscribe { this.thirdPartyEvent.onNext(it) }.addToDisposable(disposables) + this.vm.outputs.pledgeAmountHeader().subscribe { this.pledgeAmountHeader.onNext(it.toString()) }.addToDisposable(disposables) } @Test @@ -3377,6 +3379,8 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { val reward = RewardFactory.rewardWithShipping().toBuilder() .hasAddons(true) .minimum(50.0) + .pledgeAmount(50.0) + .latePledgeAmount(60.0) .build() val project = ProjectFactory.project() .toBuilder() @@ -3386,6 +3390,8 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { val addOn = RewardFactory.itemizedAddOn().toBuilder().quantity(2) .minimum(9.0) + .pledgeAmount(9.0) + .latePledgeAmount(10.0) .shippingRules( listOf( ShippingRuleFactory.usShippingRule() @@ -3399,6 +3405,8 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { val addOn2 = RewardFactory.itemizedAddOn().toBuilder().quantity(4) .minimum(11.0) + .pledgeAmount(11.0) + .latePledgeAmount(15.0) .shippingRules( listOf( ShippingRuleFactory.usShippingRule() @@ -3412,6 +3420,8 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { val addOn3 = RewardFactory.itemizedAddOn().toBuilder().quantity(10) .minimum(15.0) + .pledgeAmount(15.0) + .latePledgeAmount(20.0) .shippingRules( listOf( ShippingRuleFactory.usShippingRule() @@ -3439,6 +3449,8 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { val reward = RewardFactory.rewardWithShipping().toBuilder() .hasAddons(true) .minimum(50.0) + .pledgeAmount(50.0) + .latePledgeAmount(60.0) .build() val project = ProjectFactory.project() .toBuilder() @@ -3448,6 +3460,8 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { val addOn = RewardFactory.itemizedAddOn().toBuilder().quantity(2) .minimum(9.0) + .pledgeAmount(9.0) + .latePledgeAmount(10.0) .shippingRules( listOf( ShippingRuleFactory.usShippingRule() @@ -3461,6 +3475,8 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { val addOn2 = RewardFactory.itemizedAddOn().toBuilder().quantity(4) .minimum(11.0) + .pledgeAmount(11.0) + .latePledgeAmount(12.0) .shippingRules( listOf( ShippingRuleFactory.usShippingRule() @@ -3474,6 +3490,8 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { val addOn3 = RewardFactory.itemizedAddOn().toBuilder().quantity(10) .minimum(15.0) + .pledgeAmount(15.0) + .latePledgeAmount(20.0) .shippingRules( listOf( ShippingRuleFactory.usShippingRule() @@ -3545,6 +3563,56 @@ class PledgeFragmentViewModelTest : KSRobolectricTestCase() { assertNotNull(updateBackingData.backing) } + @Test + fun `test_when_backing_is_not_late_pledge_then_pledge_amount_header_is_correct`() { + val shipRule = ShippingRuleFactory.usShippingRule() + val reward = RewardFactory.rewardWithShipping() + val backing = BackingFactory.backing() + .toBuilder() + .amount(20.0) + .shippingAmount(0f) + .reward(reward) + .rewardId(reward.id()) + .isPostCampaign(false) // original price from campaign should be used ($20) + .build() + val backedProject = ProjectFactory.backedProject() + .toBuilder() + .backing(backing) + .build() + + setUpEnvironment(environment(), reward, backedProject, PledgeReason.UPDATE_PAYMENT) + + vm.shippingRuleSelected(shipRule) + + // Reward amount is 20 with no shipping + this.pledgeAmountHeader.assertValues("$20") + } + + @Test + fun `test_when_backing_is_late_pledge_then_pledge_amount_header_is_correct`() { + val shipRule = ShippingRuleFactory.usShippingRule() + val reward = RewardFactory.rewardWithShipping() + val backing = BackingFactory.backing() + .toBuilder() + .amount(30.0) + .shippingAmount(0f) + .reward(reward) + .rewardId(reward.id()) + .isPostCampaign(true) // new price from late pledges should be used ($30) + .build() + val backedProject = ProjectFactory.backedProject() + .toBuilder() + .backing(backing) + .build() + + setUpEnvironment(environment(), reward, backedProject, PledgeReason.UPDATE_PAYMENT) + + vm.shippingRuleSelected(shipRule) + + // Reward amount for late pledge is 30 with no shipping + this.pledgeAmountHeader.assertValues("$30") + } + private fun assertInitialPledgeCurrencyStates_NoShipping_USProject(environment: Environment, project: Project) { this.additionalPledgeAmount.assertValue(expectedCurrency(environment, project, 0.0)) this.conversionText.assertNoValues()