Skip to content

Commit

Permalink
feat: add support for outgoing webhook custom http headers (#1001)
Browse files Browse the repository at this point in the history
Co-authored-by: Riddhiagrawal001 <[email protected]>
  • Loading branch information
JeevaRamu0104 and Riddhiagrawal001 authored Jul 18, 2024
1 parent 3c8ef45 commit f693f41
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 64 deletions.
298 changes: 246 additions & 52 deletions src/screens/Developer/PaymentSettings/PaymentSettings.res
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,220 @@ module InfoViewForWebhooks = {
}
}

module AuthenticationInput = {
@react.component
let make = (~removeAuthHeaders, ~authHeaders, ~index) => {
open LogicUtils
open FormRenderer
let formState: ReactFinalForm.formState = ReactFinalForm.useFormState(
ReactFinalForm.useFormSubscription(["values"])->Nullable.make,
)
let outGoingWebhookRef = React.useRef(formState.values)

React.useEffect1(() => {
// Update the ref whenever formState.values changes
outGoingWebhookRef.current = formState.values
None
}, [formState.values])

let outGoingWebhookDict =
formState.values
->getDictFromJsonObject
->getDictfromDict("outgoing_webhook_custom_http_headers")

let outGoingWebhookKey =
outGoingWebhookDict->Dict.keysToArray->Array.at(index)->Option.getOr("")
let outGoingWebHookValue = outGoingWebhookDict->getString(outGoingWebhookKey, "")
let (key, setKey) = React.useState(_ => outGoingWebhookKey)
let (metaValue, setValue) = React.useState(_ => outGoingWebHookValue)
let form = ReactFinalForm.useForm()
let name = `outgoing_webhook_custom_http_headers.${key}`
let keyInput: ReactFinalForm.fieldRenderPropsInput = {
name: "string",
onBlur: _ => {
if key->String.length > 0 {
form.change(name, metaValue->JSON.Encode.string)
}
},
onChange: ev => {
let value = ReactEvent.Form.target(ev)["value"]
setKey(_ => value)
},
onFocus: _ => {
if key->String.length > 0 {
form.change(name, JSON.Encode.null)
}
},
value: key->JSON.Encode.string,
checked: true,
}

let valueInput: ReactFinalForm.fieldRenderPropsInput = {
name: "string",
onBlur: _ => {
form.change(name, metaValue->JSON.Encode.string)
},
onChange: ev => {
let value = ReactEvent.Form.target(ev)["value"]
setValue(_ => value)
},
onFocus: _ => (),
value: metaValue->JSON.Encode.string,
checked: true,
}

<div className="flex gap-4">
<DesktopRow wrapperClass="flex-1">
<div className="mt-5">
<TextInput input={keyInput} placeholder={"Enter key"} />
</div>
<div className="mt-5">
<TextInput input={valueInput} placeholder={"Enter value"} />
</div>
</DesktopRow>
<UIUtils.RenderIf condition={authHeaders->Array.length > 1}>
<div className="mt-6 flex gap-4">
<ModalCloseIcon onClick={_ev => removeAuthHeaders(index, key)} />
</div>
</UIUtils.RenderIf>
</div>
}
}
module WebHookAuthenticationHeaders = {
@react.component
let make = () => {
open LogicUtils
let form = ReactFinalForm.useForm()
let formState: ReactFinalForm.formState = ReactFinalForm.useFormState(
ReactFinalForm.useFormSubscription(["values"])->Nullable.make,
)
let outGoingWebhookArr =
formState.values
->getDictFromJsonObject
->getDictfromDict("outgoing_webhook_custom_http_headers")
->Dict.keysToArray
->Array.mapWithIndex((_, index) => index)

let (authHeaders, setAuthHeaders) = React.useState(_ =>
outGoingWebhookArr->Array.length > 0 ? outGoingWebhookArr : [0]
)
let formValuesRef = React.useRef(formState.values)

React.useEffect1(() => {
// Update the ref whenever formState.values changes
formValuesRef.current = formState.values
None
}, [formState.values])

let removeAuthHeaders = (removeIndex, name) => {
let outGoingWebhookDict =
formValuesRef.current
->getDictFromJsonObject
->getDictfromDict("outgoing_webhook_custom_http_headers")
->Dict.copy
outGoingWebhookDict->Dict.set(name, JSON.Encode.null)
form.change(
`outgoing_webhook_custom_http_headers`,
outGoingWebhookDict->Identity.genericTypeToJson,
)
setAuthHeaders(prevAuthHeaders =>
prevAuthHeaders->Array.filterWithIndex((_, index) => index != removeIndex)
)
}

let addAuthHeaders = () => {
if authHeaders->Array.length < 4 {
let update = authHeaders->Array.concat([authHeaders->Array.length])
setAuthHeaders(_ => update)
}
}

<div className="flex-1">
<p
className={`ml-4 text-fs-13 text-jp-gray-900 dark:text-jp-gray-text_darktheme dark:text-opacity-50 ml-1 !text-base !text-grey-700 font-semibold ml-1`}>
{"Authorization"->React.string}
</p>
{authHeaders
->Array.mapWithIndex((_, index) => {
<div key={index->Int.toString} className="grid grid-cols-5 flex gap-4">
<div className=" col-span-4">
<AuthenticationInput removeAuthHeaders authHeaders index />
</div>
<UIUtils.RenderIf condition={index === authHeaders->Array.length - 1 && index != 3}>
<div className="flex justify-start items-center mt-4">
<Icon
name="plus-circle"
size=27
customIconColor="text-gray-400"
className="flex items-center justify-center w-fit h-fit"
onClick={_ => addAuthHeaders()}
/>
</div>
</UIUtils.RenderIf>
</div>
})
->React.array}
</div>
}
}

module WebHook = {
@react.component
let make = () => {
let h2RegularTextStyle = `${HSwitchUtils.getTextClass((H3, Leading_1))}`

<>
<div>
<div className="ml-4">
<p className=h2RegularTextStyle> {"Webhook Setup"->React.string} </p>
</div>
<div className="ml-4 mt-4">
<FormRenderer.FieldRenderer
field={DeveloperUtils.webhookUrl}
labelClass="!text-base !text-grey-700 font-semibold"
fieldWrapperClass="max-w-xl"
/>
</div>
</div>
<WebHookAuthenticationHeaders />
</>
}
}

module ReturnUrl = {
@react.component
let make = () => {
open FormRenderer
<>
<DesktopRow>
<FieldRenderer
field={DeveloperUtils.returnUrl}
errorClass={HSwitchUtils.errorClass}
labelClass="!text-base !text-grey-700 font-semibold"
fieldWrapperClass="max-w-xl"
/>
</DesktopRow>
</>
}
}

@react.component
let make = (~webhookOnly=false, ~showFormOnly=false, ~profileId="") => {
open DeveloperUtils
open APIUtils
open HSwitchUtils
open MerchantAccountUtils
open HSwitchSettingTypes
open FormRenderer
let getURL = useGetURL()
let url = RescriptReactRouter.useUrl()
let id = HSwitchUtils.getConnectorIDFromUrl(url.path->List.toArray, profileId)
let businessProfileDetails = BusinessProfileHook.useGetBusinessProflile(id)
let featureFlagDetails = HyperswitchAtom.featureFlagAtom->Recoil.useRecoilValueFromAtom
let showToast = ToastState.useShowToast()
let updateDetails = useUpdateMethod()
let (isDisabled, setIsDisabled) = React.useState(_ => false)
let (profileInfo, setProfileInfo) = React.useState(() => businessProfileDetails)

let (busiProfieDetails, setBusiProfie) = React.useState(_ => businessProfileDetails)

let (screenState, setScreenState) = React.useState(_ => PageLoaderWrapper.Success)
let bgClass = webhookOnly ? "" : "bg-white dark:bg-jp-gray-lightgray_background"
Expand Down Expand Up @@ -69,8 +267,7 @@ let make = (~webhookOnly=false, ~showFormOnly=false, ~profileId="") => {
let url = getURL(~entityName=BUSINESS_PROFILE, ~methodType=Post, ~id=Some(id), ())
let body = values->getBusinessProfilePayload->JSON.Encode.object
let res = await updateDetails(url, body, Post, ())
let profileTypeInfo = res->BusinessProfileMapper.businessProfileTypeMapper
setProfileInfo(_ => profileTypeInfo)
setBusiProfie(_ => res->BusinessProfileMapper.businessProfileTypeMapper)
showToast(~message=`Details updated`, ~toastType=ToastState.ToastSuccess, ())
setScreenState(_ => PageLoaderWrapper.Success)
fetchBusinessProfiles()->ignore
Expand All @@ -93,7 +290,7 @@ let make = (~webhookOnly=false, ~showFormOnly=false, ~profileId="") => {
link: "/payment-settings",
},
]
currentPageTitle={businessProfileDetails.profile_name}
currentPageTitle={busiProfieDetails.profile_name}
cursorStyle="cursor-pointer"
/>
</UIUtils.RenderIf>
Expand All @@ -104,14 +301,12 @@ let make = (~webhookOnly=false, ~showFormOnly=false, ~profileId="") => {
: "border border-jp-gray-500 rounded-md dark:border-jp-gray-960"} ${bgClass} `}>
<ReactFinalForm.Form
key="merchantAccount"
initialValues={businessProfileDetails->parseBussinessProfileJson->JSON.Encode.object}
initialValues={busiProfieDetails->parseBussinessProfileJson->JSON.Encode.object}
subscription=ReactFinalForm.subscribeToValues
validate={values => {
MerchantAccountUtils.validateMerchantAccountForm(
~values,
~setIsDisabled=Some(setIsDisabled),
~fieldsToValidate={fieldsToValidate()},
~initialData=profileInfo->parseBussinessProfileJson->JSON.Encode.object,
~isLiveMode=featureFlagDetails.isLiveMode,
)
}}
Expand All @@ -124,83 +319,82 @@ let make = (~webhookOnly=false, ~showFormOnly=false, ~profileId="") => {
: "px-2 py-4"} flex flex-col gap-7 overflow-hidden`}>
<div className="flex items-center">
<InfoViewForWebhooks
heading="Profile ID" subHeading=businessProfileDetails.profile_id isCopy=true
heading="Profile ID" subHeading=busiProfieDetails.profile_id isCopy=true
/>
<InfoViewForWebhooks
heading="Profile Name" subHeading=businessProfileDetails.profile_name
heading="Profile Name" subHeading=busiProfieDetails.profile_name
/>
</div>
<div className="flex items-center">
<InfoViewForWebhooks
heading="Merchant ID" subHeading={businessProfileDetails.merchant_id}
heading="Merchant ID" subHeading={busiProfieDetails.merchant_id}
/>
<InfoViewForWebhooks
heading="Payment Response Hash Key"
subHeading={businessProfileDetails.payment_response_hash_key->Option.getOr(
"NA",
)}
subHeading={busiProfieDetails.payment_response_hash_key->Option.getOr("NA")}
isCopy=true
/>
</div>
<FormRenderer.DesktopRow>
{[webhookUrl, returnUrl]
->Array.filter(urlField => urlField.label === "Webhook URL" || !webhookOnly)
->Array.mapWithIndex((field, index) =>
<FormRenderer.FieldRenderer
key={index->Int.toString}
field
errorClass
labelClass="!text-base !text-grey-700 font-semibold"
fieldWrapperClass="max-w-xl"
/>
)
->React.array}
</FormRenderer.DesktopRow>
<DesktopRow>
<FieldRenderer
labelClass="!text-base !text-grey-700 font-semibold"
fieldWrapperClass="max-w-xl"
field={makeFieldInfo(
~name="collect_shipping_details_from_wallet_connector",
~label="Collect Shipping Details",
~customInput=InputFields.boolInput(
~isDisabled=false,
~boolCustomClass="rounded-lg",
(),
),
(),
)}
/>
<FieldRenderer
labelClass="!text-base !text-grey-700 font-semibold"
fieldWrapperClass="max-w-xl"
field={makeFieldInfo(
~name="is_connector_agnostic_mit_enabled",
~label="Connector Agnostic",
~customInput=InputFields.boolInput(
~isDisabled=false,
~boolCustomClass="rounded-lg",
(),
),
(),
)}
/>
</DesktopRow>
<UIUtils.RenderIf condition={isBusinessProfileHasThreeds}>
<FormRenderer.DesktopRow>
<FormRenderer.FieldRenderer
<DesktopRow>
<FieldRenderer
field={threedsConnectorList
->Array.map(item => item.connector_name)
->authenticationConnectors}
errorClass
labelClass="!text-base !text-grey-700 font-semibold"
fieldWrapperClass="max-w-xl"
/>
<FormRenderer.FieldRenderer
<FieldRenderer
field={threeDsRequestorUrl}
errorClass
labelClass="!text-base !text-grey-700 font-semibold"
fieldWrapperClass="max-w-xl"
/>
</FormRenderer.DesktopRow>
</DesktopRow>
</UIUtils.RenderIf>
<FormRenderer.DesktopRow>
<FormRenderer.FieldRenderer
labelClass="!text-base !text-grey-700 font-semibold"
fieldWrapperClass="max-w-xl"
field={FormRenderer.makeFieldInfo(
~name="collect_shipping_details_from_wallet_connector",
~label="Collect Shipping Details",
~customInput=InputFields.boolInput(
~isDisabled=false,
~boolCustomClass="rounded-lg",
(),
),
(),
)}
/>
</FormRenderer.DesktopRow>
<FormRenderer.DesktopRow>
<ReturnUrl />
<WebHook />
<DesktopRow>
<div className="flex justify-start w-full">
<FormRenderer.SubmitButton
<SubmitButton
customSumbitButtonStyle="justify-start"
text="Update"
buttonType=Button.Primary
disabledParamter=isDisabled
buttonSize=Button.Small
/>
</div>
</FormRenderer.DesktopRow>
</DesktopRow>
<FormValuesSpy />
</form>
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ let itemToObjMapper = dict => {
dict,
"collect_shipping_details_from_wallet_connector",
),
outgoing_webhook_custom_http_headers: None,
is_connector_agnostic_mit_enabled: None,
}
}

Expand Down
Loading

0 comments on commit f693f41

Please sign in to comment.