diff --git a/Library/SharedFunctions.swift b/Library/SharedFunctions.swift index 6ed9afcc6f..add77b8be5 100644 --- a/Library/SharedFunctions.swift +++ b/Library/SharedFunctions.swift @@ -662,25 +662,16 @@ public func isRewardDigital(_ reward: Reward?) -> Bool { } public func estimatedShippingText( - for reward: Reward, + for rewards: [Reward], project: Project, selectedShippingRule: ShippingRule ) -> String { - guard reward.shipping.enabled else { return "" } - - /// Make sure the current reward has shipping rules and that one of them matches the selected shipping rule (from the locations dropdown). - guard let shippingRules = reward.shippingRules, - let currentRewardShippingRule = shippingRules - .first(where: { $0.location.country == selectedShippingRule.location.country }) - else { - return "" - } + let (estimatedMin, estimatedMax) = estimatedMinMax( + from: rewards, + selectedShippingRule: selectedShippingRule + ) - guard let estimatedMin = currentRewardShippingRule.estimatedMin?.amount, - let estimatedMax = currentRewardShippingRule.estimatedMax?.amount, - estimatedMin > 0 || estimatedMax > 0 else { - return "" - } + guard estimatedMin > 0, estimatedMax > 0 else { return "" } let currentCountry = project.stats.currentCountry ?? Project.Country.us @@ -706,25 +697,18 @@ public func estimatedShippingText( } public func estimatedShippingConversionText( - for reward: Reward, + for rewards: [Reward], project: Project, selectedShippingRule: ShippingRule ) -> String { guard project.stats.needsConversion else { return "" } - /// Make sure the current reward has shipping rules and that one of them matches the selected shipping rule (from the locations dropdown). - guard let shippingRules = reward.shippingRules, - let selectedShippingRule = shippingRules - .first(where: { $0.location.country == selectedShippingRule.location.country }) - else { - return "" - } + let (estimatedMin, estimatedMax) = estimatedMinMax( + from: rewards, + selectedShippingRule: selectedShippingRule + ) - guard let estimatedMin = selectedShippingRule.estimatedMin?.amount, - let estimatedMax = selectedShippingRule.estimatedMax?.amount, - estimatedMin > 0 || estimatedMax > 0 else { - return "" - } + guard estimatedMin > 0, estimatedMax > 0 else { return "" } let convertedMin = estimatedMin * Double(project.stats.currentCurrencyRate ?? project.stats.staticUsdRate) let convertedMax = estimatedMax * Double(project.stats.currentCurrencyRate ?? project.stats.staticUsdRate) @@ -764,3 +748,38 @@ public func attributedCurrency(withProject project: Project, total: Double) -> N superscriptAttributes: checkoutCurrencySuperscriptAttributes() ) } + +private func estimatedMinMax( + from rewards: [Reward], + selectedShippingRule: ShippingRule +) -> (Double, Double) { + var min: Double = 0 + var max: Double = 0 + + rewards.forEach { reward in + guard reward.shipping.enabled, let shippingRules = reward.shippingRules else { return } + + var shippingRule: ShippingRule? + + /// If the reward's shipping prefernce is Anywhere in the world, use its first and only shipping rule. + /// Else use the rule that matches the selected shipping rule (from the locations dropdown). + shippingRule = reward.shipping.preference == .unrestricted + ? shippingRules.first + : shippingRules + .first(where: { $0.location.country == selectedShippingRule.location.country }) + + guard let shipping = shippingRule else { return } + + /// Verify there are estimated amounts greater than 0 + guard let estimatedMin = shipping.estimatedMin?.amount, + let estimatedMax = shipping.estimatedMax?.amount, + estimatedMin > 0 || estimatedMax > 0 else { + return + } + + min += estimatedMin + max += estimatedMax + } + + return (min, max) +} diff --git a/Library/SharedFunctionsTests.swift b/Library/SharedFunctionsTests.swift index 1842853c57..2870aacd87 100644 --- a/Library/SharedFunctionsTests.swift +++ b/Library/SharedFunctionsTests.swift @@ -671,4 +671,32 @@ final class SharedFunctionsTests: TestCase { XCTAssertTrue(isRewardDigital(reward)) } + + func test_estimatedShippingText() { + let rewardShippingRule = ShippingRule.template + |> ShippingRule.lens.estimatedMin .~ Money(amount: 2) + |> ShippingRule.lens.estimatedMax .~ Money(amount: 7) + let addOnShippingRule = ShippingRule.template + |> ShippingRule.lens.estimatedMin .~ Money(amount: 1) + |> ShippingRule.lens.estimatedMax .~ Money(amount: 5) + + let reward = Reward.template + |> Reward.lens.shipping .~ (.template |> Reward.Shipping.lens.enabled .~ true) + |> Reward.lens.shippingRules .~ [rewardShippingRule] + |> Reward.lens.id .~ 99 + let addOn = Reward.template + |> Reward.lens.shipping .~ (.template |> Reward.Shipping.lens.enabled .~ true) + |> Reward.lens.id .~ 5 + |> Reward.lens.shippingRules .~ [addOnShippingRule] + + let project = Project.template + + let estimatedShipping = estimatedShippingText( + for: [reward, addOn], + project: project, + selectedShippingRule: .template + ) + + XCTAssertEqual(estimatedShipping, "$3-$12") + } } diff --git a/Library/ViewModels/NoShippingPledgeViewModel.swift b/Library/ViewModels/NoShippingPledgeViewModel.swift index 4237724c30..ec60505afc 100644 --- a/Library/ViewModels/NoShippingPledgeViewModel.swift +++ b/Library/ViewModels/NoShippingPledgeViewModel.swift @@ -313,13 +313,13 @@ public class NoShippingPledgeViewModel: NoShippingPledgeViewModelType, NoShippin self.pledgeDisclaimerViewDidTapLearnMoreSignal.mapConst(.trust) ) - self.configureEstimatedShippingView = Signal.combineLatest(project, baseReward, selectedShippingRule) - .map { project, reward, shippingRule in + self.configureEstimatedShippingView = Signal.combineLatest(project, rewards, selectedShippingRule) + .map { project, rewards, shippingRule in guard let rule = shippingRule else { return ("", "") } return ( - estimatedShippingText(for: reward, project: project, selectedShippingRule: rule), - estimatedShippingConversionText(for: reward, project: project, selectedShippingRule: rule) + estimatedShippingText(for: rewards, project: project, selectedShippingRule: rule), + estimatedShippingConversionText(for: rewards, project: project, selectedShippingRule: rule) ) } diff --git a/Library/ViewModels/NoShippingPostCampaignCheckoutViewModel.swift b/Library/ViewModels/NoShippingPostCampaignCheckoutViewModel.swift index a098fcb8e1..20de34fa35 100644 --- a/Library/ViewModels/NoShippingPostCampaignCheckoutViewModel.swift +++ b/Library/ViewModels/NoShippingPostCampaignCheckoutViewModel.swift @@ -61,6 +61,7 @@ public class NoShippingPostCampaignCheckoutViewModel: NoShippingPostCampaignChec let context = initialData.map(\.context) let checkoutId = initialData.map(\.checkoutId) let backingId = initialData.map(\.backingId) + let rewards = initialData.map(\.rewards) let baseReward = initialData.map(\.rewards).map(\.first) let project = initialData.map(\.project) let selectedShippingRule = initialData.map(\.selectedShippingRule) @@ -108,13 +109,13 @@ public class NoShippingPostCampaignCheckoutViewModel: NoShippingPostCampaignChec ) } - self.configureEstimatedShippingView = Signal.combineLatest(project, baseReward, selectedShippingRule) - .map { project, baseReward, shippingRule in - guard let rule = shippingRule, let reward = baseReward else { return ("", "") } + self.configureEstimatedShippingView = Signal.combineLatest(project, rewards, selectedShippingRule) + .map { project, rewards, shippingRule in + guard let rule = shippingRule else { return ("", "") } return ( - estimatedShippingText(for: reward, project: project, selectedShippingRule: rule), - estimatedShippingConversionText(for: reward, project: project, selectedShippingRule: rule) + estimatedShippingText(for: rewards, project: project, selectedShippingRule: rule), + estimatedShippingConversionText(for: rewards, project: project, selectedShippingRule: rule) ) } diff --git a/Library/ViewModels/RewardAddOnCardViewModel.swift b/Library/ViewModels/RewardAddOnCardViewModel.swift index 2ebd3361f8..79647a6236 100644 --- a/Library/ViewModels/RewardAddOnCardViewModel.swift +++ b/Library/ViewModels/RewardAddOnCardViewModel.swift @@ -105,7 +105,7 @@ public final class RewardAddOnCardViewModel: RewardAddOnCardViewModelType, Rewar self.estimatedShippingLabelText = Signal.combineLatest(reward, project, shippingRule) .map { reward, project, shippingRule in - estimatedShippingText(for: reward, project: project, selectedShippingRule: shippingRule) + estimatedShippingText(for: [reward], project: project, selectedShippingRule: shippingRule) } self.estimatedShippingStackViewHidden = Signal.combineLatest(reward, self.estimatedShippingLabelText) diff --git a/Library/ViewModels/RewardCardViewModel.swift b/Library/ViewModels/RewardCardViewModel.swift index 1c00e93ec6..ea0e98b966 100644 --- a/Library/ViewModels/RewardCardViewModel.swift +++ b/Library/ViewModels/RewardCardViewModel.swift @@ -130,7 +130,7 @@ public final class RewardCardViewModel: RewardCardViewModelType, RewardCardViewM self.estimatedShippingLabelText = Signal.combineLatest(reward, project, currentShippingRule) .map { reward, project, shippingRule in - estimatedShippingText(for: reward, project: project, selectedShippingRule: shippingRule) + estimatedShippingText(for: [reward], project: project, selectedShippingRule: shippingRule) } self.estimatedDeliveryDateLabelText = reward.map(estimatedDeliveryDateText(with:)).skipNil()