From b92f70ccf9ce4185538db26c01f70806ed3e3416 Mon Sep 17 00:00:00 2001 From: Scott Clampet <110618242+scottkicks@users.noreply.github.com> Date: Fri, 31 May 2024 12:31:17 -0500 Subject: [PATCH] [MBL-1504] Fix Updating Payment Method/Fixing Payment Method For Late Pledge Campaign (#2070) * pass a nil amount to GraphAPI.UpdateBacking if the backing isLatePledge * display the correct reward card amount on manage pledge screen * add tests * pr feedback --- .../Library/SharedFunctionsTests.swift | 64 ++++++++++++++++++- KsApi/models/lenses/RewardLenses.swift | 56 ++++++++++++++++ Library/SharedFunctions.swift | 15 ++++- Library/UpdateBackingInput+Constructor.swift | 2 +- .../UpdateBackingInput+ConstructorTests.swift | 61 ++++++++++++++++++ 5 files changed, 193 insertions(+), 5 deletions(-) diff --git a/Kickstarter-iOS/Library/SharedFunctionsTests.swift b/Kickstarter-iOS/Library/SharedFunctionsTests.swift index 607c6a0212..4292356b69 100644 --- a/Kickstarter-iOS/Library/SharedFunctionsTests.swift +++ b/Kickstarter-iOS/Library/SharedFunctionsTests.swift @@ -471,7 +471,8 @@ internal final class SharedFunctionsTests: TestCase { XCTAssertEqual(currencyText, "MX$ 12") } - func testMinAndMaxPledgeAmount_NoReward_ProjectCurrencyCountry_MinMaxPledgeReturned_Success() { + func testMinAndMaxPledgeAmount_NoReward_ProjectCurrencyCountry_isNotLatePledge_MinMaxPledgeReturned_Success( + ) { let mexicanCurrencyProjectTemplate = Project.template |> Project.lens.stats.currency .~ Project.Country.mx.currencyCode @@ -481,7 +482,8 @@ internal final class SharedFunctionsTests: TestCase { XCTAssertEqual(max, 200_000) } - func testMinAndMaxPledgeAmount_Reward_ProjectCurrencyCountry_MinMaxPledgeReturned_Success() { + func testMinAndMaxPledgeAmount_Reward_ProjectCurrencyCountry_isNotLatePledge_MinimumPledgeReturned_Success( + ) { let mexicanCurrencyProjectTemplate = Project.template |> Project.lens.stats.currency .~ Project.Country.mx.currencyCode @@ -494,7 +496,63 @@ internal final class SharedFunctionsTests: TestCase { XCTAssertEqual(max, 200_000) } - func testMinAndMaxPledgeAmount_NoReward_NoProjectCurrencyCountry_DefaultMinMaxPledgeReturned_Success() { + func testMinAndMaxPledgeAmount_Reward_ProjectCurrencyCountry_NoBacking_ProjectIsLatePledge_MinimumPledgeReturned_Success( + ) { + let mexicanCurrencyProjectTemplate = Project.template + |> Project.lens.stats.currency .~ Project.Country.mx.currencyCode + |> Project.lens.isInPostCampaignPledgingPhase .~ true + |> Project.lens.personalization.backing .~ nil + + let reward = Reward.template + |> Reward.lens.minimum .~ 12.00 + |> Reward.lens.latePledgeAmount .~ 10.00 + |> Reward.lens.pledgeAmount .~ 6.00 + + let (min, max) = minAndMaxPledgeAmount(forProject: mexicanCurrencyProjectTemplate, reward: reward) + + XCTAssertEqual(min, 12) + XCTAssertEqual(max, 200_000) + } + + func testMinAndMaxPledgeAmount_Reward_ProjectCurrencyCountry_isLatePledge_MinMaxLatePledgeAmountReturned_latePledgeAmount_Success( + ) { + let backing = Backing.template + |> Backing.lens.isLatePledge .~ true + let mexicanCurrencyProjectTemplate = Project.template + |> Project.lens.stats.currency .~ Project.Country.mx.currencyCode + |> Project.lens.personalization.backing .~ backing + + let reward = Reward.template + |> Reward.lens.minimum .~ 12.00 + |> Reward.lens.latePledgeAmount .~ 6.00 + + let (min, max) = minAndMaxPledgeAmount(forProject: mexicanCurrencyProjectTemplate, reward: reward) + + XCTAssertEqual(min, 6) + XCTAssertEqual(max, 200_000) + } + + func testMinAndMaxPledgeAmount_Reward_ProjectCurrencyCountry_HasLatePledge_MinMaxPledgeAmountReturned_Success( + ) { + let backing = Backing.template + |> Backing.lens.isLatePledge .~ false + let mexicanCurrencyProjectTemplate = Project.template + |> Project.lens.stats.currency .~ Project.Country.mx.currencyCode + |> Project.lens.personalization.backing .~ backing + + let reward = Reward.template + |> Reward.lens.minimum .~ 12.00 + |> Reward.lens.latePledgeAmount .~ 6.00 + |> Reward.lens.pledgeAmount .~ 3.00 + + let (min, max) = minAndMaxPledgeAmount(forProject: mexicanCurrencyProjectTemplate, reward: reward) + + XCTAssertEqual(min, 3) + XCTAssertEqual(max, 200_000) + } + + func testMinAndMaxPledgeAmount_NoReward_NoProjectCurrencyCountry_isNotLatePledge__DefaultMinMaxPledgeReturned_Success( + ) { let mexicanCurrencyProjectTemplate = Project.template |> Project.lens.stats.currency .~ Project.Country.mx.currencyCode diff --git a/KsApi/models/lenses/RewardLenses.swift b/KsApi/models/lenses/RewardLenses.swift index 57f1b0c566..4f469d7439 100644 --- a/KsApi/models/lenses/RewardLenses.swift +++ b/KsApi/models/lenses/RewardLenses.swift @@ -227,6 +227,34 @@ extension Reward { ) } ) + public static let latePledgeAmount = Lens( + view: { $0.latePledgeAmount }, + set: { Reward( + backersCount: $1.backersCount, + convertedMinimum: $1.convertedMinimum, + description: $1.description, + endsAt: $1.endsAt, + estimatedDeliveryOn: $1.estimatedDeliveryOn, + hasAddOns: $1.hasAddOns, + id: $1.id, + latePledgeAmount: $0, + limit: $1.limit, + limitPerBacker: $1.limitPerBacker, + minimum: $1.minimum, + pledgeAmount: $1.pledgeAmount, + postCampaignPledgingEnabled: $1.postCampaignPledgingEnabled, + remaining: $1.remaining, + rewardsItems: $1.rewardsItems, + shipping: $1.shipping, + shippingRules: $1.shippingRules, + shippingRulesExpanded: $1.shippingRulesExpanded, + startsAt: $1.startsAt, + title: $1.title, + localPickup: $1.localPickup, + isAvailable: $1.isAvailable + ) } + ) + public static let limit = Lens( view: { $0.limit }, set: { Reward( @@ -311,6 +339,34 @@ extension Reward { ) } ) + public static let pledgeAmount = Lens( + view: { $0.latePledgeAmount }, + set: { Reward( + backersCount: $1.backersCount, + convertedMinimum: $1.convertedMinimum, + description: $1.description, + endsAt: $1.endsAt, + estimatedDeliveryOn: $1.estimatedDeliveryOn, + hasAddOns: $1.hasAddOns, + id: $1.id, + latePledgeAmount: $1.latePledgeAmount, + limit: $1.limit, + limitPerBacker: $1.limitPerBacker, + minimum: $1.minimum, + pledgeAmount: $0, + postCampaignPledgingEnabled: $1.postCampaignPledgingEnabled, + remaining: $1.remaining, + rewardsItems: $1.rewardsItems, + shipping: $1.shipping, + shippingRules: $1.shippingRules, + shippingRulesExpanded: $1.shippingRulesExpanded, + startsAt: $1.startsAt, + title: $1.title, + localPickup: $1.localPickup, + isAvailable: $1.isAvailable + ) } + ) + public static let postCampaignPledgingEnabled = Lens( view: { $0.postCampaignPledgingEnabled }, set: { Reward( diff --git a/Library/SharedFunctions.swift b/Library/SharedFunctions.swift index 6ac3a75905..703c6014e6 100644 --- a/Library/SharedFunctions.swift +++ b/Library/SharedFunctions.swift @@ -106,7 +106,20 @@ internal func minAndMaxPledgeAmount(forProject project: Project, reward: Reward? case .none, .some(Reward.noReward): return (Double(country.minPledge ?? 1), Double(country.maxPledge ?? 10_000)) case let .some(reward): - return (reward.minimum, Double(country.maxPledge ?? 10_000)) + guard let backing = project.personalization.backing else { + return (reward.minimum, Double(country.maxPledge ?? 10_000)) + } + + let min: Double + + /// Account for the case where the originally selected reward pricing, for this backing, has since changed to late pledge pricing. We should always use the original pricing at the time of the backing over the most current state. + if backing.isLatePledge == true { + min = reward.latePledgeAmount > 0 ? reward.latePledgeAmount : reward.minimum + } else { + min = reward.pledgeAmount > 0 ? reward.pledgeAmount : reward.minimum + } + + return (min, Double(country.maxPledge ?? 10_000)) } } diff --git a/Library/UpdateBackingInput+Constructor.swift b/Library/UpdateBackingInput+Constructor.swift index 5fa930f553..3a5c9dabcf 100644 --- a/Library/UpdateBackingInput+Constructor.swift +++ b/Library/UpdateBackingInput+Constructor.swift @@ -15,7 +15,7 @@ extension UpdateBackingInput { ) return UpdateBackingInput( - amount: pledgeTotal, + amount: updateBackingData.backing.isLatePledge ? nil : pledgeTotal, applePay: isApplePay ? updateBackingData.applePayParams : nil, id: backingId, locationId: locationId, diff --git a/Library/UpdateBackingInput+ConstructorTests.swift b/Library/UpdateBackingInput+ConstructorTests.swift index 3513e22ea7..8bcd90e25a 100644 --- a/Library/UpdateBackingInput+ConstructorTests.swift +++ b/Library/UpdateBackingInput+ConstructorTests.swift @@ -94,6 +94,67 @@ final class UpdateBackingInput_ConstructorTests: TestCase { XCTAssertEqual(input.rewardIds, ["UmV3YXJkLTE="]) } + func testUpdateBackingInput_UpdateBackingData_AmountIsNull_WhenLatePledge_isApplePay() { + let applePayParams = ApplePayParams( + paymentInstrumentName: "paymentInstrumentName", + paymentNetwork: "paymentNetwork", + transactionIdentifier: "transactionIdentifier", + token: "token" + ) + + let backing = Backing.template + |> Backing.lens.isLatePledge .~ true + + let reward = Reward.template + + let data: UpdateBackingData = ( + backing: backing, + rewards: [reward], + pledgeTotal: 105, + selectedQuantities: [reward.id: 1], + shippingRule: ShippingRule.template, + paymentSourceId: UserCreditCards.amex.id, + setupIntentClientSecret: nil, + applePayParams: applePayParams + ) + + let input = UpdateBackingInput.input(from: data, isApplePay: true) + + XCTAssertNil(input.amount) + XCTAssertEqual(input.applePay, applePayParams) + XCTAssertEqual(input.id, "QmFja2luZy0x") + XCTAssertEqual(input.locationId, "42") + XCTAssertNil(input.paymentSourceId) + XCTAssertEqual(input.rewardIds, ["UmV3YXJkLTE="]) + } + + func testUpdateBackingInput_UpdateBackingData_AmountIsNull_WhenLatePledge_isNotApplePay() { + let backing = Backing.template + |> Backing.lens.isLatePledge .~ true + + let reward = Reward.template + + let data: UpdateBackingData = ( + backing: backing, + rewards: [reward], + pledgeTotal: 105, + selectedQuantities: [reward.id: 1], + shippingRule: ShippingRule.template, + paymentSourceId: UserCreditCards.amex.id, + setupIntentClientSecret: nil, + applePayParams: nil + ) + + let input = UpdateBackingInput.input(from: data, isApplePay: false) + + XCTAssertNil(input.amount) + XCTAssertNil(input.applePay) + XCTAssertEqual(input.id, "QmFja2luZy0x") + XCTAssertEqual(input.locationId, "42") + XCTAssertEqual(input.paymentSourceId, "6") + XCTAssertEqual(input.rewardIds, ["UmV3YXJkLTE="]) + } + func testUpdateBackingInput_WithShipping_RefTag_HasAddOns() { let reward = Reward.template let shippingRule = ShippingRule.template