Skip to content

Commit

Permalink
feat: add click to pay in payment settings (#1927)
Browse files Browse the repository at this point in the history
  • Loading branch information
PritishBudhiraja authored Dec 18, 2024
1 parent e5bae80 commit db89acf
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 15 deletions.
1 change: 1 addition & 0 deletions config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ down_time=false
tax_processor=true
x_feature_route=false
tenant_user=false
dev_click_to_pay=false
[default.merchant_config]
[default.merchant_config.new_analytics]
org_ids=[]
Expand Down
1 change: 1 addition & 0 deletions cypress/e2e/auth/auth.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ describe("Auth Module", () => {
payout: true,
recon: false,
test_processors: true,
dev_click_to_pay: false,
feedback: false,
mixpanel: false,
generate_report: false,
Expand Down
7 changes: 0 additions & 7 deletions src/container/BusinessProfileContainer.res
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,6 @@ let make = () => {
<AccessControl authorization=Access>
<BusinessProfile />
</AccessControl>
| list{"payment-settings", ...remainingPath} =>
<EntityScaffold
entityName="PaymentSettings"
remainingPath
renderList={() => <PaymentSettingsList />}
renderShow={(_, _) => <PaymentSettings webhookOnly=false showFormOnly=false />}
/>
| list{"unauthorized"} => <UnauthorizedPage />
| _ => <NotFoundPage />
}}
Expand Down
7 changes: 7 additions & 0 deletions src/container/ConnectorContainer.res
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ let make = () => {
renderShow={(routingType, _) => <PayoutRoutingConfigure routingType />}
/>
</AccessControl>
| list{"payment-settings", ...remainingPath} =>
<EntityScaffold
entityName="PaymentSettings"
remainingPath
renderList={() => <PaymentSettingsList />}
renderShow={(_, _) => <PaymentSettings webhookOnly=false showFormOnly=false />}
/>
| list{"unauthorized"} => <UnauthorizedPage />
| _ => <NotFoundPage />
}}
Expand Down
6 changes: 4 additions & 2 deletions src/entryPoints/FeatureFlagUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ type featureFlag = {
taxProcessor: bool,
xFeatureRoute: bool,
tenantUser: bool,
clickToPay: bool,
}

let featureFlagType = (featureFlags: JSON.t) => {
open LogicUtils
let dict = featureFlags->getDictFromJsonObject->getDictfromDict("features")
let typedFeatureFlag: featureFlag = {

{
default: dict->getBool("default", true),
testLiveToggle: dict->getBool("test_live_toggle", false),
email: dict->getBool("email", false),
Expand All @@ -60,6 +62,7 @@ let featureFlagType = (featureFlags: JSON.t) => {
payOut: dict->getBool("payout", false),
recon: dict->getBool("recon", false),
testProcessors: dict->getBool("test_processors", false),
clickToPay: dict->getBool("dev_click_to_pay", false),
feedback: dict->getBool("feedback", false),
generateReport: dict->getBool("generate_report", false),
mixpanel: dict->getBool("mixpanel", false),
Expand Down Expand Up @@ -88,7 +91,6 @@ let featureFlagType = (featureFlags: JSON.t) => {
xFeatureRoute: dict->getBool("x_feature_route", false),
tenantUser: dict->getBool("tenant_user", false),
}
typedFeatureFlag
}

let configMapper = dict => {
Expand Down
6 changes: 3 additions & 3 deletions src/entryPoints/HyperSwitchApp.res
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,11 @@ let make = () => {
| list{"fraud-risk-management", ..._}
| list{"configure-pmts", ..._}
| list{"routing", ..._}
| list{"payoutrouting", ..._} =>
| list{"payoutrouting", ..._}
| list{"payment-settings", ..._} =>
<ConnectorContainer />
| list{"business-details", ..._}
| list{"business-profiles", ..._}
| list{"payment-settings", ..._} =>
| list{"business-profiles", ..._} =>
<BusinessProfileContainer />
| list{"payments", ..._}
| list{"refunds", ..._}
Expand Down
60 changes: 59 additions & 1 deletion src/screens/Developer/PaymentSettings/PaymentSettings.res
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,63 @@ module AutoRetries = {
}
}

module ClickToPaySection = {
@react.component
let make = () => {
open FormRenderer
open LogicUtils

let {userHasAccess} = GroupACLHooks.useUserGroupACLHook()
let formState: ReactFinalForm.formState = ReactFinalForm.useFormState(
ReactFinalForm.useFormSubscription(["values"])->Nullable.make,
)
let connectorListAtom = HyperswitchAtom.connectorListAtom->Recoil.useRecoilValueFromAtom
let featureFlagDetails = HyperswitchAtom.featureFlagAtom->Recoil.useRecoilValueFromAtom
let connectorView = userHasAccess(~groupAccess=ConnectorsView) === Access
let isClickToPayEnabled =
formState.values->getDictFromJsonObject->getBool("is_click_to_pay_enabled", false)
let dropDownOptions = connectorListAtom->Array.map((item): SelectBox.dropdownOption => {
{
label: `${item.connector_name} - ${item.merchant_connector_id}`,
value: item.merchant_connector_id,
}
})

<RenderIf condition={featureFlagDetails.clickToPay && connectorView}>
<DesktopRow>
<FieldRenderer
labelClass="!text-base !text-grey-700 font-semibold"
fieldWrapperClass="max-w-xl"
field={makeFieldInfo(
~name="is_click_to_pay_enabled",
~label="Click to Pay",
~customInput=InputFields.boolInput(~isDisabled=false, ~boolCustomClass="rounded-lg"),
~description="Click to Pay is a secure, seamless digital payment solution that lets customers checkout quickly using saved cards without entering details",
~toolTipPosition=Right,
)}
/>
</DesktopRow>
<RenderIf condition={isClickToPayEnabled}>
<DesktopRow>
<FormRenderer.FieldRenderer
labelClass="!text-base !text-grey-700 font-semibold"
field={FormRenderer.makeFieldInfo(
~label="Click to Pay - Connector ID",
~name="authentication_product_ids.click_to_pay",
~placeholder="",
~customInput=InputFields.selectInput(
~options=dropDownOptions,
~buttonText="Select Click to Pay - Connector ID",
~deselectDisable=true,
),
)}
/>
</DesktopRow>
</RenderIf>
</RenderIf>
}
}

@react.component
let make = (~webhookOnly=false, ~showFormOnly=false, ~profileId="") => {
open DeveloperUtils
Expand Down Expand Up @@ -623,6 +680,7 @@ let make = (~webhookOnly=false, ~showFormOnly=false, ~profileId="") => {
</DesktopRow>
</RenderIf>
<AutoRetries setCheckMaxAutoRetry />
<ClickToPaySection />
<ReturnUrl />
<WebHook />
<DesktopRow>
Expand All @@ -643,7 +701,7 @@ let make = (~webhookOnly=false, ~showFormOnly=false, ~profileId="") => {
/>
</div>
</DesktopRow>
// <FormValuesSpy />
<FormValuesSpy />
</form>
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ let itemToObjMapper = dict => {
is_connector_agnostic_mit_enabled: None,
is_auto_retries_enabled: dict->getOptionBool("is_auto_retries_enabled"),
max_auto_retries_enabled: dict->getOptionInt("max_auto_retries_enabled"),
is_click_to_pay_enabled: dict->getOptionBool("is_click_to_pay_enabled"),
authentication_product_ids: Some(
dict
->getDictfromDict("authentication_product_ids")
->JSON.Encode.object,
),
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ let itemToObjMapper = dict => {
is_connector_agnostic_mit_enabled: None,
is_auto_retries_enabled: dict->getOptionBool("is_auto_retries_enabled"),
max_auto_retries_enabled: dict->getOptionInt("max_auto_retries_enabled"),
is_click_to_pay_enabled: dict->getOptionBool("is_click_to_pay_enabled"),
authentication_product_ids: Some(
dict
->getDictfromDict("authentication_product_ids")
->JSON.Encode.object,
),
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/screens/Settings/HSwitchSettingTypes.res
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ type profileEntity = {
collect_billing_details_from_wallet_connector: option<bool>,
always_collect_billing_details_from_wallet_connector: option<bool>,
is_connector_agnostic_mit_enabled: option<bool>,
is_click_to_pay_enabled: option<bool>,
authentication_product_ids: option<JSON.t>,
outgoing_webhook_custom_http_headers: option<Dict.t<JSON.t>>,
is_auto_retries_enabled: option<bool>,
max_auto_retries_enabled: option<int>,
Expand Down
17 changes: 17 additions & 0 deletions src/screens/Settings/MerchantAccountUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ let parseBussinessProfileJson = (profileRecord: profileEntity) => {
always_collect_shipping_details_from_wallet_connector,
is_auto_retries_enabled,
max_auto_retries_enabled,
is_click_to_pay_enabled,
authentication_product_ids,
} = profileRecord

let profileInfo =
Expand Down Expand Up @@ -67,6 +69,9 @@ let parseBussinessProfileJson = (profileRecord: profileEntity) => {
authentication_connector_details.three_ds_requestor_url,
)
profileInfo->setOptionBool("is_connector_agnostic_mit_enabled", is_connector_agnostic_mit_enabled)
profileInfo->setOptionBool("is_click_to_pay_enabled", is_click_to_pay_enabled)
profileInfo->setOptionJson("authentication_product_ids", authentication_product_ids)

profileInfo->setOptionDict(
"outgoing_webhook_custom_http_headers",
outgoing_webhook_custom_http_headers,
Expand Down Expand Up @@ -239,6 +244,16 @@ let getBusinessProfilePayload = (values: JSON.t) => {
!(authenticationConnectorDetails->isEmptyDict) ? Some(authenticationConnectorDetails) : None,
)

profileDetailsDict->setOptionBool(
"is_click_to_pay_enabled",
valuesDict->getOptionBool("is_click_to_pay_enabled"),
)

let authenticationProductIds = valuesDict->getJsonObjectFromDict("authentication_product_ids")
if !(authenticationProductIds->getDictFromJsonObject->isEmptyDict) {
profileDetailsDict->Dict.set("authentication_product_ids", authenticationProductIds)
}

profileDetailsDict
}

Expand Down Expand Up @@ -505,6 +520,8 @@ let defaultValueForBusinessProfile = {
is_connector_agnostic_mit_enabled: None,
is_auto_retries_enabled: None,
max_auto_retries_enabled: None,
is_click_to_pay_enabled: None,
authentication_product_ids: None,
}

let getValueFromBusinessProfile = businessProfileValue => {
Expand Down
3 changes: 3 additions & 0 deletions src/utils/LogicUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ let setDictNull = (dict, key, optionStr) => {
let setOptionString = (dict, key, optionStr) =>
optionStr->Option.mapOr((), str => dict->Dict.set(key, str->JSON.Encode.string))

let setOptionJson = (dict, key, optionJson) =>
optionJson->Option.mapOr((), json => dict->Dict.set(key, json))

let setOptionBool = (dict, key, optionInt) =>
optionInt->Option.mapOr((), bool => dict->Dict.set(key, bool->JSON.Encode.bool))

Expand Down
10 changes: 8 additions & 2 deletions src/utils/Mappers/BusinessProfileMapper.res
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ let businessProfileTypeMapper = values => {
let webhookDetailsDict = jsonDict->getDictfromDict("webhook_details")
let authenticationConnectorDetails = jsonDict->getDictfromDict("authentication_connector_details")
let outgoingWebhookHeades = jsonDict->getDictfromDict("outgoing_webhook_custom_http_headers")
let businessProfile = {

{
merchant_id: jsonDict->getString("merchant_id", ""),
profile_id: jsonDict->getString("profile_id", ""),
profile_name: jsonDict->getString("profile_name", ""),
Expand All @@ -56,8 +57,13 @@ let businessProfileTypeMapper = values => {
: None,
is_auto_retries_enabled: jsonDict->getOptionBool("is_auto_retries_enabled"),
max_auto_retries_enabled: jsonDict->getOptionInt("max_auto_retries_enabled"),
is_click_to_pay_enabled: jsonDict->getOptionBool("is_click_to_pay_enabled"),
authentication_product_ids: Some(
jsonDict
->getDictfromDict("authentication_product_ids")
->JSON.Encode.object,
),
}
businessProfile
}

let convertObjectToType = value => {
Expand Down

0 comments on commit db89acf

Please sign in to comment.