PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 4.16.5
GiveWP – Donation Plugin and Fundraising Platform v4.16.5
4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / DonationForms / resources / registrars / templates / fields / Gateways.tsx
give / src / DonationForms / resources / registrars / templates / fields Last commit date
Amount 1 month ago Consent 1 year ago utils 1 year ago Checkbox.tsx 1 year ago Date.tsx 1 year ago Email.tsx 1 year ago File.tsx 1 year ago Gateways.tsx 1 year ago Hidden.tsx 2 years ago Honeypot.tsx 1 year ago MultiSelect.tsx 2 years ago Password.tsx 2 years ago Phone.tsx 1 year ago Radio.tsx 2 years ago Select.tsx 1 year ago Text.tsx 1 year ago TextArea.tsx 2 years ago Url.tsx 2 years ago
Gateways.tsx
317 lines
1 import {ErrorMessage} from '@hookform/error-message';
2 import type {GatewayFieldProps, GatewayOptionProps} from '@givewp/forms/propTypes';
3 import {ErrorBoundary} from 'react-error-boundary';
4 import {__, sprintf} from '@wordpress/i18n';
5 import {createInterpolateElement, useEffect, useMemo} from '@wordpress/element';
6 import cx from 'classnames';
7 import {isDonationTypeSubscription} from '@givewp/forms/types';
8
9 interface EmptyMessageProps {
10 message: string;
11 }
12
13 interface GatewayMissingMessageProps {
14 donationAmountMinimumNotReached?: boolean;
15 currencyNotSupported?: boolean;
16 subscriptionNotSupported?: boolean;
17 }
18
19 interface GatewayFieldsErrorFallbackProps {
20 error: Error;
21 resetErrorBoundary: () => void;
22 }
23
24 /**
25 * Empty message component displayed when no payment gateways are available.
26 *
27 * @since 3.20.0
28 */
29 function EmptyMessage({message}: EmptyMessageProps) {
30 return (
31 <div className="givewp-fields-gateways__gateway--empty">
32 <p>
33 <b>{__('Payment options are not available:', 'give')}</b>
34 </p>
35
36 <p>
37 <em>{message}</em>
38 </p>
39 </div>
40 );
41 }
42
43 /**
44 * Component that displays contextual messages when no gateways are available.
45 * Handles different scenarios like minimum donation amount, currency support, and subscription support.
46 *
47 * @since 3.20.0 updated message to account for minimum donation amount
48 * @since 3.0.0
49 */
50 function GatewayMissingMessage({
51 donationAmountMinimumNotReached,
52 currencyNotSupported,
53 subscriptionNotSupported,
54 }: GatewayMissingMessageProps) {
55 let message = __(
56 'No gateways have been enabled yet. To get started accepting donations, enable a compatible payment gateway in your settings.',
57 'give'
58 );
59
60 if (donationAmountMinimumNotReached) {
61 message = __('Donation amount must be greater than zero.', 'give');
62 } else if (currencyNotSupported) {
63 message = __(
64 '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.',
65 'give'
66 );
67 } else if (subscriptionNotSupported) {
68 message = __(
69 'No gateways support recurring payments. Please select a different payment gateway or contact the site administrator for assistance.',
70 'give'
71 );
72 }
73
74 return <EmptyMessage message={message} />;
75 }
76
77 /**
78 * Error fallback component for gateway fields that fail to render.
79 * Provides user-friendly error message and reload functionality.
80 *
81 * @since 3.0.0
82 */
83 function GatewayFieldsErrorFallback({error, resetErrorBoundary}: GatewayFieldsErrorFallbackProps) {
84 return (
85 <div role="alert">
86 <p>
87 {__(
88 'An error occurred while loading the gateway fields. Please notify the site administrator. The error message is:',
89 'give'
90 )}
91 </p>
92 <pre style={{padding: '0.5rem'}}>{error.message}</pre>
93 <button type="button" onClick={resetErrorBoundary}>
94 {__('Reload form', 'give')}
95 </button>
96 </div>
97 );
98 }
99
100 /**
101 * Notice component displayed when the donation form is in test mode.
102 * Informs users that no live donations will be processed.
103 *
104 * @since 3.0.0
105 */
106 const TestModeNotice = () => {
107 return (
108 <div className="givewp-test-mode-notice">
109 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
110 <path
111 fillRule="evenodd"
112 clipRule="evenodd"
113 d="M12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1ZM12 7C11.4477 7 11 7.44772 11 8C11 8.55228 11.4477 9 12 9H12.01C12.5623 9 13.01 8.55228 13.01 8C13.01 7.44772 12.5623 7 12.01 7H12ZM13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12V16C11 16.5523 11.4477 17 12 17C12.5523 17 13 16.5523 13 16V12Z"
114 fill="#F29718"
115 />
116 </svg>{' '}
117 <p>
118 {createInterpolateElement(
119 __(
120 'Test mode is <strong>enabled</strong>. While in test mode no live donations are processed.',
121 'give'
122 ),
123 {
124 strong: <strong />,
125 }
126 )}
127 </p>
128 </div>
129 );
130 };
131
132 /**
133 * Main Gateways component that handles payment gateway selection and display.
134 *
135 * This component:
136 * - Filters available gateways based on donation amount, currency, and subscription support
137 * - Handles currency switcher settings to show only compatible gateways
138 * - Automatically selects appropriate default gateway when options change
139 * - Displays contextual messages when no gateways are available
140 * - Shows test mode notice when applicable
141 *
142 * @since 4.4.0 filter gateways based on donation type
143 * @since 3.0.0
144 */
145 export default function Gateways({isTestMode, defaultValue, inputProps, gateways}: GatewayFieldProps) {
146 const {useFormState, useWatch, useFormContext, useDonationFormSettings} = window.givewp.form.hooks;
147 const {errors} = useFormState();
148 const {setValue} = useFormContext();
149 const {currencySwitcherSettings} = useDonationFormSettings();
150
151 // Watch form values that affect gateway availability
152 const donationAmount = useWatch({name: 'amount'});
153 const currency = useWatch({name: 'currency'});
154 const activeGatewayId = useWatch({name: 'gatewayId'});
155 const donationType = useWatch({name: 'donationType'});
156 const isSubscription = isDonationTypeSubscription(donationType);
157
158 const donationAmountMinimumNotReached = donationAmount === 0;
159
160 /**
161 * Filter gateway options based on currency switcher settings.
162 * If currency switcher is enabled, only show gateways that support the selected currency.
163 */
164 const gatewayOptionsWithCurrencySettings = useMemo(() => {
165 if (currencySwitcherSettings.length <= 1) {
166 return gateways;
167 }
168
169 const currencySwitcherSetting = currencySwitcherSettings.find(({id}) => id === currency);
170
171 if (!currencySwitcherSetting) {
172 return [];
173 }
174
175 return gateways.filter(({id}) => currencySwitcherSetting.gateways.includes(id));
176 }, [currency]);
177
178 /**
179 * Filter gateways that support subscription/recurring donations.
180 */
181 const gatewayOptionsWithSubscriptionSupport = useMemo(() => {
182 return gatewayOptionsWithCurrencySettings.filter(({supportsSubscriptions}) => supportsSubscriptions);
183 }, [gatewayOptionsWithCurrencySettings]);
184
185 /**
186 * Final filtered gateway options based on all criteria:
187 * - Donation amount must be greater than zero
188 * - For subscriptions, gateway must support recurring payments
189 * - Gateway must support the selected currency
190 */
191 const gatewayOptions = useMemo(() => {
192 if (donationAmountMinimumNotReached) {
193 return [];
194 }
195
196 if (isSubscription) {
197 return gatewayOptionsWithSubscriptionSupport.length > 0 ? gatewayOptionsWithSubscriptionSupport : [];
198 }
199
200 return gatewayOptionsWithCurrencySettings.length > 0 ? gatewayOptionsWithCurrencySettings : [];
201 }, [
202 donationAmountMinimumNotReached,
203 gatewayOptionsWithSubscriptionSupport,
204 gatewayOptionsWithCurrencySettings,
205 isSubscription,
206 ]);
207
208 /**
209 * Automatically set the selected gateway when available options change.
210 * - If default gateway is still available, keep it selected
211 * - Otherwise, select the first available gateway
212 * - If no gateways available, clear the selection
213 */
214 useEffect(() => {
215 if (gatewayOptions.length > 0) {
216 const optionsDefaultValue = gatewayOptions.find(option => option.id === defaultValue)
217 ? defaultValue
218 : gatewayOptions[0].id;
219
220 setValue(inputProps.name, optionsDefaultValue);
221 } else {
222 setValue(inputProps.name, null);
223 }
224 }, [gatewayOptions]);
225
226 return (
227 <>
228 {gatewayOptions.length > 0 ? (
229 <>
230 {isTestMode && <TestModeNotice />}
231 <ul className="givewp-fields-gateways__list" style={{listStyleType: 'none', padding: 0}}>
232 {gatewayOptions.map((gateway) => (
233 <GatewayOption
234 gateway={gateway}
235 defaultChecked={gateway.id === defaultValue}
236 key={gateway.id}
237 inputProps={inputProps}
238 isActive={gateway.id === activeGatewayId}
239 />
240 ))}
241 </ul>
242 </>
243 ) : (
244 <GatewayMissingMessage
245 donationAmountMinimumNotReached={donationAmountMinimumNotReached}
246 currencyNotSupported={gatewayOptionsWithCurrencySettings.length === 0 && !donationAmountMinimumNotReached}
247 subscriptionNotSupported={isSubscription && gatewayOptionsWithSubscriptionSupport.length === 0}
248 />
249 )}
250
251 <ErrorMessage
252 errors={errors}
253 name={'gatewayId'}
254 render={({message}) => <span className="give-next-gen__error-message">{message}</span>}
255 />
256 </>
257 );
258 }
259
260 /**
261 * Individual gateway option component that renders a radio button with gateway information.
262 *
263 * Features:
264 * - Radio button input for gateway selection
265 * - Gateway-specific styling and icons
266 * - Dynamic icon selection based on gateway type
267 * - Error boundary for gateway-specific fields
268 * - Conditional rendering of gateway fields when active
269 *
270 * @since 3.0.0
271 */
272 function GatewayOption({gateway, defaultChecked, inputProps, isActive}: GatewayOptionProps) {
273 const gatewayClass = `givewp-fields-gateways__gateway--${gateway.id}`;
274 const className = cx('givewp-fields-gateways__gateway', {
275 [gatewayClass]: true,
276 'givewp-fields-gateways__gateway--active': isActive,
277 });
278
279 // Determine appropriate icon based on gateway type
280 let fontAwesomeClass = 'fa-solid fa-gear';
281 if (gateway.id.includes('stripe') || gateway.id.includes('card')) {
282 fontAwesomeClass = 'fa-solid fa-credit-card';
283 } else if (gateway.id.includes('paypal')) {
284 fontAwesomeClass = 'fa-brands fa-paypal';
285 } else if (gateway.id.includes('test')) {
286 fontAwesomeClass = 'fa-solid fa-screwdriver-wrench';
287 }
288
289 return (
290 <li className={className}>
291 <label>
292 <input
293 type="radio"
294 value={gateway.id}
295 id={gateway.id}
296 defaultChecked={defaultChecked}
297 {...inputProps}
298 />
299 <span className="givewp-fields-gateways__gateway__label">
300 {sprintf(__('Donate with %s', 'give'), gateway.label)}
301 </span>
302 <i className={`givewp-fields-gateways__gateway__icon ${fontAwesomeClass}`}></i>
303 </label>
304 <div className="givewp-fields-gateways__gateway__fields">
305 <ErrorBoundary
306 FallbackComponent={GatewayFieldsErrorFallback}
307 onReset={() => {
308 window.location.reload();
309 }}
310 >
311 {isActive && <gateway.Fields />}
312 </ErrorBoundary>
313 </div>
314 </li>
315 );
316 }
317