Skip to content

Commit

Permalink
feat: add Cashfree Payment Provider
Browse files Browse the repository at this point in the history
  • Loading branch information
AyushChothe authored and ansmonjol committed Oct 25, 2024
1 parent 4172bbb commit e668d9a
Show file tree
Hide file tree
Showing 16 changed files with 1,755 additions and 47 deletions.
9 changes: 9 additions & 0 deletions src/components/customers/CustomerMainInfos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
import { useInternationalization } from '~/hooks/core/useInternationalization'
import Adyen from '~/public/images/adyen.svg'
import Anrok from '~/public/images/anrok.svg'
import Cashfree from '~/public/images/cashfree.svg'
import Gocardless from '~/public/images/gocardless.svg'
import Hubspot from '~/public/images/hubspot.svg'
import Netsuite from '~/public/images/netsuite.svg'
Expand Down Expand Up @@ -126,6 +127,12 @@ gql`
code
}
... on CashfreeProvider {
id
name
code
}
... on AdyenProvider {
id
name
Expand Down Expand Up @@ -436,6 +443,8 @@ export const CustomerMainInfos = ({ loading, customer, onEdit }: CustomerMainInf
<Gocardless />
) : paymentProvider === ProviderTypeEnum?.Adyen ? (
<Adyen />
) : paymentProvider === ProviderTypeEnum?.Cashfree ? (
<Cashfree />
) : null}
</Avatar>
<Typography color="grey700">{linkedProvider?.name}</Typography>
Expand Down
9 changes: 6 additions & 3 deletions src/components/customers/addDrawer/AddCustomerDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
NetsuiteCustomer,
ProviderCustomer,
ProviderPaymentMethodsEnum,
ProviderTypeEnum,
TimezoneEnum,
UpdateCustomerInput,
XeroCustomer,
Expand Down Expand Up @@ -139,9 +140,11 @@ export const AddCustomerDrawer = forwardRef<AddCustomerDrawerRef>((_, ref) => {
return false
}

// if syncWithProvider is false, providerCustomerId is required
if (!value?.syncWithProvider && !value?.providerCustomerId) {
return false
if (from?.[1].value.paymentProvider !== ProviderTypeEnum.Cashfree) {
// if syncWithProvider is false, providerCustomerId is required
if (!value?.syncWithProvider && !value?.providerCustomerId) {
return false
}
}

return true
Expand Down
20 changes: 19 additions & 1 deletion src/components/customers/addDrawer/ExternalAppsAccordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
import { useInternationalization } from '~/hooks/core/useInternationalization'
import Adyen from '~/public/images/adyen.svg'
import Anrok from '~/public/images/anrok.svg'
import Cashfree from '~/public/images/cashfree.svg'
import GoCardless from '~/public/images/gocardless.svg'
import Hubspot from '~/public/images/hubspot.svg'
import Netsuite from '~/public/images/netsuite.svg'
Expand Down Expand Up @@ -123,6 +124,13 @@ gql`
code
}
... on CashfreeProvider {
__typename
id
name
code
}
... on AdyenProvider {
__typename
id
Expand Down Expand Up @@ -277,6 +285,14 @@ export const ExternalAppsAccordion = ({ formikProps, isEdition }: TExternalAppsA
})

const isSyncWithProviderDisabled = !!formikProps.values.providerCustomer?.syncWithProvider

const isSyncWithProviderSupported = useMemo(() => {
if (!formikProps.values.paymentProvider) return false
const unsupportedPaymentProviders: ProviderTypeEnum[] = [ProviderTypeEnum.Cashfree]

return !unsupportedPaymentProviders.includes(formikProps.values.paymentProvider)
}, [formikProps.values.paymentProvider])

const hadInitialNetsuiteIntegrationCustomer =
!!formikProps.initialValues.integrationCustomers?.find(
(i) => i.integrationType === IntegrationTypeEnum.Netsuite,
Expand Down Expand Up @@ -473,6 +489,8 @@ export const ExternalAppsAccordion = ({ formikProps, isEdition }: TExternalAppsA
<GoCardless />
) : formikProps.values.paymentProvider === ProviderTypeEnum?.Adyen ? (
<Adyen />
) : formikProps.values.paymentProvider === ProviderTypeEnum?.Cashfree ? (
<Cashfree />
) : null}
</Avatar>
) : (
Expand Down Expand Up @@ -540,7 +558,7 @@ export const ExternalAppsAccordion = ({ formikProps, isEdition }: TExternalAppsA
}}
/>

{!!formikProps.values.paymentProviderCode && (
{!!formikProps.values.paymentProviderCode && isSyncWithProviderSupported && (
<>
<TextInputField
name="providerCustomer.providerCustomerId"
Expand Down
3 changes: 3 additions & 0 deletions src/components/settings/integrations/AddAdyenDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ gql`
... on GocardlessProvider {
id
}
... on CashfreeProvider {
id
}
... on StripeProvider {
id
}
Expand Down
279 changes: 279 additions & 0 deletions src/components/settings/integrations/AddCashfreeDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
import { gql } from '@apollo/client'
import { Stack } from '@mui/material'
import { useFormik } from 'formik'
import { forwardRef, RefObject, useImperativeHandle, useRef, useState } from 'react'
import { useNavigate } from 'react-router'
import { generatePath } from 'react-router-dom'
import { object, string } from 'yup'

import { Button, Dialog, DialogRef } from '~/components/designSystem'
import { TextInputField } from '~/components/form'
import { addToast } from '~/core/apolloClient'
import { CASHFREE_INTEGRATION_DETAILS_ROUTE } from '~/core/router'
import {
AddCashfreePaymentProviderInput,
AddCashfreeProviderDialogFragment,
CashfreeIntegrationDetailsFragmentDoc,
LagoApiError,
useAddCashfreeApiKeyMutation,
useGetProviderByCodeForCashfreeLazyQuery,
useUpdateCashfreeApiKeyMutation,
} from '~/generated/graphql'
import { useInternationalization } from '~/hooks/core/useInternationalization'

import { DeleteCashfreeIntegrationDialogRef } from './DeleteCashfreeIntegrationDialog'

gql`
fragment AddCashfreeProviderDialog on CashfreeProvider {
id
name
code
clientId
clientSecret
}
query getProviderByCodeForCashfree($code: String) {
paymentProvider(code: $code) {
... on CashfreeProvider {
id
}
... on GocardlessProvider {
id
}
... on AdyenProvider {
id
}
... on StripeProvider {
id
}
}
}
mutation addCashfreeApiKey($input: AddCashfreePaymentProviderInput!) {
addCashfreePaymentProvider(input: $input) {
id
...AddCashfreeProviderDialog
...CashfreeIntegrationDetails
}
}
mutation updateCashfreeApiKey($input: UpdateCashfreePaymentProviderInput!) {
updateCashfreePaymentProvider(input: $input) {
id
...AddCashfreeProviderDialog
...CashfreeIntegrationDetails
}
}
${CashfreeIntegrationDetailsFragmentDoc}
`

type TAddCashfreeDialogProps = Partial<{
deleteModalRef: RefObject<DeleteCashfreeIntegrationDialogRef>
provider: AddCashfreeProviderDialogFragment
deleteDialogCallback: Function
}>

export interface AddCashfreeDialogRef {
openDialog: (props?: TAddCashfreeDialogProps) => unknown
closeDialog: () => unknown
}

export const AddCashfreeDialog = forwardRef<AddCashfreeDialogRef>((_, ref) => {
const navigate = useNavigate()
const dialogRef = useRef<DialogRef>(null)

const { translate } = useInternationalization()
const [localData, setLocalData] = useState<TAddCashfreeDialogProps | undefined>(undefined)
const cashfreeProvider = localData?.provider
const isEdition = !!cashfreeProvider

const [addApiKey] = useAddCashfreeApiKeyMutation({
onCompleted({ addCashfreePaymentProvider }) {
if (addCashfreePaymentProvider?.id) {
navigate(
generatePath(CASHFREE_INTEGRATION_DETAILS_ROUTE, {
integrationId: addCashfreePaymentProvider.id,
}),
)

addToast({
message: translate('text_17276219350329d36mgsotee'),
severity: 'success',
})
}
},
})

const [updateApiKey] = useUpdateCashfreeApiKeyMutation({
onCompleted({ updateCashfreePaymentProvider }) {
if (updateCashfreePaymentProvider?.id) {
navigate(
generatePath(CASHFREE_INTEGRATION_DETAILS_ROUTE, {
integrationId: updateCashfreePaymentProvider.id,
}),
)

addToast({
message: translate('text_1727621947600tg14usmdbb0'),
severity: 'success',
})
}
},
})

const [getCashfreeProviderByCode] = useGetProviderByCodeForCashfreeLazyQuery()

const formikProps = useFormik<AddCashfreePaymentProviderInput>({
initialValues: {
code: cashfreeProvider?.code || '',
name: cashfreeProvider?.name || '',
clientId: cashfreeProvider?.clientId || '',
clientSecret: cashfreeProvider?.clientSecret || '',
},
validationSchema: object().shape({
name: string(),
code: string().required(''),
clientId: string().required(''),
clientSecret: string().required(''),
}),
onSubmit: async ({ clientId, clientSecret, ...values }, formikBag) => {
const res = await getCashfreeProviderByCode({
context: { silentErrorCodes: [LagoApiError.NotFound] },
variables: {
code: values.code,
},
})
const isNotAllowedToMutate =
(!!res.data?.paymentProvider?.id && !isEdition) ||
(isEdition &&
!!res.data?.paymentProvider?.id &&
res.data?.paymentProvider?.id !== cashfreeProvider?.id)

if (isNotAllowedToMutate) {
formikBag.setFieldError('code', translate('text_632a2d437e341dcc76817556'))
return
}

if (isEdition) {
await updateApiKey({
variables: {
input: {
id: cashfreeProvider?.id || '',
...values,
},
},
})
} else {
await addApiKey({
variables: {
input: { clientId, clientSecret, ...values },
},
})
}
dialogRef.current?.closeDialog()
},
validateOnMount: true,
enableReinitialize: true,
})

useImperativeHandle(ref, () => ({
openDialog: (data) => {
setLocalData(data)
dialogRef.current?.openDialog()
},
closeDialog: () => dialogRef.current?.closeDialog(),
}))

return (
<Dialog
ref={dialogRef}
title={translate(
isEdition ? 'text_658461066530343fe1808cd9' : 'text_172450747075633492aqpbm2',
{
name: cashfreeProvider?.name,
},
)}
description={translate(
isEdition ? 'text_1724507963056bu20ky8z98g' : 'text_17245079170372xxmw737fhf',
)}
onClose={() => {
formikProps.resetForm()
}}
actions={({ closeDialog }) => (
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
width={isEdition ? '100%' : 'inherit'}
spacing={3}
>
{isEdition && (
<Button
danger
variant="quaternary"
onClick={() => {
closeDialog()
localData?.deleteModalRef?.current?.openDialog({
provider: cashfreeProvider,
callback: localData?.deleteDialogCallback,
})
}}
>
{translate('text_65845f35d7d69c3ab4793dad')}
</Button>
)}
<Stack direction="row" spacing={3} alignItems="center">
<Button variant="quaternary" onClick={closeDialog}>
{translate('text_62b1edddbf5f461ab971276d')}
</Button>
<Button
variant="primary"
disabled={!formikProps.isValid || !formikProps.dirty}
onClick={formikProps.submitForm}
>
{translate(
isEdition ? 'text_65845f35d7d69c3ab4793dac' : 'text_172450747075633492aqpbm2',
)}
</Button>
</Stack>
</Stack>
)}
>
<div className="mb-8 flex flex-col gap-6">
<div className="flex flex-row items-start gap-6 *:flex-1">
<TextInputField
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
formikProps={formikProps}
name="name"
label={translate('text_6584550dc4cec7adf861504d')}
placeholder={translate('text_6584550dc4cec7adf861504f')}
/>
<TextInputField
formikProps={formikProps}
name="code"
label={translate('text_6584550dc4cec7adf8615051')}
placeholder={translate('text_6584550dc4cec7adf8615053')}
/>
</div>
<TextInputField
formikProps={formikProps}
disabled={isEdition}
name="clientId"
label={translate('text_1727620558031ftsky1vpr55')}
placeholder={translate('text_1727624537843s2ublm4rsyj')}
/>
<TextInputField
formikProps={formikProps}
disabled={isEdition}
name="clientSecret"
label={translate('text_1727620574228qfyoqtsdih7')}
placeholder={translate('text_17276245391922l9540z7f78')}
/>
</div>
</Dialog>
)
})

AddCashfreeDialog.displayName = 'AddCashfreeDialog'
Loading

0 comments on commit e668d9a

Please sign in to comment.