Skip to content

Commit

Permalink
MBL-1850: Restricted shipping to EU wrong shipping cost being charged…
Browse files Browse the repository at this point in the history
… to the backer (#2163)
  • Loading branch information
Arkariang authored Nov 13, 2024
1 parent c4a72f9 commit 6712519
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 73 deletions.
13 changes: 10 additions & 3 deletions app/src/main/graphql/fragments.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ fragment fullProject on Project {
items {
... rewardItems
}
... on Reward {
simpleShippingRulesExpanded {
cost
country
estimatedMax
estimatedMin
locationId
locationName
}
}
}
}
risks
Expand Down Expand Up @@ -300,9 +310,6 @@ fragment reward on Reward {
convertedAmount{
... amount
}
shippingRules {
... shippingRule
}
shippingPreference
remainingQuantity
limit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ fun environmentalCommitmentTransformer(envCommit: fragment.EnvironmentalCommitme
fun rewardTransformer(
rewardGr: fragment.Reward,
shippingRulesExpanded: List<fragment.ShippingRule> = emptyList(),
simpleShippingRules: List<FullProject.SimpleShippingRulesExpanded> = emptyList(),
allowedAddons: Boolean = false,
rewardItems: List<RewardsItem> = emptyList(),
addOnItems: List<RewardsItem> = emptyList()
Expand Down Expand Up @@ -161,15 +162,12 @@ fun rewardTransformer(
val limit = if (isAddOn) chooseLimit(rewardGr.limit(), rewardGr.limitPerBacker())
else rewardGr.limit()

val shippingRules = if (shippingRulesExpanded.isNotEmpty()) {
shippingRulesExpanded.map {
shippingRuleTransformer(it)
}
} else {
rewardGr.shippingRules().map {
shippingRuleTransformer(it.fragments().shippingRule())
}
}
val shippingRules =
shippingRulesExpanded.takeIf { it.isNotEmpty() }?.map { shippingRuleTransformer(it) }
?: simpleShippingRules.takeIf { it.isNotEmpty() }?.map {
return@map simpleShippingRuleTransformer(it)
}
?: emptyList()

val localReceiptLocation = locationTransformer(rewardGr.localReceiptLocation()?.fragments()?.location())

Expand Down Expand Up @@ -200,6 +198,30 @@ fun rewardTransformer(
.build()
}

fun simpleShippingRuleTransformer(simpleShippingRules: FullProject.SimpleShippingRulesExpanded): ShippingRule {
val id = decodeRelayId(simpleShippingRules.locationId()) ?: -1
val country = simpleShippingRules.country() ?: ""
val displayName = simpleShippingRules.locationName()

val location = Location.builder()
.id(id)
.country(country)
.displayableName(displayName)
.name(displayName)
.build()
val cost = simpleShippingRules.cost()?.toDoubleOrNull() ?: 0.0
val estimatedMin = simpleShippingRules.estimatedMin()?.toDoubleOrNull() ?: 0.0
val estimatedMax = simpleShippingRules.estimatedMax()?.toDoubleOrNull() ?: 0.0

return ShippingRule.builder()
.id(id)
.location(location)
.cost(cost)
.estimatedMax(estimatedMax)
.estimatedMin(estimatedMin)
.build()
}

/**
* Choose the available limit being the smallest one, we can have limit by backer available just in add-ons
* or limit by reward, available in V1 and Graphql and for both add-ons and Rewards
Expand Down Expand Up @@ -305,8 +327,10 @@ fun projectTransformer(projectFragment: FullProject?): Project {
val minPledge = projectFragment?.minPledge()?.toDouble() ?: 1.0
val rewards =
projectFragment?.rewards()?.nodes()?.map {
val shippingRules = it.simpleShippingRulesExpanded()
rewardTransformer(
it.fragments().reward(),
simpleShippingRules = shippingRules,
allowedAddons = it.allowedAddons().pageInfo().startCursor()?.isNotEmpty() ?: false,
rewardItems = complexRewardItemsTransformer(it.items()?.fragments()?.rewardItems())
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ class RewardsSelectionViewModel(private val environment: Environment, private va
environment.currentConfigV2()?.observable()?.asFlow()?.collectLatest {
if (shippingRulesUseCase == null) {
shippingRulesUseCase = GetShippingRulesUseCase(
apolloClient,
projectData.project(),
it,
viewModelScope,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@ import com.kickstarter.models.Location
import com.kickstarter.models.Project
import com.kickstarter.models.Reward
import com.kickstarter.models.ShippingRule
import com.kickstarter.services.ApolloClientTypeV2
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx2.asFlow

data class ShippingRulesState(
val shippingRules: List<ShippingRule> = emptyList(),
Expand All @@ -43,7 +39,6 @@ data class ShippingRulesState(
* As the UseCase is lifecycle agnostic and is scoped to the class that uses it.
*/
class GetShippingRulesUseCase(
private val apolloClient: ApolloClientTypeV2,
private val project: Project,
private val config: Config?,
private val scope: CoroutineScope,
Expand Down Expand Up @@ -97,7 +92,7 @@ class GetShippingRulesUseCase(
if (rewardsByShippingType.isNotEmpty() && project.isAllowedToPledge()) {
rewardsByShippingType.forEachIndexed { index, reward ->

if (RewardUtils.shipsToRestrictedLocations(reward)) {
if (RewardUtils.shipsToRestrictedLocations(reward) || RewardUtils.shipsWorldwide(reward)) {
reward.shippingRules()?.map {
avShipMap.put(
requireNotNull(
Expand All @@ -107,9 +102,6 @@ class GetShippingRulesUseCase(
)
}
}
if (RewardUtils.shipsWorldwide(reward)) {
getGlobalShippingRulesForReward(reward, avShipMap)
}

// - Filter rewards once all shipping rules have been collected
if (index == rewardsByShippingType.size - 1) {
Expand Down Expand Up @@ -162,29 +154,6 @@ class GetShippingRulesUseCase(
)
}

private suspend fun getGlobalShippingRulesForReward(
reward: Reward,
shippingRules: MutableMap<Long, ShippingRule>
) {
apolloClient.getShippingRules(reward)
.asFlow()
.map { rulesEnvelope ->
rulesEnvelope.shippingRules()?.map { rule ->
rule?.let {
shippingRules.put(
requireNotNull(
it.location()?.id()
),
it
)
}
}
}
.catch { throwable ->
emitCurrentState(isLoading = false, errorMessage = throwable?.message)
}.collect()
}

/**
* Check if the given @param rule is available in the list
* of @param allAvailableShippingRules for this project.
Expand Down
140 changes: 140 additions & 0 deletions app/src/test/java/com/kickstarter/services/GraphQLTransformersTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,26 @@ import com.kickstarter.services.transformers.aiDisclosureTransformer
import com.kickstarter.services.transformers.decodeRelayId
import com.kickstarter.services.transformers.environmentalCommitmentTransformer
import com.kickstarter.services.transformers.projectFaqTransformer
import com.kickstarter.services.transformers.rewardTransformer
import com.kickstarter.services.transformers.simpleShippingRuleTransformer
import com.kickstarter.services.transformers.updateTransformer
import com.kickstarter.services.transformers.userPrivacyTransformer
import com.kickstarter.services.transformers.userTransformer
import fragment.AiDisclosure
import fragment.Amount
import fragment.EnvironmentalCommitment
import fragment.Faq
import fragment.FullProject
import fragment.Reward
import fragment.Reward.AllowedAddons
import fragment.User
import org.joda.time.DateTime
import org.junit.Test
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
import type.EnvironmentalCommitmentCategory
import type.RewardType
import type.ShippingPreference

class GraphQLTransformersTest : KSRobolectricTestCase() {

Expand Down Expand Up @@ -176,4 +184,136 @@ class GraphQLTransformersTest : KSRobolectricTestCase() {
assertTrue(post.publishedAt() == date)
assertTrue(post.updatedAt() == date)
}

@Test
fun `test simpleShippingRuleTransformer provides appropriate shippingRule`() {
val canadaSimpleSR = FullProject.SimpleShippingRulesExpanded(
"SimpleShippingRule",
"17.34562379823645234875620384756203847234",
"CA",
null,
null,
"TG9jYXRpb24tMjM0MjQ3NzU=",
"Canada"
)

val australiaSR = FullProject.SimpleShippingRulesExpanded(
"SimpleShippingRule",
"0",
"AU",
"20", // - Shipping Rule disable at shipping, cost = 0, estimated range max/min values not null
"2",
"TG9jYXRpb24tMjM0MjQ3NDg=",
"Australia"
)

val forbiddenValues = FullProject.SimpleShippingRulesExpanded(
"SimpleShippingRule",
"Pikachusito",
"AU",
"AyOma", // - Shipping Rule disable at shipping, cost = 0, estimated range max/min values not null
"",
"TG9jYXRpb24tMjM0MjQ3NDg=",
"Australia"
)

val cadShipping = simpleShippingRuleTransformer(canadaSimpleSR)
val ausShipping = simpleShippingRuleTransformer(australiaSR)

// - Expected output model in case of forbidden values (non-numeric) on Cost/Monetary related fields
val nonDoubleValues = simpleShippingRuleTransformer(forbiddenValues)

assert(cadShipping.location()?.id() == decodeRelayId(canadaSimpleSR.locationId()))
assert(cadShipping.cost() == 17.345623798236453) // - Rounds up after 15th digit
assert(cadShipping.estimatedMax() == 0.0)
assert(cadShipping.estimatedMin() == 0.0)
assert(cadShipping.location()?.name() == canadaSimpleSR.locationName())
assert(cadShipping.location()?.displayableName() == canadaSimpleSR.locationName())
assert(cadShipping.location()?.country() == canadaSimpleSR.country())

assert(ausShipping.location()?.id() == decodeRelayId(australiaSR.locationId()))
assert(ausShipping.cost() == 0.0)
assert(ausShipping.estimatedMax() == 20.0)
assert(ausShipping.estimatedMin() == 2.0)
assert(ausShipping.location()?.name() == australiaSR.locationName())
assert(ausShipping.location()?.displayableName() == australiaSR.locationName())
assert(ausShipping.location()?.country() == australiaSR.country())

assert(nonDoubleValues.location()?.id() == decodeRelayId(forbiddenValues.locationId()))
assert(nonDoubleValues.cost() == 0.0)
assert(nonDoubleValues.estimatedMax() == 0.0)
assert(nonDoubleValues.estimatedMin() == 0.0)
assert(nonDoubleValues.location()?.name() == forbiddenValues.locationName())
assert(nonDoubleValues.location()?.displayableName() == forbiddenValues.locationName())
assert(nonDoubleValues.location()?.country() == forbiddenValues.country())
}

/**
* Reward{__typename=Reward, id=UmV3YXJkLTk2NTM3NzY=, name=Stormgate Supporter, ...
* Mock of a rewardFragment received from GraphQL data types
*/
val fragmentReward = Reward(
"Reward",
"UmV3YXJkLTk2NTM3NzY",
"Stormgate Supporter",
452,
"Some reward description here",
DateTime.now().toDate(),
true,
Reward.Amount("Amount", Reward.Amount.Fragments(Amount("Money", "20.0", null, null))),
Reward.PledgeAmount(
"PledgeAmount",
Reward.PledgeAmount.Fragments(Amount("Money", "10.0", null, null))
),
Reward.LatePledgeAmount(
"LatePledgeAmount",
Reward.LatePledgeAmount.Fragments(Amount("Money", "30.0", null, null))
),
Reward.ConvertedAmount(
"ConvertedAmount",
Reward.ConvertedAmount.Fragments(Amount("Money", "30.0", null, null))
),
ShippingPreference.UNRESTRICTED,
3,
3,
3,
DateTime.now().minusDays(50),
DateTime.now().plusDays(50),
RewardType.BASE,
AllowedAddons("RewardConnection", listOf(Reward.Node("Reward", "UmV3YXJkLTk3MDA2NjA"))),
null,
)

@Test
fun `test rewardTransformer returns appropriate shippingRules field when querying for simpleShippingRulesExpanded`() {

val canadaSimpleSR = FullProject.SimpleShippingRulesExpanded(
"SimpleShippingRule",
"17.34562379823645234875620384756203847234",
"CA",
null,
null,
"TG9jYXRpb24tMjM0MjQ3NzU=",
"Canada"
)

val australiaSR = FullProject.SimpleShippingRulesExpanded(
"SimpleShippingRule",
"0",
"AU",
"20", // - Shipping Rule disable at shipping, cost = 0, estimated range max/min values not null
"2",
"TG9jYXRpb24tMjM0MjQ3NDg=",
"Australia"
)

val reward = rewardTransformer(
rewardGr = fragmentReward,
simpleShippingRules = listOf(canadaSimpleSR, australiaSR)
)

assertTrue(reward.shippingRules()?.size == 2)
assertTrue(reward.shippingRules()?.first()?.id() == decodeRelayId(canadaSimpleSR.locationId()))
assertTrue(reward.shippingRules()?.last()?.id() == decodeRelayId(australiaSR.locationId()))
}
}
Loading

0 comments on commit 6712519

Please sign in to comment.