import { useIsMobileViewport } from '@greenpay/hooks' import { usePaymentDispatch, useSettingsSelect, useValidationDispatch } from '@greenpay/redux' import { appendRibbitSessionBillingToFormData, checkRibbitBankTan, getCheckoutBillingDetails, validateRibbitSessionBilling, } from '@greenpay/utils' import { useEffect, useRef, useState } from '@wordpress/element' import { RIBBITConnect } from 'react-ribbit-connect' import { parseConnectEventData } from './parseConnectEventData' interface RibbitWidgetProps { showWidget: boolean onWidget: (value: boolean) => void onError: (message: string) => void onBusyChange?: (busy: boolean) => void } const RibbitWidget: React.FC = ({ showWidget, onWidget, onError, onBusyChange }) => { const settings = useSettingsSelect().getSettings() const { setRibbit, setMethod, setAccountMask } = usePaymentDispatch() const { setPlaidError, setPlaidTouched } = useValidationDispatch() const isMobile = useIsMobileViewport() const [sessionToken, setSessionToken] = useState(null) const [loading, setLoading] = useState(false) const connectDetailsRef = useRef>({ customerToken: '', accountToken: '', institutionName: '', accountMask: '', }) useEffect(() => { if (!showWidget) { connectDetailsRef.current = { customerToken: '', accountToken: '', institutionName: '', accountMask: '', } } }, [showWidget]) useEffect(() => { const ajaxUrl = settings?.ribbitSessionAjaxUrl if (!showWidget || !ajaxUrl) return let cancelled = false const fetchSession = async () => { setLoading(true) setSessionToken(null) const billing = getCheckoutBillingDetails() const validation = validateRibbitSessionBilling(billing) if (!validation.valid) { if (!cancelled) { onError(validation.message) onWidget(false) setLoading(false) } return } try { const formData = new FormData() formData.append('action', 'get_ribbit_connect_session') formData.append('_ajax_nonce', settings.ribbitSessionNonce ?? '') appendRibbitSessionBillingToFormData(formData, billing) const response = await fetch(ajaxUrl, { method: 'POST', body: formData, credentials: 'same-origin', }) const json = (await response.json()) as { success: boolean data?: { sessionToken?: string; message?: string; code?: string } } if (cancelled) return if (!json.success || !json.data?.sessionToken) { const message = json.data?.message ?? 'Bank Login failed. Please refresh the page and try again!' onError(message) onWidget(false) return } setSessionToken(json.data.sessionToken) } catch { if (!cancelled) { onError('Bank Login failed. Please refresh the page and try again!') onWidget(false) } } finally { if (!cancelled) { setLoading(false) } } } fetchSession() return () => { cancelled = true } }, [showWidget, settings, onError, onWidget]) const mergeConnectDetails = (eventData: unknown) => { const parsed = parseConnectEventData(eventData) const current = connectDetailsRef.current connectDetailsRef.current = { customerToken: parsed.customerToken || current.customerToken, accountToken: parsed.accountToken || current.accountToken, institutionName: parsed.institutionName || current.institutionName, accountMask: parsed.accountMask || current.accountMask, bankId: parsed.bankId ?? current.bankId, } return connectDetailsRef.current } const handleConnectMessage = (_functionName: string, message: unknown) => { mergeConnectDetails(message) } const handleComplete = async (eventData: unknown) => { const tokens = mergeConnectDetails(eventData) if (!tokens.accountToken) { onError('Bank login was unsuccessful, please try again.') onWidget(false) return } onWidget(false) onBusyChange?.(true) const institutionName = tokens.institutionName || 'Bank Account' try { const tanResult = await checkRibbitBankTan(tokens.bankId, institutionName, settings) setRibbit({ customerToken: tokens.customerToken || settings?.clientId || '', accountToken: tokens.accountToken, institutionName, accountMask: tokens.accountMask, success: true, isTAN: tanResult.isTAN, helpLink: tanResult.helpLink, bankId: tokens.bankId, }) if (!tanResult.isTAN) { setAccountMask(tokens.accountMask) setMethod('ribbit') } setPlaidError('') setPlaidTouched(false) } catch { onError('Bank login was unsuccessful, please try again.') } finally { onBusyChange?.(false) } } const handleExit = () => { onWidget(false) } const handleManualEnrollment = () => { onError('Manual bank entry is not supported for bank login. Use Manual Debit Entry or try another bank.') onWidget(false) } if (!showWidget || loading || !sessionToken) { return null } return ( ) } export default RibbitWidget