v6 - Card brands - US debit cards (restricted cards)#2756
v6 - Card brands - US debit cards (restricted cards)#2756araratthehero wants to merge 6 commits into
Conversation
COSDK-1204
Restricted brands must flow through to the BIN lookup request, matching Web/iOS behavior. Filtering is moved to the view layer in the next commit. COSDK-1204
Restricted brands (accel, pulse, star, nyce) are now filtered out in CardViewStateProducer.produce() so they don't appear in the logo grid. The unfiltered list remains on CardComponentState for the BIN lookup service. COSDK-1204
Add RestrictedBrand and DualBrandWithRestrictedBrand to CardBrandState. Update CardBrandIntentsHandler to detect restricted brands in network and cached sources, classifying them into the new states. Update all consumers: cardBrand(), validator, and view state producer. COSDK-1204
Add test confirming restricted brands pass through to the BinLookupData callback output unchanged. Fix line length. COSDK-1204
COSDK-1204
✅ No public API changes |
There was a problem hiding this comment.
Code Review
This pull request refactors the handling of restricted card brands (accel, pulse, star, nyce) by moving the logic to a dedicated helper and updating the card detection flow. It introduces new states, RestrictedBrand and SingleReliableWithRestrictedBrand, within CardBrandState to better manage scenarios where restricted brands are detected alongside or instead of supported brands. Additionally, the CardViewStateProducer now filters restricted brands from the supported brands list, and extensive unit tests have been added to ensure correct behavior. I have no feedback to provide as there were no review comments to assess.
|
There was a problem hiding this comment.
Do you think using hidden instead of restricted could make this flow clearer?
There was a problem hiding this comment.
I agree. Will make the change.
| DetectedCardTypeList.Source.NETWORK, | ||
| DetectedCardTypeList.Source.CACHED -> { | ||
| val nonRestrictedSupportedBrands = supportedDetectedCardTypes.filterNot { | ||
| isRestrictedCardType(it.cardBrand.txVariant) |
There was a problem hiding this comment.
What do you think of adding a boolean inside DetectedCardType that indicates if the brand should be hidden/restricted? IMO it achieves two things:
- It aligns with other similar values such as
isSupportedandisShopperSelectionAllowedInDualBranded - When this value starts coming from the backend, it will be easy to migrate the logic, only inside
NetworkCardBrandDetectionService(same will happen forisShopperSelectionAllowedInDualBranded)
| // network detection + no detected brands | ||
| detectedCardTypes.isEmpty() -> CardBrandState.NoBrandsDetected | ||
|
|
||
| // network detection + only restricted brands are supported |
There was a problem hiding this comment.
I think this when block is a bit difficult to read now, I had a similar issue when I was writing it in my PR and here's what I can suggest:
when {
// network detection + no detected brands
detectedCardTypes.isEmpty() -> CardBrandState.NoBrandsDetected
nonRestrictedSupportedBrands.isEmpty() -> {
if (anyRestrictedBrandDetected) {
// network detection + only restricted brands are supported
CardBrandState.RestrictedBrand
} else {
// network detection + detected brands but no supported brands
CardBrandState.UnsupportedBrand
}
}
nonRestrictedSupportedBrands.size == 1 -> {
if (anyRestrictedBrandDetected) {
// network detection + 1 non-restricted supported brand + restricted brand(s)
CardBrandState.SingleReliableWithRestrictedBrand(
cardBrandData = nonRestrictedSupportedBrands.first().toCardBrandData(),
)
} else {
// network detection + 1 non-restricted supported brand
CardBrandState.SingleReliableBrand(
cardBrandData = nonRestrictedSupportedBrands.first().toCardBrandData(),
)
}
}
// network detection + multiple non-restricted supported brands
else -> {
getDualBrandedCardBrandState(currentState.cardBrandState, nonRestrictedSupportedBrands)
}
}Basically you make it a when with 4 cases:
- No brands at all
- No brands that are supported and not restricted
- 1 brand that is supported and not restricted
- More than 1 brand supported and not restricted
Then inside the blocks you check if anything is restricted. Let me know if you find this easier to read.
| val shopperSelectedCardBrandData: CardBrandData, | ||
| ) : CardBrandState() | ||
|
|
||
| data object RestrictedBrand : CardBrandState() |
There was a problem hiding this comment.
Nit: for readability consider ordering the values of this sealed class by: no brands - single brand - dual brand instead of putting the cases with restricted at the bottom.



Description
Restricted-brand handling alignment with Web/iOS.
Restricted brands (
accel,pulse,star,nyce— US ATM/debit network rails) were stripped fromsupportedCardBrandsclient-side before the BIN lookup request was built. That caused the backend to return them assupported=false, the state handler to drop them, and merchants to never know the card had a restricted rail.This PR moves restricted-brand handling from a client-side filter to the data-to-state classification boundary, matching Web/iOS behavior:
CardComponentParamsMapper.removeRestrictedCards()is removed. Restricted brands flow through to the BIN lookup request.RestrictedCardTypemoves frominternal.ui.modeltointernal.helperand is simplified from an enum to aSet<String>with a top-levelisRestrictedCardType()function.internalCardBrandStatevariants:RestrictedBrand— only restricted brand(s) detected. UI mirrorsNoBrandsDetected. Nobrandsent in/payments.SingleReliableWithRestrictedBrand(cardBrandData)— single supported brand alongside restricted brand(s) (e.g.[visa, accel]). Only the supported logo is shown; restricted logos are never rendered. Nobrandsent in/paymentssince the rail is ambiguous.CardViewStateProducer, so they never appear in the UI logo list.6 commits, all on
internalcode.Behavior matrix
brandin payload[visa]SingleReliableBrand(unchanged)[visa, mc]DualBrand(unchanged)[visa, cartebancaire]DualBrandWithShopperSelection(unchanged)[visa, accel]SingleReliableWithRestrictedBrand(new)[visa, cartebancaire, accel]DualBrandWithShopperSelection(existing path)[accel]RestrictedBrand(new)[visa(supported), accel(unsupported)]SingleReliableBrand(unsupported restricted ignored)[discover](merchant=[visa])UnsupportedBrand(unchanged)[]NoBrandsDetected(unchanged)Checklist
Ticket Number
COSDK-1204