Skip to content

Commit

Permalink
feat: added card network supported validation (#189)
Browse files Browse the repository at this point in the history
Co-authored-by: Kuntimaddi Manideep <[email protected]>
  • Loading branch information
ChiragKV-Juspay and manideepk90 authored Jan 27, 2025
1 parent 646e952 commit ee551f4
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 20 deletions.
25 changes: 23 additions & 2 deletions src/components/elements/CardElement.res
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
open Validation
type cardFormType = {isZipAvailable: bool}
type viewType = PaymentSheet | CardForm(cardFormType)

@react.component
let make = (
~setIsAllValid,
~viewType=PaymentSheet,
~reset: bool,
~keyToTrigerButtonClickError=0,
~cardNetworks=?,
) => {
let isZipAvailable = switch viewType {
| CardForm(cardFormType) => cardFormType.isZipAvailable
| _ => false
}
let isCardBrandSupported = (
~cardBrand,
~cardNetworks: option<array<PaymentMethodListType.card_networks>>,
) => {
switch cardNetworks {
| Some(cardNetwork) => cardNetwork->Array.some(network => network.card_network == cardBrand)
| None => true
}
}

// let (cardNumber, setCardNumber) = React.useState(_ => "")
// let (expireDate, setExpireDate) = React.useState(_ => "")
Expand All @@ -30,14 +41,15 @@ let make = (
cardData.isCardNumberValid,
cardData.isCvvValid,
cardData.isExpireDataValid,
cardData.isCardBrandSupported,
!isZipAvailable ||
switch cardData.isZipValid {
| Some(zipValid) => zipValid
| None => false
},
) {
| (Some(cardValid), Some(cvvValid), Some(expValid), zipValid) =>
cardValid && cvvValid && expValid && zipValid
| (Some(cardValid), Some(cvvValid), Some(expValid), Some(isCardBrandSupported), zipValid) =>
cardValid && cvvValid && expValid && isCardBrandSupported && zipValid
| _ => false
}
}, [cardData])
Expand All @@ -57,7 +69,14 @@ let make = (
) => {
let cardBrand = getCardBrand(text)
let num = formatCardNumber(text, cardType(cardBrand))

let isthisValid = cardValid(num, cardBrand)

let isSupported = switch cardNetworks {
| Some(networks) => isCardBrandSupported(~cardBrand, ~cardNetworks=networks)
| None => true
}

let shouldShiftFocusToNextField = isCardNumberEqualsMax(num, cardBrand)
if cardData.cardBrand !== cardBrand && cardData.cardBrand != "" {
setCardData(prev => {
Expand All @@ -72,6 +91,7 @@ let make = (
...prev,
cardNumber: num,
isCardNumberValid: Some(isthisValid),
isCardBrandSupported: Some(isSupported),
cardBrand,
})

Expand Down Expand Up @@ -170,6 +190,7 @@ let make = (
onScanCard
isCardNumberValid=cardData.isCardNumberValid
isExpireDataValid=cardData.isExpireDataValid
isCardBrandSupported=cardData.isCardBrandSupported
isCvvValid=cardData.isCvvValid
keyToTrigerButtonClickError
/>
Expand Down
7 changes: 7 additions & 0 deletions src/components/elements/PaymentSheetUi.res
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ let make = (
~onChangeCvv,
~isCardNumberValid,
~isExpireDataValid,
~isCardBrandSupported,
~isCvvValid,
~onScanCard,
~keyToTrigerButtonClickError,
) => {
let (nativeProp, _) = React.useContext(NativePropContext.nativePropContext)
let isCardNumberValid = isCardNumberValid->Option.getOr(true)
let isExpireDateValid = isExpireDataValid->Option.getOr(true)
let isCardBrandSupported = isCardBrandSupported->Option.getOr(true)
let isCvvValid = isCvvValid->Option.getOr(true)
let isMaxCardLength =
cardNumber->clearSpaces->String.length == maxCardLength(getCardBrand(cardNumber))
Expand All @@ -28,6 +30,9 @@ let make = (
let isCardNumberValid = {
cardNumberIsFocus ? isCardNumberValid || !isMaxCardLength : isCardNumberValid
}
let isCardBrandSupported = {
cardNumberIsFocus ? isCardBrandSupported || !isMaxCardLength : isCardBrandSupported
}
let isExpireDateValid = {
expireDateIsFocus ? isExpireDateValid || expireDate->String.length < 7 : isExpireDateValid
}
Expand All @@ -53,6 +58,8 @@ let make = (

let errorMsgText = if !isCardNumberValid {
Some(localeObject.inValidCardErrorText)
} else if !isCardBrandSupported {
Some(localeObject.unsupportedCardErrorText)
} else if !isExpireDateValid {
Some(localeObject.inValidExpiryErrorText)
} else if !isCvvValid {
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/AllApiDataContext.res
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ let paymentList = [
PaymentMethodListType.CARD({
payment_method: "card",
payment_method_type: "debit",
card_networks: [],
card_networks: None,
required_field: [],
}),
]
Expand Down
2 changes: 2 additions & 0 deletions src/contexts/CardDataContext.res
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type cardData = {
cvv: string,
zip: string,
isCardNumberValid: option<bool>,
isCardBrandSupported: option<bool>,
isExpireDataValid: option<bool>,
isCvvValid: option<bool>,
isZipValid: option<bool>,
Expand All @@ -16,6 +17,7 @@ let dafaultVal = {
cvv: "",
zip: "",
isCardNumberValid: None,
isCardBrandSupported: None,
isExpireDataValid: None,
isCvvValid: None,
isZipValid: None,
Expand Down
5 changes: 5 additions & 0 deletions src/hooks/S3ApiHook.res
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ let getLocaleStrings: Js.Json.t => localeStrings = data => {
"inValidCardErrorText",
defaultLocale.inValidCardErrorText,
),
unsupportedCardErrorText: Utils.getString(
res,
"unsupportedCardErrorText",
defaultLocale.unsupportedCardErrorText,
),
inCompleteCVCErrorText: Utils.getString(
res,
"inCompleteCVCErrorText",
Expand Down
7 changes: 6 additions & 1 deletion src/pages/payment/Card.res
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,12 @@ let make = (
<View>
<TextWrapper text=localeObject.cardDetailsLabel textType={ModalText} />
<Space height=8. />
<CardElement setIsAllValid=setIsAllCardValuesValid reset=false keyToTrigerButtonClickError />
<CardElement
setIsAllValid=setIsAllCardValuesValid
reset=false
keyToTrigerButtonClickError
cardNetworks=cardVal.card_networks
/>
{cardVal.required_field->Array.length != 0
? <>
<DynamicFields
Expand Down
10 changes: 7 additions & 3 deletions src/pages/widgets/CardWidget.res
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@ let make = () => {
return_url: ?Utils.getReturnUrl(nativeProp.hyperParams.appId),
payment_method: prop.payment_method,
payment_method_type: prop.payment_method_type,
connector: prop.card_networks
->Array.get(0)
->Option.mapOr([], card_network => card_network.eligible_connectors),
connector: switch prop.card_networks {
| Some(cardNetworks) =>
cardNetworks
->Array.get(0)
->Option.mapOr([], card_network => card_network.eligible_connectors)
| None => []
},
payment_method_data,
billing: ?nativeProp.configuration.defaultBillingDetails,
shipping: ?nativeProp.configuration.shippingDetails,
Expand Down
2 changes: 2 additions & 0 deletions src/types/LocaleDataType.res
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type localeStrings = {
cardNumberLabel: string,
localeDirection: string,
inValidCardErrorText: string,
unsupportedCardErrorText: string,
inCompleteCVCErrorText: string,
inValidCVCErrorText: string,
inCompleteExpiryErrorText: string,
Expand Down Expand Up @@ -77,6 +78,7 @@ let defaultLocale = {
cardNumberLabel: "Card Number",
cardDetailsLabel: "Card Details",
inValidCardErrorText: "Card number is invalid.",
unsupportedCardErrorText: "Card brand is not supported.",
inCompleteCVCErrorText: "Your card's security code is incomplete.",
inValidCVCErrorText: "Your card's security code is invalid.",
inCompleteExpiryErrorText: "Your card's expiration date is incomplete.",
Expand Down
24 changes: 14 additions & 10 deletions src/types/PaymentMethodListType.res
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type card_networks = {
type payment_method_types_card = {
payment_method: string,
payment_method_type: string,
card_networks: array<card_networks>,
card_networks: option<array<card_networks>>,
required_field: RequiredFieldsTypes.required_fields,
}

Expand Down Expand Up @@ -123,15 +123,19 @@ let flattenPaymentListArray = (plist, item) => {
CARD({
payment_method: "card",
payment_method_type: dict2->getString("payment_method_type", ""),
card_networks: dict2
->getArray("card_networks")
->Array.map(item3 => {
let dict3 = item3->getDictFromJson
{
card_network: dict3->getString("card_network", ""),
eligible_connectors: dict3->getArray("eligible_connectors"),
}
}),
card_networks: switch dict2->getArray("card_networks") {
| [] => None
| data =>
Some(
data->Array.map(item3 => {
let dict3 = item3->getDictFromJson
{
card_network: dict3->getString("card_network", ""),
eligible_connectors: dict3->getArray("eligible_connectors"),
}
}),
)
},
required_field: dict2->RequiredFieldsTypes.getRequiredFieldsFromDict,
})->Js.Array.push(plist)
})
Expand Down
10 changes: 7 additions & 3 deletions src/utility/logics/PaymentUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,13 @@ let generateCardConfirmBody = (
return_url: ?Utils.getReturnUrl(nativeProp.hyperParams.appId),
payment_method: prop.payment_method,
payment_method_type: ?Some(prop.payment_method_type),
connector: ?(
prop.card_networks->Array.get(0)->Option.map(cardNetwork => cardNetwork.eligible_connectors)
),
connector: ?switch prop.card_networks {
| Some(cardNetwork) =>
cardNetwork
->Array.get(0)
->Option.mapOr(None, card_network => card_network.eligible_connectors->Some)
| None => None
},
?payment_method_data,
?payment_token,
billing: ?nativeProp.configuration.defaultBillingDetails,
Expand Down

0 comments on commit ee551f4

Please sign in to comment.