From 1bd1e563f993ec483ef43651996b9c836e33de0d Mon Sep 17 00:00:00 2001
From: s-vamshi <62489114+s-vamshi@users.noreply.github.com>
Date: Wed, 23 Oct 2024 16:16:51 +0530
Subject: [PATCH 1/8] test: creation of test payment using sdk cypress (#1555)
---
config/config.toml | 2 +-
cypress.config.js | 1 +
cypress/e2e/connector/connector.cy.js | 65 ++++++++++++++++++++++-----
3 files changed, 57 insertions(+), 11 deletions(-)
diff --git a/config/config.toml b/config/config.toml
index 80696aa4f..2c1098c21 100644
--- a/config/config.toml
+++ b/config/config.toml
@@ -4,7 +4,7 @@ primary_hover_color="#005ED6"
sidebar_color="#242F48"
[default.endpoints]
api_url="http://localhost:8080"
-sdk_url=""
+sdk_url="http://localhost:9050/HyperLoader.js"
logo_url=""
favicon_url=""
agreement_url=""
diff --git a/cypress.config.js b/cypress.config.js
index 13df40532..39d284c70 100644
--- a/cypress.config.js
+++ b/cypress.config.js
@@ -9,6 +9,7 @@ module.exports = defineConfig({
// with any changed environment variables
return config;
},
+ chromeWebSecurity: false,
},
env: {
CYPRESS_USERNAME: process.env.CYPRESS_USERNAME || "cypress@gmail.com",
diff --git a/cypress/e2e/connector/connector.cy.js b/cypress/e2e/connector/connector.cy.js
index 3241f2d5e..24f548598 100644
--- a/cypress/e2e/connector/connector.cy.js
+++ b/cypress/e2e/connector/connector.cy.js
@@ -1,9 +1,16 @@
describe("connector", () => {
- const username = `cypress${Math.round(+new Date() / 1000)}@gmail.com`;
const password = "Cypress98#";
+ const username = `cypress${Math.round(+new Date() / 1000)}@gmail.com`;
+
+ const getIframeBody = () => {
+ return cy
+ .get("iframe")
+ .its("0.contentDocument.body")
+ .should("not.be.empty")
+ .then(cy.wrap);
+ };
- // Login before each testcase
- beforeEach(() => {
+ before(() => {
cy.visit("http://localhost:9000/dashboard/login");
cy.url().should("include", "/login");
cy.get("[data-testid=card-header]").should(
@@ -23,16 +30,27 @@ describe("connector", () => {
cy.get('button[type="submit"]').click({ force: true });
cy.get("[data-testid=skip-now]").click({ force: true });
- cy.url().should("include", "/dashboard/home");
- });
-
- it("Create a dummy connector", () => {
- cy.url().should("include", "/dashboard/home");
-
cy.get('[data-form-label="Business name"]').should("exist");
cy.get("[data-testid=merchant_name]").type("test_business");
cy.get("[data-button-for=startExploring]").click();
- cy.reload(true);
+ });
+
+ beforeEach(function () {
+ if (this.currentTest.title !== "Create a dummy connector") {
+ cy.visit("http://localhost:9000/dashboard/login");
+ cy.url().should("include", "/login");
+ cy.get("[data-testid=card-header]").should(
+ "contain",
+ "Hey there, Welcome back!",
+ );
+ cy.get("[data-testid=email]").type(username);
+ cy.get("[data-testid=password]").type(password);
+ cy.get('button[type="submit"]').click({ force: true });
+ cy.get("[data-testid=skip-now]").click({ force: true });
+ }
+ });
+
+ it("Create a dummy connector", () => {
cy.get("[data-testid=connectors]").click();
cy.get("[data-testid=paymentprocessors]").click();
cy.contains("Payment Processors").should("be.visible");
@@ -69,4 +87,31 @@ describe("connector", () => {
.scrollIntoView()
.should("be.visible");
});
+ it("Use the SDK to process a payment", () => {
+ cy.get("[data-testid=connectors]").click();
+ cy.get("[data-testid=paymentprocessors]").click();
+ cy.contains("Payment Processors").should("be.visible");
+ cy.get("[data-testid=home]").click();
+ cy.get("[data-button-for=tryItOut]").click();
+ cy.get('[data-breadcrumb="Explore Demo Checkout Experience"]').should(
+ "exist",
+ );
+ cy.get('[data-value="unitedStates(USD)"]').click();
+ cy.get('[data-dropdown-value="Germany (EUR)"]').click();
+ cy.get("[data-testid=amount]").find("input").clear().type("77");
+ cy.get("[data-button-for=showPreview]").click();
+ getIframeBody()
+ .find("[data-testid=cardNoInput]", { timeout: 20000 })
+ .should("exist")
+ .type("4242424242424242");
+ getIframeBody()
+ .find("[data-testid=expiryInput]")
+ .should("exist")
+ .type("0127");
+ getIframeBody().find("[data-testid=cvvInput]").should("exist").type("492");
+ cy.get("[data-button-for=payEUR77]").should("exist").click();
+ cy.contains("Payment Successful").should("be.visible");
+ cy.get("[data-button-for=goToPayment]").should("exist").click();
+ cy.url().should("include", "dashboard/payments");
+ });
});
From 2c31177c89fa122a139f2506ae12d7c1a497fb0a Mon Sep 17 00:00:00 2001
From: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 24 Oct 2024 00:26:10 +0000
Subject: [PATCH 2/8] chore(version): 2024.10.24.0
---
CHANGELOG.md | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 25183812d..a02d3249d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [conven
- - -
+## 2024.10.24.0
+
+### Testing
+
+- Creation of test payment using sdk cypress ([#1555](https://github.com/juspay/hyperswitch-control-center/pull/1555)) ([`1bd1e56`](https://github.com/juspay/hyperswitch-control-center/commit/1bd1e563f993ec483ef43651996b9c836e33de0d))
+
+**Full Changelog:** [`2024.10.22.2...2024.10.24.0`](https://github.com/juspay/hyperswitch-control-center/compare/2024.10.22.2...2024.10.24.0)
+
+- - -
+
## 2024.10.22.2
### Bug Fixes
From 9ff488b8edc99af45fc8f77ab1f56e6cef34a838 Mon Sep 17 00:00:00 2001
From: Riddhiagrawal001 <50551695+Riddhiagrawal001@users.noreply.github.com>
Date: Thu, 24 Oct 2024 12:12:48 +0530
Subject: [PATCH 3/8] chore: TwoFa restriction after multiple failed attempts
before login (#1594)
---
.../PreLoginModule/DecisionScreen.res | 2 +-
.../AuthModule/TwoFaAuth/TotpSetup.res | 34 +++--
.../AuthModule/TwoFaAuth/TwoFaLanding.res | 126 ++++++++++++++++++
.../AuthModule/TwoFaAuth/TwoFaTypes.res | 24 +++-
.../AuthModule/TwoFaAuth/TwoFaUtils.res | 30 +++++
src/screens/APIUtils/APIUtils.res | 1 +
src/screens/APIUtils/APIUtilsTypes.res | 1 +
7 files changed, 204 insertions(+), 14 deletions(-)
create mode 100644 src/entryPoints/AuthModule/TwoFaAuth/TwoFaLanding.res
diff --git a/src/entryPoints/AuthModule/PreLoginModule/DecisionScreen.res b/src/entryPoints/AuthModule/PreLoginModule/DecisionScreen.res
index d2c264667..64d6657c1 100644
--- a/src/entryPoints/AuthModule/PreLoginModule/DecisionScreen.res
+++ b/src/entryPoints/AuthModule/PreLoginModule/DecisionScreen.res
@@ -18,7 +18,7 @@ let make = () => {
| MERCHANT_SELECT
| ACCEPT_INVITE =>
- | TOTP =>
+ | TOTP =>
| FORCE_SET_PASSWORD
| RESET_PASSWORD =>
diff --git a/src/entryPoints/AuthModule/TwoFaAuth/TotpSetup.res b/src/entryPoints/AuthModule/TwoFaAuth/TotpSetup.res
index daf68f163..a07064f4f 100644
--- a/src/entryPoints/AuthModule/TwoFaAuth/TotpSetup.res
+++ b/src/entryPoints/AuthModule/TwoFaAuth/TotpSetup.res
@@ -4,16 +4,15 @@ let p3Regular = HSwitchUtils.getTextClass((P3, Regular))
module EnterAccessCode = {
@react.component
- let make = (~setTwoFaPageState, ~onClickVerifyAccessCode) => {
+ let make = (~setTwoFaPageState, ~onClickVerifyAccessCode, ~errorHandling) => {
let showToast = ToastState.useShowToast()
let verifyRecoveryCodeLogic = TotpHooks.useVerifyRecoveryCode()
let (recoveryCode, setRecoveryCode) = React.useState(_ => "")
let (buttonState, setButtonState) = React.useState(_ => Button.Normal)
- let verifyAccessCode = async () => {
+ let verifyAccessCode = async _ => {
+ open LogicUtils
try {
- open LogicUtils
-
setButtonState(_ => Button.Loading)
if recoveryCode->String.length > 0 {
@@ -25,7 +24,12 @@ module EnterAccessCode = {
}
setButtonState(_ => Button.Normal)
} catch {
- | _ => {
+ | Exn.Error(e) => {
+ let err = Exn.message(e)->Option.getOr("Something went wrong")
+ let errorCode = err->safeParse->getDictFromJsonObject->getString("code", "")
+ if errorCode == "UR_49" {
+ errorHandling(errorCode)
+ }
setRecoveryCode(_ => "")
setButtonState(_ => Button.Normal)
}
@@ -105,6 +109,7 @@ module ConfigureTotpScreen = {
~twoFaStatus,
~setTwoFaPageState,
~terminateTwoFactorAuth,
+ ~errorHandling,
) => {
open TwoFaTypes
@@ -115,9 +120,8 @@ module ConfigureTotpScreen = {
let (buttonState, setButtonState) = React.useState(_ => Button.Normal)
let verifyTOTP = async () => {
+ open LogicUtils
try {
- open LogicUtils
-
setButtonState(_ => Button.Loading)
if otp->String.length > 0 {
@@ -135,7 +139,12 @@ module ConfigureTotpScreen = {
}
setButtonState(_ => Button.Normal)
} catch {
- | _ => {
+ | Exn.Error(e) => {
+ let err = Exn.message(e)->Option.getOr("Something went wrong")
+ let errorCode = err->safeParse->getDictFromJsonObject->getString("code", "")
+ if errorCode == "UR_48" {
+ errorHandling(errorCode)
+ }
setOtp(_ => "")
setButtonState(_ => Button.Normal)
}
@@ -225,7 +234,7 @@ module ConfigureTotpScreen = {
}
@react.component
-let make = () => {
+let make = (~setTwoFaPageState, ~twoFaPageState, ~errorHandling) => {
open HSwitchUtils
open TwoFaTypes
@@ -238,7 +247,6 @@ let make = () => {
let (isQrVisible, setIsQrVisible) = React.useState(_ => false)
let (totpUrl, setTotpUrl) = React.useState(_ => "")
let (twoFaStatus, setTwoFaStatus) = React.useState(_ => TWO_FA_NOT_SET)
- let (twoFaPageState, setTwoFaPageState) = React.useState(_ => TOTP_SHOW_QR)
let (showNewQR, setShowNewQR) = React.useState(_ => false)
let delayTimer = () => {
@@ -325,14 +333,16 @@ let make = () => {
{switch twoFaPageState {
| TOTP_SHOW_QR =>
| TOTP_SHOW_RC =>
| TOTP_INPUT_RECOVERY_CODE =>
-
+
}}
{"Log in with a different account?"->React.string}
diff --git a/src/entryPoints/AuthModule/TwoFaAuth/TwoFaLanding.res b/src/entryPoints/AuthModule/TwoFaAuth/TwoFaLanding.res
new file mode 100644
index 000000000..0c384863e
--- /dev/null
+++ b/src/entryPoints/AuthModule/TwoFaAuth/TwoFaLanding.res
@@ -0,0 +1,126 @@
+let p2Regular = HSwitchUtils.getTextClass((P2, Regular))
+
+module AttemptsExpiredComponent = {
+ @react.component
+ let make = (~expiredType, ~setTwoFaPageState, ~setTwoFaStatus) => {
+ open TwoFaTypes
+ open HSwitchUtils
+ let {setAuthStatus} = React.useContext(AuthInfoProvider.authStatusContext)
+
+ let belowComponent = switch expiredType {
+ | TOTP_ATTEMPTS_EXPIRED =>
+
+ {"or "->React.string}
+ {
+ setTwoFaStatus(_ => TwoFaNotExpired)
+ setTwoFaPageState(_ => TOTP_INPUT_RECOVERY_CODE)
+ }}>
+ {"Use recovery-code"->React.string}
+
+
+ | RC_ATTEMPTS_EXPIRED =>
+
+ {"or "->React.string}
+ {
+ setTwoFaStatus(_ => TwoFaNotExpired)
+ setTwoFaPageState(_ => TOTP_SHOW_QR)
+ }}>
+ {"Use totp"->React.string}
+
+
+ | TWO_FA_EXPIRED => React.null
+ }
+
+
+
+
+
+ {"There have been multiple unsuccessful sign-in attempts for this account. Please wait a moment before trying again."->React.string}
+
+ {belowComponent}
+
+
+ {"Log in with a different account?"->React.string}
+
setAuthStatus(LoggedOut)}>
+ {"Click here to log out."->React.string}
+
+
+
+
+ }
+}
+
+@react.component
+let make = () => {
+ open TwoFaTypes
+ let getURL = APIUtils.useGetURL()
+ let fetchDetails = APIUtils.useGetMethod()
+ let {setAuthStatus} = React.useContext(AuthInfoProvider.authStatusContext)
+ let (twoFaStatus, setTwoFaStatus) = React.useState(_ => TwoFaNotExpired)
+ let (twoFaPageState, setTwoFaPageState) = React.useState(_ => TOTP_SHOW_QR)
+ let (screenState, setScreenState) = React.useState(_ => PageLoaderWrapper.Loading)
+
+ let handlePageBasedOnAttempts = responseDict => {
+ switch responseDict {
+ | Some(value) =>
+ if value.totp.attemptsRemaining > 0 && value.recoveryCode.attemptsRemaining > 0 {
+ setTwoFaStatus(_ => TwoFaNotExpired)
+ setTwoFaPageState(_ => TOTP_SHOW_QR)
+ } else if value.totp.attemptsRemaining == 0 && value.recoveryCode.attemptsRemaining == 0 {
+ setTwoFaStatus(_ => TwoFaExpired(TWO_FA_EXPIRED))
+ } else if value.totp.attemptsRemaining == 0 {
+ setTwoFaStatus(_ => TwoFaExpired(TOTP_ATTEMPTS_EXPIRED))
+ } else if value.recoveryCode.attemptsRemaining == 0 {
+ setTwoFaStatus(_ => TwoFaExpired(RC_ATTEMPTS_EXPIRED))
+ }
+ | None => setTwoFaStatus(_ => TwoFaNotExpired)
+ }
+ }
+
+ let checkTwofaStatus = async () => {
+ try {
+ let url = getURL(
+ ~entityName=USERS,
+ ~userType=#CHECK_TWO_FACTOR_AUTH_STATUS_V2,
+ ~methodType=Get,
+ )
+ let response = await fetchDetails(url)
+ let responseDict = response->TwoFaUtils.jsonTocheckTwofaResponseType
+ handlePageBasedOnAttempts(responseDict.status)
+ setScreenState(_ => PageLoaderWrapper.Success)
+ } catch {
+ | _ => {
+ setScreenState(_ => PageLoaderWrapper.Error("Failed to fetch!"))
+ setAuthStatus(LoggedOut)
+ }
+ }
+ }
+
+ let errorHandling = errorCode => {
+ if errorCode == "UR_48" {
+ setTwoFaStatus(_ => TwoFaExpired(TOTP_ATTEMPTS_EXPIRED))
+ } else if errorCode == "UR_49" {
+ setTwoFaStatus(_ => TwoFaExpired(RC_ATTEMPTS_EXPIRED))
+ }
+ }
+
+ React.useEffect(() => {
+ checkTwofaStatus()->ignore
+ None
+ }, [])
+
+
+ {switch twoFaStatus {
+ | TwoFaExpired(expiredType) =>
+
+ | TwoFaNotExpired =>
+ }}
+
+}
diff --git a/src/entryPoints/AuthModule/TwoFaAuth/TwoFaTypes.res b/src/entryPoints/AuthModule/TwoFaAuth/TwoFaTypes.res
index 289485444..fad0c7f59 100644
--- a/src/entryPoints/AuthModule/TwoFaAuth/TwoFaTypes.res
+++ b/src/entryPoints/AuthModule/TwoFaAuth/TwoFaTypes.res
@@ -1,3 +1,25 @@
-type twoFaPageState = TOTP_SHOW_QR | TOTP_SHOW_RC | TOTP_INPUT_RECOVERY_CODE
+type twoFaPageState =
+ | TOTP_SHOW_QR
+ | TOTP_SHOW_RC
+ | TOTP_INPUT_RECOVERY_CODE
type twoFaStatus = TWO_FA_NOT_SET | TWO_FA_SET
+
+type twoFaValueType = {
+ isCompleted: bool,
+ attemptsRemaining: int,
+}
+
+type twoFatype = {
+ totp: twoFaValueType,
+ recoveryCode: twoFaValueType,
+}
+
+type checkTwofaResponseType = {status: option
}
+
+type expiredTypes =
+ | TOTP_ATTEMPTS_EXPIRED
+ | RC_ATTEMPTS_EXPIRED
+ | TWO_FA_EXPIRED
+
+type twoFaStatusType = TwoFaExpired(expiredTypes) | TwoFaNotExpired
diff --git a/src/entryPoints/AuthModule/TwoFaAuth/TwoFaUtils.res b/src/entryPoints/AuthModule/TwoFaAuth/TwoFaUtils.res
index 3832ceac7..8c5e06e8b 100644
--- a/src/entryPoints/AuthModule/TwoFaAuth/TwoFaUtils.res
+++ b/src/entryPoints/AuthModule/TwoFaAuth/TwoFaUtils.res
@@ -63,3 +63,33 @@ let downloadRecoveryCodes = (~recoveryCodes) => {
~content=JSON.stringifyWithIndent(recoveryCodes->getJsonFromArrayOfString, 3),
)
}
+
+let jsonToTwoFaValueType: Dict.t<'a> => TwoFaTypes.twoFaValueType = dict => {
+ open LogicUtils
+
+ {
+ isCompleted: dict->getBool("is_completed", false),
+ attemptsRemaining: dict->getInt("remaining_attempts", 4),
+ }
+}
+
+let jsonTocheckTwofaResponseType: JSON.t => TwoFaTypes.checkTwofaResponseType = json => {
+ open LogicUtils
+ let jsonToDict = json->getDictFromJsonObject->Dict.get("status")
+
+ let statusValue = switch jsonToDict {
+ | Some(json) => {
+ let dict = json->getDictFromJsonObject
+ let twoFaValue: TwoFaTypes.twoFatype = {
+ totp: dict->getDictfromDict("totp")->jsonToTwoFaValueType,
+ recoveryCode: dict->getDictfromDict("recovery_code")->jsonToTwoFaValueType,
+ }
+ Some(twoFaValue)
+ }
+ | None => None
+ }
+
+ {
+ status: statusValue,
+ }
+}
diff --git a/src/screens/APIUtils/APIUtils.res b/src/screens/APIUtils/APIUtils.res
index 6888eb4de..12f79baeb 100644
--- a/src/screens/APIUtils/APIUtils.res
+++ b/src/screens/APIUtils/APIUtils.res
@@ -661,6 +661,7 @@ let useGetURL = () => {
// SPT FLOWS (Totp)
| #BEGIN_TOTP => `${userUrl}/2fa/totp/begin`
+ | #CHECK_TWO_FACTOR_AUTH_STATUS_V2 => `${userUrl}/2fa/v2`
| #VERIFY_TOTP => `${userUrl}/2fa/totp/verify`
| #VERIFY_RECOVERY_CODE => `${userUrl}/2fa/recovery_code/verify`
| #GENERATE_RECOVERY_CODES => `${userUrl}/2fa/recovery_code/generate`
diff --git a/src/screens/APIUtils/APIUtilsTypes.res b/src/screens/APIUtils/APIUtilsTypes.res
index aaa908515..82a6d2ca4 100644
--- a/src/screens/APIUtils/APIUtilsTypes.res
+++ b/src/screens/APIUtils/APIUtilsTypes.res
@@ -114,6 +114,7 @@ type userType = [
| #USER_DETAILS
| #LIST_ROLES_FOR_ROLE_UPDATE
| #ACCEPT_INVITATION_HOME
+ | #CHECK_TWO_FACTOR_AUTH_STATUS_V2
| #NONE
]
From 46f4de64f0c68e587a62c876b56e94a5e25ec26d Mon Sep 17 00:00:00 2001
From: Gitanjli <96485413+gitanjli525@users.noreply.github.com>
Date: Thu, 24 Oct 2024 12:16:51 +0530
Subject: [PATCH 4/8] fix: auto retry bug (#1639)
---
src/screens/Settings/MerchantAccountUtils.res | 34 +++++++++----------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/src/screens/Settings/MerchantAccountUtils.res b/src/screens/Settings/MerchantAccountUtils.res
index b4168ed63..93ab9c946 100644
--- a/src/screens/Settings/MerchantAccountUtils.res
+++ b/src/screens/Settings/MerchantAccountUtils.res
@@ -403,14 +403,6 @@ let validateCustom = (key, errors, value, isLiveMode) => {
Dict.set(errors, key->validationFieldsMapper, "Please Enter Valid URL"->JSON.Encode.string)
}
}
- | MaxAutoRetries =>
- if !RegExp.test(%re("/^(?:[1-5])$/"), value) {
- Dict.set(
- errors,
- key->validationFieldsMapper,
- "Please enter integer value from 1 to 5"->JSON.Encode.string,
- )
- }
| _ => ()
}
}
@@ -426,16 +418,24 @@ let validateMerchantAccountForm = (
let valuesDict = values->getDictFromJsonObject
fieldsToValidate->Array.forEach(key => {
- let val = valuesDict->getJsonObjectFromDict(key->validationFieldsMapper)
-
- switch val->JSON.Classify.classify {
- | String(str) =>
- switch str->getNonEmptyString {
- | Some(str) => key->validateCustom(errors, str, isLiveMode)
- | _ => ()
+ switch key {
+ | MaxAutoRetries => {
+ let value = getInt(valuesDict, key->validationFieldsMapper, 0)
+ if !RegExp.test(%re("/^(?:[1-5])$/"), value->Int.toString) {
+ Dict.set(
+ errors,
+ key->validationFieldsMapper,
+ "Please enter integer value from 1 to 5"->JSON.Encode.string,
+ )
+ }
+ }
+ | _ => {
+ let value = getString(valuesDict, key->validationFieldsMapper, "")->getNonEmptyString
+ switch value {
+ | Some(str) => key->validateCustom(errors, str, isLiveMode)
+ | _ => ()
+ }
}
- | Number(num) => key->validateCustom(errors, num->Float.toString, isLiveMode)
- | _ => ()
}
})
From 9108801555d2e3e03908dbe33f4a52ce9c0503a3 Mon Sep 17 00:00:00 2001
From: Kanika Bansal
Date: Thu, 24 Oct 2024 12:52:47 +0530
Subject: [PATCH 5/8] fix: merchant account credentials not shown in profile
view (#1626)
---
src/container/MerchantAccountContainer.res | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/container/MerchantAccountContainer.res b/src/container/MerchantAccountContainer.res
index e85142445..6c9ec2c10 100644
--- a/src/container/MerchantAccountContainer.res
+++ b/src/container/MerchantAccountContainer.res
@@ -18,8 +18,9 @@ let make = () => {
let setUpConnectoreContainer = async () => {
try {
setScreenState(_ => PageLoaderWrapper.Loading)
-
- let _ = await fetchMerchantAccountDetails()
+ if !checkUserEntity([#Profile]) {
+ let _ = await fetchMerchantAccountDetails()
+ }
if userHasAccess(~groupAccess=ConnectorsView) === Access {
if !featureFlagDetails.isLiveMode {
let _ = await fetchConnectorListResponse()
@@ -67,6 +68,7 @@ let make = () => {
}}
Option.isNone}>
From aac4adabf17e96ef7d02fe91048fca8b668030a8 Mon Sep 17 00:00:00 2001
From: Jeeva Ramachandran <120017870+JeevaRamu0104@users.noreply.github.com>
Date: Thu, 24 Oct 2024 15:16:29 +0530
Subject: [PATCH 6/8] chore: Add merchant specific config (#1643)
---
config/config.toml | 5 +
cypress/e2e/auth/auth.cy.js | 2 +-
cypress/start_hyperswitch.sh | 2 +-
src/Recoils/HyperswitchAtom.res | 3 +
src/entryPoints/FeatureFlagUtils.res | 23 ++
src/entryPoints/HyperSwitchApp.res | 11 +-
src/entryPoints/HyperSwitchEntry.res | 2 +-
src/entryPoints/SidebarValues.res | 9 +-
src/hooks/AuthHooks.res | 6 +-
.../Hooks/MerchantSpecificConfigHook.res | 57 +++++
src/server/Server.res | 12 +-
src/server/config.mjs | 220 ++++++++++++------
webpack.dev.js | 15 +-
13 files changed, 287 insertions(+), 80 deletions(-)
create mode 100644 src/screens/Hooks/MerchantSpecificConfigHook.res
diff --git a/config/config.toml b/config/config.toml
index 2c1098c21..0a18fa679 100644
--- a/config/config.toml
+++ b/config/config.toml
@@ -49,3 +49,8 @@ down_time=false
tax_processor=true
transaction_view=false
x_feature_route=false
+[default.merchant_config]
+[default.merchant_config.new_analytics]
+org_ids=[]
+merchant_ids=[]
+profile_ids=[]
diff --git a/cypress/e2e/auth/auth.cy.js b/cypress/e2e/auth/auth.cy.js
index d52c4df65..d72dfb8f7 100644
--- a/cypress/e2e/auth/auth.cy.js
+++ b/cypress/e2e/auth/auth.cy.js
@@ -45,7 +45,7 @@ describe("Auth Module", () => {
});
it("sets true the feature flag for magic link and forgot password,then checks auth page back button functioning", () => {
- cy.intercept("GET", "merchant-config?domain=default", {
+ cy.intercept("GET", "/dashboard/config/feature?domain=default", {
statusCode: 200,
body: {
theme: {
diff --git a/cypress/start_hyperswitch.sh b/cypress/start_hyperswitch.sh
index c1f1e3950..5579379e7 100755
--- a/cypress/start_hyperswitch.sh
+++ b/cypress/start_hyperswitch.sh
@@ -28,4 +28,4 @@ echo "[network_tokenization_service] section removed from $toml_file."
chmod +x /usr/local/bin/docker-compose
# Start Docker Compose services in detached mode
-docker-compose up -d pg redis-standalone migration_runner hyperswitch-server
\ No newline at end of file
+docker-compose up -d pg redis-standalone migration_runner hyperswitch-server hyperswitch-web
\ No newline at end of file
diff --git a/src/Recoils/HyperswitchAtom.res b/src/Recoils/HyperswitchAtom.res
index 335b1b54a..e10bc3ef6 100644
--- a/src/Recoils/HyperswitchAtom.res
+++ b/src/Recoils/HyperswitchAtom.res
@@ -20,6 +20,9 @@ let featureFlagAtom: Recoil.recoilAtom = Recoil.at
"featureFlag",
JSON.Encode.null->FeatureFlagUtils.featureFlagType,
)
+let merchantSpecificConfigAtom: Recoil.recoilAtom<
+ FeatureFlagUtils.merchantSpecificConfig,
+> = Recoil.atom("merchantSpecificConfig", JSON.Encode.null->FeatureFlagUtils.merchantSpecificConfig)
let paypalAccountStatusAtom: Recoil.recoilAtom = Recoil.atom(
"paypalAccountStatusAtom",
PayPalFlowTypes.Connect_paypal_landing,
diff --git a/src/entryPoints/FeatureFlagUtils.res b/src/entryPoints/FeatureFlagUtils.res
index a73672db8..729b20cd2 100644
--- a/src/entryPoints/FeatureFlagUtils.res
+++ b/src/entryPoints/FeatureFlagUtils.res
@@ -1,3 +1,9 @@
+type config = {
+ orgIds: array,
+ merchantIds: array,
+ profileIds: array,
+}
+type merchantSpecificConfig = {newAnalytics: config}
type featureFlag = {
default: bool,
testLiveToggle: bool,
@@ -82,3 +88,20 @@ let featureFlagType = (featureFlags: JSON.t) => {
}
typedFeatureFlag
}
+
+let configMapper = dict => {
+ open LogicUtils
+ {
+ orgIds: dict->getStrArrayFromDict("org_ids", []),
+ merchantIds: dict->getStrArrayFromDict("merchant_ids", []),
+ profileIds: dict->getStrArrayFromDict("profile_ids", []),
+ }
+}
+
+let merchantSpecificConfig = (config: JSON.t) => {
+ open LogicUtils
+ let dict = config->getDictFromJsonObject
+ {
+ newAnalytics: dict->getDictfromDict("new_analytics")->configMapper,
+ }
+}
diff --git a/src/entryPoints/HyperSwitchApp.res b/src/entryPoints/HyperSwitchApp.res
index bf06ff8ec..8ae0c0187 100644
--- a/src/entryPoints/HyperSwitchApp.res
+++ b/src/entryPoints/HyperSwitchApp.res
@@ -18,7 +18,14 @@ let make = () => {
let merchantDetailsTypedValue = Recoil.useRecoilValueFromAtom(merchantDetailsValueAtom)
let featureFlagDetails = featureFlagAtom->Recoil.useRecoilValueFromAtom
let (userGroupACL, setuserGroupACL) = Recoil.useRecoilState(userGroupACLAtom)
+
+ let {
+ fetchMerchantSpecificConfig,
+ useIsFeatureEnabledForMerchant,
+ merchantSpecificConfig,
+ } = MerchantSpecificConfigHook.useMerchantSpecificConfig()
let {fetchUserGroupACL, userHasAccess} = GroupACLHooks.useUserGroupACLHook()
+
let {userInfo: {orgId, merchantId, profileId, roleId}, checkUserEntity} = React.useContext(
UserInfoProvider.defaultContext,
)
@@ -42,6 +49,7 @@ let make = () => {
setScreenState(_ => PageLoaderWrapper.Loading)
setuserGroupACL(_ => None)
Window.connectorWasmInit()->ignore
+ let _ = await fetchMerchantSpecificConfig()
let _ = await fetchUserGroupACL()
switch url.path->urlPath {
| list{"unauthorized"} => RescriptReactRouter.push(appendDashboardPath(~url="/home"))
@@ -161,7 +169,8 @@ let make = () => {
| list{"new-analytics-payment"} =>
diff --git a/src/entryPoints/HyperSwitchEntry.res b/src/entryPoints/HyperSwitchEntry.res
index 140d665fe..c79afb7f8 100644
--- a/src/entryPoints/HyperSwitchEntry.res
+++ b/src/entryPoints/HyperSwitchEntry.res
@@ -51,7 +51,7 @@ module HyperSwitchEntryComponent = {
let fetchConfig = async () => {
try {
let domain = HyperSwitchEntryUtils.getSessionData(~key="domain", ~defaultValue="default")
- let apiURL = `${GlobalVars.getHostUrlWithBasePath}/config/merchant-config?domain=${domain}`
+ let apiURL = `${GlobalVars.getHostUrlWithBasePath}/config/feature?domain=${domain}`
let res = await fetchDetails(apiURL)
let featureFlags = res->FeatureFlagUtils.featureFlagType
setFeatureFlag(_ => featureFlags)
diff --git a/src/entryPoints/SidebarValues.res b/src/entryPoints/SidebarValues.res
index 8c8537a0b..0ad447a0a 100644
--- a/src/entryPoints/SidebarValues.res
+++ b/src/entryPoints/SidebarValues.res
@@ -655,7 +655,12 @@ let useGetSidebarValues = (~isReconEnabled: bool) => {
taxProcessor,
newAnalytics,
} = featureFlagDetails
-
+ let {
+ useIsFeatureEnabledForMerchant,
+ merchantSpecificConfig,
+ } = MerchantSpecificConfigHook.useMerchantSpecificConfig()
+ let isNewAnalyticsEnable =
+ newAnalytics && useIsFeatureEnabledForMerchant(merchantSpecificConfig.newAnalytics)
let sidebar = [
productionAccessComponent(quickStart, userHasAccess),
default->home,
@@ -674,7 +679,7 @@ let useGetSidebarValues = (~isReconEnabled: bool) => {
authenticationAnalyticsFlag,
disputeAnalytics,
performanceMonitorFlag,
- newAnalytics,
+ isNewAnalyticsEnable,
~userHasAccess,
),
default->workflow(isSurchargeEnabled, ~userHasAccess, ~isPayoutEnabled=payOut, ~userEntity),
diff --git a/src/hooks/AuthHooks.res b/src/hooks/AuthHooks.res
index 29ecab6c7..9aa076eab 100644
--- a/src/hooks/AuthHooks.res
+++ b/src/hooks/AuthHooks.res
@@ -1,6 +1,10 @@
type contentType = Headers(string) | Unknown
let headersForXFeature = (~uri, ~headers) => {
- if uri->String.includes("lottie-files") || uri->String.includes("config/merchant-access") {
+ if (
+ uri->String.includes("lottie-files") ||
+ uri->String.includes("config/merchant") ||
+ uri->String.includes("config/feature")
+ ) {
headers->Dict.set("Content-Type", `application/json`)
} else {
headers->Dict.set("x-feature", "integ-custom")
diff --git a/src/screens/Hooks/MerchantSpecificConfigHook.res b/src/screens/Hooks/MerchantSpecificConfigHook.res
new file mode 100644
index 000000000..34f942409
--- /dev/null
+++ b/src/screens/Hooks/MerchantSpecificConfigHook.res
@@ -0,0 +1,57 @@
+/**
+ * @module MerchantSpecificConfigHook
+ *
+ * @description This exposes a hook to fetch the merchant specific config
+ * and to check if the merchant has access to config
+ *
+ * @functions
+ * - fetchMerchantSpecificConfig : fetches the list of user group level access
+ * - useIsFeatureEnabledForMerchant: checks if the merchant has access
+ * @params
+ * - config : merchant config
+ *
+ *
+ *
+ */
+type useMerchantSpecificConfig = {
+ fetchMerchantSpecificConfig: unit => promise,
+ useIsFeatureEnabledForMerchant: FeatureFlagUtils.config => bool,
+ merchantSpecificConfig: FeatureFlagUtils.merchantSpecificConfig,
+}
+
+let useMerchantSpecificConfig = () => {
+ open APIUtils
+ let updateAPIHook = useUpdateMethod(~showErrorToast=false)
+ let setMerchantSpecificConfig =
+ HyperswitchAtom.merchantSpecificConfigAtom->Recoil.useSetRecoilState
+ let {userInfo: {orgId, merchantId, profileId}} = React.useContext(UserInfoProvider.defaultContext)
+ let merchantSpecificConfig =
+ HyperswitchAtom.merchantSpecificConfigAtom->Recoil.useRecoilValueFromAtom
+ let fetchMerchantSpecificConfig = async () => {
+ try {
+ let domain = HyperSwitchEntryUtils.getSessionData(~key="domain", ~defaultValue="default")
+ let merchantConfigURL = ` ${GlobalVars.getHostUrlWithBasePath}/config/merchant?domain=${domain}`
+ let body =
+ [
+ ("org_id", orgId->JSON.Encode.string),
+ ("merchant_id", merchantId->JSON.Encode.string),
+ ("profile_id", profileId->JSON.Encode.string),
+ ]->LogicUtils.getJsonFromArrayOfJson
+ let response = await updateAPIHook(merchantConfigURL, body, Post)
+ let mapMerchantSpecificConfig = response->FeatureFlagUtils.merchantSpecificConfig
+ setMerchantSpecificConfig(_ => mapMerchantSpecificConfig)
+ } catch {
+ | Exn.Error(e) => {
+ let err = Exn.message(e)->Option.getOr("Failed to Fetch!")
+ Exn.raiseError(err)
+ }
+ }
+ }
+ let useIsFeatureEnabledForMerchant = (config: FeatureFlagUtils.config) => {
+ config.orgIds->Array.length > 0 ||
+ config.merchantIds->Array.length > 0 ||
+ config.profileIds->Array.length > 0
+ }
+
+ {fetchMerchantSpecificConfig, useIsFeatureEnabledForMerchant, merchantSpecificConfig}
+}
diff --git a/src/server/Server.res b/src/server/Server.res
index 3c7739d37..9397f95a9 100644
--- a/src/server/Server.res
+++ b/src/server/Server.res
@@ -12,6 +12,10 @@ open NodeJs
external configHandler: (Http.request, Http.response, bool, string, string) => unit =
"configHandler"
+@module("./config.mjs")
+external merchantConfigHandler: (Http.request, Http.response, bool, string, string) => unit =
+ "merchantConfigHandler"
+
@module("./health.mjs")
external healthHandler: (Http.request, Http.response) => unit = "healthHandler"
@@ -69,7 +73,13 @@ let serverHandler: Http.serverHandler = (request, response) => {
->String.replaceRegExp(%re("/^\/\//"), "/")
->String.replaceRegExp(%re("/^\/v4\//"), "/")
- if path->String.includes("/config/merchant-config") && request.method === "GET" {
+ if path->String.includes("/config/merchant") && request.method === "POST" {
+ let path = env->Dict.get("configPath")->Option.getOr("dist/server/config/config.toml")
+ Promise.make((resolve, _reject) => {
+ merchantConfigHandler(request, response, true, domain, path)
+ ()->(resolve(_))
+ })
+ } else if path->String.includes("/config/feature") && request.method === "GET" {
let path = env->Dict.get("configPath")->Option.getOr("dist/server/config/config.toml")
Promise.make((resolve, _reject) => {
configHandler(request, response, true, domain, path)
diff --git a/src/server/config.mjs b/src/server/config.mjs
index e901e854f..8e391cf86 100644
--- a/src/server/config.mjs
+++ b/src/server/config.mjs
@@ -1,95 +1,173 @@
import * as Fs from "fs";
import toml from "@iarna/toml";
-function updateConfigWithEnv(updatedConfig, domain = "", prefix = "") {
- for (const key in updatedConfig) {
- if (typeof updatedConfig[key] === "object" && updatedConfig[key] !== null) {
- updateConfigWithEnv(updatedConfig[key], domain, key); // Recursively update nested objects
+// Helper function for error handling
+const errorHandler = (res, errorMessage = "something went wrong") => {
+ res.writeHead(500, { "Content-Type": "text/plain" });
+ console.error(errorMessage);
+ res.end("Internal Server Error");
+};
+
+// Update config with environment variables
+function updateConfigWithEnv(config, domain = "", prefix = "") {
+ for (const key in config) {
+ if (typeof config[key] === "object" && config[key] !== null) {
+ updateConfigWithEnv(config[key], domain, key); // Recursively update nested objects
} else {
- // Check if environment variable exists for the key
const envVar = process.env[`${domain}__${prefix}__${key}`];
if (envVar !== undefined) {
- // Convert string to appropriate type if necessary (e.g., "true" to true)
- if (typeof updatedConfig[key] === "boolean") {
- updatedConfig[key] = envVar.toLowerCase() === "true";
- } else if (typeof updatedConfig[key] === "number") {
- updatedConfig[key] = parseFloat(envVar);
- } else {
- updatedConfig[key] = envVar;
- }
+ config[key] = inferType(config[key], envVar); // Convert string to appropriate type
}
}
}
- return updatedConfig;
+
+ return config;
}
-const errorHandler = (res, errorMessage = "something went wrong") => {
- res.writeHead(500, { "Content-Type": "text/plain" });
- console.log(errorMessage);
- res.end("Internal Server Error");
+// Infer type based on the original value's type
+const inferType = (originalValue, envValue) => {
+ if (typeof originalValue === "boolean")
+ return envValue.toLowerCase() === "true";
+ if (typeof originalValue === "number") return parseFloat(envValue);
+ return envValue;
};
-// Main function to read TOML file, override config, and output the result
-const configHandler = (
- _req,
- res,
- is_deployed = false,
- domain = "default",
- configPath = "dist/server/config/config.toml",
-) => {
- let configFile = is_deployed ? configPath : "config/config.toml";
- try {
- Fs.readFile(configFile, { encoding: "utf8" }, (err, data) => {
+// Check if env values exist or fall back to TOML config
+function checkEnvValues(env, tomlConfig) {
+ if (typeof env === "string") {
+ return env.split(",");
+ }
+ if (typeof tomlConfig === "object" && tomlConfig !== null) {
+ return tomlConfig;
+ }
+ return [];
+}
+
+// Update merchant config using environment variables
+function updateMerchantConfigWithEnv(tomlConfig, body, domain = "default") {
+ let modifiedConfig = {};
+ for (const key in tomlConfig) {
+ const envOrgIds =
+ process.env[`${domain}__merchant_config__${key}__org_ids`];
+ const envMerchantIds =
+ process.env[`${domain}__merchant_config__${key}__merchant_ids`];
+ const envProfileIds =
+ process.env[`${domain}__merchant_config__${key}__profile_ids`];
+
+ const orgIds = checkEnvValues(envOrgIds, tomlConfig[key].org_ids).filter(
+ (id) => body.org_id === id,
+ );
+ const merchantIds = checkEnvValues(
+ envMerchantIds,
+ tomlConfig[key].merchant_ids,
+ ).filter((id) => body.org_id === id);
+ const profileIds = checkEnvValues(
+ envProfileIds,
+ tomlConfig[key].profile_ids,
+ ).filter((id) => body.org_id === id);
+
+ modifiedConfig[key] = {
+ org_ids: orgIds,
+ merchant_ids: merchantIds,
+ profile_ids: profileIds,
+ };
+ }
+ return modifiedConfig;
+}
+
+// Read and parse TOML config file
+const readTomlConfig = (configPath, res) => {
+ return new Promise((resolve, reject) => {
+ Fs.readFile(configPath, { encoding: "utf8" }, (err, data) => {
if (err) {
errorHandler(res, "Error on Reading File");
- return;
- }
- let config;
- try {
- config = toml.parse(data);
- } catch {
- errorHandler(res, "Error on Parsing toml");
- return;
+ return reject(err);
}
- let merchantConfig = config["default"];
try {
- // If the domain is present in the toml file
- if (
- domain.length > 0 &&
- config[domain] != undefined &&
- Object.keys(config[domain]).length > 0
- ) {
- merchantConfig = updateConfigWithEnv(config[domain], domain, "theme");
- }
- // If the domain not is present in the toml file but need to overide the default value with the theme set in the env
- else if (domain && domain.length > 0 && domain !== undefined) {
- merchantConfig = updateConfigWithEnv(
- config["default"],
- domain,
- "theme",
- );
- } else {
- merchantConfig = updateConfigWithEnv("default", "", merchantConfig);
- }
- } catch {
- errorHandler(res, "Error on Overding ENV");
- return;
- }
- if (typeof merchantConfig === "object") {
- res.writeHead(200, {
- "Content-Type": "application/json",
- "Access-Control-Allow-Origin": "*",
- });
- res.write(JSON.stringify(merchantConfig));
- res.end();
- } else {
- errorHandler(res, "Error on sending response");
- return;
+ resolve(toml.parse(data));
+ } catch (err) {
+ errorHandler(res, "Error on Parsing TOML");
+ reject(err);
}
});
+ });
+};
+
+// Main config handler
+const configHandler = async (
+ req,
+ res,
+ isDeployed = false,
+ domain = "default",
+ configPath = "dist/server/config/config.toml",
+) => {
+ const filePath = isDeployed ? configPath : "config/config.toml";
+ try {
+ const config = await readTomlConfig(filePath, res);
+ let merchantConfig = config.default;
+
+ if (config[domain] && Object.keys(config[domain]).length > 0) {
+ merchantConfig = updateConfigWithEnv(config[domain], domain, "theme");
+ } else if (domain.length > 0) {
+ merchantConfig = updateConfigWithEnv(config.default, domain, "theme");
+ } else {
+ merchantConfig = updateConfigWithEnv(merchantConfig, "default", "");
+ }
+ if (merchantConfig && merchantConfig["merchant_config"]) {
+ delete merchantConfig["merchant_config"];
+ }
+
+ res.writeHead(200, {
+ "Content-Type": "application/json",
+ "Access-Control-Allow-Origin": "*",
+ });
+ res.end(JSON.stringify(merchantConfig));
} catch (error) {
errorHandler(res, "Server Error");
}
};
-export { configHandler };
+/*
+@module getRequestBody
+
+To Parse the request body
+
+*/
+
+// Get request body
+const getRequestBody = (req) => {
+ return new Promise((resolve, reject) => {
+ let body = "";
+ req.on("data", (chunk) => (body += chunk.toString()));
+ req.on("end", () => resolve(JSON.parse(body)));
+ req.on("error", reject);
+ });
+};
+
+// Merchant config handler
+const merchantConfigHandler = async (
+ req,
+ res,
+ isDeployed = false,
+ domain = "default",
+ configPath = "dist/server/config/config.toml",
+) => {
+ const filePath = isDeployed ? configPath : "config/config.toml";
+ try {
+ const body = await getRequestBody(req);
+ const config = await readTomlConfig(filePath, res);
+ const merchantConfig =
+ config[domain]?.merchant_config || config.default.merchant_config;
+ const data = updateMerchantConfigWithEnv(merchantConfig, body, domain);
+
+ res.writeHead(200, {
+ "Content-Type": "application/json",
+ "Access-Control-Allow-Origin": "*",
+ });
+ res.end(JSON.stringify(data));
+ } catch (error) {
+ console.log(error);
+ errorHandler(res, "Server Error");
+ }
+};
+export { configHandler, merchantConfigHandler };
diff --git a/webpack.dev.js b/webpack.dev.js
index 7c5d50ead..f588b7340 100644
--- a/webpack.dev.js
+++ b/webpack.dev.js
@@ -11,7 +11,7 @@ let port = 9000;
let proxy = {};
let configMiddleware = (req, res, next) => {
- if (req.path.includes("/config/merchant-config") && req.method == "GET") {
+ if (req.path.includes("/config/feature") && req.method == "GET") {
let { domain = "default" } = req.query;
config
.then((result) => {
@@ -24,6 +24,19 @@ let configMiddleware = (req, res, next) => {
});
return;
}
+ if (req.path.includes("/config/merchant") && req.method == "POST") {
+ let { domain = "default" } = req.query;
+ config
+ .then((result) => {
+ result.merchantConfigHandler(req, res, false, domain);
+ })
+ .catch((error) => {
+ console.log(error, "error");
+ res.writeHead(500, { "Content-Type": "text/plain" });
+ res.end("Internal Server Error");
+ });
+ return;
+ }
next();
};
From fc44a6a650cdbc001f40148662527909911437aa Mon Sep 17 00:00:00 2001
From: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 25 Oct 2024 00:26:30 +0000
Subject: [PATCH 7/8] chore(version): 2024.10.25.0
---
CHANGELOG.md | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a02d3249d..4c030976f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [conven
- - -
+## 2024.10.25.0
+
+### Bug Fixes
+
+- Auto retry bug ([#1639](https://github.com/juspay/hyperswitch-control-center/pull/1639)) ([`46f4de6`](https://github.com/juspay/hyperswitch-control-center/commit/46f4de64f0c68e587a62c876b56e94a5e25ec26d))
+- Merchant account credentials not shown in profile view ([#1626](https://github.com/juspay/hyperswitch-control-center/pull/1626)) ([`9108801`](https://github.com/juspay/hyperswitch-control-center/commit/9108801555d2e3e03908dbe33f4a52ce9c0503a3))
+
+### Miscellaneous Tasks
+
+- TwoFa restriction after multiple failed attempts before login ([#1594](https://github.com/juspay/hyperswitch-control-center/pull/1594)) ([`9ff488b`](https://github.com/juspay/hyperswitch-control-center/commit/9ff488b8edc99af45fc8f77ab1f56e6cef34a838))
+- Add merchant specific config ([#1643](https://github.com/juspay/hyperswitch-control-center/pull/1643)) ([`aac4ada`](https://github.com/juspay/hyperswitch-control-center/commit/aac4adabf17e96ef7d02fe91048fca8b668030a8))
+
+**Full Changelog:** [`2024.10.24.0...2024.10.25.0`](https://github.com/juspay/hyperswitch-control-center/compare/2024.10.24.0...2024.10.25.0)
+
+- - -
+
## 2024.10.24.0
### Testing
From d910c1e649ac7ae78f5d7cf02a20769abb5e17eb Mon Sep 17 00:00:00 2001
From: Riddhiagrawal001 <50551695+Riddhiagrawal001@users.noreply.github.com>
Date: Mon, 28 Oct 2024 10:52:42 +0530
Subject: [PATCH 8/8] fix: changes to enable card-network (#1655)
Co-authored-by: Pritish Budhiraja
---
src/screens/Order/OrderUIUtils.res | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/screens/Order/OrderUIUtils.res b/src/screens/Order/OrderUIUtils.res
index 721e36154..51bd76bb4 100644
--- a/src/screens/Order/OrderUIUtils.res
+++ b/src/screens/Order/OrderUIUtils.res
@@ -274,11 +274,7 @@ let initialFilters = (json, filtervalues) => {
let connectorFilter = filtervalues->getArrayFromDict("connector", [])->getStrArrayFromJsonArray
- // TODO: Remove the card-network delete once card-network issue is fixed
- let filterDict =
- json
- ->getDictFromJsonObject
- ->DictionaryUtils.deleteKeys(["card_network"])
+ let filterDict = json->getDictFromJsonObject
let filterArr = filterDict->itemToObjMapper
let arr = filterDict->Dict.keysToArray