import {ErrorMessage} from '@hookform/error-message'; import type {GatewayFieldProps, GatewayOptionProps} from '@givewp/forms/propTypes'; import {ErrorBoundary} from 'react-error-boundary'; import {__, sprintf} from '@wordpress/i18n'; import {createInterpolateElement, useEffect, useMemo} from '@wordpress/element'; import cx from 'classnames'; import {isDonationTypeSubscription} from '@givewp/forms/types'; interface EmptyMessageProps { message: string; } interface GatewayMissingMessageProps { donationAmountMinimumNotReached?: boolean; currencyNotSupported?: boolean; subscriptionNotSupported?: boolean; } interface GatewayFieldsErrorFallbackProps { error: Error; resetErrorBoundary: () => void; } /** * Empty message component displayed when no payment gateways are available. * * @since 3.20.0 */ function EmptyMessage({message}: EmptyMessageProps) { return (

{__('Payment options are not available:', 'give')}

{message}

); } /** * Component that displays contextual messages when no gateways are available. * Handles different scenarios like minimum donation amount, currency support, and subscription support. * * @since 3.20.0 updated message to account for minimum donation amount * @since 3.0.0 */ function GatewayMissingMessage({ donationAmountMinimumNotReached, currencyNotSupported, subscriptionNotSupported, }: GatewayMissingMessageProps) { let message = __( 'No gateways have been enabled yet. To get started accepting donations, enable a compatible payment gateway in your settings.', 'give' ); if (donationAmountMinimumNotReached) { message = __('Donation amount must be greater than zero.', 'give'); } else if (currencyNotSupported) { message = __( 'The selected currency is not supported by any of the available payment gateways. Please select a different currency or contact the site administrator for assistance.', 'give' ); } else if (subscriptionNotSupported) { message = __( 'No gateways support recurring payments. Please select a different payment gateway or contact the site administrator for assistance.', 'give' ); } return ; } /** * Error fallback component for gateway fields that fail to render. * Provides user-friendly error message and reload functionality. * * @since 3.0.0 */ function GatewayFieldsErrorFallback({error, resetErrorBoundary}: GatewayFieldsErrorFallbackProps) { return (

{__( 'An error occurred while loading the gateway fields. Please notify the site administrator. The error message is:', 'give' )}

{error.message}
); } /** * Notice component displayed when the donation form is in test mode. * Informs users that no live donations will be processed. * * @since 3.0.0 */ const TestModeNotice = () => { return (
{' '}

{createInterpolateElement( __( 'Test mode is enabled. While in test mode no live donations are processed.', 'give' ), { strong: , } )}

); }; /** * Main Gateways component that handles payment gateway selection and display. * * This component: * - Filters available gateways based on donation amount, currency, and subscription support * - Handles currency switcher settings to show only compatible gateways * - Automatically selects appropriate default gateway when options change * - Displays contextual messages when no gateways are available * - Shows test mode notice when applicable * * @since 4.4.0 filter gateways based on donation type * @since 3.0.0 */ export default function Gateways({isTestMode, defaultValue, inputProps, gateways}: GatewayFieldProps) { const {useFormState, useWatch, useFormContext, useDonationFormSettings} = window.givewp.form.hooks; const {errors} = useFormState(); const {setValue} = useFormContext(); const {currencySwitcherSettings} = useDonationFormSettings(); // Watch form values that affect gateway availability const donationAmount = useWatch({name: 'amount'}); const currency = useWatch({name: 'currency'}); const activeGatewayId = useWatch({name: 'gatewayId'}); const donationType = useWatch({name: 'donationType'}); const isSubscription = isDonationTypeSubscription(donationType); const donationAmountMinimumNotReached = donationAmount === 0; /** * Filter gateway options based on currency switcher settings. * If currency switcher is enabled, only show gateways that support the selected currency. */ const gatewayOptionsWithCurrencySettings = useMemo(() => { if (currencySwitcherSettings.length <= 1) { return gateways; } const currencySwitcherSetting = currencySwitcherSettings.find(({id}) => id === currency); if (!currencySwitcherSetting) { return []; } return gateways.filter(({id}) => currencySwitcherSetting.gateways.includes(id)); }, [currency]); /** * Filter gateways that support subscription/recurring donations. */ const gatewayOptionsWithSubscriptionSupport = useMemo(() => { return gatewayOptionsWithCurrencySettings.filter(({supportsSubscriptions}) => supportsSubscriptions); }, [gatewayOptionsWithCurrencySettings]); /** * Final filtered gateway options based on all criteria: * - Donation amount must be greater than zero * - For subscriptions, gateway must support recurring payments * - Gateway must support the selected currency */ const gatewayOptions = useMemo(() => { if (donationAmountMinimumNotReached) { return []; } if (isSubscription) { return gatewayOptionsWithSubscriptionSupport.length > 0 ? gatewayOptionsWithSubscriptionSupport : []; } return gatewayOptionsWithCurrencySettings.length > 0 ? gatewayOptionsWithCurrencySettings : []; }, [ donationAmountMinimumNotReached, gatewayOptionsWithSubscriptionSupport, gatewayOptionsWithCurrencySettings, isSubscription, ]); /** * Automatically set the selected gateway when available options change. * - If default gateway is still available, keep it selected * - Otherwise, select the first available gateway * - If no gateways available, clear the selection */ useEffect(() => { if (gatewayOptions.length > 0) { const optionsDefaultValue = gatewayOptions.find(option => option.id === defaultValue) ? defaultValue : gatewayOptions[0].id; setValue(inputProps.name, optionsDefaultValue); } else { setValue(inputProps.name, null); } }, [gatewayOptions]); return ( <> {gatewayOptions.length > 0 ? ( <> {isTestMode && }
    {gatewayOptions.map((gateway) => ( ))}
) : ( )} {message}} /> ); } /** * Individual gateway option component that renders a radio button with gateway information. * * Features: * - Radio button input for gateway selection * - Gateway-specific styling and icons * - Dynamic icon selection based on gateway type * - Error boundary for gateway-specific fields * - Conditional rendering of gateway fields when active * * @since 3.0.0 */ function GatewayOption({gateway, defaultChecked, inputProps, isActive}: GatewayOptionProps) { const gatewayClass = `givewp-fields-gateways__gateway--${gateway.id}`; const className = cx('givewp-fields-gateways__gateway', { [gatewayClass]: true, 'givewp-fields-gateways__gateway--active': isActive, }); // Determine appropriate icon based on gateway type let fontAwesomeClass = 'fa-solid fa-gear'; if (gateway.id.includes('stripe') || gateway.id.includes('card')) { fontAwesomeClass = 'fa-solid fa-credit-card'; } else if (gateway.id.includes('paypal')) { fontAwesomeClass = 'fa-brands fa-paypal'; } else if (gateway.id.includes('test')) { fontAwesomeClass = 'fa-solid fa-screwdriver-wrench'; } return (
  • { window.location.reload(); }} > {isActive && }
  • ); }