import { BankAccountFundingSource, CheckoutBankAccountRefreshFundingSourceInteractionData, FundingSource, FundingSourceType, RefreshFundingSourceCheckoutBankAccountRefreshDataInput, } from '@sudoplatform/sudo-virtual-cards' import { Button, Form, HSpace, VSpace } from '@sudoplatform/web-ui' import { Checkbox, Collapse } from 'antd' import CollapsePanel from 'antd/lib/collapse/CollapsePanel' import React, { useCallback, useEffect, useRef, useState } from 'react' import { PlaidLinkError, PlaidLinkOnExitMetadata, PlaidLinkOnSuccessMetadata, PlaidLinkOptions, usePlaidLink, } from 'react-plaid-link' import { useAsyncFn } from 'react-use' import { RefreshFundingSourceRefreshDataInput } from '@sudoplatform/sudo-virtual-cards/types/public/sudoVirtualCardsClient' export interface Props { onRefreshFundingSource: ( fundingSourceId: string, refreshData: RefreshFundingSourceRefreshDataInput, ) => Promise refreshInteractionData?: CheckoutBankAccountRefreshFundingSourceInteractionData fundingSource: BankAccountFundingSource } export const RefreshCheckoutBankAccountFundingSourceForm: React.FC = ({ onRefreshFundingSource, refreshInteractionData, fundingSource, }: Props) => { const refreshInProgress = useRef(false) const [agreementAccepted, setAgreementAccepted] = useState(false) const [linkSuccess, setLinkSuccess] = useState<{ public_token: string metadata: PlaidLinkOnSuccessMetadata } | null>(null) const [linkError, setLinkError] = useState<{ err: PlaidLinkError | null metadata: PlaidLinkOnExitMetadata } | null>(null) /* eslint-disable react-hooks/exhaustive-deps */ const config: PlaidLinkOptions = { onEvent: (eventName) => { console.log(`Plaid event: ${eventName}`) }, onSuccess: (public_token, metadata) => { setLinkSuccess({ public_token, metadata }) }, onExit: (err, metadata) => { setLinkError({ err, metadata }) }, token: refreshInteractionData?.linkToken ?? '', } const plaid = usePlaidLink(config) useEffect(() => { config.token = refreshInteractionData?.linkToken ?? null }, [config, refreshInteractionData]) useEffect(() => { if (refreshInProgress.current) { return } async function beginRefreshFundingSource() { const refreshData: RefreshFundingSourceRefreshDataInput = { provider: 'checkout', type: FundingSourceType.BankAccount, applicationName: 'webApplication', } console.log('Beginning to refresh funding source') await onRefreshFundingSource(fundingSource.id, refreshData) } void beginRefreshFundingSource() }, [fundingSource, onRefreshFundingSource]) const resetForm = useCallback(() => { setLinkError(null) setLinkSuccess(null) refreshInProgress.current = false setAgreementAccepted(false) plaid.exit() }, [plaid]) useEffect(() => { async function plaidLinkFailureHandler() { if (!linkError) { return } console.error('Plaid link error', linkError) resetForm() } void plaidLinkFailureHandler() }, [resetForm, linkError]) const [, refreshFundingSource] = useAsyncFn(async () => { console.log('Checking for success', { refreshInProgress, linkSuccess, provisionalFundingSource: fundingSource, }) if (!linkSuccess) { console.log('Waiting for Plaid Link to complete ....') return } if (!agreementAccepted) { console.log('Waiting for agreement to be accepted ....') return } if (refreshInProgress.current) { console.log('Waiting for funding source set up to complete ...') return } if (!fundingSource || !refreshInteractionData) { throw new Error('Funding source not set up') } refreshInProgress.current = true if (!linkSuccess.metadata.institution) { console.error('No institution returned on link success') throw Error('No institution returned on link success') } console.log('Plaid link success', linkSuccess) const refreshData: RefreshFundingSourceCheckoutBankAccountRefreshDataInput = { provider: 'checkout', type: FundingSourceType.BankAccount, accountId: linkSuccess.metadata.accounts[0].id, authorizationText: refreshInteractionData.authorizationText[0], } try { await onRefreshFundingSource(fundingSource.id, refreshData) } finally { resetForm() } }, [onRefreshFundingSource, linkSuccess, agreementAccepted, resetForm]) function doPlaidLinkOpen(): void { plaid.open() } let authorizationTextHtml = refreshInteractionData?.authorizationText?.find( (a) => a.contentType === 'text/html', )?.content if (!authorizationTextHtml) { const authorizationTextPlain = refreshInteractionData?.authorizationText?.find( (a) => a.contentType === 'text/plain', )?.content if (refreshInteractionData && !authorizationTextPlain) { throw new Error('No authorization text in refresh data') } authorizationTextHtml = `

${authorizationTextPlain ?? ''}

` } return (

Institution: {linkSuccess?.metadata?.institution?.name}

Account type: {linkSuccess?.metadata?.accounts[0].subtype}

Account name: {linkSuccess?.metadata?.accounts[0].name}

Account number ending: **** {linkSuccess?.metadata?.accounts[0].mask}

setAgreementAccepted(e.target.checked)} /> By checking this box you agree to the terms of the ACH Authorization Agreement above. ) }