PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 3.17.2
GiveWP – Donation Plugin and Fundraising Platform v3.17.2
4.16.7.2 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 / PaymentGateways / Gateways / PayPalCommerce / payPalCommerceGateway.tsx
give / src / PaymentGateways / Gateways / PayPalCommerce Last commit date
PayPalCommerceGateway.php 2 years ago payPalCommerceGateway.tsx 1 year ago types.ts 2 years ago
payPalCommerceGateway.tsx
700 lines
1 import {
2 PayPalButtons,
3 PayPalHostedField,
4 PayPalHostedFieldsProvider,
5 PayPalScriptProvider,
6 usePayPalHostedFields,
7 usePayPalScriptReducer,
8 } from '@paypal/react-paypal-js';
9 import type {Gateway} from '@givewp/forms/types';
10 import {__, sprintf} from '@wordpress/i18n';
11 import {debounce} from 'react-ace/lib/editorOptions';
12 import {Flex, TextControl} from '@wordpress/components';
13 import {CSSProperties, useEffect, useState} from 'react';
14 import {PayPalSubscriber} from './types';
15
16 (() => {
17 /**
18 * Hoisted values are used to pass data in contexts where hooks cannot be used.
19 * For example, the form uses hooks to get the values of the form fields,
20 * but the callbacks for PayPal cannot use hooks to access those values.
21 *
22 * The <FormFieldsProvider /> component is used to hoist the form field values
23 * which are then collected in createOrderHandler(). This callback is not
24 * a component, so it cannot use hooks to access the form field values.
25 */
26 let amount;
27 let feeRecovery;
28 let firstName;
29 let lastName;
30 let email;
31 let cardholderName;
32 let hostedField;
33 let payPalDonationsSettings;
34 let payPalOrderId;
35 let payPalSubscriptionId;
36
37 let subscriptionFrequency;
38 let subscriptionInstallments;
39 let subscriptionPeriod;
40
41 let country;
42 let state;
43 let city;
44 let addressLine1;
45 let addressLine2;
46 let postalCode;
47
48 let updateOrderAmount = false;
49 let orderCreated = false;
50
51 let currency;
52
53 let eventTickets;
54
55 /**
56 * @since 3.12.2
57 */
58 const getEventTicketsTotalAmount = (
59 eventTickets: Array<{
60 ticketId: number;
61 quantity: number;
62 amount: number;
63 }>
64 ) => {
65 const totalAmount = eventTickets.reduce((accumulator, eventTicket) => accumulator + eventTicket.amount, 0);
66 if (totalAmount > 0) {
67 return totalAmount / 100;
68 } else {
69 return 0;
70 }
71 };
72
73 const buttonsStyle = {
74 color: 'gold' as 'gold' | 'blue' | 'silver' | 'white' | 'black',
75 label: 'paypal' as 'paypal' | 'checkout' | 'buynow' | 'pay' | 'installment' | 'subscribe' | 'donate',
76 layout: 'vertical' as 'vertical' | 'horizontal',
77 shape: 'rect' as 'rect' | 'pill',
78 tagline: false,
79 };
80
81 const CUSTOM_FIELD_STYLE = {
82 height: '50px', // @todo Magic number, but it works
83 borderWidth: '.078rem',
84 borderStyle: 'solid',
85 borderColor: '#666',
86 borderRadius: '.25rem',
87 padding: '0 1.1875rem',
88 width: '100%',
89 marginBottom: '.5rem',
90 boxSizing: 'inherit',
91 inlineSize: '100%',
92 backgroundColor: '#fff',
93 color: '#4d4d4d',
94 fontSize: '1rem',
95 fontFamily: 'inherit',
96 fontWeight: '500',
97 lineHeight: '1.2',
98 } as CSSProperties;
99
100 /**
101 * Get PayPal script options.
102 *
103 * This function return the paypal script options on basis of context.
104 * - If donation type is subscription then remove hosted fields from components.
105 *
106 * @return {object} PayPal script options.
107 */
108 const getPayPalScriptOptions = ({isSubscription}) => {
109 let paypalScriptOptions = {...payPalDonationsSettings.sdkOptions};
110
111 // Remove hosted fields from components if subscription.
112 if (isSubscription && -1 !== paypalScriptOptions.components.indexOf('hosted-fields')) {
113 paypalScriptOptions.components = paypalScriptOptions.components
114 .split(',')
115 .filter((component) => component !== 'hosted-fields')
116 .join(',');
117 }
118
119 return paypalScriptOptions;
120 };
121
122 /**
123 * Get amount with fee (if any).
124 *
125 * @since 3.6.1 Append 'give-cs-form-currency' to formData
126 * @since 3.2.0
127 * @return {number} Amount with fee.
128 */
129 const getAmount = () => {
130 const feeAmount = feeRecovery ? feeRecovery : 0;
131 let amountWithFee = amount + feeAmount;
132 amountWithFee = Math.round(amountWithFee * 100) / 100;
133
134 return amountWithFee;
135 };
136
137 const getFormData = () => {
138 const formData = new FormData();
139
140 formData.append('give-form-id', payPalDonationsSettings.donationFormId);
141 formData.append('give-form-hash', payPalDonationsSettings.donationFormNonce);
142
143 formData.append('give_payment_mode', 'paypal-commerce');
144
145 const eventTicketsTotalAmount = eventTickets ? getEventTicketsTotalAmount(JSON.parse(eventTickets)) : 0;
146 const isSubscription = subscriptionPeriod ? subscriptionPeriod !== 'one-time' : false;
147 if (!isSubscription) {
148 formData.append('give-amount', getAmount() + eventTicketsTotalAmount);
149 } else {
150 formData.append('give-amount', getAmount()); // We don't want to charge the event tickets for each subscription renewal
151 }
152
153 formData.append('give-event-tickets-total-amount', String(eventTicketsTotalAmount));
154
155 formData.append('give-recurring-period', subscriptionPeriod);
156 formData.append('period', subscriptionPeriod);
157 formData.append('frequency', subscriptionFrequency);
158 formData.append('times', subscriptionInstallments);
159
160 formData.append('give_first', firstName);
161 formData.append('give_last', lastName);
162 formData.append('give_email', email);
163
164 if (country) {
165 formData.append('card_address', addressLine1);
166 formData.append('card_address_2', addressLine2);
167 formData.append('card_city', city);
168 formData.append('card_state', state);
169 formData.append('card_zip', postalCode);
170 formData.append('billing_country', country);
171 }
172
173 /**
174 * Ensure the proper currency will be used when using the Currency Switcher add-on.
175 */
176 formData.append('give-cs-form-currency', currency);
177
178 return formData;
179 };
180
181 const validateHostedFields = () => {
182 return Object.values(hostedField.cardFields.getState().fields).some(
183 (field: {isValid: boolean}) => field.isValid
184 );
185 };
186
187 const createOrderHandler = async (): Promise<string> => {
188 const response = await fetch(`${payPalDonationsSettings.ajaxUrl}?action=give_paypal_commerce_create_order`, {
189 method: 'POST',
190 body: getFormData(),
191 });
192 const responseJson = await response.json();
193
194 if (!responseJson.success) {
195 throw responseJson.data.error;
196 }
197
198 return (payPalOrderId = responseJson.data.id);
199 };
200
201 const createSubscriptionHandler = async (data, actions) => {
202 // eslint-disable-next-line
203 const response = await fetch(`${payPalDonationsSettings.ajaxUrl}?action=give_paypal_commerce_create_plan_id`, {
204 method: 'POST',
205 body: getFormData(),
206 });
207
208 const responseJson = await response.json();
209
210 if (!responseJson.success) {
211 throw responseJson.data.error;
212 }
213
214 const subscriberData: PayPalSubscriber = {
215 name: {
216 given_name: firstName,
217 surname: lastName,
218 },
219 email_address: email,
220 };
221
222 if (country) {
223 subscriberData.shipping_address = {
224 name: {
225 full_name: `${firstName} ${lastName}`.trim(),
226 },
227 address: {
228 address_line_1: addressLine1,
229 address_line_2: addressLine2,
230 admin_area_2: city,
231 admin_area_1: state,
232 postal_code: postalCode,
233 country_code: country,
234 },
235 };
236 }
237
238 return actions.subscription
239 .create({
240 plan_id: responseJson.data.id,
241 subscriber: subscriberData,
242 })
243 .then((orderId) => {
244 return (payPalSubscriptionId = orderId);
245 });
246 };
247
248 const Divider = ({label, style = {}}) => {
249 const styles = {
250 container: {
251 fontSize: '16px',
252 fontStyle: 'italic',
253 display: 'flex',
254 justifyContent: 'center',
255 alignItems: 'center',
256 ...style,
257 },
258 dashedLine: {
259 border: '1px solid #d4d4d4',
260 flexGrow: 1,
261 },
262 label: {
263 padding: '0 6px',
264 fontSize: '14px',
265 color: '#8d8e8e',
266 },
267 };
268
269 return (
270 <div className="separator-with-text" style={styles.container}>
271 <div className="dashed-line" style={styles.dashedLine} />
272 <div className="label" style={styles.label}>
273 {label}
274 </div>
275 <div className="dashed-line" style={styles.dashedLine} />
276 </div>
277 );
278 };
279
280 const HoistHostedFieldContext = () => {
281 hostedField = usePayPalHostedFields();
282 return <></>;
283 };
284
285 const FormFieldsProvider = ({children}) => {
286 const {useWatch} = window.givewp.form.hooks;
287
288 amount = useWatch({name: 'amount'});
289 feeRecovery = useWatch({name: 'feeRecovery'});
290 firstName = useWatch({name: 'firstName'});
291 lastName = useWatch({name: 'lastName'});
292 email = useWatch({name: 'email'});
293
294 subscriptionFrequency = useWatch({name: 'subscriptionFrequency'});
295 subscriptionInstallments = useWatch({name: 'subscriptionInstallments'});
296 subscriptionPeriod = useWatch({name: 'subscriptionPeriod'});
297
298 addressLine1 = useWatch({name: 'address1'});
299 addressLine2 = useWatch({name: 'address2'});
300 city = useWatch({name: 'city'});
301 state = useWatch({name: 'state'});
302 postalCode = useWatch({name: 'zip'});
303 country = useWatch({name: 'country'});
304
305 currency = useWatch({name: 'currency'});
306
307 eventTickets = useWatch({name: 'event-tickets'});
308
309 useEffect(() => {
310 if (orderCreated) {
311 updateOrderAmount = true;
312 }
313 }, [amount, eventTickets]);
314
315 return children;
316 };
317
318 /**
319 * @since 3.12.2
320 */
321 const getRequiredValidationMessage = () => {
322 return __('This is a required field', 'give');
323 };
324
325 /**
326 * @since 3.12.2
327 */
328 const isCityRequired = () => {
329 return Boolean(document.querySelector('.givewp-fields-text-city .givewp-field-required'));
330 };
331
332 /**
333 * @since 3.12.2
334 */
335 const isStateRequired = () => {
336 return Boolean(document.querySelector('.givewp-fields-select-state .givewp-field-required'));
337 };
338
339 /**
340 * @since 3.12.2
341 */
342 const isZipRequired = () => {
343 return Boolean(document.querySelector('.givewp-fields-text-zip .givewp-field-required'));
344 };
345
346 const SmartButtonsContainer = () => {
347 const {useWatch, useFormState} = window.givewp.form.hooks;
348 const donationType = useWatch({name: 'donationType'});
349 const {isSubmitting, isSubmitSuccessful} = useFormState();
350 const {useFormContext} = window.givewp.form.hooks;
351 const {
352 getFieldState,
353 setFocus,
354 getValues,
355 formState: {errors},
356 trigger,
357 setError,
358 } = useFormContext();
359 const gateway = window.givewp.gateways.get('paypal-commerce');
360
361 const props = {
362 style: buttonsStyle,
363 disabled: isSubmitting || isSubmitSuccessful,
364 forceReRender: debounce(() => [amount, feeRecovery, firstName, lastName, email, currency], 500),
365 onClick: async (data, actions) => {
366 // Validate whether payment gateway support subscriptions.
367 if (donationType === 'subscription' && !gateway.supportsSubscriptions) {
368 setError(
369 'FORM_ERROR',
370 {
371 message: __(
372 'This payment gateway does not support recurring payments, please try selecting another payment gateway.',
373 'give'
374 ),
375 },
376 {shouldFocus: true}
377 );
378
379 // Scroll to the top of the form.
380 // Add this moment we do not have a way to scroll to the error message.
381 // In the future we can add a way to scroll to the error message and remove this code.
382 document.querySelector('#give-next-gen button[type="submit"]').scrollIntoView({behavior: 'smooth'});
383
384 return actions.reject();
385 }
386
387 // Validate the form values before proceeding.
388 const result = await trigger();
389 if (result === false) {
390 // Set focus on first invalid field.
391 for (const fieldName in getValues()) {
392 if (getFieldState(fieldName).invalid) {
393 setFocus(fieldName);
394 }
395 }
396 return actions.reject();
397 }
398
399 /**
400 * Depending on the selected country, the city, state, and zip fields can be required or not and there are custom
401 * validation rules on the server side that check that. However, in this case, these validations are reached later
402 * when the donation is already created on the PayPal side. This way, we need the conditions below to check it earlier
403 * and prevent the donation creation on the PayPal side if the required billing address fields are missing.
404 */
405 if (country) {
406 if (city.length === 0 && isCityRequired()) {
407 setError(
408 'city',
409 {
410 type: 'custom',
411 message: getRequiredValidationMessage(),
412 },
413 {shouldFocus: true}
414 );
415 return actions.reject();
416 }
417
418 if (state.length === 0 && isStateRequired()) {
419 setError(
420 'state',
421 {
422 type: 'custom',
423 message: getRequiredValidationMessage(),
424 },
425 {shouldFocus: true}
426 );
427 // As the state is a hidden field we need to use this workaround because the "shouldFocus" option does not work in hidden fields.
428 document.querySelector('.givewp-fields-select-state').scrollIntoView({behavior: 'smooth'});
429 return actions.reject();
430 }
431
432 if (postalCode.length === 0 && isZipRequired()) {
433 setError('zip', {type: 'custom', message: getRequiredValidationMessage()}, {shouldFocus: true});
434 return actions.reject();
435 }
436 }
437
438 orderCreated = true;
439 return actions.resolve();
440 },
441 onApprove: async (data, actions) => {
442 const donationFormWithSubmitButton = Array.from(document.forms).pop();
443 const submitButton: HTMLButtonElement = donationFormWithSubmitButton.querySelector('[type="submit"]');
444 const submitButtonDefaultText = submitButton.textContent;
445 submitButton.textContent = __('Waiting for PayPal...', 'give');
446 submitButton.disabled = true;
447
448 if (payPalOrderId && updateOrderAmount) {
449 const response = await fetch(
450 `${payPalDonationsSettings.ajaxUrl}?action=give_paypal_commerce_update_order_amount&order=${payPalOrderId}`,
451 {
452 method: 'POST',
453 body: getFormData(),
454 }
455 );
456
457 const {data: ajaxResponseData} = await response.json();
458
459 if (ajaxResponseData.hasOwnProperty('error')) {
460 submitButton.disabled = false;
461 submitButton.textContent = submitButtonDefaultText;
462 throw new Error(ajaxResponseData.error);
463 }
464 }
465
466 if (donationType === 'subscription') {
467 submitButton.disabled = false;
468 submitButton.textContent = submitButtonDefaultText;
469 submitButton.click();
470 return;
471 }
472
473 return actions.order.capture().then((details) => {
474 submitButton.disabled = false;
475 submitButton.textContent = submitButtonDefaultText;
476 submitButton.click();
477 });
478 },
479 };
480
481 return donationType === 'subscription' ? (
482 // @ts-ignore
483 <PayPalButtons {...props} createSubscription={createSubscriptionHandler} />
484 ) : (
485 // @ts-ignore
486 <PayPalButtons {...props} createOrder={createOrderHandler} />
487 );
488 };
489
490 const HostedFieldsContainer = () => {
491 const {useWatch} = window.givewp.form.hooks;
492 const firstName = useWatch({name: 'firstName'});
493 const lastName = useWatch({name: 'lastName'});
494
495 const cardholderDefault = [firstName ?? '', lastName ?? ''].filter((x) => x).join(' ');
496 const [_cardholderName, setCardholderName] = useState(null);
497
498 useEffect(() => {
499 cardholderName = _cardholderName ?? cardholderDefault;
500 });
501
502 return (
503 <PayPalHostedFieldsProvider createOrder={createOrderHandler}>
504 <div>
505 <Divider label={__('Or pay with card', 'give')} style={{padding: '30px 0'}} />
506
507 <TextControl
508 className="givewp-fields"
509 label={__('Cardholder Name', 'give')}
510 hideLabelFromVision={true}
511 placeholder={__('Cardholder Name', 'give')}
512 value={_cardholderName ?? cardholderDefault}
513 onChange={(value) => setCardholderName(value)}
514 />
515
516 <PayPalHostedField
517 id="card-number"
518 className="card-field"
519 style={CUSTOM_FIELD_STYLE}
520 hostedFieldType="number"
521 options={{
522 selector: '#card-number',
523 placeholder: '4111 1111 1111 1111',
524 }}
525 />
526
527 <Flex gap="10px">
528 <PayPalHostedField
529 id="expiration-date"
530 className="givewp-fields"
531 style={CUSTOM_FIELD_STYLE}
532 hostedFieldType="expirationDate"
533 options={{
534 selector: '#expiration-date',
535 placeholder: __('MM/YYYY', 'give'),
536 }}
537 />
538 <PayPalHostedField
539 id="cvv"
540 className="card-field"
541 style={CUSTOM_FIELD_STYLE}
542 hostedFieldType="cvv"
543 options={{
544 selector: '#cvv',
545 placeholder: __('CVV', 'give'),
546 maskInput: true,
547 }}
548 />
549 </Flex>
550 <div style={{display: 'flex', gap: '10px'}}></div>
551
552 <HoistHostedFieldContext />
553 </div>
554 </PayPalHostedFieldsProvider>
555 );
556 };
557
558 function PaymentMethodsWrapper() {
559 const {useWatch} = window.givewp.form.hooks;
560 const donationType = useWatch({name: 'donationType'});
561 const [{options}, dispatch] = usePayPalScriptReducer();
562
563 useEffect(() => {
564 const isSubscription = donationType === 'subscription';
565
566 dispatch({
567 type: 'resetOptions',
568 value: {
569 ...getPayPalScriptOptions({isSubscription}),
570 currency: currency,
571 vault: donationType === 'subscription',
572 intent: donationType === 'subscription' ? 'subscription' : 'capture',
573 },
574 });
575 }, [currency, donationType]);
576
577 return (
578 <>
579 <SmartButtonsContainer />
580 {-1 !== options.components.indexOf('hosted-fields') && <HostedFieldsContainer />}
581 </>
582 );
583 }
584
585 const payPalCommerceGateway: Gateway = {
586 id: 'paypal-commerce',
587 initialize() {
588 payPalDonationsSettings = this.settings;
589 },
590 /**
591 * Before create payment.
592 * @since 3.2.0 Handle error response in approveOrderCallback.
593 * @param {Object} values
594 */
595 beforeCreatePayment: async function (values): Promise<object> {
596 if (payPalOrderId) {
597 // If order ID already set by payment buttons then return early.
598 return {
599 payPalOrderId: payPalOrderId,
600 };
601 }
602
603 if (payPalSubscriptionId) {
604 return {
605 payPalSubscriptionId: payPalSubscriptionId,
606 };
607 }
608
609 if (!validateHostedFields()) {
610 throw new Error('Invalid hosted fields');
611 }
612
613 const approveOrderCallback = async (data) => {
614 const response = await fetch(
615 `${payPalDonationsSettings.ajaxUrl}?action=give_paypal_commerce_approve_order&order=${data.orderId}&update_amount=${updateOrderAmount}`,
616 {
617 method: 'POST',
618 body: getFormData(),
619 }
620 );
621
622 const {data: ajaxResponseData} = await response.json();
623
624 if (ajaxResponseData.hasOwnProperty('error')) {
625 throw new Error(ajaxResponseData.error);
626 }
627
628 return {...data, payPalOrderId: data.orderId};
629 };
630
631 try {
632 const result = await hostedField.cardFields.submit({
633 // Trigger 3D Secure authentication
634 contingencies: ['SCA_WHEN_REQUIRED'],
635 cardholderName: cardholderName,
636 });
637
638 if (
639 !result || // Check whether get result from paypal gateway server.
640 (['NO', 'POSSIBLE'].includes(result.liabilityShift) && // Check whether card required 3D secure validation.
641 !(result.liabilityShifted && 'POSSIBLE' === result.liabilityShift)) // Check whether card passed 3D secure validation.
642 ) {
643 throw new Error(
644 __(
645 'There was a problem authenticating your payment method. Please try again. If the problem persists, please try another payment method.',
646 'give'
647 )
648 );
649 }
650
651 return await approveOrderCallback(result);
652 } catch (err) {
653 console.log('paypal donations error', err);
654
655 // Handle PayPal error.
656 const isPayPalDonationError = err.hasOwnProperty('details');
657 if (isPayPalDonationError) {
658 throw new Error(err.details[0].description);
659 }
660
661 throw new Error(sprintf(__('Paypal Donations Error: %s', 'give'), err.message));
662 }
663 },
664
665 /**
666 * @since 3.17.1 Hide submit button when PayPal Commerce is selected.
667 */
668 Fields() {
669 const {useWatch} = window.givewp.form.hooks;
670 const donationType = useWatch({name: 'donationType'});
671 const isSubscription = donationType === 'subscription';
672
673 useEffect(() => {
674 const submitButton = document.querySelector<HTMLButtonElement>(
675 'form#give-next-gen button[type="submit"]'
676 );
677
678 if (submitButton) {
679 submitButton.style.display = 'none';
680 }
681
682 return () => {
683 if (submitButton) {
684 submitButton.style.display = '';
685 }
686 };
687 }, []);
688 return (
689 <FormFieldsProvider>
690 <PayPalScriptProvider deferLoading={true} options={getPayPalScriptOptions({isSubscription})}>
691 <PaymentMethodsWrapper />
692 </PayPalScriptProvider>
693 </FormFieldsProvider>
694 );
695 },
696 };
697
698 window.givewp.gateways.register(payPalCommerceGateway);
699 })();
700