import { useIsMobileViewport } from '@greenpay/hooks' import { Plaid } from '@greenpay/models' import { usePaymentDispatch, useSettingsSelect } from '@greenpay/redux' import { useEffect, useRef } from '@wordpress/element' import { dispatch } from '@wordpress/data' interface PlaidWidgetProps { showWidget: boolean onWidget: (value: boolean) => void } const PlaidWidget: React.FC = ({ showWidget, onWidget }) => { const settings = useSettingsSelect().getSettings() const { setPlaid } = usePaymentDispatch() const isMobile = useIsMobileViewport() const iframeRef = useRef(null) const backdropRef = useRef(null) const uuid = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2, 10)}` // Set up message handler once useEffect(() => { const handler = (event: MessageEvent) => { try { const data = JSON.parse(event.data) as Plaid setPlaid(data) // Set method immediately when we have success + !isTAN so checkout has it even if // the effect in Plaid.tsx doesn't run (e.g. remount/timing). const raw = data as { success?: boolean | string; isTAN?: boolean | string } const success = raw.success === true || raw.success === 'true' const isTAN = raw.isTAN === true || raw.isTAN === 'true' if (success && !isTAN) { const paymentDispatch = dispatch('green-pay/payment') as { setMethod: (m: string) => void setAccountMask: (mask: string) => void } paymentDispatch.setMethod('plaid') paymentDispatch.setAccountMask(data.mask ?? '') } onWidget(false) if (backdropRef.current && document.body.contains(backdropRef.current)) { document.body.removeChild(backdropRef.current) } if (iframeRef.current && document.body.contains(iframeRef.current)) { document.body.removeChild(iframeRef.current) } } catch (e) { console.warn('Non-Plaid message received', e) } } window.addEventListener('message', handler) return () => window.removeEventListener('message', handler) }, [setPlaid, onWidget]) // Add/remove iframe when showWidget changes useEffect(() => { if (!settings) return if (!showWidget) return const iframe = document.createElement('iframe') iframe.src = `${settings.endpoint}/Plaid/Woocommerce?client_id=${settings.clientId}&secret=${uuid}` iframe.title = 'Bank Login' iframe.style.position = 'fixed' iframe.style.border = 'none' iframe.style.zIndex = '99999' if (isMobile) { iframe.style.top = 'env(safe-area-inset-top, 0)' iframe.style.left = 'env(safe-area-inset-left, 0)' iframe.style.right = 'env(safe-area-inset-right, 0)' iframe.style.bottom = 'env(safe-area-inset-bottom, 0)' iframe.style.width = '100%' iframe.style.height = '100dvh' } else { const backdrop = document.createElement('div') backdrop.style.position = 'fixed' backdrop.style.inset = '0' backdrop.style.backgroundColor = 'rgba(0, 0, 0, 0.5)' backdrop.style.zIndex = '99998' document.body.appendChild(backdrop) backdropRef.current = backdrop iframe.style.top = '50%' iframe.style.left = '50%' iframe.style.transform = 'translate(-50%, -50%)' iframe.style.width = '100%' iframe.style.maxWidth = '480px' iframe.style.height = '80vh' iframe.style.maxHeight = '700px' iframe.style.borderRadius = '10px' iframe.style.boxShadow = '0 4px 24px rgba(0, 0, 0, 0.25)' } document.body.appendChild(iframe) iframeRef.current = iframe return () => { if (backdropRef.current && document.body.contains(backdropRef.current)) { document.body.removeChild(backdropRef.current) backdropRef.current = null } if (iframeRef.current && document.body.contains(iframeRef.current)) { document.body.removeChild(iframeRef.current) iframeRef.current = null } } }, [showWidget, isMobile, settings]) return null // no longer render an inline iframe } export default PlaidWidget