Skip to content
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

[FX-722] Add support for derivedCardInfo.usState to ElementState for ForagePANEditText #133

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import java.util.UUID
*/
class ForageSDK : ForageSDKInterface {

private fun _getForageConfigOrThrow(element: AbstractForageElement): ForageConfig {
private fun <InputDetails> _getForageConfigOrThrow(element: AbstractForageElement<InputDetails>): ForageConfig {
val context = element.getForageConfig()
return context ?: throw ForageConfigNotSetException(
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ package com.joinforage.forage.android.core.element
import com.joinforage.forage.android.core.element.state.ElementState

internal typealias SimpleElementListener = () -> Unit
internal typealias StatefulElementListener = (state: ElementState) -> Unit
internal typealias StatefulElementListener<InputDetails> = (state: ElementState<InputDetails>) -> Unit
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
package com.joinforage.forage.android.core.element.state

import com.joinforage.forage.android.core.element.ElementValidationError
import com.joinforage.forage.android.model.USState

data class ElementState(
data class ElementState<InputDetails>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devinmorgan Will this be a breaking change for the client? My guess is no, but just want to confirm.

i.e. will the client have to update their code to conform to the new interfaces?

// client code
// before:

ForageElement

// after (breaking change):
ForageElement<ClientInsertsDetailsTypeHere>

Also, should we explicitly declare public classes as public or is it fine to omit? Not sure what the kotlin linting rules prefer.

Copy link
Contributor Author

@devinmorgan devinmorgan Dec 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fear for the flakiness of the QA tests so I am taking a screenshot to capture that the CI tests passed for this PR at one point (see below).

Will this be a breaking change for the client?

It depends. As you can see the CI tests against this PR is that I did not change the Sample App in this PR. The significance of them passing is that this PR is reverse compatible (for the with the Sample App at least). The reason this change is reverse compatible with the Sample App is because the Sample App never explicitly refers to any of the types that this PR changes (e.g. ElementState, AbstractElement, etc). If the Sample App had referenced these types then this would have been a breaking change.

This same point applies to GoPuff. If they currently reference the types that this PR does change instead of letting the compiler infer the types, then this PR would be a breaking change. However, even if GoPuff does reference the types changed, it would only require minor updates in their code (just updating the types)

Screenshot of CI tests passing

image

val isFocused: Boolean,
val isBlurred: Boolean,
val isEmpty: Boolean,
val isValid: Boolean,
val isComplete: Boolean,
val validationError: ElementValidationError?
val validationError: ElementValidationError?,
val details: InputDetails,
)
internal val INITIAL_ELEMENT_STATE = ElementState(

internal typealias PinDetails = Nothing?
internal val INITIAL_PIN_ELEMENT_STATE = ElementState<PinDetails>(
isFocused = false,
isBlurred = true,
isEmpty = true,
isValid = true,
isComplete = false,
validationError = null
validationError = null,
details = null,
)

data class DerivedCardInfo(val usState: USState? = null)
data class PanDetails(
val derivedCardInfo: DerivedCardInfo?
)
internal val INITIAL_PAN_ELEMENT_STATE = ElementState<PanDetails>(
isFocused = false,
isBlurred = true,
isEmpty = true,
isValid = true,
isComplete = false,
validationError = null,
details = PanDetails(null),
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,38 @@ import com.joinforage.forage.android.core.element.ElementValidationError
import com.joinforage.forage.android.core.element.SimpleElementListener
import com.joinforage.forage.android.core.element.StatefulElementListener

internal abstract class ElementStateManager(
internal abstract class ElementStateManager<InputDetails>(
private var isFocused: Boolean,
private var isBlurred: Boolean,
internal var isEmpty: Boolean,
internal var isValid: Boolean,
internal var isComplete: Boolean,
internal var validationError: ElementValidationError?
internal var validationError: ElementValidationError?,
internal var details: InputDetails
) {
private var onFocusEventListener: SimpleElementListener? = null
private var onBlurEventListener: SimpleElementListener? = null
internal var onChangeEventListener: StatefulElementListener? = null
internal var onChangeEventListener: StatefulElementListener<InputDetails>? = null

internal constructor(state: ElementState) : this(
internal constructor(state: ElementState<InputDetails>) : this(
isFocused = state.isFocused,
isBlurred = state.isBlurred,
isEmpty = state.isEmpty,
isValid = state.isValid,
isComplete = state.isComplete,
validationError = state.validationError
validationError = state.validationError,
details = state.details
)

fun getState(): ElementState {
fun getState(): ElementState<InputDetails> {
return ElementState(
isFocused = isFocused,
isBlurred = isBlurred,
isEmpty = isEmpty,
isValid = isValid,
isComplete = isComplete,
validationError = validationError
validationError = validationError,
details = details
)
}

Expand All @@ -44,7 +47,7 @@ internal abstract class ElementStateManager(
onBlurEventListener = l
}

fun setOnChangeEventListener(l: StatefulElementListener) {
fun setOnChangeEventListener(l: StatefulElementListener<InputDetails>) {
onChangeEventListener = l
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.joinforage.forage.android.core.element.TooLongEbtPanError
import com.joinforage.forage.android.model.hasInvalidStateIIN
import com.joinforage.forage.android.model.isCorrectLength
import com.joinforage.forage.android.model.missingStateIIN
import com.joinforage.forage.android.model.queryForStateIIN
import com.joinforage.forage.android.model.tooLongForStateIIN
import com.joinforage.forage.android.model.tooShortForStateIIN

Expand Down Expand Up @@ -76,7 +77,7 @@ internal class BalanceCheckErrorCard : WhitelistedCards("5", 14)
internal class NonProdValidEbtCard : WhitelistedCards("9", 4)
internal class EmptyEbtCashBalanceCard : WhitelistedCards("654321")

internal class PanElementStateManager(state: ElementState, private val validators: Array<PanValidator>) : ElementStateManager(state) {
internal class PanElementStateManager(state: ElementState<PanDetails>, private val validators: Array<PanValidator>) : ElementStateManager<PanDetails>(state) {

private fun checkIsValid(cardNumber: String): Boolean {
return validators.any { it.checkIfValid(cardNumber) }
Expand All @@ -89,6 +90,12 @@ internal class PanElementStateManager(state: ElementState, private val validator
.map { it.checkForValidationError(cardNumber) }
.firstOrNull { it != null }
}
private fun getDetails(cardNumber: String): PanDetails {
val derivedCardInfo = DerivedCardInfo(
queryForStateIIN(cardNumber)?.publicEnum
)
return PanDetails(derivedCardInfo)
}

fun handleChangeEvent(rawInput: String) {
// because the input may be formatted, we need to
Expand All @@ -102,21 +109,24 @@ internal class PanElementStateManager(state: ElementState, private val validator
this.validationError = checkForValidationError(newCardNumber)
this.isEmpty = newCardNumber.isEmpty()

// update state details based on newCardNumber
this.details = getDetails(newCardNumber)

// invoke the registered listener with the updated state
onChangeEventListener?.invoke(getState())
}

companion object {
fun forEmptyInput(): PanElementStateManager {
return PanElementStateManager(
INITIAL_ELEMENT_STATE,
INITIAL_PAN_ELEMENT_STATE,
arrayOf(StrictEbtValidator())
)
}

fun NON_PROD_forEmptyInput(): PanElementStateManager {
return PanElementStateManager(
INITIAL_ELEMENT_STATE,
INITIAL_PAN_ELEMENT_STATE,
arrayOf(
StrictEbtValidator(),
PaymentCaptureErrorCard(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.joinforage.forage.android.core.element.state
import com.joinforage.forage.android.core.element.IncompleteEbtPinError
import com.joinforage.forage.android.core.element.WrongEbtPinError

internal class PinElementStateManager(state: ElementState) : ElementStateManager(state) {
internal class PinElementStateManager(state: ElementState<PinDetails>) : ElementStateManager<PinDetails>(state) {

// this function is used for after the submit event
// happens and we learn that the PIN was not correct
Expand All @@ -26,15 +26,22 @@ internal class PinElementStateManager(state: ElementState) : ElementStateManager
this.isEmpty = isEmpty
}

private fun setDetails() {
// right now there is no extra info we want to supply along
// side the existing element state so we set details = null
this.details = null
}

fun handleChangeEvent(isComplete: Boolean, isEmpty: Boolean) {
setPinIsComplete(isComplete)
setIsEmpty(isEmpty)
setDetails()
onChangeEventListener?.invoke(getState())
}

companion object {
fun forEmptyInput(): PinElementStateManager {
return PinElementStateManager(INITIAL_ELEMENT_STATE)
return PinElementStateManager(INITIAL_PIN_ELEMENT_STATE)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,120 @@ package com.joinforage.forage.android.model

internal const val STATE_INN_LENGTH = 6

enum class USState(val abbreviation: String) {
ALABAMA("AL"),
ALASKA("AK"),
ARIZONA("AZ"),
ARKANSAS("AR"),
CALIFORNIA("CA"),
COLORADO("CO"),
CONNECTICUT("CT"),
DELAWARE("DE"),
DISTRICT_OF_COLUMBIA("DC"),
FLORIDA("FL"),
GEORGIA("GA"),
GUAM("GU"),
HAWAII("HI"),
IDAHO("ID"),
ILLINOIS("IL"),
INDIANA("IN"),
IOWA("IA"),
KANSAS("KS"),
KENTUCKY("KY"),
LOUISIANA("LA"),
MAINE("ME"),
MARYLAND("MD"),
MASSACHUSETTS("MA"),
MICHIGAN("MI"),
MINNESOTA("MN"),
MISSISSIPPI("MS"),
MISSOURI("MO"),
MONTANA("MT"),
NEBRASKA("NE"),
NEVADA("NV"),
NEW_HAMPSHIRE("NH"),
NEW_JERSEY("NJ"),
NEW_MEXICO("NM"),
NEW_YORK("NY"),
NORTH_CAROLINA("NC"),
NORTH_DAKOTA("ND"),
OHIO("OH"),
OKLAHOMA("OK"),
OREGON("OR"),
PENNSYLVANIA("PA"),
RHODE_ISLAND("RI"),
SOUTH_CAROLINA("SC"),
SOUTH_DAKOTA("SD"),
TENNESSEE("TN"),
TEXAS("TX"),
US_VIRGIN_ISLANDS("VI"),
UTAH("UT"),
VERMONT("VT"),
VIRGINIA("VA"),
WASHINGTON("WA"),
WEST_VIRGINIA("WV"),
WISCONSIN("WI"),
WYOMING("WY")
}

internal enum class StateIIN(
val iin: String,
val panLength: Int
val panLength: Int,
val publicEnum: USState
) {
ALABAMA("507680", 16),
ALASKA("507695", 16),
ARIZONA("507706", 16),
ARKANSAS("610093", 16),
CALIFORNIA("507719", 16),
COLORADO("507681", 16),
CONNECTICUT("600890", 18),
DELAWARE("507713", 16),
DISTRICT_OF_COLUMBIA("507707", 16),
FLORIDA("508139", 16),
GEORGIA("508148", 16),
GUAM("578036", 16),
HAWAII("507698", 16),
IDAHO("507692", 16),
ILLINOIS("601453", 16),
INDIANA("507704", 16),
IOWA("627485", 19),
KANSAS("601413", 16),
KENTUCKY("507709", 16),
LOUISIANA("504476", 16),
MAINE("507703", 19),
MARYLAND("600528", 16),
MASSACHUSETTS("600875", 18),
MICHIGAN("507711", 16),
MINNESOTA("610423", 16),
MISSISSIPPI("507718", 16),
MISSOURI("507683", 16),
MONTANA("507714", 16),
NEBRASKA("507716", 16),
NEVADA("507715", 16),
NEW_HAMPSHIRE("507701", 16),
NEW_JERSEY("610434", 16),
NEW_MEXICO("586616", 16),
NEW_YORK("600486", 19),
NORTH_CAROLINA("508161", 16),
NORTH_DAKOTA("508132", 16),
OHIO("507700", 16),
OKLAHOMA("508147", 16),
OREGON("507693", 16),
PENNSYLVANIA("600760", 19),
RHODE_ISLAND("507682", 16),
SOUTH_CAROLINA("610470", 16),
SOUTH_DAKOTA("508132", 16),
TENNESSEE("507702", 16),
TEXAS("610098", 19),
US_VIRGIN_ISLANDS("507721", 16),
UTAH("601036", 16),
VERMONT("507705", 16),
VIRGINIA("622044", 16),
WASHINGTON("507710", 16),
WEST_VIRGINIA("507720", 16),
WISCONSIN("507708", 16),
WYOMING("505349", 16)
ALABAMA("507680", 16, USState.ALABAMA),
ALASKA("507695", 16, USState.ALASKA),
ARIZONA("507706", 16, USState.ARIZONA),
ARKANSAS("610093", 16, USState.ARKANSAS),
CALIFORNIA("507719", 16, USState.CALIFORNIA),
COLORADO("507681", 16, USState.COLORADO),
CONNECTICUT("600890", 18, USState.CONNECTICUT),
DELAWARE("507713", 16, USState.DELAWARE),
DISTRICT_OF_COLUMBIA("507707", 16, USState.DISTRICT_OF_COLUMBIA),
FLORIDA("508139", 16, USState.FLORIDA),
GEORGIA("508148", 16, USState.GEORGIA),
GUAM("578036", 16, USState.GUAM),
HAWAII("507698", 16, USState.HAWAII),
IDAHO("507692", 16, USState.IDAHO),
ILLINOIS("601453", 16, USState.ILLINOIS),
INDIANA("507704", 16, USState.INDIANA),
IOWA("627485", 19, USState.IOWA),
KANSAS("601413", 16, USState.KANSAS),
KENTUCKY("507709", 16, USState.KENTUCKY),
LOUISIANA("504476", 16, USState.LOUISIANA),
MAINE("507703", 19, USState.MAINE),
MARYLAND("600528", 16, USState.MARYLAND),
MASSACHUSETTS("600875", 18, USState.MASSACHUSETTS),
MICHIGAN("507711", 16, USState.MICHIGAN),
MINNESOTA("610423", 16, USState.MINNESOTA),
MISSISSIPPI("507718", 16, USState.MISSISSIPPI),
MISSOURI("507683", 16, USState.MISSOURI),
MONTANA("507714", 16, USState.MONTANA),
NEBRASKA("507716", 16, USState.NEBRASKA),
NEVADA("507715", 16, USState.NEVADA),
NEW_HAMPSHIRE("507701", 16, USState.NEW_HAMPSHIRE),
NEW_JERSEY("610434", 16, USState.NEW_JERSEY),
NEW_MEXICO("586616", 16, USState.NEW_MEXICO),
NEW_YORK("600486", 19, USState.NEW_YORK),
NORTH_CAROLINA("508161", 16, USState.NORTH_CAROLINA),
NORTH_DAKOTA("508132", 16, USState.NORTH_DAKOTA),
OHIO("507700", 16, USState.OHIO),
OKLAHOMA("508147", 16, USState.OKLAHOMA),
OREGON("507693", 16, USState.OREGON),
PENNSYLVANIA("600760", 19, USState.PENNSYLVANIA),
RHODE_ISLAND("507682", 16, USState.RHODE_ISLAND),
SOUTH_CAROLINA("610470", 16, USState.SOUTH_CAROLINA),
SOUTH_DAKOTA("508132", 16, USState.SOUTH_DAKOTA),
TENNESSEE("507702", 16, USState.TENNESSEE),
TEXAS("610098", 19, USState.TEXAS),
US_VIRGIN_ISLANDS("507721", 16, USState.US_VIRGIN_ISLANDS),
UTAH("601036", 16, USState.UTAH),
VERMONT("507705", 16, USState.VERMONT),
VIRGINIA("622044", 16, USState.VIRGINIA),
WASHINGTON("507710", 16, USState.WASHINGTON),
WEST_VIRGINIA("507720", 16, USState.WEST_VIRGINIA),
WISCONSIN("507708", 16, USState.WISCONSIN),
WYOMING("505349", 16, USState.WYOMING)
}

internal fun missingStateIIN(cardNumber: String): Boolean {
Expand All @@ -80,4 +137,4 @@ internal fun tooLongForStateIIN(cardNumber: String): Boolean {
}
internal fun isCorrectLength(cardNumber: String): Boolean {
return !tooShortForStateIIN(cardNumber) && !tooLongForStateIIN(cardNumber)
}
}
Loading
Loading