-
Notifications
You must be signed in to change notification settings - Fork 995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Show blocked brands in CBC dropdown, but block selection #4489
Conversation
(not accepted) If it's okay for these strings to be unlocalized in master (e.g. this is for an unshipped feature), add the label New strings are localized on a weekly basis and are downloaded as part of the release process. For more details on how to localize a string, you can refer to this link. |
Added the "ship without translations" b/c:
By the time we GA this feature translations will have come in, we won't GA until they do. |
case .USBankAccount, .SEPADebit: | ||
return false | ||
default: | ||
fatalError("Updating payment method has not been implemented for \(paymentMethod.type)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of fatal Error, we should be using stpAssert to avoid being noisy for integrators, but..taking a step back, is it really needed to have a fatal error/assert? Seems the function should just be something like:
var isUpdatableCardBrandCard: Bool {
guard paymentMethod.type == .card {
return false
}
let availableBrands ...
etc...
return...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I like this, good idea.
let itemsWithDisabled = items + [disabledItem] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like we should assert the number of items before directly accessing specific indexes.
@@ -265,6 +271,13 @@ extension DropdownFieldElement { | |||
} | |||
|
|||
public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { | |||
let item = items[row] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like we should add to the top of the fn:
guard row < items.count else {
stpAssertionFailure("...")
return
}
...PaymentsUI/StripePaymentsUI/Source/Internal/UI/Elements/DropDownFieldElement+CardBrand.swift
Show resolved
Hide resolved
@@ -26,17 +27,21 @@ extension STPCardBrand { | |||
return NSAttributedString(attachment: brandImageAttachment) | |||
} | |||
|
|||
func cardBrandItem(theme: ElementsAppearance = .default, maxWidth: CGFloat? = nil) -> DropdownFieldElement.DropdownItem { | |||
func cardBrandItem(theme: ElementsAppearance = .default, isAllowed: Bool, maxWidth: CGFloat? = nil) -> DropdownFieldElement.DropdownItem { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to keep a consistent nomenclature of disallowed
instead of introducing isAllowed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah agree!
let validBrandSelections = cardBrandDropDown.items.filter { !$0.isPlaceholder && !$0.isDisabled } | ||
|
||
// If we didn't previously have brands but now have them select based on merchant preference | ||
// Select the first brand in the fetched brands that appears earliest in the merchants preferred networks | ||
if !hadBrands, | ||
let preferredNetworks = self.preferredNetworks, | ||
let brandToSelect = preferredNetworks.first(where: { fetchedCardBrands.contains($0) }), | ||
let brandToSelect = preferredNetworks.first(where: { fetchedCardBrands.contains($0) && !disallowedCardBrands.contains($0) }), | ||
let indexToSelect = cardBrandDropDown.items.firstIndex(where: { $0.rawData == STPCardBrandUtilities.apiValue(from: brandToSelect) }) { | ||
cardBrandDropDown.select(index: indexToSelect, shouldAutoAdvance: false) | ||
} else if cardBrands.count == 1 && self.cardBrandFilter != .default { | ||
// If we only fetched one card brand auto select it, 1 index due to 0 index being the placeholder. | ||
} else if validBrandSelections.count == 1, | ||
!disallowedCardBrands.isEmpty, | ||
let firstItem = validBrandSelections.first, | ||
let indexToSelect = cardBrandDropDown.items.firstIndex(where: { $0.rawData == firstItem.rawData }) { | ||
// If we only fetched one card brand that is not disallowed, auto select it. | ||
// This case typically only occurs when card brand filtering is used with CBC and one of the fetched brands is filtered out. | ||
cardBrandDropDown.select(index: 1, shouldAutoAdvance: false) | ||
cardBrandDropDown.select(index: indexToSelect, shouldAutoAdvance: false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having a tough time following this logic, so i tried to refactor it a bit.... Let me know what you think about breaking up this logic in the following way:
....
cardBrandDropDown.update(items: DropdownFieldElement.items(...)
....
// Prioritize merchant preference if we did not have brands prior to calling .possibleBrands, otherwise use default logic
if !hadBrands, let indexToSelect = hasPreferredBrandIndex(fetchedCardBrands: fetchedCardBrands, disallowedCardBrands: disallowedCardBrands, cardBrandDropDown: cardBrandDropDown) {
cardBrandDropDown.select(index: indexToSelect, shouldAutoAdvance: false)
} else if let indexToSelect = useDefaultSelectionLogic(disallowedCardBrands: disallowedCardBrands, cardBrandDropDown: cardBrandDropDown) {
cardBrandDropDown.select(index: indexToSelect, shouldAutoAdvance: false)
}
Then we take the logic out of those ifstatements into two separate functions:
// Select the first brand in the fetched brands that appears earliest in the merchants preferred networks
func hasPreferredBrandIndex(fetchedCardBrands: Set<STPCardBrand>, disallowedCardBrands: Set<STPCardBrand>, cardBrandDropDown: DropdownFieldElement) -> Int? {
guard let preferredNetworks = self.preferredNetworks,
let brandToSelect = preferredNetworks.first(where: { fetchedCardBrands.contains($0) && !disallowedCardBrands.contains($0) }),
let indexToSelect = cardBrandDropDown.items.firstIndex(where: { $0.rawData == STPCardBrandUtilities.apiValue(from: brandToSelect) }) else {
return nil
}
return indexToSelect
}
// If we only fetched one card brand that is not disallowed, auto select it.
// This case typically only occurs when card brand filtering is used with CBC and one of the fetched brands is filtered out.
func useDefaultSelectionLogic(disallowedCardBrands: Set<STPCardBrand>, cardBrandDropDown: DropdownFieldElement) -> Int? {
let validBrandSelections = cardBrandDropDown.items.filter { !$0.isPlaceholder && !$0.isDisabled }
guard validBrandSelections.count == 1,
!disallowedCardBrands.isEmpty,
let firstItem = validBrandSelections.first,
let indexToSelect = cardBrandDropDown.items.firstIndex(where: { $0.rawData == firstItem.rawData }) else {
return nil
}
return indexToSelect
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I like this better!
Summary
Update card flow
Simulator.Screen.Recording.-.iOS.18.-.2025-01-22.at.09.45.28.mp4
New card flow
Simulator.Screen.Recording.-.iOS.18.-.2025-01-23.at.09.25.00.mp4
Motivation
https://jira.corp.stripe.com/browse/MOBILESDK-3035
Testing
Changelog
N/A (private beta for now)